# # patch "constants.cc" # from [eabc0d5838a67e3e4d351ce3d28eac0387f773ae] # to [2258f6165b6db8f8c952f97aaacf0addbbed060f] # # patch "constants.hh" # from [4a4494bcaa97ec07c147fc066564519bb4d13481] # to [a4741c95ae4faeb5db1f36f6ccf406f732250241] # # patch "keys.cc" # from [a4ccef56d24ee9cda7a009144f5476e3cdcb07d7] # to [9c50dc0d669ffb6ad2328c46485bf3ae65dad4b5] # # patch "keys.hh" # from [f10aaea8dbaf4005a55ec388f278d1bb81d664af] # to [ec2da9cbde80341456204b6e0e0b4f727388ed8b] # # patch "netcmd.cc" # from [68e9e475c2423fded015699b6dcaa8742326ab6e] # to [5ee6262d8ddead8fe0c14e4c32c0d0d6781ee5fe] # # patch "netcmd.hh" # from [5626696805eab24ff5bd23f114a749b3aa78645a] # to [e5ff1935d62b436ff4a8d3e63adc492146ff4a90] # # patch "netsync.cc" # from [a473cc63cf2d6086fa68ef5b309ead418bfb4661] # to [4e9c31748b5a75e9535937786862574606fab923] # # patch "vocab.cc" # from [6e07baffa263c80f013d474ad362c3b52c162201] # to [0a568b1d7a988e146671055888136fdff7818916] # # patch "vocab_terms.hh" # from [c12dd87ed775d3e2d8713a393cc055ce68e5ae16] # to [fe3bf7a7700bdde82211ba0be39caf248025e00b] # --- constants.cc +++ constants.cc @@ -160,5 +160,9 @@ 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 + size_t const netsync_hmac_value_length_in_bytes = 20; // 160 bits + std::string const & netsync_key_initializer = std::string(netsync_session_key_length_in_bytes, 0); + } --- constants.hh +++ constants.hh @@ -7,6 +7,7 @@ // see the file COPYING for details #include +#include #include "numeric_vocab.hh" namespace constants @@ -120,6 +121,15 @@ // 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; + + // netsync HMAC value length + extern size_t const netsync_hmac_value_length_in_bytes; + + // netsync session key and HMAC key default initializer + extern std::string const & netsync_key_initializer; + } #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" @@ -66,17 +68,34 @@ } void -netcmd::write(string & out) const +netcmd::write(string & out, netsync_session_key const & key, + netsync_hmac_value & hmac_val) const { + size_t oldlen = out.size(); 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); + + I(key().size() == CryptoPP::SHA::DIGESTSIZE); + I(key().size() == hmac_val().size()); + byte keybuf[CryptoPP::SHA::DIGESTSIZE]; + for (size_t i = 0; i < sizeof(keybuf); i++) + { + keybuf[i] = key()[i] ^ hmac_val()[i]; + } + CryptoPP::HMAC hmac(keybuf, sizeof(keybuf)); + char digest[CryptoPP::SHA::DIGESTSIZE]; + hmac.CalculateDigest(reinterpret_cast(digest), + reinterpret_cast(out.data() + oldlen), + out.size() - oldlen); + string digest_str(digest, sizeof(digest)); + hmac_val = netsync_hmac_value(digest_str); + out.append(digest_str); } bool -netcmd::read(string & inbuf) +netcmd::read(string & inbuf, netsync_session_key const & key, + netsync_hmac_value & hmac_val) { size_t pos = 0; @@ -124,28 +143,43 @@ 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 (inbuf.size() < pos + payload_len + CryptoPP::SHA::DIGESTSIZE) { - inbuf.reserve(pos + payload_len + sizeof(u32) + constants::bufsz); + 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. require_bytes(inbuf, pos, payload_len, "netcmd payload"); - inbuf.erase(0, pos); - payload = inbuf.substr(payload_len); - inbuf.erase(payload_len, inbuf.npos); + payload = inbuf.substr(pos + payload_len); + inbuf.erase(pos + payload_len, inbuf.npos); inbuf.swap(payload); + size_t payload_pos = pos; pos = 0; // they might have given us bogus data - u32 checksum = extract_datum_lsb(inbuf, pos, "netcmd checksum"); + string cmd_digest = extract_substring(inbuf, pos, CryptoPP::SHA::DIGESTSIZE, + "netcmd HMAC"); 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()); + I(key().size() == CryptoPP::SHA::DIGESTSIZE); + I(key().size() == hmac_val().size()); + byte keybuf[CryptoPP::SHA::DIGESTSIZE]; + for (size_t i = 0; i < sizeof(keybuf); i++) + { + keybuf[i] = key()[i] ^ hmac_val()[i]; + } + CryptoPP::HMAC hmac(keybuf, sizeof(keybuf)); + char digest_buf[CryptoPP::SHA::DIGESTSIZE]; + 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)); + hmac_val = netsync_hmac_value(digest); + payload.erase(0, payload_pos); return true; } @@ -206,37 +240,37 @@ } -void -netcmd::read_anonymous_cmd(protocol_role & role, +void +netcmd::read_anonymous_cmd(protocol_role & role, std::string & pattern, - id & nonce2) const + rsa_oaep_sha_data & hmac_key_encrypted) const { size_t pos = 0; - // syntax is: - u8 role_byte = extract_datum_lsb(payload, pos, "anonymous netcmd, role"); + // 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 netcmd, pattern"); - nonce2 = id(extract_substring(payload, pos, - constants::merkle_hash_length_in_bytes, - "anonymous netcmd, nonce2")); - assert_end_of_buffer(payload, pos, "anonymous netcmd payload"); + "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, +void +netcmd::write_anonymous_cmd(protocol_role role, std::string const & pattern, - id const & nonce2) + rsa_oaep_sha_data const & hmac_key_encrypted) { cmd_code = anonymous_cmd; - I(nonce2().size() == constants::merkle_hash_length_in_bytes); payload = static_cast(role); insert_variable_length_string(pattern, payload); - payload += nonce2(); + insert_variable_length_string(hmac_key_encrypted(), payload); } void @@ -244,74 +278,68 @@ string & pattern, id & client, id & nonce1, - id & nonce2, + 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 netcmd, pattern"); + extract_variable_length_string(payload, pattern, pos, "auth(hmac) netcmd, pattern"); client = id(extract_substring(payload, pos, constants::merkle_hash_length_in_bytes, - "auth netcmd, client identifier")); + "auth(hmac) netcmd, client identifier")); nonce1 = id(extract_substring(payload, pos, constants::merkle_hash_length_in_bytes, - "auth netcmd, nonce1")); - nonce2 = id(extract_substring(payload, pos, - constants::merkle_hash_length_in_bytes, - "auth netcmd, nonce2")); + "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 netcmd, signature"); - assert_end_of_buffer(payload, pos, "auth netcmd payload"); + "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, +void +netcmd::write_auth_cmd(protocol_role role, + string const & pattern, id const & client, - id const & nonce1, - id const & nonce2, + 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); - I(nonce2().size() == constants::merkle_hash_length_in_bytes); payload = static_cast(role); insert_variable_length_string(pattern, payload); payload += client(); payload += nonce1(); - payload += nonce2(); + insert_variable_length_string(hmac_key_encrypted(), payload); insert_variable_length_string(signature, payload); } - -void -netcmd::read_confirm_cmd(string & signature) const +void +netcmd::read_confirm_cmd() const { size_t pos = 0; - - // syntax is: - extract_variable_length_string(payload, signature, pos, - "confirm netcmd, signature"); assert_end_of_buffer(payload, pos, "confirm netcmd payload"); } -void -netcmd::write_confirm_cmd(string const & signature) +void +netcmd::write_confirm_cmd() { cmd_code = confirm_cmd; payload.clear(); - insert_variable_length_string(signature, payload); } - + void netcmd::read_refine_cmd(merkle_node & node) const { --- netcmd.hh +++ netcmd.hh @@ -62,8 +62,12 @@ // 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(constants::netsync_key_initializer), + netsync_hmac_value & hmac_val = netsync_hmac_value(constants::netsync_key_initializer)) const; + bool read(std::string & inbuf, + netsync_session_key const & key = netsync_session_key(constants::netsync_key_initializer), + netsync_hmac_value & hmac_val = netsync_hmac_value(constants::netsync_key_initializer)); // i/o functions for each type of command payload void read_error_cmd(std::string & errmsg) const; @@ -79,28 +83,28 @@ rsa_pub_key const & server_key, id const & nonce); - void read_anonymous_cmd(protocol_role & role, + void read_anonymous_cmd(protocol_role & role, std::string & pattern, - id & nonce2) const; + rsa_oaep_sha_data & hmac_key_encrypted) const; void write_anonymous_cmd(protocol_role role, std::string const & pattern, - id const & nonce2); + rsa_oaep_sha_data const & hmac_key_encrypted); void read_auth_cmd(protocol_role & role, std::string & pattern, id & client, id & nonce1, - id & nonce2, + 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, + rsa_oaep_sha_data const & hmac_key_encrypted, std::string const & signature); - void read_confirm_cmd(std::string & signature) const; - void write_confirm_cmd(std::string const & signature); + void read_confirm_cmd() const; + void write_confirm_cmd(); 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,9 @@ boost::regex pattern_re; id remote_peer_key_hash; rsa_keypair_id remote_peer_key_name; + netsync_session_key session_key; + netsync_hmac_value read_hmac; + netsync_hmac_value write_hmac; bool authenticated; time_t last_io_time; @@ -320,14 +366,16 @@ 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); - void queue_confirm_cmd(string const & signature); + string const & signature, + base64 server_key_encoded); + void queue_confirm_cmd(); void queue_refine_cmd(merkle_node const & node); void queue_send_data_cmd(netcmd_item_type type, id const & item); @@ -351,15 +399,15 @@ 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(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); @@ -437,6 +485,9 @@ pattern_re(".*"), remote_peer_key_hash(""), remote_peer_key_name(""), + session_key(constants::netsync_key_initializer), + read_hmac(constants::netsync_key_initializer), + write_hmac(constants::netsync_key_initializer), authenticated(false), last_io_time(::time(NULL)), byte_in_ticker(NULL), @@ -727,7 +778,7 @@ session::write_netcmd_and_try_flush(netcmd const & cmd) { if (!encountered_error) - cmd.write(outbuf); + cmd.write(outbuf, session_key, write_hmac); 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 @@ -1363,11 +1414,16 @@ void session::queue_anonymous_cmd(protocol_role role, string const & pattern, - id const & nonce2) + id const & nonce2, + base64 server_key_encoded) { netcmd cmd; - cmd.write_anonymous_cmd(role, pattern, nonce2); + 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(role, pattern, hmac_key_encrypted); write_netcmd_and_try_flush(cmd); + session_key = netsync_session_key(nonce2()); } void @@ -1376,18 +1432,23 @@ id const & client, id const & nonce1, id const & nonce2, - string const & signature) + string const & signature, + base64 server_key_encoded) { netcmd cmd; - cmd.write_auth_cmd(role, pattern, client, nonce1, nonce2, signature); + 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(role, pattern, client, nonce1, hmac_key_encrypted, signature); write_netcmd_and_try_flush(cmd); + session_key = netsync_session_key(nonce2()); } -void -session::queue_confirm_cmd(string const & signature) +void +session::queue_confirm_cmd() { netcmd cmd; - cmd.write_confirm_cmd(signature); + cmd.write_confirm_cmd(); write_netcmd_and_try_flush(cmd); } @@ -1727,11 +1788,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; } @@ -1749,18 +1811,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". @@ -1821,15 +1873,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; @@ -1837,20 +1880,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; @@ -1864,12 +1903,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)) { @@ -1975,13 +2008,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->pattern_re = boost::regex(this->pattern()); this->authenticated = true; @@ -2010,6 +2036,18 @@ return false; } +void +session::respond_to_auth_cmd(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(); +} + bool session::process_confirm_cmd(string const & signature) { @@ -2036,20 +2074,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 @@ -2064,6 +2088,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, @@ -2969,9 +3010,17 @@ { protocol_role role; string pattern; - id nonce2; - cmd.read_anonymous_cmd(role, pattern, nonce2); - return process_anonymous_cmd(role, pattern, nonce2); + rsa_oaep_sha_data hmac_key_encrypted; + cmd.read_anonymous_cmd(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_key_encrypted); + return true; } break; @@ -2982,9 +3031,25 @@ protocol_role role; string pattern, signature; id client, nonce1, nonce2; - cmd.read_auth_cmd(role, pattern, client, nonce1, nonce2, signature); - return process_auth_cmd(role, pattern, client, - nonce1, nonce2, signature); + rsa_oaep_sha_data hmac_key_encrypted; + cmd.read_auth_cmd(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_key_encrypted); + return true; } break; @@ -2993,8 +3058,10 @@ require(voice == client_voice, "confirm netcmd received in client voice"); { string signature; - cmd.read_confirm_cmd(signature); - return process_confirm_cmd(signature); + cmd.read_confirm_cmd(); + this->authenticated = true; + respond_to_confirm_cmd(); + return true; } break; @@ -3116,7 +3183,7 @@ { if (!armed) { - if (cmd.read(inbuf)) + if (cmd.read(inbuf, session_key, read_hmac)) { // inbuf.erase(0, cmd.encoded_size()); armed = true; --- vocab.cc +++ vocab.cc @@ -102,7 +102,43 @@ val.ok = true; } +inline void +verify(netsync_session_key & val) +{ + if (val.ok) + return; + if (val().size() == 0) + { + val.s.append(constants::netsync_session_key_length_in_bytes, 0); + return; + } + + N(val().size() == constants::netsync_session_key_length_in_bytes, + F("Invalid key length of %d bytes") % val().length()); + + val.ok = true; +} + +inline void +verify(netsync_hmac_value & val) +{ + if (val.ok) + return; + + if (val().size() == 0) + { + val.s.append(constants::netsync_hmac_value_length_in_bytes, 0); + return; + } + + N(val().size() == constants::netsync_hmac_value_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,11 @@ 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 +ATOMIC(netsync_hmac_value); // 160-bit SHA-1 HMAC + DECORATE(revision); // thing associated with a revision DECORATE(manifest); // thing associated with a manifest DECORATE(file); // thing associated with a file