From 17ea931ac70af3cc11c1729b09b8e1ff17a53348 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 17 Jul 2018 11:27:08 -0400 Subject: [PATCH] Implement DH in NSS. --- src/lib/crypt_ops/crypto_dh.c | 22 +++ src/lib/crypt_ops/crypto_dh.h | 11 +- src/lib/crypt_ops/crypto_dh_nss.c | 207 ++++++++++++++++++++++++++ src/lib/crypt_ops/crypto_dh_openssl.c | 90 +++++------ src/lib/crypt_ops/crypto_init.c | 2 + src/lib/crypt_ops/include.am | 4 +- src/test/testing_common.c | 1 - 7 files changed, 281 insertions(+), 56 deletions(-) create mode 100644 src/lib/crypt_ops/crypto_dh_nss.c diff --git a/src/lib/crypt_ops/crypto_dh.c b/src/lib/crypt_ops/crypto_dh.c index 6f763e37a1..673ef311f9 100644 --- a/src/lib/crypt_ops/crypto_dh.c +++ b/src/lib/crypt_ops/crypto_dh.c @@ -43,6 +43,28 @@ const char OAKLEY_PRIME_2[] = "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" "49286651ECE65381FFFFFFFFFFFFFFFF"; +void +crypto_dh_init(void) +{ +#ifdef ENABLE_OPENSSL + crypto_dh_init_openssl(); +#endif +#ifdef ENABLE_NSS + crypto_dh_init_nss(); +#endif +} + +void +crypto_dh_free_all(void) +{ +#ifdef ENABLE_OPENSSL + crypto_dh_free_all_openssl(); +#endif +#ifdef ENABLE_NSS + crypto_dh_free_all_nss(); +#endif +} + /** Given a DH key exchange object, and our peer's value of g^y (as a * pubkey_len-byte value in pubkey) generate * secret_bytes_out bytes of shared key material and write them diff --git a/src/lib/crypt_ops/crypto_dh.h b/src/lib/crypt_ops/crypto_dh.h index 9533626968..6e79a6404c 100644 --- a/src/lib/crypt_ops/crypto_dh.h +++ b/src/lib/crypt_ops/crypto_dh.h @@ -27,7 +27,7 @@ extern const char OAKLEY_PRIME_2[]; #define DH_TYPE_CIRCUIT 1 #define DH_TYPE_REND 2 #define DH_TYPE_TLS 3 -void crypto_set_tls_dh_prime(void); +void crypto_dh_init(void); crypto_dh_t *crypto_dh_new(int dh_type); crypto_dh_t *crypto_dh_dup(const crypto_dh_t *dh); int crypto_dh_get_bytes(crypto_dh_t *dh); @@ -52,4 +52,13 @@ void crypto_dh_free_all(void); struct dh_st; struct dh_st *crypto_dh_new_openssl_tls(void); +#ifdef ENABLE_OPENSSL +void crypto_dh_init_openssl(void); +void crypto_dh_free_all_openssl(void); +#endif +#ifdef ENABLE_OPENSSL +void crypto_dh_init_nss(void); +void crypto_dh_free_all_nss(void); +#endif + #endif /* !defined(TOR_CRYPTO_DH_H) */ diff --git a/src/lib/crypt_ops/crypto_dh_nss.c b/src/lib/crypt_ops/crypto_dh_nss.c new file mode 100644 index 0000000000..647dfb4e57 --- /dev/null +++ b/src/lib/crypt_ops/crypto_dh_nss.c @@ -0,0 +1,207 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file crypto_dh_nss.h + * + * \brief NSS implementation of Diffie-Hellman over Z_p. + **/ + +#include "lib/crypt_ops/crypto_dh.h" +#include "lib/crypt_ops/crypto_nss_mgt.h" + +#include "lib/encoding/binascii.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" + +#include +#include +#include + +static int dh_initialized = 0; +static SECKEYDHParams tls_dh_param, circuit_dh_param; +static unsigned char tls_dh_prime_data[DH1024_KEY_LEN]; +static unsigned char circuit_dh_prime_data[DH1024_KEY_LEN]; +static unsigned char dh_generator_data[1]; + +void +crypto_dh_init_nss(void) +{ + if (dh_initialized) + return; + + int r; + r = base16_decode((char*)tls_dh_prime_data, + sizeof(tls_dh_prime_data), + TLS_DH_PRIME, strlen(TLS_DH_PRIME)); + tor_assert(r == DH1024_KEY_LEN); + r = base16_decode((char*)circuit_dh_prime_data, + sizeof(circuit_dh_prime_data), + OAKLEY_PRIME_2, strlen(OAKLEY_PRIME_2)); + tor_assert(r == DH1024_KEY_LEN); + dh_generator_data[0] = DH_GENERATOR; + + tls_dh_param.prime.data = tls_dh_prime_data; + tls_dh_param.prime.len = DH1024_KEY_LEN; + tls_dh_param.base.data = dh_generator_data; + tls_dh_param.base.len = 1; + + circuit_dh_param.prime.data = circuit_dh_prime_data; + circuit_dh_param.prime.len = DH1024_KEY_LEN; + circuit_dh_param.base.data = dh_generator_data; + circuit_dh_param.base.len = 1; +} + +void +crypto_dh_free_all_nss(void) +{ + dh_initialized = 0; +} + +struct crypto_dh_t { + int dh_type; // XXXX let's remove this later on. + SECKEYPrivateKey *seckey; + SECKEYPublicKey *pubkey; +}; + +crypto_dh_t * +crypto_dh_new(int dh_type) +{ + crypto_dh_t *r = tor_malloc_zero(sizeof(crypto_dh_t)); + r->dh_type = dh_type; + return r; +} + +crypto_dh_t * +crypto_dh_dup(const crypto_dh_t *dh) +{ + tor_assert(dh); + crypto_dh_t *r = crypto_dh_new(dh->dh_type); + if (dh->seckey) + r->seckey = SECKEY_CopyPrivateKey(dh->seckey); + if (dh->pubkey) + r->pubkey = SECKEY_CopyPublicKey(dh->pubkey); + return r; +} + +int +crypto_dh_get_bytes(crypto_dh_t *dh) +{ + (void)dh; + return DH1024_KEY_LEN; +} + +int +crypto_dh_generate_public(crypto_dh_t *dh) +{ + tor_assert(dh); + SECKEYDHParams *p; + if (dh->dh_type == DH_TYPE_TLS) + p = &tls_dh_param; + else + p = &circuit_dh_param; + + dh->seckey = SECKEY_CreateDHPrivateKey(p, &dh->pubkey, NULL); + if (!dh->seckey || !dh->pubkey) + return -1; + else + return 0; +} +int +crypto_dh_get_public(crypto_dh_t *dh, char *pubkey_out, + size_t pubkey_out_len) +{ + tor_assert(dh); + tor_assert(pubkey_out); + if (!dh->pubkey) { + if (crypto_dh_generate_public(dh) < 0) + return -1; + } + + const SECItem *item = &dh->pubkey->u.dh.publicValue; + + if (item->len > pubkey_out_len) + return -1; + + /* Left-pad the result with 0s. */ + memset(pubkey_out, 0, pubkey_out_len); + memcpy(pubkey_out + pubkey_out_len - item->len, + item->data, + item->len); + + return 0; +} + +void +crypto_dh_free_(crypto_dh_t *dh) +{ + if (!dh) + return; + if (dh->seckey) + SECKEY_DestroyPrivateKey(dh->seckey); + if (dh->pubkey) + SECKEY_DestroyPublicKey(dh->pubkey); + tor_free(dh); +} + +ssize_t +crypto_dh_handshake(int severity, crypto_dh_t *dh, + const char *pubkey, size_t pubkey_len, + unsigned char *secret_out, + size_t secret_bytes_out) +{ + tor_assert(dh); + if (pubkey_len > DH1024_KEY_LEN) + return -1; + if (!dh->pubkey || !dh->seckey) + return -1; + if (secret_bytes_out < DH1024_KEY_LEN) + return -1; + + SECKEYPublicKey peer_key; + memset(&peer_key, 0, sizeof(peer_key)); + peer_key.keyType = dhKey; + peer_key.pkcs11ID = CK_INVALID_HANDLE; + + if (dh->dh_type == DH_TYPE_TLS) + peer_key.u.dh.prime.data = tls_dh_prime_data; // should never use this code + else + peer_key.u.dh.prime.data = circuit_dh_prime_data; + peer_key.u.dh.prime.len = DH1024_KEY_LEN; + peer_key.u.dh.base.data = dh_generator_data; + peer_key.u.dh.base.len = 1; + peer_key.u.dh.publicValue.data = (unsigned char *)pubkey; + peer_key.u.dh.publicValue.len = pubkey_len; + + PK11SymKey *sym = PK11_PubDerive(dh->seckey, &peer_key, + PR_FALSE, NULL, NULL, CKM_DH_PKCS_DERIVE, + CKM_GENERIC_SECRET_KEY_GEN /* ??? */, + CKA_DERIVE, 0, NULL); + if (! sym) { + crypto_nss_log_errors(severity, "deriving a DH shared secret"); + return -1; + } + + SECStatus s = PK11_ExtractKeyValue(sym); + if (s != SECSuccess) { + crypto_nss_log_errors(severity, "extracting a DH shared secret"); + PK11_FreeSymKey(sym); + return -1; + } + + SECItem *result = PK11_GetKeyData(sym); + tor_assert(result); // This cannot fail. + if (BUG(result->len > secret_bytes_out)) { + PK11_FreeSymKey(sym); + return -1; + } + + ssize_t len = result->len; + memcpy(secret_out, result->data, len); + PK11_FreeSymKey(sym); + + return len; +} diff --git a/src/lib/crypt_ops/crypto_dh_openssl.c b/src/lib/crypt_ops/crypto_dh_openssl.c index d66031afd6..54946458d5 100644 --- a/src/lib/crypt_ops/crypto_dh_openssl.c +++ b/src/lib/crypt_ops/crypto_dh_openssl.c @@ -26,14 +26,17 @@ ENABLE_GCC_WARNING(redundant-decls) #include #include +#ifndef ENABLE_NSS static int tor_check_dh_key(int severity, const BIGNUM *bn); -static DH *new_openssl_dh_from_params(BIGNUM *p, BIGNUM *g); /** A structure to hold the first half (x, g^x) of a Diffie-Hellman handshake * while we're waiting for the second.*/ struct crypto_dh_t { DH *dh; /**< The openssl DH object */ }; +#endif + +static DH *new_openssl_dh_from_params(BIGNUM *p, BIGNUM *g); /** Shared P parameter for our circuit-crypto DH key exchanges. */ static BIGNUM *dh_param_p = NULL; @@ -96,6 +99,22 @@ crypto_validate_dh_params(const BIGNUM *p, const BIGNUM *g) return ret; } +/** + * Helper: convert hex to a bignum, and return it. Assert that the + * operation was successful. + */ +static BIGNUM * +bignum_from_hex(const char *hex) +{ + BIGNUM *result = BN_new(); + tor_assert(result); + + int r = BN_hex2bn(&result, hex); + tor_assert(r); + tor_assert(result); + return result; +} + /** Set the global Diffie-Hellman generator, used for both TLS and internal * DH stuff. */ @@ -117,62 +136,23 @@ crypto_set_dh_generator(void) dh_param_g = generator; } -/** Set the global TLS Diffie-Hellman modulus. Use the Apache mod_ssl DH - * modulus. */ +/** Initialize our DH parameters. Idempotent. */ void -crypto_set_tls_dh_prime(void) +crypto_dh_init_openssl(void) { - BIGNUM *tls_prime = NULL; - int r; + if (dh_param_p && dh_param_g && dh_param_p_tls) + return; - /* If the space is occupied, free the previous TLS DH prime */ - if (BUG(dh_param_p_tls)) { - /* LCOV_EXCL_START - * - * We shouldn't be calling this twice. - */ - BN_clear_free(dh_param_p_tls); - dh_param_p_tls = NULL; - /* LCOV_EXCL_STOP */ - } + tor_assert(dh_param_g == NULL); + tor_assert(dh_param_p == NULL); + tor_assert(dh_param_p_tls == NULL); - tls_prime = BN_new(); - tor_assert(tls_prime); - - r = BN_hex2bn(&tls_prime, TLS_DH_PRIME); - tor_assert(r); - - tor_assert(tls_prime); - - dh_param_p_tls = tls_prime; crypto_set_dh_generator(); - tor_assert(0 == crypto_validate_dh_params(dh_param_p_tls, dh_param_g)); -} + dh_param_p = bignum_from_hex(OAKLEY_PRIME_2); + dh_param_p_tls = bignum_from_hex(TLS_DH_PRIME); -/** Initialize dh_param_p and dh_param_g if they are not already - * set. */ -static void -init_dh_param(void) -{ - BIGNUM *circuit_dh_prime; - int r; - if (BUG(dh_param_p && dh_param_g)) - return; // LCOV_EXCL_LINE This function isn't supposed to be called twice. - - circuit_dh_prime = BN_new(); - tor_assert(circuit_dh_prime); - - r = BN_hex2bn(&circuit_dh_prime, OAKLEY_PRIME_2); - tor_assert(r); - - /* Set the new values as the global DH parameters. */ - dh_param_p = circuit_dh_prime; - crypto_set_dh_generator(); tor_assert(0 == crypto_validate_dh_params(dh_param_p, dh_param_g)); - - if (!dh_param_p_tls) { - crypto_set_tls_dh_prime(); - } + tor_assert(0 == crypto_validate_dh_params(dh_param_p_tls, dh_param_g)); } /** Number of bits to use when choosing the x or y value in a Diffie-Hellman @@ -189,6 +169,7 @@ crypto_dh_new_openssl_tls(void) return new_openssl_dh_from_params(dh_param_p_tls, dh_param_g); } +#ifndef ENABLE_NSS /** Allocate and return a new DH object for a key exchange. Returns NULL on * failure. */ @@ -201,7 +182,7 @@ crypto_dh_new(int dh_type) dh_type == DH_TYPE_REND); if (!dh_param_p) - init_dh_param(); + crypto_dh_init(); BIGNUM *dh_p = NULL; if (dh_type == DH_TYPE_TLS) { @@ -215,6 +196,7 @@ crypto_dh_new(int dh_type) tor_free(res); // sets res to NULL. return res; } +#endif /** Create and return a new openssl DH from a given prime and generator. */ static DH * @@ -260,6 +242,7 @@ new_openssl_dh_from_params(BIGNUM *p, BIGNUM *g) /* LCOV_EXCL_STOP */ } +#ifndef ENABLE_NSS /** Return a copy of dh, sharing its internal state. */ crypto_dh_t * crypto_dh_dup(const crypto_dh_t *dh) @@ -386,7 +369,7 @@ tor_check_dh_key(int severity, const BIGNUM *bn) x = BN_new(); tor_assert(x); if (BUG(!dh_param_p)) - init_dh_param(); //LCOV_EXCL_LINE we already checked whether we did this. + crypto_dh_init(); //LCOV_EXCL_LINE we already checked whether we did this. BN_set_word(x, 1); if (BN_cmp(bn,x)<=0) { log_fn(severity, LD_CRYPTO, "DH key must be at least 2."); @@ -472,9 +455,10 @@ crypto_dh_free_(crypto_dh_t *dh) DH_free(dh->dh); tor_free(dh); } +#endif void -crypto_dh_free_all(void) +crypto_dh_free_all_openssl(void) { if (dh_param_p) BN_clear_free(dh_param_p); diff --git a/src/lib/crypt_ops/crypto_init.c b/src/lib/crypt_ops/crypto_init.c index b651474cf8..620fe8e1be 100644 --- a/src/lib/crypt_ops/crypto_init.c +++ b/src/lib/crypt_ops/crypto_init.c @@ -83,6 +83,8 @@ crypto_global_init(int useAccel, const char *accelName, const char *accelDir) crypto_global_initialized_ = 1; + crypto_dh_init(); + #ifdef ENABLE_OPENSSL if (crypto_openssl_late_init(useAccel, accelName, accelDir) < 0) return -1; diff --git a/src/lib/crypt_ops/include.am b/src/lib/crypt_ops/include.am index a5541d49e8..05451cc0e0 100644 --- a/src/lib/crypt_ops/include.am +++ b/src/lib/crypt_ops/include.am @@ -25,10 +25,12 @@ src_lib_libtor_crypt_ops_a_SOURCES = \ if USE_NSS src_lib_libtor_crypt_ops_a_SOURCES += \ src/lib/crypt_ops/aes_nss.c \ + src/lib/crypt_ops/crypto_dh_nss.c \ src/lib/crypt_ops/crypto_nss_mgt.c else src_lib_libtor_crypt_ops_a_SOURCES += \ - src/lib/crypt_ops/aes_openssl.c + src/lib/crypt_ops/aes_openssl.c \ + src/lib/crypt_ops/crypto_dh_openssl.c endif if USE_OPENSSL diff --git a/src/test/testing_common.c b/src/test/testing_common.c index 1611a54b6a..6957342c62 100644 --- a/src/test/testing_common.c +++ b/src/test/testing_common.c @@ -292,7 +292,6 @@ main(int c, const char **v) printf("Can't initialize crypto subsystem; exiting.\n"); return 1; } - crypto_set_tls_dh_prime(); if (crypto_seed_rng() < 0) { printf("Couldn't seed RNG; exiting.\n"); return 1;