# # patch "constants.cc" # from [eabc0d5838a67e3e4d351ce3d28eac0387f773ae] # to [18838196cb64a85337deb2184c7e42c865133868] # # patch "constants.hh" # from [4a4494bcaa97ec07c147fc066564519bb4d13481] # to [165716b9b2fee13374d0e18d3c4daf940475eb9e] # # patch "keys.cc" # from [a4ccef56d24ee9cda7a009144f5476e3cdcb07d7] # to [9c50dc0d669ffb6ad2328c46485bf3ae65dad4b5] # # patch "keys.hh" # from [f10aaea8dbaf4005a55ec388f278d1bb81d664af] # to [ec2da9cbde80341456204b6e0e0b4f727388ed8b] # # patch "netcmd.cc" # from [e04a41fed951a77dcef389e94cc26d04ae12220f] # to [b73006b58989e65f71da9075eff10512dec968ce] # # patch "netcmd.hh" # from [5626696805eab24ff5bd23f114a749b3aa78645a] # to [bd3f114a0a69c85257ba9692556ae158160d0323] # # patch "netsync.cc" # from [5d33f00ec5c8e80c76ace7d2cbeb494c8a99424a] # to [10e7570b6c2155fa1c88ef167e3fb8e692a4308c] # # patch "vocab.cc" # from [6e07baffa263c80f013d474ad362c3b52c162201] # to [b63535f4a650ae9ec89080324f4c8eac4fdf5402] # # patch "vocab_terms.hh" # from [c12dd87ed775d3e2d8713a393cc055ce68e5ae16] # to [cf97b245c5684ff606ffbd6fff05032c2893de24] # --- constants.cc +++ constants.cc @@ -160,5 +160,6 @@ size_t const netsync_default_port = 5253; size_t const netsync_connection_limit = 1024; size_t const netsync_timeout_seconds = 21600; // 6 hours + size_t const netsync_session_key_length_in_bytes = 20; // 160 bits } --- constants.hh +++ constants.hh @@ -120,6 +120,9 @@ // number of seconds a connection can be idle before it's dropped extern size_t const netsync_timeout_seconds; + // netsync HMAC key length + extern size_t const netsync_session_key_length_in_bytes; + } #endif // __CONSTANTS_HH__ --- keys.cc +++ keys.cc @@ -166,7 +166,21 @@ der_encoded[i] = '\0'; } +static bool +blocking_rng(lua_hooks & lua) +{ + if (!lua.hook_non_blocking_rng_ok()) + { +#ifndef BLOCKING_RNG_AVAILABLE + throw oops("no blocking RNG available and non-blocking RNG rejected"); +#else + return true; +#endif + }; + return false; +} + void generate_key_pair(lua_hooks & lua, // to hook for phrase rsa_keypair_id const & id, // to prompting user for phrase @@ -176,17 +190,7 @@ { // we will panic here if the user doesn't like urandom and we can't give // them a real entropy-driven random. - bool request_blocking_rng = false; - if (!lua.hook_non_blocking_rng_ok()) - { -#ifndef BLOCKING_RNG_AVAILABLE - throw oops("no blocking RNG available and non-blocking RNG rejected"); -#else - request_blocking_rng = true; -#endif - } - - AutoSeededRandomPool rng(request_blocking_rng); + AutoSeededRandomPool rng(blocking_rng(lua)); SecByteBlock phrase, pubkey, privkey; rsa_pub_key raw_pub_key; arc4 raw_priv_key; @@ -267,16 +271,7 @@ // we will panic here if the user doesn't like urandom and we can't give // them a real entropy-driven random. - bool request_blocking_rng = false; - if (!lua.hook_non_blocking_rng_ok()) - { -#ifndef BLOCKING_RNG_AVAILABLE - throw oops("no blocking RNG available and non-blocking RNG rejected"); -#else - request_blocking_rng = true; -#endif - } - AutoSeededRandomPool rng(request_blocking_rng); + AutoSeededRandomPool rng(blocking_rng(lua)); // we permit the user to relax security here, by caching a decrypted key // (if they permit it) through the life of a program run. this helps when @@ -396,6 +391,75 @@ return vf->GetLastResult(); } +void encrypt_rsa(lua_hooks & lua, + rsa_keypair_id const & id, + base64 & pub_encoded, + std::string const & plaintext, + rsa_oaep_sha_data & ciphertext) +{ + AutoSeededRandomPool rng(blocking_rng(lua)); + + rsa_pub_key pub; + decode_base64(pub_encoded, pub); + SecByteBlock pub_block; + pub_block.Assign(reinterpret_cast(pub().data()), pub().size()); + StringSource keysource(pub_block.data(), pub_block.size(), true); + + shared_ptr encryptor; + encryptor = shared_ptr + (new RSAES_OAEP_SHA_Encryptor(keysource)); + + string ciphertext_string; + StringSource tmp(plaintext, true, + encryptor->CreateEncryptionFilter + (rng, new StringSink(ciphertext_string))); + + ciphertext = rsa_oaep_sha_data(ciphertext_string); +} + +void decrypt_rsa(lua_hooks & lua, + rsa_keypair_id const & id, + base64< arc4 > const & priv, + rsa_oaep_sha_data const & ciphertext, + std::string & plaintext) +{ + AutoSeededRandomPool rng(blocking_rng(lua)); + arc4 decoded_key; + SecByteBlock decrypted_key; + SecByteBlock phrase; + shared_ptr decryptor; + + for (int i = 0; i < 3; i++) + { + bool force = false; + decode_base64(priv, decoded_key); + decrypted_key.Assign(reinterpret_cast(decoded_key().data()), + decoded_key().size()); + get_passphrase(lua, id, phrase, false, force); + + try + { + do_arc4(phrase, decrypted_key); + StringSource keysource(decrypted_key.data(), decrypted_key.size(), true); + decryptor = shared_ptr + (new RSAES_OAEP_SHA_Decryptor(keysource)); + } + catch (...) + { + if (i >= 2) + throw informative_failure("failed to decrypt private RSA key, " + "probably incorrect passphrase"); + // don't use the cache bad one next time + force = true; + continue; + } + } + + StringSource tmp(ciphertext(), true, + decryptor->CreateDecryptionFilter + (rng, new StringSink(plaintext))); +} + void read_pubkey(string const & in, rsa_keypair_id & id, --- keys.hh +++ keys.hh @@ -42,6 +42,18 @@ void require_password(rsa_keypair_id const & id, app_state & app); +void encrypt_rsa(lua_hooks & lua, + rsa_keypair_id const & id, + base64 & pub, + std::string const & plaintext, + rsa_oaep_sha_data & ciphertext); + +void decrypt_rsa(lua_hooks & lua, + rsa_keypair_id const & id, + base64< arc4 > const & priv, + rsa_oaep_sha_data const & ciphertext, + std::string & plaintext); + // netsync stuff void read_pubkey(std::string const & in, --- netcmd.cc +++ netcmd.cc @@ -8,6 +8,8 @@ #include #include "cryptopp/gzip.h" +#include "cryptopp/hmac.h" +#include "cryptopp/sha.h" #include "adler32.hh" #include "constants.hh" @@ -69,13 +71,26 @@ } void -netcmd::write(string & out) const +netcmd::write(string & out, netsync_session_key const & key) const { out += static_cast(version); out += static_cast(cmd_code); insert_variable_length_string(payload, out); - adler32 check(reinterpret_cast(payload.data()), payload.size()); - insert_datum_lsb(check.sum(), out); + if (version < 5) + { + adler32 check(reinterpret_cast(payload.data()), payload.size()); + insert_datum_lsb(check.sum(), out); + } + else + { + CryptoPP::HMAC hmac(reinterpret_cast(key().data()), + key().length()); + char digest[CryptoPP::SHA::DIGESTSIZE]; + hmac.CalculateDigest(reinterpret_cast(digest), + reinterpret_cast(payload.data()), + payload.size()); + out.append(digest, sizeof(digest)); + } } // last should be zero (doesn't mean we're compatible with version 0). @@ -95,7 +110,7 @@ } bool -netcmd::read(string & inbuf) +netcmd::read(string & inbuf, netsync_session_key const & key) { size_t pos = 0; @@ -156,11 +171,22 @@ throw bad_decode(F("oversized payload of '%d' bytes") % payload_len); // there might not be enough data yet in the input buffer - if (inbuf.size() < pos + payload_len + sizeof(u32)) + if (version < 5) { - inbuf.reserve(pos + payload_len + sizeof(u32) + constants::bufsz); - return false; + if (inbuf.size() < pos + payload_len + sizeof(u32)) + { + inbuf.reserve(pos + payload_len + sizeof(u32) + constants::bufsz); + return false; + } } + else + { + if (inbuf.size() < pos + payload_len + CryptoPP::SHA::DIGESTSIZE) + { + inbuf.reserve(pos + payload_len + CryptoPP::SHA::DIGESTSIZE + constants::bufsz); + return false; + } + } // out.payload = extract_substring(inbuf, pos, payload_len, "netcmd payload"); // Do this ourselves, so we can swap the strings instead of copying. @@ -172,12 +198,31 @@ pos = 0; // they might have given us bogus data - u32 checksum = extract_datum_lsb(inbuf, pos, "netcmd checksum"); - inbuf.erase(0, pos); - adler32 check(reinterpret_cast(payload.data()), - payload.size()); - if (checksum != check.sum()) - throw bad_decode(F("bad checksum 0x%x vs. 0x%x") % checksum % check.sum()); + if (version < 5) + { + u32 checksum = extract_datum_lsb(inbuf, pos, "netcmd checksum"); + inbuf.erase(0, pos); + adler32 check(reinterpret_cast(payload.data()), + payload.size()); + if (checksum != check.sum()) + throw bad_decode(F("bad checksum 0x%x vs. 0x%x") % checksum % check.sum()); + } + else + { + string cmd_digest = extract_substring(inbuf, pos, CryptoPP::SHA::DIGESTSIZE, + "netcmd HMAC"); + inbuf.erase(0, pos); + char digest_buf[CryptoPP::SHA::DIGESTSIZE]; + CryptoPP::HMAC hmac(reinterpret_cast(key().data()), + key().length()); + hmac.CalculateDigest(reinterpret_cast(digest_buf), + reinterpret_cast(payload.data()), + payload.size()); + string digest(digest_buf, sizeof(digest_buf)); + if (cmd_digest != digest) + throw bad_decode(F("bad HMAC %s vs. %s") % encode_hexenc(cmd_digest) + % encode_hexenc(digest)); + } return true; } @@ -259,6 +304,28 @@ assert_end_of_buffer(payload, pos, "anonymous netcmd payload"); } +void +netcmd::read_anonymous_cmd_hmac(protocol_role & role, + std::string & pattern, + rsa_oaep_sha_data & hmac_key_encrypted) const +{ + size_t pos = 0; + // syntax is: + u8 role_byte = extract_datum_lsb(payload, pos, "anonymous(hmac) netcmd, role"); + if (role_byte != static_cast(source_role) + && role_byte != static_cast(sink_role) + && role_byte != static_cast(source_and_sink_role)) + throw bad_decode(F("unknown role specifier %d") % widen(role_byte)); + role = static_cast(role_byte); + extract_variable_length_string(payload, pattern, pos, + "anonymous(hmac) netcmd, pattern"); + string hmac_key_string; + extract_variable_length_string(payload, hmac_key_string, pos, + "anonymous(hmac) netcmd, hmac_key_encrypted"); + hmac_key_encrypted = rsa_oaep_sha_data(hmac_key_string); + assert_end_of_buffer(payload, pos, "anonymous(hmac) netcmd payload"); +} + void netcmd::write_anonymous_cmd(protocol_role role, std::string const & pattern, @@ -271,6 +338,17 @@ payload += nonce2(); } +void +netcmd::write_anonymous_cmd_hmac(protocol_role role, + std::string const & pattern, + rsa_oaep_sha_data const & hmac_key_encrypted) +{ + cmd_code = anonymous_cmd; + payload = static_cast(role); + insert_variable_length_string(pattern, payload); + insert_variable_length_string(hmac_key_encrypted(), payload); +} + void netcmd::read_auth_cmd(protocol_role & role, string & pattern, @@ -305,6 +383,40 @@ } void +netcmd::read_auth_cmd_hmac(protocol_role & role, + string & pattern, + id & client, + id & nonce1, + rsa_oaep_sha_data & hmac_key_encrypted, + string & signature) const +{ + size_t pos = 0; + // syntax is: + // + // + u8 role_byte = extract_datum_lsb(payload, pos, "auth netcmd, role"); + if (role_byte != static_cast(source_role) + && role_byte != static_cast(sink_role) + && role_byte != static_cast(source_and_sink_role)) + throw bad_decode(F("unknown role specifier %d") % widen(role_byte)); + role = static_cast(role_byte); + extract_variable_length_string(payload, pattern, pos, "auth(hmac) netcmd, pattern"); + client = id(extract_substring(payload, pos, + constants::merkle_hash_length_in_bytes, + "auth(hmac) netcmd, client identifier")); + nonce1 = id(extract_substring(payload, pos, + constants::merkle_hash_length_in_bytes, + "auth(hmac) netcmd, nonce1")); + string hmac_key; + extract_variable_length_string(payload, hmac_key, pos, + "auth(hmac) netcmd, hmac_key_encrypted"); + hmac_key_encrypted = rsa_oaep_sha_data(hmac_key); + extract_variable_length_string(payload, signature, pos, + "auth(hmac) netcmd, signature"); + assert_end_of_buffer(payload, pos, "auth(hmac) netcmd payload"); +} + +void netcmd::write_auth_cmd(protocol_role role, string const & pattern, id const & client, @@ -325,6 +437,25 @@ } +void +netcmd::write_auth_cmd_hmac(protocol_role role, + string const & pattern, + id const & client, + id const & nonce1, + rsa_oaep_sha_data const & hmac_key_encrypted, + string const & signature) +{ + cmd_code = auth_cmd; + I(client().size() == constants::merkle_hash_length_in_bytes); + I(nonce1().size() == constants::merkle_hash_length_in_bytes); + payload = static_cast(role); + insert_variable_length_string(pattern, payload); + payload += client(); + payload += nonce1(); + insert_variable_length_string(hmac_key_encrypted(), payload); + insert_variable_length_string(signature, payload); +} + void netcmd::read_confirm_cmd(string & signature) const { @@ -335,6 +466,13 @@ "confirm netcmd, signature"); assert_end_of_buffer(payload, pos, "confirm netcmd payload"); } + +void +netcmd::read_confirm_cmd_hmac() const +{ + size_t pos = 0; + assert_end_of_buffer(payload, pos, "confirm netcmd payload"); +} void netcmd::write_confirm_cmd(string const & signature) @@ -343,7 +481,14 @@ payload.clear(); insert_variable_length_string(signature, payload); } - + +void +netcmd::write_confirm_cmd_hmac() +{ + cmd_code = confirm_cmd; + payload.clear(); +} + void netcmd::read_refine_cmd(merkle_node & node) const { --- netcmd.hh +++ netcmd.hh @@ -62,8 +62,8 @@ // basic cmd i/o (including checksums) - void write(std::string & out) const; - bool read(std::string & inbuf); + void write(std::string & out, netsync_session_key const & key = netsync_session_key()) const; + bool read(std::string & inbuf, netsync_session_key const & key = netsync_session_key()); // i/o functions for each type of command payload void read_error_cmd(std::string & errmsg) const; @@ -82,9 +82,15 @@ void read_anonymous_cmd(protocol_role & role, std::string & pattern, id & nonce2) const; + void read_anonymous_cmd_hmac(protocol_role & role, + std::string & pattern, + rsa_oaep_sha_data & hmac_key_encrypted) const; void write_anonymous_cmd(protocol_role role, std::string const & pattern, id const & nonce2); + void write_anonymous_cmd_hmac(protocol_role role, + std::string const & pattern, + rsa_oaep_sha_data const & hmac_key_encrypted); void read_auth_cmd(protocol_role & role, std::string & pattern, @@ -92,15 +98,29 @@ id & nonce1, id & nonce2, std::string & signature) const; + void read_auth_cmd_hmac(protocol_role & role, + std::string & pattern, + id & client, + id & nonce1, + rsa_oaep_sha_data & hmac_key_encrypted, + std::string & signature) const; void write_auth_cmd(protocol_role role, std::string const & pattern, id const & client, id const & nonce1, id const & nonce2, std::string const & signature); + void write_auth_cmd_hmac(protocol_role role, + std::string const & pattern, + id const & client, + id const & nonce1, + rsa_oaep_sha_data const & hmac_key_encrypted, + std::string const & signature); void read_confirm_cmd(std::string & signature) const; + void read_confirm_cmd_hmac() const; void write_confirm_cmd(std::string const & signature); + void write_confirm_cmd_hmac(); void read_refine_cmd(merkle_node & node) const; void write_refine_cmd(merkle_node const & node); --- netsync.cc +++ netsync.cc @@ -112,18 +112,34 @@ // protocol // -------- // +// The protocol is a simple binary command-packet system over TCP; +// each packet consists of a single byte which identifies the protocol +// version, a byte which identifies the command name inside that +// version, a size_t sent as a uleb128 indicating the length of the +// packet, that many bytes of payload, and finally 20 bytes of SHA-1 +// HMAC calculated over the payload. The key for the SHA-1 HMAC is 20 +// bytes of 0 during authentication, and a 20-byte random key chosen +// by the client after authentication (discussed below). +// +//---- Pre-v5 packet format ---- +// // the protocol is a simple binary command-packet system over tcp; each // packet consists of a byte which identifies the protocol version, a byte // which identifies the command name inside that version, a size_t sent as // a uleb128 indicating the length of the packet, and then that many bytes // of payload, and finally 4 bytes of adler32 checksum (in LSB order) over -// the payload. decoding involves simply buffering until a sufficient -// number of bytes are received, then advancing the buffer pointer. any -// time an adler32 check fails, the protocol is assumed to have lost -// synchronization, and the connection is dropped. the parties are free to -// drop the tcp stream at any point, if too much data is received or too -// much idle time passes; no commitments or transactions are made. +// the payload. // +// ---- end pre-v5 packet format ---- +// +// decoding involves simply buffering until a sufficient number of +// bytes are received, then advancing the buffer pointer. any time an +// integrity check (adler32 for pre-v5, HMAC for post-v5) fails, the +// protocol is assumed to have lost synchronization, and the +// connection is dropped. the parties are free to drop the tcp stream +// at any point, if too much data is received or too much idle time +// passes; no commitments or transactions are made. +// // one special command, "bye", is used to shut down a connection // gracefully. once each side has received all the data they want, they // can send a "bye" command to the other side. as soon as either side has @@ -135,6 +151,31 @@ // "hello " command, which identifies the server's RSA key and // issues a nonce which must be used for a subsequent authentication. // +// The client then responds with either: +// +// An "auth (source|sink|both) +// " command, which identifies its RSA key, notes the role it +// wishes to play in the synchronization, identifies the pattern it +// wishes to sync with, signs the previous nonce with its own key, and +// informs the server of the HMAC key it wishes to use for this +// session (encrypted with the server's public key); or +// +// An "anonymous (source|sink|both) " command, +// which identifies the role it wishes to play in the synchronization, +// the pattern it ishes to sync with, and the HMAC key it wishes to +// use for this session (also encrypted with the server's public key). +// +// The server then replies with a "confirm" command, which contains no +// other data but will only have the correct HMAC integrity code if +// the server received and properly decrypted the HMAC key offered by +// the client. This transitions the peers into an authenticated state +// and begins refinement. +// +// ---- Pre-v5 authentication process notes ---- +// +// the exchange begins in a non-authenticated state. the server sends a +// "hello " command, which identifies the server's RSA key and +// issues a nonce which must be used for a subsequent authentication. // the client can then respond with an "auth (source|sink|both) // " command which identifies its // RSA key, notes the role it wishes to play in the synchronization, @@ -146,6 +187,8 @@ // the signature of the second nonce sent by the client. this // transitions the peers into an authenticated state and begins refinement. // +// ---- End pre-v5 authentication process ---- +// // refinement begins with the client sending its root public key and // manifest certificate merkle nodes to the server. the server then // compares the root to each slot in *its* root node, and for each slot @@ -228,6 +271,7 @@ utf8 pattern; id remote_peer_key_hash; rsa_keypair_id remote_peer_key_name; + netsync_session_key session_key; bool authenticated; time_t last_io_time; @@ -320,14 +364,17 @@ id const & nonce); void queue_anonymous_cmd(protocol_role role, string const & pattern, - id const & nonce2); + id const & nonce2, + base64 server_key_encoded); void queue_auth_cmd(protocol_role role, string const & pattern, id const & client, id const & nonce1, id const & nonce2, - string const & signature); + string const & signature, + base64 server_key_encoded); void queue_confirm_cmd(string const & signature); + void queue_confirm_cmd_hmac(); void queue_refine_cmd(merkle_node const & node); void queue_send_data_cmd(netcmd_item_type type, id const & item); @@ -351,15 +398,16 @@ rsa_pub_key const & server_key, id const & nonce); bool process_anonymous_cmd(protocol_role role, - string const & pattern, - id const & nonce2); + string const & pattern); bool process_auth_cmd(protocol_role role, string const & pattern, id const & client, id const & nonce1, - id const & nonce2, string const & signature); + void respond_to_auth_cmd(id const & nonce2); + void respond_to_auth_cmd_hmac(rsa_oaep_sha_data hmac_key_encrypted); bool process_confirm_cmd(string const & signature); + void respond_to_confirm_cmd(); bool process_refine_cmd(merkle_node const & node); bool process_send_data_cmd(netcmd_item_type type, id const & item); @@ -726,7 +774,7 @@ session::write_netcmd_and_try_flush(netcmd const & cmd) { if (!encountered_error) - cmd.write(outbuf); + cmd.write(outbuf, session_key); else L(F("dropping outgoing netcmd (because we're in error unwind mode)\n")); // FIXME: this helps keep the protocol pipeline full but it seems to @@ -1364,11 +1412,24 @@ void session::queue_anonymous_cmd(protocol_role role, string const & pattern, - id const & nonce2) + id const & nonce2, + base64 server_key_encoded) { netcmd cmd(protocol_version); - cmd.write_anonymous_cmd(role, pattern, nonce2); - write_netcmd_and_try_flush(cmd); + if (protocol_version < 5) + { + cmd.write_anonymous_cmd(role, pattern, nonce2); + write_netcmd_and_try_flush(cmd); + } + else + { + rsa_oaep_sha_data hmac_key_encrypted; + encrypt_rsa(app.lua, remote_peer_key_name, server_key_encoded, + nonce2(), hmac_key_encrypted); + cmd.write_anonymous_cmd_hmac(role, pattern, hmac_key_encrypted); + write_netcmd_and_try_flush(cmd); + session_key = netsync_session_key(nonce2()); + } } void @@ -1377,11 +1438,24 @@ id const & client, id const & nonce1, id const & nonce2, - string const & signature) + string const & signature, + base64 server_key_encoded) { netcmd cmd(protocol_version); - cmd.write_auth_cmd(role, pattern, client, nonce1, nonce2, signature); - write_netcmd_and_try_flush(cmd); + if (protocol_version < 5) + { + cmd.write_auth_cmd(role, pattern, client, nonce1, nonce2, signature); + write_netcmd_and_try_flush(cmd); + } + else + { + rsa_oaep_sha_data hmac_key_encrypted; + encrypt_rsa(app.lua, remote_peer_key_name, server_key_encoded, + nonce2(), hmac_key_encrypted); + cmd.write_auth_cmd_hmac(role, pattern, client, nonce1, hmac_key_encrypted, signature); + write_netcmd_and_try_flush(cmd); + session_key = netsync_session_key(nonce2()); + } } void @@ -1392,6 +1466,14 @@ write_netcmd_and_try_flush(cmd); } +void +session::queue_confirm_cmd_hmac() +{ + netcmd cmd(protocol_version); + cmd.write_confirm_cmd_hmac(); + write_netcmd_and_try_flush(cmd); +} + void session::queue_refine_cmd(merkle_node const & node) { @@ -1751,11 +1833,12 @@ // make a new nonce of our own and send off the 'auth' queue_auth_cmd(this->role, this->pattern(), our_key_hash_raw, - nonce, mk_nonce(), sig_raw()); + nonce, mk_nonce(), sig_raw(), their_key_encoded); } else { - queue_anonymous_cmd(this->role, this->pattern(), mk_nonce()); + queue_anonymous_cmd(this->role, this->pattern(), + mk_nonce(), their_key_encoded); } return true; } @@ -1773,18 +1856,8 @@ bool session::process_anonymous_cmd(protocol_role role, - string const & pattern, - id const & nonce2) + string const & pattern) { - hexenc hnonce2; - encode_hexenc(nonce2, hnonce2); - - L(F("received 'anonymous' netcmd from client for pattern '%s' " - "in %s mode with nonce2 '%s'\n") - % pattern % (role == source_and_sink_role ? "source and sink" : - (role == source_role ? "source " : "sink")) - % hnonce2); - // // internally netsync thinks in terms of sources and sinks. users like // thinking of repositories as "readonly", "readwrite", or "writeonly". @@ -1845,15 +1918,6 @@ rebuild_merkle_trees(app, ok_branches); - // get our private key and sign back - L(F("anonymous read permitted, signing back nonce\n")); - base64 sig; - rsa_sha1_signature sig_raw; - base64< arc4 > our_priv; - load_priv_key(app, app.signing_key, our_priv); - make_signature(app.lua, app.signing_key, our_priv, nonce2(), sig); - decode_base64(sig, sig_raw); - queue_confirm_cmd(sig_raw()); this->pattern = pattern; this->remote_peer_key_name = rsa_keypair_id(""); this->authenticated = true; @@ -1861,20 +1925,16 @@ return true; } -bool -session::process_auth_cmd(protocol_role role, - string const & pattern, - id const & client, - id const & nonce1, - id const & nonce2, +bool +session::process_auth_cmd(protocol_role role, + string const & pattern, + id const & client, + id const & nonce1, string const & signature) { I(this->remote_peer_key_hash().size() == 0); I(this->saved_nonce().size() == constants::merkle_hash_length_in_bytes); - hexenc hnonce1, hnonce2; - encode_hexenc(nonce1, hnonce1); - encode_hexenc(nonce2, hnonce2); hexenc their_key_hash; encode_hexenc(client, their_key_hash); set ok_branches; @@ -1888,12 +1948,6 @@ } boost::regex reg(pattern); - L(F("received 'auth' netcmd from client '%s' for pattern '%s' " - "in %s mode with nonce1 '%s' and nonce2 '%s'\n") - % their_key_hash % pattern % (role == source_and_sink_role ? "source and sink" : - (role == source_role ? "source " : "sink")) - % hnonce1 % hnonce2); - // check that they replied with the nonce we asked for if (!(nonce1 == this->saved_nonce)) { @@ -1999,13 +2053,6 @@ { // get our private key and sign back L(F("client signature OK, accepting authentication\n")); - base64 sig; - rsa_sha1_signature sig_raw; - base64< arc4 > our_priv; - load_priv_key(app, app.signing_key, our_priv); - make_signature(app.lua, app.signing_key, our_priv, nonce2(), sig); - decode_base64(sig, sig_raw); - queue_confirm_cmd(sig_raw()); this->pattern = pattern; this->authenticated = true; this->remote_peer_key_name = their_id; @@ -2033,6 +2080,31 @@ return false; } +void +session::respond_to_auth_cmd(id const & nonce2) +{ + L(F("Writing netsync v4 or older confirm command")); + base64 sig; + rsa_sha1_signature sig_raw; + base64< arc4 > our_priv; + load_priv_key(app, app.signing_key, our_priv); + make_signature(app.lua, app.signing_key, our_priv, nonce2(), sig); + decode_base64(sig, sig_raw); + queue_confirm_cmd(sig_raw()); +} + +void +session::respond_to_auth_cmd_hmac(rsa_oaep_sha_data hmac_key_encrypted) +{ + L(F("Writing HMAC confirm command")); + base64< arc4 > our_priv; + load_priv_key(app, app.signing_key, our_priv); + string hmac_key; + decrypt_rsa(app.lua, app.signing_key, our_priv, hmac_key_encrypted, hmac_key); + session_key = netsync_session_key(hmac_key); + queue_confirm_cmd_hmac(); +} + bool session::process_confirm_cmd(string const & signature) { @@ -2059,20 +2131,6 @@ if (check_signature(app.lua, their_id, their_key, this->saved_nonce(), sig)) { L(F("server signature OK, accepting authentication\n")); - this->authenticated = true; - - merkle_ptr root; - load_merkle_node(epoch_item, 0, get_root_prefix().val, root); - queue_refine_cmd(*root); - queue_done_cmd(0, epoch_item); - - load_merkle_node(key_item, 0, get_root_prefix().val, root); - queue_refine_cmd(*root); - queue_done_cmd(0, key_item); - - load_merkle_node(cert_item, 0, get_root_prefix().val, root); - queue_refine_cmd(*root); - queue_done_cmd(0, cert_item); return true; } else @@ -2087,6 +2145,23 @@ return false; } +void +session::respond_to_confirm_cmd() +{ + merkle_ptr root; + load_merkle_node(epoch_item, 0, get_root_prefix().val, root); + queue_refine_cmd(*root); + queue_done_cmd(0, epoch_item); + + load_merkle_node(key_item, 0, get_root_prefix().val, root); + queue_refine_cmd(*root); + queue_done_cmd(0, key_item); + + load_merkle_node(cert_item, 0, get_root_prefix().val, root); + queue_refine_cmd(*root); + queue_done_cmd(0, cert_item); +} + static bool data_exists(netcmd_item_type type, id const & item, @@ -2994,11 +3069,39 @@ { protocol_role role; string pattern; - id nonce2; - cmd.read_anonymous_cmd(role, pattern, nonce2); if (cmd.get_version() < protocol_version) protocol_version = cmd.get_version(); - return process_anonymous_cmd(role, pattern, nonce2); + if (protocol_version < 5) + { + id nonce2; + cmd.read_anonymous_cmd(role, pattern, nonce2); + hexenc hnonce2; + encode_hexenc(nonce2, hnonce2); + + L(F("received 'anonymous' netcmd from client for pattern '%s' " + "in %s mode with nonce2 '%s'\n") + % pattern % (role == source_and_sink_role ? "source and sink" : + (role == source_role ? "source " : "sink")) + % hnonce2); + + if (!process_anonymous_cmd(role, pattern)) + return false; + respond_to_auth_cmd(nonce2); + } + else + { + rsa_oaep_sha_data hmac_key_encrypted; + cmd.read_anonymous_cmd_hmac(role, pattern, hmac_key_encrypted); + L(F("received 'anonymous' netcmd from client for pattern '%s' " + "in %s mode\n") + % pattern % (role == source_and_sink_role ? "source and sink" : + (role == source_role ? "source " : "sink"))); + + if (!process_anonymous_cmd(role, pattern)) + return false; + respond_to_auth_cmd_hmac(hmac_key_encrypted); + } + return true; } break; @@ -3009,11 +3112,50 @@ protocol_role role; string pattern, signature; id client, nonce1, nonce2; - cmd.read_auth_cmd(role, pattern, client, nonce1, nonce2, signature); if (cmd.get_version() < protocol_version) protocol_version = cmd.get_version(); - return process_auth_cmd(role, pattern, client, - nonce1, nonce2, signature); + if (protocol_version < 5) + { + cmd.read_auth_cmd(role, pattern, client, nonce1, nonce2, signature); + + hexenc their_key_hash; + encode_hexenc(client, their_key_hash); + hexenc hnonce1, hnonce2; + encode_hexenc(nonce1, hnonce1); + encode_hexenc(nonce2, hnonce2); + + L(F("received 'auth' netcmd from client '%s' for pattern '%s' " + "in %s mode with nonce1 '%s' and nonce2 '%s'\n") + % their_key_hash % pattern % (role == source_and_sink_role ? "source and sink" : + (role == source_role ? "source " : "sink")) + % hnonce1 % hnonce2); + + if (!process_auth_cmd(role, pattern, client, nonce1, signature)) + return false; + respond_to_auth_cmd(nonce2); + } + else + { + rsa_oaep_sha_data hmac_key_encrypted; + cmd.read_auth_cmd_hmac(role, pattern, client, nonce1, + hmac_key_encrypted, signature); + + hexenc their_key_hash; + encode_hexenc(client, their_key_hash); + hexenc hnonce1; + encode_hexenc(nonce1, hnonce1); + + L(F("received 'auth(hmac)' netcmd from client '%s' for pattern '%s' " + "in %s mode with nonce1 '%s'\n") + % their_key_hash % pattern % (role == source_and_sink_role ? "source and sink" : + (role == source_role ? "source " : "sink")) + % hnonce1); + + if (!process_auth_cmd(role, pattern, client, nonce1, signature)) + return false; + respond_to_auth_cmd_hmac(hmac_key_encrypted); + } + return true; } break; @@ -3022,8 +3164,19 @@ require(voice == client_voice, "confirm netcmd received in client voice"); { string signature; - cmd.read_confirm_cmd(signature); - return process_confirm_cmd(signature); + if (protocol_version < 5) + { + cmd.read_confirm_cmd(signature); + if (!process_confirm_cmd(signature)) + return false; + } + else + { + cmd.read_confirm_cmd_hmac(); + } + this->authenticated = true; + respond_to_confirm_cmd(); + return true; } break; @@ -3145,7 +3298,7 @@ { if (!armed) { - if (cmd.read(inbuf)) + if (cmd.read(inbuf, session_key)) { // inbuf.erase(0, cmd.encoded_size()); armed = true; --- vocab.cc +++ vocab.cc @@ -102,7 +102,24 @@ val.ok = true; } +inline void +verify(netsync_session_key & val) +{ + if (val.ok) + return; + if (val().length() == 0) + { + val.s.append(constants::netsync_session_key_length_in_bytes, 0); + return; + } + + N(val().length() == constants::netsync_session_key_length_in_bytes, + F("Invalid key length of %d bytes") % val().length()); + + val.ok = true; +} + inline void verify(local_path & val) { --- vocab_terms.hh +++ vocab_terms.hh @@ -34,7 +34,10 @@ ATOMIC_NOVERIFY(rsa_pub_key); // some nice numbers ATOMIC_NOVERIFY(rsa_priv_key); // some nice numbers ATOMIC_NOVERIFY(rsa_sha1_signature); // some other nice numbers +ATOMIC_NOVERIFY(rsa_oaep_sha_data); +ATOMIC(netsync_session_key); // key for netsync session HMAC + DECORATE(revision); // thing associated with a revision DECORATE(manifest); // thing associated with a manifest DECORATE(file); // thing associated with a file