/** Run unit tests for Diffie-Hellman functionality. */ static void test_crypto_dh(void) { crypto_dh_t *dh1 = crypto_dh_new(DH_TYPE_CIRCUIT); crypto_dh_t *dh2 = crypto_dh_new(DH_TYPE_CIRCUIT); char p1[DH_BYTES]; char p2[DH_BYTES]; char s1[DH_BYTES]; char s2[DH_BYTES]; ssize_t s1len, s2len; test_eq(crypto_dh_get_bytes(dh1), DH_BYTES); test_eq(crypto_dh_get_bytes(dh2), DH_BYTES); memset(p1, 0, DH_BYTES); memset(p2, 0, DH_BYTES); test_memeq(p1, p2, DH_BYTES); test_assert(! crypto_dh_get_public(dh1, p1, DH_BYTES)); test_memneq(p1, p2, DH_BYTES); test_assert(! crypto_dh_get_public(dh2, p2, DH_BYTES)); test_memneq(p1, p2, DH_BYTES); memset(s1, 0, DH_BYTES); memset(s2, 0xFF, DH_BYTES); s1len = crypto_dh_compute_secret(LOG_WARN, dh1, p2, DH_BYTES, s1, 50); s2len = crypto_dh_compute_secret(LOG_WARN, dh2, p1, DH_BYTES, s2, 50); test_assert(s1len > 0); test_eq(s1len, s2len); test_memeq(s1, s2, s1len); { /* XXXX Now fabricate some bad values and make sure they get caught, * Check 0, 1, N-1, >= N, etc. */ } done: crypto_dh_free(dh1); crypto_dh_free(dh2); }
/** Given a router's 128 byte public key, * stores the following in onion_skin_out: * - [42 bytes] OAEP padding * - [16 bytes] Symmetric key for encrypting blob past RSA * - [70 bytes] g^x part 1 (inside the RSA) * - [58 bytes] g^x part 2 (symmetrically encrypted) * * Stores the DH private key into handshake_state_out for later completion * of the handshake. * * The meeting point/cookies and auth are zeroed out for now. */ int onion_skin_TAP_create(crypto_pk_t *dest_router_key, crypto_dh_t **handshake_state_out, char *onion_skin_out) /* TAP_ONIONSKIN_CHALLENGE_LEN bytes */ { char challenge[DH1024_KEY_LEN]; crypto_dh_t *dh = NULL; int dhbytes, pkbytes; tor_assert(dest_router_key); tor_assert(handshake_state_out); tor_assert(onion_skin_out); *handshake_state_out = NULL; memset(onion_skin_out, 0, TAP_ONIONSKIN_CHALLENGE_LEN); if (!(dh = crypto_dh_new(DH_TYPE_CIRCUIT))) goto err; dhbytes = crypto_dh_get_bytes(dh); pkbytes = (int) crypto_pk_keysize(dest_router_key); tor_assert(dhbytes == 128); tor_assert(pkbytes == 128); if (crypto_dh_get_public(dh, challenge, dhbytes)) goto err; /* set meeting point, meeting cookie, etc here. Leave zero for now. */ if (crypto_pk_obsolete_public_hybrid_encrypt(dest_router_key, onion_skin_out, TAP_ONIONSKIN_CHALLENGE_LEN, challenge, DH1024_KEY_LEN, PK_PKCS1_OAEP_PADDING, 1)<0) goto err; memwipe(challenge, 0, sizeof(challenge)); *handshake_state_out = dh; return 0; err: /* LCOV_EXCL_START * We only get here if RSA encryption fails or DH keygen fails. Those * shouldn't be possible. */ memwipe(challenge, 0, sizeof(challenge)); if (dh) crypto_dh_free(dh); return -1; /* LCOV_EXCL_STOP */ }
/** Given a router's 128 byte public key, * stores the following in onion_skin_out: * - [42 bytes] OAEP padding * - [16 bytes] Symmetric key for encrypting blob past RSA * - [70 bytes] g^x part 1 (inside the RSA) * - [58 bytes] g^x part 2 (symmetrically encrypted) * * Stores the DH private key into handshake_state_out for later completion * of the handshake. * * The meeting point/cookies and auth are zeroed out for now. */ int onion_skin_TAP_create(crypto_pk_t *dest_router_key, crypto_dh_t **handshake_state_out, char *onion_skin_out) /* TAP_ONIONSKIN_CHALLENGE_LEN bytes */ { char challenge[DH_KEY_LEN]; crypto_dh_t *dh = NULL; int dhbytes, pkbytes; tor_assert(dest_router_key); tor_assert(handshake_state_out); tor_assert(onion_skin_out); *handshake_state_out = NULL; memset(onion_skin_out, 0, TAP_ONIONSKIN_CHALLENGE_LEN); if (!(dh = crypto_dh_new(DH_TYPE_CIRCUIT))) goto err; dhbytes = crypto_dh_get_bytes(dh); pkbytes = (int) crypto_pk_keysize(dest_router_key); tor_assert(dhbytes == 128); tor_assert(pkbytes == 128); if (crypto_dh_get_public(dh, challenge, dhbytes)) goto err; note_crypto_pk_op(ENC_ONIONSKIN); /* set meeting point, meeting cookie, etc here. Leave zero for now. */ if (crypto_pk_public_hybrid_encrypt(dest_router_key, onion_skin_out, TAP_ONIONSKIN_CHALLENGE_LEN, challenge, DH_KEY_LEN, PK_PKCS1_OAEP_PADDING, 1)<0) goto err; memwipe(challenge, 0, sizeof(challenge)); *handshake_state_out = dh; return 0; err: memwipe(challenge, 0, sizeof(challenge)); if (dh) crypto_dh_free(dh); return -1; }
/** Finish the client side of the DH handshake. * Given the 128 byte DH reply + 20 byte hash as generated by * onion_skin_server_handshake and the handshake state generated by * onion_skin_create, verify H(K) with the first 20 bytes of shared * key material, then generate key_out_len more bytes of shared key * material and store them in key_out. * * After the invocation, call crypto_dh_free on handshake_state. */ int onion_skin_TAP_client_handshake(crypto_dh_t *handshake_state, const char *handshake_reply, /* TAP_ONIONSKIN_REPLY_LEN bytes */ char *key_out, size_t key_out_len, const char **msg_out) { ssize_t len; char *key_material=NULL; size_t key_material_len; tor_assert(crypto_dh_get_bytes(handshake_state) == DH1024_KEY_LEN); key_material_len = DIGEST_LEN + key_out_len; key_material = tor_malloc(key_material_len); len = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, handshake_state, handshake_reply, DH1024_KEY_LEN, key_material, key_material_len); if (len < 0) { if (msg_out) *msg_out = "DH computation failed."; goto err; } if (tor_memneq(key_material, handshake_reply+DH1024_KEY_LEN, DIGEST_LEN)) { /* H(K) does *not* match. Something fishy. */ if (msg_out) *msg_out = "Digest DOES NOT MATCH on onion handshake. Bug or attack."; goto err; } /* use the rest of the key material for our shared keys, digests, etc */ memcpy(key_out, key_material+DIGEST_LEN, key_out_len); memwipe(key_material, 0, key_material_len); tor_free(key_material); return 0; err: memwipe(key_material, 0, key_material_len); tor_free(key_material); return -1; }
/** Finish the client side of the DH handshake. * Given the 128 byte DH reply + 20 byte hash as generated by * onion_skin_server_handshake and the handshake state generated by * onion_skin_create, verify H(K) with the first 20 bytes of shared * key material, then generate key_out_len more bytes of shared key * material and store them in key_out. * * After the invocation, call crypto_dh_free on handshake_state. */ int onion_skin_client_handshake(crypto_dh_env_t *handshake_state, const char *handshake_reply, /* ONIONSKIN_REPLY_LEN bytes */ char *key_out, size_t key_out_len) { ssize_t len; char *key_material=NULL; size_t key_material_len; tor_assert(crypto_dh_get_bytes(handshake_state) == DH_KEY_LEN); key_material_len = DIGEST_LEN + key_out_len; key_material = tor_malloc(key_material_len); len = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, handshake_state, handshake_reply, DH_KEY_LEN, key_material, key_material_len); if (len < 0) goto err; if (memcmp(key_material, handshake_reply+DH_KEY_LEN, DIGEST_LEN)) { /* H(K) does *not* match. Something fishy. */ log_warn(LD_PROTOCOL,"Digest DOES NOT MATCH on onion handshake. " "Bug or attack."); goto err; } /* use the rest of the key material for our shared keys, digests, etc */ memcpy(key_out, key_material+DIGEST_LEN, key_out_len); memset(key_material, 0, key_material_len); tor_free(key_material); return 0; err: memset(key_material, 0, key_material_len); tor_free(key_material); return -1; }