diff --git a/src/or/hs_ntor.c b/src/or/hs_ntor.c new file mode 100644 index 0000000000..119899817e --- /dev/null +++ b/src/or/hs_ntor.c @@ -0,0 +1,626 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** \file hs_ntor.c + * \brief Implements the ntor variant used in Tor hidden services. + * + * \details + * This module handles the variant of the ntor handshake that is documented in + * section [NTOR-WITH-EXTRA-DATA] of rend-spec-ng.txt . + * + * The functions in this file provide an API that should be used when sending + * or receiving INTRODUCE1/RENDEZVOUS1 cells to generate the various key + * material required to create and handle those cells. + * + * In the case of INTRODUCE1 it provides encryption and MAC keys to + * encode/decode the encrypted blob (see hs_ntor_intro_cell_keys_t). The + * relevant pub functions are hs_ntor_{client,service}_get_introduce1_keys(). + * + * In the case of RENDEZVOUS1 it calculates the MAC required to authenticate + * the cell, and also provides the key seed that is used to derive the crypto + * material for rendezvous encryption (see hs_ntor_rend_cell_keys_t). The + * relevant pub functions are hs_ntor_{client,service}_get_rendezvous1_keys(). + * It also provides a function (hs_ntor_circuit_key_expansion()) that does the + * rendezvous key expansion to setup end-to-end rend circuit keys. + */ + +#include "or.h" +#include "hs_ntor.h" + +/* String constants used by the ntor HS protocol */ +#define PROTOID "tor-hs-ntor-curve25519-sha3-256-1" +#define PROTOID_LEN (sizeof(PROTOID) - 1) +#define SERVER_STR "Server" +#define SERVER_STR_LEN (sizeof(SERVER_STR) - 1) + +/* Protocol-specific tweaks to our crypto inputs */ +#define T_HSENC PROTOID ":hs_key_extract" +#define T_HSENC_LEN (sizeof(T_HSENC) - 1) +#define T_HSVERIFY PROTOID ":hs_verify" +#define T_HSMAC PROTOID ":hs_mac" +#define M_HSEXPAND PROTOID ":hs_key_expand" +#define M_HSEXPAND_LEN (sizeof(M_HSEXPAND) - 1) + +/************************* Helper functions: *******************************/ + +/** Helper macro: copy len bytes from inp to ptr and + *advance ptr by the number of bytes copied. Stolen from onion_ntor.c */ +#define APPEND(ptr, inp, len) \ + STMT_BEGIN { \ + memcpy(ptr, (inp), (len)); \ + ptr += len; \ + } STMT_END + +/* Length of EXP(X,y) | EXP(X,b) | AUTH_KEY | B | X | Y | PROTOID */ +#define REND_SECRET_HS_INPUT_LEN (CURVE25519_OUTPUT_LEN * 2 + \ + ED25519_PUBKEY_LEN + CURVE25519_PUBKEY_LEN * 3 + PROTOID_LEN) +/* Length of auth_input = verify | AUTH_KEY | B | Y | X | PROTOID | "Server" */ +#define REND_AUTH_INPUT_LEN (DIGEST256_LEN + ED25519_PUBKEY_LEN + \ + CURVE25519_PUBKEY_LEN * 3 + PROTOID_LEN + SERVER_STR_LEN) + +/** 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 intro_auth_pubkey, B is + * intro_enc_pubkey, Y is service_ephemeral_rend_pubkey, and X + * is client_ephemeral_enc_pubkey. The provided + * rend_secret_hs_input is of size REND_SECRET_HS_INPUT_LEN. + * + * The final results of NTOR_KEY_SEED and auth_input_mac are placed in + * hs_ntor_rend_cell_keys_out. 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; +} + +/** Length of secret_input = EXP(B,x) | AUTH_KEY | X | B | PROTOID */ +#define INTRO_SECRET_HS_INPUT_LEN (CURVE25519_OUTPUT_LEN +ED25519_PUBKEY_LEN +\ + CURVE25519_PUBKEY_LEN + CURVE25519_PUBKEY_LEN + PROTOID_LEN) +/* Length of info = m_hsexpand | subcredential */ +#define INFO_BLOB_LEN (M_HSEXPAND_LEN + DIGEST256_LEN) +/* Length of KDF input = intro_secret_hs_input | t_hsenc | info */ +#define KDF_INPUT_LEN (INTRO_SECRET_HS_INPUT_LEN + T_HSENC_LEN + INFO_BLOB_LEN) + +/** Helper function: Compute the part of the HS ntor handshake that generates + * key material for creating and handling INTRODUCE1 cells. Function used + * by both client and service. Specifically, calculate the following: + * + * info = m_hsexpand | subcredential + * hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN) + * ENC_KEY = hs_keys[0:S_KEY_LEN] + * MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN] + * + * where intro_secret_hs_input is secret_input (of size + * INTRO_SECRET_HS_INPUT_LEN), and subcredential is of size + * DIGEST256_LEN. + * + * If everything went well, fill hs_ntor_intro_cell_keys_out with the + * necessary key material, and return 0. */ +static void +get_introduce1_key_material(const uint8_t *secret_input, + const uint8_t *subcredential, + hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out) +{ + uint8_t keystream[CIPHER256_KEY_LEN + DIGEST256_LEN]; + uint8_t info_blob[INFO_BLOB_LEN]; + uint8_t kdf_input[KDF_INPUT_LEN]; + crypto_xof_t *xof; + uint8_t *ptr; + + /* Let's build info */ + ptr = info_blob; + APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND)); + APPEND(ptr, subcredential, DIGEST256_LEN); + tor_assert(ptr == info_blob + sizeof(info_blob)); + + /* Let's build the input to the KDF */ + ptr = kdf_input; + APPEND(ptr, secret_input, INTRO_SECRET_HS_INPUT_LEN); + APPEND(ptr, T_HSENC, strlen(T_HSENC)); + APPEND(ptr, info_blob, sizeof(info_blob)); + tor_assert(ptr == kdf_input + sizeof(kdf_input)); + + /* Now we need to run kdf_input over SHAKE-256 */ + xof = crypto_xof_new(); + crypto_xof_add_bytes(xof, kdf_input, sizeof(kdf_input)); + crypto_xof_squeeze_bytes(xof, keystream, sizeof(keystream)) ; + crypto_xof_free(xof); + + { /* Get the keys */ + memcpy(&hs_ntor_intro_cell_keys_out->enc_key, keystream,CIPHER256_KEY_LEN); + memcpy(&hs_ntor_intro_cell_keys_out->mac_key, + keystream+CIPHER256_KEY_LEN, DIGEST256_LEN); + } + + memwipe(keystream, 0, sizeof(keystream)); + memwipe(kdf_input, 0, sizeof(kdf_input)); +} + +/** Helper function: Calculate the 'intro_secret_hs_input' element used by the + * HS ntor handshake and place it in secret_input_out. This function is + * used by both client and service code. + * + * For the client-side it looks like this: + * + * intro_secret_hs_input = EXP(B,x) | AUTH_KEY | X | B | PROTOID + * + * whereas for the service-side it looks like this: + * + * intro_secret_hs_input = EXP(X,b) | AUTH_KEY | X | B | PROTOID + * + * In this function, dh_result carries the EXP() result (and has size + * CURVE25519_OUTPUT_LEN) intro_auth_pubkey is AUTH_KEY, + * client_ephemeral_enc_pubkey is X, and intro_enc_pubkey is B. + */ +static void +get_intro_secret_hs_input(const uint8_t *dh_result, + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_public_key_t *client_ephemeral_enc_pubkey, + const curve25519_public_key_t *intro_enc_pubkey, + uint8_t *secret_input_out) +{ + uint8_t *ptr; + + /* Append EXP() */ + ptr = secret_input_out; + APPEND(ptr, dh_result, CURVE25519_OUTPUT_LEN); + /* Append AUTH_KEY */ + APPEND(ptr, intro_auth_pubkey->pubkey, ED25519_PUBKEY_LEN); + /* Append X */ + APPEND(ptr, client_ephemeral_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN); + /* Append B */ + APPEND(ptr, intro_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN); + /* Append PROTOID */ + APPEND(ptr, PROTOID, strlen(PROTOID)); + tor_assert(ptr == secret_input_out + INTRO_SECRET_HS_INPUT_LEN); +} + +/** Calculate the 'rend_secret_hs_input' element used by the HS ntor handshake + * and place it in rend_secret_hs_input_out. This function is used by + * both client and service code. + * + * The computation on the client side is: + * rend_secret_hs_input = EXP(X,y) | EXP(X,b) | AUTH_KEY | B | X | Y | PROTOID + * whereas on the service side it is: + * rend_secret_hs_input = EXP(Y,x) | EXP(B,x) | AUTH_KEY | B | X | Y | PROTOID + * + * where: + * dh_result1 and dh_result2 carry the two EXP() results (of size + * CURVE25519_OUTPUT_LEN) + * intro_auth_pubkey is AUTH_KEY, + * intro_enc_pubkey is B, + * client_ephemeral_enc_pubkey is X, and + * service_ephemeral_rend_pubkey is Y. + */ +static void +get_rend_secret_hs_input(const uint8_t *dh_result1, const uint8_t *dh_result2, + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_public_key_t *intro_enc_pubkey, + const curve25519_public_key_t *client_ephemeral_enc_pubkey, + const curve25519_public_key_t *service_ephemeral_rend_pubkey, + uint8_t *rend_secret_hs_input_out) +{ + uint8_t *ptr; + + ptr = rend_secret_hs_input_out; + /* Append the first EXP() */ + APPEND(ptr, dh_result1, CURVE25519_OUTPUT_LEN); + /* Append the other EXP() */ + APPEND(ptr, dh_result2, CURVE25519_OUTPUT_LEN); + /* 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 X */ + APPEND(ptr, + client_ephemeral_enc_pubkey->public_key, CURVE25519_PUBKEY_LEN); + /* Append Y */ + APPEND(ptr, + service_ephemeral_rend_pubkey->public_key, CURVE25519_PUBKEY_LEN); + /* Append PROTOID */ + APPEND(ptr, PROTOID, strlen(PROTOID)); + tor_assert(ptr == rend_secret_hs_input_out + REND_SECRET_HS_INPUT_LEN); +} + +/************************* Public functions: *******************************/ + +/* Public function: Do the appropriate ntor calculations and derive the keys + * needed to encrypt and authenticate INTRODUCE1 cells. Return 0 and place the + * final key material in hs_ntor_intro_cell_keys_out if everything went + * well, otherwise return -1; + * + * The relevant calculations are as follows: + * + * intro_secret_hs_input = EXP(B,x) | AUTH_KEY | X | B | PROTOID + * info = m_hsexpand | subcredential + * hs_keys = KDF(intro_secret_hs_input | t_hsenc | info, S_KEY_LEN+MAC_LEN) + * ENC_KEY = hs_keys[0:S_KEY_LEN] + * MAC_KEY = hs_keys[S_KEY_LEN:S_KEY_LEN+MAC_KEY_LEN] + * + * where: + * intro_auth_pubkey is AUTH_KEY (found in HS descriptor), + * intro_enc_pubkey is B (also found in HS descriptor), + * client_ephemeral_enc_keypair is freshly generated keypair (x,X) + * subcredential is the hidden service subcredential (of size + * DIGEST256_LEN). */ +int +hs_ntor_client_get_introduce1_keys( + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_public_key_t *intro_enc_pubkey, + const curve25519_keypair_t *client_ephemeral_enc_keypair, + 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_pubkey); + tor_assert(client_ephemeral_enc_keypair); + tor_assert(subcredential); + tor_assert(hs_ntor_intro_cell_keys_out); + + /* Calculate EXP(B,x) */ + curve25519_handshake(dh_result, + &client_ephemeral_enc_keypair->seckey, + intro_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_keypair->pubkey, + intro_enc_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); + + /* Cleanup */ + 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; +} + +/* Public function: Do the appropriate ntor calculations and derive the keys + * needed to verify RENDEZVOUS1 cells and encrypt further rendezvous + * traffic. Return 0 and place the final key material in + * hs_ntor_rend_cell_keys_out if everything went well, else return -1. + * + * The relevant calculations are as follows: + * + * rend_secret_hs_input = EXP(Y,x) | EXP(B,x) | 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: + * intro_auth_pubkey is AUTH_KEY (found in HS descriptor), + * client_ephemeral_enc_keypair is freshly generated keypair (x,X) + * intro_enc_pubkey is B (also found in HS descriptor), + * service_ephemeral_rend_pubkey is Y (SERVER_PK in RENDEZVOUS1 cell) */ +int +hs_ntor_client_get_rendezvous1_keys( + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_keypair_t *client_ephemeral_enc_keypair, + const curve25519_public_key_t *intro_enc_pubkey, + const curve25519_public_key_t *service_ephemeral_rend_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(client_ephemeral_enc_keypair); + tor_assert(intro_enc_pubkey); + tor_assert(service_ephemeral_rend_pubkey); + tor_assert(hs_ntor_rend_cell_keys_out); + + /* Compute EXP(Y, x) */ + curve25519_handshake(dh_result1, + &client_ephemeral_enc_keypair->seckey, + service_ephemeral_rend_pubkey); + bad |= safe_mem_is_zero(dh_result1, CURVE25519_OUTPUT_LEN); + + /* Compute EXP(B, x) */ + curve25519_handshake(dh_result2, + &client_ephemeral_enc_keypair->seckey, + intro_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_pubkey, + &client_ephemeral_enc_keypair->pubkey, + service_ephemeral_rend_pubkey, + rend_secret_hs_input); + + /* Get NTOR_KEY_SEED and the auth_input MAC */ + bad |= get_rendezvous1_key_material(rend_secret_hs_input, + intro_auth_pubkey, + intro_enc_pubkey, + service_ephemeral_rend_pubkey, + &client_ephemeral_enc_keypair->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 hs_ntor_intro_cell_keys_out 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: + * intro_auth_pubkey is AUTH_KEY (introduction point auth key), + * intro_enc_keypair is (b,B) (introduction point encryption keypair), + * client_ephemeral_enc_pubkey is X (CLIENT_PK in INTRODUCE2 cell), + * subcredential 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; +} + +/* 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 hs_ntor_rend_cell_keys_out 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: + * intro_auth_pubkey is AUTH_KEY (intro point auth key), + * intro_enc_keypair is (b,B) (intro point enc keypair) + * service_ephemeral_rend_keypair is a fresh (y,Y) keypair + * client_ephemeral_enc_pubkey 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; +} + +/** Given a received RENDEZVOUS2 MAC in mac (of length DIGEST256_LEN), + * and the RENDEZVOUS1 key material in hs_ntor_rend_cell_keys, return 1 + * if the MAC is good, otherwise return 0. */ +int +hs_ntor_client_rendezvous2_mac_is_good( + const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys, + const uint8_t *rcvd_mac) +{ + tor_assert(rcvd_mac); + tor_assert(hs_ntor_rend_cell_keys); + + return tor_memeq(hs_ntor_rend_cell_keys->rend_cell_auth_mac, + rcvd_mac, DIGEST256_LEN); +} + +/* Input length to KDF for key expansion */ +#define NTOR_KEY_EXPANSION_KDF_INPUT_LEN (DIGEST256_LEN + M_HSEXPAND_LEN) +/* Output length of KDF for key expansion */ +#define NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN (DIGEST256_LEN*3+CIPHER256_KEY_LEN*2) + +/** Given the rendezvous key material in hs_ntor_rend_cell_keys, do the + * circuit key expansion as specified by section '4.2.1. Key expansion' and + * return a hs_ntor_rend_circuit_keys_t structure with the computed keys. */ +hs_ntor_rend_circuit_keys_t * +hs_ntor_circuit_key_expansion( + const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys) +{ + uint8_t *ptr; + uint8_t kdf_input[NTOR_KEY_EXPANSION_KDF_INPUT_LEN]; + uint8_t keys[NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN]; + crypto_xof_t *xof; + hs_ntor_rend_circuit_keys_t *rend_circuit_keys = NULL; + + /* Let's build the input to the KDF */ + ptr = kdf_input; + APPEND(ptr, hs_ntor_rend_cell_keys->ntor_key_seed, DIGEST256_LEN); + APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND)); + tor_assert(ptr == kdf_input + sizeof(kdf_input)); + + /* Generate the keys */ + xof = crypto_xof_new(); + crypto_xof_add_bytes(xof, kdf_input, sizeof(kdf_input)); + crypto_xof_squeeze_bytes(xof, keys, sizeof(keys)); + crypto_xof_free(xof); + + /* Generate keys structure and assign keys to it */ + rend_circuit_keys = tor_malloc_zero(sizeof(hs_ntor_rend_circuit_keys_t)); + ptr = keys; + memcpy(rend_circuit_keys->KH, ptr, DIGEST256_LEN); + ptr += DIGEST256_LEN;; + memcpy(rend_circuit_keys->Df, ptr, DIGEST256_LEN); + ptr += DIGEST256_LEN; + memcpy(rend_circuit_keys->Db, ptr, DIGEST256_LEN); + ptr += DIGEST256_LEN; + memcpy(rend_circuit_keys->Kf, ptr, CIPHER256_KEY_LEN); + ptr += CIPHER256_KEY_LEN; + memcpy(rend_circuit_keys->Kb, ptr, CIPHER256_KEY_LEN); + ptr += CIPHER256_KEY_LEN; + tor_assert(ptr == keys + sizeof(keys)); + + return rend_circuit_keys; +} + diff --git a/src/or/hs_ntor.h b/src/or/hs_ntor.h new file mode 100644 index 0000000000..cd75f46a4c --- /dev/null +++ b/src/or/hs_ntor.h @@ -0,0 +1,77 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_HS_NTOR_H +#define TOR_HS_NTOR_H + +#include "or.h" + +/* Key material needed to encode/decode INTRODUCE1 cells */ +typedef struct { + /* Key used for encryption of encrypted INTRODUCE1 blob */ + uint8_t enc_key[CIPHER256_KEY_LEN]; + /* MAC key used to protect encrypted INTRODUCE1 blob */ + uint8_t mac_key[DIGEST256_LEN]; +} hs_ntor_intro_cell_keys_t; + +/* Key material needed to encode/decode RENDEZVOUS1 cells */ +typedef struct { + /* This is the MAC of the HANDSHAKE_INFO field */ + uint8_t rend_cell_auth_mac[DIGEST256_LEN]; + /* This is the key seed used to derive further rendezvous crypto keys as + * detailed in section 4.2.1 of rend-spec-ng.txt. */ + uint8_t ntor_key_seed[DIGEST256_LEN]; +} hs_ntor_rend_cell_keys_t; + +/* Key material resulting from key expansion as detailed in section "4.2.1. Key + * expansion" of rend-spec-ng.txt. */ +typedef struct { + /* Per-circuit key material used in ESTABLISH_INTRO cell */ + uint8_t KH[DIGEST256_LEN]; + /* Authentication key for outgoing RELAY cells */ + uint8_t Df[DIGEST256_LEN]; + /* Authentication key for incoming RELAY cells */ + uint8_t Db[DIGEST256_LEN]; + /* Encryption key for outgoing RELAY cells */ + uint8_t Kf[CIPHER256_KEY_LEN]; + /* Decryption key for incoming RELAY cells */ + uint8_t Kb[CIPHER256_KEY_LEN]; +} hs_ntor_rend_circuit_keys_t; + +int hs_ntor_client_get_introduce1_keys( + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_public_key_t *intro_enc_pubkey, + const curve25519_keypair_t *client_ephemeral_enc_keypair, + const uint8_t *subcredential, + hs_ntor_intro_cell_keys_t *hs_ntor_intro_cell_keys_out); + +int hs_ntor_client_get_rendezvous1_keys( + const ed25519_public_key_t *intro_auth_pubkey, + const curve25519_keypair_t *client_ephemeral_enc_keypair, + const curve25519_public_key_t *intro_enc_pubkey, + const curve25519_public_key_t *service_ephemeral_rend_pubkey, + hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out); + +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 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); + +hs_ntor_rend_circuit_keys_t *hs_ntor_circuit_key_expansion( + const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys); + +int hs_ntor_client_rendezvous2_mac_is_good( + const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys, + const uint8_t *rcvd_mac); + +#endif + diff --git a/src/or/include.am b/src/or/include.am index 4e54deca55..a6b60c16db 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -48,6 +48,7 @@ LIBTOR_A_SOURCES = \ src/or/geoip.c \ src/or/hs_intropoint.c \ src/or/hs_circuitmap.c \ + src/or/hs_ntor.c \ src/or/hs_service.c \ src/or/entrynodes.c \ src/or/ext_orport.c \ @@ -171,6 +172,7 @@ ORHEADERS = \ src/or/hs_descriptor.h \ src/or/hs_intropoint.h \ src/or/hs_circuitmap.h \ + src/or/hs_ntor.h \ src/or/hs_service.h \ src/or/keypin.h \ src/or/main.h \