# # patch "netcmd.cc" # from [850ebd9fc752f43e1a386bbfa458ac445011f54b] # to [b73006b58989e65f71da9075eff10512dec968ce] # # patch "netcmd.hh" # from [cade7a00aa72deb9ad34b9047f26f04c4dbeecaa] # to [bd3f114a0a69c85257ba9692556ae158160d0323] # # patch "netsync.cc" # from [10aeefac2f35cdf860b60b2301c51d6da490c455] # to [6ac8baa7f1590808b738415ecd8770da32e23b71] # --- netcmd.cc +++ netcmd.cc @@ -304,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, @@ -316,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, @@ -350,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, @@ -370,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 { @@ -380,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) @@ -388,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 @@ -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 @@ -321,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); @@ -352,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); @@ -1365,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 @@ -1378,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 @@ -1393,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) { @@ -1752,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; } @@ -1774,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". @@ -1846,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; @@ -1862,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; @@ -1889,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)) { @@ -2000,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; @@ -2034,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) { @@ -2060,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 @@ -2088,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, @@ -2995,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; @@ -3010,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; @@ -3023,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;