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 \