static void bench_dh(void) { const int iters = 1<<10; int i; uint64_t start, end; reset_perftime(); start = perftime(); for (i = 0; i < iters; ++i) { char dh_pubkey_a[DH_BYTES], dh_pubkey_b[DH_BYTES]; char secret_a[DH_BYTES], secret_b[DH_BYTES]; ssize_t slen_a, slen_b; crypto_dh_t *dh_a = crypto_dh_new(DH_TYPE_TLS); crypto_dh_t *dh_b = crypto_dh_new(DH_TYPE_TLS); crypto_dh_generate_public(dh_a); crypto_dh_generate_public(dh_b); crypto_dh_get_public(dh_a, dh_pubkey_a, sizeof(dh_pubkey_a)); crypto_dh_get_public(dh_b, dh_pubkey_b, sizeof(dh_pubkey_b)); slen_a = crypto_dh_compute_secret(LOG_NOTICE, dh_a, dh_pubkey_b, sizeof(dh_pubkey_b), secret_a, sizeof(secret_a)); slen_b = crypto_dh_compute_secret(LOG_NOTICE, dh_b, dh_pubkey_a, sizeof(dh_pubkey_a), secret_b, sizeof(secret_b)); tor_assert(slen_a == slen_b); tor_assert(!memcmp(secret_a, secret_b, slen_a)); crypto_dh_free(dh_a); crypto_dh_free(dh_b); } end = perftime(); printf("Complete DH handshakes (1024 bit, public and private ops):\n" " %f millisec each.\n", NANOCOUNT(start, end, iters)/1e6); }
/** 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); }
/* We are a v2 legacy HS client: Create and return a crypt path for the hidden * service on the other side of the rendezvous circuit <b>circ</b>. Initialize * the crypt path crypto using the body of the RENDEZVOUS1 cell at * <b>rend_cell_body</b> (which must be at least DH_KEY_LEN+DIGEST_LEN bytes). */ static crypt_path_t * create_rend_cpath_legacy(origin_circuit_t *circ, const uint8_t *rend_cell_body) { crypt_path_t *hop = NULL; char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh * handshake...*/ tor_assert(circ->build_state); tor_assert(circ->build_state->pending_final_cpath); hop = circ->build_state->pending_final_cpath; tor_assert(hop->rend_dh_handshake_state); if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, hop->rend_dh_handshake_state, (char*)rend_cell_body, DH_KEY_LEN, keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) { log_warn(LD_GENERAL, "Couldn't complete DH handshake."); goto err; } /* ... and set up cpath. */ if (circuit_init_cpath_crypto(hop, keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN, 0, 0) < 0) goto err; /* Check whether the digest is right... */ if (tor_memneq(keys, rend_cell_body+DH_KEY_LEN, DIGEST_LEN)) { log_warn(LD_PROTOCOL, "Incorrect digest of key material."); goto err; } /* clean up the crypto stuff we just made */ crypto_dh_free(hop->rend_dh_handshake_state); hop->rend_dh_handshake_state = NULL; goto done; err: hop = NULL; done: memwipe(keys, 0, sizeof(keys)); return hop; }
/** 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; }
/** Given an encrypted DH public key as generated by onion_skin_create, * and the private key for this onion router, generate the reply (128-byte * DH plus the first 20 bytes of shared key material), and store the * next key_out_len bytes of key material in key_out. */ int onion_skin_server_handshake(const char *onion_skin, /*ONIONSKIN_CHALLENGE_LEN*/ crypto_pk_env_t *private_key, crypto_pk_env_t *prev_private_key, char *handshake_reply_out, /*ONIONSKIN_REPLY_LEN*/ char *key_out, size_t key_out_len) { char challenge[ONIONSKIN_CHALLENGE_LEN]; crypto_dh_env_t *dh = NULL; ssize_t len; char *key_material=NULL; size_t key_material_len=0; int i; crypto_pk_env_t *k; len = -1; for (i=0;i<2;++i) { k = i==0?private_key:prev_private_key; if (!k) break; note_crypto_pk_op(DEC_ONIONSKIN); len = crypto_pk_private_hybrid_decrypt(k, challenge, onion_skin, ONIONSKIN_CHALLENGE_LEN, PK_PKCS1_OAEP_PADDING,0); if (len>0) break; } if (len<0) { log_info(LD_PROTOCOL, "Couldn't decrypt onionskin: client may be using old onion key"); goto err; } else if (len != DH_KEY_LEN) { log_warn(LD_PROTOCOL, "Unexpected onionskin length after decryption: %ld", (long)len); goto err; } dh = crypto_dh_new(); if (crypto_dh_get_public(dh, handshake_reply_out, DH_KEY_LEN)) { log_info(LD_GENERAL, "crypto_dh_get_public failed."); goto err; } key_material_len = DIGEST_LEN+key_out_len; key_material = tor_malloc(key_material_len); len = crypto_dh_compute_secret(dh, challenge, DH_KEY_LEN, key_material, key_material_len); if (len < 0) { log_info(LD_GENERAL, "crypto_dh_compute_secret failed."); goto err; } /* send back H(K|0) as proof that we learned K. */ memcpy(handshake_reply_out+DH_KEY_LEN, key_material, DIGEST_LEN); /* use the rest of the key material for our shared keys, digests, etc */ memcpy(key_out, key_material+DIGEST_LEN, key_out_len); memset(challenge, 0, sizeof(challenge)); memset(key_material, 0, key_material_len); tor_free(key_material); crypto_dh_free(dh); return 0; err: memset(challenge, 0, sizeof(challenge)); if (key_material) { memset(key_material, 0, key_material_len); tor_free(key_material); } if (dh) crypto_dh_free(dh); return -1; }
/** Given an encrypted DH public key as generated by onion_skin_create, * and the private key for this onion router, generate the reply (128-byte * DH plus the first 20 bytes of shared key material), and store the * next key_out_len bytes of key material in key_out. */ int onion_skin_TAP_server_handshake( /*TAP_ONIONSKIN_CHALLENGE_LEN*/ const char *onion_skin, crypto_pk_t *private_key, crypto_pk_t *prev_private_key, /*TAP_ONIONSKIN_REPLY_LEN*/ char *handshake_reply_out, char *key_out, size_t key_out_len) { char challenge[TAP_ONIONSKIN_CHALLENGE_LEN]; crypto_dh_t *dh = NULL; ssize_t len; char *key_material=NULL; size_t key_material_len=0; int i; crypto_pk_t *k; len = -1; for (i=0;i<2;++i) { k = i==0?private_key:prev_private_key; if (!k) break; len = crypto_pk_obsolete_private_hybrid_decrypt(k, challenge, TAP_ONIONSKIN_CHALLENGE_LEN, onion_skin, TAP_ONIONSKIN_CHALLENGE_LEN, PK_PKCS1_OAEP_PADDING,0); if (len>0) break; } if (len<0) { log_info(LD_PROTOCOL, "Couldn't decrypt onionskin: client may be using old onion key"); goto err; } else if (len != DH1024_KEY_LEN) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Unexpected onionskin length after decryption: %ld", (long)len); goto err; } dh = crypto_dh_new(DH_TYPE_CIRCUIT); if (!dh) { /* LCOV_EXCL_START * Failure to allocate a DH key should be impossible. */ log_warn(LD_BUG, "Couldn't allocate DH key"); goto err; /* LCOV_EXCL_STOP */ } if (crypto_dh_get_public(dh, handshake_reply_out, DH1024_KEY_LEN)) { /* LCOV_EXCL_START * This can only fail if the length of the key we just allocated is too * big. That should be impossible. */ log_info(LD_GENERAL, "crypto_dh_get_public failed."); goto err; /* LCOV_EXCL_STOP */ } key_material_len = DIGEST_LEN+key_out_len; key_material = tor_malloc(key_material_len); len = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh, challenge, DH1024_KEY_LEN, key_material, key_material_len); if (len < 0) { log_info(LD_GENERAL, "crypto_dh_compute_secret failed."); goto err; } /* send back H(K|0) as proof that we learned K. */ memcpy(handshake_reply_out+DH1024_KEY_LEN, key_material, DIGEST_LEN); /* use the rest of the key material for our shared keys, digests, etc */ memcpy(key_out, key_material+DIGEST_LEN, key_out_len); memwipe(challenge, 0, sizeof(challenge)); memwipe(key_material, 0, key_material_len); tor_free(key_material); crypto_dh_free(dh); return 0; err: memwipe(challenge, 0, sizeof(challenge)); if (key_material) { memwipe(key_material, 0, key_material_len); tor_free(key_material); } if (dh) crypto_dh_free(dh); return -1; }