diff --git a/.gitignore b/.gitignore index 33d31c037c..2bab082ec6 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,9 @@ build/ # VScode .vscode/ +# Tests via Zoff +.localrun/ + # Netbeans nbproject diff --git a/auto_tests/crypto_test.c b/auto_tests/crypto_test.c index 75d94ea1b2..ac6c595b82 100644 --- a/auto_tests/crypto_test.c +++ b/auto_tests/crypto_test.c @@ -6,6 +6,8 @@ #include "../toxcore/crypto_core.h" #include "../toxcore/net_crypto.h" #include "check_compat.h" +// TODO(goldroom): necessary to print bytes +// #include "../other/fun/create_common.h" static void rand_bytes(const Random *rng, uint8_t *b, size_t blen) { @@ -371,6 +373,324 @@ static void test_memzero(void) } } +/* Noise_IK_25519_ChaChaPoly_BLAKE2b test vectors from here: https://github.com/rweather/noise-c/blob/cfe25410979a87391bb9ac8d4d4bef64e9f268c6/tests/vector/noise-c-basic.txt */ +/* "init_prologue": "50726f6c6f677565313233" (same as `resp_prologue`) */ +static const uint8_t prologue[11] = { + 0x50, 0x72, 0x6f, 0x6c, 0x6f, 0x67, 0x75, 0x65, + 0x31, 0x32, 0x33 +}; + +/* Initiator static private key + "init_static": "e61ef9919cde45dd5f82166404bd08e38bceb5dfdfded0a34c8df7ed542214d1" */ +static const uint8_t init_static[CRYPTO_SECRET_KEY_SIZE] = { + 0xe6, 0x1e, 0xf9, 0x91, 0x9c, 0xde, 0x45, 0xdd, + 0x5f, 0x82, 0x16, 0x64, 0x04, 0xbd, 0x08, 0xe3, + 0x8b, 0xce, 0xb5, 0xdf, 0xdf, 0xde, 0xd0, 0xa3, + 0x4c, 0x8d, 0xf7, 0xed, 0x54, 0x22, 0x14, 0xd1 +}; + +/* Initiator ephemeral private key + "init_ephemeral": "893e28b9dc6ca8d611ab664754b8ceb7bac5117349a4439a6b0569da977c464a" */ +static const uint8_t init_ephemeral[CRYPTO_SECRET_KEY_SIZE] = { + 0x89, 0x3e, 0x28, 0xb9, 0xdc, 0x6c, 0xa8, 0xd6, + 0x11, 0xab, 0x66, 0x47, 0x54, 0xb8, 0xce, 0xb7, + 0xba, 0xc5, 0x11, 0x73, 0x49, 0xa4, 0x43, 0x9a, + 0x6b, 0x05, 0x69, 0xda, 0x97, 0x7c, 0x46, 0x4a +}; + +/* Responder static public key + "init_remote_static": "31e0303fd6418d2f8c0e78b91f22e8caed0fbe48656dcf4767e4834f701b8f62" */ +static const uint8_t init_remote_static[CRYPTO_PUBLIC_KEY_SIZE] = { + 0x31, 0xe0, 0x30, 0x3f, 0xd6, 0x41, 0x8d, 0x2f, + 0x8c, 0x0e, 0x78, 0xb9, 0x1f, 0x22, 0xe8, 0xca, + 0xed, 0x0f, 0xbe, 0x48, 0x65, 0x6d, 0xcf, 0x47, + 0x67, 0xe4, 0x83, 0x4f, 0x70, 0x1b, 0x8f, 0x62 +}; + +/* Responder static private key + "resp_static": "4a3acbfdb163dec651dfa3194dece676d437029c62a408b4c5ea9114246e4893" */ +static const uint8_t resp_static[CRYPTO_SECRET_KEY_SIZE] = { + 0x4a, 0x3a, 0xcb, 0xfd, 0xb1, 0x63, 0xde, 0xc6, + 0x51, 0xdf, 0xa3, 0x19, 0x4d, 0xec, 0xe6, 0x76, + 0xd4, 0x37, 0x02, 0x9c, 0x62, 0xa4, 0x08, 0xb4, + 0xc5, 0xea, 0x91, 0x14, 0x24, 0x6e, 0x48, 0x93 +}; + +/* Responder ephermal private key + "resp_ephemeral": "bbdb4cdbd309f1a1f2e1456967fe288cadd6f712d65dc7b7793d5e63da6b375b" */ +static const uint8_t resp_ephemeral[CRYPTO_SECRET_KEY_SIZE] = { + 0xbb, 0xdb, 0x4c, 0xdb, 0xd3, 0x09, 0xf1, 0xa1, + 0xf2, 0xe1, 0x45, 0x69, 0x67, 0xfe, 0x28, 0x8c, + 0xad, 0xd6, 0xf7, 0x12, 0xd6, 0x5d, 0xc7, 0xb7, + 0x79, 0x3d, 0x5e, 0x63, 0xda, 0x6b, 0x37, 0x5b +}; + +/* Payload for initiator handshake message + "payload": "4c756477696720766f6e204d69736573" */ +static const uint8_t init_payload_hs[16] = { + 0x4c, 0x75, 0x64, 0x77, 0x69, 0x67, 0x20, 0x76, + 0x6f,0x6e, 0x20, 0x4d, 0x69, 0x73, 0x65, 0x73 +}; +/* "ciphertext": is actually three values for initiator handshake message: + Initiator ephemeral public key in plaintext: ca35def5ae56cec33dc2036731ab14896bc4c75dbb07a61f879f8e3afa4c7944 + Encrypted static public key (of initiator): ba83a447b38c83e327ad936929812f624884847b7831e95e197b2f797088efdd232fe541af156ec6d0657602902a8c3e + Encrypted payload: e64e470f4b6fcd9298ce0b56fe20f86e60d9d933ec6e103ffb09e6001d6abb64*/ +static const uint8_t init_ephemeral_public[CRYPTO_PUBLIC_KEY_SIZE] = { + 0xca, 0x35, 0xde, 0xf5, 0xae, 0x56, 0xce, 0xc3, + 0x3d, 0xc2, 0x03, 0x67, 0x31, 0xab, 0x14, 0x89, + 0x6b, 0xc4, 0xc7, 0x5d, 0xbb, 0x07, 0xa6, 0x1f, + 0x87, 0x9f, 0x8e, 0x3a, 0xfa, 0x4c, 0x79, 0x44 +}; +static const uint8_t init_encrypted_static_public[CRYPTO_PUBLIC_KEY_SIZE+CRYPTO_MAC_SIZE] = { + 0xba, 0x83, 0xa4, 0x47, 0xb3, 0x8c, 0x83, 0xe3, + 0x27, 0xad, 0x93, 0x69, 0x29, 0x81, 0x2f, 0x62, + 0x48, 0x84, 0x84, 0x7b, 0x78, 0x31, 0xe9, 0x5e, + 0x19, 0x7b, 0x2f, 0x79, 0x70, 0x88, 0xef, 0xdd, + 0x23, 0x2f, 0xe5, 0x41, 0xaf, 0x15, 0x6e, 0xc6, + 0xd0, 0x65, 0x76, 0x02, 0x90, 0x2a, 0x8c, 0x3e +}; +static const uint8_t init_payload_hs_encrypted[sizeof(init_payload_hs)+CRYPTO_MAC_SIZE] = { + 0xe6, 0x4e, 0x47, 0x0f, 0x4b, 0x6f, 0xcd, 0x92, + 0x98, 0xce, 0x0b, 0x56, 0xfe, 0x20, 0xf8, 0x6e, + 0x60, 0xd9, 0xd9, 0x33, 0xec, 0x6e, 0x10, 0x3f, + 0xfb, 0x09, 0xe6, 0x00, 0x1d, 0x6a, 0xbb, 0x64 +}; + +/* Payload for responder handshake message + "payload": "4d757272617920526f746862617264", */ +static const uint8_t resp_payload_hs[15] = { + 0x4d, 0x75, 0x72, 0x72, 0x61, 0x79, 0x20, 0x52, + 0x6f, 0x74, 0x68, 0x62, 0x61, 0x72, 0x64 +}; +/* "ciphertext": is actually two values for responder handshake message: + Responder ephemeral public key in plaintext: + 95ebc60d2b1fa672c1f46a8aa265ef51bfe38e7ccb39ec5be34069f144808843 + Encrypted payload: + 9f069b267a06b3de3ecb1043bcb09807c6cd101f3826192a65f11ef3fe4317 */ +static const uint8_t resp_ephemeral_public[CRYPTO_PUBLIC_KEY_SIZE] = { + 0x95, 0xeb, 0xc6, 0x0d, 0x2b, 0x1f, 0xa6, 0x72, + 0xc1, 0xf4, 0x6a, 0x8a, 0xa2, 0x65, 0xef, 0x51, + 0xbf, 0xe3, 0x8e, 0x7c, 0xcb, 0x39, 0xec, 0x5b, + 0xe3, 0x40, 0x69, 0xf1, 0x44, 0x80, 0x88, 0x43 +}; +static const uint8_t resp_payload_hs_encrypted[sizeof(resp_payload_hs)+CRYPTO_MAC_SIZE] = { + 0x9f, 0x06, 0x9b, 0x26, 0x7a, 0x06, 0xb3, 0xde, + 0x3e, 0xcb, 0x10, 0x43, 0xbc, 0xb0, 0x98, 0x07, + 0xc6, 0xcd, 0x10, 0x1f, 0x38, 0x26, 0x19, 0x2a, + 0x65, 0xf1, 0x1e, 0xf3, 0xfe, 0x43, 0x17 +}; + +/* Payload for initiator transport message 1 + "payload": "462e20412e20486179656b" */ +static const uint8_t init_payload_transport1[11] = { + 0x46, 0x2e, 0x20, 0x41, 0x2e, 0x20, 0x48, 0x61, + 0x79, 0x65, 0x6b +}; +/* Payload ciphertext for initiator transport message 1 + "ciphertext": "cd54383060e7a28434cca27fb1cc524cfbabeb18181589df219d07" */ +static const uint8_t init_payload_transport1_encrypted[sizeof(init_payload_transport1)+CRYPTO_MAC_SIZE] = { + 0xcd, 0x54, 0x38, 0x30, 0x60, 0xe7, 0xa2, 0x84, + 0x34, 0xcc, 0xa2, 0x7f, 0xb1, 0xcc, 0x52, 0x4c, + 0xfb, 0xab, 0xeb, 0x18, 0x18, 0x15, 0x89, 0xdf, + 0x21, 0x9d, 0x07 +}; + +/* Payload for responder transport message 1 + "payload": "4361726c204d656e676572" */ +static const uint8_t resp_payload_transport1[11] = { + 0x43, 0x61, 0x72, 0x6c, 0x20, 0x4d, 0x65, 0x6e, + 0x67, 0x65, 0x72 +}; +/* Payload ciphertext for responder transport message 1 + "ciphertext": "a856d3bf0246bfc476c655009cd1ed677b8dcc5b349ae8ef2a05f2" */ +static const uint8_t resp_payload_transport1_encrypted[sizeof(resp_payload_transport1)+CRYPTO_MAC_SIZE] = { + 0xa8, 0x56, 0xd3, 0xbf, 0x02, 0x46, 0xbf, 0xc4, + 0x76, 0xc6, 0x55, 0x00, 0x9c, 0xd1, 0xed, 0x67, + 0x7b, 0x8d, 0xcc, 0x5b, 0x34, 0x9a, 0xe8, 0xef, + 0x2a, 0x05, 0xf2 +}; + +/* Final handshake hash value (MUST be the same for both initiator and responder) + "handshake_hash": "00e51d2aac81a9b8ebe441d6af3e1c8efc0f030cc608332edcb42588ff6a0ce26415ddc106e95277a5e6d54132f1e5245976b89caf96d262f1fe5a7f0c55c078" */ +static const uint8_t handshake_hash[CRYPTO_SHA512_SIZE] = { + 0x00, 0xe5, 0x1d, 0x2a, 0xac, 0x81, 0xa9, 0xb8, + 0xeb, 0xe4, 0x41, 0xd6, 0xaf, 0x3e, 0x1c, 0x8e, + 0xfc, 0x0f, 0x03, 0x0c, 0xc6, 0x08, 0x33, 0x2e, + 0xdc, 0xb4, 0x25, 0x88, 0xff, 0x6a, 0x0c, 0xe2, + 0x64, 0x15, 0xdd, 0xc1, 0x06, 0xe9, 0x52, 0x77, + 0xa5, 0xe6, 0xd5, 0x41, 0x32, 0xf1, 0xe5, 0x24, + 0x59, 0x76, 0xb8, 0x9c, 0xaf, 0x96, 0xd2, 0x62, + 0xf1, 0xfe, 0x5a, 0x7f, 0x0c, 0x55, 0xc0, 0x78 +}; + +/* TODO(goldroom): Currently unused */ +// TODO(goldroom): "payload": "4a65616e2d426170746973746520536179", +// TODO(goldroom): "ciphertext": "49063084b2c51f098337cb8a13739ac848f907e67cfb2cc8a8b60586467aa02fc7" + +// TODO(goldroom): "payload": "457567656e2042f6686d20766f6e2042617765726b", +// TODO(goldroom): "ciphertext": "8b9709d23b47e4639df7678d7a21741eba4ef1e9c60383001c7435549c20f9d56f30e935d3" + +static void test_noiseik(void) +{ + /* INITIATOR: Create handshake packet for responder */ + Noise_Handshake *noise_handshake_initiator = (Noise_Handshake *) calloc(1, sizeof(Noise_Handshake)); + + noise_handshake_init(noise_handshake_initiator, init_static, init_remote_static, true, prologue, sizeof(prologue)); + + memcpy(noise_handshake_initiator->ephemeral_private, init_ephemeral, CRYPTO_SECRET_KEY_SIZE); + crypto_derive_public_key(noise_handshake_initiator->ephemeral_public, init_ephemeral); + + ck_assert_msg(memcmp(noise_handshake_initiator->ephemeral_public, init_ephemeral_public, CRYPTO_PUBLIC_KEY_SIZE) == 0, "initiator ephemeral public keys differ"); + + uint8_t resp_static_pub[CRYPTO_PUBLIC_KEY_SIZE]; + crypto_derive_public_key(resp_static_pub, resp_static); + ck_assert_msg(memcmp(resp_static_pub, init_remote_static, CRYPTO_PUBLIC_KEY_SIZE) == 0, "responder static public keys differ"); + + /* e */ + noise_mix_hash(noise_handshake_initiator->hash, noise_handshake_initiator->ephemeral_public, CRYPTO_PUBLIC_KEY_SIZE); + + /* es */ + uint8_t noise_handshake_temp_key[CRYPTO_SHARED_KEY_SIZE]; + noise_mix_key(noise_handshake_initiator->chaining_key, noise_handshake_temp_key, noise_handshake_initiator->ephemeral_private, noise_handshake_initiator->remote_static); + + /* s */ + + /* Nonce for static pub key encryption is _always_ 0 in case of ChaCha20-Poly1305 */ + uint8_t ciphertext1[CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE]; + noise_encrypt_and_hash(ciphertext1, noise_handshake_initiator->static_public, CRYPTO_PUBLIC_KEY_SIZE, noise_handshake_temp_key, + noise_handshake_initiator->hash); + + ck_assert_msg(memcmp(ciphertext1, init_encrypted_static_public, CRYPTO_PUBLIC_KEY_SIZE+CRYPTO_MAC_SIZE) == 0, "initiator encrypted static public keys differ"); + + /* ss */ + noise_mix_key(noise_handshake_initiator->chaining_key, noise_handshake_temp_key, + noise_handshake_initiator->static_private, noise_handshake_initiator->remote_static); + + /* Noise Handshake Payload */ + uint8_t ciphertext2[sizeof(init_payload_hs) + CRYPTO_MAC_SIZE]; + + /* Nonce for payload encryption is _always_ 0 in case of ChaCha20-Poly1305 */ + noise_encrypt_and_hash(ciphertext2, + init_payload_hs, sizeof(init_payload_hs), noise_handshake_temp_key, + noise_handshake_initiator->hash); + + ck_assert_msg(memcmp(ciphertext2, init_payload_hs_encrypted, sizeof(init_payload_hs_encrypted)) == 0, "initiator encrypted handshake payloads differ"); + + // INITIATOR: END Create handshake packet for responder + + /* RESPONDER: Consume handshake packet from initiator */ + Noise_Handshake *noise_handshake_responder = (Noise_Handshake *) calloc(1, sizeof(Noise_Handshake)); + + uint8_t init_static_pub[CRYPTO_PUBLIC_KEY_SIZE]; + crypto_derive_public_key(init_static_pub, init_static); + + noise_handshake_init(noise_handshake_responder, resp_static, nullptr, false, prologue, sizeof(prologue)); + + /* e */ + memcpy(noise_handshake_responder->remote_ephemeral, noise_handshake_initiator->ephemeral_public, CRYPTO_PUBLIC_KEY_SIZE); + noise_mix_hash(noise_handshake_responder->hash, noise_handshake_initiator->ephemeral_public, CRYPTO_PUBLIC_KEY_SIZE); + + /* es */ + uint8_t noise_handshake_temp_key_resp[CRYPTO_SHARED_KEY_SIZE]; + noise_mix_key(noise_handshake_responder->chaining_key, noise_handshake_temp_key_resp, + noise_handshake_responder->static_private, noise_handshake_responder->remote_ephemeral); + + /* s */ + noise_decrypt_and_hash(noise_handshake_responder->remote_static, ciphertext1, CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE, + noise_handshake_temp_key_resp, noise_handshake_responder->hash); + + /* ss */ + noise_mix_key(noise_handshake_responder->chaining_key, noise_handshake_temp_key_resp, noise_handshake_responder->static_private, + noise_handshake_responder->remote_static); + + /* Payload decryption */ + uint8_t handshake_payload_plain_initiator[sizeof(init_payload_hs)]; + noise_decrypt_and_hash(handshake_payload_plain_initiator, ciphertext2, + sizeof(ciphertext2), noise_handshake_temp_key_resp, + noise_handshake_responder->hash); + + /* RESPONDER: Create handshake packet for initiator */ + + /* set ephemeral private+public */ + memcpy(noise_handshake_responder->ephemeral_private, resp_ephemeral, CRYPTO_SECRET_KEY_SIZE); + crypto_derive_public_key(noise_handshake_responder->ephemeral_public, resp_ephemeral); + + ck_assert_msg(memcmp(noise_handshake_responder->ephemeral_public, resp_ephemeral_public, CRYPTO_PUBLIC_KEY_SIZE) == 0, "responder ephemeral public keys differ"); + + /* e */ + noise_mix_hash(noise_handshake_responder->hash, noise_handshake_responder->ephemeral_public, CRYPTO_PUBLIC_KEY_SIZE); + + /* ee */ + uint8_t noise_handshake_temp_key_resp2[CRYPTO_SHARED_KEY_SIZE]; + noise_mix_key(noise_handshake_responder->chaining_key, noise_handshake_temp_key_resp2, noise_handshake_responder->ephemeral_private, + noise_handshake_responder->remote_ephemeral); + + /* se */ + noise_mix_key(noise_handshake_responder->chaining_key, noise_handshake_temp_key_resp2, noise_handshake_responder->ephemeral_private, + noise_handshake_responder->remote_static); + + + /* Nonce for payload encryption is _always_ 0 in case of ChaCha20-Poly1305 */ + uint8_t ciphertext3_hs_responder[sizeof(resp_payload_hs) + CRYPTO_MAC_SIZE]; + noise_encrypt_and_hash(ciphertext3_hs_responder, + resp_payload_hs, sizeof(resp_payload_hs), noise_handshake_temp_key_resp2, + noise_handshake_responder->hash); + + ck_assert_msg(memcmp(ciphertext3_hs_responder, resp_payload_hs_encrypted, sizeof(resp_payload_hs_encrypted)) == 0, "responder encrypted handshake payloads differ"); + + /* RESPONDER: END create handshake packet for initiator */ + + /* INITIATOR: Consume handshake packet from responder */ + memcpy(noise_handshake_initiator->remote_ephemeral, noise_handshake_responder->ephemeral_public, CRYPTO_PUBLIC_KEY_SIZE); + noise_mix_hash(noise_handshake_initiator->hash, noise_handshake_initiator->remote_ephemeral, CRYPTO_PUBLIC_KEY_SIZE); + + /* ee */ + uint8_t noise_handshake_temp_key_init[CRYPTO_SHARED_KEY_SIZE]; + noise_mix_key(noise_handshake_initiator->chaining_key, noise_handshake_temp_key_init, noise_handshake_initiator->ephemeral_private, + noise_handshake_initiator->remote_ephemeral); + + /* se */ + noise_mix_key(noise_handshake_initiator->chaining_key, noise_handshake_temp_key_init, noise_handshake_initiator->static_private, + noise_handshake_initiator->remote_ephemeral); + + uint8_t handshake_payload_plain_responder[sizeof(resp_payload_hs)]; + if(noise_decrypt_and_hash(handshake_payload_plain_responder, ciphertext3_hs_responder, + sizeof(ciphertext3_hs_responder), noise_handshake_temp_key_init, + noise_handshake_initiator->hash) != sizeof(resp_payload_hs)) { + printf("Initiator: HS decryption failed\n"); + } + + /* INITIATOR Noise Split(), nonces already set in crypto connection */ + uint8_t initiator_send_key[CRYPTO_SHARED_KEY_SIZE]; + uint8_t initiator_recv_key[CRYPTO_SHARED_KEY_SIZE]; + crypto_hkdf(initiator_send_key, CRYPTO_SHARED_KEY_SIZE, initiator_recv_key, CRYPTO_SHARED_KEY_SIZE, nullptr, 0, + noise_handshake_initiator->chaining_key); + + ck_assert_msg(memcmp(noise_handshake_initiator->hash, handshake_hash, CRYPTO_SHA512_SIZE) == 0, "initiator handshake hash differ"); + + uint8_t ciphertext4_transport1_initiator[sizeof(init_payload_transport1) + CRYPTO_MAC_SIZE]; + uint8_t nonce_chacha20_ietf[CRYPTO_NOISE_NONCE_SIZE] = {0}; + encrypt_data_symmetric_aead(initiator_send_key, nonce_chacha20_ietf, init_payload_transport1, sizeof(init_payload_transport1), ciphertext4_transport1_initiator, nullptr, 0); + + ck_assert_msg(memcmp(ciphertext4_transport1_initiator, init_payload_transport1_encrypted, sizeof(init_payload_transport1_encrypted)) == 0, "initiator transport1 ciphertext differ"); + + /* RESPONDER Noise Split(): vice-verse keys in comparison to initiator */ + uint8_t responder_send_key[CRYPTO_SHARED_KEY_SIZE]; + uint8_t responder_recv_key[CRYPTO_SHARED_KEY_SIZE]; + crypto_hkdf(responder_recv_key, CRYPTO_SYMMETRIC_KEY_SIZE, responder_send_key, CRYPTO_SYMMETRIC_KEY_SIZE, nullptr, 0, noise_handshake_responder->chaining_key); + + ck_assert_msg(memcmp(noise_handshake_responder->hash, handshake_hash, CRYPTO_SHA512_SIZE) == 0, "responder handshake hash differ"); + + uint8_t ciphertext5_transport1_responder[sizeof(resp_payload_transport1) + CRYPTO_MAC_SIZE]; + encrypt_data_symmetric_aead(responder_send_key, nonce_chacha20_ietf, + resp_payload_transport1, sizeof(resp_payload_transport1), ciphertext5_transport1_responder, nullptr, 0); + + ck_assert_msg(memcmp(ciphertext5_transport1_responder, resp_payload_transport1_encrypted, sizeof(resp_payload_transport1_encrypted)) == 0, "responder transport1 ciphertext differ"); + + free(noise_handshake_initiator); + free(noise_handshake_responder); +} + int main(void) { setvbuf(stdout, nullptr, _IONBF, 0); @@ -383,6 +703,7 @@ int main(void) test_very_large_data(); test_increment_nonce(); test_memzero(); + test_noiseik(); return 0; } diff --git a/auto_tests/forwarding_test.c b/auto_tests/forwarding_test.c index a1b8d6d248..ea2641a71d 100644 --- a/auto_tests/forwarding_test.c +++ b/auto_tests/forwarding_test.c @@ -127,7 +127,7 @@ static Forwarding_Subtox *new_forwarding_subtox(const Memory *mem, bool no_udp, subtox->dht = new_dht(subtox->log, mem, rng, ns, subtox->mono_time, subtox->net, true, true); const TCP_Proxy_Info inf = {{{{0}}}}; - subtox->c = new_net_crypto(subtox->log, mem, rng, ns, subtox->mono_time, subtox->dht, &inf); + subtox->c = new_net_crypto(subtox->log, mem, rng, ns, subtox->mono_time, subtox->dht, &inf, true); subtox->forwarding = new_forwarding(subtox->log, mem, rng, subtox->mono_time, subtox->dht); ck_assert(subtox->forwarding != nullptr); diff --git a/auto_tests/onion_test.c b/auto_tests/onion_test.c index b73ffbd165..eb904fb441 100644 --- a/auto_tests/onion_test.c +++ b/auto_tests/onion_test.c @@ -472,7 +472,7 @@ static Onions *new_onions(const Memory *mem, const Random *rng, uint16_t port, u } TCP_Proxy_Info inf = {{{{0}}}}; - on->onion_c = new_onion_client(on->log, mem, rng, on->mono_time, new_net_crypto(on->log, mem, rng, ns, on->mono_time, dht, &inf)); + on->onion_c = new_onion_client(on->log, mem, rng, on->mono_time, new_net_crypto(on->log, mem, rng, ns, on->mono_time, dht, &inf, true)); if (!on->onion_c) { kill_onion_announce(on->onion_a); diff --git a/third_party/cmp b/third_party/cmp index 52bfcfa17d..2ac6bca152 160000 --- a/third_party/cmp +++ b/third_party/cmp @@ -1 +1 @@ -Subproject commit 52bfcfa17d2eb4322da2037ad625f5575129cece +Subproject commit 2ac6bca152987c805c04423ebbba4b750585337f diff --git a/toxcore/Messenger.c b/toxcore/Messenger.c index b89f831e1a..ee720fb90e 100644 --- a/toxcore/Messenger.c +++ b/toxcore/Messenger.c @@ -3531,7 +3531,7 @@ Messenger *new_messenger(Mono_Time *mono_time, const Memory *mem, const Random * return nullptr; } - m->net_crypto = new_net_crypto(m->log, m->mem, m->rng, m->ns, m->mono_time, m->dht, &options->proxy_info); + m->net_crypto = new_net_crypto(m->log, m->mem, m->rng, m->ns, m->mono_time, m->dht, &options->proxy_info, options->noise_compatibility_enabled); if (m->net_crypto == nullptr) { LOGGER_WARNING(m->log, "net_crypto initialisation failed"); diff --git a/toxcore/Messenger.h b/toxcore/Messenger.h index d9bdaf3540..f968d01805 100644 --- a/toxcore/Messenger.h +++ b/toxcore/Messenger.h @@ -70,6 +70,7 @@ typedef struct Messenger_State_Plugin { typedef struct Messenger_Options { bool ipv6enabled; + bool noise_compatibility_enabled; bool udp_disabled; TCP_Proxy_Info proxy_info; uint16_t port_range[2]; diff --git a/toxcore/crypto_core.c b/toxcore/crypto_core.c index 8a6aa38fba..09e1ebd45d 100644 --- a/toxcore/crypto_core.c +++ b/toxcore/crypto_core.c @@ -27,6 +27,8 @@ static_assert(CRYPTO_MAC_SIZE == crypto_box_MACBYTES, "CRYPTO_MAC_SIZE should be equal to crypto_box_MACBYTES"); static_assert(CRYPTO_NONCE_SIZE == crypto_box_NONCEBYTES, "CRYPTO_NONCE_SIZE should be equal to crypto_box_NONCEBYTES"); +static_assert(CRYPTO_NOISE_NONCE_SIZE == crypto_stream_chacha20_ietf_NONCEBYTES, + "CRYPTO_NOISEIK_NONCE_SIZE should be equal to crypto_stream_chacha20_ietf_NONCEBYTES"); static_assert(CRYPTO_HMAC_SIZE == crypto_auth_BYTES, "CRYPTO_HMAC_SIZE should be equal to crypto_auth_BYTES"); static_assert(CRYPTO_HMAC_KEY_SIZE == crypto_auth_KEYBYTES, @@ -45,6 +47,11 @@ static_assert(CRYPTO_SIGN_PUBLIC_KEY_SIZE == crypto_sign_PUBLICKEYBYTES, static_assert(CRYPTO_SIGN_SECRET_KEY_SIZE == crypto_sign_SECRETKEYBYTES, "CRYPTO_SIGN_SECRET_KEY_SIZE should be equal to crypto_sign_SECRETKEYBYTES"); +/* CRYPTO_BLAKE2b_HASH_SIZE -> crypto_generichash_blake2b_BYTES_MAX (libsodium) */ +static_assert(CRYPTO_NOISE_BLAKE2B_HASH_SIZE == crypto_generichash_blake2b_BYTES_MAX, + "CRYPTO_BLAKE2b_HASH_SIZE should be equal to crypto_generichash_blake2b_BYTES_MAX"); +/* CRYPTO_BLAKE2b_BLOCK_SIZE -> BLAKE2B_BLOCKBYTES (not exposed by libsodium) */ + bool create_extended_keypair(Extended_Public_Key *pk, Extended_Secret_Key *sk, const Random *rng) { /* create signature key pair */ @@ -533,3 +540,434 @@ void random_bytes(const Random *rng, uint8_t *bytes, size_t length) { rng->funcs->random_bytes(rng->obj, bytes, length); } + +/* Necessary functions for Noise, cf. https://noiseprotocol.org/noise.html (Revision 34) */ + +int32_t encrypt_data_symmetric_aead(const uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE], const uint8_t nonce[CRYPTO_NOISE_NONCE_SIZE], + const uint8_t *plain, size_t plain_length, uint8_t *encrypted, + const uint8_t *ad, size_t ad_length) +{ + /* Additional data ad can be a NULL pointer with ad_length equal to 0; encrypted_length is calculated by libsodium */ + if (plain_length == 0 || shared_key == nullptr || nonce == nullptr || plain == nullptr || encrypted == nullptr) { + return -1; + } + + /* Passing NULL instead, encrypted length is clear anwyay (plain_length + crypto_aead_chacha20poly1305_IETF_ABYTES) */ + // unsigned long long encrypted_length = 0; + + /* nsec is not used by this particular construction and should always be NULL. */ + if (crypto_aead_chacha20poly1305_ietf_encrypt(encrypted, NULL, plain, plain_length, + ad, ad_length, nullptr, nonce, shared_key) != 0) { + return -1; + } + + assert(plain_length < INT32_MAX - crypto_aead_chacha20poly1305_IETF_ABYTES); + return (int32_t)(plain_length + crypto_aead_chacha20poly1305_IETF_ABYTES); +} + +int32_t decrypt_data_symmetric_aead(const uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE], const uint8_t nonce[CRYPTO_NOISE_NONCE_SIZE], + const uint8_t *encrypted, size_t encrypted_length, uint8_t *plain, + const uint8_t *ad, size_t ad_length) +{ + /* Additional data ad can be a NULL pointer with ad_length equal to 0; plain_length is calculated by libsodium */ + if (encrypted_length <= CRYPTO_MAC_SIZE || shared_key == nullptr || nonce == nullptr || encrypted == nullptr + || plain == nullptr) { + return -1; + } + + /* Passing NULL instead, encrypted length is clear anwyay (plain_length + crypto_aead_chacha20poly1305_IETF_ABYTES) */ + // unsigned long long plain_length = 0; + + if (crypto_aead_chacha20poly1305_ietf_decrypt(plain, NULL, nullptr, encrypted, + encrypted_length, ad, ad_length, nonce, shared_key) != 0) { + return -1; + } + + assert(encrypted_length > crypto_aead_chacha20poly1305_IETF_ABYTES); + assert(encrypted_length < INT32_MAX); + return (int32_t)(encrypted_length - crypto_aead_chacha20poly1305_IETF_ABYTES); +} + +int32_t encrypt_data_symmetric_xaead(const uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE], const uint8_t nonce[CRYPTO_NONCE_SIZE], + const uint8_t *plain, size_t plain_length, uint8_t *encrypted, + const uint8_t *ad, size_t ad_length) +{ + /* Additional data ad can be a NULL pointer with ad_length equal to 0; encrypted_length is calculated by libsodium */ + if (plain_length == 0 || shared_key == nullptr || nonce == nullptr || plain == nullptr || encrypted == nullptr) { + return -1; + } + + /* Passing NULL instead, encrypted length is clear anwyay (plain_length + crypto_aead_xchacha20poly1305_ietf_ABYTES) */ + // unsigned long long encrypted_length = 0; + + /* nsec is not used by this particular construction and should always be NULL. */ + if (crypto_aead_xchacha20poly1305_ietf_encrypt(encrypted, NULL, plain, plain_length, + ad, ad_length, nullptr, nonce, shared_key) != 0) { + return -1; + } + + assert(plain_length < INT32_MAX - crypto_aead_xchacha20poly1305_ietf_ABYTES); + return (int32_t)(plain_length + crypto_aead_xchacha20poly1305_ietf_ABYTES); +} + +int32_t decrypt_data_symmetric_xaead(const uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE], const uint8_t nonce[CRYPTO_NONCE_SIZE], + const uint8_t *encrypted, size_t encrypted_length, uint8_t *plain, + const uint8_t *ad, size_t ad_length) +{ + /* Additional data ad can be a NULL pointer with ad_length equal to 0; plain_length is calculated by libsodium */ + if (encrypted_length <= CRYPTO_MAC_SIZE || shared_key == nullptr || nonce == nullptr || encrypted == nullptr + || plain == nullptr) { + return -1; + } + + /* Passing NULL instead, encrypted length is clear anwyay (plain_length + crypto_aead_xchacha20poly1305_ietf_ABYTES) */ + // unsigned long long plain_length = 0; + + if (crypto_aead_xchacha20poly1305_ietf_decrypt(plain, NULL, nullptr, encrypted, + encrypted_length, ad, ad_length, nonce, shared_key) != 0) { + return -1; + } + + assert(encrypted_length > crypto_aead_xchacha20poly1305_ietf_ABYTES); + assert(encrypted_length < INT32_MAX); + return (int32_t)(encrypted_length - crypto_aead_xchacha20poly1305_ietf_ABYTES); +} + +/* +* Helper function to print hashes, keys, packets, etc. +* TODO(goldroom): remove from production code or make dependent on MIN_LOGGER_LEVEL=DEBUG? +* bytes_to_string() from util.h +*/ +// static void bytes2string(char *string, size_t string_length, const uint8_t *bytes, size_t bytes_length) +// { +// bytes_to_string(bytes, bytes_length, string, string_length); +// } + +/* Actually only 33 bytes necessary (but terminator necessary for CI), but test vectors still verify with 34 bytes */ +static const uint8_t noise_protocol[34] = "Noise_IK_25519_ChaChaPoly_BLAKE2b"; + +/** + * cf. Noise sections 4.3, 5.1 and 12.8: HMAC-BLAKE2b-512 + * HASH(input): BLAKE2b with digest length 64 + * HASHLEN = 64 + * BLOCKLEN = 128 + * Applies HMAC from RFC2104 (https://www.ietf.org/rfc/rfc2104.txt) using the HASH() (=BLAKE2b) function. + * This function is only called via `crypto_hkdf()`. + * Necessary for Noise (cf. sections 4.3 and 12.8) to return 64 bytes (BLAKE2b HASHLEN). + * Cf. https://doc.libsodium.org/hashing/generic_hashing + * key is CRYPTO_BLAKE2b_HASH_SIZE bytes because this function is only called via crypto_hkdf() where the key (ck, temp_key) + * is always HASHLEN bytes. + */ +static void crypto_hmac_blake2b_512(uint8_t *out_hmac, const uint8_t *in, size_t in_length, const uint8_t *key, + size_t key_length) +{ + crypto_generichash_blake2b_state state; + + /* + * (1) append zeros to the end of K to create a B byte string (e.g., if K is of length 20 bytes and B=64, + * then K will be appended with 44 zero bytes 0x00) + * B = Blake2b block length = 128 + * L the byte-length of Blake2b hash output = 64 + */ + uint8_t x_key[CRYPTO_BLAKE2B_BLOCK_SIZE] = { 0 }; + uint8_t i_hash[CRYPTO_NOISE_BLAKE2B_HASH_SIZE]; + int i; + + /* + * The authentication key K can be of any length up to B, the + * block length of the hash function. Applications that use keys longer + * than B bytes will first hash the key using H and then use the + * resultant L byte string as the actual key to HMAC. + * In any case the minimal recommended length for K is L bytes (as the hash output + * length). + */ + if (key_length > CRYPTO_BLAKE2B_BLOCK_SIZE) { + crypto_generichash_blake2b_init(&state, NULL, 0, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + crypto_generichash_blake2b_update(&state, key, key_length); + crypto_generichash_blake2b_final(&state, x_key, CRYPTO_BLAKE2B_BLOCK_SIZE); + } else { + memcpy(x_key, key, key_length); + } + + /* + * K XOR ipad, ipad = the byte 0x36 repeated B times + * (2) XOR (bitwise exclusive-OR) the B byte string computed in step + * (1) with ipad + */ + for (i = 0; i < CRYPTO_BLAKE2B_BLOCK_SIZE; ++i) { + x_key[i] ^= 0x36; + } + + /* + * H(K XOR ipad, text) + * (3) append the stream of data 'text' to the B byte string resulting + from step (2) + (4) apply H to the stream generated in step (3) + */ + crypto_generichash_blake2b_init(&state, NULL, 0, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + crypto_generichash_blake2b_update(&state, x_key, CRYPTO_BLAKE2B_BLOCK_SIZE); + crypto_generichash_blake2b_update(&state, in, in_length); + crypto_generichash_blake2b_final(&state, i_hash, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + + /* + * K XOR opad, opad = the byte 0x5C repeated B times + * (5) XOR (bitwise exclusive-OR) the B byte string computed in + step (1) with opad + */ + for (i = 0; i < CRYPTO_BLAKE2B_BLOCK_SIZE; ++i) { + x_key[i] ^= 0x5c ^ 0x36; + } + + /* + * H(K XOR opad, H(K XOR ipad, text)) + * (6) append the H result from step (4) to the B byte string + resulting from step (5) + (7) apply H to the stream generated in step (6) and output + the result + */ + crypto_generichash_blake2b_init(&state, NULL, 0, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + crypto_generichash_blake2b_update(&state, x_key, CRYPTO_BLAKE2B_BLOCK_SIZE); + crypto_generichash_blake2b_update(&state, i_hash, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + crypto_generichash_blake2b_final(&state, i_hash, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + + memcpy(out_hmac, i_hash, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + + /* Clear sensitive data from stack */ + crypto_memzero(x_key, CRYPTO_BLAKE2B_BLOCK_SIZE); + crypto_memzero(i_hash, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); +} + +/* This is Hugo Krawczyk's HKDF (i.e. HKDF-BLAKE2b-512): + * - https://eprint.iacr.org/2010/264.pdf + * - https://tools.ietf.org/html/rfc5869 + * HKDF(chaining_key, input_key_material, num_outputs): Takes a + * chaining_key byte sequence of length HASHLEN, and an input_key_material + * byte sequence with length either zero bytes, 32 bytes, or DHLEN bytes. + * Returns a pair or triple of byte sequences each of length HASHLEN, + * depending on whether num_outputs is two or three: + * – Sets temp_key = HMAC-HASH(chaining_key, input_key_material). + * – Sets output1 = HMAC-HASH(temp_key, byte(0x01)). + * – Sets output2 = HMAC-HASH(temp_key, output1 || byte(0x02)). + * – If num_outputs == 2 then returns the pair (output1, output2). + * – Sets output3 = HMAC-HASH(temp_key, output2 || byte(0x03)). + * – Returns the triple (output1, output2, output3). + * Note that temp_key, output1, output2, and output3 are all HASHLEN bytes in + * length. Also note that the HKDF() function is simply HKDF with the + * chaining_key as HKDF salt, and zero-length HKDF info. + * + * HASH(input): BLAKE2b with digest length 64. + * HASHLEN = 64 + * BLOCKLEN = 128 + * + * Verified using Noise_IK_25519_ChaChaPoly_BLAKE2b test vectors. + */ +void crypto_hkdf(uint8_t *output1, size_t first_len, uint8_t *output2, + size_t second_len, const uint8_t *data, + size_t data_len, const uint8_t chaining_key[CRYPTO_NOISE_BLAKE2B_HASH_SIZE]) +{ + uint8_t output[CRYPTO_NOISE_BLAKE2B_HASH_SIZE + 1]; + // temp_key = secret in WG + uint8_t temp_key[CRYPTO_NOISE_BLAKE2B_HASH_SIZE]; + + /* Extract entropy from data into temp_key */ + /* HKDF-Extract(salt, IKM) -> PRK, where chaining_key is HKDF salt, DH result (data) is input keying material (IKM) (and zero-length HKDF info in expand). + Result is a pseudo random key (PRK) = temp_key */ + /* data => input_key_material => X25519-DH result in Noise */ + crypto_hmac_blake2b_512(temp_key, data, data_len, chaining_key, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + /* Noise spec: Note that temp_key, output1, output2, and output3 are all HASHLEN bytes in length. + Also note that the HKDF() function is simply HKDF from [4] with the chaining_key as HKDF salt, and zero-length HKDF info. */ + + /* Expand first key: key = temp_key, data = 0x1 */ + output[0] = 1; + crypto_hmac_blake2b_512(output, output, 1, temp_key, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + memcpy(output1, output, first_len); + + /* Expand both keys in one operation (verified): */ + /* HKDF-Expand(PRK, info, L) -> OKM, where PRK = temp_key, zero-length HKDF info (ctx) + and L (length of output keying material in octets) = 2*64 byte (i.e. 2x HashLen) */ + /* OKM = HKDF -> T(0) + T(1); cf. RFC5869: https://datatracker.ietf.org/doc/html/rfc5869#section-2.3 */ + /* ctx parameter = RFC5869 info -> i.e. optional context and application specific information (can be a zero-length string) */ + + /* Expand second key: key = secret, data = first-key || 0x2 */ + output[CRYPTO_NOISE_BLAKE2B_HASH_SIZE] = 2; + crypto_hmac_blake2b_512(output, output, CRYPTO_NOISE_BLAKE2B_HASH_SIZE +1, temp_key, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + memcpy(output2, output, second_len); + + /* Expand third key: key = temp_key, data = second-key || 0x3 */ + /* TODO(goldroom): Currently output3 is not used in Tox, maybe necessary in future for pre-shared symmetric keys (cf. Noise spec) */ + // output[CRYPTO_SHA512_SIZE] = 3; + // crypto_hmac512(output, temp_key, output, CRYPTO_SHA512_SIZE + 1); + // memcpy(output3, output, third_len); + + /* Clear sensitive data from stack */ + crypto_memzero(temp_key, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + crypto_memzero(output, CRYPTO_NOISE_BLAKE2B_HASH_SIZE + 1); +} + +/* + * cf. Noise section 5.2: based on HKDF-BLAKE2b + * Executes the following steps: + * - Sets ck, temp_k = HKDF(ck, input_key_material, 2). + * - If HASHLEN is 64, then truncates temp_k to 32 bytes + * - Calls InitializeKey(temp_k). + * input_key_material = DH_X25519(private, public) + * + */ +int32_t noise_mix_key(uint8_t chaining_key[CRYPTO_NOISE_BLAKE2B_HASH_SIZE], + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE], + const uint8_t private_key[CRYPTO_SECRET_KEY_SIZE], + const uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]) +{ + uint8_t dh_calculation[CRYPTO_SHARED_KEY_SIZE]; + memset(dh_calculation, 0, CRYPTO_SHARED_KEY_SIZE); + + /* X25519: returns plain DH result, afterwards hashed with HKDF (necessary for NoiseIK) */ + if (crypto_scalarmult_curve25519(dh_calculation, private_key, public_key) != 0) { + return -1; + } + + /* chaining_key is HKDF output1 and shared_key is HKDF output2 => different values/results! */ + /* If HASHLEN is 64, then truncates temp_k (= shared_key) to 32 bytes. => done via call to crypto_hkdf() */ + crypto_hkdf(chaining_key, CRYPTO_NOISE_BLAKE2B_HASH_SIZE, shared_key, CRYPTO_SHARED_KEY_SIZE, dh_calculation, + CRYPTO_SHARED_KEY_SIZE, chaining_key); + + crypto_memzero(dh_calculation, CRYPTO_SHARED_KEY_SIZE); + + return 0; +} + +/* + * Noise MixHash(data): Sets h = HASH(h || data). + * Blake2b + * + * cf. Noise section 5.2 + */ +void noise_mix_hash(uint8_t hash[CRYPTO_NOISE_BLAKE2B_HASH_SIZE], const uint8_t *data, size_t data_len) +{ + crypto_generichash_blake2b_state state; + crypto_generichash_blake2b_init(&state, NULL, 0, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + crypto_generichash_blake2b_update(&state, hash, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + crypto_generichash_blake2b_update(&state, data, data_len); + crypto_generichash_blake2b_final(&state, hash, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); +} + +/* + * cf. Noise section 5.2 + * "Noise spec: Note that if k is empty, the EncryptWithAd() call will set ciphertext equal to plaintext." + * This is not the case in Tox. + */ +void noise_encrypt_and_hash(uint8_t *ciphertext, const uint8_t *plaintext, + size_t plain_length, uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE], + uint8_t hash[CRYPTO_NOISE_BLAKE2B_HASH_SIZE]) +{ + static uint8_t nonce_chacha20_ietf[CRYPTO_NOISE_NONCE_SIZE] = {0}; + memset(nonce_chacha20_ietf, 0, CRYPTO_NOISE_NONCE_SIZE); + + const int32_t encrypted_length = encrypt_data_symmetric_aead(shared_key, nonce_chacha20_ietf, + plaintext, plain_length, ciphertext, + hash, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + + noise_mix_hash(hash, ciphertext, encrypted_length); +} + +/* + * cf. Noise section 5.2 + * "Note that if k is empty, the DecryptWithAd() call will set plaintext equal to ciphertext." + * This is not the case in Tox. + */ +int noise_decrypt_and_hash(uint8_t *plaintext, const uint8_t *ciphertext, + size_t encrypted_length, uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE], + uint8_t hash[CRYPTO_NOISE_BLAKE2B_HASH_SIZE]) +{ + static uint8_t nonce_chacha20_ietf[CRYPTO_NOISE_NONCE_SIZE] = {0}; + memset(nonce_chacha20_ietf, 0, CRYPTO_NOISE_NONCE_SIZE); + + const int32_t plaintext_length = decrypt_data_symmetric_aead(shared_key, nonce_chacha20_ietf, + ciphertext, encrypted_length, plaintext, + hash, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + + // TODO(goldroom): add condition for plaintext_length? (if decrypt failed mix_hash is unnecessary) + // TODO(goldroom): or instead fix all cases that lead to decrypt failed + noise_mix_hash(hash, ciphertext, encrypted_length); + + return plaintext_length; +} + +/** + * @brief Initializes a Noise Handshake State with provided static X25519 ID key pair, X25519 static ID public key from peer + * and sets if initiator or not. + * + * cf. Noise section 5.3 + * Calls InitializeSymmetric(protocol_name). + * Calls MixHash(prologue). + * Sets the initiator, s, e, rs, and re variables to the corresponding arguments. + * Calls MixHash() once for each public key listed in the pre-messages. + * + * @param noise_handshake handshake struct to save the necessary values to + * @param self_id_secret_key static private ID X25519 key of this Tox instance + * @param peer_id_public_key static public ID X25519 key from peer to connect to + * @param initiator specifies if this Tox instance is the initiator of this crypto connection + * @param prologue specifies the Noise prologue, used in call to MixHash(prologue) which maybe zero-length + * @param prologue_length length of Noise prologue in bytes + * + * @return -1 on failure + * @return 0 on success + */ +int noise_handshake_init +(Noise_Handshake *noise_handshake, const uint8_t self_id_secret_key[CRYPTO_SECRET_KEY_SIZE], const uint8_t peer_id_public_key[CRYPTO_PUBLIC_KEY_SIZE], bool initiator, const uint8_t *prologue, size_t prologue_length) +{ + /* IntializeSymmetric(protocol_name) => set h to NOISE_PROTOCOL_NAME and append zero bytes to make 64 bytes, sets ck = h + Nothing gets hashed in Tox case because NOISE_PROTOCOL_NAME < CRYPTO_SHA512_SIZE */ + uint8_t temp_hash[CRYPTO_NOISE_BLAKE2B_HASH_SIZE]; + memset(temp_hash, 0, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + memcpy(temp_hash, noise_protocol, sizeof(noise_protocol)); + memcpy(noise_handshake->hash, temp_hash, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + memcpy(noise_handshake->chaining_key, temp_hash, CRYPTO_NOISE_BLAKE2B_HASH_SIZE); + + /* IMPORTANT needs to be called with (empty/zero-length) prologue! */ + noise_mix_hash(noise_handshake->hash, prologue, prologue_length); + + /* Sets the initiator, s => ephemeral keys are set afterwards */ + noise_handshake->initiator = initiator; + if (self_id_secret_key != nullptr) { + memcpy(noise_handshake->static_private, self_id_secret_key, CRYPTO_SECRET_KEY_SIZE); + crypto_derive_public_key(noise_handshake->static_public, self_id_secret_key); + } else { + // fprintf(stderr, "Local static private key required, but not provided.\n"); + // LOGGER_DEBUG(log, "Local static private key required, but not provided."); + return -1; + } + /* <- s: pre-message from responder to initiator => sets rs (only initiator) */ + if (initiator) { + if (peer_id_public_key != nullptr) { + memcpy(noise_handshake->remote_static, peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + /* Calls MixHash() once for each public key listed in the pre-messages from Noise IK */ + noise_mix_hash(noise_handshake->hash, peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE); + } else { + // fprintf(stderr, "Remote peer static public key required, but not provided.\n"); + // LOGGER_DEBUG(log, "Remote peer static public key required, but not provided."); + return -1; + } + } + /* Noise RESPONDER */ + else { + /* Calls MixHash() once for each public key listed in the pre-messages from Noise IK */ + noise_mix_hash(noise_handshake->hash, noise_handshake->static_public, CRYPTO_PUBLIC_KEY_SIZE); + + // TODO(goldroom): precompute static static here (ss)? cf. WireGuard wg_noise_handshake_init() + } + + /* Ready to go */ + return 0; +} + +// TODO(goldroom): abstract creation and handling of NoiseIK handshake packets from net_crypto (_after_ cookie adaption) +// /* Noise create INITIATOR: -> e, es, s, ss */ +// int noise_handshake_create_initiator() +// /* Noise handle INITIATOR: -> e, es, s, ss */ +// int noise_handshake_handle_initiator() +// /* Noise create RESPONDER: <- e, ee, se */ +// int noise_handshake_create_responder() +// /* Noise handle RESPONDER: <- e, ee, se */ +// int noise_handshake_handle_responder() diff --git a/toxcore/crypto_core.h b/toxcore/crypto_core.h index 558118e397..5041e18c40 100644 --- a/toxcore/crypto_core.h +++ b/toxcore/crypto_core.h @@ -68,6 +68,11 @@ extern "C" { */ #define CRYPTO_NONCE_SIZE 24 +/** + * @brief NoiseIK: The number of bytes in a nonce used for encryption/decryption (ChaChaPoly1305-IETF). + */ +#define CRYPTO_NOISE_NONCE_SIZE 12 + /** * @brief The number of bytes in a SHA256 hash. */ @@ -78,6 +83,16 @@ extern "C" { */ #define CRYPTO_SHA512_SIZE 64 +/** + * @brief The number of bytes in a BLAKE2b-512 hash (as defined for Noise in section 12.8.). + */ +#define CRYPTO_NOISE_BLAKE2B_HASH_SIZE 64 + +/** + * @brief The number of bytes in a BLAKE2b block. + */ +#define CRYPTO_BLAKE2B_BLOCK_SIZE 128 + /** @brief Fill a byte array with random bytes. * * This is the key generator callback and as such must be a cryptographically @@ -110,6 +125,25 @@ typedef struct Random { void *obj; } Random; +// TODO(goldroom): struct necessary? +/** @brief Necessary NoiseIK handshake state information/values. + */ +typedef struct Noise_Handshake { + uint8_t static_private[CRYPTO_SECRET_KEY_SIZE]; + uint8_t static_public[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t ephemeral_private[CRYPTO_SECRET_KEY_SIZE]; + uint8_t ephemeral_public[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t remote_static[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t remote_ephemeral[CRYPTO_PUBLIC_KEY_SIZE]; + // TODO(goldroom): precompute static static? cf. WireGuard struct noise_handshake + // uint8_t precomputed_static_static[CRYPTO_SHARED_KEY_SIZE]; + + uint8_t hash[CRYPTO_SHA512_SIZE]; + uint8_t chaining_key[CRYPTO_SHA512_SIZE]; + + bool initiator; +} Noise_Handshake; + /** @brief System random number generator. * * Uses libsodium's CSPRNG (on Linux, `/dev/urandom`). @@ -506,6 +540,201 @@ bool crypto_memunlock(void *data, size_t length); non_null() void new_hmac_key(const Random *rng, uint8_t key[CRYPTO_HMAC_KEY_SIZE]); +/* Necessary functions for Noise, cf. https://noiseprotocol.org/noise.html (Revision 34) */ + +/** + * @brief Encrypt message with precomputed shared key using ChaCha20-Poly1305-IETF (RFC7539). + * + * Encrypts plain of plain_length to encrypted of plain_length + @ref CRYPTO_MAC_SIZE + * using a shared key @ref CRYPTO_SYMMETRIC_KEY_SIZE big and a @ref CRYPTO_NOISE_NONCE_SIZE + * byte nonce. The encrypted message, as well as a tag authenticating both the confidential + * message m and adlen bytes of non-confidential data ad, are put into encrypted. + * + * @retval -1 if there was a problem. + * @return length of encrypted data if everything was fine. + */ +non_null(1, 2, 3, 5) nullable(6) +int32_t encrypt_data_symmetric_aead(const uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE], const uint8_t nonce[CRYPTO_NOISE_NONCE_SIZE], const uint8_t *plain, size_t plain_length, + uint8_t *encrypted, const uint8_t *ad, size_t ad_length); + +/** + * @brief Decrypt message with precomputed shared key using ChaCha20-Poly1305-IETF (RFC7539). + * + * Decrypts encrypted of encrypted_length to plain of length + * `length - CRYPTO_MAC_SIZE` using a shared key @ref CRYPTO_SHARED_KEY_SIZE + * big and a @ref CRYPTO_NOISE_NONCE_SIZE byte nonce. + * + * @retval -1 if there was a problem (decryption failed). + * @return length of plain data if everything was fine. + */ +non_null(1, 2, 3, 5) nullable(6) +int32_t decrypt_data_symmetric_aead(const uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE], const uint8_t nonce[CRYPTO_NOISE_NONCE_SIZE], const uint8_t *encrypted, size_t encrypted_length, + uint8_t *plain, const uint8_t *ad, size_t ad_length); + +/** + * @brief Encrypt message with precomputed shared key using XChaCha20-Poly1305. + * + * Encrypts plain of plain_length to encrypted of plain_length + @ref CRYPTO_MAC_SIZE + * using a shared key @ref CRYPTO_SYMMETRIC_KEY_SIZE big and a @ref CRYPTO_NONCE_SIZE + * byte nonce. The encrypted message, as well as a tag authenticating both the confidential + * message m and adlen bytes of non-confidential data ad, are put into encrypted. + * + * @retval -1 if there was a problem. + * @return length of encrypted data if everything was fine. + */ +non_null(1, 2, 3, 5) nullable(6) +int32_t encrypt_data_symmetric_xaead(const uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE], const uint8_t nonce[CRYPTO_NONCE_SIZE], const uint8_t *plain, size_t plain_length, + uint8_t *encrypted, const uint8_t *ad, size_t ad_length); + +/** + * @brief Decrypt message with precomputed shared key using XChaCha20-Poly1305. + * + * Decrypts encrypted of encrypted_length to plain of length + * `length - CRYPTO_MAC_SIZE` using a shared key @ref CRYPTO_SHARED_KEY_SIZE + * big and a @ref CRYPTO_NONCE_SIZE byte nonce. + * + * @retval -1 if there was a problem (decryption failed). + * @return length of plain data if everything was fine. + */ +non_null(1, 2, 3, 5) nullable(6) +int32_t decrypt_data_symmetric_xaead(const uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE], const uint8_t nonce[CRYPTO_NONCE_SIZE], const uint8_t *encrypted, size_t encrypted_length, + uint8_t *plain, const uint8_t *ad, size_t ad_length); + +/** + * @brief Computes the number of provides outputs (=keys) with HKDF-SHA512. + * + * cf. Noise sections 4.3 and 5.1 + * + * This is Hugo Krawczyk's HKDF: + * - https://eprint.iacr.org/2010/264.pdf + * - https://tools.ietf.org/html/rfc5869 + * HKDF(chaining_key, input_key_material, num_outputs): Takes a + * chaining_key byte sequence of length HASHLEN, and an input_key_material + * byte sequence with length either zero bytes, 32 bytes, or DHLEN bytes. + * Returns a pair or triple of byte sequences each of length HASHLEN, + * depending on whether num_outputs is two or three: + * – Sets temp_key = HMAC-HASH(chaining_key, input_key_material). + * – Sets output1 = HMAC-HASH(temp_key, byte(0x01)). + * – Sets output2 = HMAC-HASH(temp_key, output1 || byte(0x02)). + * – If num_outputs == 2 then returns the pair (output1, output2). + * – Sets output3 = HMAC-HASH(temp_key, output2 || byte(0x03)). + * – Returns the triple (output1, output2, output3). + * Note that temp_key, output1, output2, and output3 are all HASHLEN bytes in + * length. Also note that the HKDF() function is simply HKDF with the + * chaining_key as HKDF salt, and zero-length HKDF info. + * + * @param output1 First key to compute + * @param first_len Length of output1/key + * @param output2 Second key to compute + * @param second_len Length of output2/key + * @param data HKDF input_key_material byte sequence with length either zero bytes, 32 bytes, or DHLEN bytes + * @param data_len length of either zero bytes, 32 bytes, or DHLEN bytes + * @param chaining_key Noise 64 byte chaining key as HKDF salt + */ +non_null(1, 3, 7) nullable(5) +void crypto_hkdf(uint8_t *output1, size_t first_len, uint8_t *output2, + size_t second_len, const uint8_t *data, + size_t data_len, const uint8_t chaining_key[CRYPTO_SHA512_SIZE]); + +/** + * @brief Initializes a Noise Handshake State with provided static X25519 ID key pair, X25519 static ID public key from peer + * and sets if initiator or not. + * + * cf. Noise section 5.3 + * Calls InitializeSymmetric(protocol_name). + * Calls MixHash(prologue). + * Sets the initiator, s, e, rs, and re variables to the corresponding arguments. + * Calls MixHash() once for each public key listed in the pre-messages. + * + * @param noise_handshake Noise handshake struct to save the necessary values to + * @param self_id_secret_key static private ID X25519 key of this Tox instance + * @param peer_id_public_key static public ID X25519 key from the peer to connect to + * @param initiator specifies if this Tox instance is the initiator of this crypto connection + * @param prologue specifies the Noise prologue, used in call to MixHash(prologue) which maybe zero-length + * @param prologue_length length of Noise prologue in bytes + * + * @return -1 on failure + * @return 0 on success + */ +non_null(1, 2) nullable(3, 5) +int noise_handshake_init +(Noise_Handshake *noise_handshake, const uint8_t self_id_secret_key[CRYPTO_SECRET_KEY_SIZE], const uint8_t peer_id_public_key[CRYPTO_PUBLIC_KEY_SIZE], bool initiator, const uint8_t *prologue, size_t prologue_length); + +/** + * @brief Noise MixKey(input_key_material) + * + * cf. Noise section 5.2 + * Executes the following steps: + * - Sets ck, temp_k = HKDF(ck, input_key_material, 2). + * - If HASHLEN is 64, then truncates temp_k to 32 bytes + * - Calls InitializeKey(temp_k). + * input_key_material = DH_X25519(private, public) + * + * @param chaining_key 64 byte Noise ck + * @param shared_key 32 byte secret key to be calculated + * @param private_key X25519 private key + * @param public_key X25519 public key + */ + non_null() +int32_t noise_mix_key(uint8_t chaining_key[CRYPTO_SHA512_SIZE], uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE], + const uint8_t private_key[CRYPTO_SECRET_KEY_SIZE], + const uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]); + +/** + * @brief Noise MixHash(data): Sets h = HASH(h || data). + * + * cf. Noise section 5.2 + * + * @param hash Contains current hash, is updated with new hash + * @param data to add to hash + * @param data_len length of data to hash + * + */ +non_null(1) nullable(2) +void noise_mix_hash(uint8_t hash[CRYPTO_SHA512_SIZE], const uint8_t *data, size_t data_len); + +/** + * @brief Noise EncryptAndHash(plaintext): Sets ciphertext = EncryptWithAd(h, + * plaintext), calls MixHash(ciphertext), and returns ciphertext. Note + * that if k is empty, the EncryptWithAd() call will set ciphertext equal + * to plaintext. + * + * cf. Noise section 5.2 + * "Noise spec: Note that if k is empty, the EncryptWithAd() call will set ciphertext equal to plaintext." + * This is not the case in Tox. + * + * @param ciphertext stores encrypted plaintext + * @param plaintext to be encrypted + * @param plain_length length of plaintext + * @param shared_key used for XAEAD encryption + * @param hash stores hash value, used as associated data in XAEAD + */ +non_null() +void noise_encrypt_and_hash(uint8_t *ciphertext, const uint8_t *plaintext, + size_t plain_length, uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE], + uint8_t hash[CRYPTO_SHA512_SIZE]); + +/** + * @brief DecryptAndHash(ciphertext): Sets plaintext = DecryptWithAd(h, + * ciphertext), calls MixHash(ciphertext), and returns plaintext. Note + * that if k is empty, the DecryptWithAd() call will set plaintext equal to + * ciphertext. + * + * cf. Noise section 5.2 + * "Note that if k is empty, the DecryptWithAd() call will set plaintext equal to ciphertext." + * This is not the case in Tox. + * + * @param ciphertext contains ciphertext to decrypt + * @param plaintext stores decrypted ciphertext + * @param encrypted_length length of ciphertext+MAC + * @param shared_key used for XAEAD decryption + * @param hash stores hash value, used as associated data in XAEAD + */ +non_null() +int noise_decrypt_and_hash(uint8_t *plaintext, const uint8_t *ciphertext, + size_t encrypted_length, uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE], + uint8_t hash[CRYPTO_SHA512_SIZE]); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/toxcore/friend_connection.c b/toxcore/friend_connection.c index 78c6d4acdc..22e7c54625 100644 --- a/toxcore/friend_connection.c +++ b/toxcore/friend_connection.c @@ -569,7 +569,7 @@ non_null() static int handle_new_connections(void *object, const New_Connection *n_c) { Friend_Connections *const fr_c = (Friend_Connections *)object; - const int friendcon_id = getfriend_conn_id_pk(fr_c, n_c->public_key); + const int friendcon_id = getfriend_conn_id_pk(fr_c, n_c->peer_id_public_key); Friend_Conn *const friend_con = get_conn(fr_c, friendcon_id); if (friend_con == nullptr) { @@ -598,8 +598,8 @@ static int handle_new_connections(void *object, const New_Connection *n_c) friend_con->dht_ip_port_lastrecv = mono_time_get(fr_c->mono_time); } - if (!pk_equal(friend_con->dht_temp_pk, n_c->dht_public_key)) { - change_dht_pk(fr_c, friendcon_id, n_c->dht_public_key); + if (!pk_equal(friend_con->dht_temp_pk, n_c->peer_dht_public_key)) { + change_dht_pk(fr_c, friendcon_id, n_c->peer_dht_public_key); } nc_dht_pk_callback(fr_c->net_crypto, id, &dht_pk_callback, fr_c, friendcon_id); diff --git a/toxcore/logger.c b/toxcore/logger.c index b97ef8e184..5db75775fd 100644 --- a/toxcore/logger.c +++ b/toxcore/logger.c @@ -83,7 +83,8 @@ void logger_write(const Logger *log, Logger_Level level, const char *file, uint3 #endif /* WIN32 */ // Format message - char msg[1024]; + // TODO(goldroom): changed from 1024 to 4096 + char msg[4096]; va_list args; va_start(args, format); vsnprintf(msg, sizeof(msg), format, args); diff --git a/toxcore/net_crypto.c b/toxcore/net_crypto.c old mode 100644 new mode 100755 index b9602415c6..86aace912f --- a/toxcore/net_crypto.c +++ b/toxcore/net_crypto.c @@ -5,6 +5,12 @@ /** * Functions for the core network crypto. + * This implements NoiseIK: + * + * <- s + * ... + * -> e, es, s, ss + * <- e, ee, se * * NOTE: This code has to be perfect. We don't mess around with encryption. */ @@ -27,7 +33,6 @@ #include "net_profile.h" #include "network.h" #include "util.h" - typedef struct Packet_Data { uint64_t sent_time; uint16_t length; @@ -55,16 +60,32 @@ typedef enum Crypto_Conn_State { } Crypto_Conn_State; typedef struct Crypto_Connection { - uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The real public key of the peer. */ - uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of received packets. */ - uint8_t sent_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of sent packets. */ - uint8_t sessionpublic_key[CRYPTO_PUBLIC_KEY_SIZE]; /* Our public key for this session. */ - uint8_t sessionsecret_key[CRYPTO_SECRET_KEY_SIZE]; /* Our private key for this session. */ - uint8_t peersessionpublic_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The public key of the peer. */ - uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; /* The precomputed shared key from encrypt_precompute. */ + // TODO(goldroom): kept for backwards compatibility in NoiseIK, to be removed at some point. Not used in NoiseIK handshake. + uint8_t peer_id_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The real/static identity public X25519 key of the peer. */ + + uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce used to decrypt incoming packets after non-Noise and NoiseIK handshake. */ + uint8_t send_nonce[CRYPTO_NONCE_SIZE]; /* Nonce used to encrypt outgoing packets after non-Noise and NoiseIK handshake. */ + + // TODO(goldroom): currently in use for backwards compatibility in NoiseIK, to be removed at some point. + uint8_t ephemeral_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* Our public ephemeral X25519 key for this session. */ + uint8_t ephemeral_secret_key[CRYPTO_SECRET_KEY_SIZE]; /* Our private ephemeral X25519 key for this session. */ + + // TODO(goldroom): kept for backwards compatibility in NoiseIK, to be removed at some point. Not used in NoiseIK handshake. + uint8_t peer_ephemeral_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The public ephemeral X25519 key of the peer. */ + + /* The precomputed shared key from encrypt_precompute. + Used for cookie requests/responses in non-Noise and NoiseIK handshake: and for transport payload encryption (non-Noise only). */ + uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; + Crypto_Conn_State status; /* See Crypto_Conn_State documentation */ uint64_t cookie_request_number; /* number used in the cookie request packets for this connection */ - uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The dht public key of the peer */ + uint8_t peer_dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The DHT public X25519 key of the peer. */ + + bool noise_handshake_enabled; /* Necessary for Noise handshake backwards compatibility */ + // TODO(goldroom): Can this be moved out of struct Crypto_Connection? Not necessary after handshake is finished + Noise_Handshake *noise_handshake; /* NoiseIK handshake information */ + uint8_t send_key[CRYPTO_SHARED_KEY_SIZE]; /* Symmetric key used to encrypt outgoing packets after NoiseIK handshake. */ + uint8_t recv_key[CRYPTO_SHARED_KEY_SIZE]; /* Symmetric key used to decrypt incoming packets after NoiseIK handshake. */ uint8_t *temp_packet; /* Where the cookie request/handshake packet is stored while it is being sent. */ uint16_t temp_packet_length; @@ -146,11 +167,11 @@ struct Net_Crypto { uint32_t crypto_connections_length; /* Length of connections array. */ /* Our public and secret keys. */ - uint8_t self_public_key[CRYPTO_PUBLIC_KEY_SIZE]; - uint8_t self_secret_key[CRYPTO_SECRET_KEY_SIZE]; + uint8_t self_id_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + uint8_t self_id_secret_key[CRYPTO_SECRET_KEY_SIZE]; /* The secret key used for cookies */ - uint8_t secret_symmetric_key[CRYPTO_SYMMETRIC_KEY_SIZE]; + uint8_t cookie_symmetric_key[CRYPTO_SYMMETRIC_KEY_SIZE]; new_connection_cb *new_connection_callback; void *new_connection_callback_object; @@ -159,16 +180,19 @@ struct Net_Crypto { uint32_t current_sleep_time; BS_List ip_port_list; + + /* Sets backwards compatibility to non-Noise handshake to true or false */ + bool noise_compatibility_enabled; }; const uint8_t *nc_get_self_public_key(const Net_Crypto *c) { - return c->self_public_key; + return c->self_id_public_key; } const uint8_t *nc_get_self_secret_key(const Net_Crypto *c) { - return c->self_secret_key; + return c->self_id_secret_key; } TCP_Connections *nc_get_tcp_c(const Net_Crypto *c) @@ -220,9 +244,13 @@ non_null() static int create_cookie_request(const Net_Crypto *c, uint8_t *packet, const uint8_t *dht_public_key, uint64_t number, uint8_t *shared_key) { + LOGGER_DEBUG(c->log, "Packet: %d/NET_PACKET_COOKIE_REQUEST", NET_PACKET_COOKIE_REQUEST); + + // TODO(goldroom): adapt for new Noise-cookie mechanism _only_ or different cookie mechanism? E.g. as in WireGuard? uint8_t plain[COOKIE_REQUEST_PLAIN_LENGTH]; - memcpy(plain, c->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(plain, c->self_id_public_key, CRYPTO_PUBLIC_KEY_SIZE); + // TODO(goldroom): "Padding is used to maintain backwards-compatibility with previous versions of the protocol." => can this be removed by now? memzero(plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_PUBLIC_KEY_SIZE); memcpy(plain + (CRYPTO_PUBLIC_KEY_SIZE * 2), &number, sizeof(uint64_t)); const uint8_t *tmp_shared_key = dht_get_shared_key_sent(c->dht, dht_public_key); @@ -305,12 +333,13 @@ non_null() static int create_cookie_response(const Net_Crypto *c, uint8_t *packet, const uint8_t *request_plain, const uint8_t *shared_key, const uint8_t *dht_public_key) { + LOGGER_DEBUG(c->log, "Packet: %d/NET_PACKET_COOKIE_RESPONSE", NET_PACKET_COOKIE_RESPONSE); uint8_t cookie_plain[COOKIE_DATA_LENGTH]; memcpy(cookie_plain, request_plain, CRYPTO_PUBLIC_KEY_SIZE); memcpy(cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); uint8_t plain[COOKIE_LENGTH + sizeof(uint64_t)]; - if (create_cookie(c->mem, c->rng, c->mono_time, plain, cookie_plain, c->secret_symmetric_key) != 0) { + if (create_cookie(c->mem, c->rng, c->mono_time, plain, cookie_plain, c->cookie_symmetric_key) != 0) { return -1; } @@ -337,6 +366,7 @@ non_null() static int handle_cookie_request(const Net_Crypto *c, uint8_t *request_plain, uint8_t *shared_key, uint8_t *dht_public_key, const uint8_t *packet, uint16_t length) { + LOGGER_DEBUG(c->log, "Packet: %d/length: %d", packet[0], length); if (length != COOKIE_REQUEST_LENGTH) { return -1; } @@ -361,6 +391,9 @@ static int udp_handle_cookie_request(void *object, const IP_Port *source, const void *userdata) { const Net_Crypto *c = (const Net_Crypto *)object; + + LOGGER_DEBUG(c->log, "Packet: %d/length: %d", packet[0], length); + uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; @@ -387,6 +420,7 @@ non_null() static int tcp_handle_cookie_request(const Net_Crypto *c, int connections_number, const uint8_t *packet, uint16_t length) { + LOGGER_DEBUG(c->log, "Packet: %d/length: %d", packet[0], length); uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; @@ -410,6 +444,7 @@ non_null() static int tcp_oob_handle_cookie_request(const Net_Crypto *c, unsigned int tcp_connections_number, const uint8_t *dht_public_key, const uint8_t *packet, uint16_t length) { + LOGGER_DEBUG(c->log, "Packet: %d/length: %d", packet[0], length); uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; uint8_t shared_key[CRYPTO_SHARED_KEY_SIZE]; uint8_t dht_public_key_temp[CRYPTO_PUBLIC_KEY_SIZE]; @@ -462,104 +497,378 @@ static int handle_cookie_response(const Memory *mem, uint8_t *cookie, uint64_t * return COOKIE_LENGTH; } +/* +* TODO(goldroom): Helper function to print hashes, keys, packets, etc. +* TODO(goldroom): remove from production code or make dependent on MIN_LOGGER_LEVEL=DEBUG? +* bytes_to_string() from util.h +*/ +static void bytes2string(char *string, size_t string_length, const uint8_t *bytes, size_t bytes_length, const Logger *log) +{ + bytes_to_string(bytes, bytes_length, string, string_length); +} + +/* Non-noise: Necessary for backwards compatiblity to non-Noise handshake */ #define HANDSHAKE_PACKET_LENGTH (1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE + COOKIE_LENGTH + CRYPTO_MAC_SIZE) +/* Noise: Necessary for Noise-based handshake */ +#define NOISE_HANDSHAKE_PACKET_LENGTH_INITIATOR (1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE + COOKIE_LENGTH + COOKIE_LENGTH + CRYPTO_MAC_SIZE) +#define NOISE_HANDSHAKE_PACKET_LENGTH_RESPONDER (1 + CRYPTO_PUBLIC_KEY_SIZE + COOKIE_LENGTH + CRYPTO_MAC_SIZE) +#define NOISE_HANDSHAKE_PAYLOAD_PLAIN_LENGTH_INITIATOR (COOKIE_LENGTH + COOKIE_LENGTH) +#define NOISE_HANDSHAKE_PAYLOAD_PLAIN_LENGTH_RESPONDER (COOKIE_LENGTH) -/** @brief Create a handshake packet and put it in packet. - * @param cookie must be COOKIE_LENGTH bytes. +/** @brief Create a handshake packet and put it in packet. Currently supports noise-Noise and Noise handshake. + * + * cf. Noise section 5.3 -> WriteMessage(payload, message_buffer) + * + * @param peer_cookie must be COOKIE_LENGTH bytes. Cookie received from the peer. * @param packet must be of size HANDSHAKE_PACKET_LENGTH or bigger. + * @param send_nonce base nonce for this Tox instance, to be used for transport message encryption after non-Noise handshake + * @param ephemeral_private_key Ephemeral private X25519 key of this non-Noise handshake + * @param ephemeral_public_key Ephemeral public X25519 key of this non-Noise handshake + * @param peer_id_public_key X25519 static ID public key from peer to connect to + * @param peer_dht_public_key X25519 DHT public key from peer to connect to + * @param noise_handshake struct containing Noise information/values * * @retval -1 on failure. - * @retval HANDSHAKE_PACKET_LENGTH on success. - */ -non_null() -static int create_crypto_handshake(const Net_Crypto *c, uint8_t *packet, const uint8_t *cookie, const uint8_t *nonce, - const uint8_t *session_pk, const uint8_t *peer_real_pk, const uint8_t *peer_dht_pubkey) -{ - uint8_t plain[CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE + COOKIE_LENGTH]; - memcpy(plain, nonce, CRYPTO_NONCE_SIZE); - memcpy(plain + CRYPTO_NONCE_SIZE, session_pk, CRYPTO_PUBLIC_KEY_SIZE); - crypto_sha512(plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, cookie, COOKIE_LENGTH); - uint8_t cookie_plain[COOKIE_DATA_LENGTH]; - memcpy(cookie_plain, peer_real_pk, CRYPTO_PUBLIC_KEY_SIZE); - memcpy(cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, peer_dht_pubkey, CRYPTO_PUBLIC_KEY_SIZE); + * @retval HANDSHAKE_PACKET_LENGTH on success (non-Noise handshake). + * @retval NOISE_HANDSHAKE_PACKET_LENGTH_INITIATOR if Noise handshake initiator + * @retval NOISE_HANDSHAKE_PACKET_LENGTH_RESPONDER if Noise handshake responder + * + */ +non_null(1, 2, 3, 8) nullable(4, 5, 6, 7, 9) +static int create_crypto_handshake(const Net_Crypto *c, uint8_t *packet, const uint8_t peer_cookie[COOKIE_LENGTH], const uint8_t send_nonce[CRYPTO_NONCE_SIZE], const uint8_t ephemeral_private_key[CRYPTO_SECRET_KEY_SIZE], + const uint8_t ephemeral_public_key[CRYPTO_PUBLIC_KEY_SIZE], const uint8_t peer_id_public_key[CRYPTO_PUBLIC_KEY_SIZE], const uint8_t peer_dht_public_key[CRYPTO_PUBLIC_KEY_SIZE], Noise_Handshake *noise_handshake) +{ + /* Noise-based handshake */ + if (noise_handshake != nullptr) { + /* Noise INITIATOR: -> e, es, s, ss */ + /* Initiator: Handshake packet structure (Noise_IK_25519_ChaChaPoly_BLAKE2b) + [uint8_t 26] + [session public key of the peer (32 bytes)] => currently in plain + ~~[24 bytes nonce for static public key encryption]~~ NOT necessary for ChaCha20-Poly1305 + [encrypted static public key of the INITIATOR (32 bytes)] + [MAC encrypted static pulic key 16 bytes] + ~~[24 bytes nonce handshake payload encryption]~~ NOT necessary for ChaCha20-Poly1305 + [Encrypted message containing: + ~~[24 bytes base nonce]~~ => WITHOUT base Nonce, just a counter (not necessary with different symmetric keys for outgoing/incoming packets) + [Cookie 112 bytes] => RESPONDER cookie encrypted and authenticated via AEAD + [112 bytes other/INITIATOR cookie] => used by the RESPONDER peer to respond to the handshake packet + [MAC encrypted payload 16 bytes] + => 321 bytes in total + */ + if (noise_handshake->initiator) { + LOGGER_DEBUG(c->log, "Noise: INITIATOR"); + + /* e */ + memcpy(packet + 1, noise_handshake->ephemeral_public, CRYPTO_PUBLIC_KEY_SIZE); + noise_mix_hash(noise_handshake->hash, noise_handshake->ephemeral_public, CRYPTO_PUBLIC_KEY_SIZE); + + /* es */ + uint8_t noise_handshake_temp_key[CRYPTO_SHARED_KEY_SIZE]; + noise_mix_key(noise_handshake->chaining_key, noise_handshake_temp_key, noise_handshake->ephemeral_private, noise_handshake->remote_static); + + /* s */ + /* Nonce for static pub key encryption is _always_ 0 in case of ChaCha20-Poly1305 */ + noise_encrypt_and_hash(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, noise_handshake->static_public, CRYPTO_PUBLIC_KEY_SIZE, noise_handshake_temp_key, + noise_handshake->hash); + + /* ss */ + noise_mix_key(noise_handshake->chaining_key, noise_handshake_temp_key, noise_handshake->static_private, noise_handshake->remote_static); + + /* Noise Handshake Payload */ + uint8_t handshake_payload_plain[NOISE_HANDSHAKE_PAYLOAD_PLAIN_LENGTH_INITIATOR]; + + /* Noise: Cookie from RESPONDER */ + memcpy(handshake_payload_plain, peer_cookie, COOKIE_LENGTH); + + uint8_t cookie_plain[COOKIE_DATA_LENGTH]; + memcpy(cookie_plain, noise_handshake->remote_static, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, peer_dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + /* Cookies are currently double-encrypted, but decryption also necessary if they would be authenticated via AD */ + /* INITIATOR OtherCookie is added to payload */ + if (create_cookie(c->mem, c->rng, c->mono_time, handshake_payload_plain + COOKIE_LENGTH, + cookie_plain, c->cookie_symmetric_key) != 0) { + return -1; + } - if (create_cookie(c->mem, c->rng, c->mono_time, plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE, - cookie_plain, c->secret_symmetric_key) != 0) { - return -1; - } + /* Nonce for payload encryption is _always_ 0 in case of ChaCha20-Poly1305 */ + noise_encrypt_and_hash(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE, + handshake_payload_plain, sizeof(handshake_payload_plain), noise_handshake_temp_key, + noise_handshake->hash); - random_nonce(c->rng, packet + 1 + COOKIE_LENGTH); - const int len = encrypt_data(c->mem, peer_real_pk, c->self_secret_key, packet + 1 + COOKIE_LENGTH, plain, sizeof(plain), - packet + 1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE); + packet[0] = NET_PACKET_CRYPTO_HS; - if (len != HANDSHAKE_PACKET_LENGTH - (1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE)) { - return -1; - } + crypto_memzero(noise_handshake_temp_key, CRYPTO_SHARED_KEY_SIZE); + crypto_memzero(handshake_payload_plain, NOISE_HANDSHAKE_PAYLOAD_PLAIN_LENGTH_INITIATOR); + + return NOISE_HANDSHAKE_PACKET_LENGTH_INITIATOR; + } else { + /* Noise RESPONDER: <- e, ee, se */ + /* Responder: Handshake packet structure (Noise_IK_25519_ChaChaPoly_BLAKE2b) + [uint8_t 26] + [session public key of the peer (32 bytes)] => currently in plain + ~~[24 bytes nonce for handshake payload encryption]~~ NOT necessary for ChaCha20-Poly1305 + [Encrypted message containing: + ~~[24 bytes base nonce]~~ => WITHOUT base Nonce, just use a counter WITHOUT base Nonce, just a counter (not necessary with different symmetric keys for outgoing/incoming packets) + [Cookie 112 bytes] => INITIATOR cookie encrypted and authenticated via AEAD + ~~[112 bytes other/RESPONDER cookie]~~ NOT necessary for NoiseIK + [MAC encrypted payload 16 bytes] + => 161 bytes in total + */ + LOGGER_DEBUG(c->log, "Noise: RESPONDER"); + + /* e */ + memcpy(packet + 1, noise_handshake->ephemeral_public, CRYPTO_PUBLIC_KEY_SIZE); + noise_mix_hash(noise_handshake->hash, noise_handshake->ephemeral_public, CRYPTO_PUBLIC_KEY_SIZE); + + /* ee */ + uint8_t noise_handshake_temp_key[CRYPTO_SHARED_KEY_SIZE]; + noise_mix_key(noise_handshake->chaining_key, noise_handshake_temp_key, noise_handshake->ephemeral_private, noise_handshake->remote_ephemeral); + + /* se */ + noise_mix_key(noise_handshake->chaining_key, noise_handshake_temp_key, noise_handshake->ephemeral_private, noise_handshake->remote_static); + + /* Create Noise Handshake Payload */ + uint8_t handshake_payload_plain[NOISE_HANDSHAKE_PAYLOAD_PLAIN_LENGTH_RESPONDER]; + + /* Noise: Cookie from INITIATOR */ + memcpy(handshake_payload_plain, peer_cookie, COOKIE_LENGTH); + + /* Nonce for payload encryption is _always_ 0 in case of ChaCha20-Poly1305 */ + noise_encrypt_and_hash(packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, + handshake_payload_plain, sizeof(handshake_payload_plain), noise_handshake_temp_key, + noise_handshake->hash); + + packet[0] = NET_PACKET_CRYPTO_HS; + + crypto_memzero(noise_handshake_temp_key, CRYPTO_SHARED_KEY_SIZE); + crypto_memzero(handshake_payload_plain, NOISE_HANDSHAKE_PAYLOAD_PLAIN_LENGTH_RESPONDER); + + return NOISE_HANDSHAKE_PACKET_LENGTH_RESPONDER; + } + } else { /* non-Noise handshake, check for enabled backwards compatibility happens in create_send_handshake() */ + LOGGER_DEBUG(c->log, "non-Noise handshake"); + uint8_t plain[CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE + COOKIE_LENGTH]; + memcpy(plain, send_nonce, CRYPTO_NONCE_SIZE); + memcpy(plain + CRYPTO_NONCE_SIZE, ephemeral_public_key, CRYPTO_PUBLIC_KEY_SIZE); + crypto_sha512(plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE, peer_cookie, COOKIE_LENGTH); + uint8_t cookie_plain[COOKIE_DATA_LENGTH]; + memcpy(cookie_plain, peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, peer_dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + if (create_cookie(c->mem, c->rng, c->mono_time, plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE, + cookie_plain, c->cookie_symmetric_key) != 0) { + return -1; + } + + random_nonce(c->rng, packet + 1 + COOKIE_LENGTH); + const int len = encrypt_data(c->mem, peer_id_public_key, c->self_id_secret_key, packet + 1 + COOKIE_LENGTH, plain, sizeof(plain), + packet + 1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE); - packet[0] = NET_PACKET_CRYPTO_HS; - memcpy(packet + 1, cookie, COOKIE_LENGTH); + if (len != HANDSHAKE_PACKET_LENGTH - (1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE)) { + return -1; + } - return HANDSHAKE_PACKET_LENGTH; + packet[0] = NET_PACKET_CRYPTO_HS; + memcpy(packet + 1, peer_cookie, COOKIE_LENGTH); + + return HANDSHAKE_PACKET_LENGTH; + } } -/** @brief Handle a crypto handshake packet of length. - * put the nonce contained in the packet in nonce, - * the session public key in session_pk - * the real public key of the peer in peer_real_pk - * the dht public key of the peer in dht_public_key and - * the cookie inside the encrypted part of the packet in cookie. +/** @brief Handle a handshake packet (of packet_length), recieved cookie(s) and crypto material. + * Currently supports noise-Noise and Noise handshake. * - * if expected_real_pk isn't NULL it denotes the real public key - * the packet should be from. + * cf. Noise section 5.3 -> ReadMessage(payload, message_buffer) * - * nonce must be at least CRYPTO_NONCE_SIZE - * session_pk must be at least CRYPTO_PUBLIC_KEY_SIZE - * peer_real_pk must be at least CRYPTO_PUBLIC_KEY_SIZE - * cookie must be at least COOKIE_LENGTH + * @param packet received handshake packet. + * @param recv_nonce base nonce of the peers Tox instance to be used for transport message decryption after non-Noise handshake + * @param peer_ephemeral_public_key Ephemeral public X25519 key of the peers Tox instance for this handshake + * @param peer_id_public_key X25519 static ID public key from peer to connect to + * @param peer_dht_public_key X25519 DHT public key from peer to connect to + * @param peer_cookie must be COOKIE_LENGTH bytes. Cookie received from the peer. + * @param noise_handshake struct containing Noise information/values * * @retval false on failure. * @retval true on success. + * */ -non_null(1, 2, 3, 4, 5, 6, 7) nullable(9) -static bool handle_crypto_handshake(const Net_Crypto *c, uint8_t *nonce, uint8_t *session_pk, uint8_t *peer_real_pk, - uint8_t *dht_public_key, uint8_t *cookie, const uint8_t *packet, uint16_t length, const uint8_t *expected_real_pk) -{ - if (length != HANDSHAKE_PACKET_LENGTH) { - return false; - } +non_null(1, 5, 7) nullable(2, 3, 4, 6, 9, 10) +static bool handle_crypto_handshake(const Net_Crypto *c, uint8_t recv_nonce[CRYPTO_NONCE_SIZE], uint8_t peer_ephemeral_public_key[CRYPTO_PUBLIC_KEY_SIZE], uint8_t peer_id_public_key[CRYPTO_PUBLIC_KEY_SIZE], + uint8_t peer_dht_public_key[CRYPTO_PUBLIC_KEY_SIZE], uint8_t peer_cookie[COOKIE_LENGTH], const uint8_t *packet, uint16_t packet_length, const uint8_t expected_peer_id_pk[CRYPTO_PUBLIC_KEY_SIZE], + Noise_Handshake *noise_handshake) +{ + /* Noise-based handshake */ + if (noise_handshake != nullptr) { + LOGGER_DEBUG(c->log, "noise_handshake->initiator: %d", noise_handshake->initiator); + + uint8_t cookie_plain[COOKIE_DATA_LENGTH]; + + /* -> e, es, s, ss */ + /* Initiator: Handshake packet structure (Noise_IK_25519_ChaChaPoly_BLAKE2b) + [uint8_t 26] + [session public key of the peer (32 bytes)] => currently in plain + ~~[24 bytes nonce for static public key encryption]~~ NOT necessary for ChaCha20-Poly1305 + [encrypted static public key of the INITIATOR (32 bytes)] + [MAC encrypted static pulic key 16 bytes] + ~~[24 bytes nonce handshake payload encryption]~~ NOT necessary for ChaCha20-Poly1305 + [Encrypted message containing: + ~~[24 bytes base nonce]~~ => WITHOUT base Nonce, just a counter (not necessary with different symmetric keys for outgoing/incoming packets) + [Cookie 112 bytes] => RESPONDER cookie encrypted and authenticated via AEAD + [112 bytes other/INITIATOR cookie] => used by the RESPONDER peer to respond to the handshake packet + [MAC encrypted payload 16 bytes] + => 321 bytes in total + */ + if (!noise_handshake->initiator) { + // TODO(goldroom): Check here if remote_ephemeral is already the same ephemeral key? => should not be possible to call it twice + /* e */ + memcpy(noise_handshake->remote_ephemeral, packet + 1, CRYPTO_PUBLIC_KEY_SIZE); + noise_mix_hash(noise_handshake->hash, noise_handshake->remote_ephemeral, CRYPTO_PUBLIC_KEY_SIZE); + + /* es */ + uint8_t noise_handshake_temp_key[CRYPTO_SHARED_KEY_SIZE]; + noise_mix_key(noise_handshake->chaining_key, noise_handshake_temp_key, noise_handshake->static_private, noise_handshake->remote_ephemeral); + + /* s */ + /* Nonce for static pub key decryption is _always_ 0 in case of ChaCha20-Poly1305 */ + if (noise_decrypt_and_hash(noise_handshake->remote_static, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE, + noise_handshake_temp_key, noise_handshake->hash) != CRYPTO_PUBLIC_KEY_SIZE) { + LOGGER_DEBUG(c->log, "RESPONDER: Noise ReadMessage remote static decryption failed"); + return false; + } - uint8_t cookie_plain[COOKIE_DATA_LENGTH]; + /* ss */ + noise_mix_key(noise_handshake->chaining_key, noise_handshake_temp_key, noise_handshake->static_private, noise_handshake->remote_static); + /* Payload decryption */ + uint8_t handshake_payload_plain[NOISE_HANDSHAKE_PAYLOAD_PLAIN_LENGTH_INITIATOR]; + + /* Nonce for payload decryption is _always_ 0 in case of ChaCha20-Poly1305 */ + if (noise_decrypt_and_hash(handshake_payload_plain, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_MAC_SIZE, + sizeof(handshake_payload_plain) + CRYPTO_MAC_SIZE, noise_handshake_temp_key, + noise_handshake->hash) != sizeof(handshake_payload_plain)) { + LOGGER_DEBUG(c->log, "RESPONDER: Noise HS payload decryption failed"); + return false; + } - if (open_cookie(c->mem, c->mono_time, cookie_plain, packet + 1, c->secret_symmetric_key) != 0) { - return false; - } + crypto_memzero(noise_handshake_temp_key, CRYPTO_SHARED_KEY_SIZE); - if (expected_real_pk != nullptr && !pk_equal(cookie_plain, expected_real_pk)) { - return false; - } + /* Cookie is verified later than in non-Noise handshake, but this should be acceptable */ + if (open_cookie(c->mem, c->mono_time, cookie_plain, handshake_payload_plain, c->cookie_symmetric_key) != 0) { + return false; + } - uint8_t cookie_hash[CRYPTO_SHA512_SIZE]; - crypto_sha512(cookie_hash, packet + 1, COOKIE_LENGTH); + /* Compares static identity public keys from the peer */ + if (!pk_equal(cookie_plain, noise_handshake->remote_static)) { + return false; + } - uint8_t plain[CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE + COOKIE_LENGTH]; - const int len = decrypt_data(c->mem, cookie_plain, c->self_secret_key, packet + 1 + COOKIE_LENGTH, - packet + 1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE, - HANDSHAKE_PACKET_LENGTH - (1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE), plain); + /* Cookie necessary for Noise RESPONDER, used afterwards in create_send_handshake() */ + memcpy(peer_cookie, handshake_payload_plain + COOKIE_LENGTH, COOKIE_LENGTH); + /* Noise: not necessary for Noise (=remote static), but necessary for friend_connection.c:handle_new_connections() */ + if (peer_id_public_key != nullptr) { + memcpy(peer_id_public_key, noise_handshake->remote_static, CRYPTO_PUBLIC_KEY_SIZE); + } + /* necessary */ + memcpy(peer_dht_public_key, cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_PUBLIC_KEY_SIZE); - if (len != sizeof(plain)) { - return false; - } + crypto_memzero(handshake_payload_plain, NOISE_HANDSHAKE_PAYLOAD_PLAIN_LENGTH_INITIATOR); - if (!crypto_sha512_eq(cookie_hash, plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE)) { - return false; - } + return true; + } else { + /* Noise ReadMessage() if initiator: <- e, ee, se */ + /* Responder: Handshake packet structure (Noise_IK_25519_ChaChaPoly_BLAKE2b) + [uint8_t 26] + [session public key of the peer (32 bytes)] => currently in plain + ~~[24 bytes nonce for handshake payload encryption]~~ NOT necessary for ChaCha20-Poly1305 + [Encrypted message containing: + ~~[24 bytes base nonce]~~ => WITHOUT base Nonce, just use a counter WITHOUT base Nonce, just a counter (not necessary with different symmetric keys for outgoing/incoming packets) + [Cookie 112 bytes] => INITIATOR cookie encrypted and authenticated via AEAD + ~~[112 bytes other/RESPONDER cookie]~~ NOT necessary for NoiseIK + [MAC encrypted payload 16 bytes] + => 161 bytes in total + */ + memcpy(noise_handshake->remote_ephemeral, packet + 1, CRYPTO_PUBLIC_KEY_SIZE); + noise_mix_hash(noise_handshake->hash, noise_handshake->remote_ephemeral, CRYPTO_PUBLIC_KEY_SIZE); + + /* ee */ + uint8_t noise_handshake_temp_key[CRYPTO_SHARED_KEY_SIZE]; + noise_mix_key(noise_handshake->chaining_key, noise_handshake_temp_key, noise_handshake->ephemeral_private, noise_handshake->remote_ephemeral); + + /* se */ + noise_mix_key(noise_handshake->chaining_key, noise_handshake_temp_key, noise_handshake->static_private, noise_handshake->remote_ephemeral); + + /* Payload decryption */ + uint8_t handshake_payload_plain[NOISE_HANDSHAKE_PAYLOAD_PLAIN_LENGTH_RESPONDER]; + + /* Nonce for payload decryption is 0 in case of ChaCha20-Poly1305 */ + if (noise_decrypt_and_hash(handshake_payload_plain, packet + 1 + CRYPTO_PUBLIC_KEY_SIZE, + sizeof(handshake_payload_plain) + CRYPTO_MAC_SIZE, noise_handshake_temp_key, + noise_handshake->hash) != sizeof(handshake_payload_plain)) { + LOGGER_DEBUG(c->log, "INITIATOR: Noise ReadMessage decryption failed"); + return false; + } - memcpy(nonce, plain, CRYPTO_NONCE_SIZE); - memcpy(session_pk, plain + CRYPTO_NONCE_SIZE, CRYPTO_PUBLIC_KEY_SIZE); - memcpy(cookie, plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE, COOKIE_LENGTH); - memcpy(peer_real_pk, cookie_plain, CRYPTO_PUBLIC_KEY_SIZE); - memcpy(dht_public_key, cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_PUBLIC_KEY_SIZE); - return true; + crypto_memzero(noise_handshake_temp_key, CRYPTO_SHARED_KEY_SIZE); + + /* Cookie is verified later than in non-Noise handshake, but this should be acceptable */ + if (open_cookie(c->mem, c->mono_time, cookie_plain, handshake_payload_plain, c->cookie_symmetric_key) != 0) { + return false; + } + + /* Compares static identity public keys from the peer */ + if (expected_peer_id_pk != nullptr && !pk_equal(cookie_plain, expected_peer_id_pk)) { + return false; + } + + /* necessary */ + memcpy(peer_dht_public_key, cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_PUBLIC_KEY_SIZE); + + crypto_memzero(handshake_payload_plain, NOISE_HANDSHAKE_PAYLOAD_PLAIN_LENGTH_RESPONDER); + + return true; + } + } else { /* non-Noise handshake, check for enabled backwards compatibility happens in calling functions */ + LOGGER_DEBUG(c->log, "non-Noise handshake"); + + if (packet_length != HANDSHAKE_PACKET_LENGTH) { + return false; + } + + uint8_t cookie_plain[COOKIE_DATA_LENGTH]; + + if (open_cookie(c->mem, c->mono_time, cookie_plain, packet + 1, c->cookie_symmetric_key) != 0) { + return false; + } + + /* Compares static identity public keys from the peer */ + if (expected_peer_id_pk != nullptr && !pk_equal(cookie_plain, expected_peer_id_pk)) { + return false; + } + + uint8_t cookie_hash[CRYPTO_SHA512_SIZE]; + crypto_sha512(cookie_hash, packet + 1, COOKIE_LENGTH); + + uint8_t plain[CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE + COOKIE_LENGTH]; + const int len = decrypt_data(c->mem, cookie_plain, c->self_id_secret_key, packet + 1 + COOKIE_LENGTH, + packet + 1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE, + HANDSHAKE_PACKET_LENGTH - (1 + COOKIE_LENGTH + CRYPTO_NONCE_SIZE), plain); + + if (len != sizeof(plain)) { + return false; + } + + if (!crypto_sha512_eq(cookie_hash, plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE)) { + return false; + } + + memcpy(recv_nonce, plain, CRYPTO_NONCE_SIZE); + memcpy(peer_ephemeral_public_key, plain + CRYPTO_NONCE_SIZE, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(peer_cookie, plain + CRYPTO_NONCE_SIZE + CRYPTO_PUBLIC_KEY_SIZE + CRYPTO_SHA512_SIZE, COOKIE_LENGTH); + memcpy(peer_id_public_key, cookie_plain, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(peer_dht_public_key, cookie_plain + CRYPTO_PUBLIC_KEY_SIZE, CRYPTO_PUBLIC_KEY_SIZE); + + return true; + } } non_null() @@ -1060,7 +1369,9 @@ static int handle_request_packet(const Memory *mem, const Mono_Time *mono_time, #define MAX_DATA_DATA_PACKET_SIZE (MAX_CRYPTO_PACKET_SIZE - (1 + sizeof(uint16_t) + CRYPTO_MAC_SIZE)) -/** @brief Creates and sends a data packet to the peer using the fastest route. +/** @brief Creates and sends a data packet to the peer using the fastest route. Currently only supports Noise (XChaCha20-Poly1305) + * transport encryption. + * * * @retval -1 on failure. * @retval 0 on success. @@ -1085,15 +1396,23 @@ static int send_data_packet(const Net_Crypto *c, int crypt_connection_id, const const uint16_t packet_size = 1 + sizeof(uint16_t) + length + CRYPTO_MAC_SIZE; VLA(uint8_t, packet, packet_size); packet[0] = NET_PACKET_CRYPTO_DATA; - memcpy(packet + 1, conn->sent_nonce + (CRYPTO_NONCE_SIZE - sizeof(uint16_t)), sizeof(uint16_t)); - const int len = encrypt_data_symmetric(c->mem, conn->shared_key, conn->sent_nonce, data, length, packet + 1 + sizeof(uint16_t)); + memcpy(packet + 1, conn->send_nonce + (CRYPTO_NONCE_SIZE - sizeof(uint16_t)), sizeof(uint16_t)); + + // TODO(goldroom): const len when backwards compatiblity removed + int len = 0; + if (conn->noise_handshake_enabled) { /* Case NoiseIK handshake */ + // TODO(goldroom): no data authenticated as AD (also none in WireGuard) + len = encrypt_data_symmetric_xaead(conn->send_key, conn->send_nonce, data, length, packet + 1 + sizeof(uint16_t), nullptr, 0); + } else { /* Case non-Noise handshake */ + len = encrypt_data_symmetric(c->mem, conn->shared_key, conn->send_nonce, data, length, packet + 1 + sizeof(uint16_t)); + } if (len + 1 + sizeof(uint16_t) != packet_size) { LOGGER_ERROR(c->log, "encryption failed: %d", len); return -1; } - increment_nonce(conn->sent_nonce); + increment_nonce(conn->send_nonce); return send_packet_to(c, crypt_connection_id, packet, packet_size); } @@ -1230,6 +1549,9 @@ static uint16_t get_nonce_uint16(const uint8_t *nonce) /** @brief Handle a data packet. * Decrypt packet of length and put it into data. * data must be at least MAX_DATA_DATA_PACKET_SIZE big. + * Currently only supports Noise (XChaCha20-Poly1305) + * transport decryption. + * * * @retval -1 on failure. * @return length of data on success. @@ -1257,8 +1579,17 @@ static int handle_data_packet(const Net_Crypto *c, int crypt_connection_id, uint net_unpack_u16(packet + 1, &num); const uint16_t diff = num - num_cur_nonce; increment_nonce_number(nonce, diff); - const int len = decrypt_data_symmetric(c->mem, conn->shared_key, nonce, packet + 1 + sizeof(uint16_t), + + // TODO(goldroom): const len when backwards compatiblity removed + int len = 0; + if (conn->noise_handshake_enabled) { /* case NoiseIK handshake */ + // TODO(goldroom): no data authenticated as AD (also none in WireGuard) + len = decrypt_data_symmetric_xaead(conn->recv_key, nonce, packet + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), data, + nullptr, 0); + } else { /* case non-Noise handshake */ + len = decrypt_data_symmetric(c->mem, conn->shared_key, nonce, packet + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), data); + } if ((unsigned int)len != length - crypto_packet_overhead) { return -1; @@ -1437,7 +1768,8 @@ static int send_temp_packet(const Net_Crypto *c, int crypt_connection_id) return 0; } -/** @brief Create a handshake packet and set it as a temp packet. +/** @brief Create a handshake packet and set it as a temp packet. Currently supports non-Noise and + * Noise-based handshake. * @param cookie must be COOKIE_LENGTH. * * @retval -1 on failure. @@ -1453,14 +1785,46 @@ static int create_send_handshake(const Net_Crypto *c, int crypt_connection_id, c return -1; } - uint8_t handshake_packet[HANDSHAKE_PACKET_LENGTH]; + /* Noise-based handshake */ + if (conn->noise_handshake_enabled && conn->noise_handshake != nullptr) { + LOGGER_DEBUG(c->log, "conn->noise_handshake->initiator: %d", conn->noise_handshake->initiator); + if (conn->noise_handshake->initiator) { + uint8_t handshake_packet[NOISE_HANDSHAKE_PACKET_LENGTH_INITIATOR]; - if (create_crypto_handshake(c, handshake_packet, cookie, conn->sent_nonce, conn->sessionpublic_key, - conn->public_key, dht_public_key) != sizeof(handshake_packet)) { - return -1; - } + if (create_crypto_handshake(c, handshake_packet, cookie, nullptr, conn->ephemeral_secret_key, conn->ephemeral_public_key, + conn->noise_handshake->remote_static, dht_public_key, conn->noise_handshake) != sizeof(handshake_packet)) { + return -1; + } + + if (new_temp_packet(c, crypt_connection_id, handshake_packet, sizeof(handshake_packet)) != 0) { + return -1; + } + } else { /* Noise RESPONDER */ + uint8_t handshake_packet[NOISE_HANDSHAKE_PACKET_LENGTH_RESPONDER]; - if (new_temp_packet(c, crypt_connection_id, handshake_packet, sizeof(handshake_packet)) != 0) { + if (create_crypto_handshake(c, handshake_packet, cookie, nullptr, conn->ephemeral_secret_key, conn->ephemeral_public_key, + conn->noise_handshake->remote_static, dht_public_key, conn->noise_handshake) != sizeof(handshake_packet)) { + return -1; + } + + if (new_temp_packet(c, crypt_connection_id, handshake_packet, sizeof(handshake_packet)) != 0) { + return -1; + } + } + } else if(c->noise_compatibility_enabled) { /* non-Noise handshake*/ + LOGGER_DEBUG(c->log, "non-Noise handshake"); + uint8_t handshake_packet[HANDSHAKE_PACKET_LENGTH]; + + /* ephemeral_private and noise_handshake not necessary for old handshake */ + if (create_crypto_handshake(c, handshake_packet, cookie, conn->send_nonce, nullptr, conn->ephemeral_public_key, + conn->peer_id_public_key, dht_public_key, nullptr) != sizeof(handshake_packet)) { + return -1; + } + + if (new_temp_packet(c, crypt_connection_id, handshake_packet, sizeof(handshake_packet)) != 0) { + return -1; + } + } else { return -1; } @@ -1483,6 +1847,9 @@ static int send_kill_packet(const Net_Crypto *c, int crypt_connection_id) } const uint8_t kill_packet[1] = {PACKET_ID_KILL}; + + LOGGER_DEBUG(c->log, ">"); + return send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, conn->send_array.buffer_end, kill_packet, sizeof(kill_packet)); } @@ -1492,6 +1859,8 @@ static void connection_kill(Net_Crypto *c, int crypt_connection_id, void *userda { const Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + LOGGER_DEBUG(c->log, ">"); + if (conn == nullptr) { return; } @@ -1527,6 +1896,14 @@ static int handle_data_packet_core(Net_Crypto *c, int crypt_connection_id, const const int len = handle_data_packet(c, crypt_connection_id, data, packet, length); if (len <= (int)(sizeof(uint32_t) * 2)) { + // TODO(goldroom): remove before merge? + char log_id_public[CRYPTO_PUBLIC_KEY_SIZE*2+1]; + bytes2string(log_id_public, sizeof(log_id_public), conn->peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE, c->log); + // TODO(goldroom): remove print of static id public key before merge? + LOGGER_DEBUG(c->log, "decryption failure/crypt_connection_id: %d/conn->status: %d/peer_id_public_key: %s", crypt_connection_id, conn->status, conn->peer_id_public_key); + // TODO(goldroom): unwanted side effects? => yes, happens in practice => but why? + // TODO(goldroom): Enables DoS attacks? + // connection_kill(c, crypt_connection_id, userdata); return -1; } @@ -1564,6 +1941,7 @@ static int handle_data_packet_core(Net_Crypto *c, int crypt_connection_id, const } if (real_data[0] == PACKET_ID_KILL) { + LOGGER_DEBUG(c->log, "KILL PACKET RECEIVED crypt_connection_id: %d/conn->status: %d", crypt_connection_id, conn->status); connection_kill(c, crypt_connection_id, userdata); return 0; } @@ -1572,6 +1950,26 @@ static int handle_data_packet_core(Net_Crypto *c, int crypt_connection_id, const clear_temp_packet(c, crypt_connection_id); conn->status = CRYPTO_CONN_ESTABLISHED; + // TODO(goldroom): remove before merge? + char log_id_public[CRYPTO_PUBLIC_KEY_SIZE*2+1]; + bytes2string(log_id_public, sizeof(log_id_public), conn->peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE, c->log); + // TODO(goldroom): remove print of static id public key before merge? + LOGGER_DEBUG(c->log, "CRYPTO_CONN_ESTABLISHED/crypt_connection_id: %d/conn->status: %d/peer_id_public_key: %s", crypt_connection_id, conn->status, log_id_public); + + /* Noise: noise_handshake not necessary anymore => memzero and free */ + if (conn->noise_handshake != nullptr) { + crypto_memzero(conn->noise_handshake, sizeof(Noise_Handshake)); + /* mem_delete(c->mem, conn->noise_handshake)/conn->noise_handshake = nullptr: + not possible here, memory possibly needed in handle_new_connection_handshake() */ + } + + /* also crypto_memzero() non-Noise values from crypto connection */ + crypto_memzero(conn->ephemeral_secret_key, CRYPTO_SECRET_KEY_SIZE); + crypto_memzero(conn->ephemeral_public_key, CRYPTO_PUBLIC_KEY_SIZE); + // TODO(goldroom): no memzero for testing purposes, memzero for merge? + // crypto_memzero(conn->peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE); + crypto_memzero(conn->peer_ephemeral_public_key, CRYPTO_PUBLIC_KEY_SIZE); + if (conn->connection_status_callback != nullptr) { conn->connection_status_callback(conn->connection_status_callback_object, conn->connection_status_callback_id, true, userdata); @@ -1649,6 +2047,12 @@ static int handle_data_packet_core(Net_Crypto *c, int crypt_connection_id, const } non_null() +/** + * @brief Handles a cookie response packet. Currently supports non-Noise and Noise-bashed handshake. + * + * @return -1 in case of failure + * @return 0 if cookie response handled successfully + */ static int handle_packet_cookie_response(const Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); @@ -1657,6 +2061,13 @@ static int handle_packet_cookie_response(const Net_Crypto *c, int crypt_connecti return -1; } + // TODO(goldroom): remove before merge? + char log_id_public[CRYPTO_PUBLIC_KEY_SIZE*2+1]; + bytes2string(log_id_public, sizeof(log_id_public), conn->peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE, c->log); + // TODO(goldroom): remove print of static id public key before merge? + LOGGER_DEBUG(c->log, "Packet: %d/length: %d/crypt_connection_id: %d/conn->status: %d/peer_id_public_key: %s", + packet[0], length, crypt_connection_id, conn->status, log_id_public); + if (conn->status != CRYPTO_CONN_COOKIE_REQUESTING) { return -1; } @@ -1672,7 +2083,22 @@ static int handle_packet_cookie_response(const Net_Crypto *c, int crypt_connecti return -1; } - if (create_send_handshake(c, crypt_connection_id, cookie, conn->dht_public_key) != 0) { + if (conn->noise_handshake != nullptr) { + if (conn->noise_handshake->initiator) { + LOGGER_DEBUG(c->log, "INITIATOR: Noise handshake"); + if (create_send_handshake(c, crypt_connection_id, cookie, conn->peer_dht_public_key) != 0) { + return -1; + } + } else { + return -1; + } + } else if (c->noise_compatibility_enabled) { + /* non-Noise handshake */ + LOGGER_DEBUG(c->log, "non-Noise handshake"); + if (create_send_handshake(c, crypt_connection_id, cookie, conn->peer_dht_public_key) != 0) { + return -1; + } + } else { return -1; } @@ -1681,7 +2107,13 @@ static int handle_packet_cookie_response(const Net_Crypto *c, int crypt_connecti } non_null(1, 3) nullable(5) -static int handle_packet_crypto_hs(const Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length, +/** + * @brief Handles receive handshake packets. Currently supports non-Noise and Noise-based handshake. + * + * @return -1 in case of failure + * @return 0 if successful + */ +static int handle_packet_crypto_hs(Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length, void *userdata) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); @@ -1690,31 +2122,161 @@ static int handle_packet_crypto_hs(const Net_Crypto *c, int crypt_connection_id, return -1; } - if (conn->status != CRYPTO_CONN_COOKIE_REQUESTING + // TODO(goldroom): remove before merge? + char log_id_public[CRYPTO_PUBLIC_KEY_SIZE*2+1]; + bytes2string(log_id_public, sizeof(log_id_public), conn->peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE, c->log); + // TODO(goldroom): remove print of static id public key before merge? + LOGGER_DEBUG(c->log, "Packet: %d/length: %d/crypt_connection_id: %d/conn->status: %d/peer_id_public_key: %s", + packet[0], length, crypt_connection_id, conn->status, log_id_public); + + if (conn->noise_handshake != nullptr) { + // TODO(goldroom): removing CRYPTO_CONN_NOT_CONFIRMED breaks auto_reconnect_test in bazel-asan + if (conn->status != CRYPTO_CONN_COOKIE_REQUESTING + && conn->status != CRYPTO_CONN_HANDSHAKE_SENT) { + LOGGER_DEBUG(c->log, "NoiseIK: already handled handshake packet"); + return -1; + } + // if (conn->status != CRYPTO_CONN_COOKIE_REQUESTING + // && conn->status != CRYPTO_CONN_HANDSHAKE_SENT + // && conn->status != CRYPTO_CONN_NOT_CONFIRMED) { + // return -1; + // } + } else { + if (conn->status != CRYPTO_CONN_COOKIE_REQUESTING && conn->status != CRYPTO_CONN_HANDSHAKE_SENT && conn->status != CRYPTO_CONN_NOT_CONFIRMED) { - return -1; + return -1; + } } - uint8_t peer_real_pk[CRYPTO_PUBLIC_KEY_SIZE]; + /* necessary for Noise and non-Noise */ uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + /* necessary for Noise RESPONDER and non-Noise */ uint8_t cookie[COOKIE_LENGTH]; - if (!handle_crypto_handshake(c, conn->recv_nonce, conn->peersessionpublic_key, peer_real_pk, dht_public_key, cookie, - packet, length, conn->public_key)) { - return -1; + /* necessary for compatiblity to non-Noise handshake; + check for conn->noise_handshake_enabled necessary to not switch again after handle_new_connection_handshake() + and create new crypto material. */ + if (length == HANDSHAKE_PACKET_LENGTH && c->noise_compatibility_enabled && conn->noise_handshake_enabled) { + if (conn->noise_handshake != nullptr) { + /* non-Noise: noise_handshake not necessary anymore => memzero and free */ + crypto_memzero(conn->noise_handshake, sizeof(Noise_Handshake)); + mem_delete(c->mem, conn->noise_handshake); + conn->noise_handshake = nullptr; + } + conn->noise_handshake_enabled = false; + /* Ephemeral key pair needed in non-Noise handshake */ + crypto_new_keypair(c->rng, conn->ephemeral_public_key, conn->ephemeral_secret_key); + /* Random nonce needed in non-Noise handshake */ + random_nonce(c->rng, conn->send_nonce); + LOGGER_DEBUG(c->log, "Switch to non-Noise handshake"); } - if (pk_equal(dht_public_key, conn->dht_public_key)) { - encrypt_precompute(conn->peersessionpublic_key, conn->sessionsecret_key, conn->shared_key); + if (conn->noise_handshake_enabled) { + if (conn->noise_handshake == nullptr) { // TODO(goldroom): Is this check necessary? + return -1; + } + LOGGER_DEBUG(c->log, "conn->noise_handshake->initiator: %d", conn->noise_handshake->initiator); + if (conn->noise_handshake->initiator) { + if (length == NOISE_HANDSHAKE_PACKET_LENGTH_RESPONDER) { + LOGGER_DEBUG(c->log, "INITIATOR: Noise handshake -> normal"); + if (!handle_crypto_handshake(c, nullptr, nullptr, nullptr, dht_public_key, nullptr, + packet, length, nullptr, conn->noise_handshake)) { + return -1; + } + } else if (length == NOISE_HANDSHAKE_PACKET_LENGTH_INITIATOR) { /* At least in auto tests this only happens for UDP connections */ + LOGGER_DEBUG(c->log, "INITIATOR: Noise handshake -> CHANGE TO RESPONDER"); + crypto_memzero(conn->noise_handshake, sizeof(Noise_Handshake)); + if (noise_handshake_init(conn->noise_handshake, c->self_id_secret_key, nullptr, false, nullptr, 0) != 0) { + return -1; + } + + /* Noise: create and set ephemeral private+public */ + crypto_new_keypair(c->rng, conn->noise_handshake->ephemeral_public, conn->noise_handshake->ephemeral_private); - if (conn->status == CRYPTO_CONN_COOKIE_REQUESTING) { - if (create_send_handshake(c, crypt_connection_id, cookie, dht_public_key) != 0) { + /* Noise: peer_id_public_key (=conn->peer_id_public_key) not necessary for NoiseIK */ + if (!handle_crypto_handshake(c, nullptr, nullptr, nullptr, dht_public_key, cookie, + packet, length, nullptr, conn->noise_handshake)) { + return -1; + } + + /* Noise RESPONDER needs to send handshake packet, afterwards finished */ + if (create_send_handshake(c, crypt_connection_id, cookie, dht_public_key) != 0) { + return -1; + } + + conn->status = CRYPTO_CONN_HANDSHAKE_SENT; + } else { + return -1; + } + } else { /* Case where RESPONDER with and without change from INITIATOR */ + // TODO(goldroom): if status CRYPTO_CONN_NOT_CONFIRMED is handled this does happen (which is necessary for auto_reconnect_test) + if (length == NOISE_HANDSHAKE_PACKET_LENGTH_INITIATOR) { + LOGGER_DEBUG(c->log, "RESPONDER: Noise handshake -> NOISE_HANDSHAKE_PACKET_LENGTH_INITIATOR"); + /* necessary, otherwise broken after INITIATOR to RESPONDER change; also necessary without change */ + crypto_memzero(conn->noise_handshake, sizeof(Noise_Handshake)); + if (noise_handshake_init(conn->noise_handshake, c->self_id_secret_key, nullptr, false, nullptr, 0) != 0) { + return -1; + } + + /* Noise: create and set ephemeral private+public */ + crypto_new_keypair(c->rng, conn->noise_handshake->ephemeral_public, conn->noise_handshake->ephemeral_private); + + /* Noise: peer_real_pk (=conn->public_key) not necessary here */ + if (!handle_crypto_handshake(c, nullptr, nullptr, nullptr, dht_public_key, cookie, + packet, length, nullptr, conn->noise_handshake)) { + return -1; + } + /* RESPONDER needs to send handshake packet, afterwards finished */ + if (create_send_handshake(c, crypt_connection_id, cookie, dht_public_key) != 0) { + return -1; + } + // TODO(goldroom): here? + conn->status = CRYPTO_CONN_HANDSHAKE_SENT; + } else if (length == NOISE_HANDSHAKE_PACKET_LENGTH_RESPONDER) { + /* cannot change to INITIATOR here, connection broken */ + // TODO(goldroom): if status CRYPTO_CONN_NOT_CONFIRMED is handled this does happen (which is necessary for auto_reconnect_test) + LOGGER_DEBUG(c->log, "RESPONDER: NOISE_HANDSHAKE_PACKET_LENGTH_RESPONDER"); + // connection_kill(c, crypt_connection_id, userdata); // TODO(goldroom): leave here? leads to weird behavior in real-world tests return -1; } } + } else { /* non-Noise handshake */ + LOGGER_DEBUG(c->log, "non-Noise handshake"); + /* necessary only for non-Noise */ + uint8_t peer_id_public_key[CRYPTO_PUBLIC_KEY_SIZE]; + if (!handle_crypto_handshake(c, conn->recv_nonce, conn->peer_ephemeral_public_key, peer_id_public_key, dht_public_key, cookie, + packet, length, conn->peer_id_public_key, nullptr)) { + return -1; + } + } - conn->status = CRYPTO_CONN_NOT_CONFIRMED; + // TODO(goldroom): adapt for NoiseIK, makes only sense for RESPONDER not initiator? + if (pk_equal(dht_public_key, conn->peer_dht_public_key)) { + if (conn->noise_handshake_enabled && conn->noise_handshake != nullptr) { + conn->status = CRYPTO_CONN_NOT_CONFIRMED; + LOGGER_DEBUG(c->log, "conn->status: set to CRYPTO_CONN_NOT_CONFIRMED (%d)", conn->status); + if (conn->noise_handshake->initiator) { + /* INITIATOR Noise Split(), nonces already set in crypto connection */ + crypto_hkdf(conn->send_key, CRYPTO_SYMMETRIC_KEY_SIZE, conn->recv_key, CRYPTO_SYMMETRIC_KEY_SIZE, nullptr, 0, conn->noise_handshake->chaining_key); + LOGGER_DEBUG(c->log, "INITIATOR: After Noise Split()"); + } else { /* Noise RESPONDER */ + /* RESPONDER Noise Split(): vice-verse keys in comparison to initiator */ + crypto_hkdf(conn->recv_key, CRYPTO_SYMMETRIC_KEY_SIZE, conn->send_key, CRYPTO_SYMMETRIC_KEY_SIZE, nullptr, 0, conn->noise_handshake->chaining_key); + LOGGER_DEBUG(c->log, "RESPONDER: After Noise Split()"); + } + } else { /* Backwards compatibility: non-Noise handshake case */ + LOGGER_DEBUG(c->log, "non-Noise handshake"); + // if (conn->status == CRYPTO_CONN_COOKIE_REQUESTING) { // TODO(goldroom): doesn't work if Noise handshake packet was sent before + if (create_send_handshake(c, crypt_connection_id, cookie, dht_public_key) != 0) { + return -1; + } + // } + /* Backwards compatibility: necessary for non-Noise handshake */ + encrypt_precompute(conn->peer_ephemeral_public_key, conn->ephemeral_secret_key, conn->shared_key); + /* Backwards compatibility: necessary for non-Noise handshake */ + conn->status = CRYPTO_CONN_NOT_CONFIRMED; + } } else { if (conn->dht_pk_callback != nullptr) { conn->dht_pk_callback(conn->dht_pk_callback_object, conn->dht_pk_callback_number, dht_public_key, userdata); @@ -1734,6 +2296,14 @@ static int handle_packet_crypto_data(Net_Crypto *c, int crypt_connection_id, con return -1; } + // TODO(goldroom): remove before merge? + char log_id_public[CRYPTO_PUBLIC_KEY_SIZE*2+1]; + bytes2string(log_id_public, sizeof(log_id_public), conn->peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE, c->log); + // TODO(goldroom): remove print of static id public key before merge? + LOGGER_DEBUG(c->log, "Packet: %d/length: %d/crypt_connection_id: %d/conn->status: %d/peer_id_public_key: %s", + packet[0], length, crypt_connection_id, conn->status, log_id_public); + + if (conn->status != CRYPTO_CONN_NOT_CONFIRMED && conn->status != CRYPTO_CONN_ESTABLISHED) { return -1; } @@ -1791,6 +2361,7 @@ static int realloc_cryptoconnection(Net_Crypto *c, uint32_t num) } c->crypto_connections = newcrypto_connections; + return 0; } @@ -1802,6 +2373,8 @@ static int realloc_cryptoconnection(Net_Crypto *c, uint32_t num) non_null() static int create_crypto_connection(Net_Crypto *c) { + LOGGER_DEBUG(c->log, ">"); + int id = -1; for (uint32_t i = 0; i < c->crypto_connections_length; ++i) { @@ -1820,6 +2393,13 @@ static int create_crypto_connection(Net_Crypto *c) } if (id != -1) { + c->crypto_connections[id].noise_handshake = (Noise_Handshake *) mem_alloc(c->mem, sizeof(Noise_Handshake)); + + if (c->crypto_connections[id].noise_handshake == nullptr) { + LOGGER_ERROR(c->log, "failed to alloc noise_handshake"); + return -1; + } + // Memsetting float/double to 0 is non-portable, so we explicitly set them to 0 c->crypto_connections[id].packet_recv_rate = 0.0; c->crypto_connections[id].packet_send_rate = 0.0; @@ -1829,6 +2409,8 @@ static int create_crypto_connection(Net_Crypto *c) // TODO(Green-Sky): This enum is likely unneeded and the same as FREE. c->crypto_connections[id].status = CRYPTO_CONN_NO_CONNECTION; + + LOGGER_DEBUG(c->log, "crypt_connection_id: %d", id); } return id; @@ -1842,6 +2424,8 @@ static int create_crypto_connection(Net_Crypto *c) non_null() static int wipe_crypto_connection(Net_Crypto *c, int crypt_connection_id) { + LOGGER_DEBUG(c->log, ">"); + if ((uint32_t)crypt_connection_id >= c->crypto_connections_length) { return -1; } @@ -1858,6 +2442,14 @@ static int wipe_crypto_connection(Net_Crypto *c, int crypt_connection_id) uint32_t i; + /* NoiseIK: necessary for backwards compatibility and because after CRYPTO_CONN_ESTABLISHED noise_handshake is already memzeroed/freed */ + // TODO(goldroom): refactor if backwards compatiblity removed + if (c->crypto_connections[crypt_connection_id].noise_handshake != nullptr) { + crypto_memzero(c->crypto_connections[crypt_connection_id].noise_handshake, sizeof(Noise_Handshake)); + mem_delete(c->mem, c->crypto_connections[crypt_connection_id].noise_handshake); + c->crypto_connections[crypt_connection_id].noise_handshake = nullptr; + } + crypto_memzero(&c->crypto_connections[crypt_connection_id], sizeof(Crypto_Connection)); /* check if we can resize the connections array */ @@ -1888,7 +2480,7 @@ static int getcryptconnection_id(const Net_Crypto *c, const uint8_t *public_key) continue; } - if (pk_equal(public_key, c->crypto_connections[i].public_key)) { + if (pk_equal(public_key, c->crypto_connections[i].peer_id_public_key)) { return i; } } @@ -1956,7 +2548,7 @@ void new_connection_handler(Net_Crypto *c, new_connection_cb *new_connection_cal * @retval 0 on success. */ non_null(1, 2, 3) nullable(5) -static int handle_new_connection_handshake(Net_Crypto *c, const IP_Port *source, const uint8_t *data, uint16_t length, +static int handle_new_connection_handshake(Net_Crypto *c, const IP_Port *source, const uint8_t *packet, uint16_t length, void *userdata) { uint8_t *cookie = (uint8_t *)mem_balloc(c->mem, COOKIE_LENGTH); @@ -1966,68 +2558,163 @@ static int handle_new_connection_handshake(Net_Crypto *c, const IP_Port *source, } New_Connection n_c = {{{{0}}}}; - n_c.cookie = cookie; + n_c.peer_cookie = cookie; n_c.source = *source; n_c.cookie_length = COOKIE_LENGTH; - if (!handle_crypto_handshake(c, n_c.recv_nonce, n_c.peersessionpublic_key, n_c.public_key, n_c.dht_public_key, - n_c.cookie, data, length, nullptr)) { - mem_delete(c->mem, n_c.cookie); + + /* Backwards comptability: Differention between non-Noise and Noise-based handshake based on received HS packet length */ + // TODO(goldroom): In tests this also happens for Noise INITIATOR => how to handle this case? + if (length != HANDSHAKE_PACKET_LENGTH) { + // TODO(goldroom): adapt static allocation? + n_c.noise_handshake = &n_c.noise_handshake_data; + if (noise_handshake_init(n_c.noise_handshake, c->self_id_secret_key, nullptr, false, nullptr, 0) != 0) { + crypto_memzero(n_c.noise_handshake, sizeof(Noise_Handshake)); + n_c.noise_handshake = nullptr; + mem_delete(c->mem, n_c.peer_cookie); + return -1; + } + + /* Noise: create and set ephemeral private+public */ + // TODO(goldroom): can this be moved to accept_crypto_connection()? + crypto_new_keypair(c->rng, n_c.noise_handshake->ephemeral_public, n_c.noise_handshake->ephemeral_private); + + LOGGER_DEBUG(c->log, "Noise RESPONDER: After Handshake init"); + + /* Noise: peer_id_public_key (=n_c.peer_id_public_key) not necessary for Noise, but need to include -> otherwise not working (call via friend_connection.c) */ + // TODO(goldroom): adapt peer_id_public_key (=n_c.peer_id_public_key) for Noise? + if (!handle_crypto_handshake(c, nullptr, nullptr, n_c.peer_id_public_key, n_c.peer_dht_public_key, + n_c.peer_cookie, packet, length, nullptr, n_c.noise_handshake)) { + crypto_memzero(n_c.noise_handshake, sizeof(Noise_Handshake)); + n_c.noise_handshake = nullptr; + mem_delete(c->mem, n_c.peer_cookie); + return -1; + } + } else if (c->noise_compatibility_enabled) { /* case non-Noise handshake */ + LOGGER_DEBUG(c->log, "non-Noise handshake"); + /* Necessary for backwards compatibility */ + n_c.noise_handshake = nullptr; + if (!handle_crypto_handshake(c, n_c.recv_nonce, n_c.peer_ephemeral_public_key, n_c.peer_id_public_key, n_c.peer_dht_public_key, + n_c.peer_cookie, packet, length, nullptr, nullptr)) { + mem_delete(c->mem, n_c.peer_cookie); + return -1; + } + } else { return -1; } - const int crypt_connection_id = getcryptconnection_id(c, n_c.public_key); + char log_id_public[CRYPTO_PUBLIC_KEY_SIZE*2+1]; + bytes2string(log_id_public, sizeof(log_id_public), n_c.peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE, c->log); + LOGGER_DEBUG(c->log, ": peer_id_public_key: %s", log_id_public); + const int crypt_connection_id = getcryptconnection_id(c, n_c.peer_id_public_key); + + /* This is only called if a crypto_connection already exists (e.g. new_crypto_connection() was already called)! Now Noise RESPONDER! */ + /* happens NoiseIK handshake (e.g. auto_tox_many_test) */ + // TODO(goldroom): why is this called twice in row via tcp_oob_callback()? if (crypt_connection_id != -1) { + // TODO(goldroom): remove before merge? + bytes2string(log_id_public, sizeof(log_id_public), n_c.peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE, c->log); + // TODO(goldroom): remove print of static id public key before merge? + LOGGER_DEBUG(c->log, "RESPONDER: CRYPTO CONN EXISTING -> crypt_connection_id: %d/peer_id_public_key: %s", crypt_connection_id, log_id_public); Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == nullptr) { return -1; } - if (!pk_equal(n_c.dht_public_key, conn->dht_public_key)) { + if (!pk_equal(n_c.peer_dht_public_key, conn->peer_dht_public_key)) { connection_kill(c, crypt_connection_id, userdata); - } else { + } else if(length != HANDSHAKE_PACKET_LENGTH) { /* case NoiseIK handshake */ if (conn->status != CRYPTO_CONN_COOKIE_REQUESTING && conn->status != CRYPTO_CONN_HANDSHAKE_SENT) { - mem_delete(c->mem, n_c.cookie); + mem_delete(c->mem, n_c.peer_cookie); return -1; } + /* Data from new_crypto_connection():conn->noise_handshake is overwritten here */ + memcpy(conn->noise_handshake, n_c.noise_handshake, sizeof(Noise_Handshake)); + + crypto_connection_add_source(c, crypt_connection_id, source); + + if (create_send_handshake(c, crypt_connection_id, n_c.peer_cookie, n_c.peer_dht_public_key) != 0) { + mem_delete(c->mem, n_c.peer_cookie); + return -1; + } + + conn->status = CRYPTO_CONN_NOT_CONFIRMED; + + /* RESPONDER Noise Split(): vice-verse keys in comparison to initiator */ + crypto_hkdf(conn->recv_key, CRYPTO_SYMMETRIC_KEY_SIZE, conn->send_key, CRYPTO_SYMMETRIC_KEY_SIZE, nullptr, 0, conn->noise_handshake->chaining_key); + + LOGGER_DEBUG(c->log, "Noise RESPONDER crypt_connection_id: %d/conn->status: %d/peer_id_public_key: %s", crypt_connection_id, conn->status, log_id_public); + + crypto_memzero(n_c.noise_handshake, sizeof(Noise_Handshake)); + n_c.noise_handshake = nullptr; + + mem_delete(c->mem, n_c.peer_cookie); + return 0; + } else { /* case non-Noise handshake */ + LOGGER_DEBUG(c->log, "non-Noise handshake"); + conn->noise_handshake_enabled = false; + + /* necessary for compatiblity to non-Noise handshake; */ + if (conn->noise_handshake != nullptr) { + /* non-Noise: noise_handshake not necessary anymore => memzero and free */ + crypto_memzero(conn->noise_handshake, sizeof(Noise_Handshake)); + mem_delete(c->mem, conn->noise_handshake); + conn->noise_handshake = nullptr; + } + + uint8_t zeros[CRYPTO_PUBLIC_KEY_SIZE]; + memset(zeros, 0, CRYPTO_PUBLIC_KEY_SIZE); + /* Need to check here, values overwritten if handle_new_connection_handshake() is called twice (happened in tests) */ + if (memcmp(conn->ephemeral_public_key, zeros, CRYPTO_PUBLIC_KEY_SIZE) == 0 + && memcmp(conn->ephemeral_secret_key, zeros, CRYPTO_SECRET_KEY_SIZE) == 0 + && memcmp(conn->send_nonce, zeros, CRYPTO_NONCE_SIZE) == 0) { + /* Ephemeral key pair needed in non-Noise handshake */ + crypto_new_keypair(c->rng, conn->ephemeral_public_key, conn->ephemeral_secret_key); + /* Random nonce needed in non-Noise handshake */ + random_nonce(c->rng, conn->send_nonce); + LOGGER_DEBUG(c->log, "Switch to non-Noise handshake"); + } memcpy(conn->recv_nonce, n_c.recv_nonce, CRYPTO_NONCE_SIZE); - memcpy(conn->peersessionpublic_key, n_c.peersessionpublic_key, CRYPTO_PUBLIC_KEY_SIZE); - encrypt_precompute(conn->peersessionpublic_key, conn->sessionsecret_key, conn->shared_key); + memcpy(conn->peer_ephemeral_public_key, n_c.peer_ephemeral_public_key, CRYPTO_PUBLIC_KEY_SIZE); + encrypt_precompute(conn->peer_ephemeral_public_key, conn->ephemeral_secret_key, conn->shared_key); crypto_connection_add_source(c, crypt_connection_id, source); - if (create_send_handshake(c, crypt_connection_id, n_c.cookie, n_c.dht_public_key) != 0) { - mem_delete(c->mem, n_c.cookie); + if (create_send_handshake(c, crypt_connection_id, n_c.peer_cookie, n_c.peer_dht_public_key) != 0) { + mem_delete(c->mem, n_c.peer_cookie); return -1; } conn->status = CRYPTO_CONN_NOT_CONFIRMED; - mem_delete(c->mem, n_c.cookie); + mem_delete(c->mem, n_c.peer_cookie); return 0; } } const int ret = c->new_connection_callback(c->new_connection_callback_object, &n_c); - mem_delete(c->mem, n_c.cookie); + mem_delete(c->mem, n_c.peer_cookie); return ret; } -/** @brief Accept a crypto connection. +/** @brief Accept a crypto connection. Currently supports non-Noise and Noise-based handshake. * * return -1 on failure. * return connection id on success. */ int accept_crypto_connection(Net_Crypto *c, const New_Connection *n_c) { - if (getcryptconnection_id(c, n_c->public_key) != -1) { + if (getcryptconnection_id(c, n_c->peer_id_public_key) != -1) { + LOGGER_DEBUG(c->log, "RESPONDER: Crypto Connection already exists"); return -1; } const int crypt_connection_id = create_crypto_connection(c); + LOGGER_DEBUG(c->log, "crypt_connection_id: %d", crypt_connection_id); + if (crypt_connection_id == -1) { LOGGER_ERROR(c->log, "Could not create new crypto connection"); return -1; @@ -2040,7 +2727,7 @@ int accept_crypto_connection(Net_Crypto *c, const New_Connection *n_c) return -1; } - const int connection_number_tcp = new_tcp_connection_to(c->tcp_c, n_c->dht_public_key, crypt_connection_id); + const int connection_number_tcp = new_tcp_connection_to(c->tcp_c, n_c->peer_dht_public_key, crypt_connection_id); if (connection_number_tcp == -1) { wipe_crypto_connection(c, crypt_connection_id); @@ -2048,30 +2735,83 @@ int accept_crypto_connection(Net_Crypto *c, const New_Connection *n_c) } conn->connection_number_tcp = connection_number_tcp; - memcpy(conn->public_key, n_c->public_key, CRYPTO_PUBLIC_KEY_SIZE); - memcpy(conn->recv_nonce, n_c->recv_nonce, CRYPTO_NONCE_SIZE); - memcpy(conn->peersessionpublic_key, n_c->peersessionpublic_key, CRYPTO_PUBLIC_KEY_SIZE); - random_nonce(c->rng, conn->sent_nonce); - crypto_new_keypair(c->rng, conn->sessionpublic_key, conn->sessionsecret_key); - encrypt_precompute(conn->peersessionpublic_key, conn->sessionsecret_key, conn->shared_key); - conn->status = CRYPTO_CONN_NOT_CONFIRMED; - - if (create_send_handshake(c, crypt_connection_id, n_c->cookie, n_c->dht_public_key) != 0) { + + char log_id_public[CRYPTO_PUBLIC_KEY_SIZE*2+1]; + bytes2string(log_id_public, sizeof(log_id_public), n_c->peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE, c->log); + LOGGER_DEBUG(c->log, "crypt_connection_id: %d/peer_id_public_key: %s", crypt_connection_id, log_id_public); + + /* NoiseIK: only happening for RESPONDER */ + if (n_c->noise_handshake != nullptr) { + if (!n_c->noise_handshake->initiator) { + conn->noise_handshake_enabled = true; + LOGGER_DEBUG(c->log, "Responder: Noise WriteMessage"); + memcpy(conn->noise_handshake, n_c->noise_handshake, sizeof(Noise_Handshake)); + // TODO(goldroom): not necessary for Noise, added for debugging/testing purposes + memcpy(conn->peer_id_public_key, n_c->peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + /* TODO(goldroom): NOT possible here, need content afterwards! */ + // crypto_memzero(n_c->noise_handshake, sizeof(struct noise_handshake)); + + /* IMPORTANT: in this case here/before create_send_handshake(), otherwise get_crypto_connection() in + create_send_handshake() returns a nullptr */ + conn->status = CRYPTO_CONN_NOT_CONFIRMED; + + if (create_send_handshake(c, crypt_connection_id, n_c->peer_cookie, n_c->peer_dht_public_key) != 0) { + kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); + wipe_crypto_connection(c, crypt_connection_id); + return -1; + } + + /* NoiseIK handshake finished */ + memset(conn->send_nonce, 0, CRYPTO_NONCE_SIZE); + memset(conn->recv_nonce, 0, CRYPTO_NONCE_SIZE); + /* Noise Split(), base nonces already set */ + crypto_hkdf(conn->recv_key, CRYPTO_SYMMETRIC_KEY_SIZE, conn->send_key, CRYPTO_SYMMETRIC_KEY_SIZE, nullptr, 0, conn->noise_handshake->chaining_key); + + LOGGER_DEBUG(c->log, "RESPONDER: After Noise Split()/conn->status: %d", conn->status); + // TODO(goldroom): here correct? + crypto_memzero(n_c->noise_handshake, sizeof(Noise_Handshake)); + } else { + kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); + wipe_crypto_connection(c, crypt_connection_id); + return -1; + } + + } else if (c->noise_compatibility_enabled) { /* non-Noise handshake */ + LOGGER_DEBUG(c->log, "non-Noise handshake"); + memcpy(conn->peer_id_public_key, n_c->peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(conn->recv_nonce, n_c->recv_nonce, CRYPTO_NONCE_SIZE); + memcpy(conn->peer_ephemeral_public_key, n_c->peer_ephemeral_public_key, CRYPTO_PUBLIC_KEY_SIZE); + random_nonce(c->rng, conn->send_nonce); + crypto_new_keypair(c->rng, conn->ephemeral_public_key, conn->ephemeral_secret_key); + encrypt_precompute(conn->peer_ephemeral_public_key, conn->ephemeral_secret_key, conn->shared_key); + conn->status = CRYPTO_CONN_NOT_CONFIRMED; + + if (create_send_handshake(c, crypt_connection_id, n_c->peer_cookie, n_c->peer_dht_public_key) != 0) { + kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); + wipe_crypto_connection(c, crypt_connection_id); + return -1; + } + } else { kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); wipe_crypto_connection(c, crypt_connection_id); return -1; } - memcpy(conn->dht_public_key, n_c->dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(conn->peer_dht_public_key, n_c->peer_dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; conn->packet_send_rate_requested = CRYPTO_PACKET_MIN_RATE; conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; conn->rtt_time = DEFAULT_PING_CONNECTION; crypto_connection_add_source(c, crypt_connection_id, &n_c->source); + + // TODO(goldroom): here correct? => nope, crashes backwards compatbility + // crypto_memzero(n_c->noise_handshake, sizeof(Noise_Handshake)); + return crypt_connection_id; } -/** @brief Create a crypto connection. +/** @brief Create a crypto connection. Currently supports both non-Noise and NoiseIK handshake. * If one to that real public key already exists, return it. * * return -1 on failure. @@ -2082,6 +2822,7 @@ int new_crypto_connection(Net_Crypto *c, const uint8_t *real_public_key, const u int crypt_connection_id = getcryptconnection_id(c, real_public_key); if (crypt_connection_id != -1) { + LOGGER_DEBUG(c->log, "Crypto connection already exists => crypt_connection_id: %d", crypt_connection_id); return crypt_connection_id; } @@ -2092,6 +2833,7 @@ int new_crypto_connection(Net_Crypto *c, const uint8_t *real_public_key, const u } Crypto_Connection *conn = &c->crypto_connections[crypt_connection_id]; + LOGGER_DEBUG(c->log, "crypt_connection_id: %d", crypt_connection_id); const int connection_number_tcp = new_tcp_connection_to(c->tcp_c, dht_public_key, crypt_connection_id); @@ -2101,20 +2843,47 @@ int new_crypto_connection(Net_Crypto *c, const uint8_t *real_public_key, const u } conn->connection_number_tcp = connection_number_tcp; - memcpy(conn->public_key, real_public_key, CRYPTO_PUBLIC_KEY_SIZE); - random_nonce(c->rng, conn->sent_nonce); - crypto_new_keypair(c->rng, conn->sessionpublic_key, conn->sessionsecret_key); + /* Necessary for backwards compatibility to switch to non-Noise handshake */ + memcpy(conn->peer_id_public_key, real_public_key, CRYPTO_PUBLIC_KEY_SIZE); + // TODO(goldroom): remove before merge + char log_id_public[CRYPTO_PUBLIC_KEY_SIZE*2+1]; + bytes2string(log_id_public, sizeof(log_id_public), conn->peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE, c->log); + LOGGER_DEBUG(c->log, "crypt_connection_id: %d/peer_id_public_key: %s", crypt_connection_id, log_id_public); + + + /* Base nonces are a counter in transport phase after NoiseIK handshake */ + /* Only necessary after handshake is finished, but would need to set in multiple different places */ + memset(conn->send_nonce, 0, CRYPTO_NONCE_SIZE); + memset(conn->recv_nonce, 0, CRYPTO_NONCE_SIZE); + conn->status = CRYPTO_CONN_COOKIE_REQUESTING; conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; conn->packet_send_rate_requested = CRYPTO_PACKET_MIN_RATE; conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; conn->rtt_time = DEFAULT_PING_CONNECTION; - memcpy(conn->dht_public_key, dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(conn->peer_dht_public_key, dht_public_key, CRYPTO_PUBLIC_KEY_SIZE); + + /* Necessary for backwards compatibility to switch to non-Noise handshake (only if enabled with option noise_compatibility_enabled) */ + conn->noise_handshake_enabled = true; + /* Need to set for check in handle_new_connection_handshake() */ + memset(conn->ephemeral_public_key, 0, CRYPTO_PUBLIC_KEY_SIZE); + memset(conn->ephemeral_secret_key, 0, CRYPTO_SECRET_KEY_SIZE); + + /* TODO(goldroom): Noise: only necessary if Cookie response was successful, but moved here to avoid saving peer_id_public_key twice + (to remove it a some point from struct Crypto_Connection) */ + if (conn->noise_handshake_enabled && noise_handshake_init(conn->noise_handshake, c->self_id_secret_key, real_public_key, true, nullptr, 0) != 0) { + kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); + wipe_crypto_connection(c, crypt_connection_id); + return -1; + } + + /* Noise: create and set ephemeral private+public */ + crypto_new_keypair(c->rng, conn->noise_handshake->ephemeral_public, conn->noise_handshake->ephemeral_private); conn->cookie_request_number = random_u64(c->rng); uint8_t cookie_request[COOKIE_REQUEST_LENGTH]; - if (create_cookie_request(c, cookie_request, conn->dht_public_key, conn->cookie_request_number, + if (create_cookie_request(c, cookie_request, conn->peer_dht_public_key, conn->cookie_request_number, conn->shared_key) != sizeof(cookie_request) || new_temp_packet(c, crypt_connection_id, cookie_request, sizeof(cookie_request)) != 0) { kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); @@ -2171,6 +2940,13 @@ static int tcp_data_callback(void *object, int crypt_connection_id, const uint8_ return -1; } + // TODO(goldroom): remove before merge? + char log_id_public[CRYPTO_PUBLIC_KEY_SIZE*2+1]; + bytes2string(log_id_public, sizeof(log_id_public), conn->peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE, c->log); + // TODO(goldroom): remove print of static id public key before merge? + LOGGER_DEBUG(c->log, "Packet: %d/length: %d/crypt_connection_id: %d/conn->status: %d/peer_id_public_key: %s", + packet[0], length, crypt_connection_id, conn->status, log_id_public); + if (packet[0] == NET_PACKET_COOKIE_REQUEST) { return tcp_handle_cookie_request(c, conn->connection_number_tcp, packet, length); } @@ -2191,6 +2967,8 @@ static int tcp_oob_callback(void *object, const uint8_t *public_key, unsigned in { Net_Crypto *c = (Net_Crypto *)object; + LOGGER_DEBUG(c->log, "Packet: %d/length: %d", packet[0], length); + if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) { return -1; } @@ -2284,7 +3062,7 @@ int send_tcp_forward_request(const Logger *logger, Net_Crypto *c, const IP_Port const uint8_t *data, uint16_t data_length) { return tcp_send_forward_request(logger, c->tcp_c, tcp_forwarder, dht_node, - chain_keys, chain_length, data, data_length); + chain_keys, chain_length, data, data_length); } /** @brief Copy a maximum of num random TCP relays we are connected to to tcp_relays. @@ -2428,6 +3206,12 @@ int nc_dht_pk_callback(const Net_Crypto *c, int crypt_connection_id, dht_pk_cb * return -1; } + // TODO(goldroom): remove before merge? + char log_id_public[CRYPTO_PUBLIC_KEY_SIZE*2+1]; + bytes2string(log_id_public, sizeof(log_id_public), conn->peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE, c->log); + // TODO(goldroom): remove print of static id public key before merge? + LOGGER_DEBUG(c->log, "crypt_connection_id: %d/peer_id_public_key: %s", crypt_connection_id, log_id_public); + conn->dht_pk_callback = function; conn->dht_pk_callback_object = object; conn->dht_pk_callback_number = number; @@ -2467,6 +3251,9 @@ static int udp_handle_packet(void *object, const IP_Port *source, const uint8_t const int crypt_connection_id = crypto_id_ip_port(c, source); + LOGGER_DEBUG(c->log, "Packet: %d/length: %d/crypt_connection_id: %d", packet[0], length, crypt_connection_id); + + /* No crypto connection yet = RESPONDER case */ if (crypt_connection_id == -1) { if (packet[0] != NET_PACKET_CRYPTO_HS) { return 1; @@ -2532,10 +3319,25 @@ static void send_crypto_packets(Net_Crypto *c) continue; } + // TODO(goldroom): remove? interesting if want to adapt interval + // LOGGER_DEBUG(c->log, "conn->handshake_send_interval: %d", conn->handshake_send_interval); + // LOGGER_DEBUG(c->log, "conn->temp_packet_sent_time: %lu", conn->temp_packet_sent_time); + // LOGGER_DEBUG(c->log, "(conn->handshake_send_interval + conn->temp_packet_sent_time): %lu", (conn->handshake_send_interval + conn->temp_packet_sent_time)); + // LOGGER_DEBUG(c->log, "temp_time: %lu", temp_time); + + // TODO(goldroom): Use again? / adapt interval? if ((CRYPTO_SEND_PACKET_INTERVAL + conn->temp_packet_sent_time) < temp_time) { + // TODO(goldroom): remove + //LOGGER_DEBUG(c->log, "=> call send_temp_packet() => random_backoff: %d", random_backoff); + // c_sleep(random_backoff); send_temp_packet(c, i); } + // TODO(goldroom): remove? where to add? + // if ((conn->handshake_send_interval + conn->temp_packet_sent_time) < temp_time) { + // send_temp_packet(c, i); + // } + if ((conn->status == CRYPTO_CONN_NOT_CONFIRMED || conn->status == CRYPTO_CONN_ESTABLISHED) && (CRYPTO_SEND_PACKET_INTERVAL + conn->last_request_packet_sent) < temp_time) { if (send_request_packet(c, i) == 0) { @@ -2915,6 +3717,13 @@ int crypto_kill(Net_Crypto *c, int crypt_connection_id) int ret = -1; if (conn != nullptr) { + // TODO(goldroom): remove before merge? + char log_id_public[CRYPTO_PUBLIC_KEY_SIZE*2+1]; + bytes2string(log_id_public, sizeof(log_id_public), conn->peer_id_public_key, CRYPTO_PUBLIC_KEY_SIZE, c->log); + // TODO(goldroom): remove print of static id public key before merge? + LOGGER_DEBUG(c->log, "crypt_connection_id: %d/peer_id_public_key: %s", crypt_connection_id, log_id_public); + + // TODO(goldroom): Add CRYPTO_CONN_NOT_CONFIRMED for broken Noise handshakes? => removed connection_kill() for now from handle_packet_crypto_hs() if (conn->status == CRYPTO_CONN_ESTABLISHED) { send_kill_packet(c, crypt_connection_id); } @@ -2961,7 +3770,7 @@ bool crypto_connection_status(const Net_Crypto *c, int crypt_connection_id, bool void new_keys(Net_Crypto *c) { - crypto_new_keypair(c->rng, c->self_public_key, c->self_secret_key); + crypto_new_keypair(c->rng, c->self_id_public_key, c->self_id_secret_key); } /** @brief Save the public and private keys to the keys array. @@ -2971,8 +3780,8 @@ void new_keys(Net_Crypto *c) */ void save_keys(const Net_Crypto *c, uint8_t *keys) { - memcpy(keys, c->self_public_key, CRYPTO_PUBLIC_KEY_SIZE); - memcpy(keys + CRYPTO_PUBLIC_KEY_SIZE, c->self_secret_key, CRYPTO_SECRET_KEY_SIZE); + memcpy(keys, c->self_id_public_key, CRYPTO_PUBLIC_KEY_SIZE); + memcpy(keys + CRYPTO_PUBLIC_KEY_SIZE, c->self_id_secret_key, CRYPTO_SECRET_KEY_SIZE); } /** @brief Load the secret key. @@ -2980,15 +3789,15 @@ void save_keys(const Net_Crypto *c, uint8_t *keys) */ void load_secret_key(Net_Crypto *c, const uint8_t *sk) { - memcpy(c->self_secret_key, sk, CRYPTO_SECRET_KEY_SIZE); - crypto_derive_public_key(c->self_public_key, c->self_secret_key); + memcpy(c->self_id_secret_key, sk, CRYPTO_SECRET_KEY_SIZE); + crypto_derive_public_key(c->self_id_public_key, c->self_id_secret_key); } /** @brief Create new instance of Net_Crypto. * Sets all the global connection variables to their default values. */ Net_Crypto *new_net_crypto(const Logger *log, const Memory *mem, const Random *rng, const Network *ns, - Mono_Time *mono_time, DHT *dht, const TCP_Proxy_Info *proxy_info) + Mono_Time *mono_time, DHT *dht, const TCP_Proxy_Info *proxy_info, const bool noise_compatibility_enabled) { if (dht == nullptr) { return nullptr; @@ -3016,10 +3825,18 @@ Net_Crypto *new_net_crypto(const Logger *log, const Memory *mem, const Random *r set_packet_tcp_connection_callback(temp->tcp_c, &tcp_data_callback, temp); set_oob_packet_tcp_connection_callback(temp->tcp_c, &tcp_oob_callback, temp); + /* Sets backwards compatibility to non-Noise handshake to true or false */ + temp->noise_compatibility_enabled = noise_compatibility_enabled; + temp->dht = dht; new_keys(temp); - new_symmetric_key(rng, temp->secret_symmetric_key); + new_symmetric_key(rng, temp->cookie_symmetric_key); + // TODO(goldroom): remove before merge? + char log_id_public[CRYPTO_PUBLIC_KEY_SIZE*2+1]; + bytes2string(log_id_public, sizeof(log_id_public), temp->self_id_public_key, CRYPTO_PUBLIC_KEY_SIZE, temp->log); + // TODO(goldroom): remove print of static id public key before merge? + LOGGER_DEBUG(temp->log, "self_id_public_key: %s", log_id_public); temp->current_sleep_time = CRYPTO_SEND_PACKET_INTERVAL; @@ -3030,6 +3847,8 @@ Net_Crypto *new_net_crypto(const Logger *log, const Memory *mem, const Random *r bs_list_init(&temp->ip_port_list, mem, sizeof(IP_Port), 8, ipport_cmp_handler); + LOGGER_DEBUG(temp->log, "DONE"); + return temp; } @@ -3049,6 +3868,7 @@ static void kill_timedout(Net_Crypto *c, void *userdata) continue; } + LOGGER_DEBUG(c->log, "connection_kill"); connection_kill(c, i, userdata); } @@ -3072,6 +3892,7 @@ uint32_t crypto_run_interval(const Net_Crypto *c) /** Main loop. */ void do_net_crypto(Net_Crypto *c, void *userdata) { + // TODO(goldroom) update cookie symmetric key every ~2 minutes (cf. WireGuard)? kill_timedout(c, userdata); do_tcp(c, userdata); send_crypto_packets(c); @@ -3083,6 +3904,8 @@ void kill_net_crypto(Net_Crypto *c) return; } + LOGGER_DEBUG(c->log, ">"); + const Memory *mem = c->mem; for (uint32_t i = 0; i < c->crypto_connections_length; ++i) { diff --git a/toxcore/net_crypto.h b/toxcore/net_crypto.h index ee89a9eeb0..9420b469a8 100644 --- a/toxcore/net_crypto.h +++ b/toxcore/net_crypto.h @@ -130,11 +130,23 @@ non_null() DHT *nc_get_dht(const Net_Crypto *c); typedef struct New_Connection { IP_Port source; - uint8_t public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The real public key of the peer. */ - uint8_t dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The dht public key of the peer. */ - uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce of received packets. */ - uint8_t peersessionpublic_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The public key of the peer. */ - uint8_t *cookie; + // Necessary for non-Noise handshake + uint8_t peer_id_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The real public key of the peer. */ + uint8_t peer_dht_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The dht public key of the peer. */ + uint8_t recv_nonce[CRYPTO_NONCE_SIZE]; /* Nonce to decrypt received packets. */ + uint8_t peer_ephemeral_public_key[CRYPTO_PUBLIC_KEY_SIZE]; /* The public key of the peer. */ + // Necessary for Noise + // TODO(goldroom): refactor to not use struct + Noise_Handshake noise_handshake_data; + Noise_Handshake *noise_handshake; + // TODO(goldroom): if no struct necessary + // uint8_t noise_hash[CRYPTO_SHA512_SIZE]; + // uint8_t noise_chaining_key[CRYPTO_SHA512_SIZE]; + // uint8_t niose_send_key[CRYPTO_SHARED_KEY_SIZE]; + // uint8_t noise_recv_key[CRYPTO_SHARED_KEY_SIZE]; + // bool initiator; + + uint8_t *peer_cookie; uint8_t cookie_length; } New_Connection; @@ -406,7 +418,7 @@ void load_secret_key(Net_Crypto *c, const uint8_t *sk); */ non_null() Net_Crypto *new_net_crypto(const Logger *log, const Memory *mem, const Random *rng, const Network *ns, - Mono_Time *mono_time, DHT *dht, const TCP_Proxy_Info *proxy_info); + Mono_Time *mono_time, DHT *dht, const TCP_Proxy_Info *proxy_info, bool noise_compatibility_enabled); /** return the optimal interval in ms for running do_net_crypto. */ non_null() @@ -419,6 +431,10 @@ void do_net_crypto(Net_Crypto *c, void *userdata); nullable(1) void kill_net_crypto(Net_Crypto *c); +// TODO(goldroom): necessary? +//static void handshake_zero(struct noise_handshake *handshake); + + /** * Returns a pointer to the net profile object for the TCP client associated with `c`. * Returns null if `c` is null or the TCP_Connections associated with `c` is null. diff --git a/toxcore/tox.c b/toxcore/tox.c index 8e1b43903d..9e1c4a5ba6 100644 --- a/toxcore/tox.c +++ b/toxcore/tox.c @@ -794,6 +794,7 @@ static Tox *tox_new_system(const struct Tox_Options *options, Tox_Err_New *error } m_options.ipv6enabled = tox_options_get_ipv6_enabled(opts); + m_options.noise_compatibility_enabled = tox_options_get_noise_compatibility_enabled(opts); m_options.udp_disabled = !tox_options_get_udp_enabled(opts); m_options.port_range[0] = tox_options_get_start_port(opts); m_options.port_range[1] = tox_options_get_end_port(opts); diff --git a/toxcore/tox_options.c b/toxcore/tox_options.c index d67a8aebd0..de302eccaa 100644 --- a/toxcore/tox_options.c +++ b/toxcore/tox_options.c @@ -181,6 +181,15 @@ void tox_options_set_experimental_groups_persistence( { options->experimental_groups_persistence = experimental_groups_persistence; } +bool tox_options_get_noise_compatibility_enabled(const Tox_Options *options) +{ + return options->noise_compatibility_enabled; +} +void tox_options_set_noise_compatibility_enabled( + Tox_Options *options, bool noise_compatibility_enabled) +{ + options->noise_compatibility_enabled = noise_compatibility_enabled; +} bool tox_options_get_experimental_disable_dns(const Tox_Options *options) { return options->experimental_disable_dns; @@ -255,6 +264,7 @@ void tox_options_default(Tox_Options *options) tox_options_set_dht_announcements_enabled(options, true); tox_options_set_experimental_thread_safety(options, false); tox_options_set_experimental_groups_persistence(options, false); + tox_options_set_noise_compatibility_enabled(options, true); tox_options_set_experimental_disable_dns(options, false); tox_options_set_experimental_owned_data(options, false); } diff --git a/toxcore/tox_options.h b/toxcore/tox_options.h index 7d8b7aafaa..79bc5ea0e9 100644 --- a/toxcore/tox_options.h +++ b/toxcore/tox_options.h @@ -265,6 +265,16 @@ struct Tox_Options { */ bool experimental_groups_persistence; + /** + * @brief Compatibility for old non-Noise handshake. + * + * If this is set to false, non-Noise handshake + * will not work anymore. + * + * Default: true. + */ + bool noise_compatibility_enabled; + /** * @brief Disable DNS hostname resolution. * @@ -393,6 +403,10 @@ bool tox_options_get_experimental_groups_persistence(const Tox_Options *options) void tox_options_set_experimental_groups_persistence( Tox_Options *options, bool experimental_groups_persistence); +bool tox_options_get_noise_compatibility_enabled(const struct Tox_Options *options); + +void tox_options_set_noise_compatibility_enabled(struct Tox_Options *options, bool noise_compatibility_enabled); + bool tox_options_get_experimental_disable_dns(const Tox_Options *options); void tox_options_set_experimental_disable_dns(Tox_Options *options, bool experimental_disable_dns); diff --git a/toxcore/util.c b/toxcore/util.c index 455e513cef..34471a6571 100644 --- a/toxcore/util.c +++ b/toxcore/util.c @@ -13,6 +13,7 @@ #include "util.h" +#include #include #include "ccompat.h" @@ -180,3 +181,17 @@ uint32_t jenkins_one_at_a_time_hash(const uint8_t *key, size_t len) hash += (uint32_t)((uint64_t)hash << 15); return hash; } + +/** generic function to print bytes as String; based on id_to_string() from Messenger.c */ +void bytes_to_string(const uint8_t *bytes, size_t bytes_length, char *str, size_t str_length) +{ + if (str_length < (bytes_length * 2 + 1)) { + snprintf(str, str_length, "Bad buf length"); + } + + for (uint32_t i = 0; i < bytes_length; ++i) { + snprintf(&str[i * 2], str_length - i * 2, "%02X", bytes[i]); + } + + str[bytes_length * 2] = '\0'; +} diff --git a/toxcore/util.h b/toxcore/util.h index 9be721318b..e3ca6b78da 100644 --- a/toxcore/util.h +++ b/toxcore/util.h @@ -101,6 +101,17 @@ uint32_t jenkins_one_at_a_time_hash(const uint8_t *key, size_t len); non_null() uint16_t data_checksum(const uint8_t *data, uint32_t length); +/** + * @brief Generic function to print bytes as String; based on id_to_string() from Messenger.c + * + * @param bytes Bytes to be printed as String. + * @param bytes_length The length in bytes + * @param str The string to save the result to. + * @param str_length Length of the string + */ +non_null() +void bytes_to_string(const uint8_t *bytes, size_t bytes_length, char *str, size_t str_length); + #ifdef __cplusplus } /* extern "C" */ #endif