/* Public function: Do the appropriate ntor calculations and derive the keys * needed to create and authenticate RENDEZVOUS1 cells. Return 0 and place the * final key material in <b>hs_ntor_rend_cell_keys_out</b> if all went fine, * return -1 if error happened. * * The relevant calculations are as follows: * * rend_secret_hs_input = EXP(X,y) | EXP(X,b) | AUTH_KEY | B | X | Y | PROTOID * NTOR_KEY_SEED = MAC(rend_secret_hs_input, t_hsenc) * verify = MAC(rend_secret_hs_input, t_hsverify) * auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server" * auth_input_mac = MAC(auth_input, t_hsmac) * * where: * <b>intro_auth_pubkey</b> is AUTH_KEY (intro point auth key), * <b>intro_enc_keypair</b> is (b,B) (intro point enc keypair) * <b>service_ephemeral_rend_keypair</b> is a fresh (y,Y) keypair * <b>client_ephemeral_enc_pubkey</b> is X (CLIENT_PK in INTRODUCE2 cell) */ int hs_ntor_service_get_rendezvous1_keys( const ed25519_public_key_t *intro_auth_pubkey, const curve25519_keypair_t *intro_enc_keypair, const curve25519_keypair_t *service_ephemeral_rend_keypair, const curve25519_public_key_t *client_ephemeral_enc_pubkey, hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out) { int bad = 0; uint8_t rend_secret_hs_input[REND_SECRET_HS_INPUT_LEN]; uint8_t dh_result1[CURVE25519_OUTPUT_LEN]; uint8_t dh_result2[CURVE25519_OUTPUT_LEN]; tor_assert(intro_auth_pubkey); tor_assert(intro_enc_keypair); tor_assert(service_ephemeral_rend_keypair); tor_assert(client_ephemeral_enc_pubkey); tor_assert(hs_ntor_rend_cell_keys_out); /* Compute EXP(X, y) */ curve25519_handshake(dh_result1, &service_ephemeral_rend_keypair->seckey, client_ephemeral_enc_pubkey); bad |= safe_mem_is_zero(dh_result1, CURVE25519_OUTPUT_LEN); /* Compute EXP(X, b) */ curve25519_handshake(dh_result2, &intro_enc_keypair->seckey, client_ephemeral_enc_pubkey); bad |= safe_mem_is_zero(dh_result2, CURVE25519_OUTPUT_LEN); /* Get rend_secret_hs_input */ get_rend_secret_hs_input(dh_result1, dh_result2, intro_auth_pubkey, &intro_enc_keypair->pubkey, client_ephemeral_enc_pubkey, &service_ephemeral_rend_keypair->pubkey, rend_secret_hs_input); /* Get NTOR_KEY_SEED and AUTH_INPUT_MAC! */ bad |= get_rendezvous1_key_material(rend_secret_hs_input, intro_auth_pubkey, &intro_enc_keypair->pubkey, &service_ephemeral_rend_keypair->pubkey, client_ephemeral_enc_pubkey, hs_ntor_rend_cell_keys_out); memwipe(rend_secret_hs_input, 0, sizeof(rend_secret_hs_input)); if (bad) { memwipe(hs_ntor_rend_cell_keys_out, 0, sizeof(hs_ntor_rend_cell_keys_t)); } return bad ? -1 : 0; }
/* Public function: Do the appropriate ntor calculations and derive the keys * needed to decrypt and verify INTRODUCE1 cells. Return 0 and place the final * key material in <b>hs_ntor_intro_cell_keys_out</b> if everything went well, * otherwise return -1; * * The relevant calculations are as follows: * * intro_secret_hs_input = EXP(X,b) | AUTH_KEY | X | B | PROTOID * info = m_hsexpand | subcredential * hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN) * HS_DEC_KEY = hs_keys[0:S_KEY_LEN] * HS_MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN] * * where: * <b>intro_auth_pubkey</b> is AUTH_KEY (introduction point auth key), * <b>intro_enc_keypair</b> is (b,B) (introduction point encryption keypair), * <b>client_ephemeral_enc_pubkey</b> is X (CLIENT_PK in INTRODUCE2 cell), * <b>subcredential</b> is the HS subcredential (of size DIGEST256_LEN) */ int hs_ntor_service_get_introduce1_keys( const ed25519_public_key_t *intro_auth_pubkey, const curve25519_keypair_t *intro_enc_keypair, const curve25519_public_key_t *client_ephemeral_enc_pubkey, const uint8_t *subcredential, hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out) { int bad = 0; uint8_t secret_input[INTRO_SECRET_HS_INPUT_LEN]; uint8_t dh_result[CURVE25519_OUTPUT_LEN]; tor_assert(intro_auth_pubkey); tor_assert(intro_enc_keypair); tor_assert(client_ephemeral_enc_pubkey); tor_assert(subcredential); tor_assert(hs_ntor_intro_cell_keys_out); /* Compute EXP(X, b) */ curve25519_handshake(dh_result, &intro_enc_keypair->seckey, client_ephemeral_enc_pubkey); bad |= safe_mem_is_zero(dh_result, CURVE25519_OUTPUT_LEN); /* Get intro_secret_hs_input */ get_intro_secret_hs_input(dh_result, intro_auth_pubkey, client_ephemeral_enc_pubkey, &intro_enc_keypair->pubkey, secret_input); bad |= safe_mem_is_zero(secret_input, CURVE25519_OUTPUT_LEN); /* Get ENC_KEY and MAC_KEY! */ get_introduce1_key_material(secret_input, subcredential, hs_ntor_intro_cell_keys_out); memwipe(secret_input, 0, sizeof(secret_input)); if (bad) { memwipe(hs_ntor_intro_cell_keys_out, 0, sizeof(hs_ntor_intro_cell_keys_t)); } return bad ? -1 : 0; }
/** Helper function: Compute the last part of the HS ntor handshake which * derives key material necessary to create and handle RENDEZVOUS1 * cells. Function used by both client and service. The actual calculations is * as follows: * * NTOR_KEY_SEED = MAC(rend_secret_hs_input, t_hsenc) * verify = MAC(rend_secret_hs_input, t_hsverify) * auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server" * auth_input_mac = MAC(auth_input, t_hsmac) * * where in the above, AUTH_KEY is <b>intro_auth_pubkey</b>, B is * <b>intro_enc_pubkey</b>, Y is <b>service_ephemeral_rend_pubkey</b>, and X * is <b>client_ephemeral_enc_pubkey</b>. The provided * <b>rend_secret_hs_input</b> is of size REND_SECRET_HS_INPUT_LEN. * * The final results of NTOR_KEY_SEED and auth_input_mac are placed in * <b>hs_ntor_rend_cell_keys_out</b>. Return 0 if everything went fine. */ static int get_rendezvous1_key_material(const uint8_t *rend_secret_hs_input, const ed25519_public_key_t *intro_auth_pubkey, const curve25519_public_key_t *intro_enc_pubkey, const curve25519_public_key_t *service_ephemeral_rend_pubkey, const curve25519_public_key_t *client_ephemeral_enc_pubkey, hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out) { int bad = 0; uint8_t ntor_key_seed[DIGEST256_LEN]; uint8_t ntor_verify[DIGEST256_LEN]; uint8_t rend_auth_input[REND_AUTH_INPUT_LEN]; uint8_t rend_cell_auth[DIGEST256_LEN]; uint8_t *ptr; /* Let's build NTOR_KEY_SEED */ crypto_mac_sha3_256(ntor_key_seed, sizeof(ntor_key_seed), rend_secret_hs_input, REND_SECRET_HS_INPUT_LEN, (const uint8_t *)T_HSENC, strlen(T_HSENC)); bad |= safe_mem_is_zero(ntor_key_seed, DIGEST256_LEN); /* Let's build ntor_verify */ crypto_mac_sha3_256(ntor_verify, sizeof(ntor_verify), rend_secret_hs_input, REND_SECRET_HS_INPUT_LEN, (const uint8_t *)T_HSVERIFY, strlen(T_HSVERIFY)); bad |= safe_mem_is_zero(ntor_verify, DIGEST256_LEN); /* Let's build auth_input: */ ptr = rend_auth_input; /* Append ntor_verify */ APPEND(ptr, ntor_verify, sizeof(ntor_verify)); /* Append AUTH_KEY */ APPEND(ptr, intro_auth_pubkey->pubkey, ED25519_PUBKEY_LEN); /* Append B */ APPEND(ptr, intro_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN); /* Append Y */ APPEND(ptr, service_ephemeral_rend_pubkey->public_key, CURVE25519_PUBKEY_LEN); /* Append X */ APPEND(ptr, client_ephemeral_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN); /* Append PROTOID */ APPEND(ptr, PROTOID, strlen(PROTOID)); /* Append "Server" */ APPEND(ptr, SERVER_STR, strlen(SERVER_STR)); tor_assert(ptr == rend_auth_input + sizeof(rend_auth_input)); /* Let's build auth_input_mac that goes in RENDEZVOUS1 cell */ crypto_mac_sha3_256(rend_cell_auth, sizeof(rend_cell_auth), rend_auth_input, sizeof(rend_auth_input), (const uint8_t *)T_HSMAC, strlen(T_HSMAC)); bad |= safe_mem_is_zero(ntor_verify, DIGEST256_LEN); { /* Get the computed RENDEZVOUS1 material! */ memcpy(&hs_ntor_rend_cell_keys_out->rend_cell_auth_mac, rend_cell_auth, DIGEST256_LEN); memcpy(&hs_ntor_rend_cell_keys_out->ntor_key_seed, ntor_key_seed, DIGEST256_LEN); } memwipe(rend_cell_auth, 0, sizeof(rend_cell_auth)); memwipe(rend_auth_input, 0, sizeof(rend_auth_input)); memwipe(ntor_key_seed, 0, sizeof(ntor_key_seed)); return bad; }
/** * Perform the final client side of the ntor handshake, using the state in * <b>handshake_state</b> and the server's NTOR_REPLY_LEN-byte reply in * <b>handshake_reply</b>. Generate <b>key_out_len</b> bytes of key material * in <b>key_out</b>. Return 0 on success, -1 on failure. */ int onion_skin_ntor_client_handshake( const ntor_handshake_state_t *handshake_state, const uint8_t *handshake_reply, uint8_t *key_out, size_t key_out_len, const char **msg_out) { const tweakset_t *T = &proto1_tweaks; /* Sensitive stack-allocated material. Kept in an anonymous struct to make * it easy to wipe. */ struct { curve25519_public_key_t pubkey_Y; uint8_t secret_input[SECRET_INPUT_LEN]; uint8_t verify[DIGEST256_LEN]; uint8_t auth_input[AUTH_INPUT_LEN]; uint8_t auth[DIGEST256_LEN]; } s; uint8_t *ai = s.auth_input, *si = s.secret_input; const uint8_t *auth_candidate; int bad; /* Decode input */ memcpy(s.pubkey_Y.public_key, handshake_reply, CURVE25519_PUBKEY_LEN); auth_candidate = handshake_reply + CURVE25519_PUBKEY_LEN; /* See note in server_handshake above about checking points. The * circumstances under which we'd need to check Y for membership are * different than those under which we'd be checking X. */ /* Compute secret_input */ curve25519_handshake(si, &handshake_state->seckey_x, &s.pubkey_Y); bad = safe_mem_is_zero(si, CURVE25519_OUTPUT_LEN); si += CURVE25519_OUTPUT_LEN; curve25519_handshake(si, &handshake_state->seckey_x, &handshake_state->pubkey_B); bad |= (safe_mem_is_zero(si, CURVE25519_OUTPUT_LEN) << 1); si += CURVE25519_OUTPUT_LEN; APPEND(si, handshake_state->router_id, DIGEST_LEN); APPEND(si, handshake_state->pubkey_B.public_key, CURVE25519_PUBKEY_LEN); APPEND(si, handshake_state->pubkey_X.public_key, CURVE25519_PUBKEY_LEN); APPEND(si, s.pubkey_Y.public_key, CURVE25519_PUBKEY_LEN); APPEND(si, PROTOID, PROTOID_LEN); tor_assert(si == s.secret_input + sizeof(s.secret_input)); /* Compute verify from secret_input */ h_tweak(s.verify, s.secret_input, sizeof(s.secret_input), T->t_verify); /* Compute auth_input */ APPEND(ai, s.verify, DIGEST256_LEN); APPEND(ai, handshake_state->router_id, DIGEST_LEN); APPEND(ai, handshake_state->pubkey_B.public_key, CURVE25519_PUBKEY_LEN); APPEND(ai, s.pubkey_Y.public_key, CURVE25519_PUBKEY_LEN); APPEND(ai, handshake_state->pubkey_X.public_key, CURVE25519_PUBKEY_LEN); APPEND(ai, PROTOID, PROTOID_LEN); APPEND(ai, SERVER_STR, SERVER_STR_LEN); tor_assert(ai == s.auth_input + sizeof(s.auth_input)); /* Compute auth */ h_tweak(s.auth, s.auth_input, sizeof(s.auth_input), T->t_mac); bad |= (tor_memneq(s.auth, auth_candidate, DIGEST256_LEN) << 2); crypto_expand_key_material_rfc5869_sha256( s.secret_input, sizeof(s.secret_input), (const uint8_t*)T->t_key, strlen(T->t_key), (const uint8_t*)T->m_expand, strlen(T->m_expand), key_out, key_out_len); memwipe(&s, 0, sizeof(s)); if (bad) { if (bad & 4) { if (msg_out) *msg_out = NULL; /* Don't report this one; we probably just had the * wrong onion key.*/ log_fn(LOG_INFO, LD_PROTOCOL, "Invalid result from curve25519 handshake: %d", bad); } if (bad & 3) { if (msg_out) *msg_out = "Zero output from curve25519 handshake"; log_fn(LOG_WARN, LD_PROTOCOL, "Invalid result from curve25519 handshake: %d", bad); } } return bad ? -1 : 0; }
/** * Perform the server side of an ntor handshake. Given an * NTOR_ONIONSKIN_LEN-byte message in <b>onion_skin</b>, our own identity * fingerprint as <b>my_node_id</b>, and an associative array mapping public * onion keys to curve25519_keypair_t in <b>private_keys</b>, attempt to * perform the handshake. Use <b>junk_keys</b> if present if the handshake * indicates an unrecognized public key. Write an NTOR_REPLY_LEN-byte * message to send back to the client into <b>handshake_reply_out</b>, and * generate <b>key_out_len</b> bytes of key material in <b>key_out</b>. Return * 0 on success, -1 on failure. */ int onion_skin_ntor_server_handshake(const uint8_t *onion_skin, const di_digest256_map_t *private_keys, const curve25519_keypair_t *junk_keys, const uint8_t *my_node_id, uint8_t *handshake_reply_out, uint8_t *key_out, size_t key_out_len) { const tweakset_t *T = &proto1_tweaks; /* Sensitive stack-allocated material. Kept in an anonymous struct to make * it easy to wipe. */ struct { uint8_t secret_input[SECRET_INPUT_LEN]; uint8_t auth_input[AUTH_INPUT_LEN]; curve25519_public_key_t pubkey_X; curve25519_secret_key_t seckey_y; curve25519_public_key_t pubkey_Y; uint8_t verify[DIGEST256_LEN]; } s; uint8_t *si = s.secret_input, *ai = s.auth_input; const curve25519_keypair_t *keypair_bB; int bad; /* Decode the onion skin */ /* XXXX Does this possible early-return business threaten our security? */ if (tor_memneq(onion_skin, my_node_id, DIGEST_LEN)) return -1; /* Note that on key-not-found, we go through with this operation anyway, * using "junk_keys". This will result in failed authentication, but won't * leak whether we recognized the key. */ keypair_bB = dimap_search(private_keys, onion_skin + DIGEST_LEN, (void*)junk_keys); if (!keypair_bB) return -1; memcpy(s.pubkey_X.public_key, onion_skin+DIGEST_LEN+DIGEST256_LEN, CURVE25519_PUBKEY_LEN); /* Make y, Y */ curve25519_secret_key_generate(&s.seckey_y, 0); curve25519_public_key_generate(&s.pubkey_Y, &s.seckey_y); /* NOTE: If we ever use a group other than curve25519, or a different * representation for its points, we may need to perform different or * additional checks on X here and on Y in the client handshake, or lose our * security properties. What checks we need would depend on the properties * of the group and its representation. * * In short: if you use anything other than curve25519, this aspect of the * code will need to be reconsidered carefully. */ /* build secret_input */ curve25519_handshake(si, &s.seckey_y, &s.pubkey_X); bad = safe_mem_is_zero(si, CURVE25519_OUTPUT_LEN); si += CURVE25519_OUTPUT_LEN; curve25519_handshake(si, &keypair_bB->seckey, &s.pubkey_X); bad |= safe_mem_is_zero(si, CURVE25519_OUTPUT_LEN); si += CURVE25519_OUTPUT_LEN; APPEND(si, my_node_id, DIGEST_LEN); APPEND(si, keypair_bB->pubkey.public_key, CURVE25519_PUBKEY_LEN); APPEND(si, s.pubkey_X.public_key, CURVE25519_PUBKEY_LEN); APPEND(si, s.pubkey_Y.public_key, CURVE25519_PUBKEY_LEN); APPEND(si, PROTOID, PROTOID_LEN); tor_assert(si == s.secret_input + sizeof(s.secret_input)); /* Compute hashes of secret_input */ h_tweak(s.verify, s.secret_input, sizeof(s.secret_input), T->t_verify); /* Compute auth_input */ APPEND(ai, s.verify, DIGEST256_LEN); APPEND(ai, my_node_id, DIGEST_LEN); APPEND(ai, keypair_bB->pubkey.public_key, CURVE25519_PUBKEY_LEN); APPEND(ai, s.pubkey_Y.public_key, CURVE25519_PUBKEY_LEN); APPEND(ai, s.pubkey_X.public_key, CURVE25519_PUBKEY_LEN); APPEND(ai, PROTOID, PROTOID_LEN); APPEND(ai, SERVER_STR, SERVER_STR_LEN); tor_assert(ai == s.auth_input + sizeof(s.auth_input)); /* Build the reply */ memcpy(handshake_reply_out, s.pubkey_Y.public_key, CURVE25519_PUBKEY_LEN); h_tweak(handshake_reply_out+CURVE25519_PUBKEY_LEN, s.auth_input, sizeof(s.auth_input), T->t_mac); /* Generate the key material */ crypto_expand_key_material_rfc5869_sha256( s.secret_input, sizeof(s.secret_input), (const uint8_t*)T->t_key, strlen(T->t_key), (const uint8_t*)T->m_expand, strlen(T->m_expand), key_out, key_out_len); /* Wipe all of our local state */ memwipe(&s, 0, sizeof(s)); return bad ? -1 : 0; }
/** * Return true iff a curve25519_public_key_t seems valid. (It's not necessary * to see if the point is on the curve, since the twist is also secure, but we * do need to make sure that it isn't the point at infinity.) */ int curve25519_public_key_is_ok(const curve25519_public_key_t *key) { return !safe_mem_is_zero(key->public_key, CURVE25519_PUBKEY_LEN); }