diff options
author | Nick Mathewson <nickm@torproject.org> | 2018-09-04 20:21:07 -0400 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2018-09-04 20:21:07 -0400 |
commit | 0db5c549571eb0098a3f709ffb25c2e4909ca01d (patch) | |
tree | 0dbf1fbe9b25ddab292eadca50703f34de4c919e /src/lib | |
parent | fd994f55c441922d3c5ae19ad5c254a1ae1eccfd (diff) | |
parent | d644c93ae9373f99d95870c7b752b790f8714201 (diff) | |
download | tor-0db5c549571eb0098a3f709ffb25c2e4909ca01d.tar.gz tor-0db5c549571eb0098a3f709ffb25c2e4909ca01d.zip |
Merge branch 'nss_squashed' into nss_merge
Diffstat (limited to 'src/lib')
51 files changed, 7885 insertions, 4399 deletions
diff --git a/src/lib/crypt_ops/aes_nss.c b/src/lib/crypt_ops/aes_nss.c new file mode 100644 index 0000000000..272edc5592 --- /dev/null +++ b/src/lib/crypt_ops/aes_nss.c @@ -0,0 +1,106 @@ +/* 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 aes_nss.c + * \brief Use NSS to implement AES_CTR. + **/ + +#include "orconfig.h" +#include "lib/crypt_ops/aes.h" +#include "lib/crypt_ops/crypto_nss_mgt.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/log/util_bug.h" + +DISABLE_GCC_WARNING(strict-prototypes) +#include <pk11pub.h> +#include <secerr.h> +ENABLE_GCC_WARNING(strict-prototypes) + +aes_cnt_cipher_t * +aes_new_cipher(const uint8_t *key, const uint8_t *iv, + int key_bits) +{ + const CK_MECHANISM_TYPE ckm = CKM_AES_CTR; + SECItem keyItem = { .type = siBuffer, + .data = (unsigned char *)key, + .len = (key_bits / 8) }; + CK_AES_CTR_PARAMS params; + params.ulCounterBits = 128; + memcpy(params.cb, iv, 16); + SECItem ivItem = { .type = siBuffer, + .data = (unsigned char *)¶ms, + .len = sizeof(params) }; + PK11SlotInfo *slot = NULL; + PK11SymKey *keyObj = NULL; + SECItem *ivObj = NULL; + PK11Context *result = NULL; + + slot = PK11_GetBestSlot(ckm, NULL); + if (!slot) + goto err; + + keyObj = PK11_ImportSymKey(slot, ckm, PK11_OriginUnwrap, + CKA_ENCRYPT, &keyItem, NULL); + if (!keyObj) + goto err; + + ivObj = PK11_ParamFromIV(ckm, &ivItem); + if (!ivObj) + goto err; + + PORT_SetError(SEC_ERROR_IO); + result = PK11_CreateContextBySymKey(ckm, CKA_ENCRYPT, keyObj, ivObj); + + err: + memwipe(¶ms, 0, sizeof(params)); + if (ivObj) + SECITEM_FreeItem(ivObj, PR_TRUE); + if (keyObj) + PK11_FreeSymKey(keyObj); + if (slot) + PK11_FreeSlot(slot); + + tor_assert(result); + return (aes_cnt_cipher_t *)result; +} + +void +aes_cipher_free_(aes_cnt_cipher_t *cipher) +{ + if (!cipher) + return; + PK11_DestroyContext((PK11Context*) cipher, PR_TRUE); +} + +void +aes_crypt_inplace(aes_cnt_cipher_t *cipher, char *data_, size_t len_) +{ + tor_assert(len_ <= INT_MAX); + + SECStatus s; + PK11Context *ctx = (PK11Context*)cipher; + unsigned char *data = (unsigned char *)data_; + int len = (int) len_; + int result_len = 0; + + s = PK11_CipherOp(ctx, data, &result_len, len, data, len); + tor_assert(s == SECSuccess); + tor_assert(result_len == len); +} + +int +evaluate_evp_for_aes(int force_value) +{ + (void)force_value; + return 0; +} + +int +evaluate_ctr_for_aes(void) +{ + return 0; +} diff --git a/src/lib/crypt_ops/aes.c b/src/lib/crypt_ops/aes_openssl.c index ff9d4d855c..387f5d3df0 100644 --- a/src/lib/crypt_ops/aes.c +++ b/src/lib/crypt_ops/aes_openssl.c @@ -5,8 +5,8 @@ /* See LICENSE for licensing information */ /** - * \file aes.c - * \brief Implements a counter-mode stream cipher on top of AES. + * \file aes_openssl.c + * \brief Use OpenSSL to implement AES_CTR. **/ #include "orconfig.h" diff --git a/src/lib/crypt_ops/compat_openssl.h b/src/lib/crypt_ops/compat_openssl.h index 317c01134a..f2f632ab40 100644 --- a/src/lib/crypt_ops/compat_openssl.h +++ b/src/lib/crypt_ops/compat_openssl.h @@ -7,6 +7,10 @@ #ifndef TOR_COMPAT_OPENSSL_H #define TOR_COMPAT_OPENSSL_H +#include "orconfig.h" + +#ifdef ENABLE_OPENSSL + #include <openssl/opensslv.h> #include "lib/crypt_ops/crypto_openssl_mgt.h" @@ -47,5 +51,7 @@ #define CONST_IF_OPENSSL_1_1_API const #endif /* !defined(OPENSSL_1_1_API) */ +#endif /* defined(ENABLE_OPENSSL) */ + #endif /* !defined(TOR_COMPAT_OPENSSL_H) */ diff --git a/src/lib/crypt_ops/crypto.c b/src/lib/crypt_ops/crypto.c deleted file mode 100644 index 5bc2da76ab..0000000000 --- a/src/lib/crypt_ops/crypto.c +++ /dev/null @@ -1,509 +0,0 @@ -/* 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.c - * \brief Wrapper functions to present a consistent interface to - * public-key and symmetric cryptography operations from OpenSSL and - * other places. - **/ - -#include "orconfig.h" - -#ifdef _WIN32 -#include <winsock2.h> -#include <windows.h> -#include <wincrypt.h> -/* Windows defines this; so does OpenSSL 0.9.8h and later. We don't actually - * use either definition. */ -#undef OCSP_RESPONSE -#endif /* defined(_WIN32) */ - -#define CRYPTO_PRIVATE -#include "lib/crypt_ops/compat_openssl.h" -#include "lib/crypt_ops/crypto.h" -#include "lib/crypt_ops/crypto_curve25519.h" -#include "lib/crypt_ops/crypto_digest.h" -#include "lib/crypt_ops/crypto_dh.h" -#include "lib/crypt_ops/crypto_ed25519.h" -#include "lib/crypt_ops/crypto_format.h" -#include "lib/crypt_ops/crypto_rand.h" -#include "lib/crypt_ops/crypto_rsa.h" -#include "lib/crypt_ops/crypto_util.h" - -DISABLE_GCC_WARNING(redundant-decls) - -#include <openssl/err.h> -#include <openssl/evp.h> -#include <openssl/engine.h> -#include <openssl/bn.h> -#include <openssl/dh.h> -#include <openssl/conf.h> -#include <openssl/hmac.h> -#include <openssl/ssl.h> - -ENABLE_GCC_WARNING(redundant-decls) - -#if __GNUC__ && GCC_VERSION >= 402 -#if GCC_VERSION >= 406 -#pragma GCC diagnostic pop -#else -#pragma GCC diagnostic warning "-Wredundant-decls" -#endif -#endif /* __GNUC__ && GCC_VERSION >= 402 */ - -#ifdef HAVE_CTYPE_H -#include <ctype.h> -#endif -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif - -#include "lib/log/log.h" -#include "lib/log/util_bug.h" -#include "lib/cc/torint.h" -#include "lib/crypt_ops/aes.h" -#include "lib/encoding/binascii.h" - -#include "keccak-tiny/keccak-tiny.h" - -#include "siphash.h" - -#include <string.h> - -/** Boolean: has OpenSSL's crypto been initialized? */ -static int crypto_early_initialized_ = 0; - -/** Boolean: has OpenSSL's crypto been initialized? */ -static int crypto_global_initialized_ = 0; - -#ifndef DISABLE_ENGINES -/** Log any OpenSSL engines we're using at NOTICE. */ -static void -log_engine(const char *fn, ENGINE *e) -{ - if (e) { - const char *name, *id; - name = ENGINE_get_name(e); - id = ENGINE_get_id(e); - log_notice(LD_CRYPTO, "Default OpenSSL engine for %s is %s [%s]", - fn, name?name:"?", id?id:"?"); - } else { - log_info(LD_CRYPTO, "Using default implementation for %s", fn); - } -} -#endif /* !defined(DISABLE_ENGINES) */ - -#ifndef DISABLE_ENGINES -/** Try to load an engine in a shared library via fully qualified path. - */ -static ENGINE * -try_load_engine(const char *path, const char *engine) -{ - ENGINE *e = ENGINE_by_id("dynamic"); - if (e) { - if (!ENGINE_ctrl_cmd_string(e, "ID", engine, 0) || - !ENGINE_ctrl_cmd_string(e, "DIR_LOAD", "2", 0) || - !ENGINE_ctrl_cmd_string(e, "DIR_ADD", path, 0) || - !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0)) { - ENGINE_free(e); - e = NULL; - } - } - return e; -} -#endif /* !defined(DISABLE_ENGINES) */ - -static int have_seeded_siphash = 0; - -/** Set up the siphash key if we haven't already done so. */ -int -crypto_init_siphash_key(void) -{ - struct sipkey key; - if (have_seeded_siphash) - return 0; - - crypto_rand((char*) &key, sizeof(key)); - siphash_set_global_key(&key); - have_seeded_siphash = 1; - return 0; -} - -/** Initialize the crypto library. Return 0 on success, -1 on failure. - */ -int -crypto_early_init(void) -{ - if (!crypto_early_initialized_) { - - crypto_early_initialized_ = 1; - -#ifdef OPENSSL_1_1_API - OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | - OPENSSL_INIT_LOAD_CRYPTO_STRINGS | - OPENSSL_INIT_ADD_ALL_CIPHERS | - OPENSSL_INIT_ADD_ALL_DIGESTS, NULL); -#else - ERR_load_crypto_strings(); - OpenSSL_add_all_algorithms(); -#endif - - setup_openssl_threading(); - - unsigned long version_num = OpenSSL_version_num(); - const char *version_str = OpenSSL_version(OPENSSL_VERSION); - if (version_num == OPENSSL_VERSION_NUMBER && - !strcmp(version_str, OPENSSL_VERSION_TEXT)) { - log_info(LD_CRYPTO, "OpenSSL version matches version from headers " - "(%lx: %s).", version_num, version_str); - } else { - log_warn(LD_CRYPTO, "OpenSSL version from headers does not match the " - "version we're running with. If you get weird crashes, that " - "might be why. (Compiled with %lx: %s; running with %lx: %s).", - (unsigned long)OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_TEXT, - version_num, version_str); - } - - crypto_force_rand_ssleay(); - - if (crypto_seed_rng() < 0) - return -1; - if (crypto_init_siphash_key() < 0) - return -1; - - curve25519_init(); - ed25519_init(); - } - return 0; -} - -/** Initialize the crypto library. Return 0 on success, -1 on failure. - */ -int -crypto_global_init(int useAccel, const char *accelName, const char *accelDir) -{ - if (!crypto_global_initialized_) { - if (crypto_early_init() < 0) - return -1; - - crypto_global_initialized_ = 1; - - if (useAccel > 0) { -#ifdef DISABLE_ENGINES - (void)accelName; - (void)accelDir; - log_warn(LD_CRYPTO, "No OpenSSL hardware acceleration support enabled."); -#else - ENGINE *e = NULL; - - log_info(LD_CRYPTO, "Initializing OpenSSL engine support."); - ENGINE_load_builtin_engines(); - ENGINE_register_all_complete(); - - if (accelName) { - if (accelDir) { - log_info(LD_CRYPTO, "Trying to load dynamic OpenSSL engine \"%s\"" - " via path \"%s\".", accelName, accelDir); - e = try_load_engine(accelName, accelDir); - } else { - log_info(LD_CRYPTO, "Initializing dynamic OpenSSL engine \"%s\"" - " acceleration support.", accelName); - e = ENGINE_by_id(accelName); - } - if (!e) { - log_warn(LD_CRYPTO, "Unable to load dynamic OpenSSL engine \"%s\".", - accelName); - } else { - log_info(LD_CRYPTO, "Loaded dynamic OpenSSL engine \"%s\".", - accelName); - } - } - if (e) { - log_info(LD_CRYPTO, "Loaded OpenSSL hardware acceleration engine," - " setting default ciphers."); - ENGINE_set_default(e, ENGINE_METHOD_ALL); - } - /* Log, if available, the intersection of the set of algorithms - used by Tor and the set of algorithms available in the engine */ - log_engine("RSA", ENGINE_get_default_RSA()); - log_engine("DH", ENGINE_get_default_DH()); -#ifdef OPENSSL_1_1_API - log_engine("EC", ENGINE_get_default_EC()); -#else - log_engine("ECDH", ENGINE_get_default_ECDH()); - log_engine("ECDSA", ENGINE_get_default_ECDSA()); -#endif /* defined(OPENSSL_1_1_API) */ - log_engine("RAND", ENGINE_get_default_RAND()); - log_engine("RAND (which we will not use)", ENGINE_get_default_RAND()); - log_engine("SHA1", ENGINE_get_digest_engine(NID_sha1)); - log_engine("3DES-CBC", ENGINE_get_cipher_engine(NID_des_ede3_cbc)); - log_engine("AES-128-ECB", ENGINE_get_cipher_engine(NID_aes_128_ecb)); - log_engine("AES-128-CBC", ENGINE_get_cipher_engine(NID_aes_128_cbc)); -#ifdef NID_aes_128_ctr - log_engine("AES-128-CTR", ENGINE_get_cipher_engine(NID_aes_128_ctr)); -#endif -#ifdef NID_aes_128_gcm - log_engine("AES-128-GCM", ENGINE_get_cipher_engine(NID_aes_128_gcm)); -#endif - log_engine("AES-256-CBC", ENGINE_get_cipher_engine(NID_aes_256_cbc)); -#ifdef NID_aes_256_gcm - log_engine("AES-256-GCM", ENGINE_get_cipher_engine(NID_aes_256_gcm)); -#endif - -#endif /* defined(DISABLE_ENGINES) */ - } else { - log_info(LD_CRYPTO, "NOT using OpenSSL engine support."); - } - - if (crypto_force_rand_ssleay()) { - if (crypto_seed_rng() < 0) - return -1; - } - - evaluate_evp_for_aes(-1); - evaluate_ctr_for_aes(); - } - return 0; -} - -/** Free crypto resources held by this thread. */ -void -crypto_thread_cleanup(void) -{ -#ifndef NEW_THREAD_API - ERR_remove_thread_state(NULL); -#endif -} - -/** Allocate and return a new symmetric cipher using the provided key and iv. - * The key is <b>bits</b> bits long; the IV is CIPHER_IV_LEN bytes. Both - * must be provided. Key length must be 128, 192, or 256 */ -crypto_cipher_t * -crypto_cipher_new_with_iv_and_bits(const uint8_t *key, - const uint8_t *iv, - int bits) -{ - tor_assert(key); - tor_assert(iv); - - return aes_new_cipher((const uint8_t*)key, (const uint8_t*)iv, bits); -} - -/** Allocate and return a new symmetric cipher using the provided key and iv. - * The key is CIPHER_KEY_LEN bytes; the IV is CIPHER_IV_LEN bytes. Both - * must be provided. - */ -crypto_cipher_t * -crypto_cipher_new_with_iv(const char *key, const char *iv) -{ - return crypto_cipher_new_with_iv_and_bits((uint8_t*)key, (uint8_t*)iv, - 128); -} - -/** Return a new crypto_cipher_t with the provided <b>key</b> and an IV of all - * zero bytes and key length <b>bits</b>. Key length must be 128, 192, or - * 256. */ -crypto_cipher_t * -crypto_cipher_new_with_bits(const char *key, int bits) -{ - char zeroiv[CIPHER_IV_LEN]; - memset(zeroiv, 0, sizeof(zeroiv)); - return crypto_cipher_new_with_iv_and_bits((uint8_t*)key, (uint8_t*)zeroiv, - bits); -} - -/** Return a new crypto_cipher_t with the provided <b>key</b> (of - * CIPHER_KEY_LEN bytes) and an IV of all zero bytes. */ -crypto_cipher_t * -crypto_cipher_new(const char *key) -{ - return crypto_cipher_new_with_bits(key, 128); -} - -/** Free a symmetric cipher. - */ -void -crypto_cipher_free_(crypto_cipher_t *env) -{ - if (!env) - return; - - aes_cipher_free(env); -} - -/** Copy <b>in</b> to the <b>outlen</b>-byte buffer <b>out</b>, adding spaces - * every four characters. */ -void -crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in) -{ - int n = 0; - char *end = out+outlen; - tor_assert(outlen < SIZE_T_CEILING); - - while (*in && out<end) { - *out++ = *in++; - if (++n == 4 && *in && out<end) { - n = 0; - *out++ = ' '; - } - } - tor_assert(out<end); - *out = '\0'; -} - -/* symmetric crypto */ - -/** Encrypt <b>fromlen</b> bytes from <b>from</b> using the cipher - * <b>env</b>; on success, store the result to <b>to</b> and return 0. - * Does not check for failure. - */ -int -crypto_cipher_encrypt(crypto_cipher_t *env, char *to, - const char *from, size_t fromlen) -{ - tor_assert(env); - tor_assert(env); - tor_assert(from); - tor_assert(fromlen); - tor_assert(to); - tor_assert(fromlen < SIZE_T_CEILING); - - memcpy(to, from, fromlen); - aes_crypt_inplace(env, to, fromlen); - return 0; -} - -/** Decrypt <b>fromlen</b> bytes from <b>from</b> using the cipher - * <b>env</b>; on success, store the result to <b>to</b> and return 0. - * Does not check for failure. - */ -int -crypto_cipher_decrypt(crypto_cipher_t *env, char *to, - const char *from, size_t fromlen) -{ - tor_assert(env); - tor_assert(from); - tor_assert(to); - tor_assert(fromlen < SIZE_T_CEILING); - - memcpy(to, from, fromlen); - aes_crypt_inplace(env, to, fromlen); - return 0; -} - -/** Encrypt <b>len</b> bytes on <b>from</b> using the cipher in <b>env</b>; - * on success. Does not check for failure. - */ -void -crypto_cipher_crypt_inplace(crypto_cipher_t *env, char *buf, size_t len) -{ - tor_assert(len < SIZE_T_CEILING); - aes_crypt_inplace(env, buf, len); -} - -/** Encrypt <b>fromlen</b> bytes (at least 1) from <b>from</b> with the key in - * <b>key</b> to the buffer in <b>to</b> of length - * <b>tolen</b>. <b>tolen</b> must be at least <b>fromlen</b> plus - * CIPHER_IV_LEN bytes for the initialization vector. On success, return the - * number of bytes written, on failure, return -1. - */ -int -crypto_cipher_encrypt_with_iv(const char *key, - char *to, size_t tolen, - const char *from, size_t fromlen) -{ - crypto_cipher_t *cipher; - tor_assert(from); - tor_assert(to); - tor_assert(fromlen < INT_MAX); - - if (fromlen < 1) - return -1; - if (tolen < fromlen + CIPHER_IV_LEN) - return -1; - - char iv[CIPHER_IV_LEN]; - crypto_rand(iv, sizeof(iv)); - cipher = crypto_cipher_new_with_iv(key, iv); - - memcpy(to, iv, CIPHER_IV_LEN); - crypto_cipher_encrypt(cipher, to+CIPHER_IV_LEN, from, fromlen); - crypto_cipher_free(cipher); - memwipe(iv, 0, sizeof(iv)); - return (int)(fromlen + CIPHER_IV_LEN); -} - -/** Decrypt <b>fromlen</b> bytes (at least 1+CIPHER_IV_LEN) from <b>from</b> - * with the key in <b>key</b> to the buffer in <b>to</b> of length - * <b>tolen</b>. <b>tolen</b> must be at least <b>fromlen</b> minus - * CIPHER_IV_LEN bytes for the initialization vector. On success, return the - * number of bytes written, on failure, return -1. - */ -int -crypto_cipher_decrypt_with_iv(const char *key, - char *to, size_t tolen, - const char *from, size_t fromlen) -{ - crypto_cipher_t *cipher; - tor_assert(key); - tor_assert(from); - tor_assert(to); - tor_assert(fromlen < INT_MAX); - - if (fromlen <= CIPHER_IV_LEN) - return -1; - if (tolen < fromlen - CIPHER_IV_LEN) - return -1; - - cipher = crypto_cipher_new_with_iv(key, from); - - crypto_cipher_encrypt(cipher, to, from+CIPHER_IV_LEN, fromlen-CIPHER_IV_LEN); - crypto_cipher_free(cipher); - return (int)(fromlen - CIPHER_IV_LEN); -} - -/** @{ */ -/** Uninitialize the crypto library. Return 0 on success. Does not detect - * failure. - */ -int -crypto_global_cleanup(void) -{ -#ifndef OPENSSL_1_1_API - EVP_cleanup(); -#endif -#ifndef NEW_THREAD_API - ERR_remove_thread_state(NULL); -#endif -#ifndef OPENSSL_1_1_API - ERR_free_strings(); -#endif - - crypto_dh_free_all(); - -#ifndef DISABLE_ENGINES -#ifndef OPENSSL_1_1_API - ENGINE_cleanup(); -#endif -#endif - - CONF_modules_unload(1); -#ifndef OPENSSL_1_1_API - CRYPTO_cleanup_all_ex_data(); -#endif - - crypto_openssl_free_all(); - - crypto_early_initialized_ = 0; - crypto_global_initialized_ = 0; - have_seeded_siphash = 0; - siphash_unset_global_key(); - - return 0; -} - -/** @} */ diff --git a/src/lib/crypt_ops/crypto_cipher.c b/src/lib/crypt_ops/crypto_cipher.c new file mode 100644 index 0000000000..6b762e374d --- /dev/null +++ b/src/lib/crypt_ops/crypto_cipher.c @@ -0,0 +1,190 @@ +/* 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_cipher.c + * \brief Symmetric cryptography (low-level) with AES. + **/ + +#include "orconfig.h" + +#include "lib/crypt_ops/crypto_cipher.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" + +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/cc/torint.h" +#include "lib/crypt_ops/aes.h" + +#include <string.h> + +/** Allocate and return a new symmetric cipher using the provided key and iv. + * The key is <b>bits</b> bits long; the IV is CIPHER_IV_LEN bytes. Both + * must be provided. Key length must be 128, 192, or 256 */ +crypto_cipher_t * +crypto_cipher_new_with_iv_and_bits(const uint8_t *key, + const uint8_t *iv, + int bits) +{ + tor_assert(key); + tor_assert(iv); + + return aes_new_cipher((const uint8_t*)key, (const uint8_t*)iv, bits); +} + +/** Allocate and return a new symmetric cipher using the provided key and iv. + * The key is CIPHER_KEY_LEN bytes; the IV is CIPHER_IV_LEN bytes. Both + * must be provided. + */ +crypto_cipher_t * +crypto_cipher_new_with_iv(const char *key, const char *iv) +{ + return crypto_cipher_new_with_iv_and_bits((uint8_t*)key, (uint8_t*)iv, + 128); +} + +/** Return a new crypto_cipher_t with the provided <b>key</b> and an IV of all + * zero bytes and key length <b>bits</b>. Key length must be 128, 192, or + * 256. */ +crypto_cipher_t * +crypto_cipher_new_with_bits(const char *key, int bits) +{ + char zeroiv[CIPHER_IV_LEN]; + memset(zeroiv, 0, sizeof(zeroiv)); + return crypto_cipher_new_with_iv_and_bits((uint8_t*)key, (uint8_t*)zeroiv, + bits); +} + +/** Return a new crypto_cipher_t with the provided <b>key</b> (of + * CIPHER_KEY_LEN bytes) and an IV of all zero bytes. */ +crypto_cipher_t * +crypto_cipher_new(const char *key) +{ + return crypto_cipher_new_with_bits(key, 128); +} + +/** Free a symmetric cipher. + */ +void +crypto_cipher_free_(crypto_cipher_t *env) +{ + if (!env) + return; + + aes_cipher_free(env); +} + +/* symmetric crypto */ + +/** Encrypt <b>fromlen</b> bytes from <b>from</b> using the cipher + * <b>env</b>; on success, store the result to <b>to</b> and return 0. + * Does not check for failure. + */ +int +crypto_cipher_encrypt(crypto_cipher_t *env, char *to, + const char *from, size_t fromlen) +{ + tor_assert(env); + tor_assert(env); + tor_assert(from); + tor_assert(fromlen); + tor_assert(to); + tor_assert(fromlen < SIZE_T_CEILING); + + memcpy(to, from, fromlen); + aes_crypt_inplace(env, to, fromlen); + return 0; +} + +/** Decrypt <b>fromlen</b> bytes from <b>from</b> using the cipher + * <b>env</b>; on success, store the result to <b>to</b> and return 0. + * Does not check for failure. + */ +int +crypto_cipher_decrypt(crypto_cipher_t *env, char *to, + const char *from, size_t fromlen) +{ + tor_assert(env); + tor_assert(from); + tor_assert(to); + tor_assert(fromlen < SIZE_T_CEILING); + + memcpy(to, from, fromlen); + aes_crypt_inplace(env, to, fromlen); + return 0; +} + +/** Encrypt <b>len</b> bytes on <b>from</b> using the cipher in <b>env</b>; + * on success. Does not check for failure. + */ +void +crypto_cipher_crypt_inplace(crypto_cipher_t *env, char *buf, size_t len) +{ + tor_assert(len < SIZE_T_CEILING); + aes_crypt_inplace(env, buf, len); +} + +/** Encrypt <b>fromlen</b> bytes (at least 1) from <b>from</b> with the key in + * <b>key</b> to the buffer in <b>to</b> of length + * <b>tolen</b>. <b>tolen</b> must be at least <b>fromlen</b> plus + * CIPHER_IV_LEN bytes for the initialization vector. On success, return the + * number of bytes written, on failure, return -1. + */ +int +crypto_cipher_encrypt_with_iv(const char *key, + char *to, size_t tolen, + const char *from, size_t fromlen) +{ + crypto_cipher_t *cipher; + tor_assert(from); + tor_assert(to); + tor_assert(fromlen < INT_MAX); + + if (fromlen < 1) + return -1; + if (tolen < fromlen + CIPHER_IV_LEN) + return -1; + + char iv[CIPHER_IV_LEN]; + crypto_rand(iv, sizeof(iv)); + cipher = crypto_cipher_new_with_iv(key, iv); + + memcpy(to, iv, CIPHER_IV_LEN); + crypto_cipher_encrypt(cipher, to+CIPHER_IV_LEN, from, fromlen); + crypto_cipher_free(cipher); + memwipe(iv, 0, sizeof(iv)); + return (int)(fromlen + CIPHER_IV_LEN); +} + +/** Decrypt <b>fromlen</b> bytes (at least 1+CIPHER_IV_LEN) from <b>from</b> + * with the key in <b>key</b> to the buffer in <b>to</b> of length + * <b>tolen</b>. <b>tolen</b> must be at least <b>fromlen</b> minus + * CIPHER_IV_LEN bytes for the initialization vector. On success, return the + * number of bytes written, on failure, return -1. + */ +int +crypto_cipher_decrypt_with_iv(const char *key, + char *to, size_t tolen, + const char *from, size_t fromlen) +{ + crypto_cipher_t *cipher; + tor_assert(key); + tor_assert(from); + tor_assert(to); + tor_assert(fromlen < INT_MAX); + + if (fromlen <= CIPHER_IV_LEN) + return -1; + if (tolen < fromlen - CIPHER_IV_LEN) + return -1; + + cipher = crypto_cipher_new_with_iv(key, from); + + crypto_cipher_encrypt(cipher, to, from+CIPHER_IV_LEN, fromlen-CIPHER_IV_LEN); + crypto_cipher_free(cipher); + return (int)(fromlen - CIPHER_IV_LEN); +} diff --git a/src/lib/crypt_ops/crypto.h b/src/lib/crypt_ops/crypto_cipher.h index 3a0b330be6..f9444d03fc 100644 --- a/src/lib/crypt_ops/crypto.h +++ b/src/lib/crypt_ops/crypto_cipher.h @@ -5,19 +5,18 @@ /* See LICENSE for licensing information */ /** - * \file crypto.h + * \file crypto_cipher.h * - * \brief Headers for crypto.c + * \brief Headers for crypto_cipher.c **/ -#ifndef TOR_CRYPTO_H -#define TOR_CRYPTO_H +#ifndef TOR_CRYPTO_CIPHER_H +#define TOR_CRYPTO_CIPHER_H #include "orconfig.h" #include <stdio.h> #include "lib/cc/torint.h" -#include "lib/crypt_ops/crypto_rsa.h" /** Length of our symmetric cipher's keys of 128-bit. */ #define CIPHER_KEY_LEN 16 @@ -26,22 +25,8 @@ /** Length of our symmetric cipher's keys of 256-bit. */ #define CIPHER256_KEY_LEN 32 -/** Length of encoded public key fingerprints, including space; but not - * including terminating NUL. */ -#define FINGERPRINT_LEN 49 - typedef struct aes_cnt_cipher crypto_cipher_t; -/* global state */ -int crypto_init_siphash_key(void); -int crypto_early_init(void) ATTR_WUR; -int crypto_global_init(int hardwareAccel, - const char *accelName, - const char *accelPath) ATTR_WUR; - -void crypto_thread_cleanup(void); -int crypto_global_cleanup(void); - /* environment setup */ crypto_cipher_t *crypto_cipher_new(const char *key); crypto_cipher_t *crypto_cipher_new_with_bits(const char *key, int bits); @@ -69,6 +54,4 @@ int crypto_cipher_decrypt_with_iv(const char *key, char *to, size_t tolen, const char *from, size_t fromlen); -void crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in); - #endif /* !defined(TOR_CRYPTO_H) */ diff --git a/src/lib/crypt_ops/crypto_dh.c b/src/lib/crypt_ops/crypto_dh.c index 3e82996935..673ef311f9 100644 --- a/src/lib/crypt_ops/crypto_dh.c +++ b/src/lib/crypt_ops/crypto_dh.c @@ -7,6 +7,8 @@ /** * \file crypto_dh.c * \brief Block of functions related with DH utilities and operations. + * over Z_p. We aren't using this for any new crypto -- EC is more + * efficient. **/ #include "lib/crypt_ops/compat_openssl.h" @@ -17,411 +19,50 @@ #include "lib/log/log.h" #include "lib/log/util_bug.h" -DISABLE_GCC_WARNING(redundant-decls) - -#include <openssl/dh.h> - -ENABLE_GCC_WARNING(redundant-decls) - -#include <openssl/bn.h> -#include <string.h> - -/** 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 */ -}; - -static int tor_check_dh_key(int severity, const BIGNUM *bn); - -/** Used by tortls.c: Get the DH* from a crypto_dh_t. - */ -DH * -crypto_dh_get_dh_(crypto_dh_t *dh) -{ - return dh->dh; -} - /** Our DH 'g' parameter */ -#define DH_GENERATOR 2 - -/** Shared P parameter for our circuit-crypto DH key exchanges. */ -static BIGNUM *dh_param_p = NULL; -/** Shared P parameter for our TLS DH key exchanges. */ -static BIGNUM *dh_param_p_tls = NULL; -/** Shared G parameter for our DH key exchanges. */ -static BIGNUM *dh_param_g = NULL; - -/** Validate a given set of Diffie-Hellman parameters. This is moderately - * computationally expensive (milliseconds), so should only be called when - * the DH parameters change. Returns 0 on success, * -1 on failure. +const unsigned DH_GENERATOR = 2; +/** This is the 1024-bit safe prime that Apache uses for its DH stuff; see + * modules/ssl/ssl_engine_dh.c; Apache also uses a generator of 2 with this + * prime. */ -static int -crypto_validate_dh_params(const BIGNUM *p, const BIGNUM *g) -{ - DH *dh = NULL; - int ret = -1; - - /* Copy into a temporary DH object, just so that DH_check() can be called. */ - if (!(dh = DH_new())) - goto out; -#ifdef OPENSSL_1_1_API - BIGNUM *dh_p, *dh_g; - if (!(dh_p = BN_dup(p))) - goto out; - if (!(dh_g = BN_dup(g))) - goto out; - if (!DH_set0_pqg(dh, dh_p, NULL, dh_g)) - goto out; -#else /* !(defined(OPENSSL_1_1_API)) */ - if (!(dh->p = BN_dup(p))) - goto out; - if (!(dh->g = BN_dup(g))) - goto out; -#endif /* defined(OPENSSL_1_1_API) */ - - /* Perform the validation. */ - int codes = 0; - if (!DH_check(dh, &codes)) - goto out; - if (BN_is_word(g, DH_GENERATOR_2)) { - /* Per https://wiki.openssl.org/index.php/Diffie-Hellman_parameters - * - * OpenSSL checks the prime is congruent to 11 when g = 2; while the - * IETF's primes are congruent to 23 when g = 2. - */ - BN_ULONG residue = BN_mod_word(p, 24); - if (residue == 11 || residue == 23) - codes &= ~DH_NOT_SUITABLE_GENERATOR; - } - if (codes != 0) /* Specifics on why the params suck is irrelevant. */ - goto out; - - /* Things are probably not evil. */ - ret = 0; - - out: - if (dh) - DH_free(dh); - return ret; -} - -/** Set the global Diffie-Hellman generator, used for both TLS and internal - * DH stuff. +const char TLS_DH_PRIME[] = + "D67DE440CBBBDC1936D693D34AFD0AD50C84D239A45F520BB88174CB98" + "BCE951849F912E639C72FB13B4B4D7177E16D55AC179BA420B2A29FE324A" + "467A635E81FF5901377BEDDCFD33168A461AAD3B72DAE8860078045B07A7" + "DBCA7874087D1510EA9FCC9DDD330507DD62DB88AEAA747DE0F4D6E2BD68" + "B0E7393E0F24218EB3"; +/** + * This is from rfc2409, section 6.2. It's a safe prime, and + * supposedly it equals: + * 2^1024 - 2^960 - 1 + 2^64 * { [2^894 pi] + 129093 }. */ -static void -crypto_set_dh_generator(void) -{ - BIGNUM *generator; - int r; +const char OAKLEY_PRIME_2[] = + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + "49286651ECE65381FFFFFFFFFFFFFFFF"; - if (dh_param_g) - return; - - generator = BN_new(); - tor_assert(generator); - - r = BN_set_word(generator, DH_GENERATOR); - tor_assert(r); - - dh_param_g = generator; -} - -/** Set the global TLS Diffie-Hellman modulus. Use the Apache mod_ssl DH - * modulus. */ void -crypto_set_tls_dh_prime(void) -{ - BIGNUM *tls_prime = NULL; - int r; - - /* 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 */ - } - - tls_prime = BN_new(); - tor_assert(tls_prime); - - /* This is the 1024-bit safe prime that Apache uses for its DH stuff; see - * modules/ssl/ssl_engine_dh.c; Apache also uses a generator of 2 with this - * prime. - */ - r = BN_hex2bn(&tls_prime, - "D67DE440CBBBDC1936D693D34AFD0AD50C84D239A45F520BB88174CB98" - "BCE951849F912E639C72FB13B4B4D7177E16D55AC179BA420B2A29FE324A" - "467A635E81FF5901377BEDDCFD33168A461AAD3B72DAE8860078045B07A7" - "DBCA7874087D1510EA9FCC9DDD330507DD62DB88AEAA747DE0F4D6E2BD68" - "B0E7393E0F24218EB3"); - 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)); -} - -/** 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); - - /* This is from rfc2409, section 6.2. It's a safe prime, and - supposedly it equals: - 2^1024 - 2^960 - 1 + 2^64 * { [2^894 pi] + 129093 }. - */ - r = BN_hex2bn(&circuit_dh_prime, - "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" - "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" - "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" - "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" - "49286651ECE65381FFFFFFFFFFFFFFFF"); - 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(); - } -} - -/** Number of bits to use when choosing the x or y value in a Diffie-Hellman - * handshake. Since we exponentiate by this value, choosing a smaller one - * lets our handhake go faster. - */ -#define DH_PRIVATE_KEY_BITS 320 - -/** Allocate and return a new DH object for a key exchange. Returns NULL on - * failure. - */ -crypto_dh_t * -crypto_dh_new(int dh_type) -{ - crypto_dh_t *res = tor_malloc_zero(sizeof(crypto_dh_t)); - - tor_assert(dh_type == DH_TYPE_CIRCUIT || dh_type == DH_TYPE_TLS || - dh_type == DH_TYPE_REND); - - if (!dh_param_p) - init_dh_param(); - - if (!(res->dh = DH_new())) - goto err; - -#ifdef OPENSSL_1_1_API - BIGNUM *dh_p = NULL, *dh_g = NULL; - - if (dh_type == DH_TYPE_TLS) { - dh_p = BN_dup(dh_param_p_tls); - } else { - dh_p = BN_dup(dh_param_p); - } - if (!dh_p) - goto err; - - dh_g = BN_dup(dh_param_g); - if (!dh_g) { - BN_free(dh_p); - goto err; - } - - if (!DH_set0_pqg(res->dh, dh_p, NULL, dh_g)) { - goto err; - } - - if (!DH_set_length(res->dh, DH_PRIVATE_KEY_BITS)) - goto err; -#else /* !(defined(OPENSSL_1_1_API)) */ - if (dh_type == DH_TYPE_TLS) { - if (!(res->dh->p = BN_dup(dh_param_p_tls))) - goto err; - } else { - if (!(res->dh->p = BN_dup(dh_param_p))) - goto err; - } - - if (!(res->dh->g = BN_dup(dh_param_g))) - goto err; - - res->dh->length = DH_PRIVATE_KEY_BITS; -#endif /* defined(OPENSSL_1_1_API) */ - - return res; - - /* LCOV_EXCL_START - * This error condition is only reached when an allocation fails */ - err: - crypto_log_errors(LOG_WARN, "creating DH object"); - if (res->dh) DH_free(res->dh); /* frees p and g too */ - tor_free(res); - return NULL; - /* LCOV_EXCL_STOP */ -} - -/** Return a copy of <b>dh</b>, sharing its internal state. */ -crypto_dh_t * -crypto_dh_dup(const crypto_dh_t *dh) +crypto_dh_init(void) { - crypto_dh_t *dh_new = tor_malloc_zero(sizeof(crypto_dh_t)); - tor_assert(dh); - tor_assert(dh->dh); - dh_new->dh = dh->dh; - DH_up_ref(dh->dh); - return dh_new; -} - -/** Return the length of the DH key in <b>dh</b>, in bytes. - */ -int -crypto_dh_get_bytes(crypto_dh_t *dh) -{ - tor_assert(dh); - return DH_size(dh->dh); -} - -/** Generate \<x,g^x\> for our part of the key exchange. Return 0 on - * success, -1 on failure. - */ -int -crypto_dh_generate_public(crypto_dh_t *dh) -{ -#ifndef OPENSSL_1_1_API - again: +#ifdef ENABLE_OPENSSL + crypto_dh_init_openssl(); #endif - if (!DH_generate_key(dh->dh)) { - /* LCOV_EXCL_START - * To test this we would need some way to tell openssl to break DH. */ - crypto_log_errors(LOG_WARN, "generating DH key"); - return -1; - /* LCOV_EXCL_STOP */ - } -#ifdef OPENSSL_1_1_API - /* OpenSSL 1.1.x doesn't appear to let you regenerate a DH key, without - * recreating the DH object. I have no idea what sort of aliasing madness - * can occur here, so do the check, and just bail on failure. - */ - const BIGNUM *pub_key, *priv_key; - DH_get0_key(dh->dh, &pub_key, &priv_key); - if (tor_check_dh_key(LOG_WARN, pub_key)<0) { - log_warn(LD_CRYPTO, "Weird! Our own DH key was invalid. I guess once-in-" - "the-universe chances really do happen. Treating as a failure."); - return -1; - } -#else /* !(defined(OPENSSL_1_1_API)) */ - if (tor_check_dh_key(LOG_WARN, dh->dh->pub_key)<0) { - /* LCOV_EXCL_START - * If this happens, then openssl's DH implementation is busted. */ - log_warn(LD_CRYPTO, "Weird! Our own DH key was invalid. I guess once-in-" - "the-universe chances really do happen. Trying again."); - /* Free and clear the keys, so OpenSSL will actually try again. */ - BN_clear_free(dh->dh->pub_key); - BN_clear_free(dh->dh->priv_key); - dh->dh->pub_key = dh->dh->priv_key = NULL; - goto again; - /* LCOV_EXCL_STOP */ - } -#endif /* defined(OPENSSL_1_1_API) */ - return 0; -} - -/** Generate g^x as necessary, and write the g^x for the key exchange - * as a <b>pubkey_len</b>-byte value into <b>pubkey</b>. Return 0 on - * success, -1 on failure. <b>pubkey_len</b> must be \>= DH1024_KEY_LEN. - */ -int -crypto_dh_get_public(crypto_dh_t *dh, char *pubkey, size_t pubkey_len) -{ - int bytes; - tor_assert(dh); - - const BIGNUM *dh_pub; - -#ifdef OPENSSL_1_1_API - const BIGNUM *dh_priv; - DH_get0_key(dh->dh, &dh_pub, &dh_priv); -#else - dh_pub = dh->dh->pub_key; -#endif /* defined(OPENSSL_1_1_API) */ - - if (!dh_pub) { - if (crypto_dh_generate_public(dh)<0) - return -1; - else { -#ifdef OPENSSL_1_1_API - DH_get0_key(dh->dh, &dh_pub, &dh_priv); -#else - dh_pub = dh->dh->pub_key; +#ifdef ENABLE_NSS + crypto_dh_init_nss(); #endif - } - } - - tor_assert(dh_pub); - bytes = BN_num_bytes(dh_pub); - tor_assert(bytes >= 0); - if (pubkey_len < (size_t)bytes) { - log_warn(LD_CRYPTO, - "Weird! pubkey_len (%d) was smaller than DH1024_KEY_LEN (%d)", - (int) pubkey_len, bytes); - return -1; - } - - memset(pubkey, 0, pubkey_len); - BN_bn2bin(dh_pub, (unsigned char*)(pubkey+(pubkey_len-bytes))); - - return 0; } -/** Check for bad Diffie-Hellman public keys (g^x). Return 0 if the key is - * okay (in the subgroup [2,p-2]), or -1 if it's bad. - * See http://www.cl.cam.ac.uk/ftp/users/rja14/psandqs.ps.gz for some tips. - */ -static int -tor_check_dh_key(int severity, const BIGNUM *bn) +void +crypto_dh_free_all(void) { - BIGNUM *x; - char *s; - tor_assert(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. - BN_set_word(x, 1); - if (BN_cmp(bn,x)<=0) { - log_fn(severity, LD_CRYPTO, "DH key must be at least 2."); - goto err; - } - BN_copy(x,dh_param_p); - BN_sub_word(x, 1); - if (BN_cmp(bn,x)>=0) { - log_fn(severity, LD_CRYPTO, "DH key must be at most p-2."); - goto err; - } - BN_clear_free(x); - return 0; - err: - BN_clear_free(x); - s = BN_bn2hex(bn); - log_fn(severity, LD_CRYPTO, "Rejecting insecure DH key [%s]", s); - OPENSSL_free(s); - return -1; +#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 @@ -439,31 +80,20 @@ crypto_dh_compute_secret(int severity, crypto_dh_t *dh, const char *pubkey, size_t pubkey_len, char *secret_out, size_t secret_bytes_out) { - char *secret_tmp = NULL; - BIGNUM *pubkey_bn = NULL; - size_t secret_len=0, secret_tmp_len=0; - int result=0; - tor_assert(dh); tor_assert(secret_bytes_out/DIGEST_LEN <= 255); - tor_assert(pubkey_len < INT_MAX); - if (!(pubkey_bn = BN_bin2bn((const unsigned char*)pubkey, - (int)pubkey_len, NULL))) - goto error; - if (tor_check_dh_key(severity, pubkey_bn)<0) { - /* Check for invalid public keys. */ - log_fn(severity, LD_CRYPTO,"Rejected invalid g^x"); - goto error; - } + unsigned char *secret_tmp = NULL; + size_t secret_len=0, secret_tmp_len=0; secret_tmp_len = crypto_dh_get_bytes(dh); secret_tmp = tor_malloc(secret_tmp_len); - result = DH_compute_key((unsigned char*)secret_tmp, pubkey_bn, dh->dh); - if (result < 0) { - log_warn(LD_CRYPTO,"DH_compute_key() failed."); + + ssize_t result = crypto_dh_handshake(severity, dh, pubkey, pubkey_len, + secret_tmp, secret_tmp_len); + if (result < 0) goto error; - } + secret_len = result; - if (crypto_expand_key_material_TAP((uint8_t*)secret_tmp, secret_len, + if (crypto_expand_key_material_TAP(secret_tmp, secret_len, (uint8_t*)secret_out, secret_bytes_out)<0) goto error; secret_len = secret_bytes_out; @@ -472,9 +102,6 @@ crypto_dh_compute_secret(int severity, crypto_dh_t *dh, error: result = -1; done: - crypto_log_errors(LOG_WARN, "completing DH handshake"); - if (pubkey_bn) - BN_clear_free(pubkey_bn); if (secret_tmp) { memwipe(secret_tmp, 0, secret_tmp_len); tor_free(secret_tmp); @@ -484,28 +111,3 @@ crypto_dh_compute_secret(int severity, crypto_dh_t *dh, else return secret_len; } - -/** Free a DH key exchange object. - */ -void -crypto_dh_free_(crypto_dh_t *dh) -{ - if (!dh) - return; - tor_assert(dh->dh); - DH_free(dh->dh); - tor_free(dh); -} - -void -crypto_dh_free_all(void) -{ - if (dh_param_p) - BN_clear_free(dh_param_p); - if (dh_param_p_tls) - BN_clear_free(dh_param_p_tls); - if (dh_param_g) - BN_clear_free(dh_param_g); - - dh_param_p = dh_param_p_tls = dh_param_g = NULL; -} diff --git a/src/lib/crypt_ops/crypto_dh.h b/src/lib/crypt_ops/crypto_dh.h index 88e8a919a8..3ee343a278 100644 --- a/src/lib/crypt_ops/crypto_dh.h +++ b/src/lib/crypt_ops/crypto_dh.h @@ -19,11 +19,15 @@ typedef struct crypto_dh_t crypto_dh_t; +extern const unsigned DH_GENERATOR; +extern const char TLS_DH_PRIME[]; +extern const char OAKLEY_PRIME_2[]; + /* Key negotiation */ #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); @@ -36,12 +40,25 @@ ssize_t crypto_dh_compute_secret(int severity, crypto_dh_t *dh, void crypto_dh_free_(crypto_dh_t *dh); #define crypto_dh_free(dh) FREE_AND_NULL(crypto_dh_t, crypto_dh_free_, (dh)) -/* Crypto DH free */ +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); + void crypto_dh_free_all(void); /* Prototypes for private functions only used by tortls.c, crypto.c, and the * unit tests. */ struct dh_st; -struct dh_st *crypto_dh_get_dh_(crypto_dh_t *dh); +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_NSS +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..9a14b809b4 --- /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 <cryptohi.h> +#include <keyhi.h> +#include <pk11pub.h> + +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 = (int) 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 new file mode 100644 index 0000000000..54946458d5 --- /dev/null +++ b/src/lib/crypt_ops/crypto_dh_openssl.c @@ -0,0 +1,471 @@ +/* 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_openssl.c + * \brief Implement Tor's Z_p diffie-hellman stuff for OpenSSL. + **/ + +#include "lib/crypt_ops/compat_openssl.h" +#include "lib/crypt_ops/crypto_dh.h" +#include "lib/crypt_ops/crypto_digest.h" +#include "lib/crypt_ops/crypto_hkdf.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +DISABLE_GCC_WARNING(redundant-decls) + +#include <openssl/dh.h> + +ENABLE_GCC_WARNING(redundant-decls) + +#include <openssl/bn.h> +#include <string.h> + +#ifndef ENABLE_NSS +static int tor_check_dh_key(int severity, const BIGNUM *bn); + +/** 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; +/** Shared P parameter for our TLS DH key exchanges. */ +static BIGNUM *dh_param_p_tls = NULL; +/** Shared G parameter for our DH key exchanges. */ +static BIGNUM *dh_param_g = NULL; + +/** Validate a given set of Diffie-Hellman parameters. This is moderately + * computationally expensive (milliseconds), so should only be called when + * the DH parameters change. Returns 0 on success, * -1 on failure. + */ +static int +crypto_validate_dh_params(const BIGNUM *p, const BIGNUM *g) +{ + DH *dh = NULL; + int ret = -1; + + /* Copy into a temporary DH object, just so that DH_check() can be called. */ + if (!(dh = DH_new())) + goto out; +#ifdef OPENSSL_1_1_API + BIGNUM *dh_p, *dh_g; + if (!(dh_p = BN_dup(p))) + goto out; + if (!(dh_g = BN_dup(g))) + goto out; + if (!DH_set0_pqg(dh, dh_p, NULL, dh_g)) + goto out; +#else /* !(defined(OPENSSL_1_1_API)) */ + if (!(dh->p = BN_dup(p))) + goto out; + if (!(dh->g = BN_dup(g))) + goto out; +#endif /* defined(OPENSSL_1_1_API) */ + + /* Perform the validation. */ + int codes = 0; + if (!DH_check(dh, &codes)) + goto out; + if (BN_is_word(g, DH_GENERATOR_2)) { + /* Per https://wiki.openssl.org/index.php/Diffie-Hellman_parameters + * + * OpenSSL checks the prime is congruent to 11 when g = 2; while the + * IETF's primes are congruent to 23 when g = 2. + */ + BN_ULONG residue = BN_mod_word(p, 24); + if (residue == 11 || residue == 23) + codes &= ~DH_NOT_SUITABLE_GENERATOR; + } + if (codes != 0) /* Specifics on why the params suck is irrelevant. */ + goto out; + + /* Things are probably not evil. */ + ret = 0; + + out: + if (dh) + DH_free(dh); + return ret; +} + +/** + * Helper: convert <b>hex<b> 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. + */ +static void +crypto_set_dh_generator(void) +{ + BIGNUM *generator; + int r; + + if (dh_param_g) + return; + + generator = BN_new(); + tor_assert(generator); + + r = BN_set_word(generator, DH_GENERATOR); + tor_assert(r); + + dh_param_g = generator; +} + +/** Initialize our DH parameters. Idempotent. */ +void +crypto_dh_init_openssl(void) +{ + if (dh_param_p && dh_param_g && dh_param_p_tls) + return; + + tor_assert(dh_param_g == NULL); + tor_assert(dh_param_p == NULL); + tor_assert(dh_param_p_tls == NULL); + + crypto_set_dh_generator(); + dh_param_p = bignum_from_hex(OAKLEY_PRIME_2); + dh_param_p_tls = bignum_from_hex(TLS_DH_PRIME); + + tor_assert(0 == crypto_validate_dh_params(dh_param_p, dh_param_g)); + 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 + * handshake. Since we exponentiate by this value, choosing a smaller one + * lets our handhake go faster. + */ +#define DH_PRIVATE_KEY_BITS 320 + +/** Used by tortls.c: Get the DH* for use with TLS. + */ +DH * +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. + */ +crypto_dh_t * +crypto_dh_new(int dh_type) +{ + crypto_dh_t *res = tor_malloc_zero(sizeof(crypto_dh_t)); + + tor_assert(dh_type == DH_TYPE_CIRCUIT || dh_type == DH_TYPE_TLS || + dh_type == DH_TYPE_REND); + + if (!dh_param_p) + crypto_dh_init(); + + BIGNUM *dh_p = NULL; + if (dh_type == DH_TYPE_TLS) { + dh_p = dh_param_p_tls; + } else { + dh_p = dh_param_p; + } + + res->dh = new_openssl_dh_from_params(dh_p, dh_param_g); + if (res->dh == NULL) + 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 * +new_openssl_dh_from_params(BIGNUM *p, BIGNUM *g) +{ + DH *res_dh; + if (!(res_dh = DH_new())) + goto err; + + BIGNUM *dh_p = NULL, *dh_g = NULL; + dh_p = BN_dup(p); + if (!dh_p) + goto err; + + dh_g = BN_dup(g); + if (!dh_g) { + BN_free(dh_p); + goto err; + } + +#ifdef OPENSSL_1_1_API + + if (!DH_set0_pqg(res_dh, dh_p, NULL, dh_g)) { + goto err; + } + + if (!DH_set_length(res_dh, DH_PRIVATE_KEY_BITS)) + goto err; +#else /* !(defined(OPENSSL_1_1_API)) */ + res_dh->p = dh_p; + res_dh->g = dh_g; + res_dh->length = DH_PRIVATE_KEY_BITS; +#endif /* defined(OPENSSL_1_1_API) */ + + return res_dh; + + /* LCOV_EXCL_START + * This error condition is only reached when an allocation fails */ + err: + crypto_openssl_log_errors(LOG_WARN, "creating DH object"); + if (res_dh) DH_free(res_dh); /* frees p and g too */ + return NULL; + /* LCOV_EXCL_STOP */ +} + +#ifndef ENABLE_NSS +/** Return a copy of <b>dh</b>, sharing its internal state. */ +crypto_dh_t * +crypto_dh_dup(const crypto_dh_t *dh) +{ + crypto_dh_t *dh_new = tor_malloc_zero(sizeof(crypto_dh_t)); + tor_assert(dh); + tor_assert(dh->dh); + dh_new->dh = dh->dh; + DH_up_ref(dh->dh); + return dh_new; +} + +/** Return the length of the DH key in <b>dh</b>, in bytes. + */ +int +crypto_dh_get_bytes(crypto_dh_t *dh) +{ + tor_assert(dh); + return DH_size(dh->dh); +} + +/** Generate \<x,g^x\> for our part of the key exchange. Return 0 on + * success, -1 on failure. + */ +int +crypto_dh_generate_public(crypto_dh_t *dh) +{ +#ifndef OPENSSL_1_1_API + again: +#endif + if (!DH_generate_key(dh->dh)) { + /* LCOV_EXCL_START + * To test this we would need some way to tell openssl to break DH. */ + crypto_openssl_log_errors(LOG_WARN, "generating DH key"); + return -1; + /* LCOV_EXCL_STOP */ + } +#ifdef OPENSSL_1_1_API + /* OpenSSL 1.1.x doesn't appear to let you regenerate a DH key, without + * recreating the DH object. I have no idea what sort of aliasing madness + * can occur here, so do the check, and just bail on failure. + */ + const BIGNUM *pub_key, *priv_key; + DH_get0_key(dh->dh, &pub_key, &priv_key); + if (tor_check_dh_key(LOG_WARN, pub_key)<0) { + log_warn(LD_CRYPTO, "Weird! Our own DH key was invalid. I guess once-in-" + "the-universe chances really do happen. Treating as a failure."); + return -1; + } +#else /* !(defined(OPENSSL_1_1_API)) */ + if (tor_check_dh_key(LOG_WARN, dh->dh->pub_key)<0) { + /* LCOV_EXCL_START + * If this happens, then openssl's DH implementation is busted. */ + log_warn(LD_CRYPTO, "Weird! Our own DH key was invalid. I guess once-in-" + "the-universe chances really do happen. Trying again."); + /* Free and clear the keys, so OpenSSL will actually try again. */ + BN_clear_free(dh->dh->pub_key); + BN_clear_free(dh->dh->priv_key); + dh->dh->pub_key = dh->dh->priv_key = NULL; + goto again; + /* LCOV_EXCL_STOP */ + } +#endif /* defined(OPENSSL_1_1_API) */ + return 0; +} + +/** Generate g^x as necessary, and write the g^x for the key exchange + * as a <b>pubkey_len</b>-byte value into <b>pubkey</b>. Return 0 on + * success, -1 on failure. <b>pubkey_len</b> must be \>= DH1024_KEY_LEN. + */ +int +crypto_dh_get_public(crypto_dh_t *dh, char *pubkey, size_t pubkey_len) +{ + int bytes; + tor_assert(dh); + + const BIGNUM *dh_pub; + +#ifdef OPENSSL_1_1_API + const BIGNUM *dh_priv; + DH_get0_key(dh->dh, &dh_pub, &dh_priv); +#else + dh_pub = dh->dh->pub_key; +#endif /* defined(OPENSSL_1_1_API) */ + + if (!dh_pub) { + if (crypto_dh_generate_public(dh)<0) + return -1; + else { +#ifdef OPENSSL_1_1_API + DH_get0_key(dh->dh, &dh_pub, &dh_priv); +#else + dh_pub = dh->dh->pub_key; +#endif + } + } + + tor_assert(dh_pub); + bytes = BN_num_bytes(dh_pub); + tor_assert(bytes >= 0); + if (pubkey_len < (size_t)bytes) { + log_warn(LD_CRYPTO, + "Weird! pubkey_len (%d) was smaller than DH1024_KEY_LEN (%d)", + (int) pubkey_len, bytes); + return -1; + } + + memset(pubkey, 0, pubkey_len); + BN_bn2bin(dh_pub, (unsigned char*)(pubkey+(pubkey_len-bytes))); + + return 0; +} + +/** Check for bad Diffie-Hellman public keys (g^x). Return 0 if the key is + * okay (in the subgroup [2,p-2]), or -1 if it's bad. + * See http://www.cl.cam.ac.uk/ftp/users/rja14/psandqs.ps.gz for some tips. + */ +static int +tor_check_dh_key(int severity, const BIGNUM *bn) +{ + BIGNUM *x; + char *s; + tor_assert(bn); + x = BN_new(); + tor_assert(x); + if (BUG(!dh_param_p)) + 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."); + goto err; + } + BN_copy(x,dh_param_p); + BN_sub_word(x, 1); + if (BN_cmp(bn,x)>=0) { + log_fn(severity, LD_CRYPTO, "DH key must be at most p-2."); + goto err; + } + BN_clear_free(x); + return 0; + err: + BN_clear_free(x); + s = BN_bn2hex(bn); + log_fn(severity, LD_CRYPTO, "Rejecting insecure DH key [%s]", s); + OPENSSL_free(s); + return -1; +} + +/** Given a DH key exchange object, and our peer's value of g^y (as a + * <b>pubkey_len</b>-byte value in <b>pubkey</b>) generate + * g^xy as a big-endian integer in <b>secret_out</b>. + * Return the number of bytes generated on success, + * or -1 on failure. + * + * This function MUST validate that g^y is actually in the group. + */ +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) +{ + BIGNUM *pubkey_bn = NULL; + size_t secret_len=0; + int result=0; + + tor_assert(dh); + tor_assert(secret_bytes_out/DIGEST_LEN <= 255); + tor_assert(pubkey_len < INT_MAX); + + if (BUG(crypto_dh_get_bytes(dh) > (int)secret_bytes_out)) { + goto error; + } + + if (!(pubkey_bn = BN_bin2bn((const unsigned char*)pubkey, + (int)pubkey_len, NULL))) + goto error; + if (tor_check_dh_key(severity, pubkey_bn)<0) { + /* Check for invalid public keys. */ + log_fn(severity, LD_CRYPTO,"Rejected invalid g^x"); + goto error; + } + result = DH_compute_key(secret_out, pubkey_bn, dh->dh); + if (result < 0) { + log_warn(LD_CRYPTO,"DH_compute_key() failed."); + goto error; + } + secret_len = result; + + goto done; + error: + result = -1; + done: + crypto_openssl_log_errors(LOG_WARN, "completing DH handshake"); + if (pubkey_bn) + BN_clear_free(pubkey_bn); + if (result < 0) + return result; + else + return secret_len; +} + +/** Free a DH key exchange object. + */ +void +crypto_dh_free_(crypto_dh_t *dh) +{ + if (!dh) + return; + tor_assert(dh->dh); + DH_free(dh->dh); + tor_free(dh); +} +#endif + +void +crypto_dh_free_all_openssl(void) +{ + if (dh_param_p) + BN_clear_free(dh_param_p); + if (dh_param_p_tls) + BN_clear_free(dh_param_p_tls); + if (dh_param_g) + BN_clear_free(dh_param_g); + + dh_param_p = dh_param_p_tls = dh_param_g = NULL; +} diff --git a/src/lib/crypt_ops/crypto_digest.c b/src/lib/crypt_ops/crypto_digest.c index ee5c362e38..77cf18dca9 100644 --- a/src/lib/crypt_ops/crypto_digest.c +++ b/src/lib/crypt_ops/crypto_digest.c @@ -12,7 +12,6 @@ #include "lib/container/smartlist.h" #include "lib/crypt_ops/crypto_digest.h" -#include "lib/crypt_ops/crypto_openssl_mgt.h" #include "lib/crypt_ops/crypto_util.h" #include "lib/log/log.h" #include "lib/log/util_bug.h" @@ -24,12 +23,92 @@ #include "lib/arch/bytes.h" +#ifdef ENABLE_NSS +DISABLE_GCC_WARNING(strict-prototypes) +#include <pk11pub.h> +ENABLE_GCC_WARNING(strict-prototypes) +#else + +#include "lib/crypt_ops/crypto_openssl_mgt.h" + DISABLE_GCC_WARNING(redundant-decls) #include <openssl/hmac.h> #include <openssl/sha.h> ENABLE_GCC_WARNING(redundant-decls) +#endif + +#ifdef ENABLE_NSS +/** + * Convert a digest_algorithm_t (used by tor) to a HashType (used by NSS). + * On failure, return SEC_OID_UNKNOWN. */ +static SECOidTag +digest_alg_to_nss_oid(digest_algorithm_t alg) +{ + switch (alg) { + case DIGEST_SHA1: return SEC_OID_SHA1; + case DIGEST_SHA256: return SEC_OID_SHA256; + case DIGEST_SHA512: return SEC_OID_SHA512; + case DIGEST_SHA3_256: /* Fall through */ + case DIGEST_SHA3_512: /* Fall through */ + default: + return SEC_OID_UNKNOWN; + } +} + +/* Helper: get an unkeyed digest via pk11wrap */ +static int +digest_nss_internal(SECOidTag alg, + char *digest, unsigned len_out, + const char *msg, size_t msg_len) +{ + if (alg == SEC_OID_UNKNOWN) + return -1; + tor_assert(msg_len <= UINT_MAX); + + int rv = -1; + SECStatus s; + PK11Context *ctx = PK11_CreateDigestContext(alg); + if (!ctx) + return -1; + + s = PK11_DigestBegin(ctx); + if (s != SECSuccess) + goto done; + + s = PK11_DigestOp(ctx, (const unsigned char *)msg, (unsigned int)msg_len); + if (s != SECSuccess) + goto done; + + unsigned int len = 0; + s = PK11_DigestFinal(ctx, (unsigned char *)digest, &len, len_out); + if (s != SECSuccess) + goto done; + + rv = 0; + done: + PK11_DestroyContext(ctx, PR_TRUE); + return rv; +} + +/** True iff alg is implemented in our crypto library, and we want to use that + * implementation */ +static bool +library_supports_digest(digest_algorithm_t alg) +{ + switch (alg) { + case DIGEST_SHA1: /* Fall through */ + case DIGEST_SHA256: /* Fall through */ + case DIGEST_SHA512: /* Fall through */ + return true; + case DIGEST_SHA3_256: /* Fall through */ + case DIGEST_SHA3_512: /* Fall through */ + default: + return false; + } +} +#endif /* Crypto digest functions */ @@ -37,13 +116,18 @@ ENABLE_GCC_WARNING(redundant-decls) * <b>m</b>. Write the DIGEST_LEN byte result into <b>digest</b>. * Return 0 on success, -1 on failure. */ -int -crypto_digest(char *digest, const char *m, size_t len) +MOCK_IMPL(int, +crypto_digest,(char *digest, const char *m, size_t len)) { tor_assert(m); tor_assert(digest); - if (SHA1((const unsigned char*)m,len,(unsigned char*)digest) == NULL) +#ifdef ENABLE_NSS + return digest_nss_internal(SEC_OID_SHA1, digest, DIGEST_LEN, m, len); +#else + if (SHA1((const unsigned char*)m,len,(unsigned char*)digest) == NULL) { return -1; + } +#endif return 0; } @@ -59,11 +143,16 @@ crypto_digest256(char *digest, const char *m, size_t len, tor_assert(algorithm == DIGEST_SHA256 || algorithm == DIGEST_SHA3_256); int ret = 0; - if (algorithm == DIGEST_SHA256) + if (algorithm == DIGEST_SHA256) { +#ifdef ENABLE_NSS + return digest_nss_internal(SEC_OID_SHA256, digest, DIGEST256_LEN, m, len); +#else ret = (SHA256((const uint8_t*)m,len,(uint8_t*)digest) != NULL); - else +#endif + } else { ret = (sha3_256((uint8_t *)digest, DIGEST256_LEN,(const uint8_t *)m, len) > -1); + } if (!ret) return -1; @@ -82,12 +171,17 @@ crypto_digest512(char *digest, const char *m, size_t len, tor_assert(algorithm == DIGEST_SHA512 || algorithm == DIGEST_SHA3_512); int ret = 0; - if (algorithm == DIGEST_SHA512) + if (algorithm == DIGEST_SHA512) { +#ifdef ENABLE_NSS + return digest_nss_internal(SEC_OID_SHA512, digest, DIGEST512_LEN, m, len); +#else ret = (SHA512((const unsigned char*)m,len,(unsigned char*)digest) != NULL); - else +#endif + } else { ret = (sha3_512((uint8_t*)digest, DIGEST512_LEN, (const uint8_t*)m, len) > -1); + } if (!ret) return -1; @@ -181,9 +275,13 @@ struct crypto_digest_t { * that space for other members might not even be allocated! */ union { +#ifdef ENABLE_NSS + PK11Context *ctx; +#else SHA_CTX sha1; /**< state for SHA1 */ SHA256_CTX sha2; /**< state for SHA256 */ SHA512_CTX sha512; /**< state for SHA512 */ +#endif keccak_state sha3; /**< state for SHA3-[256,512] */ } d; }; @@ -214,12 +312,19 @@ crypto_digest_alloc_bytes(digest_algorithm_t alg) #define END_OF_FIELD(f) (offsetof(crypto_digest_t, f) + \ STRUCT_FIELD_SIZE(crypto_digest_t, f)) switch (alg) { +#ifdef ENABLE_NSS + case DIGEST_SHA1: /* Fall through */ + case DIGEST_SHA256: /* Fall through */ + case DIGEST_SHA512: + return END_OF_FIELD(d.ctx); +#else case DIGEST_SHA1: return END_OF_FIELD(d.sha1); case DIGEST_SHA256: return END_OF_FIELD(d.sha2); case DIGEST_SHA512: return END_OF_FIELD(d.sha512); +#endif case DIGEST_SHA3_256: case DIGEST_SHA3_512: return END_OF_FIELD(d.sha3); @@ -243,6 +348,21 @@ crypto_digest_new_internal(digest_algorithm_t algorithm) switch (algorithm) { +#ifdef ENABLE_NSS + case DIGEST_SHA1: /* fall through */ + case DIGEST_SHA256: /* fall through */ + case DIGEST_SHA512: + r->d.ctx = PK11_CreateDigestContext(digest_alg_to_nss_oid(algorithm)); + if (BUG(!r->d.ctx)) { + tor_free(r); + return NULL; + } + if (BUG(SECSuccess != PK11_DigestBegin(r->d.ctx))) { + crypto_digest_free(r); + return NULL; + } + break; +#else case DIGEST_SHA1: SHA1_Init(&r->d.sha1); break; @@ -252,6 +372,7 @@ crypto_digest_new_internal(digest_algorithm_t algorithm) case DIGEST_SHA512: SHA512_Init(&r->d.sha512); break; +#endif case DIGEST_SHA3_256: keccak_digest_init(&r->d.sha3, 256); break; @@ -302,6 +423,11 @@ crypto_digest_free_(crypto_digest_t *digest) { if (!digest) return; +#ifdef ENABLE_NSS + if (library_supports_digest(digest->algorithm)) { + PK11_DestroyContext(digest->d.ctx, PR_TRUE); + } +#endif size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); memwipe(digest, 0, bytes); tor_free(digest); @@ -324,6 +450,17 @@ crypto_digest_add_bytes(crypto_digest_t *digest, const char *data, * just doing it ourselves. Hashes are fast. */ switch (digest->algorithm) { +#ifdef ENABLE_NSS + case DIGEST_SHA1: /* fall through */ + case DIGEST_SHA256: /* fall through */ + case DIGEST_SHA512: + tor_assert(len <= UINT_MAX); + SECStatus s = PK11_DigestOp(digest->d.ctx, + (const unsigned char *)data, + (unsigned int)len); + tor_assert(s == SECSuccess); + break; +#else case DIGEST_SHA1: SHA1_Update(&digest->d.sha1, (void*)data, len); break; @@ -333,6 +470,7 @@ crypto_digest_add_bytes(crypto_digest_t *digest, const char *data, case DIGEST_SHA512: SHA512_Update(&digest->d.sha512, (void*)data, len); break; +#endif case DIGEST_SHA3_256: /* FALLSTHROUGH */ case DIGEST_SHA3_512: keccak_digest_update(&digest->d.sha3, (const uint8_t *)data, len); @@ -357,7 +495,6 @@ crypto_digest_get_digest(crypto_digest_t *digest, char *out, size_t out_len) { unsigned char r[DIGEST512_LEN]; - crypto_digest_t tmpenv; tor_assert(digest); tor_assert(out); tor_assert(out_len <= crypto_digest_algorithm_get_length(digest->algorithm)); @@ -370,7 +507,26 @@ crypto_digest_get_digest(crypto_digest_t *digest, return; } +#ifdef ENABLE_NSS + /* Copy into a temporary buffer since DigestFinal (alters) the context */ + unsigned char buf[1024]; + unsigned int saved_len = 0; + unsigned rlen; + unsigned char *saved = PK11_SaveContextAlloc(digest->d.ctx, + buf, sizeof(buf), + &saved_len); + tor_assert(saved); + SECStatus s = PK11_DigestFinal(digest->d.ctx, r, &rlen, sizeof(r)); + tor_assert(s == SECSuccess); + tor_assert(rlen >= out_len); + s = PK11_RestoreContext(digest->d.ctx, saved, saved_len); + tor_assert(s == SECSuccess); + if (saved != buf) { + PORT_ZFree(saved, saved_len); + } +#else const size_t alloc_bytes = crypto_digest_alloc_bytes(digest->algorithm); + crypto_digest_t tmpenv; /* memcpy into a temporary ctx, since SHA*_Final clears the context */ memcpy(&tmpenv, digest, alloc_bytes); switch (digest->algorithm) { @@ -393,6 +549,7 @@ crypto_digest_get_digest(crypto_digest_t *digest, break; //LCOV_EXCL_STOP } +#endif memcpy(out, r, out_len); memwipe(r, 0, sizeof(r)); } @@ -408,7 +565,13 @@ crypto_digest_dup(const crypto_digest_t *digest) { tor_assert(digest); const size_t alloc_bytes = crypto_digest_alloc_bytes(digest->algorithm); - return tor_memdup(digest, alloc_bytes); + crypto_digest_t *result = tor_memdup(digest, alloc_bytes); +#ifdef ENABLE_NSS + if (library_supports_digest(digest->algorithm)) { + result->d.ctx = PK11_CloneContext(digest->d.ctx); + } +#endif + return result; } /** Temporarily save the state of <b>digest</b> in <b>checkpoint</b>. @@ -420,6 +583,18 @@ crypto_digest_checkpoint(crypto_digest_checkpoint_t *checkpoint, { const size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); tor_assert(bytes <= sizeof(checkpoint->mem)); +#ifdef ENABLE_NSS + if (library_supports_digest(digest->algorithm)) { + unsigned char *allocated; + allocated = PK11_SaveContextAlloc(digest->d.ctx, + (unsigned char *)checkpoint->mem, + sizeof(checkpoint->mem), + &checkpoint->bytes_used); + /* No allocation is allowed here. */ + tor_assert(allocated == checkpoint->mem); + return; + } +#endif memcpy(checkpoint->mem, digest, bytes); } @@ -431,6 +606,15 @@ crypto_digest_restore(crypto_digest_t *digest, const crypto_digest_checkpoint_t *checkpoint) { const size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); +#ifdef ENABLE_NSS + if (library_supports_digest(digest->algorithm)) { + SECStatus s = PK11_RestoreContext(digest->d.ctx, + (unsigned char *)checkpoint->mem, + checkpoint->bytes_used); + tor_assert(s == SECSuccess); + return; + } +#endif memcpy(digest, checkpoint->mem, bytes); } @@ -446,6 +630,13 @@ crypto_digest_assign(crypto_digest_t *into, tor_assert(from); tor_assert(into->algorithm == from->algorithm); const size_t alloc_bytes = crypto_digest_alloc_bytes(from->algorithm); +#ifdef ENABLE_NSS + if (library_supports_digest(from->algorithm)) { + PK11_DestroyContext(into->d.ctx, PR_TRUE); + into->d.ctx = PK11_CloneContext(from->d.ctx); + return; + } +#endif memcpy(into,from,alloc_bytes); } @@ -496,14 +687,63 @@ crypto_hmac_sha256(char *hmac_out, const char *key, size_t key_len, const char *msg, size_t msg_len) { - unsigned char *rv = NULL; /* If we've got OpenSSL >=0.9.8 we can use its hmac implementation. */ tor_assert(key_len < INT_MAX); tor_assert(msg_len < INT_MAX); tor_assert(hmac_out); +#ifdef ENABLE_NSS + PK11SlotInfo *slot = NULL; + PK11SymKey *symKey = NULL; + PK11Context *hmac = NULL; + + int ok = 0; + SECStatus s; + SECItem keyItem, paramItem; + keyItem.data = (unsigned char *)key; + keyItem.len = (unsigned)key_len; + paramItem.type = siBuffer; + paramItem.data = NULL; + paramItem.len = 0; + + slot = PK11_GetBestSlot(CKM_SHA256_HMAC, NULL); + if (!slot) + goto done; + symKey = PK11_ImportSymKey(slot, CKM_SHA256_HMAC, + PK11_OriginUnwrap, CKA_SIGN, &keyItem, NULL); + if (!symKey) + goto done; + + hmac = PK11_CreateContextBySymKey(CKM_SHA256_HMAC, CKA_SIGN, symKey, + ¶mItem); + if (!hmac) + goto done; + s = PK11_DigestBegin(hmac); + if (s != SECSuccess) + goto done; + s = PK11_DigestOp(hmac, (const unsigned char *)msg, (unsigned int)msg_len); + if (s != SECSuccess) + goto done; + unsigned int len=0; + s = PK11_DigestFinal(hmac, (unsigned char *)hmac_out, &len, DIGEST256_LEN); + if (s != SECSuccess || len != DIGEST256_LEN) + goto done; + ok = 1; + + done: + if (hmac) + PK11_DestroyContext(hmac, PR_TRUE); + if (symKey) + PK11_FreeSymKey(symKey); + if (slot) + PK11_FreeSlot(slot); + + tor_assert(ok); +#else + unsigned char *rv = NULL; rv = HMAC(EVP_sha256(), key, (int)key_len, (unsigned char*)msg, (int)msg_len, (unsigned char*)hmac_out, NULL); tor_assert(rv); +#endif } /** Compute a MAC using SHA3-256 of <b>msg_len</b> bytes in <b>msg</b> using a diff --git a/src/lib/crypt_ops/crypto_digest.h b/src/lib/crypt_ops/crypto_digest.h index 9facf3b981..204f1aaff3 100644 --- a/src/lib/crypt_ops/crypto_digest.h +++ b/src/lib/crypt_ops/crypto_digest.h @@ -16,6 +16,7 @@ #include "lib/cc/torint.h" #include "lib/defs/digest_sizes.h" #include "lib/malloc/malloc.h" +#include "lib/testsupport/testsupport.h" /** Length of a sha1 message digest when encoded in base32 with trailing = * signs removed. */ @@ -51,6 +52,9 @@ typedef enum { /** Structure used to temporarily save the a digest object. Only implemented * for SHA1 digest for now. */ typedef struct crypto_digest_checkpoint_t { +#ifdef ENABLE_NSS + unsigned int bytes_used; +#endif uint8_t mem[DIGEST_CHECKPOINT_BYTES]; } crypto_digest_checkpoint_t; @@ -72,7 +76,7 @@ typedef struct crypto_xof_t crypto_xof_t; struct smartlist_t; /* SHA-1 and other digests */ -int crypto_digest(char *digest, const char *m, size_t len); +MOCK_DECL(int, crypto_digest,(char *digest, const char *m, size_t len)); int crypto_digest256(char *digest, const char *m, size_t len, digest_algorithm_t algorithm); int crypto_digest512(char *digest, const char *m, size_t len, diff --git a/src/lib/crypt_ops/crypto_ed25519.c b/src/lib/crypt_ops/crypto_ed25519.c index 9d2c9e9fab..11c1f56aef 100644 --- a/src/lib/crypt_ops/crypto_ed25519.c +++ b/src/lib/crypt_ops/crypto_ed25519.c @@ -37,6 +37,7 @@ #include "ed25519/donna/ed25519_donna_tor.h" #include <string.h> +#include <errno.h> static void pick_ed25519_impl(void); diff --git a/src/lib/crypt_ops/crypto_format.c b/src/lib/crypt_ops/crypto_format.c index 50916a8d68..09ec753a00 100644 --- a/src/lib/crypt_ops/crypto_format.c +++ b/src/lib/crypt_ops/crypto_format.c @@ -29,6 +29,7 @@ #include "lib/fs/files.h" #include <string.h> +#include <errno.h> /** Write the <b>datalen</b> bytes from <b>data</b> to the file named * <b>fname</b> in the tagged-data format. This format contains a diff --git a/src/lib/crypt_ops/crypto_hkdf.c b/src/lib/crypt_ops/crypto_hkdf.c index 1873632a9d..a63d9131d9 100644 --- a/src/lib/crypt_ops/crypto_hkdf.c +++ b/src/lib/crypt_ops/crypto_hkdf.c @@ -17,12 +17,14 @@ #include "lib/intmath/cmp.h" #include "lib/log/util_bug.h" +#ifdef ENABLE_OPENSSL #include <openssl/opensslv.h> #if defined(HAVE_ERR_LOAD_KDF_STRINGS) #include <openssl/kdf.h> #define HAVE_OPENSSL_HKDF 1 #endif +#endif #include <string.h> diff --git a/src/lib/crypt_ops/crypto_init.c b/src/lib/crypt_ops/crypto_init.c new file mode 100644 index 0000000000..c731662d49 --- /dev/null +++ b/src/lib/crypt_ops/crypto_init.c @@ -0,0 +1,193 @@ +/* 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_init.c + * + * \brief Initialize and shut down Tor's crypto library and subsystem. + **/ + +#include "orconfig.h" + +#include "lib/crypt_ops/crypto_init.h" + +#include "lib/crypt_ops/crypto_curve25519.h" +#include "lib/crypt_ops/crypto_dh.h" +#include "lib/crypt_ops/crypto_ed25519.h" +#include "lib/crypt_ops/crypto_openssl_mgt.h" +#include "lib/crypt_ops/crypto_nss_mgt.h" +#include "lib/crypt_ops/crypto_rand.h" + +#include "siphash.h" + +/** Boolean: has our crypto library been initialized? (early phase) */ +static int crypto_early_initialized_ = 0; + +/** Boolean: has our crypto library been initialized? (late phase) */ +static int crypto_global_initialized_ = 0; + +static int have_seeded_siphash = 0; + +/** Set up the siphash key if we haven't already done so. */ +int +crypto_init_siphash_key(void) +{ + struct sipkey key; + if (have_seeded_siphash) + return 0; + + crypto_rand((char*) &key, sizeof(key)); + siphash_set_global_key(&key); + have_seeded_siphash = 1; + return 0; +} + +/** Initialize the crypto library. Return 0 on success, -1 on failure. + */ +int +crypto_early_init(void) +{ + if (!crypto_early_initialized_) { + + crypto_early_initialized_ = 1; + +#ifdef ENABLE_OPENSSL + crypto_openssl_early_init(); +#endif +#ifdef ENABLE_NSS + crypto_nss_early_init(0); +#endif + + if (crypto_seed_rng() < 0) + return -1; + if (crypto_init_siphash_key() < 0) + return -1; + + curve25519_init(); + ed25519_init(); + } + return 0; +} + +/** Initialize the crypto library. Return 0 on success, -1 on failure. + */ +int +crypto_global_init(int useAccel, const char *accelName, const char *accelDir) +{ + if (!crypto_global_initialized_) { + if (crypto_early_init() < 0) + return -1; + + crypto_global_initialized_ = 1; + + crypto_dh_init(); + +#ifdef ENABLE_OPENSSL + if (crypto_openssl_late_init(useAccel, accelName, accelDir) < 0) + return -1; +#else + (void)useAccel; + (void)accelName; + (void)accelDir; +#endif +#ifdef ENABLE_NSS + if (crypto_nss_late_init() < 0) + return -1; +#endif + } + return 0; +} + +/** Free crypto resources held by this thread. */ +void +crypto_thread_cleanup(void) +{ +#ifdef ENABLE_OPENSSL + crypto_openssl_thread_cleanup(); +#endif +} + +/** + * Uninitialize the crypto library. Return 0 on success. Does not detect + * failure. + */ +int +crypto_global_cleanup(void) +{ + crypto_dh_free_all(); + +#ifdef ENABLE_OPENSSL + crypto_openssl_global_cleanup(); +#endif +#ifdef ENABLE_NSS + crypto_nss_global_cleanup(); +#endif + + crypto_early_initialized_ = 0; + crypto_global_initialized_ = 0; + have_seeded_siphash = 0; + siphash_unset_global_key(); + + return 0; +} + +/** Run operations that the crypto library requires to be happy again + * after forking. */ +void +crypto_prefork(void) +{ +#ifdef ENABLE_NSS + crypto_nss_prefork(); +#endif +} + +/** Run operations that the crypto library requires to be happy again + * after forking. */ +void +crypto_postfork(void) +{ +#ifdef ENABLE_NSS + crypto_nss_postfork(); +#endif +} + +/** Return the name of the crypto library we're using. */ +const char * +crypto_get_library_name(void) +{ +#ifdef ENABLE_OPENSSL + return "OpenSSL"; +#endif +#ifdef ENABLE_NSS + return "NSS"; +#endif +} + +/** Return the version of the crypto library we are using, as given in the + * library. */ +const char * +crypto_get_library_version_string(void) +{ +#ifdef ENABLE_OPENSSL + return crypto_openssl_get_version_str(); +#endif +#ifdef ENABLE_NSS + return crypto_nss_get_version_str(); +#endif +} + +/** Return the version of the crypto library we're using, as given in the + * headers. */ +const char * +crypto_get_header_version_string(void) +{ +#ifdef ENABLE_OPENSSL + return crypto_openssl_get_header_version_str(); +#endif +#ifdef ENABLE_NSS + return crypto_nss_get_header_version_str(); +#endif +} diff --git a/src/lib/crypt_ops/crypto_init.h b/src/lib/crypt_ops/crypto_init.h new file mode 100644 index 0000000000..5b6d65d48c --- /dev/null +++ b/src/lib/crypt_ops/crypto_init.h @@ -0,0 +1,34 @@ +/* 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_init.h + * + * \brief Headers for crypto_init.c + **/ + +#ifndef TOR_CRYPTO_INIT_H +#define TOR_CRYPTO_INIT_H + +#include "orconfig.h" +#include "lib/cc/compat_compiler.h" + +int crypto_init_siphash_key(void); +int crypto_early_init(void) ATTR_WUR; +int crypto_global_init(int hardwareAccel, + const char *accelName, + const char *accelPath) ATTR_WUR; + +void crypto_thread_cleanup(void); +int crypto_global_cleanup(void); +void crypto_prefork(void); +void crypto_postfork(void); + +const char *crypto_get_library_name(void); +const char *crypto_get_library_version_string(void); +const char *crypto_get_header_version_string(void); + +#endif /* !defined(TOR_CRYPTO_H) */ diff --git a/src/lib/crypt_ops/crypto_nss_mgt.c b/src/lib/crypt_ops/crypto_nss_mgt.c new file mode 100644 index 0000000000..a1da74aff5 --- /dev/null +++ b/src/lib/crypt_ops/crypto_nss_mgt.c @@ -0,0 +1,132 @@ +/* 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_nss_mgt.c + * + * \brief Manage the NSS library (if used) + **/ + +#include "lib/crypt_ops/crypto_nss_mgt.h" + +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/string/printf.h" + +DISABLE_GCC_WARNING(strict-prototypes) +#include <nss.h> +#include <pk11func.h> +#include <ssl.h> + +#include <prerror.h> +#include <prtypes.h> +#include <prinit.h> +ENABLE_GCC_WARNING(strict-prototypes) + +const char * +crypto_nss_get_version_str(void) +{ + return NSS_GetVersion(); +} +const char * +crypto_nss_get_header_version_str(void) +{ + return NSS_VERSION; +} + +/** A password function that always returns NULL. */ +static char * +nss_password_func_always_fail(PK11SlotInfo *slot, + PRBool retry, + void *arg) +{ + (void) slot; + (void) retry; + (void) arg; + return NULL; +} + +void +crypto_nss_early_init(int nss_only) +{ + if (! nss_only) { + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); + PK11_SetPasswordFunc(nss_password_func_always_fail); + } + + /* Eventually we should use NSS_Init() instead -- but that wants a + directory. The documentation says that we can't use this if we want + to use OpenSSL. */ + if (NSS_NoDB_Init(NULL) == SECFailure) { + log_err(LD_CRYPTO, "Unable to initialize NSS."); + crypto_nss_log_errors(LOG_ERR, "initializing NSS"); + tor_assert_unreached(); + } + + if (NSS_SetDomesticPolicy() == SECFailure) { + log_err(LD_CRYPTO, "Unable to set NSS cipher policy."); + crypto_nss_log_errors(LOG_ERR, "setting cipher policy"); + tor_assert_unreached(); + } + + /* We need to override the default here, or NSS will reject all the + * legacy Tor certificates. */ + SECStatus rv = NSS_OptionSet(NSS_RSA_MIN_KEY_SIZE, 1024); + if (rv != SECSuccess) { + log_err(LD_CRYPTO, "Unable to set NSS min RSA key size"); + crypto_nss_log_errors(LOG_ERR, "setting cipher option."); + tor_assert_unreached(); + } +} + +void +crypto_nss_log_errors(int severity, const char *doing) +{ + PRErrorCode code = PR_GetError(); + const char *string = PORT_ErrorToString(code); + const char *name = PORT_ErrorToName(code); + char buf[16]; + if (!string) + string = "<unrecognized>"; + if (!name) { + tor_snprintf(buf, sizeof(buf), "%d", code); + name = buf; + } + if (doing) { + tor_log(severity, LD_CRYPTO, "NSS error %s while %s: %s", + name, doing, string); + } else { + tor_log(severity, LD_CRYPTO, "NSS error %s: %s", name, string); + } +} + +int +crypto_nss_late_init(void) +{ + /* Possibly, SSL_OptionSetDefault? */ + + return 0; +} + +void +crypto_nss_global_cleanup(void) +{ + NSS_Shutdown(); + PL_ArenaFinish(); + PR_Cleanup(); +} + +void +crypto_nss_prefork(void) +{ + NSS_Shutdown(); +} + +void +crypto_nss_postfork(void) +{ + crypto_nss_early_init(1); +} diff --git a/src/lib/crypt_ops/crypto_nss_mgt.h b/src/lib/crypt_ops/crypto_nss_mgt.h new file mode 100644 index 0000000000..27793dcc45 --- /dev/null +++ b/src/lib/crypt_ops/crypto_nss_mgt.h @@ -0,0 +1,34 @@ +/* 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_nss_mgt.h + * + * \brief Headers for crypto_nss_mgt.c + **/ + +#ifndef TOR_CRYPTO_NSS_MGT_H +#define TOR_CRYPTO_NSS_MGT_H + +#include "orconfig.h" + +#ifdef ENABLE_NSS +/* global nss state */ +const char *crypto_nss_get_version_str(void); +const char *crypto_nss_get_header_version_str(void); + +void crypto_nss_log_errors(int severity, const char *doing); + +void crypto_nss_early_init(int nss_only); +int crypto_nss_late_init(void); + +void crypto_nss_global_cleanup(void); + +void crypto_nss_prefork(void); +void crypto_nss_postfork(void); +#endif + +#endif /* !defined(TOR_CRYPTO_NSS_H) */ diff --git a/src/lib/crypt_ops/crypto_ope.c b/src/lib/crypt_ops/crypto_ope.c index ca42ae4341..fd5d5f3770 100644 --- a/src/lib/crypt_ops/crypto_ope.c +++ b/src/lib/crypt_ops/crypto_ope.c @@ -18,8 +18,8 @@ #define CRYPTO_OPE_PRIVATE #include "lib/crypt_ops/crypto_ope.h" -#include "lib/crypt_ops/crypto.h" #include "lib/crypt_ops/crypto_util.h" +#include "lib/crypt_ops/crypto_cipher.h" #include "lib/log/util_bug.h" #include "lib/malloc/malloc.h" #include "lib/arch/bytes.h" diff --git a/src/lib/crypt_ops/crypto_openssl_mgt.c b/src/lib/crypt_ops/crypto_openssl_mgt.c index 01de6a9d9e..125da0786b 100644 --- a/src/lib/crypt_ops/crypto_openssl_mgt.c +++ b/src/lib/crypt_ops/crypto_openssl_mgt.c @@ -12,8 +12,12 @@ #include "lib/crypt_ops/compat_openssl.h" #include "lib/crypt_ops/crypto_openssl_mgt.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/aes.h" #include "lib/string/util_string.h" #include "lib/lock/compat_mutex.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" #include "lib/testsupport/testsupport.h" #include "lib/thread/threads.h" @@ -30,6 +34,7 @@ DISABLE_GCC_WARNING(redundant-decls) #include <openssl/conf.h> #include <openssl/hmac.h> #include <openssl/crypto.h> +#include <openssl/ssl.h> ENABLE_GCC_WARNING(redundant-decls) @@ -49,6 +54,27 @@ STATIC void openssl_locking_cb_(int mode, int n, const char *file, int line); STATIC void tor_set_openssl_thread_id(CRYPTO_THREADID *threadid); #endif +/** Log all pending crypto errors at level <b>severity</b>. Use + * <b>doing</b> to describe our current activities. + */ +void +crypto_openssl_log_errors(int severity, const char *doing) +{ + unsigned long err; + const char *msg, *lib, *func; + while ((err = ERR_get_error()) != 0) { + msg = (const char*)ERR_reason_error_string(err); + lib = (const char*)ERR_lib_error_string(err); + func = (const char*)ERR_func_error_string(err); + if (!msg) msg = "(null)"; + if (!lib) lib = "(null)"; + if (!func) func = "(null)"; + if (BUG(!doing)) doing = "(null)"; + tor_log(severity, LD_CRYPTO, "crypto error while %s: %s (in %s:%s)", + doing, msg, lib, func); + } +} + /* Returns a trimmed and human-readable version of an openssl version string * <b>raw_version</b>. They are usually in the form of 'OpenSSL 1.0.0b 10 * May 2012' and this will parse them into a form similar to '1.0.0b' */ @@ -127,7 +153,7 @@ tor_set_openssl_thread_id(CRYPTO_THREADID *threadid) /** Helper: Construct mutexes, and set callbacks to help OpenSSL handle being * multithreaded. Returns 0. */ -int +static int setup_openssl_threading(void) { #ifndef NEW_THREAD_API @@ -144,7 +170,7 @@ setup_openssl_threading(void) } /** free OpenSSL variables */ -void +static void crypto_openssl_free_all(void) { tor_free(crypto_openssl_version_str); @@ -164,3 +190,201 @@ crypto_openssl_free_all(void) } #endif /* !defined(NEW_THREAD_API) */ } + +/** Perform early (pre-configuration) initialization tasks for OpenSSL. */ +void +crypto_openssl_early_init(void) +{ +#ifdef OPENSSL_1_1_API + OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | + OPENSSL_INIT_LOAD_CRYPTO_STRINGS | + OPENSSL_INIT_ADD_ALL_CIPHERS | + OPENSSL_INIT_ADD_ALL_DIGESTS, NULL); +#else + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); +#endif + + setup_openssl_threading(); + + unsigned long version_num = OpenSSL_version_num(); + const char *version_str = OpenSSL_version(OPENSSL_VERSION); + if (version_num == OPENSSL_VERSION_NUMBER && + !strcmp(version_str, OPENSSL_VERSION_TEXT)) { + log_info(LD_CRYPTO, "OpenSSL version matches version from headers " + "(%lx: %s).", version_num, version_str); + } else { + log_warn(LD_CRYPTO, "OpenSSL version from headers does not match the " + "version we're running with. If you get weird crashes, that " + "might be why. (Compiled with %lx: %s; running with %lx: %s).", + (unsigned long)OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_TEXT, + version_num, version_str); + } + + crypto_force_rand_ssleay(); +} + +#ifndef DISABLE_ENGINES +/** Try to load an engine in a shared library via fully qualified path. + */ +static ENGINE * +try_load_engine(const char *path, const char *engine) +{ + ENGINE *e = ENGINE_by_id("dynamic"); + if (e) { + if (!ENGINE_ctrl_cmd_string(e, "ID", engine, 0) || + !ENGINE_ctrl_cmd_string(e, "DIR_LOAD", "2", 0) || + !ENGINE_ctrl_cmd_string(e, "DIR_ADD", path, 0) || + !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0)) { + ENGINE_free(e); + e = NULL; + } + } + return e; +} +#endif /* !defined(DISABLE_ENGINES) */ + +#ifndef DISABLE_ENGINES +/** Log any OpenSSL engines we're using at NOTICE. */ +static void +log_engine(const char *fn, ENGINE *e) +{ + if (e) { + const char *name, *id; + name = ENGINE_get_name(e); + id = ENGINE_get_id(e); + log_notice(LD_CRYPTO, "Default OpenSSL engine for %s is %s [%s]", + fn, name?name:"?", id?id:"?"); + } else { + log_info(LD_CRYPTO, "Using default implementation for %s", fn); + } +} +#endif /* !defined(DISABLE_ENGINES) */ + +/** Initialize engines for openssl (if enabled). */ +static void +crypto_openssl_init_engines(const char *accelName, + const char *accelDir) +{ +#ifdef DISABLE_ENGINES + (void)accelName; + (void)accelDir; + log_warn(LD_CRYPTO, "No OpenSSL hardware acceleration support enabled."); +#else + ENGINE *e = NULL; + + log_info(LD_CRYPTO, "Initializing OpenSSL engine support."); + ENGINE_load_builtin_engines(); + ENGINE_register_all_complete(); + + if (accelName) { + if (accelDir) { + log_info(LD_CRYPTO, "Trying to load dynamic OpenSSL engine \"%s\"" + " via path \"%s\".", accelName, accelDir); + e = try_load_engine(accelName, accelDir); + } else { + log_info(LD_CRYPTO, "Initializing dynamic OpenSSL engine \"%s\"" + " acceleration support.", accelName); + e = ENGINE_by_id(accelName); + } + if (!e) { + log_warn(LD_CRYPTO, "Unable to load dynamic OpenSSL engine \"%s\".", + accelName); + } else { + log_info(LD_CRYPTO, "Loaded dynamic OpenSSL engine \"%s\".", + accelName); + } + } + if (e) { + log_info(LD_CRYPTO, "Loaded OpenSSL hardware acceleration engine," + " setting default ciphers."); + ENGINE_set_default(e, ENGINE_METHOD_ALL); + } + /* Log, if available, the intersection of the set of algorithms + used by Tor and the set of algorithms available in the engine */ + log_engine("RSA", ENGINE_get_default_RSA()); + log_engine("DH", ENGINE_get_default_DH()); +#ifdef OPENSSL_1_1_API + log_engine("EC", ENGINE_get_default_EC()); +#else + log_engine("ECDH", ENGINE_get_default_ECDH()); + log_engine("ECDSA", ENGINE_get_default_ECDSA()); +#endif /* defined(OPENSSL_1_1_API) */ + log_engine("RAND", ENGINE_get_default_RAND()); + log_engine("RAND (which we will not use)", ENGINE_get_default_RAND()); + log_engine("SHA1", ENGINE_get_digest_engine(NID_sha1)); + log_engine("3DES-CBC", ENGINE_get_cipher_engine(NID_des_ede3_cbc)); + log_engine("AES-128-ECB", ENGINE_get_cipher_engine(NID_aes_128_ecb)); + log_engine("AES-128-CBC", ENGINE_get_cipher_engine(NID_aes_128_cbc)); +#ifdef NID_aes_128_ctr + log_engine("AES-128-CTR", ENGINE_get_cipher_engine(NID_aes_128_ctr)); +#endif +#ifdef NID_aes_128_gcm + log_engine("AES-128-GCM", ENGINE_get_cipher_engine(NID_aes_128_gcm)); +#endif + log_engine("AES-256-CBC", ENGINE_get_cipher_engine(NID_aes_256_cbc)); +#ifdef NID_aes_256_gcm + log_engine("AES-256-GCM", ENGINE_get_cipher_engine(NID_aes_256_gcm)); +#endif + +#endif /* defined(DISABLE_ENGINES) */ +} + +/** Perform late (post-init) initialization tasks for OpenSSL */ +int +crypto_openssl_late_init(int useAccel, const char *accelName, + const char *accelDir) +{ + if (useAccel > 0) { + crypto_openssl_init_engines(accelName, accelDir); + } else { + log_info(LD_CRYPTO, "NOT using OpenSSL engine support."); + } + + if (crypto_force_rand_ssleay()) { + if (crypto_seed_rng() < 0) + return -1; + } + + evaluate_evp_for_aes(-1); + evaluate_ctr_for_aes(); + + return 0; +} + +/** Free crypto resources held by this thread. */ +void +crypto_openssl_thread_cleanup(void) +{ +#ifndef NEW_THREAD_API + ERR_remove_thread_state(NULL); +#endif +} + +/** Clean up global resources held by openssl. */ +void +crypto_openssl_global_cleanup(void) +{ + #ifndef OPENSSL_1_1_API + EVP_cleanup(); +#endif +#ifndef NEW_THREAD_API + ERR_remove_thread_state(NULL); +#endif +#ifndef OPENSSL_1_1_API + ERR_free_strings(); +#endif + +#ifndef DISABLE_ENGINES +#ifndef OPENSSL_1_1_API + ENGINE_cleanup(); +#endif +#endif + + CONF_modules_unload(1); +#ifndef OPENSSL_1_1_API + CRYPTO_cleanup_all_ex_data(); +#endif + + crypto_openssl_free_all(); +} diff --git a/src/lib/crypt_ops/crypto_openssl_mgt.h b/src/lib/crypt_ops/crypto_openssl_mgt.h index a2c53302e1..3b288fb9d8 100644 --- a/src/lib/crypt_ops/crypto_openssl_mgt.h +++ b/src/lib/crypt_ops/crypto_openssl_mgt.h @@ -14,6 +14,8 @@ #define TOR_CRYPTO_OPENSSL_H #include "orconfig.h" + +#ifdef ENABLE_OPENSSL #include <openssl/engine.h> /* @@ -69,14 +71,19 @@ #define NEW_THREAD_API #endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_VER(1,1,0,0,5) && ... */ +void crypto_openssl_log_errors(int severity, const char *doing); + /* global openssl state */ const char * crypto_openssl_get_version_str(void); const char * crypto_openssl_get_header_version_str(void); -/* OpenSSL threading setup function */ -int setup_openssl_threading(void); +void crypto_openssl_early_init(void); +int crypto_openssl_late_init(int useAccel, const char *accelName, + const char *accelDir); + +void crypto_openssl_thread_cleanup(void); +void crypto_openssl_global_cleanup(void); -/* Tor OpenSSL utility functions */ -void crypto_openssl_free_all(void); +#endif /* ENABLE_OPENSSL */ #endif /* !defined(TOR_CRYPTO_OPENSSL_H) */ diff --git a/src/lib/crypt_ops/crypto_pwbox.c b/src/lib/crypt_ops/crypto_pwbox.c index c001e295da..2377f216a0 100644 --- a/src/lib/crypt_ops/crypto_pwbox.c +++ b/src/lib/crypt_ops/crypto_pwbox.c @@ -11,7 +11,7 @@ #include <string.h> #include "lib/arch/bytes.h" -#include "lib/crypt_ops/crypto.h" +#include "lib/crypt_ops/crypto_cipher.h" #include "lib/crypt_ops/crypto_digest.h" #include "lib/crypt_ops/crypto_pwbox.h" #include "lib/crypt_ops/crypto_rand.h" diff --git a/src/lib/crypt_ops/crypto_rand.c b/src/lib/crypt_ops/crypto_rand.c index fb9d0c2c6c..78471bf398 100644 --- a/src/lib/crypt_ops/crypto_rand.c +++ b/src/lib/crypt_ops/crypto_rand.c @@ -35,9 +35,24 @@ #include "lib/testsupport/testsupport.h" #include "lib/fs/files.h" +#include "lib/defs/digest_sizes.h" +#include "lib/crypt_ops/crypto_digest.h" + +#ifdef ENABLE_NSS +#include "lib/crypt_ops/crypto_nss_mgt.h" +#endif + +#ifdef ENABLE_OPENSSL DISABLE_GCC_WARNING(redundant-decls) #include <openssl/rand.h> ENABLE_GCC_WARNING(redundant-decls) +#endif + +#ifdef ENABLE_NSS +#include <pk11pub.h> +#include <secerr.h> +#include <prerror.h> +#endif #if __GNUC__ && GCC_VERSION >= 402 #if GCC_VERSION >= 406 @@ -67,6 +82,7 @@ ENABLE_GCC_WARNING(redundant-decls) #endif #include <string.h> +#include <errno.h> /** * How many bytes of entropy we add at once. @@ -322,16 +338,24 @@ crypto_strongest_rand_raw(uint8_t *out, size_t out_len) void crypto_strongest_rand(uint8_t *out, size_t out_len) { -#define DLEN SHA512_DIGEST_LENGTH +#define DLEN DIGEST512_LEN + /* We're going to hash DLEN bytes from the system RNG together with some - * bytes from the openssl PRNG, in order to yield DLEN bytes. + * bytes from the PRNGs from our crypto librar(y/ies), in order to yield + * DLEN bytes. */ - uint8_t inp[DLEN*2]; + uint8_t inp[DLEN*3]; uint8_t tmp[DLEN]; tor_assert(out); while (out_len) { - crypto_rand((char*) inp, DLEN); - if (crypto_strongest_rand_raw(inp+DLEN, DLEN) < 0) { + memset(inp, 0, sizeof(inp)); +#ifdef ENABLE_OPENSSL + RAND_bytes(inp, DLEN); +#endif +#ifdef ENABLE_NSS + PK11_GenerateRandom(inp+DLEN, DLEN); +#endif + if (crypto_strongest_rand_raw(inp+DLEN*2, DLEN) < 0) { // LCOV_EXCL_START log_err(LD_CRYPTO, "Failed to load strong entropy when generating an " "important key. Exiting."); @@ -340,11 +364,11 @@ crypto_strongest_rand(uint8_t *out, size_t out_len) // LCOV_EXCL_STOP } if (out_len >= DLEN) { - SHA512(inp, sizeof(inp), out); + crypto_digest512((char*)out, (char*)inp, sizeof(inp), DIGEST_SHA512); out += DLEN; out_len -= DLEN; } else { - SHA512(inp, sizeof(inp), tmp); + crypto_digest512((char*)tmp, (char*)inp, sizeof(inp), DIGEST_SHA512); memcpy(out, tmp, out_len); break; } @@ -354,12 +378,13 @@ crypto_strongest_rand(uint8_t *out, size_t out_len) #undef DLEN } +#ifdef ENABLE_OPENSSL /** * Seed OpenSSL's random number generator with bytes from the operating * system. Return 0 on success, -1 on failure. **/ -int -crypto_seed_rng(void) +static int +crypto_seed_openssl_rng(void) { int rand_poll_ok = 0, load_entropy_ok = 0; uint8_t buf[ADD_ENTROPY]; @@ -383,6 +408,52 @@ crypto_seed_rng(void) else return -1; } +#endif + +#ifdef ENABLE_NSS +/** + * Seed OpenSSL's random number generator with bytes from the operating + * system. Return 0 on success, -1 on failure. + **/ +static int +crypto_seed_nss_rng(void) +{ + uint8_t buf[ADD_ENTROPY]; + + int load_entropy_ok = !crypto_strongest_rand_raw(buf, sizeof(buf)); + if (load_entropy_ok) { + if (PK11_RandomUpdate(buf, sizeof(buf)) != SECSuccess) { + load_entropy_ok = 0; + } + } + + memwipe(buf, 0, sizeof(buf)); + + return load_entropy_ok ? 0 : -1; +} +#endif + +/** + * Seed the RNG for any and all crypto libraries that we're using with bytes + * from the operating system. Return 0 on success, -1 on failure. + */ +int +crypto_seed_rng(void) +{ + int seeded = 0; +#ifdef ENABLE_NSS + if (crypto_seed_nss_rng() < 0) + return -1; + ++seeded; +#endif +#ifdef ENABLE_OPENSSL + if (crypto_seed_openssl_rng() < 0) + return -1; + ++seeded; +#endif + tor_assert(seeded); + return 0; +} /** * Write <b>n</b> bytes of strong random data to <b>to</b>. Supports mocking @@ -407,17 +478,44 @@ crypto_rand, (char *to, size_t n)) void crypto_rand_unmocked(char *to, size_t n) { - int r; if (n == 0) return; tor_assert(n < INT_MAX); tor_assert(to); - r = RAND_bytes((unsigned char*)to, (int)n); + +#ifdef ENABLE_NSS + SECStatus s = PK11_GenerateRandom((unsigned char*)to, (int)n); + if (s != SECSuccess) { + /* NSS rather sensibly might refuse to generate huge amounts of random + * data at once. Unfortunately, our unit test do this in a couple of + * places. To solve this issue, we use our XOF to stretch a shorter + * output when a longer one is needed. + * + * Yes, this is secure. */ + + /* This is longer than it needs to be; 1600 bits == 200 bytes is the + * state-size of SHA3. */ +#define BUFLEN 512 + tor_assert(PR_GetError() == SEC_ERROR_INVALID_ARGS && n > BUFLEN); + unsigned char buf[BUFLEN]; + s = PK11_GenerateRandom(buf, BUFLEN); + tor_assert(s == SECSuccess); + crypto_xof_t *xof = crypto_xof_new(); + crypto_xof_add_bytes(xof, buf, BUFLEN); + crypto_xof_squeeze_bytes(xof, (unsigned char *)to, n); + crypto_xof_free(xof); + memwipe(buf, 0, BUFLEN); + +#undef BUFLEN + } +#else + int r = RAND_bytes((unsigned char*)to, (int)n); /* We consider a PRNG failure non-survivable. Let's assert so that we get a * stack trace about where it happened. */ tor_assert(r >= 0); +#endif } /** @@ -605,6 +703,7 @@ smartlist_shuffle(smartlist_t *sl) int crypto_force_rand_ssleay(void) { +#ifdef ENABLE_OPENSSL RAND_METHOD *default_method; default_method = RAND_OpenSSL(); if (RAND_get_rand_method() != default_method) { @@ -614,6 +713,7 @@ crypto_force_rand_ssleay(void) RAND_set_rand_method(default_method); return 1; } +#endif return 0; } diff --git a/src/lib/crypt_ops/crypto_rsa.c b/src/lib/crypt_ops/crypto_rsa.c index 5ec69d7319..6a9e2948f1 100644 --- a/src/lib/crypt_ops/crypto_rsa.c +++ b/src/lib/crypt_ops/crypto_rsa.c @@ -9,7 +9,7 @@ * \brief Block of functions related with RSA utilities and operations. **/ -#include "lib/crypt_ops/crypto.h" +#include "lib/crypt_ops/crypto_cipher.h" #include "lib/crypt_ops/crypto_curve25519.h" #include "lib/crypt_ops/crypto_digest.h" #include "lib/crypt_ops/crypto_format.h" @@ -21,32 +21,14 @@ #include "lib/log/util_bug.h" #include "lib/fs/files.h" -DISABLE_GCC_WARNING(redundant-decls) - -#include <openssl/err.h> -#include <openssl/rsa.h> -#include <openssl/pem.h> -#include <openssl/evp.h> -#include <openssl/engine.h> -#include <openssl/rand.h> -#include <openssl/bn.h> -#include <openssl/dh.h> -#include <openssl/conf.h> -#include <openssl/hmac.h> - -ENABLE_GCC_WARNING(redundant-decls) - #include "lib/log/log.h" #include "lib/encoding/binascii.h" +#include "lib/encoding/pem.h" #include <string.h> - -/** Declaration for crypto_pk_t structure. */ -struct crypto_pk_t -{ - int refs; /**< reference count, so we don't have to copy keys */ - RSA *key; /**< The key itself */ -}; +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif /** Return the number of bytes added by padding method <b>padding</b>. */ @@ -55,11 +37,12 @@ crypto_get_rsa_padding_overhead(int padding) { switch (padding) { - case RSA_PKCS1_OAEP_PADDING: return PKCS1_OAEP_PADDING_OVERHEAD; + case PK_PKCS1_OAEP_PADDING: return PKCS1_OAEP_PADDING_OVERHEAD; default: tor_assert(0); return -1; // LCOV_EXCL_LINE } } +#ifdef ENABLE_OPENSSL /** Given a padding method <b>padding</b>, return the correct OpenSSL constant. */ int @@ -71,442 +54,7 @@ crypto_get_rsa_padding(int padding) default: tor_assert(0); return -1; // LCOV_EXCL_LINE } } - -/** used internally: quicly validate a crypto_pk_t object as a private key. - * Return 1 iff the public key is valid, 0 if obviously invalid. - */ -static int -crypto_pk_private_ok(const crypto_pk_t *k) -{ -#ifdef OPENSSL_1_1_API - if (!k || !k->key) - return 0; - - const BIGNUM *p, *q; - RSA_get0_factors(k->key, &p, &q); - return p != NULL; /* XXX/yawning: Should we check q? */ -#else /* !(defined(OPENSSL_1_1_API)) */ - return k && k->key && k->key->p; -#endif /* defined(OPENSSL_1_1_API) */ -} - -/** used by tortls.c: wrap an RSA* in a crypto_pk_t. */ -crypto_pk_t * -crypto_new_pk_from_rsa_(RSA *rsa) -{ - crypto_pk_t *env; - tor_assert(rsa); - env = tor_malloc(sizeof(crypto_pk_t)); - env->refs = 1; - env->key = rsa; - return env; -} - -/** Helper, used by tor-gencert.c. Return the RSA from a - * crypto_pk_t. */ -RSA * -crypto_pk_get_rsa_(crypto_pk_t *env) -{ - return env->key; -} - -/** used by tortls.c: get an equivalent EVP_PKEY* for a crypto_pk_t. Iff - * private is set, include the private-key portion of the key. Return a valid - * pointer on success, and NULL on failure. */ -MOCK_IMPL(EVP_PKEY *, -crypto_pk_get_evp_pkey_,(crypto_pk_t *env, int private)) -{ - RSA *key = NULL; - EVP_PKEY *pkey = NULL; - tor_assert(env->key); - if (private) { - if (!(key = RSAPrivateKey_dup(env->key))) - goto error; - } else { - if (!(key = RSAPublicKey_dup(env->key))) - goto error; - } - if (!(pkey = EVP_PKEY_new())) - goto error; - if (!(EVP_PKEY_assign_RSA(pkey, key))) - goto error; - return pkey; - error: - if (pkey) - EVP_PKEY_free(pkey); - if (key) - RSA_free(key); - return NULL; -} - -/** Allocate and return storage for a public key. The key itself will not yet - * be set. - */ -MOCK_IMPL(crypto_pk_t *, -crypto_pk_new,(void)) -{ - RSA *rsa; - - rsa = RSA_new(); - tor_assert(rsa); - return crypto_new_pk_from_rsa_(rsa); -} - -/** Release a reference to an asymmetric key; when all the references - * are released, free the key. - */ -void -crypto_pk_free_(crypto_pk_t *env) -{ - if (!env) - return; - - if (--env->refs > 0) - return; - tor_assert(env->refs == 0); - - if (env->key) - RSA_free(env->key); - - tor_free(env); -} - -/** Generate a <b>bits</b>-bit new public/private keypair in <b>env</b>. - * Return 0 on success, -1 on failure. - */ -MOCK_IMPL(int, -crypto_pk_generate_key_with_bits,(crypto_pk_t *env, int bits)) -{ - tor_assert(env); - - if (env->key) { - RSA_free(env->key); - env->key = NULL; - } - - { - BIGNUM *e = BN_new(); - RSA *r = NULL; - if (!e) - goto done; - if (! BN_set_word(e, 65537)) - goto done; - r = RSA_new(); - if (!r) - goto done; - if (RSA_generate_key_ex(r, bits, e, NULL) == -1) - goto done; - - env->key = r; - r = NULL; - done: - if (e) - BN_clear_free(e); - if (r) - RSA_free(r); - } - - if (!env->key) { - crypto_log_errors(LOG_WARN, "generating RSA key"); - return -1; - } - - return 0; -} - -/** A PEM callback that always reports a failure to get a password */ -static int -pem_no_password_cb(char *buf, int size, int rwflag, void *u) -{ - (void)buf; - (void)size; - (void)rwflag; - (void)u; - return -1; -} - -/** Read a PEM-encoded private key from the <b>len</b>-byte string <b>s</b> - * into <b>env</b>. Return 0 on success, -1 on failure. If len is -1, - * the string is nul-terminated. - */ -int -crypto_pk_read_private_key_from_string(crypto_pk_t *env, - const char *s, ssize_t len) -{ - BIO *b; - - tor_assert(env); - tor_assert(s); - tor_assert(len < INT_MAX && len < SSIZE_T_CEILING); - - /* Create a read-only memory BIO, backed by the string 's' */ - b = BIO_new_mem_buf((char*)s, (int)len); - if (!b) - return -1; - - if (env->key) - RSA_free(env->key); - - env->key = PEM_read_bio_RSAPrivateKey(b,NULL,pem_no_password_cb,NULL); - - BIO_free(b); - - if (!env->key) { - crypto_log_errors(LOG_WARN, "Error parsing private key"); - return -1; - } - return 0; -} - -/** Read a PEM-encoded private key from the file named by - * <b>keyfile</b> into <b>env</b>. Return 0 on success, -1 on failure. - */ -int -crypto_pk_read_private_key_from_filename(crypto_pk_t *env, - const char *keyfile) -{ - char *contents; - int r; - - /* Read the file into a string. */ - contents = read_file_to_str(keyfile, 0, NULL); - if (!contents) { - log_warn(LD_CRYPTO, "Error reading private key from \"%s\"", keyfile); - return -1; - } - - /* Try to parse it. */ - r = crypto_pk_read_private_key_from_string(env, contents, -1); - memwipe(contents, 0, strlen(contents)); - tor_free(contents); - if (r) - return -1; /* read_private_key_from_string already warned, so we don't.*/ - - /* Make sure it's valid. */ - if (crypto_pk_check_key(env) <= 0) - return -1; - - return 0; -} - -/** Helper function to implement crypto_pk_write_*_key_to_string. Return 0 on - * success, -1 on failure. */ -static int -crypto_pk_write_key_to_string_impl(crypto_pk_t *env, char **dest, - size_t *len, int is_public) -{ - BUF_MEM *buf; - BIO *b; - int r; - - tor_assert(env); - tor_assert(env->key); - tor_assert(dest); - - b = BIO_new(BIO_s_mem()); /* Create a memory BIO */ - if (!b) - return -1; - - /* Now you can treat b as if it were a file. Just use the - * PEM_*_bio_* functions instead of the non-bio variants. - */ - if (is_public) - r = PEM_write_bio_RSAPublicKey(b, env->key); - else - r = PEM_write_bio_RSAPrivateKey(b, env->key, NULL,NULL,0,NULL,NULL); - - if (!r) { - crypto_log_errors(LOG_WARN, "writing RSA key to string"); - BIO_free(b); - return -1; - } - - BIO_get_mem_ptr(b, &buf); - - *dest = tor_malloc(buf->length+1); - memcpy(*dest, buf->data, buf->length); - (*dest)[buf->length] = 0; /* nul terminate it */ - *len = buf->length; - - BIO_free(b); - - return 0; -} - -/** PEM-encode the public key portion of <b>env</b> and write it to a - * newly allocated string. On success, set *<b>dest</b> to the new - * string, *<b>len</b> to the string's length, and return 0. On - * failure, return -1. - */ -int -crypto_pk_write_public_key_to_string(crypto_pk_t *env, char **dest, - size_t *len) -{ - return crypto_pk_write_key_to_string_impl(env, dest, len, 1); -} - -/** PEM-encode the private key portion of <b>env</b> and write it to a - * newly allocated string. On success, set *<b>dest</b> to the new - * string, *<b>len</b> to the string's length, and return 0. On - * failure, return -1. - */ -int -crypto_pk_write_private_key_to_string(crypto_pk_t *env, char **dest, - size_t *len) -{ - return crypto_pk_write_key_to_string_impl(env, dest, len, 0); -} - -/** Read a PEM-encoded public key from the first <b>len</b> characters of - * <b>src</b>, and store the result in <b>env</b>. Return 0 on success, -1 on - * failure. - */ -int -crypto_pk_read_public_key_from_string(crypto_pk_t *env, const char *src, - size_t len) -{ - BIO *b; - - tor_assert(env); - tor_assert(src); - tor_assert(len<INT_MAX); - - b = BIO_new(BIO_s_mem()); /* Create a memory BIO */ - if (!b) - return -1; - - BIO_write(b, src, (int)len); - - if (env->key) - RSA_free(env->key); - env->key = PEM_read_bio_RSAPublicKey(b, NULL, pem_no_password_cb, NULL); - BIO_free(b); - if (!env->key) { - crypto_log_errors(LOG_WARN, "reading public key from string"); - return -1; - } - - return 0; -} - -/** Write the private key from <b>env</b> into the file named by <b>fname</b>, - * PEM-encoded. Return 0 on success, -1 on failure. - */ -int -crypto_pk_write_private_key_to_filename(crypto_pk_t *env, - const char *fname) -{ - BIO *bio; - char *cp; - long len; - char *s; - int r; - - tor_assert(crypto_pk_private_ok(env)); - - if (!(bio = BIO_new(BIO_s_mem()))) - return -1; - if (PEM_write_bio_RSAPrivateKey(bio, env->key, NULL,NULL,0,NULL,NULL) - == 0) { - crypto_log_errors(LOG_WARN, "writing private key"); - BIO_free(bio); - return -1; - } - len = BIO_get_mem_data(bio, &cp); - tor_assert(len >= 0); - s = tor_malloc(len+1); - memcpy(s, cp, len); - s[len]='\0'; - r = write_str_to_file(fname, s, 0); - BIO_free(bio); - memwipe(s, 0, strlen(s)); - tor_free(s); - return r; -} - -/** Return true iff <b>env</b> has a valid key. - */ -int -crypto_pk_check_key(crypto_pk_t *env) -{ - int r; - tor_assert(env); - - r = RSA_check_key(env->key); - if (r <= 0) - crypto_log_errors(LOG_WARN,"checking RSA key"); - return r; -} - -/** Return true iff <b>key</b> contains the private-key portion of the RSA - * key. */ -int -crypto_pk_key_is_private(const crypto_pk_t *key) -{ - tor_assert(key); - return crypto_pk_private_ok(key); -} - -/** Return true iff <b>env</b> contains a public key whose public exponent - * equals 65537. - */ -int -crypto_pk_public_exponent_ok(crypto_pk_t *env) -{ - tor_assert(env); - tor_assert(env->key); - - const BIGNUM *e; - -#ifdef OPENSSL_1_1_API - const BIGNUM *n, *d; - RSA_get0_key(env->key, &n, &e, &d); -#else - e = env->key->e; -#endif /* defined(OPENSSL_1_1_API) */ - return BN_is_word(e, 65537); -} - -/** Compare the public-key components of a and b. Return less than 0 - * if a\<b, 0 if a==b, and greater than 0 if a\>b. A NULL key is - * considered to be less than all non-NULL keys, and equal to itself. - * - * Note that this may leak information about the keys through timing. - */ -int -crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b) -{ - int result; - char a_is_non_null = (a != NULL) && (a->key != NULL); - char b_is_non_null = (b != NULL) && (b->key != NULL); - char an_argument_is_null = !a_is_non_null | !b_is_non_null; - - result = tor_memcmp(&a_is_non_null, &b_is_non_null, sizeof(a_is_non_null)); - if (an_argument_is_null) - return result; - - const BIGNUM *a_n, *a_e; - const BIGNUM *b_n, *b_e; - -#ifdef OPENSSL_1_1_API - const BIGNUM *a_d, *b_d; - RSA_get0_key(a->key, &a_n, &a_e, &a_d); - RSA_get0_key(b->key, &b_n, &b_e, &b_d); -#else - a_n = a->key->n; - a_e = a->key->e; - b_n = b->key->n; - b_e = b->key->e; -#endif /* defined(OPENSSL_1_1_API) */ - - tor_assert(a_n != NULL && a_e != NULL); - tor_assert(b_n != NULL && b_e != NULL); - - result = BN_cmp(a_n, b_n); - if (result) - return result; - return BN_cmp(a_e, b_e); -} +#endif /** Compare the public-key components of a and b. Return non-zero iff * a==b. A NULL key is considered to be distinct from all non-NULL @@ -520,98 +68,6 @@ crypto_pk_eq_keys(const crypto_pk_t *a, const crypto_pk_t *b) return (crypto_pk_cmp_keys(a, b) == 0); } -/** Return the size of the public key modulus in <b>env</b>, in bytes. */ -size_t -crypto_pk_keysize(const crypto_pk_t *env) -{ - tor_assert(env); - tor_assert(env->key); - - return (size_t) RSA_size((RSA*)env->key); -} - -/** Return the size of the public key modulus of <b>env</b>, in bits. */ -int -crypto_pk_num_bits(crypto_pk_t *env) -{ - tor_assert(env); - tor_assert(env->key); - -#ifdef OPENSSL_1_1_API - /* It's so stupid that there's no other way to check that n is valid - * before calling RSA_bits(). - */ - const BIGNUM *n, *e, *d; - RSA_get0_key(env->key, &n, &e, &d); - tor_assert(n != NULL); - - return RSA_bits(env->key); -#else /* !(defined(OPENSSL_1_1_API)) */ - tor_assert(env->key->n); - return BN_num_bits(env->key->n); -#endif /* defined(OPENSSL_1_1_API) */ -} - -/** Increase the reference count of <b>env</b>, and return it. - */ -crypto_pk_t * -crypto_pk_dup_key(crypto_pk_t *env) -{ - tor_assert(env); - tor_assert(env->key); - - env->refs++; - return env; -} - -#ifdef TOR_UNIT_TESTS -/** For testing: replace dest with src. (Dest must have a refcount - * of 1) */ -void -crypto_pk_assign_(crypto_pk_t *dest, const crypto_pk_t *src) -{ - tor_assert(dest); - tor_assert(dest->refs == 1); - tor_assert(src); - RSA_free(dest->key); - dest->key = RSAPrivateKey_dup(src->key); -} -#endif /* defined(TOR_UNIT_TESTS) */ - -/** Make a real honest-to-goodness copy of <b>env</b>, and return it. - * Returns NULL on failure. */ -crypto_pk_t * -crypto_pk_copy_full(crypto_pk_t *env) -{ - RSA *new_key; - int privatekey = 0; - tor_assert(env); - tor_assert(env->key); - - if (crypto_pk_private_ok(env)) { - new_key = RSAPrivateKey_dup(env->key); - privatekey = 1; - } else { - new_key = RSAPublicKey_dup(env->key); - } - if (!new_key) { - /* LCOV_EXCL_START - * - * We can't cause RSA*Key_dup() to fail, so we can't really test this. - */ - log_err(LD_CRYPTO, "Unable to duplicate a %s key: openssl failed.", - privatekey?"private":"public"); - crypto_log_errors(LOG_ERR, - privatekey ? "Duplicating a private key" : - "Duplicating a public key"); - tor_fragile_assert(); - return NULL; - /* LCOV_EXCL_STOP */ - } - - return crypto_new_pk_from_rsa_(new_key); -} - /** Perform a hybrid (public/secret) encryption on <b>fromlen</b> * bytes of data from <b>from</b>, with padding type 'padding', * storing the results on <b>to</b>. @@ -646,7 +102,7 @@ crypto_pk_obsolete_public_hybrid_encrypt(crypto_pk_t *env, tor_assert(to); tor_assert(fromlen < SIZE_T_CEILING); - overhead = crypto_get_rsa_padding_overhead(crypto_get_rsa_padding(padding)); + overhead = crypto_get_rsa_padding_overhead(padding); pkeylen = crypto_pk_keysize(env); if (!force && fromlen+overhead <= pkeylen) { @@ -754,179 +210,6 @@ crypto_pk_obsolete_private_hybrid_decrypt(crypto_pk_t *env, return -1; } -/** Encrypt <b>fromlen</b> bytes from <b>from</b> with the public key - * in <b>env</b>, using the padding method <b>padding</b>. On success, - * write the result to <b>to</b>, and return the number of bytes - * written. On failure, return -1. - * - * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be - * at least the length of the modulus of <b>env</b>. - */ -int -crypto_pk_public_encrypt(crypto_pk_t *env, char *to, size_t tolen, - const char *from, size_t fromlen, int padding) -{ - int r; - tor_assert(env); - tor_assert(from); - tor_assert(to); - tor_assert(fromlen<INT_MAX); - tor_assert(tolen >= crypto_pk_keysize(env)); - - r = RSA_public_encrypt((int)fromlen, - (unsigned char*)from, (unsigned char*)to, - env->key, crypto_get_rsa_padding(padding)); - if (r<0) { - crypto_log_errors(LOG_WARN, "performing RSA encryption"); - return -1; - } - return r; -} - -/** Decrypt <b>fromlen</b> bytes from <b>from</b> with the private key - * in <b>env</b>, using the padding method <b>padding</b>. On success, - * write the result to <b>to</b>, and return the number of bytes - * written. On failure, return -1. - * - * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be - * at least the length of the modulus of <b>env</b>. - */ -int -crypto_pk_private_decrypt(crypto_pk_t *env, char *to, - size_t tolen, - const char *from, size_t fromlen, - int padding, int warnOnFailure) -{ - int r; - tor_assert(env); - tor_assert(from); - tor_assert(to); - tor_assert(env->key); - tor_assert(fromlen<INT_MAX); - tor_assert(tolen >= crypto_pk_keysize(env)); - if (!crypto_pk_key_is_private(env)) - /* Not a private key */ - return -1; - - r = RSA_private_decrypt((int)fromlen, - (unsigned char*)from, (unsigned char*)to, - env->key, crypto_get_rsa_padding(padding)); - - if (r<0) { - crypto_log_errors(warnOnFailure?LOG_WARN:LOG_DEBUG, - "performing RSA decryption"); - return -1; - } - return r; -} - -/** Check the signature in <b>from</b> (<b>fromlen</b> bytes long) with the - * public key in <b>env</b>, using PKCS1 padding. On success, write the - * signed data to <b>to</b>, and return the number of bytes written. - * On failure, return -1. - * - * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be - * at least the length of the modulus of <b>env</b>. - */ -MOCK_IMPL(int, -crypto_pk_public_checksig,(const crypto_pk_t *env, char *to, - size_t tolen, - const char *from, size_t fromlen)) -{ - int r; - tor_assert(env); - tor_assert(from); - tor_assert(to); - tor_assert(fromlen < INT_MAX); - tor_assert(tolen >= crypto_pk_keysize(env)); - r = RSA_public_decrypt((int)fromlen, - (unsigned char*)from, (unsigned char*)to, - env->key, RSA_PKCS1_PADDING); - - if (r<0) { - crypto_log_errors(LOG_INFO, "checking RSA signature"); - return -1; - } - return r; -} - -/** Sign <b>fromlen</b> bytes of data from <b>from</b> with the private key in - * <b>env</b>, using PKCS1 padding. On success, write the signature to - * <b>to</b>, and return the number of bytes written. On failure, return - * -1. - * - * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be - * at least the length of the modulus of <b>env</b>. - */ -int -crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen, - const char *from, size_t fromlen) -{ - int r; - tor_assert(env); - tor_assert(from); - tor_assert(to); - tor_assert(fromlen < INT_MAX); - tor_assert(tolen >= crypto_pk_keysize(env)); - if (!crypto_pk_key_is_private(env)) - /* Not a private key */ - return -1; - - r = RSA_private_encrypt((int)fromlen, - (unsigned char*)from, (unsigned char*)to, - (RSA*)env->key, RSA_PKCS1_PADDING); - if (r<0) { - crypto_log_errors(LOG_WARN, "generating RSA signature"); - return -1; - } - return r; -} - -/** ASN.1-encode the public portion of <b>pk</b> into <b>dest</b>. - * Return -1 on error, or the number of characters used on success. - */ -int -crypto_pk_asn1_encode(const crypto_pk_t *pk, char *dest, size_t dest_len) -{ - int len; - unsigned char *buf = NULL; - - len = i2d_RSAPublicKey(pk->key, &buf); - if (len < 0 || buf == NULL) - return -1; - - if ((size_t)len > dest_len || dest_len > SIZE_T_CEILING) { - OPENSSL_free(buf); - return -1; - } - /* We don't encode directly into 'dest', because that would be illegal - * type-punning. (C99 is smarter than me, C99 is smarter than me...) - */ - memcpy(dest,buf,len); - OPENSSL_free(buf); - return len; -} - -/** Decode an ASN.1-encoded public key from <b>str</b>; return the result on - * success and NULL on failure. - */ -crypto_pk_t * -crypto_pk_asn1_decode(const char *str, size_t len) -{ - RSA *rsa; - unsigned char *buf; - const unsigned char *cp; - cp = buf = tor_malloc(len); - memcpy(buf,str,len); - rsa = d2i_RSAPublicKey(NULL, &cp, len); - tor_free(buf); - if (!rsa) { - crypto_log_errors(LOG_WARN,"decoding public key"); - return NULL; - } - return crypto_new_pk_from_rsa_(rsa); -} - /** Given a private or public key <b>pk</b>, put a fingerprint of the * public key into <b>fp_out</b> (must have at least FINGERPRINT_LEN+1 bytes of * space). Return 0 on success, -1 on failure. @@ -976,6 +259,26 @@ crypto_pk_get_hashed_fingerprint(crypto_pk_t *pk, char *fp_out) return 0; } +/** Copy <b>in</b> to the <b>outlen</b>-byte buffer <b>out</b>, adding spaces + * every four characters. */ +void +crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in) +{ + int n = 0; + char *end = out+outlen; + tor_assert(outlen < SIZE_T_CEILING); + + while (*in && out<end) { + *out++ = *in++; + if (++n == 4 && *in && out<end) { + n = 0; + *out++ = ' '; + } + } + tor_assert(out<end); + *out = '\0'; +} + /** Check a siglen-byte long signature at <b>sig</b> against * <b>datalen</b> bytes of data at <b>data</b>, using the public key * in <b>env</b>. Return 0 if <b>sig</b> is a correct signature for @@ -1092,6 +395,188 @@ crypto_pk_get_common_digests(crypto_pk_t *pk, common_digests_t *digests_out) return rv; } +static const char RSA_PUBLIC_TAG[] = "RSA PUBLIC KEY"; +static const char RSA_PRIVATE_TAG[] = "RSA PRIVATE KEY"; + +/* These are overestimates for how many extra bytes we might need to encode + * a key in DER */ +#define PRIVATE_ASN_MAX_OVERHEAD_FACTOR 16 +#define PUBLIC_ASN_MAX_OVERHEAD_FACTOR 3 + +/** Helper: PEM-encode <b>env</b> and write it to a newly allocated string. + * If <b>private_key</b>, write the private part of <b>env</b>; otherwise + * write only the public portion. On success, set *<b>dest</b> to the new + * string, *<b>len</b> to the string's length, and return 0. On failure, + * return -1. + */ +static int +crypto_pk_write_to_string_generic(crypto_pk_t *env, + char **dest, size_t *len, + bool private_key) +{ + const int factor = + private_key ? PRIVATE_ASN_MAX_OVERHEAD_FACTOR + : PUBLIC_ASN_MAX_OVERHEAD_FACTOR; + size_t buflen = crypto_pk_keysize(env) * factor; + const char *tag = + private_key ? RSA_PRIVATE_TAG : RSA_PUBLIC_TAG; + char *buf = tor_malloc(buflen); + char *result = NULL; + size_t resultlen = 0; + int rv = -1; + + int n = private_key + ? crypto_pk_asn1_encode_private(env, buf, buflen) + : crypto_pk_asn1_encode(env, buf, buflen); + if (n < 0) + goto done; + + resultlen = pem_encoded_size(n, tag); + result = tor_malloc(resultlen); + if (pem_encode(result, resultlen, + (const unsigned char *)buf, n, tag) < 0) { + goto done; + } + + *dest = result; + *len = resultlen; + rv = 0; + + done: + if (rv < 0 && result) { + memwipe(result, 0, resultlen); + tor_free(result); + } + memwipe(buf, 0, buflen); + tor_free(buf); + return rv; +} + +/** PEM-encode the public key portion of <b>env</b> and write it to a + * newly allocated string. On success, set *<b>dest</b> to the new + * string, *<b>len</b> to the string's length, and return 0. On + * failure, return -1. + */ +int +crypto_pk_write_public_key_to_string(crypto_pk_t *env, + char **dest, size_t *len) +{ + return crypto_pk_write_to_string_generic(env, dest, len, false); +} + +/** PEM-encode the private key portion of <b>env</b> and write it to a + * newly allocated string. On success, set *<b>dest</b> to the new + * string, *<b>len</b> to the string's length, and return 0. On + * failure, return -1. + */ +int +crypto_pk_write_private_key_to_string(crypto_pk_t *env, + char **dest, size_t *len) +{ + return crypto_pk_write_to_string_generic(env, dest, len, true); +} + +/** + * Helper. Read a PEM-encoded RSA from the first <b>len</b> characters of + * <b>src</b>, and store the result in <b>env</b>. If <b>private_key</b>, + * expect a private key; otherwise expect a public key. Return 0 on success, + * -1 on failure. If len is -1, the string is nul-terminated. + */ +static int +crypto_pk_read_from_string_generic(crypto_pk_t *env, const char *src, + size_t len, bool private_key) +{ + if (len == (size_t)-1) // "-1" indicates "use the length of the string." + len = strlen(src); + + const char *tag = + private_key ? RSA_PRIVATE_TAG : RSA_PUBLIC_TAG; + size_t buflen = len; + uint8_t *buf = tor_malloc(buflen); + int rv = -1; + + int n = pem_decode(buf, buflen, src, len, tag); + if (n < 0) + goto done; + + crypto_pk_t *pk = private_key + ? crypto_pk_asn1_decode_private((const char*)buf, n) + : crypto_pk_asn1_decode((const char*)buf, n); + if (! pk) + goto done; + + if (private_key) + crypto_pk_assign_private(env, pk); + else + crypto_pk_assign_public(env, pk); + crypto_pk_free(pk); + rv = 0; + + done: + memwipe(buf, 0, buflen); + tor_free(buf); + return rv; +} + +/** Read a PEM-encoded public key from the first <b>len</b> characters of + * <b>src</b>, and store the result in <b>env</b>. Return 0 on success, -1 on + * failure. If len is -1, the string is nul-terminated. + */ +int +crypto_pk_read_public_key_from_string(crypto_pk_t *env, + const char *src, size_t len) +{ + return crypto_pk_read_from_string_generic(env, src, len, false); +} + +/** Read a PEM-encoded private key from the <b>len</b>-byte string <b>src</b> + * into <b>env</b>. Return 0 on success, -1 on failure. If len is -1, + * the string is nul-terminated. + */ +int +crypto_pk_read_private_key_from_string(crypto_pk_t *env, + const char *src, ssize_t len) +{ + return crypto_pk_read_from_string_generic(env, src, len, true); +} + +/** Read a PEM-encoded private key from the file named by + * <b>keyfile</b> into <b>env</b>. Return 0 on success, -1 on failure. + */ +int +crypto_pk_read_private_key_from_filename(crypto_pk_t *env, + const char *keyfile) +{ + struct stat st; + char *buf = read_file_to_str(keyfile, 0, &st); + if (!buf) + return -1; + + int rv = crypto_pk_read_private_key_from_string(env, buf, st.st_size); + memwipe(buf, 0, st.st_size); + tor_free(buf); + return rv; +} + +/** Write the private key from <b>env</b> into the file named by <b>fname</b>, + * PEM-encoded. Return 0 on success, -1 on failure. + */ +int +crypto_pk_write_private_key_to_filename(crypto_pk_t *env, + const char *fname) +{ + char *s = NULL; + size_t n = 0; + + if (crypto_pk_write_private_key_to_string(env, &s, &n) < 0) + return -1; + + int rv = write_bytes_to_file(fname, s, n, 0); + memwipe(s, 0, n); + tor_free(s); + return rv; +} + /** Given a crypto_pk_t <b>pk</b>, allocate a new buffer containing the * Base64 encoding of the DER representation of the private key as a NUL * terminated string, and return it via <b>priv_out</b>. Return 0 on @@ -1100,30 +585,35 @@ crypto_pk_get_common_digests(crypto_pk_t *pk, common_digests_t *digests_out) * It is the caller's responsibility to sanitize and free the resulting buffer. */ int -crypto_pk_base64_encode(const crypto_pk_t *pk, char **priv_out) +crypto_pk_base64_encode_private(const crypto_pk_t *pk, char **priv_out) { - unsigned char *der = NULL; - int der_len; - int ret = -1; + size_t buflen = crypto_pk_keysize(pk)*16; + char *buf = tor_malloc(buflen); + char *result = NULL; + size_t reslen = 0; + bool ok = false; - *priv_out = NULL; + int n = crypto_pk_asn1_encode_private(pk, buf, buflen); - der_len = i2d_RSAPrivateKey(pk->key, &der); - if (der_len < 0 || der == NULL) - return ret; + if (n < 0) + goto done; - size_t priv_len = base64_encode_size(der_len, 0) + 1; - char *priv = tor_malloc_zero(priv_len); - if (base64_encode(priv, priv_len, (char *)der, der_len, 0) >= 0) { - *priv_out = priv; - ret = 0; - } else { - tor_free(priv); - } + reslen = base64_encode_size(n, 0)+1; + result = tor_malloc(reslen); + if (base64_encode(result, reslen, buf, n, 0) < 0) + goto done; - memwipe(der, 0, der_len); - OPENSSL_free(der); - return ret; + ok = true; + + done: + memwipe(buf, 0, buflen); + tor_free(buf); + if (result && ! ok) { + memwipe(result, 0, reslen); + tor_free(result); + } + *priv_out = result; + return ok ? 0 : -1; } /** Given a string containing the Base64 encoded DER representation of the @@ -1131,7 +621,7 @@ crypto_pk_base64_encode(const crypto_pk_t *pk, char **priv_out) * on failure. */ crypto_pk_t * -crypto_pk_base64_decode(const char *str, size_t len) +crypto_pk_base64_decode_private(const char *str, size_t len) { crypto_pk_t *pk = NULL; @@ -1142,24 +632,11 @@ crypto_pk_base64_decode(const char *str, size_t len) goto out; } - const unsigned char *dp = (unsigned char*)der; /* Shut the compiler up. */ - RSA *rsa = d2i_RSAPrivateKey(NULL, &dp, der_len); - if (!rsa) { - crypto_log_errors(LOG_WARN, "decoding private key"); - goto out; - } - - pk = crypto_new_pk_from_rsa_(rsa); - - /* Make sure it's valid. */ - if (crypto_pk_check_key(pk) <= 0) { - crypto_pk_free(pk); - pk = NULL; - goto out; - } + pk = crypto_pk_asn1_decode_private(der, der_len); out: - memwipe(der, 0, len + 1); + memwipe(der, 0, len+1); tor_free(der); + return pk; } diff --git a/src/lib/crypt_ops/crypto_rsa.h b/src/lib/crypt_ops/crypto_rsa.h index 51fc974821..007964b268 100644 --- a/src/lib/crypt_ops/crypto_rsa.h +++ b/src/lib/crypt_ops/crypto_rsa.h @@ -29,6 +29,13 @@ /** Number of bytes added for PKCS1-OAEP padding. */ #define PKCS1_OAEP_PADDING_OVERHEAD 42 +/** Length of encoded public key fingerprints, including space; but not + * including terminating NUL. */ +#define FINGERPRINT_LEN 49 + +/** Value of 'e' to use in our public keys */ +#define TOR_RSA_EXPONENT 65537 + /** A public key, or a public/private key-pair. */ typedef struct crypto_pk_t crypto_pk_t; @@ -57,7 +64,7 @@ int crypto_pk_read_private_key_from_string(crypto_pk_t *env, int crypto_pk_write_private_key_to_filename(crypto_pk_t *env, const char *fname); -int crypto_pk_check_key(crypto_pk_t *env); +int crypto_pk_is_valid_private_key(const crypto_pk_t *env); int crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b); int crypto_pk_eq_keys(const crypto_pk_t *a, const crypto_pk_t *b); size_t crypto_pk_keysize(const crypto_pk_t *env); @@ -65,7 +72,7 @@ int crypto_pk_num_bits(crypto_pk_t *env); crypto_pk_t *crypto_pk_dup_key(crypto_pk_t *orig); crypto_pk_t *crypto_pk_copy_full(crypto_pk_t *orig); int crypto_pk_key_is_private(const crypto_pk_t *key); -int crypto_pk_public_exponent_ok(crypto_pk_t *env); +int crypto_pk_public_exponent_ok(const crypto_pk_t *env); int crypto_pk_obsolete_public_hybrid_encrypt(crypto_pk_t *env, char *to, size_t tolen, const char *from, size_t fromlen, @@ -86,8 +93,12 @@ int crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen, const char *from, size_t fromlen); int crypto_pk_asn1_encode(const crypto_pk_t *pk, char *dest, size_t dest_len); crypto_pk_t *crypto_pk_asn1_decode(const char *str, size_t len); +int crypto_pk_asn1_encode_private(const crypto_pk_t *pk, + char *dest, size_t dest_len); +crypto_pk_t *crypto_pk_asn1_decode_private(const char *str, size_t len); int crypto_pk_get_fingerprint(crypto_pk_t *pk, char *fp_out,int add_space); int crypto_pk_get_hashed_fingerprint(crypto_pk_t *pk, char *fp_out); +void crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in); MOCK_DECL(int, crypto_pk_public_checksig_digest,(crypto_pk_t *env, const char *data, size_t datalen, const char *sig, size_t siglen)); @@ -96,20 +107,39 @@ int crypto_pk_private_sign_digest(crypto_pk_t *env, char *to, size_t tolen, int crypto_pk_get_digest(const crypto_pk_t *pk, char *digest_out); int crypto_pk_get_common_digests(crypto_pk_t *pk, common_digests_t *digests_out); -int crypto_pk_base64_encode(const crypto_pk_t *pk, char **priv_out); -crypto_pk_t *crypto_pk_base64_decode(const char *str, size_t len); +int crypto_pk_base64_encode_private(const crypto_pk_t *pk, char **priv_out); +crypto_pk_t *crypto_pk_base64_decode_private(const char *str, size_t len); +#ifdef ENABLE_OPENSSL /* Prototypes for private functions only used by tortls.c, crypto.c, and the * unit tests. */ struct rsa_st; -struct rsa_st *crypto_pk_get_rsa_(crypto_pk_t *env); -crypto_pk_t *crypto_new_pk_from_rsa_(struct rsa_st *rsa); -MOCK_DECL(struct evp_pkey_st *, crypto_pk_get_evp_pkey_,(crypto_pk_t *env, - int private)); struct evp_pkey_st; +struct rsa_st *crypto_pk_get_openssl_rsa_(crypto_pk_t *env); +crypto_pk_t *crypto_new_pk_from_openssl_rsa_(struct rsa_st *rsa); +MOCK_DECL(struct evp_pkey_st *, crypto_pk_get_openssl_evp_pkey_,( + crypto_pk_t *env,int private)); +#endif + +#ifdef ENABLE_NSS +struct SECKEYPublicKeyStr; +struct SECKEYPrivateKeyStr; +crypto_pk_t *crypto_pk_new_from_nss_pubkey(struct SECKEYPublicKeyStr *pub); +const struct SECKEYPublicKeyStr *crypto_pk_get_nss_pubkey( + const crypto_pk_t *key); +const struct SECKEYPrivateKeyStr *crypto_pk_get_nss_privkey( + const crypto_pk_t *key); +#endif + +void crypto_pk_assign_public(crypto_pk_t *dest, const crypto_pk_t *src); +void crypto_pk_assign_private(crypto_pk_t *dest, const crypto_pk_t *src); #ifdef TOR_UNIT_TESTS -void crypto_pk_assign_(crypto_pk_t *dest, const crypto_pk_t *src); +#ifdef ENABLE_NSS +struct SECItemStr; +STATIC int secitem_uint_cmp(const struct SECItemStr *a, + const struct SECItemStr *b); +#endif #endif #endif diff --git a/src/lib/crypt_ops/crypto_rsa_nss.c b/src/lib/crypt_ops/crypto_rsa_nss.c new file mode 100644 index 0000000000..dc282d7c9d --- /dev/null +++ b/src/lib/crypt_ops/crypto_rsa_nss.c @@ -0,0 +1,738 @@ +/* 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_rsa.c + * \brief NSS implementations of our RSA code. + **/ + +#include "lib/crypt_ops/crypto_rsa.h" + +#include "lib/crypt_ops/crypto_nss_mgt.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/ctime/di_ops.h" +#include "lib/encoding/binascii.h" +#include "lib/fs/files.h" +#include "lib/intmath/cmp.h" +#include "lib/intmath/muldiv.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" + +#include <string.h> + +#include <keyhi.h> +#include <pk11pub.h> +#include <secder.h> + +#ifdef ENABLE_OPENSSL +#include <openssl/rsa.h> +#include <openssl/evp.h> +#endif + +/** Declaration for crypto_pk_t structure. */ +struct crypto_pk_t +{ + SECKEYPrivateKey *seckey; + SECKEYPublicKey *pubkey; +}; + +/** Return true iff <b>key</b> contains the private-key portion of the RSA + * key. */ +int +crypto_pk_key_is_private(const crypto_pk_t *key) +{ + return key && key->seckey; +} + +/** used by tortls.c: wrap a SecKEYPublicKey in a crypto_pk_t. Take ownership + * of the RSA object. */ +crypto_pk_t * +crypto_pk_new_from_nss_pubkey(struct SECKEYPublicKeyStr *pub) +{ + crypto_pk_t *result = tor_malloc_zero(sizeof(crypto_pk_t)); + result->pubkey = pub; + return result; +} + +/** Return the SECKEYPublicKey for the provided crypto_pk_t. */ +const SECKEYPublicKey * +crypto_pk_get_nss_pubkey(const crypto_pk_t *key) +{ + tor_assert(key); + return key->pubkey; +} + +/** Return the SECKEYPrivateKey for the provided crypto_pk_t, or NULL if it + * does not exist. */ +const SECKEYPrivateKey * +crypto_pk_get_nss_privkey(const crypto_pk_t *key) +{ + tor_assert(key); + return key->seckey; +} + +#ifdef ENABLE_OPENSSL +/** used by tortls.c: wrap an RSA* in a crypto_pk_t. Take ownership of the + * RSA object. */ +crypto_pk_t * +crypto_new_pk_from_openssl_rsa_(RSA *rsa) +{ + crypto_pk_t *pk = NULL; + unsigned char *buf = NULL; + int len = i2d_RSAPublicKey(rsa, &buf); + RSA_free(rsa); + + if (len < 0 || buf == NULL) + goto end; + + pk = crypto_pk_asn1_decode((const char *)buf, len); + + end: + if (buf) + OPENSSL_free(buf); + return pk; +} + +/** Helper, used by tor-gencert.c. Return the RSA from a + * crypto_pk_t. */ +struct rsa_st * +crypto_pk_get_openssl_rsa_(crypto_pk_t *pk) +{ + size_t buflen = crypto_pk_keysize(pk)*16; + unsigned char *buf = tor_malloc_zero(buflen); + const unsigned char *cp = buf; + RSA *rsa = NULL; + + int used = crypto_pk_asn1_encode_private(pk, (char*)buf, buflen); + if (used < 0) + goto end; + rsa = d2i_RSAPrivateKey(NULL, &cp, used); + + end: + memwipe(buf, 0, buflen); + tor_free(buf); + return rsa; +} + +/** used by tortls.c: get an equivalent EVP_PKEY* for a crypto_pk_t. Iff + * private is set, include the private-key portion of the key. Return a valid + * pointer on success, and NULL on failure. */ +MOCK_IMPL(struct evp_pkey_st *, +crypto_pk_get_openssl_evp_pkey_,(crypto_pk_t *pk, int private)) +{ + size_t buflen = crypto_pk_keysize(pk)*16; + unsigned char *buf = tor_malloc_zero(buflen); + const unsigned char *cp = buf; + RSA *rsa = NULL; + EVP_PKEY *result = NULL; + + if (private) { + int len = crypto_pk_asn1_encode_private(pk, (char*)buf, buflen); + if (len < 0) + goto end; + rsa = d2i_RSAPrivateKey(NULL, &cp, len); + } else { + int len = crypto_pk_asn1_encode(pk, (char*)buf, buflen); + if (len < 0) + goto end; + rsa = d2i_RSAPublicKey(NULL, &cp, len); + } + if (!rsa) + goto end; + + if (!(result = EVP_PKEY_new())) + goto end; + if (!(EVP_PKEY_assign_RSA(result, rsa))) { + EVP_PKEY_free(result); + RSA_free(rsa); + result = NULL; + } + + end: + memwipe(buf, 0, buflen); + tor_free(buf); + return result; +} +#endif + +/** Allocate and return storage for a public key. The key itself will not yet + * be set. + */ +MOCK_IMPL(crypto_pk_t *, +crypto_pk_new,(void)) +{ + crypto_pk_t *result = tor_malloc_zero(sizeof(crypto_pk_t)); + return result; +} + +/** Release the NSS objects held in <b>key</b> */ +static void +crypto_pk_clear(crypto_pk_t *key) +{ + if (key->pubkey) + SECKEY_DestroyPublicKey(key->pubkey); + if (key->seckey) + SECKEY_DestroyPrivateKey(key->seckey); + memset(key, 0, sizeof(crypto_pk_t)); +} + +/** Release a reference to an asymmetric key; when all the references + * are released, free the key. + */ +void +crypto_pk_free_(crypto_pk_t *key) +{ + if (!key) + return; + + crypto_pk_clear(key); + + tor_free(key); +} + +/** Generate a <b>bits</b>-bit new public/private keypair in <b>env</b>. + * Return 0 on success, -1 on failure. + */ +MOCK_IMPL(int, +crypto_pk_generate_key_with_bits,(crypto_pk_t *key, int bits)) +{ + tor_assert(key); + + PK11RSAGenParams params = { + .keySizeInBits = bits, + .pe = TOR_RSA_EXPONENT + }; + + int result = -1; + PK11SlotInfo *slot = PK11_GetBestSlot(CKM_RSA_PKCS_KEY_PAIR_GEN, NULL); + SECKEYPrivateKey *seckey = NULL; + SECKEYPublicKey *pubkey = NULL; + + if (!slot) { + crypto_nss_log_errors(LOG_WARN, "getting slot for RSA keygen"); + goto done; + } + + seckey = PK11_GenerateKeyPair(slot, CKM_RSA_PKCS_KEY_PAIR_GEN, ¶ms, + &pubkey, + PR_FALSE /*isPerm */, + PR_FALSE /*isSensitive*/, + NULL); + if (seckey == NULL || pubkey == NULL) { + crypto_nss_log_errors(LOG_WARN, "generating an RSA key"); + goto done; + } + + crypto_pk_clear(key); + key->seckey = seckey; + key->pubkey = pubkey; + seckey = NULL; + pubkey = NULL; + + result = 0; + done: + if (slot) + PK11_FreeSlot(slot); + if (pubkey) + SECKEY_DestroyPublicKey(pubkey); + if (seckey) + SECKEY_DestroyPrivateKey(seckey); + + return result; +} + +/** Return true iff <b>env</b> is a valid private key. + */ +int +crypto_pk_is_valid_private_key(const crypto_pk_t *key) +{ + /* We don't need to do validation here, since unlike OpenSSL, NSS won't let + * us load private keys without validating them. */ + return key && key->seckey; +} + +/** Return true iff <b>env</b> contains a public key whose public exponent + * equals 65537. + */ +int +crypto_pk_public_exponent_ok(const crypto_pk_t *key) +{ + return key && + key->pubkey && + key->pubkey->keyType == rsaKey && + DER_GetUInteger(&key->pubkey->u.rsa.publicExponent) == TOR_RSA_EXPONENT; +} + +/** Compare two big-endian integers stored in a and b; return a tristate. + */ +STATIC int +secitem_uint_cmp(const SECItem *a, const SECItem *b) +{ + const unsigned abits = SECKEY_BigIntegerBitLength(a); + const unsigned bbits = SECKEY_BigIntegerBitLength(b); + + if (abits < bbits) + return -1; + else if (abits > bbits) + return 1; + + /* okay, they have the same number of bits set. Get a pair of aligned + * pointers to their bytes that are set... */ + const unsigned nbytes = CEIL_DIV(abits, 8); + tor_assert(nbytes <= a->len); + tor_assert(nbytes <= b->len); + + const unsigned char *aptr = a->data + (a->len - nbytes); + const unsigned char *bptr = b->data + (b->len - nbytes); + + /* And compare them. */ + return fast_memcmp(aptr, bptr, nbytes); +} + +/** Compare the public-key components of a and b. Return less than 0 + * if a\<b, 0 if a==b, and greater than 0 if a\>b. A NULL key is + * considered to be less than all non-NULL keys, and equal to itself. + * + * Note that this may leak information about the keys through timing. + */ +int +crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b) +{ + int result; + char a_is_non_null = (a != NULL) && (a->pubkey != NULL); + char b_is_non_null = (b != NULL) && (b->pubkey != NULL); + char an_argument_is_null = !a_is_non_null | !b_is_non_null; + + result = tor_memcmp(&a_is_non_null, &b_is_non_null, sizeof(a_is_non_null)); + if (an_argument_is_null) + return result; + + // This is all Tor uses with this structure. + tor_assert(a->pubkey->keyType == rsaKey); + tor_assert(b->pubkey->keyType == rsaKey); + + const SECItem *a_n, *a_e, *b_n, *b_e; + a_n = &a->pubkey->u.rsa.modulus; + b_n = &b->pubkey->u.rsa.modulus; + a_e = &a->pubkey->u.rsa.publicExponent; + b_e = &b->pubkey->u.rsa.publicExponent; + + result = secitem_uint_cmp(a_n, b_n); + if (result) + return result; + return secitem_uint_cmp(a_e, b_e); +} + +/** Return the size of the public key modulus in <b>env</b>, in bytes. */ +size_t +crypto_pk_keysize(const crypto_pk_t *key) +{ + tor_assert(key); + tor_assert(key->pubkey); + return SECKEY_PublicKeyStrength(key->pubkey); +} + +/** Return the size of the public key modulus of <b>env</b>, in bits. */ +int +crypto_pk_num_bits(crypto_pk_t *key) +{ + tor_assert(key); + tor_assert(key->pubkey); + return SECKEY_PublicKeyStrengthInBits(key->pubkey); +} + +/** + * Make a copy of <b>key</b> and return it. + */ +crypto_pk_t * +crypto_pk_dup_key(crypto_pk_t *key) +{ + crypto_pk_t *result = crypto_pk_new(); + if (key->pubkey) + result->pubkey = SECKEY_CopyPublicKey(key->pubkey); + if (key->seckey) + result->seckey = SECKEY_CopyPrivateKey(key->seckey); + return result; +} + +/** For testing: replace dest with src. (Dest must have a refcount + * of 1) */ +void +crypto_pk_assign_public(crypto_pk_t *dest, const crypto_pk_t *src) +{ + crypto_pk_clear(dest); + if (src->pubkey) + dest->pubkey = SECKEY_CopyPublicKey(src->pubkey); +} + +/** For testing: replace dest with src. (Dest must have a refcount + * of 1) */ +void +crypto_pk_assign_private(crypto_pk_t *dest, const crypto_pk_t *src) +{ + crypto_pk_clear(dest); + if (src->pubkey) + dest->pubkey = SECKEY_CopyPublicKey(src->pubkey); + if (src->seckey) + dest->seckey = SECKEY_CopyPrivateKey(src->seckey); +} + +/** Make a real honest-to-goodness copy of <b>env</b>, and return it. + * Returns NULL on failure. */ +crypto_pk_t * +crypto_pk_copy_full(crypto_pk_t *key) +{ + // These aren't reference-counted is nss, so it's fine to just + // use the same function. + return crypto_pk_dup_key(key); +} + +static const CK_RSA_PKCS_OAEP_PARAMS oaep_params = { + .hashAlg = CKM_SHA_1, + .mgf = CKG_MGF1_SHA1, + .source = CKZ_DATA_SPECIFIED, + .pSourceData = NULL, + .ulSourceDataLen = 0 +}; +static const SECItem oaep_item = { + .type = siBuffer, + .data = (unsigned char *) &oaep_params, + .len = sizeof(oaep_params) +}; + +/** Return the mechanism code and parameters for a given padding method when + * used with RSA */ +static CK_MECHANISM_TYPE +padding_to_mechanism(int padding, SECItem **item_out) +{ + switch (padding) { + case PK_PKCS1_OAEP_PADDING: + *item_out = (SECItem *)&oaep_item; + return CKM_RSA_PKCS_OAEP; + default: + tor_assert_unreached(); + *item_out = NULL; + return CKM_INVALID_MECHANISM; + } +} + +/** Encrypt <b>fromlen</b> bytes from <b>from</b> with the public key + * in <b>env</b>, using the padding method <b>padding</b>. On success, + * write the result to <b>to</b>, and return the number of bytes + * written. On failure, return -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +int +crypto_pk_public_encrypt(crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen, int padding) +{ + tor_assert(env); + tor_assert(to); + tor_assert(from); + tor_assert(tolen < INT_MAX); + tor_assert(fromlen < INT_MAX); + + if (BUG(! env->pubkey)) + return -1; + + unsigned int result_len = 0; + SECItem *item = NULL; + CK_MECHANISM_TYPE m = padding_to_mechanism(padding, &item); + + SECStatus s = PK11_PubEncrypt(env->pubkey, m, item, + (unsigned char *)to, &result_len, + (unsigned int)tolen, + (const unsigned char *)from, + (unsigned int)fromlen, + NULL); + if (s != SECSuccess) { + crypto_nss_log_errors(LOG_WARN, "encrypting to an RSA key"); + return -1; + } + + return (int)result_len; +} + +/** Decrypt <b>fromlen</b> bytes from <b>from</b> with the private key + * in <b>env</b>, using the padding method <b>padding</b>. On success, + * write the result to <b>to</b>, and return the number of bytes + * written. On failure, return -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>key</b>. + */ +int +crypto_pk_private_decrypt(crypto_pk_t *key, char *to, + size_t tolen, + const char *from, size_t fromlen, + int padding, int warnOnFailure) +{ + tor_assert(key); + tor_assert(to); + tor_assert(from); + tor_assert(tolen < INT_MAX); + tor_assert(fromlen < INT_MAX); + + if (!crypto_pk_key_is_private(key)) + return -1; /* Not a private key. */ + + unsigned int result_len = 0; + SECItem *item = NULL; + CK_MECHANISM_TYPE m = padding_to_mechanism(padding, &item); + SECStatus s = PK11_PrivDecrypt(key->seckey, m, item, + (unsigned char *)to, &result_len, + (unsigned int)tolen, + (const unsigned char *)from, + (unsigned int)fromlen); + + if (s != SECSuccess) { + const int severity = warnOnFailure ? LOG_WARN : LOG_INFO; + crypto_nss_log_errors(severity, "decrypting with an RSA key"); + return -1; + } + + return (int)result_len; +} + +/** Check the signature in <b>from</b> (<b>fromlen</b> bytes long) with the + * public key in <b>key</b>, using PKCS1 padding. On success, write the + * signed data to <b>to</b>, and return the number of bytes written. + * On failure, return -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>key</b>. + */ +MOCK_IMPL(int, +crypto_pk_public_checksig,(const crypto_pk_t *key, char *to, + size_t tolen, + const char *from, size_t fromlen)) +{ + tor_assert(key); + tor_assert(to); + tor_assert(from); + tor_assert(tolen < INT_MAX); + tor_assert(fromlen < INT_MAX); + tor_assert(key->pubkey); + + SECItem sig = { + .type = siBuffer, + .data = (unsigned char *) from, + .len = (unsigned int) fromlen, + }; + SECItem dsig = { + .type = siBuffer, + .data = (unsigned char *) to, + .len = (unsigned int) tolen + }; + SECStatus s; + s = PK11_VerifyRecover(key->pubkey, &sig, &dsig, NULL); + if (s != SECSuccess) + return -1; + + return (int)dsig.len; +} + +/** Sign <b>fromlen</b> bytes of data from <b>from</b> with the private key in + * <b>env</b>, using PKCS1 padding. On success, write the signature to + * <b>to</b>, and return the number of bytes written. On failure, return + * -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +int +crypto_pk_private_sign(const crypto_pk_t *key, char *to, size_t tolen, + const char *from, size_t fromlen) +{ + tor_assert(key); + tor_assert(to); + tor_assert(from); + tor_assert(tolen < INT_MAX); + tor_assert(fromlen < INT_MAX); + + if (BUG(!crypto_pk_key_is_private(key))) + return -1; + + SECItem sig = { + .type = siBuffer, + .data = (unsigned char *)to, + .len = (unsigned int) tolen + }; + SECItem hash = { + .type = siBuffer, + .data = (unsigned char *)from, + .len = (unsigned int) fromlen + }; + CK_MECHANISM_TYPE m = CKM_RSA_PKCS; + SECStatus s = PK11_SignWithMechanism(key->seckey, m, NULL, + &sig, &hash); + + if (s != SECSuccess) { + crypto_nss_log_errors(LOG_WARN, "signing with an RSA key"); + return -1; + } + + return (int)sig.len; +} + +/* "This has lead to people trading hard-to-find object identifiers and ASN.1 + * definitions like baseball cards" - Peter Gutmann, "X.509 Style Guide". */ +static const unsigned char RSA_OID[] = { + /* RSADSI */ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + /* PKCS1 */ 0x01, 0x01, + /* RSA */ 0x01 +}; + +/** ASN.1-encode the public portion of <b>pk</b> into <b>dest</b>. + * Return -1 on error, or the number of characters used on success. + */ +int +crypto_pk_asn1_encode(const crypto_pk_t *pk, char *dest, size_t dest_len) +{ + tor_assert(pk); + if (pk->pubkey == NULL) + return -1; + + CERTSubjectPublicKeyInfo *info; + info = SECKEY_CreateSubjectPublicKeyInfo(pk->pubkey); + if (! info) + return -1; + + const SECItem *item = &info->subjectPublicKey; + size_t actual_len = (item->len) >> 3; /* bits to bytes */ + size_t n_used = MIN(actual_len, dest_len); + memcpy(dest, item->data, n_used); + + SECKEY_DestroySubjectPublicKeyInfo(info); + return (int) n_used; +} + +/** Decode an ASN.1-encoded public key from <b>str</b>; return the result on + * success and NULL on failure. + */ +crypto_pk_t * +crypto_pk_asn1_decode(const char *str, size_t len) +{ + tor_assert(str); + if (len >= INT_MAX) + return NULL; + CERTSubjectPublicKeyInfo info = { + .algorithm = { + .algorithm = { + .type = siDEROID, + .data = (unsigned char *)RSA_OID, + .len = sizeof(RSA_OID) + } + }, + .subjectPublicKey = { + .type = siBuffer, + .data = (unsigned char *)str, + .len = (unsigned int)(len << 3) /* bytes to bits */ + } + }; + + SECKEYPublicKey *pub = SECKEY_ExtractPublicKey(&info); + if (pub == NULL) + return NULL; + + crypto_pk_t *result = crypto_pk_new(); + result->pubkey = pub; + return result; +} + +DISABLE_GCC_WARNING(unused-parameter) + +/** Given a crypto_pk_t <b>pk</b>, allocate a new buffer containing the Base64 + * encoding of the DER representation of the private key into the + * <b>dest_len</b>-byte buffer in <b>dest</b>. + * Return the number of bytes written on success, -1 on failure. + */ +int +crypto_pk_asn1_encode_private(const crypto_pk_t *pk, + char *dest, size_t destlen) +{ + tor_assert(destlen <= INT_MAX); + if (!crypto_pk_key_is_private(pk)) + return -1; + + SECKEYPrivateKeyInfo *info = PK11_ExportPrivKeyInfo(pk->seckey, NULL); + if (!info) + return -1; + SECItem *item = &info->privateKey; + + if (destlen < item->len) { + SECKEY_DestroyPrivateKeyInfo(info, PR_TRUE); + return -1; + } + int result = (int)item->len; + memcpy(dest, item->data, item->len); + SECKEY_DestroyPrivateKeyInfo(info, PR_TRUE); + + return result; +} + +/** Given a buffer containing the DER representation of the + * private key <b>str</b>, decode and return the result on success, or NULL + * on failure. + */ +crypto_pk_t * +crypto_pk_asn1_decode_private(const char *str, size_t len) +{ + tor_assert(str); + tor_assert(len < INT_MAX); + PK11SlotInfo *slot = PK11_GetBestSlot(CKM_RSA_PKCS, NULL); + if (!slot) + return NULL; + + SECKEYPrivateKeyInfo info = { + .algorithm = { + .algorithm = { + .type = siBuffer, + .data = (unsigned char *)RSA_OID, + .len = sizeof(RSA_OID) + } + }, + .privateKey = { + .type = siBuffer, + .data = (unsigned char *)str, + .len = (int)len, + } + }; + + SECStatus s; + SECKEYPrivateKey *seckey = NULL; + + s = PK11_ImportPrivateKeyInfoAndReturnKey(slot, &info, + NULL /* nickname */, + NULL /* publicValue */, + PR_FALSE /* isPerm */, + PR_FALSE /* isPrivate */, + KU_ALL /* keyUsage */, + &seckey, NULL); + + crypto_pk_t *output = NULL; + + if (s == SECSuccess && seckey) { + output = crypto_pk_new(); + output->seckey = seckey; + output->pubkey = SECKEY_ConvertToPublicKey(seckey); + tor_assert(output->pubkey); + } else { + crypto_nss_log_errors(LOG_WARN, "decoding an RSA private key"); + } + + if (! crypto_pk_is_valid_private_key(output)) { + crypto_pk_free(output); + output = NULL; + } + + if (slot) + PK11_FreeSlot(slot); + + return output; +} diff --git a/src/lib/crypt_ops/crypto_rsa_openssl.c b/src/lib/crypt_ops/crypto_rsa_openssl.c new file mode 100644 index 0000000000..df81c963eb --- /dev/null +++ b/src/lib/crypt_ops/crypto_rsa_openssl.c @@ -0,0 +1,590 @@ +/* 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_rsa.c + * \brief OpenSSL implementations of our RSA code. + **/ + +#include "lib/crypt_ops/compat_openssl.h" +#include "lib/crypt_ops/crypto_rsa.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/ctime/di_ops.h" +#include "lib/log/util_bug.h" +#include "lib/fs/files.h" + +DISABLE_GCC_WARNING(redundant-decls) + +#include <openssl/err.h> +#include <openssl/rsa.h> +#include <openssl/pem.h> +#include <openssl/evp.h> +#include <openssl/engine.h> +#include <openssl/rand.h> +#include <openssl/bn.h> +#include <openssl/conf.h> + +ENABLE_GCC_WARNING(redundant-decls) + +#include "lib/log/log.h" +#include "lib/encoding/binascii.h" + +#include <string.h> + +/** Declaration for crypto_pk_t structure. */ +struct crypto_pk_t +{ + int refs; /**< reference count, so we don't have to copy keys */ + RSA *key; /**< The key itself */ +}; + +/** Return true iff <b>key</b> contains the private-key portion of the RSA + * key. */ +int +crypto_pk_key_is_private(const crypto_pk_t *k) +{ +#ifdef OPENSSL_1_1_API + if (!k || !k->key) + return 0; + + const BIGNUM *p, *q; + RSA_get0_factors(k->key, &p, &q); + return p != NULL; /* XXX/yawning: Should we check q? */ +#else /* !(defined(OPENSSL_1_1_API)) */ + return k && k->key && k->key->p; +#endif /* defined(OPENSSL_1_1_API) */ +} + +/** used by tortls.c: wrap an RSA* in a crypto_pk_t. Takes ownership of + * its argument. */ +crypto_pk_t * +crypto_new_pk_from_openssl_rsa_(RSA *rsa) +{ + crypto_pk_t *env; + tor_assert(rsa); + env = tor_malloc(sizeof(crypto_pk_t)); + env->refs = 1; + env->key = rsa; + return env; +} + +/** Helper, used by tor-gencert.c. Return a copy of the private RSA from a + * crypto_pk_t. */ +RSA * +crypto_pk_get_openssl_rsa_(crypto_pk_t *env) +{ + return RSAPrivateKey_dup(env->key); +} + +/** used by tortls.c: get an equivalent EVP_PKEY* for a crypto_pk_t. Iff + * private is set, include the private-key portion of the key. Return a valid + * pointer on success, and NULL on failure. */ +MOCK_IMPL(EVP_PKEY *, +crypto_pk_get_openssl_evp_pkey_,(crypto_pk_t *env, int private)) +{ + RSA *key = NULL; + EVP_PKEY *pkey = NULL; + tor_assert(env->key); + if (private) { + if (!(key = RSAPrivateKey_dup(env->key))) + goto error; + } else { + if (!(key = RSAPublicKey_dup(env->key))) + goto error; + } + if (!(pkey = EVP_PKEY_new())) + goto error; + if (!(EVP_PKEY_assign_RSA(pkey, key))) + goto error; + return pkey; + error: + if (pkey) + EVP_PKEY_free(pkey); + if (key) + RSA_free(key); + return NULL; +} + +/** Allocate and return storage for a public key. The key itself will not yet + * be set. + */ +MOCK_IMPL(crypto_pk_t *, +crypto_pk_new,(void)) +{ + RSA *rsa; + + rsa = RSA_new(); + tor_assert(rsa); + return crypto_new_pk_from_openssl_rsa_(rsa); +} + +/** Release a reference to an asymmetric key; when all the references + * are released, free the key. + */ +void +crypto_pk_free_(crypto_pk_t *env) +{ + if (!env) + return; + + if (--env->refs > 0) + return; + tor_assert(env->refs == 0); + + if (env->key) + RSA_free(env->key); + + tor_free(env); +} + +/** Generate a <b>bits</b>-bit new public/private keypair in <b>env</b>. + * Return 0 on success, -1 on failure. + */ +MOCK_IMPL(int, +crypto_pk_generate_key_with_bits,(crypto_pk_t *env, int bits)) +{ + tor_assert(env); + + if (env->key) { + RSA_free(env->key); + env->key = NULL; + } + + { + BIGNUM *e = BN_new(); + RSA *r = NULL; + if (!e) + goto done; + if (! BN_set_word(e, TOR_RSA_EXPONENT)) + goto done; + r = RSA_new(); + if (!r) + goto done; + if (RSA_generate_key_ex(r, bits, e, NULL) == -1) + goto done; + + env->key = r; + r = NULL; + done: + if (e) + BN_clear_free(e); + if (r) + RSA_free(r); + } + + if (!env->key) { + crypto_openssl_log_errors(LOG_WARN, "generating RSA key"); + return -1; + } + + return 0; +} + +/** Return true if <b>env</b> has a valid key; false otherwise. + */ +int +crypto_pk_is_valid_private_key(const crypto_pk_t *env) +{ + int r; + tor_assert(env); + + r = RSA_check_key(env->key); + if (r <= 0) { + crypto_openssl_log_errors(LOG_WARN,"checking RSA key"); + return 0; + } else { + return 1; + } +} + +/** Return true iff <b>env</b> contains a public key whose public exponent + * equals TOR_RSA_EXPONENT. + */ +int +crypto_pk_public_exponent_ok(const crypto_pk_t *env) +{ + tor_assert(env); + tor_assert(env->key); + + const BIGNUM *e; + +#ifdef OPENSSL_1_1_API + const BIGNUM *n, *d; + RSA_get0_key(env->key, &n, &e, &d); +#else + e = env->key->e; +#endif /* defined(OPENSSL_1_1_API) */ + return BN_is_word(e, TOR_RSA_EXPONENT); +} + +/** Compare the public-key components of a and b. Return less than 0 + * if a\<b, 0 if a==b, and greater than 0 if a\>b. A NULL key is + * considered to be less than all non-NULL keys, and equal to itself. + * + * Note that this may leak information about the keys through timing. + */ +int +crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b) +{ + int result; + char a_is_non_null = (a != NULL) && (a->key != NULL); + char b_is_non_null = (b != NULL) && (b->key != NULL); + char an_argument_is_null = !a_is_non_null | !b_is_non_null; + + result = tor_memcmp(&a_is_non_null, &b_is_non_null, sizeof(a_is_non_null)); + if (an_argument_is_null) + return result; + + const BIGNUM *a_n, *a_e; + const BIGNUM *b_n, *b_e; + +#ifdef OPENSSL_1_1_API + const BIGNUM *a_d, *b_d; + RSA_get0_key(a->key, &a_n, &a_e, &a_d); + RSA_get0_key(b->key, &b_n, &b_e, &b_d); +#else + a_n = a->key->n; + a_e = a->key->e; + b_n = b->key->n; + b_e = b->key->e; +#endif /* defined(OPENSSL_1_1_API) */ + + tor_assert(a_n != NULL && a_e != NULL); + tor_assert(b_n != NULL && b_e != NULL); + + result = BN_cmp(a_n, b_n); + if (result) + return result; + return BN_cmp(a_e, b_e); +} + +/** Return the size of the public key modulus in <b>env</b>, in bytes. */ +size_t +crypto_pk_keysize(const crypto_pk_t *env) +{ + tor_assert(env); + tor_assert(env->key); + + return (size_t) RSA_size((RSA*)env->key); +} + +/** Return the size of the public key modulus of <b>env</b>, in bits. */ +int +crypto_pk_num_bits(crypto_pk_t *env) +{ + tor_assert(env); + tor_assert(env->key); + +#ifdef OPENSSL_1_1_API + /* It's so stupid that there's no other way to check that n is valid + * before calling RSA_bits(). + */ + const BIGNUM *n, *e, *d; + RSA_get0_key(env->key, &n, &e, &d); + tor_assert(n != NULL); + + return RSA_bits(env->key); +#else /* !(defined(OPENSSL_1_1_API)) */ + tor_assert(env->key->n); + return BN_num_bits(env->key->n); +#endif /* defined(OPENSSL_1_1_API) */ +} + +/** Increase the reference count of <b>env</b>, and return it. + */ +crypto_pk_t * +crypto_pk_dup_key(crypto_pk_t *env) +{ + tor_assert(env); + tor_assert(env->key); + + env->refs++; + return env; +} + +/** Replace dest with src (private key only). (Dest must have a refcount + * of 1) + */ +void +crypto_pk_assign_private(crypto_pk_t *dest, const crypto_pk_t *src) +{ + tor_assert(dest); + tor_assert(dest->refs == 1); + tor_assert(src); + RSA_free(dest->key); + dest->key = RSAPrivateKey_dup(src->key); +} + +/** Replace dest with src (public key only). (Dest must have a refcount + * of 1) + */ +void +crypto_pk_assign_public(crypto_pk_t *dest, const crypto_pk_t *src) +{ + tor_assert(dest); + tor_assert(dest->refs == 1); + tor_assert(src); + RSA_free(dest->key); + dest->key = RSAPublicKey_dup(src->key); +} + +/** Make a real honest-to-goodness copy of <b>env</b>, and return it. + * Returns NULL on failure. */ +crypto_pk_t * +crypto_pk_copy_full(crypto_pk_t *env) +{ + RSA *new_key; + int privatekey = 0; + tor_assert(env); + tor_assert(env->key); + + if (crypto_pk_key_is_private(env)) { + new_key = RSAPrivateKey_dup(env->key); + privatekey = 1; + } else { + new_key = RSAPublicKey_dup(env->key); + } + if (!new_key) { + /* LCOV_EXCL_START + * + * We can't cause RSA*Key_dup() to fail, so we can't really test this. + */ + log_err(LD_CRYPTO, "Unable to duplicate a %s key: openssl failed.", + privatekey?"private":"public"); + crypto_openssl_log_errors(LOG_ERR, + privatekey ? "Duplicating a private key" : + "Duplicating a public key"); + tor_fragile_assert(); + return NULL; + /* LCOV_EXCL_STOP */ + } + + return crypto_new_pk_from_openssl_rsa_(new_key); +} + +/** Encrypt <b>fromlen</b> bytes from <b>from</b> with the public key + * in <b>env</b>, using the padding method <b>padding</b>. On success, + * write the result to <b>to</b>, and return the number of bytes + * written. On failure, return -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +int +crypto_pk_public_encrypt(crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen, int padding) +{ + int r; + tor_assert(env); + tor_assert(from); + tor_assert(to); + tor_assert(fromlen<INT_MAX); + tor_assert(tolen >= crypto_pk_keysize(env)); + + r = RSA_public_encrypt((int)fromlen, + (unsigned char*)from, (unsigned char*)to, + env->key, crypto_get_rsa_padding(padding)); + if (r<0) { + crypto_openssl_log_errors(LOG_WARN, "performing RSA encryption"); + return -1; + } + return r; +} + +/** Decrypt <b>fromlen</b> bytes from <b>from</b> with the private key + * in <b>env</b>, using the padding method <b>padding</b>. On success, + * write the result to <b>to</b>, and return the number of bytes + * written. On failure, return -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +int +crypto_pk_private_decrypt(crypto_pk_t *env, char *to, + size_t tolen, + const char *from, size_t fromlen, + int padding, int warnOnFailure) +{ + int r; + tor_assert(env); + tor_assert(from); + tor_assert(to); + tor_assert(env->key); + tor_assert(fromlen<INT_MAX); + tor_assert(tolen >= crypto_pk_keysize(env)); + if (!crypto_pk_key_is_private(env)) + /* Not a private key */ + return -1; + + r = RSA_private_decrypt((int)fromlen, + (unsigned char*)from, (unsigned char*)to, + env->key, crypto_get_rsa_padding(padding)); + + if (r<0) { + crypto_openssl_log_errors(warnOnFailure?LOG_WARN:LOG_DEBUG, + "performing RSA decryption"); + return -1; + } + return r; +} + +/** Check the signature in <b>from</b> (<b>fromlen</b> bytes long) with the + * public key in <b>env</b>, using PKCS1 padding. On success, write the + * signed data to <b>to</b>, and return the number of bytes written. + * On failure, return -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +MOCK_IMPL(int, +crypto_pk_public_checksig,(const crypto_pk_t *env, char *to, + size_t tolen, + const char *from, size_t fromlen)) +{ + int r; + tor_assert(env); + tor_assert(from); + tor_assert(to); + tor_assert(fromlen < INT_MAX); + tor_assert(tolen >= crypto_pk_keysize(env)); + r = RSA_public_decrypt((int)fromlen, + (unsigned char*)from, (unsigned char*)to, + env->key, RSA_PKCS1_PADDING); + + if (r<0) { + crypto_openssl_log_errors(LOG_INFO, "checking RSA signature"); + return -1; + } + return r; +} + +/** Sign <b>fromlen</b> bytes of data from <b>from</b> with the private key in + * <b>env</b>, using PKCS1 padding. On success, write the signature to + * <b>to</b>, and return the number of bytes written. On failure, return + * -1. + * + * <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be + * at least the length of the modulus of <b>env</b>. + */ +int +crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen, + const char *from, size_t fromlen) +{ + int r; + tor_assert(env); + tor_assert(from); + tor_assert(to); + tor_assert(fromlen < INT_MAX); + tor_assert(tolen >= crypto_pk_keysize(env)); + if (!crypto_pk_key_is_private(env)) + /* Not a private key */ + return -1; + + r = RSA_private_encrypt((int)fromlen, + (unsigned char*)from, (unsigned char*)to, + (RSA*)env->key, RSA_PKCS1_PADDING); + if (r<0) { + crypto_openssl_log_errors(LOG_WARN, "generating RSA signature"); + return -1; + } + return r; +} + +/** ASN.1-encode the public portion of <b>pk</b> into <b>dest</b>. + * Return -1 on error, or the number of characters used on success. + */ +int +crypto_pk_asn1_encode(const crypto_pk_t *pk, char *dest, size_t dest_len) +{ + int len; + unsigned char *buf = NULL; + + len = i2d_RSAPublicKey(pk->key, &buf); + if (len < 0 || buf == NULL) + return -1; + + if ((size_t)len > dest_len || dest_len > SIZE_T_CEILING) { + OPENSSL_free(buf); + return -1; + } + /* We don't encode directly into 'dest', because that would be illegal + * type-punning. (C99 is smarter than me, C99 is smarter than me...) + */ + memcpy(dest,buf,len); + OPENSSL_free(buf); + return len; +} + +/** Decode an ASN.1-encoded public key from <b>str</b>; return the result on + * success and NULL on failure. + */ +crypto_pk_t * +crypto_pk_asn1_decode(const char *str, size_t len) +{ + RSA *rsa; + unsigned char *buf; + const unsigned char *cp; + cp = buf = tor_malloc(len); + memcpy(buf,str,len); + rsa = d2i_RSAPublicKey(NULL, &cp, len); + tor_free(buf); + if (!rsa) { + crypto_openssl_log_errors(LOG_WARN,"decoding public key"); + return NULL; + } + return crypto_new_pk_from_openssl_rsa_(rsa); +} + +/** ASN.1-encode the private portion of <b>pk</b> into <b>dest</b>. + * Return -1 on error, or the number of characters used on success. + */ +int +crypto_pk_asn1_encode_private(const crypto_pk_t *pk, char *dest, + size_t dest_len) +{ + int len; + unsigned char *buf = NULL; + + len = i2d_RSAPrivateKey(pk->key, &buf); + if (len < 0 || buf == NULL) + return -1; + + if ((size_t)len > dest_len || dest_len > SIZE_T_CEILING) { + OPENSSL_free(buf); + return -1; + } + /* We don't encode directly into 'dest', because that would be illegal + * type-punning. (C99 is smarter than me, C99 is smarter than me...) + */ + memcpy(dest,buf,len); + OPENSSL_free(buf); + return len; +} + +/** Decode an ASN.1-encoded private key from <b>str</b>; return the result on + * success and NULL on failure. + */ +crypto_pk_t * +crypto_pk_asn1_decode_private(const char *str, size_t len) +{ + RSA *rsa; + unsigned char *buf; + const unsigned char *cp; + cp = buf = tor_malloc(len); + memcpy(buf,str,len); + rsa = d2i_RSAPrivateKey(NULL, &cp, len); + tor_free(buf); + if (!rsa) { + crypto_openssl_log_errors(LOG_WARN,"decoding public key"); + return NULL; + } + crypto_pk_t *result = crypto_new_pk_from_openssl_rsa_(rsa); + if (! crypto_pk_is_valid_private_key(result)) { + crypto_pk_free(result); + return NULL; + } + return result; +} diff --git a/src/lib/crypt_ops/crypto_s2k.c b/src/lib/crypt_ops/crypto_s2k.c index ab91d92f0e..e0b2f40bb3 100644 --- a/src/lib/crypt_ops/crypto_s2k.c +++ b/src/lib/crypt_ops/crypto_s2k.c @@ -12,7 +12,7 @@ #define CRYPTO_S2K_PRIVATE -#include "lib/crypt_ops/crypto.h" +#include "lib/crypt_ops/crypto_cipher.h" #include "lib/crypt_ops/crypto_digest.h" #include "lib/crypt_ops/crypto_hkdf.h" #include "lib/crypt_ops/crypto_rand.h" @@ -20,8 +20,14 @@ #include "lib/crypt_ops/crypto_util.h" #include "lib/ctime/di_ops.h" #include "lib/log/util_bug.h" +#include "lib/intmath/cmp.h" +#ifdef ENABLE_OPENSSL #include <openssl/evp.h> +#endif +#ifdef ENABLE_NSS +#include <pk11pub.h> +#endif #if defined(HAVE_LIBSCRYPT_H) && defined(HAVE_LIBSCRYPT_SCRYPT) #define HAVE_SCRYPT @@ -271,6 +277,7 @@ secret_to_key_compute_key(uint8_t *key_out, size_t key_out_len, log_iters = spec[spec_len-1]; if (log_iters > 31) return S2K_BAD_PARAMS; +#ifdef ENABLE_OPENSSL rv = PKCS5_PBKDF2_HMAC_SHA1(secret, (int)secret_len, spec, (int)spec_len-1, (1<<log_iters), @@ -278,6 +285,47 @@ secret_to_key_compute_key(uint8_t *key_out, size_t key_out_len, if (rv < 0) return S2K_FAILED; return (int)key_out_len; +#else + SECItem passItem = { .type = siBuffer, + .data = (unsigned char *) secret, + .len = (int)secret_len }; + SECItem saltItem = { .type = siBuffer, + .data = (unsigned char *) spec, + .len = (int)spec_len - 1 }; + SECAlgorithmID *alg = NULL; + PK11SymKey *key = NULL; + + rv = S2K_FAILED; + alg = PK11_CreatePBEV2AlgorithmID( + SEC_OID_PKCS5_PBKDF2, SEC_OID_HMAC_SHA1, SEC_OID_HMAC_SHA1, + (int)key_out_len, (1<<log_iters), &saltItem); + if (alg == NULL) + return S2K_FAILED; + + key = PK11_PBEKeyGen(NULL /* slot */, + alg, + &passItem, + false, + NULL); + + SECStatus st = PK11_ExtractKeyValue(key); + if (st != SECSuccess) + goto nss_pbkdf_err; + + const SECItem *iptr = PK11_GetKeyData(key); + if (iptr == NULL) + goto nss_pbkdf_err; + + rv = MIN((int)iptr->len, (int)key_out_len); + memcpy(key_out, iptr->data, rv); + + nss_pbkdf_err: + if (key) + PK11_FreeSymKey(key); + if (alg) + SECOID_DestroyAlgorithmID(alg, PR_TRUE); + return rv; +#endif } case S2K_TYPE_SCRYPT: { diff --git a/src/lib/crypt_ops/crypto_util.c b/src/lib/crypt_ops/crypto_util.c index 79988c6a91..7af80291ef 100644 --- a/src/lib/crypt_ops/crypto_util.c +++ b/src/lib/crypt_ops/crypto_util.c @@ -10,7 +10,6 @@ * \brief Common cryptographic utilities. **/ -#ifndef CRYPTO_UTIL_PRIVATE #define CRYPTO_UTIL_PRIVATE #include "lib/crypt_ops/crypto_util.h" @@ -24,12 +23,14 @@ #include <wincrypt.h> #endif /* defined(_WIN32) */ -DISABLE_GCC_WARNING(redundant-decls) +#include <stdlib.h> +#ifdef ENABLE_OPENSSL +DISABLE_GCC_WARNING(redundant-decls) #include <openssl/err.h> #include <openssl/crypto.h> - ENABLE_GCC_WARNING(redundant-decls) +#endif #include "lib/log/log.h" #include "lib/log/util_bug.h" @@ -105,25 +106,3 @@ memwipe(void *mem, uint8_t byte, size_t sz) **/ memset(mem, byte, sz); } - -/** Log all pending crypto errors at level <b>severity</b>. Use - * <b>doing</b> to describe our current activities. - */ -void -crypto_log_errors(int severity, const char *doing) -{ - unsigned long err; - const char *msg, *lib, *func; - while ((err = ERR_get_error()) != 0) { - msg = (const char*)ERR_reason_error_string(err); - lib = (const char*)ERR_lib_error_string(err); - func = (const char*)ERR_func_error_string(err); - if (!msg) msg = "(null)"; - if (!lib) lib = "(null)"; - if (!func) func = "(null)"; - if (BUG(!doing)) doing = "(null)"; - tor_log(severity, LD_CRYPTO, "crypto error while %s: %s (in %s:%s)", - doing, msg, lib, func); - } -} -#endif /* !defined(CRYPTO_UTIL_PRIVATE) */ diff --git a/src/lib/crypt_ops/crypto_util.h b/src/lib/crypt_ops/crypto_util.h index 3ce34e6f23..e032263225 100644 --- a/src/lib/crypt_ops/crypto_util.h +++ b/src/lib/crypt_ops/crypto_util.h @@ -18,13 +18,4 @@ /** OpenSSL-based utility functions. */ void memwipe(void *mem, uint8_t byte, size_t sz); -/** Log utility function */ -void crypto_log_errors(int severity, const char *doing); - -#ifdef CRYPTO_UTIL_PRIVATE -#ifdef TOR_UNIT_TESTS -#endif /* defined(TOR_UNIT_TESTS) */ -#endif /* defined(CRYPTO_UTIL_PRIVATE) */ - #endif /* !defined(TOR_CRYPTO_UTIL_H) */ - diff --git a/src/lib/crypt_ops/include.am b/src/lib/crypt_ops/include.am index 017d7956d0..1022096fdc 100644 --- a/src/lib/crypt_ops/include.am +++ b/src/lib/crypt_ops/include.am @@ -6,16 +6,15 @@ noinst_LIBRARIES += src/lib/libtor-crypt-ops-testing.a endif src_lib_libtor_crypt_ops_a_SOURCES = \ - src/lib/crypt_ops/aes.c \ - src/lib/crypt_ops/crypto.c \ + src/lib/crypt_ops/crypto_cipher.c \ src/lib/crypt_ops/crypto_curve25519.c \ src/lib/crypt_ops/crypto_dh.c \ src/lib/crypt_ops/crypto_digest.c \ src/lib/crypt_ops/crypto_ed25519.c \ src/lib/crypt_ops/crypto_format.c \ src/lib/crypt_ops/crypto_hkdf.c \ + src/lib/crypt_ops/crypto_init.c \ src/lib/crypt_ops/crypto_ope.c \ - src/lib/crypt_ops/crypto_openssl_mgt.c \ src/lib/crypt_ops/crypto_pwbox.c \ src/lib/crypt_ops/crypto_rand.c \ src/lib/crypt_ops/crypto_rsa.c \ @@ -23,10 +22,31 @@ src_lib_libtor_crypt_ops_a_SOURCES = \ src/lib/crypt_ops/crypto_util.c \ src/lib/crypt_ops/digestset.c +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 \ + src/lib/crypt_ops/crypto_rsa_nss.c +else +src_lib_libtor_crypt_ops_a_SOURCES += \ + src/lib/crypt_ops/aes_openssl.c \ + src/lib/crypt_ops/crypto_rsa_openssl.c +endif + +if USE_OPENSSL +src_lib_libtor_crypt_ops_a_SOURCES += \ + src/lib/crypt_ops/crypto_dh_openssl.c \ + src/lib/crypt_ops/crypto_openssl_mgt.c +endif + +src_lib_libtor_crypt_ops_a_CFLAGS = $(AM_CFLAGS) $(TOR_CFLAGS_CRYPTLIB) + src_lib_libtor_crypt_ops_testing_a_SOURCES = \ $(src_lib_libtor_crypt_ops_a_SOURCES) src_lib_libtor_crypt_ops_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) -src_lib_libtor_crypt_ops_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) +src_lib_libtor_crypt_ops_testing_a_CFLAGS = \ + $(AM_CFLAGS) $(TOR_CFLAGS_CRYPTLIB) $(TEST_CFLAGS) noinst_HEADERS += \ src/lib/crypt_ops/aes.h \ @@ -36,8 +56,10 @@ noinst_HEADERS += \ src/lib/crypt_ops/crypto_digest.h \ src/lib/crypt_ops/crypto_ed25519.h \ src/lib/crypt_ops/crypto_format.h \ - src/lib/crypt_ops/crypto.h \ + src/lib/crypt_ops/crypto_cipher.h \ src/lib/crypt_ops/crypto_hkdf.h \ + src/lib/crypt_ops/crypto_init.h \ + src/lib/crypt_ops/crypto_nss_mgt.h \ src/lib/crypt_ops/crypto_openssl_mgt.h \ src/lib/crypt_ops/crypto_ope.h \ src/lib/crypt_ops/crypto_pwbox.h \ diff --git a/src/lib/encoding/.may_include b/src/lib/encoding/.may_include index 92231b5133..7c2ef36929 100644 --- a/src/lib/encoding/.may_include +++ b/src/lib/encoding/.may_include @@ -1,5 +1,6 @@ orconfig.h lib/cc/*.h +lib/ctime/*.h lib/encoding/*.h lib/intmath/*.h lib/log/*.h diff --git a/src/lib/encoding/include.am b/src/lib/encoding/include.am index 868e531b6b..2d2aa3988a 100644 --- a/src/lib/encoding/include.am +++ b/src/lib/encoding/include.am @@ -9,6 +9,7 @@ src_lib_libtor_encoding_a_SOURCES = \ src/lib/encoding/confline.c \ src/lib/encoding/cstring.c \ src/lib/encoding/keyval.c \ + src/lib/encoding/pem.c \ src/lib/encoding/time_fmt.c src_lib_libtor_encoding_testing_a_SOURCES = \ @@ -21,4 +22,5 @@ noinst_HEADERS += \ src/lib/encoding/confline.h \ src/lib/encoding/cstring.h \ src/lib/encoding/keyval.h \ + src/lib/encoding/pem.h \ src/lib/encoding/time_fmt.h diff --git a/src/lib/encoding/pem.c b/src/lib/encoding/pem.c new file mode 100644 index 0000000000..0d4a814f6f --- /dev/null +++ b/src/lib/encoding/pem.c @@ -0,0 +1,106 @@ +/* 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 pem.c + * + * \brief Implement a trivial version of PEM encoding, for use with NSS. + * + * We deliberately do not support any encryption here. + **/ + +#include "orconfig.h" + +#include "lib/encoding/pem.h" + +#include "lib/ctime/di_ops.h" +#include "lib/encoding/binascii.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/string/printf.h" +#include "lib/string/util_string.h" + +#include <string.h> + +/** + * Return the length of a <b>src_len</b>-byte object when tagged with + * <b>objtype</b> and PEM-encoded. Includes terminating NUL. + */ +size_t +pem_encoded_size(size_t src_len, const char *objtype) +{ + return + strlen("-----BEGIN -----\n") + + strlen("-----END -----\n") + + strlen(objtype) * 2 + + base64_encode_size(src_len, BASE64_ENCODE_MULTILINE) + + 1; +} + +/** + * PEM-encode the <b>srclen</b>-byte object at <b>src</b> into the + * <b>destlen<\b>-byte buffer at <b>dest</b>, tagging it with <b>objtype</b>. + * Return 0 on success and -1 on failure. + */ +int +pem_encode(char *dest, size_t destlen, const uint8_t *src, size_t srclen, + const char *objtype) +{ + if (tor_snprintf(dest, destlen, "-----BEGIN %s-----\n", objtype) < 0) + return -1; + + size_t offset = strlen(dest); + + int n = base64_encode(dest + offset, destlen - offset, + (const char *)src, srclen, BASE64_ENCODE_MULTILINE); + if (n < 0) + return -1; + offset += n; + if (BUG(offset > destlen)) + return -1; + + if (tor_snprintf(dest + offset, destlen - offset, + "-----END %s-----\n", objtype) < 0) + return -1; + + tor_assert(strlen(dest) + 1 <= pem_encoded_size(srclen, objtype)); + return 0; +} + +/** + * Given a PEM-encoded block of size <b>srclen</b> in <b>src</b>, if it has + * object type <b>objtype</b>, decode it into the <b>destlen</b>-byte buffer + * at <b>dest</b>. Return the number of characters decoded on success, or -1 + * on failure. + */ +int +pem_decode(uint8_t *dest, size_t destlen, const char *src, size_t srclen, + const char *objtype) +{ + const char *eos = src + srclen; + + src = eat_whitespace_eos(src, eos); + + char *tag = NULL; + tor_asprintf(&tag, "-----BEGIN %s-----\n", objtype); + if ((size_t)(eos-src) < strlen(tag) || fast_memneq(src, tag, strlen(tag))) { + tor_free(tag); + return -1; + } + src += strlen(tag); + tor_free(tag); + + // NOTE lack of trailing \n. We do not enforce its presence. + tor_asprintf(&tag, "\n-----END %s-----", objtype); + const char *end_of_base64 = tor_memstr(src, eos-src, tag); + tor_free(tag); + if (end_of_base64 == NULL) + return -1; + + /* Should we actually allow extra stuff at the end? */ + + return base64_decode((char*)dest, destlen, src, end_of_base64-src); +} diff --git a/src/lib/encoding/pem.h b/src/lib/encoding/pem.h new file mode 100644 index 0000000000..ba21228848 --- /dev/null +++ b/src/lib/encoding/pem.h @@ -0,0 +1,26 @@ +/* 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 pem.h + * + * \brief Header for pem.c + **/ + +#ifndef TOR_PEM_H +#define TOR_PEM_H + +#include "orconfig.h" +#include <stddef.h> +#include "lib/cc/torint.h" + +size_t pem_encoded_size(size_t src_len, const char *objtype); +int pem_encode(char *dest, size_t destlen, const uint8_t *src, size_t srclen, + const char *objtype); +int pem_decode(uint8_t *dest, size_t destlen, const char *src, size_t srclen, + const char *objtype); + +#endif diff --git a/src/lib/include.libdonna.am b/src/lib/include.libdonna.am index 5b92dc58a0..60a3f5097e 100644 --- a/src/lib/include.libdonna.am +++ b/src/lib/include.libdonna.am @@ -1,4 +1,4 @@ -src_lib_libcurve25519_donna_a_CFLAGS= +src_lib_libcurve25519_donna_a_CFLAGS=$(TOR_CFLAGS_CRYPTO) if BUILD_CURVE25519_DONNA src_lib_libcurve25519_donna_a_SOURCES=\ diff --git a/src/lib/process/daemon.c b/src/lib/process/daemon.c index 671579838e..c64affd8b9 100644 --- a/src/lib/process/daemon.c +++ b/src/lib/process/daemon.c @@ -41,15 +41,16 @@ static int daemon_filedes[2]; /** Start putting the process into daemon mode: fork and drop all resources * except standard fds. The parent process never returns, but stays around * until finish_daemon is called. (Note: it's safe to call this more - * than once: calls after the first are ignored.) + * than once: calls after the first are ignored.) Return true if we actually + * forked and this is the child; false otherwise. */ -void +int start_daemon(void) { pid_t pid; if (start_daemon_called) - return; + return 0; start_daemon_called = 1; if (pipe(daemon_filedes)) { @@ -80,6 +81,7 @@ start_daemon(void) exit(0); // exit ok: during daemonize, daemonizing. else exit(1); /* child reported error. exit ok: daemonize failed. */ + return 0; // LCOV_EXCL_LINE unreachable } else { /* Child */ close(daemon_filedes[0]); /* we only write */ @@ -95,22 +97,23 @@ start_daemon(void) } set_main_thread(); /* We are now the main thread. */ - return; + return 1; } } /** Finish putting the process into daemon mode: drop standard fds, and tell * the parent process to exit. (Note: it's safe to call this more than once: * calls after the first are ignored. Calls start_daemon first if it hasn't - * been called already.) + * been called already.) Return true if we actually did a fork; false if we + * didn't. */ -void +int finish_daemon(const char *desired_cwd) { int nullfd; char c = '.'; if (finish_daemon_called) - return; + return 0; if (!start_daemon_called) start_daemon(); finish_daemon_called = 1; @@ -149,16 +152,20 @@ finish_daemon(const char *desired_cwd) log_err(LD_GENERAL,"write failed. Exiting."); } close(daemon_filedes[1]); + + return 0; } #else /* !(!defined(_WIN32)) */ /* defined(_WIN32) */ -void +int start_daemon(void) { + return 0; } -void +int finish_daemon(const char *cp) { (void)cp; + return 0; } #endif /* !defined(_WIN32) */ diff --git a/src/lib/process/daemon.h b/src/lib/process/daemon.h index 1f26e92221..c3b78029af 100644 --- a/src/lib/process/daemon.h +++ b/src/lib/process/daemon.h @@ -11,7 +11,7 @@ #ifndef TOR_DAEMON_H #define TOR_DAEMON_H -void start_daemon(void); -void finish_daemon(const char *desired_cwd); +int start_daemon(void); +int finish_daemon(const char *desired_cwd); #endif diff --git a/src/lib/tls/include.am b/src/lib/tls/include.am index b3b013f4dd..b25e2e16bf 100644 --- a/src/lib/tls/include.am +++ b/src/lib/tls/include.am @@ -7,14 +7,32 @@ endif src_lib_libtor_tls_a_SOURCES = \ src/lib/tls/buffers_tls.c \ - src/lib/tls/tortls.c + src/lib/tls/tortls.c \ + src/lib/tls/x509.c + +if USE_NSS +src_lib_libtor_tls_a_SOURCES += \ + src/lib/tls/tortls_nss.c \ + src/lib/tls/x509_nss.c +else +src_lib_libtor_tls_a_SOURCES += \ + src/lib/tls/tortls_openssl.c \ + src/lib/tls/x509_openssl.c +endif + +src_lib_libtor_tls_a_CFLAGS = $(AM_CFLAGS) $(TOR_CFLAGS_CRYPTLIB) src_lib_libtor_tls_testing_a_SOURCES = \ $(src_lib_libtor_tls_a_SOURCES) src_lib_libtor_tls_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) -src_lib_libtor_tls_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) +src_lib_libtor_tls_testing_a_CFLAGS = \ + $(AM_CFLAGS) $(TOR_CFLAGS_CRYPTLIB) $(TEST_CFLAGS) noinst_HEADERS += \ src/lib/tls/ciphers.inc \ src/lib/tls/buffers_tls.h \ - src/lib/tls/tortls.h + src/lib/tls/tortls.h \ + src/lib/tls/tortls_internal.h \ + src/lib/tls/tortls_st.h \ + src/lib/tls/x509.h \ + src/lib/tls/x509_internal.h diff --git a/src/lib/tls/tortls.c b/src/lib/tls/tortls.c index 7135820d2f..3ae3a1a096 100644 --- a/src/lib/tls/tortls.c +++ b/src/lib/tls/tortls.c @@ -3,168 +3,25 @@ * Copyright (c) 2007-2018, The Tor Project, Inc. */ /* See LICENSE for licensing information */ -/** - * \file tortls.c - * \brief Wrapper functions to present a consistent interface to - * TLS, SSL, and X.509 functions from OpenSSL. - **/ - -/* (Unlike other tor functions, these - * are prefixed with tor_ in order to avoid conflicting with OpenSSL - * functions and variables.) - */ - -#include "orconfig.h" - -#define TORTLS_PRIVATE -#define TORTLS_OPENSSL_PRIVATE - -#ifdef _WIN32 /*wrkard for dtls1.h >= 0.9.8m of "#include <winsock.h>"*/ - #include <winsock2.h> - #include <ws2tcpip.h> -#endif - -#include "lib/crypt_ops/crypto.h" -#include "lib/crypt_ops/crypto_rand.h" -#include "lib/crypt_ops/crypto_dh.h" -#include "lib/crypt_ops/crypto_util.h" - -/* Some versions of OpenSSL declare SSL_get_selected_srtp_profile twice in - * srtp.h. Suppress the GCC warning so we can build with -Wredundant-decl. */ -DISABLE_GCC_WARNING(redundant-decls) - -#include <openssl/opensslv.h> - -#ifdef OPENSSL_NO_EC -#error "We require OpenSSL with ECC support" -#endif - -#include <openssl/ssl.h> -#include <openssl/ssl3.h> -#include <openssl/err.h> -#include <openssl/tls1.h> -#include <openssl/asn1.h> -#include <openssl/bio.h> -#include <openssl/bn.h> -#include <openssl/rsa.h> - -ENABLE_GCC_WARNING(redundant-decls) - #define TORTLS_PRIVATE +#define TOR_X509_PRIVATE +#include "lib/tls/x509.h" +#include "lib/tls/x509_internal.h" #include "lib/tls/tortls.h" -#include "lib/log/log.h" +#include "lib/tls/tortls_st.h" +#include "lib/tls/tortls_internal.h" #include "lib/log/util_bug.h" -#include "lib/container/smartlist.h" -#include "lib/string/compat_string.h" -#include "lib/string/printf.h" -#include "lib/net/socket.h" #include "lib/intmath/cmp.h" -#include "lib/ctime/di_ops.h" -#include "lib/encoding/time_fmt.h" - -#include <stdlib.h> -#include <string.h> - -#include "lib/arch/bytes.h" - -#ifdef OPENSSL_1_1_API -#define X509_get_notBefore_const(cert) \ - X509_get0_notBefore(cert) -#define X509_get_notAfter_const(cert) \ - X509_get0_notAfter(cert) -#ifndef X509_get_notBefore -#define X509_get_notBefore(cert) \ - X509_getm_notBefore(cert) -#endif -#ifndef X509_get_notAfter -#define X509_get_notAfter(cert) \ - X509_getm_notAfter(cert) -#endif -#else /* ! OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) */ -#define X509_get_notBefore_const(cert) \ - ((const ASN1_TIME*) X509_get_notBefore((X509 *)cert)) -#define X509_get_notAfter_const(cert) \ - ((const ASN1_TIME*) X509_get_notAfter((X509 *)cert)) -#endif - -/* Copied from or.h */ -#define LEGAL_NICKNAME_CHARACTERS \ - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - -/** How long do identity certificates live? (sec) */ -#define IDENTITY_CERT_LIFETIME (365*24*60*60) - -#define ADDR(tls) (((tls) && (tls)->address) ? tls->address : "peer") - -#if OPENSSL_VERSION_NUMBER < OPENSSL_V(1,0,0,'f') -/* This is a version of OpenSSL before 1.0.0f. It does not have - * the CVE-2011-4576 fix, and as such it can't use RELEASE_BUFFERS and - * SSL3 safely at the same time. - */ -#define DISABLE_SSL3_HANDSHAKE -#endif /* OPENSSL_VERSION_NUMBER < OPENSSL_V(1,0,0,'f') */ +#include "lib/crypt_ops/crypto_rsa.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/net/socket.h" -/* We redefine these so that we can run correctly even if the vendor gives us - * a version of OpenSSL that does not match its header files. (Apple: I am - * looking at you.) - */ -#ifndef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION -#define SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION 0x00040000L -#endif -#ifndef SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION -#define SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION 0x0010 +#ifdef _WIN32 + #include <winsock2.h> + #include <ws2tcpip.h> #endif -/** Return values for tor_tls_classify_client_ciphers. - * - * @{ - */ -/** An error occurred when examining the client ciphers */ -#define CIPHERS_ERR -1 -/** The client cipher list indicates that a v1 handshake was in use. */ -#define CIPHERS_V1 1 -/** The client cipher list indicates that the client is using the v2 or the - * v3 handshake, but that it is (probably!) lying about what ciphers it - * supports */ -#define CIPHERS_V2 2 -/** The client cipher list indicates that the client is using the v2 or the - * v3 handshake, and that it is telling the truth about what ciphers it - * supports */ -#define CIPHERS_UNRESTRICTED 3 -/** @} */ - -/** The ex_data index in which we store a pointer to an SSL object's - * corresponding tor_tls_t object. */ -STATIC int tor_tls_object_ex_data_index = -1; - -/** Helper: Allocate tor_tls_object_ex_data_index. */ -STATIC void -tor_tls_allocate_tor_tls_object_ex_data_index(void) -{ - if (tor_tls_object_ex_data_index == -1) { - tor_tls_object_ex_data_index = - SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); - tor_assert(tor_tls_object_ex_data_index != -1); - } -} - -/** Helper: given a SSL* pointer, return the tor_tls_t object using that - * pointer. */ -STATIC tor_tls_t * -tor_tls_get_by_ssl(const SSL *ssl) -{ - tor_tls_t *result = SSL_get_ex_data(ssl, tor_tls_object_ex_data_index); - if (result) - tor_assert(result->magic == TOR_TLS_MAGIC); - return result; -} - -static void tor_tls_context_decref(tor_tls_context_t *ctx); -static void tor_tls_context_incref(tor_tls_context_t *ctx); - -static int check_cert_lifetime_internal(int severity, const X509 *cert, - time_t now, - int past_tolerance, int future_tolerance); +#include <time.h> /** Global TLS contexts. We keep them here because nobody else needs * to touch them. @@ -174,112 +31,18 @@ STATIC tor_tls_context_t *server_tls_context = NULL; STATIC tor_tls_context_t *client_tls_context = NULL; /**@}*/ -/** True iff tor_tls_init() has been called. */ -static int tls_library_is_initialized = 0; - -/* Module-internal error codes. */ -#define TOR_TLS_SYSCALL_ (MIN_TOR_TLS_ERROR_VAL_ - 2) -#define TOR_TLS_ZERORETURN_ (MIN_TOR_TLS_ERROR_VAL_ - 1) - -/** Write a description of the current state of <b>tls</b> into the - * <b>sz</b>-byte buffer at <b>buf</b>. */ -void -tor_tls_get_state_description(tor_tls_t *tls, char *buf, size_t sz) -{ - const char *ssl_state; - const char *tortls_state; - - if (PREDICT_UNLIKELY(!tls || !tls->ssl)) { - strlcpy(buf, "(No SSL object)", sz); - return; - } - - ssl_state = SSL_state_string_long(tls->ssl); - switch (tls->state) { -#define CASE(st) case TOR_TLS_ST_##st: tortls_state = " in "#st ; break - CASE(HANDSHAKE); - CASE(OPEN); - CASE(GOTCLOSE); - CASE(SENTCLOSE); - CASE(CLOSED); - CASE(RENEGOTIATE); -#undef CASE - case TOR_TLS_ST_BUFFEREVENT: - tortls_state = ""; - break; - default: - tortls_state = " in unknown TLS state"; - break; - } - - tor_snprintf(buf, sz, "%s%s", ssl_state, tortls_state); -} - -/** Log a single error <b>err</b> as returned by ERR_get_error(), which was - * received while performing an operation <b>doing</b> on <b>tls</b>. Log - * the message at <b>severity</b>, in log domain <b>domain</b>. */ -void -tor_tls_log_one_error(tor_tls_t *tls, unsigned long err, - int severity, int domain, const char *doing) -{ - const char *state = NULL, *addr; - const char *msg, *lib, *func; - - state = (tls && tls->ssl)?SSL_state_string_long(tls->ssl):"---"; - - addr = tls ? tls->address : NULL; - - /* Some errors are known-benign, meaning they are the fault of the other - * side of the connection. The caller doesn't know this, so override the - * priority for those cases. */ - switch (ERR_GET_REASON(err)) { - case SSL_R_HTTP_REQUEST: - case SSL_R_HTTPS_PROXY_REQUEST: - case SSL_R_RECORD_LENGTH_MISMATCH: -#ifndef OPENSSL_1_1_API - case SSL_R_RECORD_TOO_LARGE: -#endif - case SSL_R_UNKNOWN_PROTOCOL: - case SSL_R_UNSUPPORTED_PROTOCOL: - severity = LOG_INFO; - break; - default: - break; - } - - msg = (const char*)ERR_reason_error_string(err); - lib = (const char*)ERR_lib_error_string(err); - func = (const char*)ERR_func_error_string(err); - if (!msg) msg = "(null)"; - if (!lib) lib = "(null)"; - if (!func) func = "(null)"; - if (doing) { - tor_log(severity, domain, "TLS error while %s%s%s: %s (in %s:%s:%s)", - doing, addr?" with ":"", addr?addr:"", - msg, lib, func, state); - } else { - tor_log(severity, domain, "TLS error%s%s: %s (in %s:%s:%s)", - addr?" with ":"", addr?addr:"", - msg, lib, func, state); - } -} - -/** Log all pending tls errors at level <b>severity</b> in log domain - * <b>domain</b>. Use <b>doing</b> to describe our current activities. +/** + * Return the appropriate TLS context. */ -STATIC void -tls_log_errors(tor_tls_t *tls, int severity, int domain, const char *doing) +tor_tls_context_t * +tor_tls_context_get(int is_server) { - unsigned long err; - - while ((err = ERR_get_error()) != 0) { - tor_tls_log_one_error(tls, err, severity, domain, doing); - } + return is_server ? server_tls_context : client_tls_context; } /** Convert an errno (or a WSAerrno on windows) into a TOR_TLS_* error * code. */ -STATIC int +int tor_errno_to_tls_error(int e) { switch (e) { @@ -297,575 +60,6 @@ tor_errno_to_tls_error(int e) } } -/** Given a TOR_TLS_* error code, return a string equivalent. */ -const char * -tor_tls_err_to_string(int err) -{ - if (err >= 0) - return "[Not an error.]"; - switch (err) { - case TOR_TLS_ERROR_MISC: return "misc error"; - case TOR_TLS_ERROR_IO: return "unexpected close"; - case TOR_TLS_ERROR_CONNREFUSED: return "connection refused"; - case TOR_TLS_ERROR_CONNRESET: return "connection reset"; - case TOR_TLS_ERROR_NO_ROUTE: return "host unreachable"; - case TOR_TLS_ERROR_TIMEOUT: return "connection timed out"; - case TOR_TLS_CLOSE: return "closed"; - case TOR_TLS_WANTREAD: return "want to read"; - case TOR_TLS_WANTWRITE: return "want to write"; - default: return "(unknown error code)"; - } -} - -#define CATCH_SYSCALL 1 -#define CATCH_ZERO 2 - -/** Given a TLS object and the result of an SSL_* call, use - * SSL_get_error to determine whether an error has occurred, and if so - * which one. Return one of TOR_TLS_{DONE|WANTREAD|WANTWRITE|ERROR}. - * If extra&CATCH_SYSCALL is true, return TOR_TLS_SYSCALL_ instead of - * reporting syscall errors. If extra&CATCH_ZERO is true, return - * TOR_TLS_ZERORETURN_ instead of reporting zero-return errors. - * - * If an error has occurred, log it at level <b>severity</b> and describe the - * current action as <b>doing</b>. - */ -STATIC int -tor_tls_get_error(tor_tls_t *tls, int r, int extra, - const char *doing, int severity, int domain) -{ - int err = SSL_get_error(tls->ssl, r); - int tor_error = TOR_TLS_ERROR_MISC; - switch (err) { - case SSL_ERROR_NONE: - return TOR_TLS_DONE; - case SSL_ERROR_WANT_READ: - return TOR_TLS_WANTREAD; - case SSL_ERROR_WANT_WRITE: - return TOR_TLS_WANTWRITE; - case SSL_ERROR_SYSCALL: - if (extra&CATCH_SYSCALL) - return TOR_TLS_SYSCALL_; - if (r == 0) { - tor_log(severity, LD_NET, "TLS error: unexpected close while %s (%s)", - doing, SSL_state_string_long(tls->ssl)); - tor_error = TOR_TLS_ERROR_IO; - } else { - int e = tor_socket_errno(tls->socket); - tor_log(severity, LD_NET, - "TLS error: <syscall error while %s> (errno=%d: %s; state=%s)", - doing, e, tor_socket_strerror(e), - SSL_state_string_long(tls->ssl)); - tor_error = tor_errno_to_tls_error(e); - } - tls_log_errors(tls, severity, domain, doing); - return tor_error; - case SSL_ERROR_ZERO_RETURN: - if (extra&CATCH_ZERO) - return TOR_TLS_ZERORETURN_; - tor_log(severity, LD_NET, "TLS connection closed while %s in state %s", - doing, SSL_state_string_long(tls->ssl)); - tls_log_errors(tls, severity, domain, doing); - return TOR_TLS_CLOSE; - default: - tls_log_errors(tls, severity, domain, doing); - return TOR_TLS_ERROR_MISC; - } -} - -/** Initialize OpenSSL, unless it has already been initialized. - */ -static void -tor_tls_init(void) -{ - check_no_tls_errors(); - - if (!tls_library_is_initialized) { -#ifdef OPENSSL_1_1_API - OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL); -#else - SSL_library_init(); - SSL_load_error_strings(); -#endif - -#if (SIZEOF_VOID_P >= 8 && \ - OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,0,1)) - long version = OpenSSL_version_num(); - - /* LCOV_EXCL_START : we can't test these lines on the same machine */ - if (version >= OPENSSL_V_SERIES(1,0,1)) { - /* Warn if we could *almost* be running with much faster ECDH. - If we're built for a 64-bit target, using OpenSSL 1.0.1, but we - don't have one of the built-in __uint128-based speedups, we are - just one build operation away from an accelerated handshake. - - (We could be looking at OPENSSL_NO_EC_NISTP_64_GCC_128 instead of - doing this test, but that gives compile-time options, not runtime - behavior.) - */ - EC_KEY *key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); - const EC_GROUP *g = key ? EC_KEY_get0_group(key) : NULL; - const EC_METHOD *m = g ? EC_GROUP_method_of(g) : NULL; - const int warn = (m == EC_GFp_simple_method() || - m == EC_GFp_mont_method() || - m == EC_GFp_nist_method()); - EC_KEY_free(key); - - if (warn) - log_notice(LD_GENERAL, "We were built to run on a 64-bit CPU, with " - "OpenSSL 1.0.1 or later, but with a version of OpenSSL " - "that apparently lacks accelerated support for the NIST " - "P-224 and P-256 groups. Building openssl with such " - "support (using the enable-ec_nistp_64_gcc_128 option " - "when configuring it) would make ECDH much faster."); - } - /* LCOV_EXCL_STOP */ -#endif /* (SIZEOF_VOID_P >= 8 && ... */ - - tor_tls_allocate_tor_tls_object_ex_data_index(); - - tls_library_is_initialized = 1; - } -} - -/** Free all global TLS structures. */ -void -tor_tls_free_all(void) -{ - check_no_tls_errors(); - - if (server_tls_context) { - tor_tls_context_t *ctx = server_tls_context; - server_tls_context = NULL; - tor_tls_context_decref(ctx); - } - if (client_tls_context) { - tor_tls_context_t *ctx = client_tls_context; - client_tls_context = NULL; - tor_tls_context_decref(ctx); - } -} - -/** We need to give OpenSSL a callback to verify certificates. This is - * it: We always accept peer certs and complete the handshake. We - * don't validate them until later. - */ -STATIC int -always_accept_verify_cb(int preverify_ok, - X509_STORE_CTX *x509_ctx) -{ - (void) preverify_ok; - (void) x509_ctx; - return 1; -} - -/** Return a newly allocated X509 name with commonName <b>cname</b>. */ -static X509_NAME * -tor_x509_name_new(const char *cname) -{ - int nid; - X509_NAME *name; - /* LCOV_EXCL_BR_START : these branches will only fail on OOM errors */ - if (!(name = X509_NAME_new())) - return NULL; - if ((nid = OBJ_txt2nid("commonName")) == NID_undef) goto error; - if (!(X509_NAME_add_entry_by_NID(name, nid, MBSTRING_ASC, - (unsigned char*)cname, -1, -1, 0))) - goto error; - /* LCOV_EXCL_BR_STOP */ - return name; - - /* LCOV_EXCL_START : these lines will only execute on out of memory errors*/ - error: - X509_NAME_free(name); - return NULL; - /* LCOV_EXCL_STOP */ -} - -/** Generate and sign an X509 certificate with the public key <b>rsa</b>, - * signed by the private key <b>rsa_sign</b>. The commonName of the - * certificate will be <b>cname</b>; the commonName of the issuer will be - * <b>cname_sign</b>. The cert will be valid for <b>cert_lifetime</b> - * seconds, starting from some time in the past. - * - * Return a certificate on success, NULL on failure. - */ -MOCK_IMPL(STATIC X509 *, -tor_tls_create_certificate,(crypto_pk_t *rsa, - crypto_pk_t *rsa_sign, - const char *cname, - const char *cname_sign, - unsigned int cert_lifetime)) -{ - /* OpenSSL generates self-signed certificates with random 64-bit serial - * numbers, so let's do that too. */ -#define SERIAL_NUMBER_SIZE 8 - - time_t start_time, end_time; - BIGNUM *serial_number = NULL; - unsigned char serial_tmp[SERIAL_NUMBER_SIZE]; - EVP_PKEY *sign_pkey = NULL, *pkey=NULL; - X509 *x509 = NULL; - X509_NAME *name = NULL, *name_issuer=NULL; - - tor_tls_init(); - - /* Make sure we're part-way through the certificate lifetime, rather - * than having it start right now. Don't choose quite uniformly, since - * then we might pick a time where we're about to expire. Lastly, be - * sure to start on a day boundary. */ - time_t now = time(NULL); - /* Our certificate lifetime will be cert_lifetime no matter what, but if we - * start cert_lifetime in the past, we'll have 0 real lifetime. instead we - * start up to (cert_lifetime - min_real_lifetime - start_granularity) in - * the past. */ - const time_t min_real_lifetime = 24*3600; - const time_t start_granularity = 24*3600; - time_t earliest_start_time; - /* Don't actually start in the future! */ - if (cert_lifetime <= min_real_lifetime + start_granularity) { - earliest_start_time = now - 1; - } else { - earliest_start_time = now + min_real_lifetime + start_granularity - - cert_lifetime; - } - start_time = crypto_rand_time_range(earliest_start_time, now); - /* Round the start time back to the start of a day. */ - start_time -= start_time % start_granularity; - - end_time = start_time + cert_lifetime; - - tor_assert(rsa); - tor_assert(cname); - tor_assert(rsa_sign); - tor_assert(cname_sign); - if (!(sign_pkey = crypto_pk_get_evp_pkey_(rsa_sign,1))) - goto error; - if (!(pkey = crypto_pk_get_evp_pkey_(rsa,0))) - goto error; - if (!(x509 = X509_new())) - goto error; - if (!(X509_set_version(x509, 2))) - goto error; - - { /* our serial number is 8 random bytes. */ - crypto_rand((char *)serial_tmp, sizeof(serial_tmp)); - if (!(serial_number = BN_bin2bn(serial_tmp, sizeof(serial_tmp), NULL))) - goto error; - if (!(BN_to_ASN1_INTEGER(serial_number, X509_get_serialNumber(x509)))) - goto error; - } - - if (!(name = tor_x509_name_new(cname))) - goto error; - if (!(X509_set_subject_name(x509, name))) - goto error; - if (!(name_issuer = tor_x509_name_new(cname_sign))) - goto error; - if (!(X509_set_issuer_name(x509, name_issuer))) - goto error; - - if (!X509_time_adj(X509_get_notBefore(x509),0,&start_time)) - goto error; - if (!X509_time_adj(X509_get_notAfter(x509),0,&end_time)) - goto error; - if (!X509_set_pubkey(x509, pkey)) - goto error; - - if (!X509_sign(x509, sign_pkey, EVP_sha256())) - goto error; - - goto done; - error: - if (x509) { - X509_free(x509); - x509 = NULL; - } - done: - tls_log_errors(NULL, LOG_WARN, LD_NET, "generating certificate"); - if (sign_pkey) - EVP_PKEY_free(sign_pkey); - if (pkey) - EVP_PKEY_free(pkey); - if (serial_number) - BN_clear_free(serial_number); - if (name) - X509_NAME_free(name); - if (name_issuer) - X509_NAME_free(name_issuer); - return x509; - -#undef SERIAL_NUMBER_SIZE -} - -/** List of ciphers that servers should select from when the client might be - * claiming extra unsupported ciphers in order to avoid fingerprinting. */ -static const char SERVER_CIPHER_LIST[] = -#ifdef TLS1_3_TXT_AES_128_GCM_SHA256 - /* This one can never actually get selected, since if the client lists it, - * we will assume that the client is honest, and not use this list. - * Nonetheless we list it if it's available, so that the server doesn't - * conclude that it has no valid ciphers if it's running with TLS1.3. - */ - TLS1_3_TXT_AES_128_GCM_SHA256 ":" -#endif - TLS1_TXT_DHE_RSA_WITH_AES_256_SHA ":" - TLS1_TXT_DHE_RSA_WITH_AES_128_SHA; - -/** List of ciphers that servers should select from when we actually have - * our choice of what cipher to use. */ -static const char UNRESTRICTED_SERVER_CIPHER_LIST[] = - /* Here are the TLS 1.3 ciphers we like, in the order we prefer. */ -#ifdef TLS1_3_TXT_AES_256_GCM_SHA384 - TLS1_3_TXT_AES_256_GCM_SHA384 ":" -#endif -#ifdef TLS1_3_TXT_CHACHA20_POLY1305_SHA256 - TLS1_3_TXT_CHACHA20_POLY1305_SHA256 ":" -#endif -#ifdef TLS1_3_TXT_AES_128_GCM_SHA256 - TLS1_3_TXT_AES_128_GCM_SHA256 ":" -#endif -#ifdef TLS1_3_TXT_AES_128_CCM_SHA256 - TLS1_3_TXT_AES_128_CCM_SHA256 ":" -#endif - - /* This list is autogenerated with the gen_server_ciphers.py script; - * don't hand-edit it. */ -#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ":" -#endif -#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - TLS1_TXT_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ":" -#endif -#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_256_SHA384 - TLS1_TXT_ECDHE_RSA_WITH_AES_256_SHA384 ":" -#endif -#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_128_SHA256 - TLS1_TXT_ECDHE_RSA_WITH_AES_128_SHA256 ":" -#endif -#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA - TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA ":" -#endif -#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA - TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA ":" -#endif -#ifdef TLS1_TXT_DHE_RSA_WITH_AES_256_GCM_SHA384 - TLS1_TXT_DHE_RSA_WITH_AES_256_GCM_SHA384 ":" -#endif -#ifdef TLS1_TXT_DHE_RSA_WITH_AES_128_GCM_SHA256 - TLS1_TXT_DHE_RSA_WITH_AES_128_GCM_SHA256 ":" -#endif -#ifdef TLS1_TXT_DHE_RSA_WITH_AES_256_CCM - TLS1_TXT_DHE_RSA_WITH_AES_256_CCM ":" -#endif -#ifdef TLS1_TXT_DHE_RSA_WITH_AES_128_CCM - TLS1_TXT_DHE_RSA_WITH_AES_128_CCM ":" -#endif -#ifdef TLS1_TXT_DHE_RSA_WITH_AES_256_SHA256 - TLS1_TXT_DHE_RSA_WITH_AES_256_SHA256 ":" -#endif -#ifdef TLS1_TXT_DHE_RSA_WITH_AES_128_SHA256 - TLS1_TXT_DHE_RSA_WITH_AES_128_SHA256 ":" -#endif - /* Required */ - TLS1_TXT_DHE_RSA_WITH_AES_256_SHA ":" - /* Required */ - TLS1_TXT_DHE_RSA_WITH_AES_128_SHA ":" -#ifdef TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305 - TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305 ":" -#endif -#ifdef TLS1_TXT_DHE_RSA_WITH_CHACHA20_POLY1305 - TLS1_TXT_DHE_RSA_WITH_CHACHA20_POLY1305 -#endif - ; - -/* Note: to set up your own private testing network with link crypto - * disabled, set your Tors' cipher list to - * (SSL3_TXT_RSA_NULL_SHA). If you do this, you won't be able to communicate - * with any of the "real" Tors, though. */ - -#define CIPHER(id, name) name ":" -#define XCIPHER(id, name) -/** List of ciphers that clients should advertise, omitting items that - * our OpenSSL doesn't know about. */ -static const char CLIENT_CIPHER_LIST[] = -#include "ciphers.inc" - /* Tell it not to use SSLv2 ciphers, so that it can select an SSLv3 version - * of any cipher we say. */ - "!SSLv2" - ; -#undef CIPHER -#undef XCIPHER - -/** Free all storage held in <b>cert</b> */ -void -tor_x509_cert_free_(tor_x509_cert_t *cert) -{ - if (! cert) - return; - if (cert->cert) - X509_free(cert->cert); - tor_free(cert->encoded); - memwipe(cert, 0x03, sizeof(*cert)); - /* LCOV_EXCL_BR_START since cert will never be NULL here */ - tor_free(cert); - /* LCOV_EXCL_BR_STOP */ -} - -/** - * Allocate a new tor_x509_cert_t to hold the certificate "x509_cert". - * - * Steals a reference to x509_cert. - */ -MOCK_IMPL(STATIC tor_x509_cert_t *, -tor_x509_cert_new,(X509 *x509_cert)) -{ - tor_x509_cert_t *cert; - EVP_PKEY *pkey; - RSA *rsa; - int length; - unsigned char *buf = NULL; - - if (!x509_cert) - return NULL; - - length = i2d_X509(x509_cert, &buf); - cert = tor_malloc_zero(sizeof(tor_x509_cert_t)); - if (length <= 0 || buf == NULL) { - goto err; - } - cert->encoded_len = (size_t) length; - cert->encoded = tor_malloc(length); - memcpy(cert->encoded, buf, length); - OPENSSL_free(buf); - - cert->cert = x509_cert; - - crypto_common_digests(&cert->cert_digests, - (char*)cert->encoded, cert->encoded_len); - - if ((pkey = X509_get_pubkey(x509_cert)) && - (rsa = EVP_PKEY_get1_RSA(pkey))) { - crypto_pk_t *pk = crypto_new_pk_from_rsa_(rsa); - if (crypto_pk_get_common_digests(pk, &cert->pkey_digests) < 0) { - crypto_pk_free(pk); - EVP_PKEY_free(pkey); - goto err; - } - - cert->pkey_digests_set = 1; - crypto_pk_free(pk); - EVP_PKEY_free(pkey); - } - - return cert; - err: - /* LCOV_EXCL_START for the same reason as the exclusion above */ - tor_free(cert); - log_err(LD_CRYPTO, "Couldn't wrap encoded X509 certificate."); - X509_free(x509_cert); - return NULL; - /* LCOV_EXCL_STOP */ -} - -/** Return a new copy of <b>cert</b>. */ -tor_x509_cert_t * -tor_x509_cert_dup(const tor_x509_cert_t *cert) -{ - tor_assert(cert); - X509 *x509 = cert->cert; - return tor_x509_cert_new(X509_dup(x509)); -} - -/** Read a DER-encoded X509 cert, of length exactly <b>certificate_len</b>, - * from a <b>certificate</b>. Return a newly allocated tor_x509_cert_t on - * success and NULL on failure. */ -tor_x509_cert_t * -tor_x509_cert_decode(const uint8_t *certificate, size_t certificate_len) -{ - X509 *x509; - const unsigned char *cp = (const unsigned char *)certificate; - tor_x509_cert_t *newcert; - tor_assert(certificate); - check_no_tls_errors(); - - if (certificate_len > INT_MAX) - goto err; - - x509 = d2i_X509(NULL, &cp, (int)certificate_len); - - if (!x509) - goto err; /* Couldn't decode */ - if (cp - certificate != (int)certificate_len) { - X509_free(x509); - goto err; /* Didn't use all the bytes */ - } - newcert = tor_x509_cert_new(x509); - if (!newcert) { - goto err; - } - if (newcert->encoded_len != certificate_len || - fast_memneq(newcert->encoded, certificate, certificate_len)) { - /* Cert wasn't in DER */ - tor_x509_cert_free(newcert); - goto err; - } - return newcert; - err: - tls_log_errors(NULL, LOG_INFO, LD_CRYPTO, "decoding a certificate"); - return NULL; -} - -/** Set *<b>encoded_out</b> and *<b>size_out</b> to <b>cert</b>'s encoded DER - * representation and length, respectively. */ -void -tor_x509_cert_get_der(const tor_x509_cert_t *cert, - const uint8_t **encoded_out, size_t *size_out) -{ - tor_assert(cert); - tor_assert(encoded_out); - tor_assert(size_out); - *encoded_out = cert->encoded; - *size_out = cert->encoded_len; -} - -/** Return a set of digests for the public key in <b>cert</b>, or NULL if this - * cert's public key is not one we know how to take the digest of. */ -const common_digests_t * -tor_x509_cert_get_id_digests(const tor_x509_cert_t *cert) -{ - if (cert->pkey_digests_set) - return &cert->pkey_digests; - else - return NULL; -} - -/** Return a set of digests for the public key in <b>cert</b>. */ -const common_digests_t * -tor_x509_cert_get_cert_digests(const tor_x509_cert_t *cert) -{ - return &cert->cert_digests; -} - -/** Remove a reference to <b>ctx</b>, and free it if it has no more - * references. */ -static void -tor_tls_context_decref(tor_tls_context_t *ctx) -{ - tor_assert(ctx); - if (--ctx->refcnt == 0) { - SSL_CTX_free(ctx->ctx); - tor_x509_cert_free(ctx->my_link_cert); - tor_x509_cert_free(ctx->my_id_cert); - tor_x509_cert_free(ctx->my_auth_cert); - crypto_pk_free(ctx->link_key); - crypto_pk_free(ctx->auth_key); - /* LCOV_EXCL_BR_START since ctx will never be NULL here */ - tor_free(ctx); - /* LCOV_EXCL_BR_STOP */ - } -} - /** Set *<b>link_cert_out</b> and *<b>id_cert_out</b> to the link certificate * and ID certificate that we're currently using for our V3 in-protocol * handshake's certificate chain. If <b>server</b> is true, provide the certs @@ -876,7 +70,7 @@ tor_tls_get_my_certs(int server, const tor_x509_cert_t **link_cert_out, const tor_x509_cert_t **id_cert_out) { - tor_tls_context_t *ctx = server ? server_tls_context : client_tls_context; + tor_tls_context_t *ctx = tor_tls_context_get(server); if (! ctx) return -1; if (link_cert_out) @@ -893,131 +87,74 @@ tor_tls_get_my_certs(int server, crypto_pk_t * tor_tls_get_my_client_auth_key(void) { - if (! client_tls_context) + tor_tls_context_t *context = tor_tls_context_get(0); + if (! context) return NULL; - return client_tls_context->auth_key; + return context->auth_key; } -/** - * Return a newly allocated copy of the public key that a certificate - * certifies. Watch out! This returns NULL if the cert's key is not RSA. - */ -crypto_pk_t * -tor_tls_cert_get_key(tor_x509_cert_t *cert) +/** Increase the reference count of <b>ctx</b>. */ +void +tor_tls_context_incref(tor_tls_context_t *ctx) { - crypto_pk_t *result = NULL; - EVP_PKEY *pkey = X509_get_pubkey(cert->cert); - RSA *rsa; - if (!pkey) - return NULL; - rsa = EVP_PKEY_get1_RSA(pkey); - if (!rsa) { - EVP_PKEY_free(pkey); - return NULL; - } - result = crypto_new_pk_from_rsa_(rsa); - EVP_PKEY_free(pkey); - return result; + ++ctx->refcnt; } -/** Return true iff the other side of <b>tls</b> has authenticated to us, and - * the key certified in <b>cert</b> is the same as the key they used to do it. - */ -MOCK_IMPL(int, -tor_tls_cert_matches_key,(const tor_tls_t *tls, const tor_x509_cert_t *cert)) +/** Remove a reference to <b>ctx</b>, and free it if it has no more + * references. */ +void +tor_tls_context_decref(tor_tls_context_t *ctx) { - tor_x509_cert_t *peer = tor_tls_get_peer_cert((tor_tls_t *)tls); - if (!peer) - return 0; - - X509 *peercert = peer->cert; - EVP_PKEY *link_key = NULL, *cert_key = NULL; - int result; - - link_key = X509_get_pubkey(peercert); - cert_key = X509_get_pubkey(cert->cert); - - result = link_key && cert_key && EVP_PKEY_cmp(cert_key, link_key) == 1; - - tor_x509_cert_free(peer); - if (link_key) - EVP_PKEY_free(link_key); - if (cert_key) - EVP_PKEY_free(cert_key); - - return result; + tor_assert(ctx); + if (--ctx->refcnt == 0) { + tor_tls_context_impl_free(ctx->ctx); + tor_x509_cert_free(ctx->my_link_cert); + tor_x509_cert_free(ctx->my_id_cert); + tor_x509_cert_free(ctx->my_auth_cert); + crypto_pk_free(ctx->link_key); + crypto_pk_free(ctx->auth_key); + /* LCOV_EXCL_BR_START since ctx will never be NULL here */ + tor_free(ctx); + /* LCOV_EXCL_BR_STOP */ + } } -/** Check whether <b>cert</b> is well-formed, currently live, and correctly - * signed by the public key in <b>signing_cert</b>. If <b>check_rsa_1024</b>, - * make sure that it has an RSA key with 1024 bits; otherwise, just check that - * the key is long enough. Return 1 if the cert is good, and 0 if it's bad or - * we couldn't check it. */ -int -tor_tls_cert_is_valid(int severity, - const tor_x509_cert_t *cert, - const tor_x509_cert_t *signing_cert, - time_t now, - int check_rsa_1024) +/** Free all global TLS structures. */ +void +tor_tls_free_all(void) { check_no_tls_errors(); - EVP_PKEY *cert_key; - int r, key_ok = 0; - - if (!signing_cert || !cert) - goto bad; - - EVP_PKEY *signing_key = X509_get_pubkey(signing_cert->cert); - if (!signing_key) - goto bad; - r = X509_verify(cert->cert, signing_key); - EVP_PKEY_free(signing_key); - if (r <= 0) - goto bad; - /* okay, the signature checked out right. Now let's check the check the - * lifetime. */ - if (check_cert_lifetime_internal(severity, cert->cert, now, - 48*60*60, 30*24*60*60) < 0) - goto bad; - - cert_key = X509_get_pubkey(cert->cert); - if (check_rsa_1024 && cert_key) { - RSA *rsa = EVP_PKEY_get1_RSA(cert_key); -#ifdef OPENSSL_1_1_API - if (rsa && RSA_bits(rsa) == 1024) -#else - if (rsa && BN_num_bits(rsa->n) == 1024) -#endif - key_ok = 1; - if (rsa) - RSA_free(rsa); - } else if (cert_key) { - int min_bits = 1024; -#ifdef EVP_PKEY_EC - if (EVP_PKEY_base_id(cert_key) == EVP_PKEY_EC) - min_bits = 128; -#endif - if (EVP_PKEY_bits(cert_key) >= min_bits) - key_ok = 1; + if (server_tls_context) { + tor_tls_context_t *ctx = server_tls_context; + server_tls_context = NULL; + tor_tls_context_decref(ctx); + } + if (client_tls_context) { + tor_tls_context_t *ctx = client_tls_context; + client_tls_context = NULL; + tor_tls_context_decref(ctx); } - EVP_PKEY_free(cert_key); - if (!key_ok) - goto bad; - - /* XXXX compare DNs or anything? */ - - return 1; - bad: - tls_log_errors(NULL, LOG_INFO, LD_CRYPTO, "checking a certificate"); - return 0; } -/** Increase the reference count of <b>ctx</b>. */ -static void -tor_tls_context_incref(tor_tls_context_t *ctx) +/** Given a TOR_TLS_* error code, return a string equivalent. */ +const char * +tor_tls_err_to_string(int err) { - ++ctx->refcnt; + if (err >= 0) + return "[Not an error.]"; + switch (err) { + case TOR_TLS_ERROR_MISC: return "misc error"; + case TOR_TLS_ERROR_IO: return "unexpected close"; + case TOR_TLS_ERROR_CONNREFUSED: return "connection refused"; + case TOR_TLS_ERROR_CONNRESET: return "connection reset"; + case TOR_TLS_ERROR_NO_ROUTE: return "host unreachable"; + case TOR_TLS_ERROR_TIMEOUT: return "connection timed out"; + case TOR_TLS_CLOSE: return "closed"; + case TOR_TLS_WANTREAD: return "want to read"; + case TOR_TLS_WANTWRITE: return "want to write"; + default: return "(unknown error code)"; + } } /** Create new global client and server TLS contexts. @@ -1058,6 +195,9 @@ tor_tls_context_init(unsigned flags, if (old_ctx != NULL) { tor_tls_context_decref(old_ctx); } + } else { + tls_log_errors(NULL, LOG_WARN, LD_CRYPTO, + "constructing a TLS context"); } } else { if (server_identity != NULL) { @@ -1066,6 +206,9 @@ tor_tls_context_init(unsigned flags, key_lifetime, flags, 0); + if (rv1 < 0) + tls_log_errors(NULL, LOG_WARN, LD_CRYPTO, + "constructing a server TLS context"); } else { tor_tls_context_t *old_ctx = server_tls_context; server_tls_context = NULL; @@ -1080,9 +223,11 @@ tor_tls_context_init(unsigned flags, key_lifetime, flags, 1); + if (rv2 < 0) + tls_log_errors(NULL, LOG_WARN, LD_CRYPTO, + "constructing a client TLS context"); } - tls_log_errors(NULL, LOG_WARN, LD_CRYPTO, "constructing a TLS context"); return MIN(rv1, rv2); } @@ -1092,7 +237,7 @@ tor_tls_context_init(unsigned flags, * it generates new certificates; all new connections will use * the new SSL context. */ -STATIC int +int tor_tls_context_init_one(tor_tls_context_t **ppcontext, crypto_pk_t *identity, unsigned int key_lifetime, @@ -1119,27 +264,31 @@ tor_tls_context_init_one(tor_tls_context_t **ppcontext, return ((new_ctx != NULL) ? 0 : -1); } -/** The group we should use for ecdhe when none was selected. */ -#define NID_tor_default_ecdhe_group NID_X9_62_prime256v1 - +/** Size of the RSA key to use for our TLS link keys */ #define RSA_LINK_KEY_BITS 2048 -/** Create a new TLS context for use with Tor TLS handshakes. - * <b>identity</b> should be set to the identity key used to sign the - * certificate. +/** How long do identity certificates live? (sec) */ +#define IDENTITY_CERT_LIFETIME (365*24*60*60) + +/** + * Initialize the certificates and keys for a TLS context <b>result</b> + * + * Other arguments as for tor_tls_context_new(). */ -STATIC tor_tls_context_t * -tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime, - unsigned flags, int is_client) +int +tor_tls_context_init_certificates(tor_tls_context_t *result, + crypto_pk_t *identity, + unsigned key_lifetime, + unsigned flags) { - crypto_pk_t *rsa = NULL, *rsa_auth = NULL; - EVP_PKEY *pkey = NULL; - tor_tls_context_t *result = NULL; - X509 *cert = NULL, *idcert = NULL, *authcert = NULL; + (void)flags; + int rv = -1; char *nickname = NULL, *nn2 = NULL; + crypto_pk_t *rsa = NULL, *rsa_auth = NULL; + tor_x509_cert_impl_t *cert = NULL, *idcert = NULL, *authcert = NULL; - tor_tls_init(); nickname = crypto_random_hostname(8, 20, "www.", ".net"); + #ifdef DISABLE_V3_LINKPROTO_SERVERSIDE nn2 = crypto_random_hostname(8, 20, "www.", ".net"); #else @@ -1151,622 +300,55 @@ tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime, goto error; if (crypto_pk_generate_key_with_bits(rsa, RSA_LINK_KEY_BITS)<0) goto error; - if (!is_client) { - /* Generate short-term RSA key for use in the in-protocol ("v3") - * authentication handshake. */ - if (!(rsa_auth = crypto_pk_new())) - goto error; - if (crypto_pk_generate_key(rsa_auth)<0) - goto error; - /* Create a link certificate signed by identity key. */ - cert = tor_tls_create_certificate(rsa, identity, nickname, nn2, - key_lifetime); - /* Create self-signed certificate for identity key. */ - idcert = tor_tls_create_certificate(identity, identity, nn2, nn2, - IDENTITY_CERT_LIFETIME); - /* Create an authentication certificate signed by identity key. */ - authcert = tor_tls_create_certificate(rsa_auth, identity, nickname, nn2, - key_lifetime); - if (!cert || !idcert || !authcert) { - log_warn(LD_CRYPTO, "Error creating certificate"); - goto error; - } - } - result = tor_malloc_zero(sizeof(tor_tls_context_t)); - result->refcnt = 1; - if (!is_client) { - result->my_link_cert = tor_x509_cert_new(X509_dup(cert)); - result->my_id_cert = tor_x509_cert_new(X509_dup(idcert)); - result->my_auth_cert = tor_x509_cert_new(X509_dup(authcert)); - if (!result->my_link_cert || !result->my_id_cert || !result->my_auth_cert) - goto error; - result->link_key = crypto_pk_dup_key(rsa); - result->auth_key = crypto_pk_dup_key(rsa_auth); - } - -#if 0 - /* Tell OpenSSL to only use TLS1. This may have subtly different results - * from SSLv23_method() with SSLv2 and SSLv3 disabled, so we need to do some - * investigation before we consider adjusting it. It should be compatible - * with existing Tors. */ - if (!(result->ctx = SSL_CTX_new(TLSv1_method()))) + /* Generate short-term RSA key for use in the in-protocol ("v3") + * authentication handshake. */ + if (!(rsa_auth = crypto_pk_new())) goto error; -#endif /* 0 */ - - /* Tell OpenSSL to use TLS 1.0 or later but not SSL2 or SSL3. */ -#ifdef HAVE_TLS_METHOD - if (!(result->ctx = SSL_CTX_new(TLS_method()))) + if (crypto_pk_generate_key(rsa_auth)<0) goto error; -#else - if (!(result->ctx = SSL_CTX_new(SSLv23_method()))) - goto error; -#endif /* defined(HAVE_TLS_METHOD) */ - SSL_CTX_set_options(result->ctx, SSL_OP_NO_SSLv2); - SSL_CTX_set_options(result->ctx, SSL_OP_NO_SSLv3); - - /* Prefer the server's ordering of ciphers: the client's ordering has - * historically been chosen for fingerprinting resistance. */ - SSL_CTX_set_options(result->ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); - - /* Disable TLS tickets if they're supported. We never want to use them; - * using them can make our perfect forward secrecy a little worse, *and* - * create an opportunity to fingerprint us (since it's unusual to use them - * with TLS sessions turned off). - * - * In 0.2.4, clients advertise support for them though, to avoid a TLS - * distinguishability vector. This can give us worse PFS, though, if we - * get a server that doesn't set SSL_OP_NO_TICKET. With luck, there will - * be few such servers by the time 0.2.4 is more stable. - */ -#ifdef SSL_OP_NO_TICKET - if (! is_client) { - SSL_CTX_set_options(result->ctx, SSL_OP_NO_TICKET); - } -#endif - - SSL_CTX_set_options(result->ctx, SSL_OP_SINGLE_DH_USE); - SSL_CTX_set_options(result->ctx, SSL_OP_SINGLE_ECDH_USE); - -#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION - SSL_CTX_set_options(result->ctx, - SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); -#endif - /* Yes, we know what we are doing here. No, we do not treat a renegotiation - * as authenticating any earlier-received data. - */ - { - SSL_CTX_set_options(result->ctx, - SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); - } - /* Don't actually allow compression; it uses RAM and time, it makes TLS - * vulnerable to CRIME-style attacks, and most of the data we transmit over - * TLS is encrypted (and therefore uncompressible) anyway. */ -#ifdef SSL_OP_NO_COMPRESSION - SSL_CTX_set_options(result->ctx, SSL_OP_NO_COMPRESSION); -#endif -#if OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,1,0) -#ifndef OPENSSL_NO_COMP - if (result->ctx->comp_methods) - result->ctx->comp_methods = NULL; -#endif -#endif /* OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,1,0) */ - -#ifdef SSL_MODE_RELEASE_BUFFERS - SSL_CTX_set_mode(result->ctx, SSL_MODE_RELEASE_BUFFERS); -#endif - if (! is_client) { - if (cert && !SSL_CTX_use_certificate(result->ctx,cert)) - goto error; - X509_free(cert); /* We just added a reference to cert. */ - cert=NULL; - if (idcert) { - X509_STORE *s = SSL_CTX_get_cert_store(result->ctx); - tor_assert(s); - X509_STORE_add_cert(s, idcert); - X509_free(idcert); /* The context now owns the reference to idcert */ - idcert = NULL; - } - } - SSL_CTX_set_session_cache_mode(result->ctx, SSL_SESS_CACHE_OFF); - if (!is_client) { - tor_assert(rsa); - if (!(pkey = crypto_pk_get_evp_pkey_(rsa,1))) - goto error; - if (!SSL_CTX_use_PrivateKey(result->ctx, pkey)) - goto error; - EVP_PKEY_free(pkey); - pkey = NULL; - if (!SSL_CTX_check_private_key(result->ctx)) - goto error; - } - { - crypto_dh_t *dh = crypto_dh_new(DH_TYPE_TLS); - tor_assert(dh); - SSL_CTX_set_tmp_dh(result->ctx, crypto_dh_get_dh_(dh)); - crypto_dh_free(dh); - } - if (! is_client) { - int nid; - EC_KEY *ec_key; - if (flags & TOR_TLS_CTX_USE_ECDHE_P224) - nid = NID_secp224r1; - else if (flags & TOR_TLS_CTX_USE_ECDHE_P256) - nid = NID_X9_62_prime256v1; - else - nid = NID_tor_default_ecdhe_group; - /* Use P-256 for ECDHE. */ - ec_key = EC_KEY_new_by_curve_name(nid); - if (ec_key != NULL) /*XXXX Handle errors? */ - SSL_CTX_set_tmp_ecdh(result->ctx, ec_key); - EC_KEY_free(ec_key); + /* Create a link certificate signed by identity key. */ + cert = tor_tls_create_certificate(rsa, identity, nickname, nn2, + key_lifetime); + /* Create self-signed certificate for identity key. */ + idcert = tor_tls_create_certificate(identity, identity, nn2, nn2, + IDENTITY_CERT_LIFETIME); + /* Create an authentication certificate signed by identity key. */ + authcert = tor_tls_create_certificate(rsa_auth, identity, nickname, nn2, + key_lifetime); + if (!cert || !idcert || !authcert) { + log_warn(LD_CRYPTO, "Error creating certificate"); + goto error; } - SSL_CTX_set_verify(result->ctx, SSL_VERIFY_PEER, - always_accept_verify_cb); - /* let us realloc bufs that we're writing from */ - SSL_CTX_set_mode(result->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - if (rsa) - crypto_pk_free(rsa); - if (rsa_auth) - crypto_pk_free(rsa_auth); - X509_free(authcert); - tor_free(nickname); - tor_free(nn2); - return result; + result->my_link_cert = tor_x509_cert_new(cert); + cert = NULL; + result->my_id_cert = tor_x509_cert_new(idcert); + idcert = NULL; + result->my_auth_cert = tor_x509_cert_new(authcert); + authcert = NULL; + if (!result->my_link_cert || !result->my_id_cert || !result->my_auth_cert) + goto error; + result->link_key = rsa; + rsa = NULL; + result->auth_key = rsa_auth; + rsa_auth = NULL; + rv = 0; error: - tls_log_errors(NULL, LOG_WARN, LD_NET, "creating TLS context"); + tor_free(nickname); tor_free(nn2); - if (pkey) - EVP_PKEY_free(pkey); - if (rsa) - crypto_pk_free(rsa); - if (rsa_auth) - crypto_pk_free(rsa_auth); - if (result) - tor_tls_context_decref(result); - if (cert) - X509_free(cert); - if (idcert) - X509_free(idcert); - if (authcert) - X509_free(authcert); - return NULL; -} - -/** Invoked when a TLS state changes: log the change at severity 'debug' */ -STATIC void -tor_tls_debug_state_callback(const SSL *ssl, int type, int val) -{ - /* LCOV_EXCL_START since this depends on whether debug is captured or not */ - log_debug(LD_HANDSHAKE, "SSL %p is now in state %s [type=%d,val=%d].", - ssl, SSL_state_string_long(ssl), type, val); - /* LCOV_EXCL_STOP */ -} - -/* Return the name of the negotiated ciphersuite in use on <b>tls</b> */ -const char * -tor_tls_get_ciphersuite_name(tor_tls_t *tls) -{ - return SSL_get_cipher(tls->ssl); -} - -/* Here's the old V2 cipher list we sent from 0.2.1.1-alpha up to - * 0.2.3.17-beta. If a client is using this list, we can't believe the ciphers - * that it claims to support. We'll prune this list to remove the ciphers - * *we* don't recognize. */ -STATIC uint16_t v2_cipher_list[] = { - 0xc00a, /* TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_CBC_SHA */ - 0xc014, /* TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA */ - 0x0039, /* TLS1_TXT_DHE_RSA_WITH_AES_256_SHA */ - 0x0038, /* TLS1_TXT_DHE_DSS_WITH_AES_256_SHA */ - 0xc00f, /* TLS1_TXT_ECDH_RSA_WITH_AES_256_CBC_SHA */ - 0xc005, /* TLS1_TXT_ECDH_ECDSA_WITH_AES_256_CBC_SHA */ - 0x0035, /* TLS1_TXT_RSA_WITH_AES_256_SHA */ - 0xc007, /* TLS1_TXT_ECDHE_ECDSA_WITH_RC4_128_SHA */ - 0xc009, /* TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_CBC_SHA */ - 0xc011, /* TLS1_TXT_ECDHE_RSA_WITH_RC4_128_SHA */ - 0xc013, /* TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA */ - 0x0033, /* TLS1_TXT_DHE_RSA_WITH_AES_128_SHA */ - 0x0032, /* TLS1_TXT_DHE_DSS_WITH_AES_128_SHA */ - 0xc00c, /* TLS1_TXT_ECDH_RSA_WITH_RC4_128_SHA */ - 0xc00e, /* TLS1_TXT_ECDH_RSA_WITH_AES_128_CBC_SHA */ - 0xc002, /* TLS1_TXT_ECDH_ECDSA_WITH_RC4_128_SHA */ - 0xc004, /* TLS1_TXT_ECDH_ECDSA_WITH_AES_128_CBC_SHA */ - 0x0004, /* SSL3_TXT_RSA_RC4_128_MD5 */ - 0x0005, /* SSL3_TXT_RSA_RC4_128_SHA */ - 0x002f, /* TLS1_TXT_RSA_WITH_AES_128_SHA */ - 0xc008, /* TLS1_TXT_ECDHE_ECDSA_WITH_DES_192_CBC3_SHA */ - 0xc012, /* TLS1_TXT_ECDHE_RSA_WITH_DES_192_CBC3_SHA */ - 0x0016, /* SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA */ - 0x0013, /* SSL3_TXT_EDH_DSS_DES_192_CBC3_SHA */ - 0xc00d, /* TLS1_TXT_ECDH_RSA_WITH_DES_192_CBC3_SHA */ - 0xc003, /* TLS1_TXT_ECDH_ECDSA_WITH_DES_192_CBC3_SHA */ - 0xfeff, /* SSL3_TXT_RSA_FIPS_WITH_3DES_EDE_CBC_SHA */ - 0x000a, /* SSL3_TXT_RSA_DES_192_CBC3_SHA */ - 0 -}; -/** Have we removed the unrecognized ciphers from v2_cipher_list yet? */ -static int v2_cipher_list_pruned = 0; - -/** Return 0 if <b>m</b> does not support the cipher with ID <b>cipher</b>; - * return 1 if it does support it, or if we have no way to tell. */ -STATIC int -find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, uint16_t cipher) -{ - const SSL_CIPHER *c; -#ifdef HAVE_SSL_CIPHER_FIND - (void) m; - { - unsigned char cipherid[3]; - tor_assert(ssl); - set_uint16(cipherid, tor_htons(cipher)); - cipherid[2] = 0; /* If ssl23_get_cipher_by_char finds no cipher starting - * with a two-byte 'cipherid', it may look for a v2 - * cipher with the appropriate 3 bytes. */ - c = SSL_CIPHER_find((SSL*)ssl, cipherid); - if (c) - tor_assert((SSL_CIPHER_get_id(c) & 0xffff) == cipher); - return c != NULL; - } -#else /* !(defined(HAVE_SSL_CIPHER_FIND)) */ - -# if defined(HAVE_STRUCT_SSL_METHOD_ST_GET_CIPHER_BY_CHAR) - if (m && m->get_cipher_by_char) { - unsigned char cipherid[3]; - set_uint16(cipherid, tor_htons(cipher)); - cipherid[2] = 0; /* If ssl23_get_cipher_by_char finds no cipher starting - * with a two-byte 'cipherid', it may look for a v2 - * cipher with the appropriate 3 bytes. */ - c = m->get_cipher_by_char(cipherid); - if (c) - tor_assert((c->id & 0xffff) == cipher); - return c != NULL; - } -#endif /* defined(HAVE_STRUCT_SSL_METHOD_ST_GET_CIPHER_BY_CHAR) */ -# ifndef OPENSSL_1_1_API - if (m && m->get_cipher && m->num_ciphers) { - /* It would seem that some of the "let's-clean-up-openssl" forks have - * removed the get_cipher_by_char function. Okay, so now you get a - * quadratic search. - */ - int i; - for (i = 0; i < m->num_ciphers(); ++i) { - c = m->get_cipher(i); - if (c && (c->id & 0xffff) == cipher) { - return 1; - } - } - return 0; - } -#endif /* !defined(OPENSSL_1_1_API) */ - (void) ssl; - (void) m; - (void) cipher; - return 1; /* No way to search */ -#endif /* defined(HAVE_SSL_CIPHER_FIND) */ -} - -/** Remove from v2_cipher_list every cipher that we don't support, so that - * comparing v2_cipher_list to a client's cipher list will give a sensible - * result. */ -static void -prune_v2_cipher_list(const SSL *ssl) -{ - uint16_t *inp, *outp; -#ifdef HAVE_TLS_METHOD - const SSL_METHOD *m = TLS_method(); -#else - const SSL_METHOD *m = SSLv23_method(); -#endif - inp = outp = v2_cipher_list; - while (*inp) { - if (find_cipher_by_id(ssl, m, *inp)) { - *outp++ = *inp++; - } else { - inp++; - } - } - *outp = 0; + tor_x509_cert_impl_free(cert); + tor_x509_cert_impl_free(idcert); + tor_x509_cert_impl_free(authcert); + crypto_pk_free(rsa); + crypto_pk_free(rsa_auth); - v2_cipher_list_pruned = 1; + return rv; } - -/** Examine the client cipher list in <b>ssl</b>, and determine what kind of - * client it is. Return one of CIPHERS_ERR, CIPHERS_V1, CIPHERS_V2, - * CIPHERS_UNRESTRICTED. - **/ -STATIC int -tor_tls_classify_client_ciphers(const SSL *ssl, - STACK_OF(SSL_CIPHER) *peer_ciphers) -{ - int i, res; - tor_tls_t *tor_tls; - if (PREDICT_UNLIKELY(!v2_cipher_list_pruned)) - prune_v2_cipher_list(ssl); - - tor_tls = tor_tls_get_by_ssl(ssl); - if (tor_tls && tor_tls->client_cipher_list_type) - return tor_tls->client_cipher_list_type; - - /* If we reached this point, we just got a client hello. See if there is - * a cipher list. */ - if (!peer_ciphers) { - log_info(LD_NET, "No ciphers on session"); - res = CIPHERS_ERR; - goto done; - } - /* Now we need to see if there are any ciphers whose presence means we're - * dealing with an updated Tor. */ - for (i = 0; i < sk_SSL_CIPHER_num(peer_ciphers); ++i) { - const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(peer_ciphers, i); - const char *ciphername = SSL_CIPHER_get_name(cipher); - if (strcmp(ciphername, TLS1_TXT_DHE_RSA_WITH_AES_128_SHA) && - strcmp(ciphername, TLS1_TXT_DHE_RSA_WITH_AES_256_SHA) && - strcmp(ciphername, SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA) && - strcmp(ciphername, "(NONE)")) { - log_debug(LD_NET, "Got a non-version-1 cipher called '%s'", ciphername); - // return 1; - goto v2_or_higher; - } - } - res = CIPHERS_V1; - goto done; - v2_or_higher: - { - const uint16_t *v2_cipher = v2_cipher_list; - for (i = 0; i < sk_SSL_CIPHER_num(peer_ciphers); ++i) { - const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(peer_ciphers, i); - uint16_t id = SSL_CIPHER_get_id(cipher) & 0xffff; - if (id == 0x00ff) /* extended renegotiation indicator. */ - continue; - if (!id || id != *v2_cipher) { - res = CIPHERS_UNRESTRICTED; - goto dump_ciphers; - } - ++v2_cipher; - } - if (*v2_cipher != 0) { - res = CIPHERS_UNRESTRICTED; - goto dump_ciphers; - } - res = CIPHERS_V2; - } - - dump_ciphers: - { - smartlist_t *elts = smartlist_new(); - char *s; - for (i = 0; i < sk_SSL_CIPHER_num(peer_ciphers); ++i) { - const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(peer_ciphers, i); - const char *ciphername = SSL_CIPHER_get_name(cipher); - smartlist_add(elts, (char*)ciphername); - } - s = smartlist_join_strings(elts, ":", 0, NULL); - log_debug(LD_NET, "Got a %s V2/V3 cipher list from %s. It is: '%s'", - (res == CIPHERS_V2) ? "fictitious" : "real", ADDR(tor_tls), s); - tor_free(s); - smartlist_free(elts); - } - done: - if (tor_tls) - return tor_tls->client_cipher_list_type = res; - - return res; -} - -/** Return true iff the cipher list suggested by the client for <b>ssl</b> is - * a list that indicates that the client knows how to do the v2 TLS connection - * handshake. */ -STATIC int -tor_tls_client_is_using_v2_ciphers(const SSL *ssl) -{ - STACK_OF(SSL_CIPHER) *ciphers; -#ifdef HAVE_SSL_GET_CLIENT_CIPHERS - ciphers = SSL_get_client_ciphers(ssl); -#else - SSL_SESSION *session; - if (!(session = SSL_get_session((SSL *)ssl))) { - log_info(LD_NET, "No session on TLS?"); - return CIPHERS_ERR; - } - ciphers = session->ciphers; -#endif /* defined(HAVE_SSL_GET_CLIENT_CIPHERS) */ - - return tor_tls_classify_client_ciphers(ssl, ciphers) >= CIPHERS_V2; -} - -/** Invoked when we're accepting a connection on <b>ssl</b>, and the connection - * changes state. We use this: - * <ul><li>To alter the state of the handshake partway through, so we - * do not send or request extra certificates in v2 handshakes.</li> - * <li>To detect renegotiation</li></ul> - */ -STATIC void -tor_tls_server_info_callback(const SSL *ssl, int type, int val) -{ - tor_tls_t *tls; - (void) val; - - IF_BUG_ONCE(ssl == NULL) { - return; // LCOV_EXCL_LINE - } - - tor_tls_debug_state_callback(ssl, type, val); - - if (type != SSL_CB_ACCEPT_LOOP) - return; - - OSSL_HANDSHAKE_STATE ssl_state = SSL_get_state(ssl); - if (! STATE_IS_SW_SERVER_HELLO(ssl_state)) - return; - tls = tor_tls_get_by_ssl(ssl); - if (tls) { - /* Check whether we're watching for renegotiates. If so, this is one! */ - if (tls->negotiated_callback) - tls->got_renegotiate = 1; - if (tls->server_handshake_count < 127) /*avoid any overflow possibility*/ - ++tls->server_handshake_count; - } else { - log_warn(LD_BUG, "Couldn't look up the tls for an SSL*. How odd!"); - return; - } - - /* Now check the cipher list. */ - if (tor_tls_client_is_using_v2_ciphers(ssl)) { - if (tls->wasV2Handshake) - return; /* We already turned this stuff off for the first handshake; - * This is a renegotiation. */ - - /* Yes, we're casting away the const from ssl. This is very naughty of us. - * Let's hope openssl doesn't notice! */ - - /* Set SSL_MODE_NO_AUTO_CHAIN to keep from sending back any extra certs. */ - SSL_set_mode((SSL*) ssl, SSL_MODE_NO_AUTO_CHAIN); - /* Don't send a hello request. */ - SSL_set_verify((SSL*) ssl, SSL_VERIFY_NONE, NULL); - - if (tls) { - tls->wasV2Handshake = 1; - } else { - /* LCOV_EXCL_START this line is not reachable */ - log_warn(LD_BUG, "Couldn't look up the tls for an SSL*. How odd!"); - /* LCOV_EXCL_STOP */ - } - } -} - -/** Callback to get invoked on a server after we've read the list of ciphers - * the client supports, but before we pick our own ciphersuite. - * - * We can't abuse an info_cb for this, since by the time one of the - * client_hello info_cbs is called, we've already picked which ciphersuite to - * use. - * - * Technically, this function is an abuse of this callback, since the point of - * a session_secret_cb is to try to set up and/or verify a shared-secret for - * authentication on the fly. But as long as we return 0, we won't actually be - * setting up a shared secret, and all will be fine. - */ -STATIC int -tor_tls_session_secret_cb(SSL *ssl, void *secret, int *secret_len, - STACK_OF(SSL_CIPHER) *peer_ciphers, - CONST_IF_OPENSSL_1_1_API SSL_CIPHER **cipher, - void *arg) -{ - (void) secret; - (void) secret_len; - (void) peer_ciphers; - (void) cipher; - (void) arg; - - if (tor_tls_classify_client_ciphers(ssl, peer_ciphers) == - CIPHERS_UNRESTRICTED) { - SSL_set_cipher_list(ssl, UNRESTRICTED_SERVER_CIPHER_LIST); - } - - SSL_set_session_secret_cb(ssl, NULL, NULL); - - return 0; -} -static void -tor_tls_setup_session_secret_cb(tor_tls_t *tls) -{ - SSL_set_session_secret_cb(tls->ssl, tor_tls_session_secret_cb, NULL); -} - -/** Create a new TLS object from a file descriptor, and a flag to - * determine whether it is functioning as a server. - */ -tor_tls_t * -tor_tls_new(int sock, int isServer) -{ - BIO *bio = NULL; - tor_tls_t *result = tor_malloc_zero(sizeof(tor_tls_t)); - tor_tls_context_t *context = isServer ? server_tls_context : - client_tls_context; - result->magic = TOR_TLS_MAGIC; - - check_no_tls_errors(); - tor_assert(context); /* make sure somebody made it first */ - if (!(result->ssl = SSL_new(context->ctx))) { - tls_log_errors(NULL, LOG_WARN, LD_NET, "creating SSL object"); - tor_free(result); - goto err; - } - -#ifdef SSL_set_tlsext_host_name - /* Browsers use the TLS hostname extension, so we should too. */ - if (!isServer) { - char *fake_hostname = crypto_random_hostname(4,25, "www.",".com"); - SSL_set_tlsext_host_name(result->ssl, fake_hostname); - tor_free(fake_hostname); - } -#endif /* defined(SSL_set_tlsext_host_name) */ - - if (!SSL_set_cipher_list(result->ssl, - isServer ? SERVER_CIPHER_LIST : CLIENT_CIPHER_LIST)) { - tls_log_errors(NULL, LOG_WARN, LD_NET, "setting ciphers"); -#ifdef SSL_set_tlsext_host_name - SSL_set_tlsext_host_name(result->ssl, NULL); -#endif - SSL_free(result->ssl); - tor_free(result); - goto err; - } - result->socket = sock; - bio = BIO_new_socket(sock, BIO_NOCLOSE); - if (! bio) { - tls_log_errors(NULL, LOG_WARN, LD_NET, "opening BIO"); -#ifdef SSL_set_tlsext_host_name - SSL_set_tlsext_host_name(result->ssl, NULL); -#endif - SSL_free(result->ssl); - tor_free(result); - goto err; - } - { - int set_worked = - SSL_set_ex_data(result->ssl, tor_tls_object_ex_data_index, result); - if (!set_worked) { - log_warn(LD_BUG, - "Couldn't set the tls for an SSL*; connection will fail"); - } - } - SSL_set_bio(result->ssl, bio, bio); - tor_tls_context_incref(context); - result->context = context; - result->state = TOR_TLS_ST_HANDSHAKE; - result->isServer = isServer; - result->wantwrite_n = 0; - result->last_write_count = (unsigned long) BIO_number_written(bio); - result->last_read_count = (unsigned long) BIO_number_read(bio); - if (result->last_write_count || result->last_read_count) { - log_warn(LD_NET, "Newly created BIO has read count %lu, write count %lu", - result->last_read_count, result->last_write_count); - } - if (isServer) { - SSL_set_info_callback(result->ssl, tor_tls_server_info_callback); - } else { - SSL_set_info_callback(result->ssl, tor_tls_debug_state_callback); - } - - if (isServer) - tor_tls_setup_session_secret_cb(result); - - goto done; - err: - result = NULL; - done: - /* Not expected to get called. */ - tls_log_errors(NULL, LOG_WARN, LD_NET, "creating tor_tls_t object"); - return result; -} - /** Make future log messages about <b>tls</b> display the address * <b>address</b>. */ @@ -1778,64 +360,6 @@ tor_tls_set_logged_address(tor_tls_t *tls, const char *address) tls->address = tor_strdup(address); } -/** Set <b>cb</b> to be called with argument <b>arg</b> whenever <b>tls</b> - * next gets a client-side renegotiate in the middle of a read. Do not - * invoke this function until <em>after</em> initial handshaking is done! - */ -void -tor_tls_set_renegotiate_callback(tor_tls_t *tls, - void (*cb)(tor_tls_t *, void *arg), - void *arg) -{ - tls->negotiated_callback = cb; - tls->callback_arg = arg; - tls->got_renegotiate = 0; - if (cb) { - SSL_set_info_callback(tls->ssl, tor_tls_server_info_callback); - } else { - SSL_set_info_callback(tls->ssl, tor_tls_debug_state_callback); - } -} - -/** If this version of openssl requires it, turn on renegotiation on - * <b>tls</b>. - */ -void -tor_tls_unblock_renegotiation(tor_tls_t *tls) -{ - /* Yes, we know what we are doing here. No, we do not treat a renegotiation - * as authenticating any earlier-received data. */ - SSL_set_options(tls->ssl, - SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); -} - -/** If this version of openssl supports it, turn off renegotiation on - * <b>tls</b>. (Our protocol never requires this for security, but it's nice - * to use belt-and-suspenders here.) - */ -void -tor_tls_block_renegotiation(tor_tls_t *tls) -{ -#ifdef SUPPORT_UNSAFE_RENEGOTIATION_FLAG - tls->ssl->s3->flags &= ~SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; -#else - (void) tls; -#endif -} - -/** Assert that the flags that allow legacy renegotiation are still set */ -void -tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls) -{ -#if defined(SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION) && \ - SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION != 0 - long options = SSL_get_options(tls->ssl); - tor_assert(0 != (options & SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)); -#else - (void) tls; -#endif /* defined(SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION) && ... */ -} - /** Return whether this tls initiated the connect (client) or * received it (server). */ int @@ -1858,12 +382,11 @@ tor_tls_free_(tor_tls_t *tls) size_t r,w; tor_tls_get_n_raw_bytes(tls,&r,&w); /* ensure written_by_tls is updated */ } -#ifdef SSL_set_tlsext_host_name - SSL_set_tlsext_host_name(tls->ssl, NULL); -#endif - SSL_free(tls->ssl); + tor_tls_impl_free(tls->ssl); tls->ssl = NULL; +#ifdef ENABLE_OPENSSL tls->negotiated_callback = NULL; +#endif if (tls->context) tor_tls_context_decref(tls->context); tor_free(tls->address); @@ -1871,394 +394,19 @@ tor_tls_free_(tor_tls_t *tls) tor_free(tls); } -/** Underlying function for TLS reading. Reads up to <b>len</b> - * characters from <b>tls</b> into <b>cp</b>. On success, returns the - * number of characters read. On failure, returns TOR_TLS_ERROR, - * TOR_TLS_CLOSE, TOR_TLS_WANTREAD, or TOR_TLS_WANTWRITE. - */ -MOCK_IMPL(int, -tor_tls_read,(tor_tls_t *tls, char *cp, size_t len)) -{ - int r, err; - tor_assert(tls); - tor_assert(tls->ssl); - tor_assert(tls->state == TOR_TLS_ST_OPEN); - tor_assert(len<INT_MAX); - r = SSL_read(tls->ssl, cp, (int)len); - if (r > 0) { - if (tls->got_renegotiate) { - /* Renegotiation happened! */ - log_info(LD_NET, "Got a TLS renegotiation from %s", ADDR(tls)); - if (tls->negotiated_callback) - tls->negotiated_callback(tls, tls->callback_arg); - tls->got_renegotiate = 0; - } - return r; - } - err = tor_tls_get_error(tls, r, CATCH_ZERO, "reading", LOG_DEBUG, LD_NET); - if (err == TOR_TLS_ZERORETURN_ || err == TOR_TLS_CLOSE) { - log_debug(LD_NET,"read returned r=%d; TLS is closed",r); - tls->state = TOR_TLS_ST_CLOSED; - return TOR_TLS_CLOSE; - } else { - tor_assert(err != TOR_TLS_DONE); - log_debug(LD_NET,"read returned r=%d, err=%d",r,err); - return err; - } -} - -/** Total number of bytes that we've used TLS to send. Used to track TLS - * overhead. */ -STATIC uint64_t total_bytes_written_over_tls = 0; -/** Total number of bytes that TLS has put on the network for us. Used to - * track TLS overhead. */ -STATIC uint64_t total_bytes_written_by_tls = 0; - -/** Underlying function for TLS writing. Write up to <b>n</b> - * characters from <b>cp</b> onto <b>tls</b>. On success, returns the - * number of characters written. On failure, returns TOR_TLS_ERROR, - * TOR_TLS_WANTREAD, or TOR_TLS_WANTWRITE. - */ -int -tor_tls_write(tor_tls_t *tls, const char *cp, size_t n) -{ - int r, err; - tor_assert(tls); - tor_assert(tls->ssl); - tor_assert(tls->state == TOR_TLS_ST_OPEN); - tor_assert(n < INT_MAX); - if (n == 0) - return 0; - if (tls->wantwrite_n) { - /* if WANTWRITE last time, we must use the _same_ n as before */ - tor_assert(n >= tls->wantwrite_n); - log_debug(LD_NET,"resuming pending-write, (%d to flush, reusing %d)", - (int)n, (int)tls->wantwrite_n); - n = tls->wantwrite_n; - tls->wantwrite_n = 0; - } - r = SSL_write(tls->ssl, cp, (int)n); - err = tor_tls_get_error(tls, r, 0, "writing", LOG_INFO, LD_NET); - if (err == TOR_TLS_DONE) { - total_bytes_written_over_tls += r; - return r; - } - if (err == TOR_TLS_WANTWRITE || err == TOR_TLS_WANTREAD) { - tls->wantwrite_n = n; - } - return err; -} - -/** Perform initial handshake on <b>tls</b>. When finished, returns - * TOR_TLS_DONE. On failure, returns TOR_TLS_ERROR, TOR_TLS_WANTREAD, - * or TOR_TLS_WANTWRITE. - */ -int -tor_tls_handshake(tor_tls_t *tls) -{ - int r; - tor_assert(tls); - tor_assert(tls->ssl); - tor_assert(tls->state == TOR_TLS_ST_HANDSHAKE); - - check_no_tls_errors(); - - OSSL_HANDSHAKE_STATE oldstate = SSL_get_state(tls->ssl); - - if (tls->isServer) { - log_debug(LD_HANDSHAKE, "About to call SSL_accept on %p (%s)", tls, - SSL_state_string_long(tls->ssl)); - r = SSL_accept(tls->ssl); - } else { - log_debug(LD_HANDSHAKE, "About to call SSL_connect on %p (%s)", tls, - SSL_state_string_long(tls->ssl)); - r = SSL_connect(tls->ssl); - } - - OSSL_HANDSHAKE_STATE newstate = SSL_get_state(tls->ssl); - - if (oldstate != newstate) - log_debug(LD_HANDSHAKE, "After call, %p was in state %s", - tls, SSL_state_string_long(tls->ssl)); - /* We need to call this here and not earlier, since OpenSSL has a penchant - * for clearing its flags when you say accept or connect. */ - tor_tls_unblock_renegotiation(tls); - r = tor_tls_get_error(tls,r,0, "handshaking", LOG_INFO, LD_HANDSHAKE); - if (ERR_peek_error() != 0) { - tls_log_errors(tls, tls->isServer ? LOG_INFO : LOG_WARN, LD_HANDSHAKE, - "handshaking"); - return TOR_TLS_ERROR_MISC; - } - if (r == TOR_TLS_DONE) { - tls->state = TOR_TLS_ST_OPEN; - return tor_tls_finish_handshake(tls); - } - return r; -} - -/** Perform the final part of the initial TLS handshake on <b>tls</b>. This - * should be called for the first handshake only: it determines whether the v1 - * or the v2 handshake was used, and adjusts things for the renegotiation - * handshake as appropriate. - * - * tor_tls_handshake() calls this on its own; you only need to call this if - * bufferevent is doing the handshake for you. - */ -int -tor_tls_finish_handshake(tor_tls_t *tls) -{ - int r = TOR_TLS_DONE; - check_no_tls_errors(); - if (tls->isServer) { - SSL_set_info_callback(tls->ssl, NULL); - SSL_set_verify(tls->ssl, SSL_VERIFY_PEER, always_accept_verify_cb); - SSL_clear_mode(tls->ssl, SSL_MODE_NO_AUTO_CHAIN); - if (tor_tls_client_is_using_v2_ciphers(tls->ssl)) { - /* This check is redundant, but back when we did it in the callback, - * we might have not been able to look up the tor_tls_t if the code - * was buggy. Fixing that. */ - if (!tls->wasV2Handshake) { - log_warn(LD_BUG, "For some reason, wasV2Handshake didn't" - " get set. Fixing that."); - } - tls->wasV2Handshake = 1; - log_debug(LD_HANDSHAKE, "Completed V2 TLS handshake with client; waiting" - " for renegotiation."); - } else { - tls->wasV2Handshake = 0; - } - } else { - /* Client-side */ - tls->wasV2Handshake = 1; - /* XXXX this can move, probably? -NM */ - if (SSL_set_cipher_list(tls->ssl, SERVER_CIPHER_LIST) == 0) { - tls_log_errors(NULL, LOG_WARN, LD_HANDSHAKE, "re-setting ciphers"); - r = TOR_TLS_ERROR_MISC; - } - } - tls_log_errors(NULL, LOG_WARN, LD_NET, "finishing the handshake"); - return r; -} - -/** Shut down an open tls connection <b>tls</b>. When finished, returns - * TOR_TLS_DONE. On failure, returns TOR_TLS_ERROR, TOR_TLS_WANTREAD, - * or TOR_TLS_WANTWRITE. - */ -int -tor_tls_shutdown(tor_tls_t *tls) -{ - int r, err; - char buf[128]; - tor_assert(tls); - tor_assert(tls->ssl); - check_no_tls_errors(); - - while (1) { - if (tls->state == TOR_TLS_ST_SENTCLOSE) { - /* If we've already called shutdown once to send a close message, - * we read until the other side has closed too. - */ - do { - r = SSL_read(tls->ssl, buf, 128); - } while (r>0); - err = tor_tls_get_error(tls, r, CATCH_ZERO, "reading to shut down", - LOG_INFO, LD_NET); - if (err == TOR_TLS_ZERORETURN_) { - tls->state = TOR_TLS_ST_GOTCLOSE; - /* fall through... */ - } else { - return err; - } - } - - r = SSL_shutdown(tls->ssl); - if (r == 1) { - /* If shutdown returns 1, the connection is entirely closed. */ - tls->state = TOR_TLS_ST_CLOSED; - return TOR_TLS_DONE; - } - err = tor_tls_get_error(tls, r, CATCH_SYSCALL|CATCH_ZERO, "shutting down", - LOG_INFO, LD_NET); - if (err == TOR_TLS_SYSCALL_) { - /* The underlying TCP connection closed while we were shutting down. */ - tls->state = TOR_TLS_ST_CLOSED; - return TOR_TLS_DONE; - } else if (err == TOR_TLS_ZERORETURN_) { - /* The TLS connection says that it sent a shutdown record, but - * isn't done shutting down yet. Make sure that this hasn't - * happened before, then go back to the start of the function - * and try to read. - */ - if (tls->state == TOR_TLS_ST_GOTCLOSE || - tls->state == TOR_TLS_ST_SENTCLOSE) { - log_warn(LD_NET, - "TLS returned \"half-closed\" value while already half-closed"); - return TOR_TLS_ERROR_MISC; - } - tls->state = TOR_TLS_ST_SENTCLOSE; - /* fall through ... */ - } else { - return err; - } - } /* end loop */ -} - -/** Return true iff this TLS connection is authenticated. - */ -int -tor_tls_peer_has_cert(tor_tls_t *tls) -{ - X509 *cert; - cert = SSL_get_peer_certificate(tls->ssl); - tls_log_errors(tls, LOG_WARN, LD_HANDSHAKE, "getting peer certificate"); - if (!cert) - return 0; - X509_free(cert); - return 1; -} - -/** Return a newly allocated copy of the peer certificate, or NULL if there - * isn't one. */ -MOCK_IMPL(tor_x509_cert_t *, -tor_tls_get_peer_cert,(tor_tls_t *tls)) -{ - X509 *cert; - cert = SSL_get_peer_certificate(tls->ssl); - tls_log_errors(tls, LOG_WARN, LD_HANDSHAKE, "getting peer certificate"); - if (!cert) - return NULL; - return tor_x509_cert_new(cert); -} - -/** Return a newly allocated copy of the cerficate we used on the connection, - * or NULL if somehow we didn't use one. */ -MOCK_IMPL(tor_x509_cert_t *, -tor_tls_get_own_cert,(tor_tls_t *tls)) -{ - X509 *cert = SSL_get_certificate(tls->ssl); - tls_log_errors(tls, LOG_WARN, LD_HANDSHAKE, - "getting own-connection certificate"); - if (!cert) - return NULL; - /* Fun inconsistency: SSL_get_peer_certificate increments the reference - * count, but SSL_get_certificate does not. */ - X509 *duplicate = X509_dup(cert); - if (BUG(duplicate == NULL)) - return NULL; - return tor_x509_cert_new(duplicate); -} - -/** Warn that a certificate lifetime extends through a certain range. */ -static void -log_cert_lifetime(int severity, const X509 *cert, const char *problem, - time_t now) -{ - BIO *bio = NULL; - BUF_MEM *buf; - char *s1=NULL, *s2=NULL; - char mytime[33]; - struct tm tm; - size_t n; - - if (problem) - tor_log(severity, LD_GENERAL, - "Certificate %s. Either their clock is set wrong, or your clock " - "is wrong.", - problem); - - if (!(bio = BIO_new(BIO_s_mem()))) { - log_warn(LD_GENERAL, "Couldn't allocate BIO!"); goto end; - } - if (!(ASN1_TIME_print(bio, X509_get_notBefore_const(cert)))) { - tls_log_errors(NULL, LOG_WARN, LD_NET, "printing certificate lifetime"); - goto end; - } - BIO_get_mem_ptr(bio, &buf); - s1 = tor_strndup(buf->data, buf->length); - - (void)BIO_reset(bio); - if (!(ASN1_TIME_print(bio, X509_get_notAfter_const(cert)))) { - tls_log_errors(NULL, LOG_WARN, LD_NET, "printing certificate lifetime"); - goto end; - } - BIO_get_mem_ptr(bio, &buf); - s2 = tor_strndup(buf->data, buf->length); - - n = strftime(mytime, 32, "%b %d %H:%M:%S %Y UTC", tor_gmtime_r(&now, &tm)); - if (n > 0) { - tor_log(severity, LD_GENERAL, - "(certificate lifetime runs from %s through %s. Your time is %s.)", - s1,s2,mytime); - } else { - tor_log(severity, LD_GENERAL, - "(certificate lifetime runs from %s through %s. " - "Couldn't get your time.)", - s1, s2); - } - - end: - /* Not expected to get invoked */ - tls_log_errors(NULL, LOG_WARN, LD_NET, "getting certificate lifetime"); - if (bio) - BIO_free(bio); - tor_free(s1); - tor_free(s2); -} - -/** Helper function: try to extract a link certificate and an identity - * certificate from <b>tls</b>, and store them in *<b>cert_out</b> and - * *<b>id_cert_out</b> respectively. Log all messages at level - * <b>severity</b>. - * - * Note that a reference is added to cert_out, so it needs to be - * freed. id_cert_out doesn't. */ -MOCK_IMPL(STATIC void, -try_to_extract_certs_from_tls,(int severity, tor_tls_t *tls, - X509 **cert_out, X509 **id_cert_out)) -{ - X509 *cert = NULL, *id_cert = NULL; - STACK_OF(X509) *chain = NULL; - int num_in_chain, i; - *cert_out = *id_cert_out = NULL; - if (!(cert = SSL_get_peer_certificate(tls->ssl))) - return; - *cert_out = cert; - if (!(chain = SSL_get_peer_cert_chain(tls->ssl))) - return; - num_in_chain = sk_X509_num(chain); - /* 1 means we're receiving (server-side), and it's just the id_cert. - * 2 means we're connecting (client-side), and it's both the link - * cert and the id_cert. - */ - if (num_in_chain < 1) { - log_fn(severity,LD_PROTOCOL, - "Unexpected number of certificates in chain (%d)", - num_in_chain); - return; - } - for (i=0; i<num_in_chain; ++i) { - id_cert = sk_X509_value(chain, i); - if (X509_cmp(id_cert, cert) != 0) - break; - } - *id_cert_out = id_cert; -} - /** If the provided tls connection is authenticated and has a * certificate chain that is currently valid and signed, then set * *<b>identity_key</b> to the identity certificate's key and return * 0. Else, return -1 and log complaints with log-level <b>severity</b>. */ int -tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_t **identity_key) +tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_t **identity) { - X509 *cert = NULL, *id_cert = NULL; - EVP_PKEY *id_pkey = NULL; - RSA *rsa; - int r = -1; - - check_no_tls_errors(); - *identity_key = NULL; + tor_x509_cert_impl_t *cert = NULL, *id_cert = NULL; + tor_x509_cert_t *peer_x509 = NULL, *id_x509 = NULL; + tor_assert(tls); + tor_assert(identity); + int rv = -1; try_to_extract_certs_from_tls(severity, tls, &cert, &id_cert); if (!cert) @@ -2267,407 +415,22 @@ tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_t **identity_key) log_fn(severity,LD_PROTOCOL,"No distinct identity certificate found"); goto done; } - tls_log_errors(tls, severity, LD_HANDSHAKE, "before verifying certificate"); + peer_x509 = tor_x509_cert_new(cert); + id_x509 = tor_x509_cert_new(id_cert); + cert = id_cert = NULL; /* Prevent double-free */ - if (!(id_pkey = X509_get_pubkey(id_cert)) || - X509_verify(cert, id_pkey) <= 0) { - log_fn(severity,LD_PROTOCOL,"X509_verify on cert and pkey returned <= 0"); - tls_log_errors(tls, severity, LD_HANDSHAKE, "verifying certificate"); + if (! tor_tls_cert_is_valid(severity, peer_x509, id_x509, time(NULL), 0)) { goto done; } - rsa = EVP_PKEY_get1_RSA(id_pkey); - if (!rsa) - goto done; - *identity_key = crypto_new_pk_from_rsa_(rsa); - - r = 0; + *identity = tor_tls_cert_get_key(id_x509); + rv = 0; done: - if (cert) - X509_free(cert); - if (id_pkey) - EVP_PKEY_free(id_pkey); - - /* This should never get invoked, but let's make sure in case OpenSSL - * acts unexpectedly. */ - tls_log_errors(tls, LOG_WARN, LD_HANDSHAKE, "finishing tor_tls_verify"); - - return r; -} - -/** Check whether the certificate set on the connection <b>tls</b> is expired - * give or take <b>past_tolerance</b> seconds, or not-yet-valid give or take - * <b>future_tolerance</b> seconds. Return 0 for valid, -1 for failure. - * - * NOTE: you should call tor_tls_verify before tor_tls_check_lifetime. - */ -int -tor_tls_check_lifetime(int severity, tor_tls_t *tls, - time_t now, - int past_tolerance, int future_tolerance) -{ - X509 *cert; - int r = -1; - - if (!(cert = SSL_get_peer_certificate(tls->ssl))) - goto done; - - if (check_cert_lifetime_internal(severity, cert, now, - past_tolerance, future_tolerance) < 0) - goto done; - - r = 0; - done: - if (cert) - X509_free(cert); - /* Not expected to get invoked */ - tls_log_errors(tls, LOG_WARN, LD_NET, "checking certificate lifetime"); - - return r; -} - -/** Helper: check whether <b>cert</b> is expired give or take - * <b>past_tolerance</b> seconds, or not-yet-valid give or take - * <b>future_tolerance</b> seconds. (Relative to the current time - * <b>now</b>.) If it is live, return 0. If it is not live, log a message - * and return -1. */ -static int -check_cert_lifetime_internal(int severity, const X509 *cert, - time_t now, - int past_tolerance, int future_tolerance) -{ - time_t t; - - t = now + future_tolerance; - if (X509_cmp_time(X509_get_notBefore_const(cert), &t) > 0) { - log_cert_lifetime(severity, cert, "not yet valid", now); - return -1; - } - t = now - past_tolerance; - if (X509_cmp_time(X509_get_notAfter_const(cert), &t) < 0) { - log_cert_lifetime(severity, cert, "already expired", now); - return -1; - } - - return 0; -} - -#ifdef TOR_UNIT_TESTS -/* Testing only: return a new x509 cert with the same contents as <b>inp</b>, - but with the expiration time <b>new_expiration_time</b>, signed with - <b>signing_key</b>. */ -STATIC tor_x509_cert_t * -tor_x509_cert_replace_expiration(const tor_x509_cert_t *inp, - time_t new_expiration_time, - crypto_pk_t *signing_key) -{ - X509 *newc = X509_dup(inp->cert); - X509_time_adj(X509_get_notAfter(newc), 0, &new_expiration_time); - EVP_PKEY *pk = crypto_pk_get_evp_pkey_(signing_key, 1); - tor_assert(X509_sign(newc, pk, EVP_sha256())); - EVP_PKEY_free(pk); - return tor_x509_cert_new(newc); -} -#endif /* defined(TOR_UNIT_TESTS) */ - -/** Return the number of bytes available for reading from <b>tls</b>. - */ -int -tor_tls_get_pending_bytes(tor_tls_t *tls) -{ - tor_assert(tls); - return SSL_pending(tls->ssl); -} - -/** If <b>tls</b> requires that the next write be of a particular size, - * return that size. Otherwise, return 0. */ -size_t -tor_tls_get_forced_write_size(tor_tls_t *tls) -{ - return tls->wantwrite_n; -} - -/** Sets n_read and n_written to the number of bytes read and written, - * respectively, on the raw socket used by <b>tls</b> since the last time this - * function was called on <b>tls</b>. */ -void -tor_tls_get_n_raw_bytes(tor_tls_t *tls, size_t *n_read, size_t *n_written) -{ - BIO *wbio, *tmpbio; - unsigned long r, w; - r = (unsigned long) BIO_number_read(SSL_get_rbio(tls->ssl)); - /* We want the number of bytes actually for real written. Unfortunately, - * sometimes OpenSSL replaces the wbio on tls->ssl with a buffering bio, - * which makes the answer turn out wrong. Let's cope with that. Note - * that this approach will fail if we ever replace tls->ssl's BIOs with - * buffering bios for reasons of our own. As an alternative, we could - * save the original BIO for tls->ssl in the tor_tls_t structure, but - * that would be tempting fate. */ - wbio = SSL_get_wbio(tls->ssl); -#if OPENSSL_VERSION_NUMBER >= OPENSSL_VER(1,1,0,0,5) - /* BIO structure is opaque as of OpenSSL 1.1.0-pre5-dev. Again, not - * supposed to use this form of the version macro, but the OpenSSL developers - * introduced major API changes in the pre-release stage. - */ - if (BIO_method_type(wbio) == BIO_TYPE_BUFFER && - (tmpbio = BIO_next(wbio)) != NULL) - wbio = tmpbio; -#else /* !(OPENSSL_VERSION_NUMBER >= OPENSSL_VER(1,1,0,0,5)) */ - if (wbio->method == BIO_f_buffer() && (tmpbio = BIO_next(wbio)) != NULL) - wbio = tmpbio; -#endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_VER(1,1,0,0,5) */ - w = (unsigned long) BIO_number_written(wbio); - - /* We are ok with letting these unsigned ints go "negative" here: - * If we wrapped around, this should still give us the right answer, unless - * we wrapped around by more than ULONG_MAX since the last time we called - * this function. - */ - *n_read = (size_t)(r - tls->last_read_count); - *n_written = (size_t)(w - tls->last_write_count); - if (*n_read > INT_MAX || *n_written > INT_MAX) { - log_warn(LD_BUG, "Preposterously large value in tor_tls_get_n_raw_bytes. " - "r=%lu, last_read=%lu, w=%lu, last_written=%lu", - r, tls->last_read_count, w, tls->last_write_count); - } - total_bytes_written_by_tls += *n_written; - tls->last_read_count = r; - tls->last_write_count = w; -} - -/** Return a ratio of the bytes that TLS has sent to the bytes that we've told - * it to send. Used to track whether our TLS records are getting too tiny. */ -MOCK_IMPL(double, -tls_get_write_overhead_ratio,(void)) -{ - if (total_bytes_written_over_tls == 0) - return 1.0; - - return ((double)total_bytes_written_by_tls) / - ((double)total_bytes_written_over_tls); -} - -/** Implement check_no_tls_errors: If there are any pending OpenSSL - * errors, log an error message. */ -void -check_no_tls_errors_(const char *fname, int line) -{ - if (ERR_peek_error() == 0) - return; - log_warn(LD_CRYPTO, "Unhandled OpenSSL errors found at %s:%d: ", - tor_fix_source_file(fname), line); - tls_log_errors(NULL, LOG_WARN, LD_NET, NULL); -} - -/** Return true iff the initial TLS connection at <b>tls</b> did not use a v2 - * TLS handshake. Output is undefined if the handshake isn't finished. */ -int -tor_tls_used_v1_handshake(tor_tls_t *tls) -{ - return ! tls->wasV2Handshake; -} - -/** Return the number of server handshakes that we've noticed doing on - * <b>tls</b>. */ -int -tor_tls_get_num_server_handshakes(tor_tls_t *tls) -{ - return tls->server_handshake_count; -} - -/** Return true iff the server TLS connection <b>tls</b> got the renegotiation - * request it was waiting for. */ -int -tor_tls_server_got_renegotiate(tor_tls_t *tls) -{ - return tls->got_renegotiate; -} - -#ifndef HAVE_SSL_GET_CLIENT_RANDOM -static size_t -SSL_get_client_random(SSL *s, uint8_t *out, size_t len) -{ - if (len == 0) - return SSL3_RANDOM_SIZE; - tor_assert(len == SSL3_RANDOM_SIZE); - tor_assert(s->s3); - memcpy(out, s->s3->client_random, len); - return len; -} -#endif /* !defined(HAVE_SSL_GET_CLIENT_RANDOM) */ - -#ifndef HAVE_SSL_GET_SERVER_RANDOM -static size_t -SSL_get_server_random(SSL *s, uint8_t *out, size_t len) -{ - if (len == 0) - return SSL3_RANDOM_SIZE; - tor_assert(len == SSL3_RANDOM_SIZE); - tor_assert(s->s3); - memcpy(out, s->s3->server_random, len); - return len; -} -#endif /* !defined(HAVE_SSL_GET_SERVER_RANDOM) */ - -#ifndef HAVE_SSL_SESSION_GET_MASTER_KEY -STATIC size_t -SSL_SESSION_get_master_key(SSL_SESSION *s, uint8_t *out, size_t len) -{ - tor_assert(s); - if (len == 0) - return s->master_key_length; - tor_assert(len == (size_t)s->master_key_length); - tor_assert(out); - memcpy(out, s->master_key, len); - return len; -} -#endif /* !defined(HAVE_SSL_SESSION_GET_MASTER_KEY) */ - -/** Set the DIGEST256_LEN buffer at <b>secrets_out</b> to the value used in - * the v3 handshake to prove that the client knows the TLS secrets for the - * connection <b>tls</b>. Return 0 on success, -1 on failure. - */ -MOCK_IMPL(int, -tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out)) -{ -#define TLSSECRET_MAGIC "Tor V3 handshake TLS cross-certification" - uint8_t buf[128]; - size_t len; - tor_assert(tls); - - SSL *const ssl = tls->ssl; - SSL_SESSION *const session = SSL_get_session(ssl); - - tor_assert(ssl); - tor_assert(session); - - const size_t server_random_len = SSL_get_server_random(ssl, NULL, 0); - const size_t client_random_len = SSL_get_client_random(ssl, NULL, 0); - const size_t master_key_len = SSL_SESSION_get_master_key(session, NULL, 0); - - tor_assert(server_random_len); - tor_assert(client_random_len); - tor_assert(master_key_len); - - len = client_random_len + server_random_len + strlen(TLSSECRET_MAGIC) + 1; - tor_assert(len <= sizeof(buf)); - - { - size_t r = SSL_get_client_random(ssl, buf, client_random_len); - tor_assert(r == client_random_len); - } - - { - size_t r = SSL_get_server_random(ssl, - buf+client_random_len, - server_random_len); - tor_assert(r == server_random_len); - } - - uint8_t *master_key = tor_malloc_zero(master_key_len); - { - size_t r = SSL_SESSION_get_master_key(session, master_key, master_key_len); - tor_assert(r == master_key_len); - } - - uint8_t *nextbuf = buf + client_random_len + server_random_len; - memcpy(nextbuf, TLSSECRET_MAGIC, strlen(TLSSECRET_MAGIC) + 1); - - /* - The value is an HMAC, using the TLS master key as the HMAC key, of - client_random | server_random | TLSSECRET_MAGIC - */ - crypto_hmac_sha256((char*)secrets_out, - (char*)master_key, - master_key_len, - (char*)buf, len); - memwipe(buf, 0, sizeof(buf)); - memwipe(master_key, 0, master_key_len); - tor_free(master_key); - - return 0; -} - -/** Using the RFC5705 key material exporting construction, and the - * provided <b>context</b> (<b>context_len</b> bytes long) and - * <b>label</b> (a NUL-terminated string), compute a 32-byte secret in - * <b>secrets_out</b> that only the parties to this TLS session can - * compute. Return 0 on success and -1 on failure. - */ -MOCK_IMPL(int, -tor_tls_export_key_material,(tor_tls_t *tls, uint8_t *secrets_out, - const uint8_t *context, - size_t context_len, - const char *label)) -{ - tor_assert(tls); - tor_assert(tls->ssl); - - int r = SSL_export_keying_material(tls->ssl, - secrets_out, DIGEST256_LEN, - label, strlen(label), - context, context_len, 1); - return (r == 1) ? 0 : -1; -} - -/** Examine the amount of memory used and available for buffers in <b>tls</b>. - * Set *<b>rbuf_capacity</b> to the amount of storage allocated for the read - * buffer and *<b>rbuf_bytes</b> to the amount actually used. - * Set *<b>wbuf_capacity</b> to the amount of storage allocated for the write - * buffer and *<b>wbuf_bytes</b> to the amount actually used. - * - * Return 0 on success, -1 on failure.*/ -int -tor_tls_get_buffer_sizes(tor_tls_t *tls, - size_t *rbuf_capacity, size_t *rbuf_bytes, - size_t *wbuf_capacity, size_t *wbuf_bytes) -{ -#if OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) - (void)tls; - (void)rbuf_capacity; - (void)rbuf_bytes; - (void)wbuf_capacity; - (void)wbuf_bytes; - - return -1; -#else /* !(OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0)) */ - if (tls->ssl->s3->rbuf.buf) - *rbuf_capacity = tls->ssl->s3->rbuf.len; - else - *rbuf_capacity = 0; - if (tls->ssl->s3->wbuf.buf) - *wbuf_capacity = tls->ssl->s3->wbuf.len; - else - *wbuf_capacity = 0; - *rbuf_bytes = tls->ssl->s3->rbuf.left; - *wbuf_bytes = tls->ssl->s3->wbuf.left; - return 0; -#endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) */ -} - -/** Check whether the ECC group requested is supported by the current OpenSSL - * library instance. Return 1 if the group is supported, and 0 if not. - */ -int -evaluate_ecgroup_for_tls(const char *ecgroup) -{ - EC_KEY *ec_key; - int nid; - int ret; - - if (!ecgroup) - nid = NID_tor_default_ecdhe_group; - else if (!strcasecmp(ecgroup, "P256")) - nid = NID_X9_62_prime256v1; - else if (!strcasecmp(ecgroup, "P224")) - nid = NID_secp224r1; - else - return 0; - - ec_key = EC_KEY_new_by_curve_name(nid); - ret = (ec_key != NULL); - EC_KEY_free(ec_key); + tor_x509_cert_impl_free(cert); + tor_x509_cert_impl_free(id_cert); + tor_x509_cert_free(peer_x509); + tor_x509_cert_free(id_x509); - return ret; + return rv; } diff --git a/src/lib/tls/tortls.h b/src/lib/tls/tortls.h index fe192b2abc..4591927081 100644 --- a/src/lib/tls/tortls.h +++ b/src/lib/tls/tortls.h @@ -12,14 +12,27 @@ **/ #include "lib/crypt_ops/crypto_rsa.h" -#include "lib/crypt_ops/compat_openssl.h" #include "lib/testsupport/testsupport.h" +#include "lib/net/nettypes.h" /* Opaque structure to hold a TLS connection. */ typedef struct tor_tls_t tor_tls_t; -/* Opaque structure to hold an X509 certificate. */ -typedef struct tor_x509_cert_t tor_x509_cert_t; +#ifdef TORTLS_PRIVATE +#ifdef ENABLE_OPENSSL +struct ssl_st; +struct ssl_ctx_st; +struct ssl_session_st; +typedef struct ssl_ctx_st tor_tls_context_impl_t; +typedef struct ssl_st tor_tls_impl_t; +#else +struct PRFileDesc; +typedef struct PRFileDesc tor_tls_context_impl_t; +typedef struct PRFileDesc tor_tls_impl_t; +#endif +#endif + +struct tor_x509_cert_t; /* Possible return values for most tor_tls_* functions. */ #define MIN_TOR_TLS_ERROR_VAL_ -9 @@ -52,164 +65,30 @@ typedef struct tor_x509_cert_t tor_x509_cert_t; #define TOR_TLS_IS_ERROR(rv) ((rv) < TOR_TLS_CLOSE) -#ifdef TORTLS_PRIVATE -#define TOR_TLS_MAGIC 0x71571571 - -typedef enum { - TOR_TLS_ST_HANDSHAKE, TOR_TLS_ST_OPEN, TOR_TLS_ST_GOTCLOSE, - TOR_TLS_ST_SENTCLOSE, TOR_TLS_ST_CLOSED, TOR_TLS_ST_RENEGOTIATE, - TOR_TLS_ST_BUFFEREVENT -} tor_tls_state_t; -#define tor_tls_state_bitfield_t ENUM_BF(tor_tls_state_t) - -struct x509_st; -struct ssl_st; -struct ssl_ctx_st; -struct ssl_session_st; - /** Holds a SSL_CTX object and related state used to configure TLS * connections. */ -typedef struct tor_tls_context_t { - int refcnt; - struct ssl_ctx_st *ctx; - tor_x509_cert_t *my_link_cert; - tor_x509_cert_t *my_id_cert; - tor_x509_cert_t *my_auth_cert; - crypto_pk_t *link_key; - crypto_pk_t *auth_key; -} tor_tls_context_t; - -/** Structure that we use for a single certificate. */ -struct tor_x509_cert_t { - struct x509_st *cert; - uint8_t *encoded; - size_t encoded_len; - unsigned pkey_digests_set : 1; - common_digests_t cert_digests; - common_digests_t pkey_digests; -}; - -/** Holds a SSL object and its associated data. Members are only - * accessed from within tortls.c. - */ -struct tor_tls_t { - uint32_t magic; - tor_tls_context_t *context; /** A link to the context object for this tls. */ - struct ssl_st *ssl; /**< An OpenSSL SSL object. */ - int socket; /**< The underlying file descriptor for this TLS connection. */ - char *address; /**< An address to log when describing this connection. */ - tor_tls_state_bitfield_t state : 3; /**< The current SSL state, - * depending on which operations - * have completed successfully. */ - unsigned int isServer:1; /**< True iff this is a server-side connection */ - unsigned int wasV2Handshake:1; /**< True iff the original handshake for - * this connection used the updated version - * of the connection protocol (client sends - * different cipher list, server sends only - * one certificate). */ - /** True iff we should call negotiated_callback when we're done reading. */ - unsigned int got_renegotiate:1; - /** Return value from tor_tls_classify_client_ciphers, or 0 if we haven't - * called that function yet. */ - int8_t client_cipher_list_type; - /** Incremented every time we start the server side of a handshake. */ - uint8_t server_handshake_count; - size_t wantwrite_n; /**< 0 normally, >0 if we returned wantwrite last - * time. */ - /** Last values retrieved from BIO_number_read()/write(); see - * tor_tls_get_n_raw_bytes() for usage. - */ - unsigned long last_write_count; - unsigned long last_read_count; - /** If set, a callback to invoke whenever the client tries to renegotiate - * the handshake. */ - void (*negotiated_callback)(tor_tls_t *tls, void *arg); - /** Argument to pass to negotiated_callback. */ - void *callback_arg; -}; - -STATIC int tor_errno_to_tls_error(int e); -STATIC int tor_tls_get_error(tor_tls_t *tls, int r, int extra, - const char *doing, int severity, int domain); -STATIC tor_tls_t *tor_tls_get_by_ssl(const struct ssl_st *ssl); -STATIC void tor_tls_allocate_tor_tls_object_ex_data_index(void); -#ifdef TORTLS_OPENSSL_PRIVATE -STATIC int always_accept_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx); -STATIC int tor_tls_classify_client_ciphers(const struct ssl_st *ssl, - STACK_OF(SSL_CIPHER) *peer_ciphers); -#endif -STATIC int tor_tls_client_is_using_v2_ciphers(const struct ssl_st *ssl); -MOCK_DECL(STATIC void, try_to_extract_certs_from_tls, - (int severity, tor_tls_t *tls, struct x509_st **cert_out, - struct x509_st **id_cert_out)); -#ifndef HAVE_SSL_SESSION_GET_MASTER_KEY -STATIC size_t SSL_SESSION_get_master_key(struct ssl_session_st *s, - uint8_t *out, - size_t len); -#endif -STATIC void tor_tls_debug_state_callback(const struct ssl_st *ssl, - int type, int val); -STATIC void tor_tls_server_info_callback(const struct ssl_st *ssl, - int type, int val); -#ifdef TORTLS_OPENSSL_PRIVATE -STATIC int tor_tls_session_secret_cb(struct ssl_st *ssl, void *secret, - int *secret_len, - STACK_OF(SSL_CIPHER) *peer_ciphers, - CONST_IF_OPENSSL_1_1_API SSL_CIPHER **cipher, - void *arg); -STATIC int find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, - uint16_t cipher); -#endif /* defined(TORTLS_OPENSSL_PRIVATE) */ -MOCK_DECL(STATIC struct x509_st *, tor_tls_create_certificate, - (crypto_pk_t *rsa, - crypto_pk_t *rsa_sign, - const char *cname, - const char *cname_sign, - unsigned int cert_lifetime)); -STATIC tor_tls_context_t *tor_tls_context_new(crypto_pk_t *identity, - unsigned int key_lifetime, unsigned flags, int is_client); -MOCK_DECL(STATIC tor_x509_cert_t *, tor_x509_cert_new, - (struct x509_st *x509_cert)); -STATIC int tor_tls_context_init_one(tor_tls_context_t **ppcontext, - crypto_pk_t *identity, - unsigned int key_lifetime, - unsigned int flags, - int is_client); -STATIC void tls_log_errors(tor_tls_t *tls, int severity, int domain, - const char *doing); - -#ifdef TOR_UNIT_TESTS -extern int tor_tls_object_ex_data_index; -extern tor_tls_context_t *server_tls_context; -extern tor_tls_context_t *client_tls_context; -extern uint16_t v2_cipher_list[]; -extern uint64_t total_bytes_written_over_tls; -extern uint64_t total_bytes_written_by_tls; - -STATIC tor_x509_cert_t *tor_x509_cert_replace_expiration( - const tor_x509_cert_t *inp, - time_t new_expiration_time, - crypto_pk_t *signing_key); -#endif /* defined(TOR_UNIT_TESTS) */ - -#endif /* defined(TORTLS_PRIVATE) */ +typedef struct tor_tls_context_t tor_tls_context_t; -tor_x509_cert_t *tor_x509_cert_dup(const tor_x509_cert_t *cert); const char *tor_tls_err_to_string(int err); void tor_tls_get_state_description(tor_tls_t *tls, char *buf, size_t sz); - void tor_tls_free_all(void); #define TOR_TLS_CTX_IS_PUBLIC_SERVER (1u<<0) #define TOR_TLS_CTX_USE_ECDHE_P256 (1u<<1) #define TOR_TLS_CTX_USE_ECDHE_P224 (1u<<2) +void tor_tls_init(void); +void tls_log_errors(tor_tls_t *tls, int severity, int domain, + const char *doing); int tor_tls_context_init(unsigned flags, crypto_pk_t *client_identity, crypto_pk_t *server_identity, unsigned int key_lifetime); -tor_tls_t *tor_tls_new(int sock, int is_server); +void tor_tls_context_incref(tor_tls_context_t *ctx); +void tor_tls_context_decref(tor_tls_context_t *ctx); +tor_tls_context_t *tor_tls_context_get(int is_server); +tor_tls_t *tor_tls_new(tor_socket_t sock, int is_server); void tor_tls_set_logged_address(tor_tls_t *tls, const char *address); void tor_tls_set_renegotiate_callback(tor_tls_t *tls, void (*cb)(tor_tls_t *, void *arg), @@ -218,13 +97,9 @@ int tor_tls_is_server(tor_tls_t *tls); void tor_tls_free_(tor_tls_t *tls); #define tor_tls_free(tls) FREE_AND_NULL(tor_tls_t, tor_tls_free_, (tls)) int tor_tls_peer_has_cert(tor_tls_t *tls); -MOCK_DECL(tor_x509_cert_t *,tor_tls_get_peer_cert,(tor_tls_t *tls)); -MOCK_DECL(tor_x509_cert_t *,tor_tls_get_own_cert,(tor_tls_t *tls)); +MOCK_DECL(struct tor_x509_cert_t *,tor_tls_get_peer_cert,(tor_tls_t *tls)); +MOCK_DECL(struct tor_x509_cert_t *,tor_tls_get_own_cert,(tor_tls_t *tls)); int tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_t **identity); -int tor_tls_check_lifetime(int severity, - tor_tls_t *tls, time_t now, - int past_tolerance, - int future_tolerance); MOCK_DECL(int, tor_tls_read, (tor_tls_t *tls, char *cp, size_t len)); int tor_tls_write(tor_tls_t *tls, const char *cp, size_t n); int tor_tls_handshake(tor_tls_t *tls); @@ -232,7 +107,6 @@ int tor_tls_finish_handshake(tor_tls_t *tls); void tor_tls_unblock_renegotiation(tor_tls_t *tls); void tor_tls_block_renegotiation(tor_tls_t *tls); void tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls); -int tor_tls_shutdown(tor_tls_t *tls); int tor_tls_get_pending_bytes(tor_tls_t *tls); size_t tor_tls_get_forced_write_size(tor_tls_t *tls); @@ -248,6 +122,8 @@ MOCK_DECL(double, tls_get_write_overhead_ratio, (void)); int tor_tls_used_v1_handshake(tor_tls_t *tls); int tor_tls_get_num_server_handshakes(tor_tls_t *tls); int tor_tls_server_got_renegotiate(tor_tls_t *tls); +MOCK_DECL(int,tor_tls_cert_matches_key,(const tor_tls_t *tls, + const struct tor_x509_cert_t *cert)); MOCK_DECL(int,tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out)); MOCK_DECL(int,tor_tls_export_key_material,( tor_tls_t *tls, uint8_t *secrets_out, @@ -255,37 +131,23 @@ MOCK_DECL(int,tor_tls_export_key_material,( size_t context_len, const char *label)); +#ifdef ENABLE_OPENSSL /* Log and abort if there are unhandled TLS errors in OpenSSL's error stack. */ #define check_no_tls_errors() check_no_tls_errors_(__FILE__,__LINE__) - void check_no_tls_errors_(const char *fname, int line); + void tor_tls_log_one_error(tor_tls_t *tls, unsigned long err, int severity, int domain, const char *doing); +#else +#define check_no_tls_errors() STMT_NIL +#endif -void tor_x509_cert_free_(tor_x509_cert_t *cert); -#define tor_x509_cert_free(c) \ - FREE_AND_NULL(tor_x509_cert_t, tor_x509_cert_free_, (c)) -tor_x509_cert_t *tor_x509_cert_decode(const uint8_t *certificate, - size_t certificate_len); -void tor_x509_cert_get_der(const tor_x509_cert_t *cert, - const uint8_t **encoded_out, size_t *size_out); -const common_digests_t *tor_x509_cert_get_id_digests( - const tor_x509_cert_t *cert); -const common_digests_t *tor_x509_cert_get_cert_digests( - const tor_x509_cert_t *cert); int tor_tls_get_my_certs(int server, - const tor_x509_cert_t **link_cert_out, - const tor_x509_cert_t **id_cert_out); + const struct tor_x509_cert_t **link_cert_out, + const struct tor_x509_cert_t **id_cert_out); crypto_pk_t *tor_tls_get_my_client_auth_key(void); -crypto_pk_t *tor_tls_cert_get_key(tor_x509_cert_t *cert); -MOCK_DECL(int,tor_tls_cert_matches_key,(const tor_tls_t *tls, - const tor_x509_cert_t *cert)); -int tor_tls_cert_is_valid(int severity, - const tor_x509_cert_t *cert, - const tor_x509_cert_t *signing_cert, - time_t now, - int check_rsa_1024); + const char *tor_tls_get_ciphersuite_name(tor_tls_t *tls); int evaluate_ecgroup_for_tls(const char *ecgroup); diff --git a/src/lib/tls/tortls_internal.h b/src/lib/tls/tortls_internal.h new file mode 100644 index 0000000000..2bf2212104 --- /dev/null +++ b/src/lib/tls/tortls_internal.h @@ -0,0 +1,76 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TORTLS_INTERNAL_H +#define TORTLS_INTERNAL_H + +int tor_errno_to_tls_error(int e); +#ifdef ENABLE_OPENSSL +int tor_tls_get_error(tor_tls_t *tls, int r, int extra, + const char *doing, int severity, int domain); +#endif +MOCK_DECL(void, try_to_extract_certs_from_tls, + (int severity, tor_tls_t *tls, + tor_x509_cert_impl_t **cert_out, + tor_x509_cert_impl_t **id_cert_out)); + +tor_tls_context_t *tor_tls_context_new(crypto_pk_t *identity, + unsigned int key_lifetime, unsigned flags, int is_client); +int tor_tls_context_init_one(tor_tls_context_t **ppcontext, + crypto_pk_t *identity, + unsigned int key_lifetime, + unsigned int flags, + int is_client); +int tor_tls_context_init_certificates(tor_tls_context_t *result, + crypto_pk_t *identity, + unsigned key_lifetime, + unsigned flags); +void tor_tls_impl_free_(tor_tls_impl_t *ssl); +#define tor_tls_impl_free(tls) \ + FREE_AND_NULL(tor_tls_impl_t, tor_tls_impl_free_, (tls)) + +void tor_tls_context_impl_free_(tor_tls_context_impl_t *); +#define tor_tls_context_impl_free(ctx) \ + FREE_AND_NULL(tor_tls_context_impl_t, tor_tls_context_impl_free_, (ctx)) + +#ifdef ENABLE_OPENSSL +tor_tls_t *tor_tls_get_by_ssl(const struct ssl_st *ssl); +int tor_tls_client_is_using_v2_ciphers(const struct ssl_st *ssl); +void tor_tls_debug_state_callback(const struct ssl_st *ssl, + int type, int val); +void tor_tls_server_info_callback(const struct ssl_st *ssl, + int type, int val); +void tor_tls_allocate_tor_tls_object_ex_data_index(void); + +#if !defined(HAVE_SSL_SESSION_GET_MASTER_KEY) +size_t SSL_SESSION_get_master_key(struct ssl_session_st *s, + uint8_t *out, + size_t len); +#endif + +#ifdef TORTLS_OPENSSL_PRIVATE +int always_accept_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx); +int tor_tls_classify_client_ciphers(const struct ssl_st *ssl, + STACK_OF(SSL_CIPHER) *peer_ciphers); +STATIC int tor_tls_session_secret_cb(struct ssl_st *ssl, void *secret, + int *secret_len, + STACK_OF(SSL_CIPHER) *peer_ciphers, + CONST_IF_OPENSSL_1_1_API SSL_CIPHER **cipher, + void *arg); +STATIC int find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, + uint16_t cipher); +#endif +#endif + +#ifdef TOR_UNIT_TESTS +extern int tor_tls_object_ex_data_index; +extern tor_tls_context_t *server_tls_context; +extern tor_tls_context_t *client_tls_context; +extern uint16_t v2_cipher_list[]; +extern uint64_t total_bytes_written_over_tls; +extern uint64_t total_bytes_written_by_tls; +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* defined(TORTLS_INTERNAL_H) */ diff --git a/src/lib/tls/tortls_nss.c b/src/lib/tls/tortls_nss.c new file mode 100644 index 0000000000..8e6c0d2d77 --- /dev/null +++ b/src/lib/tls/tortls_nss.c @@ -0,0 +1,741 @@ +/* Copyright (c) 2003, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tortls_nss.c + * \brief Wrapper functions to present a consistent interface to + * TLS and SSL X.509 functions from NSS. + **/ + +#include "orconfig.h" + +#define TORTLS_PRIVATE +#define TOR_X509_PRIVATE + +#ifdef _WIN32 + #include <winsock2.h> + #include <ws2tcpip.h> +#endif + +#include "lib/crypt_ops/crypto_cipher.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_dh.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/crypt_ops/crypto_nss_mgt.h" +#include "lib/string/printf.h" + +#include "lib/tls/x509.h" +#include "lib/tls/x509_internal.h" +#include "lib/tls/tortls.h" +#include "lib/tls/tortls_st.h" +#include "lib/tls/tortls_internal.h" +#include "lib/log/util_bug.h" + +#include <prio.h> +// For access to raw sockets. +#include <private/pprio.h> +#include <ssl.h> +#include <sslt.h> +#include <sslproto.h> +#include <certt.h> + +static SECStatus always_accept_cert_cb(void *, PRFileDesc *, PRBool, PRBool); + +MOCK_IMPL(void, +try_to_extract_certs_from_tls,(int severity, tor_tls_t *tls, + tor_x509_cert_impl_t **cert_out, + tor_x509_cert_impl_t **id_cert_out)) +{ + tor_assert(tls); + tor_assert(cert_out); + tor_assert(id_cert_out); + (void) severity; + + *cert_out = *id_cert_out = NULL; + + CERTCertificate *peer = SSL_PeerCertificate(tls->ssl); + if (!peer) + return; + *cert_out = peer; /* Now owns pointer. */ + + CERTCertList *chain = SSL_PeerCertificateChain(tls->ssl); + CERTCertListNode *c = CERT_LIST_HEAD(chain); + for (; !CERT_LIST_END(c, chain); c = CERT_LIST_NEXT(c)) { + if (CERT_CompareCerts(c->cert, peer) == PR_FALSE) { + *id_cert_out = CERT_DupCertificate(c->cert); + break; + } + } + CERT_DestroyCertList(chain); +} + +static bool +we_like_ssl_cipher(SSLCipherAlgorithm ca) +{ + switch (ca) { + case ssl_calg_null: return false; + case ssl_calg_rc4: return false; + case ssl_calg_rc2: return false; + case ssl_calg_des: return false; + case ssl_calg_3des: return false; /* ???? */ + case ssl_calg_idea: return false; + case ssl_calg_fortezza: return false; + case ssl_calg_camellia: return false; + case ssl_calg_seed: return false; + + case ssl_calg_aes: return true; + case ssl_calg_aes_gcm: return true; + case ssl_calg_chacha20: return true; + default: return true; + } +} +static bool +we_like_ssl_kea(SSLKEAType kt) +{ + switch (kt) { + case ssl_kea_null: return false; + case ssl_kea_rsa: return false; /* ??? */ + case ssl_kea_fortezza: return false; + case ssl_kea_ecdh_psk: return false; + case ssl_kea_dh_psk: return false; + + case ssl_kea_dh: return true; + case ssl_kea_ecdh: return true; + case ssl_kea_tls13_any: return true; + + case ssl_kea_size: return true; /* prevent a warning. */ + default: return true; + } +} + +static bool +we_like_mac_algorithm(SSLMACAlgorithm ma) +{ + switch (ma) { + case ssl_mac_null: return false; + case ssl_mac_md5: return false; + case ssl_hmac_md5: return false; + + case ssl_mac_sha: return true; + case ssl_hmac_sha: return true; + case ssl_hmac_sha256: return true; + case ssl_mac_aead: return true; + case ssl_hmac_sha384: return true; + default: return true; + } +} + +static bool +we_like_auth_type(SSLAuthType at) +{ + switch (at) { + case ssl_auth_null: return false; + case ssl_auth_rsa_decrypt: return false; + case ssl_auth_dsa: return false; + case ssl_auth_kea: return false; + + case ssl_auth_ecdsa: return true; + case ssl_auth_ecdh_rsa: return true; + case ssl_auth_ecdh_ecdsa: return true; + case ssl_auth_rsa_sign: return true; + case ssl_auth_rsa_pss: return true; + case ssl_auth_psk: return true; + case ssl_auth_tls13_any: return true; + + case ssl_auth_size: return true; /* prevent a warning. */ + default: return true; + } +} + +tor_tls_context_t * +tor_tls_context_new(crypto_pk_t *identity, + unsigned int key_lifetime, unsigned flags, int is_client) +{ + SECStatus s; + tor_assert(identity); + + tor_tls_context_t *ctx = tor_malloc_zero(sizeof(tor_tls_context_t)); + ctx->refcnt = 1; + + if (! is_client) { + if (tor_tls_context_init_certificates(ctx, identity, + key_lifetime, flags) < 0) { + goto err; + } + } + + { + /* Create the "model" PRFileDesc that we will use to base others on. */ + PRFileDesc *tcp = PR_NewTCPSocket(); + if (!tcp) + goto err; + + ctx->ctx = SSL_ImportFD(NULL, tcp); + if (!ctx->ctx) { + PR_Close(tcp); + goto err; + } + } + + // Configure the certificate. + if (!is_client) { + s = SSL_ConfigServerCert(ctx->ctx, + ctx->my_link_cert->cert, + (SECKEYPrivateKey *) + crypto_pk_get_nss_privkey(ctx->link_key), + NULL, /* ExtraServerCertData */ + 0 /* DataLen */); + if (s != SECSuccess) + goto err; + } + + // We need a certificate from the other side. + if (is_client) { + // XXXX does this do anything? + s = SSL_OptionSet(ctx->ctx, SSL_REQUIRE_CERTIFICATE, PR_TRUE); + if (s != SECSuccess) + goto err; + } + + // Always accept other side's cert; we'll check it ourselves in goofy + // tor ways. + s = SSL_AuthCertificateHook(ctx->ctx, always_accept_cert_cb, NULL); + + // We allow simultaneous read and write. + s = SSL_OptionSet(ctx->ctx, SSL_ENABLE_FDX, PR_TRUE); + if (s != SECSuccess) + goto err; + // XXXX SSL_ROLLBACK_DETECTION?? + // XXXX SSL_ENABLE_ALPN?? + + // Force client-mode or server_mode. + s = SSL_OptionSet(ctx->ctx, + is_client ? SSL_HANDSHAKE_AS_CLIENT : SSL_HANDSHAKE_AS_SERVER, + PR_TRUE); + if (s != SECSuccess) + goto err; + + // Disable everything before TLS 1.0; support everything else. + { + SSLVersionRange vrange; + memset(&vrange, 0, sizeof(vrange)); + s = SSL_VersionRangeGetSupported(ssl_variant_stream, &vrange); + if (s != SECSuccess) + goto err; + if (vrange.min < SSL_LIBRARY_VERSION_TLS_1_0) + vrange.min = SSL_LIBRARY_VERSION_TLS_1_0; + s = SSL_VersionRangeSet(ctx->ctx, &vrange); + if (s != SECSuccess) + goto err; + } + + // Only support strong ciphers. + { + const PRUint16 *ciphers = SSL_GetImplementedCiphers(); + const PRUint16 n_ciphers = SSL_GetNumImplementedCiphers(); + PRUint16 i; + for (i = 0; i < n_ciphers; ++i) { + SSLCipherSuiteInfo info; + memset(&info, 0, sizeof(info)); + s = SSL_GetCipherSuiteInfo(ciphers[i], &info, sizeof(info)); + if (s != SECSuccess) + goto err; + if (BUG(info.cipherSuite != ciphers[i])) + goto err; + int disable = info.effectiveKeyBits < 128 || + info.macBits < 128 || + !we_like_ssl_cipher(info.symCipher) || + !we_like_ssl_kea(info.keaType) || + !we_like_mac_algorithm(info.macAlgorithm) || + !we_like_auth_type(info.authType)/* Requires NSS 3.24 */; + + s = SSL_CipherPrefSet(ctx->ctx, ciphers[i], + disable ? PR_FALSE : PR_TRUE); + if (s != SECSuccess) + goto err; + } + } + + // Only use DH and ECDH keys once. + s = SSL_OptionSet(ctx->ctx, SSL_REUSE_SERVER_ECDHE_KEY, PR_FALSE); + if (s != SECSuccess) + goto err; + + // don't cache sessions. + s = SSL_OptionSet(ctx->ctx, SSL_NO_CACHE, PR_TRUE); + if (s != SECSuccess) + goto err; + + // Enable DH. + s = SSL_OptionSet(ctx->ctx, SSL_ENABLE_SERVER_DHE, PR_TRUE); + if (s != SECSuccess) + goto err; + + // Set DH and ECDH groups. + SSLNamedGroup groups[] = { + ssl_grp_ec_curve25519, + ssl_grp_ec_secp256r1, + ssl_grp_ec_secp224r1, + ssl_grp_ffdhe_2048, + }; + s = SSL_NamedGroupConfig(ctx->ctx, groups, ARRAY_LENGTH(groups)); + if (s != SECSuccess) + goto err; + + // These features are off by default, so we don't need to disable them: + // Session tickets + // Renegotiation + // Compression + + goto done; + err: + tor_tls_context_decref(ctx); + ctx = NULL; + done: + return ctx; +} + +void +tor_tls_context_impl_free_(tor_tls_context_impl_t *ctx) +{ + if (!ctx) + return; + PR_Close(ctx); +} + +void +tor_tls_get_state_description(tor_tls_t *tls, char *buf, size_t sz) +{ + (void)tls; + (void)buf; + (void)sz; + // AFAICT, NSS doesn't expose its internal state. + buf[0]=0; +} + +void +tor_tls_init(void) +{ + /* We don't have any global setup to do yet, but that will change */ +} + +void +tls_log_errors(tor_tls_t *tls, int severity, int domain, + const char *doing) +{ + /* This implementation is a little different for NSS than it is for OpenSSL + -- it logs the last error whether anything actually failed or not. So we + have to only call it when something has gone wrong and we have a real + error to report. */ + + (void)tls; + PRErrorCode code = PORT_GetError(); + + const char *addr = tls ? tls->address : NULL; + const char *string = PORT_ErrorToString(code); + const char *name = PORT_ErrorToName(code); + char buf[16]; + if (!string) + string = "<unrecognized>"; + if (!name) { + tor_snprintf(buf, sizeof(buf), "%d", code); + name = buf; + } + + const char *with = addr ? " with " : ""; + addr = addr ? addr : ""; + if (doing) { + log_fn(severity, domain, "TLS error %s while %s%s%s: %s", + name, doing, with, addr, string); + } else { + log_fn(severity, domain, "TLS error %s%s%s: %s", name, string, + with, addr); + } +} + +tor_tls_t * +tor_tls_new(tor_socket_t sock, int is_server) +{ + (void)sock; + tor_tls_context_t *ctx = tor_tls_context_get(is_server); + + PRFileDesc *tcp = NULL; + if (SOCKET_OK(sock)) { + tcp = PR_ImportTCPSocket(sock); + } else { + tcp = PR_NewTCPSocket(); + } + + if (!tcp) + return NULL; + + PRFileDesc *ssl = SSL_ImportFD(ctx->ctx, tcp); + if (!ssl) { + PR_Close(tcp); + return NULL; + } + + tor_tls_t *tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->magic = TOR_TLS_MAGIC; + tls->context = ctx; + tor_tls_context_incref(ctx); + tls->ssl = ssl; + tls->socket = sock; + tls->state = TOR_TLS_ST_HANDSHAKE; + tls->isServer = !!is_server; + + if (!is_server) { + /* Set a random SNI */ + char *fake_hostname = crypto_random_hostname(4,25, "www.",".com"); + SSL_SetURL(tls->ssl, fake_hostname); + tor_free(fake_hostname); + } + SECStatus s = SSL_ResetHandshake(ssl, is_server ? PR_TRUE : PR_FALSE); + if (s != SECSuccess) { + tls_log_errors(tls, LOG_WARN, LD_CRYPTO, "resetting handshake state"); + } + + return tls; +} + +void +tor_tls_set_renegotiate_callback(tor_tls_t *tls, + void (*cb)(tor_tls_t *, void *arg), + void *arg) +{ + tor_assert(tls); + (void)cb; + (void)arg; + + /* We don't support renegotiation-based TLS with NSS. */ +} + +void +tor_tls_impl_free_(tor_tls_impl_t *tls) +{ + // XXXX This will close the underlying fd, which our OpenSSL version does + // not do! + if (!tls) + return; + + PR_Close(tls); +} + +int +tor_tls_peer_has_cert(tor_tls_t *tls) +{ + CERTCertificate *cert = SSL_PeerCertificate(tls->ssl); + int result = (cert != NULL); + CERT_DestroyCertificate(cert); + return result; +} + +MOCK_IMPL(tor_x509_cert_t *, +tor_tls_get_peer_cert,(tor_tls_t *tls)) +{ + CERTCertificate *cert = SSL_PeerCertificate(tls->ssl); + if (cert) + return tor_x509_cert_new(cert); + else + return NULL; +} + +MOCK_IMPL(tor_x509_cert_t *, +tor_tls_get_own_cert,(tor_tls_t *tls)) +{ + tor_assert(tls); + CERTCertificate *cert = SSL_LocalCertificate(tls->ssl); + if (cert) + return tor_x509_cert_new(cert); + else + return NULL; +} + +MOCK_IMPL(int, +tor_tls_read, (tor_tls_t *tls, char *cp, size_t len)) +{ + tor_assert(tls); + tor_assert(cp); + tor_assert(len < INT_MAX); + + PRInt32 rv = PR_Read(tls->ssl, cp, (int)len); + // log_debug(LD_NET, "PR_Read(%zu) returned %d", n, (int)rv); + if (rv > 0) { + tls->n_read_since_last_check += rv; + return rv; + } + if (rv == 0) + return TOR_TLS_CLOSE; + PRErrorCode err = PORT_GetError(); + if (err == PR_WOULD_BLOCK_ERROR) { + return TOR_TLS_WANTREAD; // XXXX ???? + } else { + tls_log_errors(tls, LOG_NOTICE, LD_CRYPTO, "reading"); // XXXX + return TOR_TLS_ERROR_MISC; // ???? + } +} + +int +tor_tls_write(tor_tls_t *tls, const char *cp, size_t n) +{ + tor_assert(tls); + tor_assert(cp || n == 0); + tor_assert(n < INT_MAX); + + PRInt32 rv = PR_Write(tls->ssl, cp, (int)n); + // log_debug(LD_NET, "PR_Write(%zu) returned %d", n, (int)rv); + if (rv > 0) { + tls->n_written_since_last_check += rv; + return rv; + } + if (rv == 0) + return TOR_TLS_ERROR_MISC; + PRErrorCode err = PORT_GetError(); + + if (err == PR_WOULD_BLOCK_ERROR) { + return TOR_TLS_WANTWRITE; // XXXX ???? + } else { + tls_log_errors(tls, LOG_NOTICE, LD_CRYPTO, "writing"); // XXXX + return TOR_TLS_ERROR_MISC; // ???? + } +} + +int +tor_tls_handshake(tor_tls_t *tls) +{ + tor_assert(tls); + tor_assert(tls->state == TOR_TLS_ST_HANDSHAKE); + + SECStatus s = SSL_ForceHandshake(tls->ssl); + if (s == SECSuccess) { + tls->state = TOR_TLS_ST_OPEN; + log_debug(LD_NET, "SSL handshake is supposedly complete."); + return tor_tls_finish_handshake(tls); + } + if (PORT_GetError() == PR_WOULD_BLOCK_ERROR) + return TOR_TLS_WANTREAD; /* XXXX What about wantwrite? */ + + return TOR_TLS_ERROR_MISC; // XXXX +} + +int +tor_tls_finish_handshake(tor_tls_t *tls) +{ + tor_assert(tls); + // We don't need to do any of the weird handshake nonsense stuff on NSS, + // since we only support recent handshakes. + return TOR_TLS_DONE; +} + +void +tor_tls_unblock_renegotiation(tor_tls_t *tls) +{ + tor_assert(tls); + /* We don't support renegotiation with NSS. */ +} + +void +tor_tls_block_renegotiation(tor_tls_t *tls) +{ + tor_assert(tls); + /* We don't support renegotiation with NSS. */ +} + +void +tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls) +{ + tor_assert(tls); + /* We don't support renegotiation with NSS. */ +} + +int +tor_tls_get_pending_bytes(tor_tls_t *tls) +{ + tor_assert(tls); + int n = SSL_DataPending(tls->ssl); + if (n < 0) { + tls_log_errors(tls, LOG_WARN, LD_CRYPTO, "looking up pending bytes"); + return 0; + } + return (int)n; +} + +size_t +tor_tls_get_forced_write_size(tor_tls_t *tls) +{ + tor_assert(tls); + /* NSS doesn't have the same "forced write" restriction as openssl. */ + return 0; +} + +void +tor_tls_get_n_raw_bytes(tor_tls_t *tls, + size_t *n_read, size_t *n_written) +{ + tor_assert(tls); + tor_assert(n_read); + tor_assert(n_written); + /* XXXX We don't curently have a way to measure this information correctly + * in NSS; we could do that with a PRIO layer, but it'll take a little + * coding. For now, we just track the number of bytes sent _in_ the TLS + * stream. Doing this will make our rate-limiting slightly inaccurate. */ + *n_read = tls->n_read_since_last_check; + *n_written = tls->n_written_since_last_check; + tls->n_read_since_last_check = tls->n_written_since_last_check = 0; +} + +int +tor_tls_get_buffer_sizes(tor_tls_t *tls, + size_t *rbuf_capacity, size_t *rbuf_bytes, + size_t *wbuf_capacity, size_t *wbuf_bytes) +{ + tor_assert(tls); + tor_assert(rbuf_capacity); + tor_assert(rbuf_bytes); + tor_assert(wbuf_capacity); + tor_assert(wbuf_bytes); + + /* This is an acceptable way to say "we can't measure this." */ + return -1; +} + +MOCK_IMPL(double, +tls_get_write_overhead_ratio, (void)) +{ + /* XXX We don't currently have a way to measure this in NSS; we could do that + * XXX with a PRIO layer, but it'll take a little coding. */ + return 0.95; +} + +int +tor_tls_used_v1_handshake(tor_tls_t *tls) +{ + tor_assert(tls); + /* We don't support or allow the V1 handshake with NSS. + */ + return 0; +} + +int +tor_tls_server_got_renegotiate(tor_tls_t *tls) +{ + tor_assert(tls); + return 0; /* We don't support renegotiation with NSS */ +} + +MOCK_IMPL(int, +tor_tls_cert_matches_key,(const tor_tls_t *tls, + const struct tor_x509_cert_t *cert)) +{ + tor_assert(tls); + tor_assert(cert); + int rv = 0; + + CERTCertificate *peercert = SSL_PeerCertificate(tls->ssl); + if (!peercert) + goto done; + CERTSubjectPublicKeyInfo *peer_info = &peercert->subjectPublicKeyInfo; + CERTSubjectPublicKeyInfo *cert_info = &cert->cert->subjectPublicKeyInfo; + rv = SECOID_CompareAlgorithmID(&peer_info->algorithm, + &cert_info->algorithm) == 0 && + SECITEM_ItemsAreEqual(&peer_info->subjectPublicKey, + &cert_info->subjectPublicKey); + + done: + if (peercert) + CERT_DestroyCertificate(peercert); + return rv; +} + +MOCK_IMPL(int, +tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out)) +{ + tor_assert(tls); + tor_assert(secrets_out); + + /* There's no way to get this information out of NSS. */ + + return -1; +} + +MOCK_IMPL(int, +tor_tls_export_key_material,(tor_tls_t *tls, uint8_t *secrets_out, + const uint8_t *context, + size_t context_len, + const char *label)) +{ + tor_assert(tls); + tor_assert(secrets_out); + tor_assert(context); + tor_assert(label); + tor_assert(strlen(label) <= UINT_MAX); + tor_assert(context_len <= UINT_MAX); + + SECStatus s; + s = SSL_ExportKeyingMaterial(tls->ssl, + label, (unsigned)strlen(label), + PR_TRUE, context, (unsigned)context_len, + secrets_out, DIGEST256_LEN); + + return (s == SECSuccess) ? 0 : -1; +} + +const char * +tor_tls_get_ciphersuite_name(tor_tls_t *tls) +{ + tor_assert(tls); + + SSLChannelInfo channel_info; + SSLCipherSuiteInfo cipher_info; + + memset(&channel_info, 0, sizeof(channel_info)); + memset(&cipher_info, 0, sizeof(cipher_info)); + + SECStatus s = SSL_GetChannelInfo(tls->ssl, + &channel_info, sizeof(channel_info)); + if (s != SECSuccess) + return NULL; + + s = SSL_GetCipherSuiteInfo(channel_info.cipherSuite, + &cipher_info, sizeof(cipher_info)); + if (s != SECSuccess) + return NULL; + + return cipher_info.cipherSuiteName; +} + +/** The group we should use for ecdhe when none was selected. */ +#define SEC_OID_TOR_DEFAULT_ECDHE_GROUP SEC_OID_ANSIX962_EC_PRIME256V1 + +int +evaluate_ecgroup_for_tls(const char *ecgroup) +{ + SECOidTag tag; + + if (!ecgroup) + tag = SEC_OID_TOR_DEFAULT_ECDHE_GROUP; + else if (!strcasecmp(ecgroup, "P256")) + tag = SEC_OID_ANSIX962_EC_PRIME256V1; + else if (!strcasecmp(ecgroup, "P224")) + tag = SEC_OID_SECG_EC_SECP224R1; + else + return 0; + + /* I don't think we need any additional tests here for NSS */ + (void) tag; + + return 1; +} + +static SECStatus +always_accept_cert_cb(void *arg, PRFileDesc *ssl, PRBool checkSig, + PRBool isServer) +{ + (void)arg; + (void)ssl; + (void)checkSig; + (void)isServer; + return SECSuccess; +} diff --git a/src/lib/tls/tortls_openssl.c b/src/lib/tls/tortls_openssl.c new file mode 100644 index 0000000000..ab9712962b --- /dev/null +++ b/src/lib/tls/tortls_openssl.c @@ -0,0 +1,1708 @@ +/* Copyright (c) 2003, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file tortls.c + * \brief Wrapper functions to present a consistent interface to + * TLS, SSL, and X.509 functions from OpenSSL. + **/ + +/* (Unlike other tor functions, these + * are prefixed with tor_ in order to avoid conflicting with OpenSSL + * functions and variables.) + */ + +#include "orconfig.h" + +#define TORTLS_PRIVATE +#define TORTLS_OPENSSL_PRIVATE +#define TOR_X509_PRIVATE + +#ifdef _WIN32 + /* We need to include these here, or else the dtls1.h header will include + * <winsock.h> and mess things up, in at least some openssl versions. */ + #include <winsock2.h> + #include <ws2tcpip.h> +#endif + +#include "lib/crypt_ops/crypto_cipher.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_dh.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/crypt_ops/compat_openssl.h" +#include "lib/tls/x509.h" +#include "lib/tls/x509_internal.h" + +/* Some versions of OpenSSL declare SSL_get_selected_srtp_profile twice in + * srtp.h. Suppress the GCC warning so we can build with -Wredundant-decl. */ +DISABLE_GCC_WARNING(redundant-decls) + +#include <openssl/opensslv.h> + +#ifdef OPENSSL_NO_EC +#error "We require OpenSSL with ECC support" +#endif + +#include <openssl/ssl.h> +#include <openssl/ssl3.h> +#include <openssl/err.h> +#include <openssl/tls1.h> +#include <openssl/asn1.h> +#include <openssl/bio.h> +#include <openssl/bn.h> +#include <openssl/rsa.h> + +ENABLE_GCC_WARNING(redundant-decls) + +#include "lib/tls/tortls.h" +#include "lib/tls/tortls_st.h" +#include "lib/tls/tortls_internal.h" +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/container/smartlist.h" +#include "lib/string/compat_string.h" +#include "lib/string/printf.h" +#include "lib/net/socket.h" +#include "lib/intmath/cmp.h" +#include "lib/ctime/di_ops.h" +#include "lib/encoding/time_fmt.h" + +#include <stdlib.h> +#include <string.h> + +#include "lib/arch/bytes.h" + +/* Copied from or.h */ +#define LEGAL_NICKNAME_CHARACTERS \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +#define ADDR(tls) (((tls) && (tls)->address) ? tls->address : "peer") + +#if OPENSSL_VERSION_NUMBER < OPENSSL_V(1,0,0,'f') +/* This is a version of OpenSSL before 1.0.0f. It does not have + * the CVE-2011-4576 fix, and as such it can't use RELEASE_BUFFERS and + * SSL3 safely at the same time. + */ +#define DISABLE_SSL3_HANDSHAKE +#endif /* OPENSSL_VERSION_NUMBER < OPENSSL_V(1,0,0,'f') */ + +/* We redefine these so that we can run correctly even if the vendor gives us + * a version of OpenSSL that does not match its header files. (Apple: I am + * looking at you.) + */ +#ifndef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION +#define SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION 0x00040000L +#endif +#ifndef SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION +#define SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION 0x0010 +#endif + +/** Return values for tor_tls_classify_client_ciphers. + * + * @{ + */ +/** An error occurred when examining the client ciphers */ +#define CIPHERS_ERR -1 +/** The client cipher list indicates that a v1 handshake was in use. */ +#define CIPHERS_V1 1 +/** The client cipher list indicates that the client is using the v2 or the + * v3 handshake, but that it is (probably!) lying about what ciphers it + * supports */ +#define CIPHERS_V2 2 +/** The client cipher list indicates that the client is using the v2 or the + * v3 handshake, and that it is telling the truth about what ciphers it + * supports */ +#define CIPHERS_UNRESTRICTED 3 +/** @} */ + +/** The ex_data index in which we store a pointer to an SSL object's + * corresponding tor_tls_t object. */ +STATIC int tor_tls_object_ex_data_index = -1; + +/** Helper: Allocate tor_tls_object_ex_data_index. */ +void +tor_tls_allocate_tor_tls_object_ex_data_index(void) +{ + if (tor_tls_object_ex_data_index == -1) { + tor_tls_object_ex_data_index = + SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); + tor_assert(tor_tls_object_ex_data_index != -1); + } +} + +/** Helper: given a SSL* pointer, return the tor_tls_t object using that + * pointer. */ +tor_tls_t * +tor_tls_get_by_ssl(const SSL *ssl) +{ + tor_tls_t *result = SSL_get_ex_data(ssl, tor_tls_object_ex_data_index); + if (result) + tor_assert(result->magic == TOR_TLS_MAGIC); + return result; +} + +/** True iff tor_tls_init() has been called. */ +static int tls_library_is_initialized = 0; + +/* Module-internal error codes. */ +#define TOR_TLS_SYSCALL_ (MIN_TOR_TLS_ERROR_VAL_ - 2) +#define TOR_TLS_ZERORETURN_ (MIN_TOR_TLS_ERROR_VAL_ - 1) + +/** Write a description of the current state of <b>tls</b> into the + * <b>sz</b>-byte buffer at <b>buf</b>. */ +void +tor_tls_get_state_description(tor_tls_t *tls, char *buf, size_t sz) +{ + const char *ssl_state; + const char *tortls_state; + + if (PREDICT_UNLIKELY(!tls || !tls->ssl)) { + strlcpy(buf, "(No SSL object)", sz); + return; + } + + ssl_state = SSL_state_string_long(tls->ssl); + switch (tls->state) { +#define CASE(st) case TOR_TLS_ST_##st: tortls_state = " in "#st ; break + CASE(HANDSHAKE); + CASE(OPEN); + CASE(GOTCLOSE); + CASE(SENTCLOSE); + CASE(CLOSED); + CASE(RENEGOTIATE); +#undef CASE + case TOR_TLS_ST_BUFFEREVENT: + tortls_state = ""; + break; + default: + tortls_state = " in unknown TLS state"; + break; + } + + tor_snprintf(buf, sz, "%s%s", ssl_state, tortls_state); +} + +/** Log a single error <b>err</b> as returned by ERR_get_error(), which was + * received while performing an operation <b>doing</b> on <b>tls</b>. Log + * the message at <b>severity</b>, in log domain <b>domain</b>. */ +void +tor_tls_log_one_error(tor_tls_t *tls, unsigned long err, + int severity, int domain, const char *doing) +{ + const char *state = NULL, *addr; + const char *msg, *lib, *func; + + state = (tls && tls->ssl)?SSL_state_string_long(tls->ssl):"---"; + + addr = tls ? tls->address : NULL; + + /* Some errors are known-benign, meaning they are the fault of the other + * side of the connection. The caller doesn't know this, so override the + * priority for those cases. */ + switch (ERR_GET_REASON(err)) { + case SSL_R_HTTP_REQUEST: + case SSL_R_HTTPS_PROXY_REQUEST: + case SSL_R_RECORD_LENGTH_MISMATCH: +#ifndef OPENSSL_1_1_API + case SSL_R_RECORD_TOO_LARGE: +#endif + case SSL_R_UNKNOWN_PROTOCOL: + case SSL_R_UNSUPPORTED_PROTOCOL: + severity = LOG_INFO; + break; + default: + break; + } + + msg = (const char*)ERR_reason_error_string(err); + lib = (const char*)ERR_lib_error_string(err); + func = (const char*)ERR_func_error_string(err); + if (!msg) msg = "(null)"; + if (!lib) lib = "(null)"; + if (!func) func = "(null)"; + if (doing) { + tor_log(severity, domain, "TLS error while %s%s%s: %s (in %s:%s:%s)", + doing, addr?" with ":"", addr?addr:"", + msg, lib, func, state); + } else { + tor_log(severity, domain, "TLS error%s%s: %s (in %s:%s:%s)", + addr?" with ":"", addr?addr:"", + msg, lib, func, state); + } +} + +/** Log all pending tls errors at level <b>severity</b> in log domain + * <b>domain</b>. Use <b>doing</b> to describe our current activities. + */ +void +tls_log_errors(tor_tls_t *tls, int severity, int domain, const char *doing) +{ + unsigned long err; + + while ((err = ERR_get_error()) != 0) { + tor_tls_log_one_error(tls, err, severity, domain, doing); + } +} + +#define CATCH_SYSCALL 1 +#define CATCH_ZERO 2 + +/** Given a TLS object and the result of an SSL_* call, use + * SSL_get_error to determine whether an error has occurred, and if so + * which one. Return one of TOR_TLS_{DONE|WANTREAD|WANTWRITE|ERROR}. + * If extra&CATCH_SYSCALL is true, return TOR_TLS_SYSCALL_ instead of + * reporting syscall errors. If extra&CATCH_ZERO is true, return + * TOR_TLS_ZERORETURN_ instead of reporting zero-return errors. + * + * If an error has occurred, log it at level <b>severity</b> and describe the + * current action as <b>doing</b>. + */ +int +tor_tls_get_error(tor_tls_t *tls, int r, int extra, + const char *doing, int severity, int domain) +{ + int err = SSL_get_error(tls->ssl, r); + int tor_error = TOR_TLS_ERROR_MISC; + switch (err) { + case SSL_ERROR_NONE: + return TOR_TLS_DONE; + case SSL_ERROR_WANT_READ: + return TOR_TLS_WANTREAD; + case SSL_ERROR_WANT_WRITE: + return TOR_TLS_WANTWRITE; + case SSL_ERROR_SYSCALL: + if (extra&CATCH_SYSCALL) + return TOR_TLS_SYSCALL_; + if (r == 0) { + tor_log(severity, LD_NET, "TLS error: unexpected close while %s (%s)", + doing, SSL_state_string_long(tls->ssl)); + tor_error = TOR_TLS_ERROR_IO; + } else { + int e = tor_socket_errno(tls->socket); + tor_log(severity, LD_NET, + "TLS error: <syscall error while %s> (errno=%d: %s; state=%s)", + doing, e, tor_socket_strerror(e), + SSL_state_string_long(tls->ssl)); + tor_error = tor_errno_to_tls_error(e); + } + tls_log_errors(tls, severity, domain, doing); + return tor_error; + case SSL_ERROR_ZERO_RETURN: + if (extra&CATCH_ZERO) + return TOR_TLS_ZERORETURN_; + tor_log(severity, LD_NET, "TLS connection closed while %s in state %s", + doing, SSL_state_string_long(tls->ssl)); + tls_log_errors(tls, severity, domain, doing); + return TOR_TLS_CLOSE; + default: + tls_log_errors(tls, severity, domain, doing); + return TOR_TLS_ERROR_MISC; + } +} + +/** Initialize OpenSSL, unless it has already been initialized. + */ +void +tor_tls_init(void) +{ + check_no_tls_errors(); + + if (!tls_library_is_initialized) { +#ifdef OPENSSL_1_1_API + OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL); +#else + SSL_library_init(); + SSL_load_error_strings(); +#endif + +#if (SIZEOF_VOID_P >= 8 && \ + OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,0,1)) + long version = OpenSSL_version_num(); + + /* LCOV_EXCL_START : we can't test these lines on the same machine */ + if (version >= OPENSSL_V_SERIES(1,0,1)) { + /* Warn if we could *almost* be running with much faster ECDH. + If we're built for a 64-bit target, using OpenSSL 1.0.1, but we + don't have one of the built-in __uint128-based speedups, we are + just one build operation away from an accelerated handshake. + + (We could be looking at OPENSSL_NO_EC_NISTP_64_GCC_128 instead of + doing this test, but that gives compile-time options, not runtime + behavior.) + */ + EC_KEY *key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + const EC_GROUP *g = key ? EC_KEY_get0_group(key) : NULL; + const EC_METHOD *m = g ? EC_GROUP_method_of(g) : NULL; + const int warn = (m == EC_GFp_simple_method() || + m == EC_GFp_mont_method() || + m == EC_GFp_nist_method()); + EC_KEY_free(key); + + if (warn) + log_notice(LD_GENERAL, "We were built to run on a 64-bit CPU, with " + "OpenSSL 1.0.1 or later, but with a version of OpenSSL " + "that apparently lacks accelerated support for the NIST " + "P-224 and P-256 groups. Building openssl with such " + "support (using the enable-ec_nistp_64_gcc_128 option " + "when configuring it) would make ECDH much faster."); + } + /* LCOV_EXCL_STOP */ +#endif /* (SIZEOF_VOID_P >= 8 && ... */ + + tor_tls_allocate_tor_tls_object_ex_data_index(); + + tls_library_is_initialized = 1; + } +} + +/** We need to give OpenSSL a callback to verify certificates. This is + * it: We always accept peer certs and complete the handshake. We + * don't validate them until later. + */ +int +always_accept_verify_cb(int preverify_ok, + X509_STORE_CTX *x509_ctx) +{ + (void) preverify_ok; + (void) x509_ctx; + return 1; +} + +/** List of ciphers that servers should select from when the client might be + * claiming extra unsupported ciphers in order to avoid fingerprinting. */ +static const char SERVER_CIPHER_LIST[] = +#ifdef TLS1_3_TXT_AES_128_GCM_SHA256 + /* This one can never actually get selected, since if the client lists it, + * we will assume that the client is honest, and not use this list. + * Nonetheless we list it if it's available, so that the server doesn't + * conclude that it has no valid ciphers if it's running with TLS1.3. + */ + TLS1_3_TXT_AES_128_GCM_SHA256 ":" +#endif + TLS1_TXT_DHE_RSA_WITH_AES_256_SHA ":" + TLS1_TXT_DHE_RSA_WITH_AES_128_SHA; + +/** List of ciphers that servers should select from when we actually have + * our choice of what cipher to use. */ +static const char UNRESTRICTED_SERVER_CIPHER_LIST[] = + /* Here are the TLS 1.3 ciphers we like, in the order we prefer. */ +#ifdef TLS1_3_TXT_AES_256_GCM_SHA384 + TLS1_3_TXT_AES_256_GCM_SHA384 ":" +#endif +#ifdef TLS1_3_TXT_CHACHA20_POLY1305_SHA256 + TLS1_3_TXT_CHACHA20_POLY1305_SHA256 ":" +#endif +#ifdef TLS1_3_TXT_AES_128_GCM_SHA256 + TLS1_3_TXT_AES_128_GCM_SHA256 ":" +#endif +#ifdef TLS1_3_TXT_AES_128_CCM_SHA256 + TLS1_3_TXT_AES_128_CCM_SHA256 ":" +#endif + + /* This list is autogenerated with the gen_server_ciphers.py script; + * don't hand-edit it. */ +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ":" +#endif +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + TLS1_TXT_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ":" +#endif +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_256_SHA384 + TLS1_TXT_ECDHE_RSA_WITH_AES_256_SHA384 ":" +#endif +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_128_SHA256 + TLS1_TXT_ECDHE_RSA_WITH_AES_128_SHA256 ":" +#endif +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA + TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA ":" +#endif +#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA + TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA ":" +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_AES_256_GCM_SHA384 + TLS1_TXT_DHE_RSA_WITH_AES_256_GCM_SHA384 ":" +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_AES_128_GCM_SHA256 + TLS1_TXT_DHE_RSA_WITH_AES_128_GCM_SHA256 ":" +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_AES_256_CCM + TLS1_TXT_DHE_RSA_WITH_AES_256_CCM ":" +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_AES_128_CCM + TLS1_TXT_DHE_RSA_WITH_AES_128_CCM ":" +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_AES_256_SHA256 + TLS1_TXT_DHE_RSA_WITH_AES_256_SHA256 ":" +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_AES_128_SHA256 + TLS1_TXT_DHE_RSA_WITH_AES_128_SHA256 ":" +#endif + /* Required */ + TLS1_TXT_DHE_RSA_WITH_AES_256_SHA ":" + /* Required */ + TLS1_TXT_DHE_RSA_WITH_AES_128_SHA ":" +#ifdef TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305 + TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305 ":" +#endif +#ifdef TLS1_TXT_DHE_RSA_WITH_CHACHA20_POLY1305 + TLS1_TXT_DHE_RSA_WITH_CHACHA20_POLY1305 +#endif + ; + +/* Note: to set up your own private testing network with link crypto + * disabled, set your Tors' cipher list to + * (SSL3_TXT_RSA_NULL_SHA). If you do this, you won't be able to communicate + * with any of the "real" Tors, though. */ + +#define CIPHER(id, name) name ":" +#define XCIPHER(id, name) +/** List of ciphers that clients should advertise, omitting items that + * our OpenSSL doesn't know about. */ +static const char CLIENT_CIPHER_LIST[] = +#include "ciphers.inc" + /* Tell it not to use SSLv2 ciphers, so that it can select an SSLv3 version + * of any cipher we say. */ + "!SSLv2" + ; +#undef CIPHER +#undef XCIPHER + +/** Return true iff the other side of <b>tls</b> has authenticated to us, and + * the key certified in <b>cert</b> is the same as the key they used to do it. + */ +MOCK_IMPL(int, +tor_tls_cert_matches_key,(const tor_tls_t *tls, const tor_x509_cert_t *cert)) +{ + tor_x509_cert_t *peer = tor_tls_get_peer_cert((tor_tls_t *)tls); + if (!peer) + return 0; + + X509 *peercert = peer->cert; + EVP_PKEY *link_key = NULL, *cert_key = NULL; + int result; + + link_key = X509_get_pubkey(peercert); + cert_key = X509_get_pubkey(cert->cert); + + result = link_key && cert_key && EVP_PKEY_cmp(cert_key, link_key) == 1; + + tor_x509_cert_free(peer); + if (link_key) + EVP_PKEY_free(link_key); + if (cert_key) + EVP_PKEY_free(cert_key); + + return result; +} + +void +tor_tls_context_impl_free_(struct ssl_ctx_st *ctx) +{ + if (!ctx) + return; + SSL_CTX_free(ctx); +} + +/** The group we should use for ecdhe when none was selected. */ +#define NID_tor_default_ecdhe_group NID_X9_62_prime256v1 + +/** Create a new TLS context for use with Tor TLS handshakes. + * <b>identity</b> should be set to the identity key used to sign the + * certificate. + */ +tor_tls_context_t * +tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime, + unsigned flags, int is_client) +{ + EVP_PKEY *pkey = NULL; + tor_tls_context_t *result = NULL; + + tor_tls_init(); + + result = tor_malloc_zero(sizeof(tor_tls_context_t)); + result->refcnt = 1; + + if (! is_client) { + if (tor_tls_context_init_certificates(result, identity, key_lifetime, + flags) < 0) { + goto error; + } + } + +#if 0 + /* Tell OpenSSL to only use TLS1. This may have subtly different results + * from SSLv23_method() with SSLv2 and SSLv3 disabled, so we need to do some + * investigation before we consider adjusting it. It should be compatible + * with existing Tors. */ + if (!(result->ctx = SSL_CTX_new(TLSv1_method()))) + goto error; +#endif /* 0 */ + + /* Tell OpenSSL to use TLS 1.0 or later but not SSL2 or SSL3. */ +#ifdef HAVE_TLS_METHOD + if (!(result->ctx = SSL_CTX_new(TLS_method()))) + goto error; +#else + if (!(result->ctx = SSL_CTX_new(SSLv23_method()))) + goto error; +#endif /* defined(HAVE_TLS_METHOD) */ + SSL_CTX_set_options(result->ctx, SSL_OP_NO_SSLv2); + SSL_CTX_set_options(result->ctx, SSL_OP_NO_SSLv3); + + /* Prefer the server's ordering of ciphers: the client's ordering has + * historically been chosen for fingerprinting resistance. */ + SSL_CTX_set_options(result->ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + + /* Disable TLS tickets if they're supported. We never want to use them; + * using them can make our perfect forward secrecy a little worse, *and* + * create an opportunity to fingerprint us (since it's unusual to use them + * with TLS sessions turned off). + * + * In 0.2.4, clients advertise support for them though, to avoid a TLS + * distinguishability vector. This can give us worse PFS, though, if we + * get a server that doesn't set SSL_OP_NO_TICKET. With luck, there will + * be few such servers by the time 0.2.4 is more stable. + */ +#ifdef SSL_OP_NO_TICKET + if (! is_client) { + SSL_CTX_set_options(result->ctx, SSL_OP_NO_TICKET); + } +#endif + + SSL_CTX_set_options(result->ctx, SSL_OP_SINGLE_DH_USE); + SSL_CTX_set_options(result->ctx, SSL_OP_SINGLE_ECDH_USE); + +#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION + SSL_CTX_set_options(result->ctx, + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); +#endif + /* Yes, we know what we are doing here. No, we do not treat a renegotiation + * as authenticating any earlier-received data. + */ + { + SSL_CTX_set_options(result->ctx, + SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); + } + + /* Don't actually allow compression; it uses RAM and time, it makes TLS + * vulnerable to CRIME-style attacks, and most of the data we transmit over + * TLS is encrypted (and therefore uncompressible) anyway. */ +#ifdef SSL_OP_NO_COMPRESSION + SSL_CTX_set_options(result->ctx, SSL_OP_NO_COMPRESSION); +#endif +#if OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,1,0) +#ifndef OPENSSL_NO_COMP + if (result->ctx->comp_methods) + result->ctx->comp_methods = NULL; +#endif +#endif /* OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,1,0) */ + +#ifdef SSL_MODE_RELEASE_BUFFERS + SSL_CTX_set_mode(result->ctx, SSL_MODE_RELEASE_BUFFERS); +#endif + if (! is_client) { + if (result->my_link_cert && + !SSL_CTX_use_certificate(result->ctx, + result->my_link_cert->cert)) { + goto error; + } + if (result->my_id_cert) { + X509_STORE *s = SSL_CTX_get_cert_store(result->ctx); + tor_assert(s); + X509_STORE_add_cert(s, result->my_id_cert->cert); + } + } + SSL_CTX_set_session_cache_mode(result->ctx, SSL_SESS_CACHE_OFF); + if (!is_client) { + tor_assert(result->link_key); + if (!(pkey = crypto_pk_get_openssl_evp_pkey_(result->link_key,1))) + goto error; + if (!SSL_CTX_use_PrivateKey(result->ctx, pkey)) + goto error; + EVP_PKEY_free(pkey); + pkey = NULL; + if (!SSL_CTX_check_private_key(result->ctx)) + goto error; + } + + { + DH *dh = crypto_dh_new_openssl_tls(); + tor_assert(dh); + SSL_CTX_set_tmp_dh(result->ctx, dh); + DH_free(dh); + } + if (! is_client) { + int nid; + EC_KEY *ec_key; + if (flags & TOR_TLS_CTX_USE_ECDHE_P224) + nid = NID_secp224r1; + else if (flags & TOR_TLS_CTX_USE_ECDHE_P256) + nid = NID_X9_62_prime256v1; + else + nid = NID_tor_default_ecdhe_group; + /* Use P-256 for ECDHE. */ + ec_key = EC_KEY_new_by_curve_name(nid); + if (ec_key != NULL) /*XXXX Handle errors? */ + SSL_CTX_set_tmp_ecdh(result->ctx, ec_key); + EC_KEY_free(ec_key); + } + SSL_CTX_set_verify(result->ctx, SSL_VERIFY_PEER, + always_accept_verify_cb); + /* let us realloc bufs that we're writing from */ + SSL_CTX_set_mode(result->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + return result; + + error: + tls_log_errors(NULL, LOG_WARN, LD_NET, "creating TLS context"); + if (pkey) + EVP_PKEY_free(pkey); + if (result) + tor_tls_context_decref(result); + return NULL; +} + +/** Invoked when a TLS state changes: log the change at severity 'debug' */ +void +tor_tls_debug_state_callback(const SSL *ssl, int type, int val) +{ + /* LCOV_EXCL_START since this depends on whether debug is captured or not */ + log_debug(LD_HANDSHAKE, "SSL %p is now in state %s [type=%d,val=%d].", + ssl, SSL_state_string_long(ssl), type, val); + /* LCOV_EXCL_STOP */ +} + +/* Return the name of the negotiated ciphersuite in use on <b>tls</b> */ +const char * +tor_tls_get_ciphersuite_name(tor_tls_t *tls) +{ + return SSL_get_cipher(tls->ssl); +} + +/* Here's the old V2 cipher list we sent from 0.2.1.1-alpha up to + * 0.2.3.17-beta. If a client is using this list, we can't believe the ciphers + * that it claims to support. We'll prune this list to remove the ciphers + * *we* don't recognize. */ +STATIC uint16_t v2_cipher_list[] = { + 0xc00a, /* TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_CBC_SHA */ + 0xc014, /* TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA */ + 0x0039, /* TLS1_TXT_DHE_RSA_WITH_AES_256_SHA */ + 0x0038, /* TLS1_TXT_DHE_DSS_WITH_AES_256_SHA */ + 0xc00f, /* TLS1_TXT_ECDH_RSA_WITH_AES_256_CBC_SHA */ + 0xc005, /* TLS1_TXT_ECDH_ECDSA_WITH_AES_256_CBC_SHA */ + 0x0035, /* TLS1_TXT_RSA_WITH_AES_256_SHA */ + 0xc007, /* TLS1_TXT_ECDHE_ECDSA_WITH_RC4_128_SHA */ + 0xc009, /* TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_CBC_SHA */ + 0xc011, /* TLS1_TXT_ECDHE_RSA_WITH_RC4_128_SHA */ + 0xc013, /* TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA */ + 0x0033, /* TLS1_TXT_DHE_RSA_WITH_AES_128_SHA */ + 0x0032, /* TLS1_TXT_DHE_DSS_WITH_AES_128_SHA */ + 0xc00c, /* TLS1_TXT_ECDH_RSA_WITH_RC4_128_SHA */ + 0xc00e, /* TLS1_TXT_ECDH_RSA_WITH_AES_128_CBC_SHA */ + 0xc002, /* TLS1_TXT_ECDH_ECDSA_WITH_RC4_128_SHA */ + 0xc004, /* TLS1_TXT_ECDH_ECDSA_WITH_AES_128_CBC_SHA */ + 0x0004, /* SSL3_TXT_RSA_RC4_128_MD5 */ + 0x0005, /* SSL3_TXT_RSA_RC4_128_SHA */ + 0x002f, /* TLS1_TXT_RSA_WITH_AES_128_SHA */ + 0xc008, /* TLS1_TXT_ECDHE_ECDSA_WITH_DES_192_CBC3_SHA */ + 0xc012, /* TLS1_TXT_ECDHE_RSA_WITH_DES_192_CBC3_SHA */ + 0x0016, /* SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA */ + 0x0013, /* SSL3_TXT_EDH_DSS_DES_192_CBC3_SHA */ + 0xc00d, /* TLS1_TXT_ECDH_RSA_WITH_DES_192_CBC3_SHA */ + 0xc003, /* TLS1_TXT_ECDH_ECDSA_WITH_DES_192_CBC3_SHA */ + 0xfeff, /* SSL3_TXT_RSA_FIPS_WITH_3DES_EDE_CBC_SHA */ + 0x000a, /* SSL3_TXT_RSA_DES_192_CBC3_SHA */ + 0 +}; +/** Have we removed the unrecognized ciphers from v2_cipher_list yet? */ +static int v2_cipher_list_pruned = 0; + +/** Return 0 if <b>m</b> does not support the cipher with ID <b>cipher</b>; + * return 1 if it does support it, or if we have no way to tell. */ +int +find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, uint16_t cipher) +{ + const SSL_CIPHER *c; +#ifdef HAVE_SSL_CIPHER_FIND + (void) m; + { + unsigned char cipherid[3]; + tor_assert(ssl); + set_uint16(cipherid, tor_htons(cipher)); + cipherid[2] = 0; /* If ssl23_get_cipher_by_char finds no cipher starting + * with a two-byte 'cipherid', it may look for a v2 + * cipher with the appropriate 3 bytes. */ + c = SSL_CIPHER_find((SSL*)ssl, cipherid); + if (c) + tor_assert((SSL_CIPHER_get_id(c) & 0xffff) == cipher); + return c != NULL; + } +#else /* !(defined(HAVE_SSL_CIPHER_FIND)) */ + +# if defined(HAVE_STRUCT_SSL_METHOD_ST_GET_CIPHER_BY_CHAR) + if (m && m->get_cipher_by_char) { + unsigned char cipherid[3]; + set_uint16(cipherid, tor_htons(cipher)); + cipherid[2] = 0; /* If ssl23_get_cipher_by_char finds no cipher starting + * with a two-byte 'cipherid', it may look for a v2 + * cipher with the appropriate 3 bytes. */ + c = m->get_cipher_by_char(cipherid); + if (c) + tor_assert((c->id & 0xffff) == cipher); + return c != NULL; + } +#endif /* defined(HAVE_STRUCT_SSL_METHOD_ST_GET_CIPHER_BY_CHAR) */ +# ifndef OPENSSL_1_1_API + if (m && m->get_cipher && m->num_ciphers) { + /* It would seem that some of the "let's-clean-up-openssl" forks have + * removed the get_cipher_by_char function. Okay, so now you get a + * quadratic search. + */ + int i; + for (i = 0; i < m->num_ciphers(); ++i) { + c = m->get_cipher(i); + if (c && (c->id & 0xffff) == cipher) { + return 1; + } + } + return 0; + } +#endif /* !defined(OPENSSL_1_1_API) */ + (void) ssl; + (void) m; + (void) cipher; + return 1; /* No way to search */ +#endif /* defined(HAVE_SSL_CIPHER_FIND) */ +} + +/** Remove from v2_cipher_list every cipher that we don't support, so that + * comparing v2_cipher_list to a client's cipher list will give a sensible + * result. */ +static void +prune_v2_cipher_list(const SSL *ssl) +{ + uint16_t *inp, *outp; +#ifdef HAVE_TLS_METHOD + const SSL_METHOD *m = TLS_method(); +#else + const SSL_METHOD *m = SSLv23_method(); +#endif + + inp = outp = v2_cipher_list; + while (*inp) { + if (find_cipher_by_id(ssl, m, *inp)) { + *outp++ = *inp++; + } else { + inp++; + } + } + *outp = 0; + + v2_cipher_list_pruned = 1; +} + +/** Examine the client cipher list in <b>ssl</b>, and determine what kind of + * client it is. Return one of CIPHERS_ERR, CIPHERS_V1, CIPHERS_V2, + * CIPHERS_UNRESTRICTED. + **/ +int +tor_tls_classify_client_ciphers(const SSL *ssl, + STACK_OF(SSL_CIPHER) *peer_ciphers) +{ + int i, res; + tor_tls_t *tor_tls; + if (PREDICT_UNLIKELY(!v2_cipher_list_pruned)) + prune_v2_cipher_list(ssl); + + tor_tls = tor_tls_get_by_ssl(ssl); + if (tor_tls && tor_tls->client_cipher_list_type) + return tor_tls->client_cipher_list_type; + + /* If we reached this point, we just got a client hello. See if there is + * a cipher list. */ + if (!peer_ciphers) { + log_info(LD_NET, "No ciphers on session"); + res = CIPHERS_ERR; + goto done; + } + /* Now we need to see if there are any ciphers whose presence means we're + * dealing with an updated Tor. */ + for (i = 0; i < sk_SSL_CIPHER_num(peer_ciphers); ++i) { + const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(peer_ciphers, i); + const char *ciphername = SSL_CIPHER_get_name(cipher); + if (strcmp(ciphername, TLS1_TXT_DHE_RSA_WITH_AES_128_SHA) && + strcmp(ciphername, TLS1_TXT_DHE_RSA_WITH_AES_256_SHA) && + strcmp(ciphername, SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA) && + strcmp(ciphername, "(NONE)")) { + log_debug(LD_NET, "Got a non-version-1 cipher called '%s'", ciphername); + // return 1; + goto v2_or_higher; + } + } + res = CIPHERS_V1; + goto done; + v2_or_higher: + { + const uint16_t *v2_cipher = v2_cipher_list; + for (i = 0; i < sk_SSL_CIPHER_num(peer_ciphers); ++i) { + const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(peer_ciphers, i); + uint16_t id = SSL_CIPHER_get_id(cipher) & 0xffff; + if (id == 0x00ff) /* extended renegotiation indicator. */ + continue; + if (!id || id != *v2_cipher) { + res = CIPHERS_UNRESTRICTED; + goto dump_ciphers; + } + ++v2_cipher; + } + if (*v2_cipher != 0) { + res = CIPHERS_UNRESTRICTED; + goto dump_ciphers; + } + res = CIPHERS_V2; + } + + dump_ciphers: + { + smartlist_t *elts = smartlist_new(); + char *s; + for (i = 0; i < sk_SSL_CIPHER_num(peer_ciphers); ++i) { + const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(peer_ciphers, i); + const char *ciphername = SSL_CIPHER_get_name(cipher); + smartlist_add(elts, (char*)ciphername); + } + s = smartlist_join_strings(elts, ":", 0, NULL); + log_debug(LD_NET, "Got a %s V2/V3 cipher list from %s. It is: '%s'", + (res == CIPHERS_V2) ? "fictitious" : "real", ADDR(tor_tls), s); + tor_free(s); + smartlist_free(elts); + } + done: + if (tor_tls) + return tor_tls->client_cipher_list_type = res; + + return res; +} + +/** Return true iff the cipher list suggested by the client for <b>ssl</b> is + * a list that indicates that the client knows how to do the v2 TLS connection + * handshake. */ +int +tor_tls_client_is_using_v2_ciphers(const SSL *ssl) +{ + STACK_OF(SSL_CIPHER) *ciphers; +#ifdef HAVE_SSL_GET_CLIENT_CIPHERS + ciphers = SSL_get_client_ciphers(ssl); +#else + SSL_SESSION *session; + if (!(session = SSL_get_session((SSL *)ssl))) { + log_info(LD_NET, "No session on TLS?"); + return CIPHERS_ERR; + } + ciphers = session->ciphers; +#endif /* defined(HAVE_SSL_GET_CLIENT_CIPHERS) */ + + return tor_tls_classify_client_ciphers(ssl, ciphers) >= CIPHERS_V2; +} + +/** Invoked when we're accepting a connection on <b>ssl</b>, and the connection + * changes state. We use this: + * <ul><li>To alter the state of the handshake partway through, so we + * do not send or request extra certificates in v2 handshakes.</li> + * <li>To detect renegotiation</li></ul> + */ +void +tor_tls_server_info_callback(const SSL *ssl, int type, int val) +{ + tor_tls_t *tls; + (void) val; + + IF_BUG_ONCE(ssl == NULL) { + return; // LCOV_EXCL_LINE + } + + tor_tls_debug_state_callback(ssl, type, val); + + if (type != SSL_CB_ACCEPT_LOOP) + return; + + OSSL_HANDSHAKE_STATE ssl_state = SSL_get_state(ssl); + if (! STATE_IS_SW_SERVER_HELLO(ssl_state)) + return; + tls = tor_tls_get_by_ssl(ssl); + if (tls) { + /* Check whether we're watching for renegotiates. If so, this is one! */ + if (tls->negotiated_callback) + tls->got_renegotiate = 1; + } else { + log_warn(LD_BUG, "Couldn't look up the tls for an SSL*. How odd!"); + return; + } + + /* Now check the cipher list. */ + if (tor_tls_client_is_using_v2_ciphers(ssl)) { + if (tls->wasV2Handshake) + return; /* We already turned this stuff off for the first handshake; + * This is a renegotiation. */ + + /* Yes, we're casting away the const from ssl. This is very naughty of us. + * Let's hope openssl doesn't notice! */ + + /* Set SSL_MODE_NO_AUTO_CHAIN to keep from sending back any extra certs. */ + SSL_set_mode((SSL*) ssl, SSL_MODE_NO_AUTO_CHAIN); + /* Don't send a hello request. */ + SSL_set_verify((SSL*) ssl, SSL_VERIFY_NONE, NULL); + + if (tls) { + tls->wasV2Handshake = 1; + } else { + /* LCOV_EXCL_START this line is not reachable */ + log_warn(LD_BUG, "Couldn't look up the tls for an SSL*. How odd!"); + /* LCOV_EXCL_STOP */ + } + } +} + +/** Callback to get invoked on a server after we've read the list of ciphers + * the client supports, but before we pick our own ciphersuite. + * + * We can't abuse an info_cb for this, since by the time one of the + * client_hello info_cbs is called, we've already picked which ciphersuite to + * use. + * + * Technically, this function is an abuse of this callback, since the point of + * a session_secret_cb is to try to set up and/or verify a shared-secret for + * authentication on the fly. But as long as we return 0, we won't actually be + * setting up a shared secret, and all will be fine. + */ +int +tor_tls_session_secret_cb(SSL *ssl, void *secret, int *secret_len, + STACK_OF(SSL_CIPHER) *peer_ciphers, + CONST_IF_OPENSSL_1_1_API SSL_CIPHER **cipher, + void *arg) +{ + (void) secret; + (void) secret_len; + (void) peer_ciphers; + (void) cipher; + (void) arg; + + if (tor_tls_classify_client_ciphers(ssl, peer_ciphers) == + CIPHERS_UNRESTRICTED) { + SSL_set_cipher_list(ssl, UNRESTRICTED_SERVER_CIPHER_LIST); + } + + SSL_set_session_secret_cb(ssl, NULL, NULL); + + return 0; +} +static void +tor_tls_setup_session_secret_cb(tor_tls_t *tls) +{ + SSL_set_session_secret_cb(tls->ssl, tor_tls_session_secret_cb, NULL); +} + +/** Create a new TLS object from a file descriptor, and a flag to + * determine whether it is functioning as a server. + */ +tor_tls_t * +tor_tls_new(int sock, int isServer) +{ + BIO *bio = NULL; + tor_tls_t *result = tor_malloc_zero(sizeof(tor_tls_t)); + tor_tls_context_t *context = tor_tls_context_get(isServer); + result->magic = TOR_TLS_MAGIC; + + check_no_tls_errors(); + tor_assert(context); /* make sure somebody made it first */ + if (!(result->ssl = SSL_new(context->ctx))) { + tls_log_errors(NULL, LOG_WARN, LD_NET, "creating SSL object"); + tor_free(result); + goto err; + } + +#ifdef SSL_set_tlsext_host_name + /* Browsers use the TLS hostname extension, so we should too. */ + if (!isServer) { + char *fake_hostname = crypto_random_hostname(4,25, "www.",".com"); + SSL_set_tlsext_host_name(result->ssl, fake_hostname); + tor_free(fake_hostname); + } +#endif /* defined(SSL_set_tlsext_host_name) */ + + if (!SSL_set_cipher_list(result->ssl, + isServer ? SERVER_CIPHER_LIST : CLIENT_CIPHER_LIST)) { + tls_log_errors(NULL, LOG_WARN, LD_NET, "setting ciphers"); +#ifdef SSL_set_tlsext_host_name + SSL_set_tlsext_host_name(result->ssl, NULL); +#endif + SSL_free(result->ssl); + tor_free(result); + goto err; + } + result->socket = sock; + bio = BIO_new_socket(sock, 0); + if (! bio) { + tls_log_errors(NULL, LOG_WARN, LD_NET, "opening BIO"); +#ifdef SSL_set_tlsext_host_name + SSL_set_tlsext_host_name(result->ssl, NULL); +#endif + SSL_free(result->ssl); + tor_free(result); + goto err; + } + { + int set_worked = + SSL_set_ex_data(result->ssl, tor_tls_object_ex_data_index, result); + if (!set_worked) { + log_warn(LD_BUG, + "Couldn't set the tls for an SSL*; connection will fail"); + } + } + SSL_set_bio(result->ssl, bio, bio); + tor_tls_context_incref(context); + result->context = context; + result->state = TOR_TLS_ST_HANDSHAKE; + result->isServer = isServer; + result->wantwrite_n = 0; + result->last_write_count = (unsigned long) BIO_number_written(bio); + result->last_read_count = (unsigned long) BIO_number_read(bio); + if (result->last_write_count || result->last_read_count) { + log_warn(LD_NET, "Newly created BIO has read count %lu, write count %lu", + result->last_read_count, result->last_write_count); + } + if (isServer) { + SSL_set_info_callback(result->ssl, tor_tls_server_info_callback); + } else { + SSL_set_info_callback(result->ssl, tor_tls_debug_state_callback); + } + + if (isServer) + tor_tls_setup_session_secret_cb(result); + + goto done; + err: + result = NULL; + done: + /* Not expected to get called. */ + tls_log_errors(NULL, LOG_WARN, LD_NET, "creating tor_tls_t object"); + return result; +} + +/** Set <b>cb</b> to be called with argument <b>arg</b> whenever <b>tls</b> + * next gets a client-side renegotiate in the middle of a read. Do not + * invoke this function until <em>after</em> initial handshaking is done! + */ +void +tor_tls_set_renegotiate_callback(tor_tls_t *tls, + void (*cb)(tor_tls_t *, void *arg), + void *arg) +{ + tls->negotiated_callback = cb; + tls->callback_arg = arg; + tls->got_renegotiate = 0; + if (cb) { + SSL_set_info_callback(tls->ssl, tor_tls_server_info_callback); + } else { + SSL_set_info_callback(tls->ssl, tor_tls_debug_state_callback); + } +} + +/** If this version of openssl requires it, turn on renegotiation on + * <b>tls</b>. + */ +void +tor_tls_unblock_renegotiation(tor_tls_t *tls) +{ + /* Yes, we know what we are doing here. No, we do not treat a renegotiation + * as authenticating any earlier-received data. */ + SSL_set_options(tls->ssl, + SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); +} + +/** If this version of openssl supports it, turn off renegotiation on + * <b>tls</b>. (Our protocol never requires this for security, but it's nice + * to use belt-and-suspenders here.) + */ +void +tor_tls_block_renegotiation(tor_tls_t *tls) +{ +#ifdef SUPPORT_UNSAFE_RENEGOTIATION_FLAG + tls->ssl->s3->flags &= ~SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; +#else + (void) tls; +#endif +} + +/** Assert that the flags that allow legacy renegotiation are still set */ +void +tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls) +{ +#if defined(SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION) && \ + SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION != 0 + long options = SSL_get_options(tls->ssl); + tor_assert(0 != (options & SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)); +#else + (void) tls; +#endif /* defined(SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION) && ... */ +} + +void +tor_tls_impl_free_(tor_tls_impl_t *ssl) +{ + if (!ssl) + return; + +#ifdef SSL_set_tlsext_host_name + SSL_set_tlsext_host_name(ssl, NULL); +#endif + SSL_free(ssl); +} + +/** Underlying function for TLS reading. Reads up to <b>len</b> + * characters from <b>tls</b> into <b>cp</b>. On success, returns the + * number of characters read. On failure, returns TOR_TLS_ERROR, + * TOR_TLS_CLOSE, TOR_TLS_WANTREAD, or TOR_TLS_WANTWRITE. + */ +MOCK_IMPL(int, +tor_tls_read,(tor_tls_t *tls, char *cp, size_t len)) +{ + int r, err; + tor_assert(tls); + tor_assert(tls->ssl); + tor_assert(tls->state == TOR_TLS_ST_OPEN); + tor_assert(len<INT_MAX); + r = SSL_read(tls->ssl, cp, (int)len); + if (r > 0) { + if (tls->got_renegotiate) { + /* Renegotiation happened! */ + log_info(LD_NET, "Got a TLS renegotiation from %s", ADDR(tls)); + if (tls->negotiated_callback) + tls->negotiated_callback(tls, tls->callback_arg); + tls->got_renegotiate = 0; + } + return r; + } + err = tor_tls_get_error(tls, r, CATCH_ZERO, "reading", LOG_DEBUG, LD_NET); + if (err == TOR_TLS_ZERORETURN_ || err == TOR_TLS_CLOSE) { + log_debug(LD_NET,"read returned r=%d; TLS is closed",r); + tls->state = TOR_TLS_ST_CLOSED; + return TOR_TLS_CLOSE; + } else { + tor_assert(err != TOR_TLS_DONE); + log_debug(LD_NET,"read returned r=%d, err=%d",r,err); + return err; + } +} + +/** Total number of bytes that we've used TLS to send. Used to track TLS + * overhead. */ +STATIC uint64_t total_bytes_written_over_tls = 0; +/** Total number of bytes that TLS has put on the network for us. Used to + * track TLS overhead. */ +STATIC uint64_t total_bytes_written_by_tls = 0; + +/** Underlying function for TLS writing. Write up to <b>n</b> + * characters from <b>cp</b> onto <b>tls</b>. On success, returns the + * number of characters written. On failure, returns TOR_TLS_ERROR, + * TOR_TLS_WANTREAD, or TOR_TLS_WANTWRITE. + */ +int +tor_tls_write(tor_tls_t *tls, const char *cp, size_t n) +{ + int r, err; + tor_assert(tls); + tor_assert(tls->ssl); + tor_assert(tls->state == TOR_TLS_ST_OPEN); + tor_assert(n < INT_MAX); + if (n == 0) + return 0; + if (tls->wantwrite_n) { + /* if WANTWRITE last time, we must use the _same_ n as before */ + tor_assert(n >= tls->wantwrite_n); + log_debug(LD_NET,"resuming pending-write, (%d to flush, reusing %d)", + (int)n, (int)tls->wantwrite_n); + n = tls->wantwrite_n; + tls->wantwrite_n = 0; + } + r = SSL_write(tls->ssl, cp, (int)n); + err = tor_tls_get_error(tls, r, 0, "writing", LOG_INFO, LD_NET); + if (err == TOR_TLS_DONE) { + total_bytes_written_over_tls += r; + return r; + } + if (err == TOR_TLS_WANTWRITE || err == TOR_TLS_WANTREAD) { + tls->wantwrite_n = n; + } + return err; +} + +/** Perform initial handshake on <b>tls</b>. When finished, returns + * TOR_TLS_DONE. On failure, returns TOR_TLS_ERROR, TOR_TLS_WANTREAD, + * or TOR_TLS_WANTWRITE. + */ +int +tor_tls_handshake(tor_tls_t *tls) +{ + int r; + tor_assert(tls); + tor_assert(tls->ssl); + tor_assert(tls->state == TOR_TLS_ST_HANDSHAKE); + + check_no_tls_errors(); + + OSSL_HANDSHAKE_STATE oldstate = SSL_get_state(tls->ssl); + + if (tls->isServer) { + log_debug(LD_HANDSHAKE, "About to call SSL_accept on %p (%s)", tls, + SSL_state_string_long(tls->ssl)); + r = SSL_accept(tls->ssl); + } else { + log_debug(LD_HANDSHAKE, "About to call SSL_connect on %p (%s)", tls, + SSL_state_string_long(tls->ssl)); + r = SSL_connect(tls->ssl); + } + + OSSL_HANDSHAKE_STATE newstate = SSL_get_state(tls->ssl); + + if (oldstate != newstate) + log_debug(LD_HANDSHAKE, "After call, %p was in state %s", + tls, SSL_state_string_long(tls->ssl)); + /* We need to call this here and not earlier, since OpenSSL has a penchant + * for clearing its flags when you say accept or connect. */ + tor_tls_unblock_renegotiation(tls); + r = tor_tls_get_error(tls,r,0, "handshaking", LOG_INFO, LD_HANDSHAKE); + if (ERR_peek_error() != 0) { + tls_log_errors(tls, tls->isServer ? LOG_INFO : LOG_WARN, LD_HANDSHAKE, + "handshaking"); + return TOR_TLS_ERROR_MISC; + } + if (r == TOR_TLS_DONE) { + tls->state = TOR_TLS_ST_OPEN; + return tor_tls_finish_handshake(tls); + } + return r; +} + +/** Perform the final part of the initial TLS handshake on <b>tls</b>. This + * should be called for the first handshake only: it determines whether the v1 + * or the v2 handshake was used, and adjusts things for the renegotiation + * handshake as appropriate. + * + * tor_tls_handshake() calls this on its own; you only need to call this if + * bufferevent is doing the handshake for you. + */ +int +tor_tls_finish_handshake(tor_tls_t *tls) +{ + int r = TOR_TLS_DONE; + check_no_tls_errors(); + if (tls->isServer) { + SSL_set_info_callback(tls->ssl, NULL); + SSL_set_verify(tls->ssl, SSL_VERIFY_PEER, always_accept_verify_cb); + SSL_clear_mode(tls->ssl, SSL_MODE_NO_AUTO_CHAIN); + if (tor_tls_client_is_using_v2_ciphers(tls->ssl)) { + /* This check is redundant, but back when we did it in the callback, + * we might have not been able to look up the tor_tls_t if the code + * was buggy. Fixing that. */ + if (!tls->wasV2Handshake) { + log_warn(LD_BUG, "For some reason, wasV2Handshake didn't" + " get set. Fixing that."); + } + tls->wasV2Handshake = 1; + log_debug(LD_HANDSHAKE, "Completed V2 TLS handshake with client; waiting" + " for renegotiation."); + } else { + tls->wasV2Handshake = 0; + } + } else { + /* Client-side */ + tls->wasV2Handshake = 1; + /* XXXX this can move, probably? -NM */ + if (SSL_set_cipher_list(tls->ssl, SERVER_CIPHER_LIST) == 0) { + tls_log_errors(NULL, LOG_WARN, LD_HANDSHAKE, "re-setting ciphers"); + r = TOR_TLS_ERROR_MISC; + } + } + tls_log_errors(NULL, LOG_WARN, LD_NET, "finishing the handshake"); + return r; +} + +/** Return true iff this TLS connection is authenticated. + */ +int +tor_tls_peer_has_cert(tor_tls_t *tls) +{ + X509 *cert; + cert = SSL_get_peer_certificate(tls->ssl); + tls_log_errors(tls, LOG_WARN, LD_HANDSHAKE, "getting peer certificate"); + if (!cert) + return 0; + X509_free(cert); + return 1; +} + +/** Return a newly allocated copy of the peer certificate, or NULL if there + * isn't one. */ +MOCK_IMPL(tor_x509_cert_t *, +tor_tls_get_peer_cert,(tor_tls_t *tls)) +{ + X509 *cert; + cert = SSL_get_peer_certificate(tls->ssl); + tls_log_errors(tls, LOG_WARN, LD_HANDSHAKE, "getting peer certificate"); + if (!cert) + return NULL; + return tor_x509_cert_new(cert); +} + +/** Return a newly allocated copy of the cerficate we used on the connection, + * or NULL if somehow we didn't use one. */ +MOCK_IMPL(tor_x509_cert_t *, +tor_tls_get_own_cert,(tor_tls_t *tls)) +{ + X509 *cert = SSL_get_certificate(tls->ssl); + tls_log_errors(tls, LOG_WARN, LD_HANDSHAKE, + "getting own-connection certificate"); + if (!cert) + return NULL; + /* Fun inconsistency: SSL_get_peer_certificate increments the reference + * count, but SSL_get_certificate does not. */ + X509 *duplicate = X509_dup(cert); + if (BUG(duplicate == NULL)) + return NULL; + return tor_x509_cert_new(duplicate); +} + +/** Helper function: try to extract a link certificate and an identity + * certificate from <b>tls</b>, and store them in *<b>cert_out</b> and + * *<b>id_cert_out</b> respectively. Log all messages at level + * <b>severity</b>. + * + * Note that a reference is added to cert_out, so it needs to be + * freed. id_cert_out doesn't. */ +MOCK_IMPL(void, +try_to_extract_certs_from_tls,(int severity, tor_tls_t *tls, + X509 **cert_out, X509 **id_cert_out)) +{ + X509 *cert = NULL, *id_cert = NULL; + STACK_OF(X509) *chain = NULL; + int num_in_chain, i; + *cert_out = *id_cert_out = NULL; + if (!(cert = SSL_get_peer_certificate(tls->ssl))) + return; + *cert_out = cert; + if (!(chain = SSL_get_peer_cert_chain(tls->ssl))) + return; + num_in_chain = sk_X509_num(chain); + /* 1 means we're receiving (server-side), and it's just the id_cert. + * 2 means we're connecting (client-side), and it's both the link + * cert and the id_cert. + */ + if (num_in_chain < 1) { + log_fn(severity,LD_PROTOCOL, + "Unexpected number of certificates in chain (%d)", + num_in_chain); + return; + } + for (i=0; i<num_in_chain; ++i) { + id_cert = sk_X509_value(chain, i); + if (X509_cmp(id_cert, cert) != 0) + break; + } + *id_cert_out = id_cert; +} + +/** Return the number of bytes available for reading from <b>tls</b>. + */ +int +tor_tls_get_pending_bytes(tor_tls_t *tls) +{ + tor_assert(tls); + return SSL_pending(tls->ssl); +} + +/** If <b>tls</b> requires that the next write be of a particular size, + * return that size. Otherwise, return 0. */ +size_t +tor_tls_get_forced_write_size(tor_tls_t *tls) +{ + return tls->wantwrite_n; +} + +/** Sets n_read and n_written to the number of bytes read and written, + * respectively, on the raw socket used by <b>tls</b> since the last time this + * function was called on <b>tls</b>. */ +void +tor_tls_get_n_raw_bytes(tor_tls_t *tls, size_t *n_read, size_t *n_written) +{ + BIO *wbio, *tmpbio; + unsigned long r, w; + r = (unsigned long) BIO_number_read(SSL_get_rbio(tls->ssl)); + /* We want the number of bytes actually for real written. Unfortunately, + * sometimes OpenSSL replaces the wbio on tls->ssl with a buffering bio, + * which makes the answer turn out wrong. Let's cope with that. Note + * that this approach will fail if we ever replace tls->ssl's BIOs with + * buffering bios for reasons of our own. As an alternative, we could + * save the original BIO for tls->ssl in the tor_tls_t structure, but + * that would be tempting fate. */ + wbio = SSL_get_wbio(tls->ssl); +#if OPENSSL_VERSION_NUMBER >= OPENSSL_VER(1,1,0,0,5) + /* BIO structure is opaque as of OpenSSL 1.1.0-pre5-dev. Again, not + * supposed to use this form of the version macro, but the OpenSSL developers + * introduced major API changes in the pre-release stage. + */ + if (BIO_method_type(wbio) == BIO_TYPE_BUFFER && + (tmpbio = BIO_next(wbio)) != NULL) + wbio = tmpbio; +#else /* !(OPENSSL_VERSION_NUMBER >= OPENSSL_VER(1,1,0,0,5)) */ + if (wbio->method == BIO_f_buffer() && (tmpbio = BIO_next(wbio)) != NULL) + wbio = tmpbio; +#endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_VER(1,1,0,0,5) */ + w = (unsigned long) BIO_number_written(wbio); + + /* We are ok with letting these unsigned ints go "negative" here: + * If we wrapped around, this should still give us the right answer, unless + * we wrapped around by more than ULONG_MAX since the last time we called + * this function. + */ + *n_read = (size_t)(r - tls->last_read_count); + *n_written = (size_t)(w - tls->last_write_count); + if (*n_read > INT_MAX || *n_written > INT_MAX) { + log_warn(LD_BUG, "Preposterously large value in tor_tls_get_n_raw_bytes. " + "r=%lu, last_read=%lu, w=%lu, last_written=%lu", + r, tls->last_read_count, w, tls->last_write_count); + } + total_bytes_written_by_tls += *n_written; + tls->last_read_count = r; + tls->last_write_count = w; +} + +/** Return a ratio of the bytes that TLS has sent to the bytes that we've told + * it to send. Used to track whether our TLS records are getting too tiny. */ +MOCK_IMPL(double, +tls_get_write_overhead_ratio,(void)) +{ + if (total_bytes_written_over_tls == 0) + return 1.0; + + return ((double)total_bytes_written_by_tls) / + ((double)total_bytes_written_over_tls); +} + +/** Implement check_no_tls_errors: If there are any pending OpenSSL + * errors, log an error message. */ +void +check_no_tls_errors_(const char *fname, int line) +{ + if (ERR_peek_error() == 0) + return; + log_warn(LD_CRYPTO, "Unhandled OpenSSL errors found at %s:%d: ", + tor_fix_source_file(fname), line); + tls_log_errors(NULL, LOG_WARN, LD_NET, NULL); +} + +/** Return true iff the initial TLS connection at <b>tls</b> did not use a v2 + * TLS handshake. Output is undefined if the handshake isn't finished. */ +int +tor_tls_used_v1_handshake(tor_tls_t *tls) +{ + return ! tls->wasV2Handshake; +} + +/** Return true iff the server TLS connection <b>tls</b> got the renegotiation + * request it was waiting for. */ +int +tor_tls_server_got_renegotiate(tor_tls_t *tls) +{ + return tls->got_renegotiate; +} + +#ifndef HAVE_SSL_GET_CLIENT_RANDOM +static size_t +SSL_get_client_random(SSL *s, uint8_t *out, size_t len) +{ + if (len == 0) + return SSL3_RANDOM_SIZE; + tor_assert(len == SSL3_RANDOM_SIZE); + tor_assert(s->s3); + memcpy(out, s->s3->client_random, len); + return len; +} +#endif /* !defined(HAVE_SSL_GET_CLIENT_RANDOM) */ + +#ifndef HAVE_SSL_GET_SERVER_RANDOM +static size_t +SSL_get_server_random(SSL *s, uint8_t *out, size_t len) +{ + if (len == 0) + return SSL3_RANDOM_SIZE; + tor_assert(len == SSL3_RANDOM_SIZE); + tor_assert(s->s3); + memcpy(out, s->s3->server_random, len); + return len; +} +#endif /* !defined(HAVE_SSL_GET_SERVER_RANDOM) */ + +#ifndef HAVE_SSL_SESSION_GET_MASTER_KEY +size_t +SSL_SESSION_get_master_key(SSL_SESSION *s, uint8_t *out, size_t len) +{ + tor_assert(s); + if (len == 0) + return s->master_key_length; + tor_assert(len == (size_t)s->master_key_length); + tor_assert(out); + memcpy(out, s->master_key, len); + return len; +} +#endif /* !defined(HAVE_SSL_SESSION_GET_MASTER_KEY) */ + +/** Set the DIGEST256_LEN buffer at <b>secrets_out</b> to the value used in + * the v3 handshake to prove that the client knows the TLS secrets for the + * connection <b>tls</b>. Return 0 on success, -1 on failure. + */ +MOCK_IMPL(int, +tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out)) +{ +#define TLSSECRET_MAGIC "Tor V3 handshake TLS cross-certification" + uint8_t buf[128]; + size_t len; + tor_assert(tls); + + SSL *const ssl = tls->ssl; + SSL_SESSION *const session = SSL_get_session(ssl); + + tor_assert(ssl); + tor_assert(session); + + const size_t server_random_len = SSL_get_server_random(ssl, NULL, 0); + const size_t client_random_len = SSL_get_client_random(ssl, NULL, 0); + const size_t master_key_len = SSL_SESSION_get_master_key(session, NULL, 0); + + tor_assert(server_random_len); + tor_assert(client_random_len); + tor_assert(master_key_len); + + len = client_random_len + server_random_len + strlen(TLSSECRET_MAGIC) + 1; + tor_assert(len <= sizeof(buf)); + + { + size_t r = SSL_get_client_random(ssl, buf, client_random_len); + tor_assert(r == client_random_len); + } + + { + size_t r = SSL_get_server_random(ssl, + buf+client_random_len, + server_random_len); + tor_assert(r == server_random_len); + } + + uint8_t *master_key = tor_malloc_zero(master_key_len); + { + size_t r = SSL_SESSION_get_master_key(session, master_key, master_key_len); + tor_assert(r == master_key_len); + } + + uint8_t *nextbuf = buf + client_random_len + server_random_len; + memcpy(nextbuf, TLSSECRET_MAGIC, strlen(TLSSECRET_MAGIC) + 1); + + /* + The value is an HMAC, using the TLS master key as the HMAC key, of + client_random | server_random | TLSSECRET_MAGIC + */ + crypto_hmac_sha256((char*)secrets_out, + (char*)master_key, + master_key_len, + (char*)buf, len); + memwipe(buf, 0, sizeof(buf)); + memwipe(master_key, 0, master_key_len); + tor_free(master_key); + + return 0; +} + +/** Using the RFC5705 key material exporting construction, and the + * provided <b>context</b> (<b>context_len</b> bytes long) and + * <b>label</b> (a NUL-terminated string), compute a 32-byte secret in + * <b>secrets_out</b> that only the parties to this TLS session can + * compute. Return 0 on success and -1 on failure. + */ +MOCK_IMPL(int, +tor_tls_export_key_material,(tor_tls_t *tls, uint8_t *secrets_out, + const uint8_t *context, + size_t context_len, + const char *label)) +{ + tor_assert(tls); + tor_assert(tls->ssl); + + int r = SSL_export_keying_material(tls->ssl, + secrets_out, DIGEST256_LEN, + label, strlen(label), + context, context_len, 1); + return (r == 1) ? 0 : -1; +} + +/** Examine the amount of memory used and available for buffers in <b>tls</b>. + * Set *<b>rbuf_capacity</b> to the amount of storage allocated for the read + * buffer and *<b>rbuf_bytes</b> to the amount actually used. + * Set *<b>wbuf_capacity</b> to the amount of storage allocated for the write + * buffer and *<b>wbuf_bytes</b> to the amount actually used. + * + * Return 0 on success, -1 on failure.*/ +int +tor_tls_get_buffer_sizes(tor_tls_t *tls, + size_t *rbuf_capacity, size_t *rbuf_bytes, + size_t *wbuf_capacity, size_t *wbuf_bytes) +{ +#if OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) + (void)tls; + (void)rbuf_capacity; + (void)rbuf_bytes; + (void)wbuf_capacity; + (void)wbuf_bytes; + + return -1; +#else /* !(OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0)) */ + if (tls->ssl->s3->rbuf.buf) + *rbuf_capacity = tls->ssl->s3->rbuf.len; + else + *rbuf_capacity = 0; + if (tls->ssl->s3->wbuf.buf) + *wbuf_capacity = tls->ssl->s3->wbuf.len; + else + *wbuf_capacity = 0; + *rbuf_bytes = tls->ssl->s3->rbuf.left; + *wbuf_bytes = tls->ssl->s3->wbuf.left; + return 0; +#endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) */ +} + +/** Check whether the ECC group requested is supported by the current OpenSSL + * library instance. Return 1 if the group is supported, and 0 if not. + */ +int +evaluate_ecgroup_for_tls(const char *ecgroup) +{ + EC_KEY *ec_key; + int nid; + int ret; + + if (!ecgroup) + nid = NID_tor_default_ecdhe_group; + else if (!strcasecmp(ecgroup, "P256")) + nid = NID_X9_62_prime256v1; + else if (!strcasecmp(ecgroup, "P224")) + nid = NID_secp224r1; + else + return 0; + + ec_key = EC_KEY_new_by_curve_name(nid); + ret = (ec_key != NULL); + EC_KEY_free(ec_key); + + return ret; +} diff --git a/src/lib/tls/tortls_st.h b/src/lib/tls/tortls_st.h new file mode 100644 index 0000000000..a1b59a37af --- /dev/null +++ b/src/lib/tls/tortls_st.h @@ -0,0 +1,74 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_TORTLS_ST_H +#define TOR_TORTLS_ST_H + +#include "lib/net/socket.h" + +#define TOR_TLS_MAGIC 0x71571571 + +typedef enum { + TOR_TLS_ST_HANDSHAKE, TOR_TLS_ST_OPEN, TOR_TLS_ST_GOTCLOSE, + TOR_TLS_ST_SENTCLOSE, TOR_TLS_ST_CLOSED, TOR_TLS_ST_RENEGOTIATE, + TOR_TLS_ST_BUFFEREVENT +} tor_tls_state_t; +#define tor_tls_state_bitfield_t ENUM_BF(tor_tls_state_t) + +struct tor_tls_context_t { + int refcnt; + tor_tls_context_impl_t *ctx; + struct tor_x509_cert_t *my_link_cert; + struct tor_x509_cert_t *my_id_cert; + struct tor_x509_cert_t *my_auth_cert; + crypto_pk_t *link_key; + crypto_pk_t *auth_key; +}; + +/** Holds a SSL object and its associated data. Members are only + * accessed from within tortls.c. + */ +struct tor_tls_t { + uint32_t magic; + tor_tls_context_t *context; /** A link to the context object for this tls. */ + tor_tls_impl_t *ssl; /**< An OpenSSL SSL object or NSS PRFileDesc. */ + tor_socket_t socket; /**< The underlying file descriptor for this TLS + * connection. */ + char *address; /**< An address to log when describing this connection. */ + tor_tls_state_bitfield_t state : 3; /**< The current SSL state, + * depending on which operations + * have completed successfully. */ + unsigned int isServer:1; /**< True iff this is a server-side connection */ + unsigned int wasV2Handshake:1; /**< True iff the original handshake for + * this connection used the updated version + * of the connection protocol (client sends + * different cipher list, server sends only + * one certificate). */ + /** True iff we should call negotiated_callback when we're done reading. */ + unsigned int got_renegotiate:1; +#ifdef ENABLE_OPENSSL + /** Return value from tor_tls_classify_client_ciphers, or 0 if we haven't + * called that function yet. */ + int8_t client_cipher_list_type; + size_t wantwrite_n; /**< 0 normally, >0 if we returned wantwrite last + * time. */ + /** Last values retrieved from BIO_number_read()/write(); see + * tor_tls_get_n_raw_bytes() for usage. + */ + unsigned long last_write_count; + unsigned long last_read_count; + /** If set, a callback to invoke whenever the client tries to renegotiate + * the handshake. */ + void (*negotiated_callback)(tor_tls_t *tls, void *arg); + /** Argument to pass to negotiated_callback. */ + void *callback_arg; +#endif +#ifdef ENABLE_NSS + size_t n_read_since_last_check; + size_t n_written_since_last_check; +#endif +}; + +#endif diff --git a/src/lib/tls/x509.c b/src/lib/tls/x509.c new file mode 100644 index 0000000000..0d62ee41af --- /dev/null +++ b/src/lib/tls/x509.c @@ -0,0 +1,143 @@ +/* Copyright (c) 2003, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file x509_openssl.c + * \brief Wrapper functions to present a consistent interface to + * X.509 functions. + **/ + +#define TOR_X509_PRIVATE +#include "lib/tls/x509.h" +#include "lib/tls/x509_internal.h" +#include "lib/log/util_bug.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" + +/** Choose the start and end times for a certificate */ +void +tor_tls_pick_certificate_lifetime(time_t now, + unsigned int cert_lifetime, + time_t *start_time_out, + time_t *end_time_out) +{ + time_t start_time, end_time; + /* Make sure we're part-way through the certificate lifetime, rather + * than having it start right now. Don't choose quite uniformly, since + * then we might pick a time where we're about to expire. Lastly, be + * sure to start on a day boundary. */ + /* Our certificate lifetime will be cert_lifetime no matter what, but if we + * start cert_lifetime in the past, we'll have 0 real lifetime. instead we + * start up to (cert_lifetime - min_real_lifetime - start_granularity) in + * the past. */ + const time_t min_real_lifetime = 24*3600; + const time_t start_granularity = 24*3600; + time_t earliest_start_time; + /* Don't actually start in the future! */ + if (cert_lifetime <= min_real_lifetime + start_granularity) { + earliest_start_time = now - 1; + } else { + earliest_start_time = now + min_real_lifetime + start_granularity + - cert_lifetime; + } + start_time = crypto_rand_time_range(earliest_start_time, now); + /* Round the start time back to the start of a day. */ + start_time -= start_time % start_granularity; + + end_time = start_time + cert_lifetime; + + *start_time_out = start_time; + *end_time_out = end_time; +} + +/** Return a set of digests for the public key in <b>cert</b>, or NULL if this + * cert's public key is not one we know how to take the digest of. */ +const common_digests_t * +tor_x509_cert_get_id_digests(const tor_x509_cert_t *cert) +{ + if (cert->pkey_digests_set) + return &cert->pkey_digests; + else + return NULL; +} + +/** Return a set of digests for the public key in <b>cert</b>. */ +const common_digests_t * +tor_x509_cert_get_cert_digests(const tor_x509_cert_t *cert) +{ + return &cert->cert_digests; +} + +/** Free all storage held in <b>cert</b> */ +void +tor_x509_cert_free_(tor_x509_cert_t *cert) +{ + if (! cert) + return; + tor_x509_cert_impl_free(cert->cert); +#ifdef ENABLE_OPENSSL + tor_free(cert->encoded); +#endif + memwipe(cert, 0x03, sizeof(*cert)); + /* LCOV_EXCL_BR_START since cert will never be NULL here */ + tor_free(cert); + /* LCOV_EXCL_BR_STOP */ +} + +/** + * Allocate a new tor_x509_cert_t to hold the certificate "x509_cert". + * + * Steals a reference to x509_cert. + */ +MOCK_IMPL(tor_x509_cert_t *, +tor_x509_cert_new,(tor_x509_cert_impl_t *x509_cert)) +{ + tor_x509_cert_t *cert; + + if (!x509_cert) + return NULL; + + cert = tor_malloc_zero(sizeof(tor_x509_cert_t)); + cert->cert = x509_cert; + + if (tor_x509_cert_set_cached_der_encoding(cert) < 0) + goto err; + + { + const uint8_t *encoded=NULL; + size_t encoded_len=0; + tor_x509_cert_get_der(cert, &encoded, &encoded_len); + tor_assert(encoded); + crypto_common_digests(&cert->cert_digests, (char *)encoded, encoded_len); + } + + { + crypto_pk_t *pk = tor_tls_cert_get_key(cert); + if (pk) { + if (crypto_pk_get_common_digests(pk, &cert->pkey_digests) < 0) { + log_warn(LD_CRYPTO, "unable to compute digests of certificate key"); + crypto_pk_free(pk); + goto err; + } + } + cert->pkey_digests_set = 1; + crypto_pk_free(pk); + } + + return cert; + err: + log_err(LD_CRYPTO, "Couldn't wrap encoded X509 certificate."); + tor_x509_cert_free(cert); + return NULL; +} + +/** Return a new copy of <b>cert</b>. */ +tor_x509_cert_t * +tor_x509_cert_dup(const tor_x509_cert_t *cert) +{ + tor_assert(cert); + tor_assert(cert->cert); + return tor_x509_cert_new(tor_x509_cert_impl_dup_(cert->cert)); +} diff --git a/src/lib/tls/x509.h b/src/lib/tls/x509.h new file mode 100644 index 0000000000..e7440a192f --- /dev/null +++ b/src/lib/tls/x509.h @@ -0,0 +1,75 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_X509_H +#define TOR_X509_H + +/** + * \file x509.h + * \brief Headers for tortls.c + **/ + +#include "lib/crypt_ops/crypto_rsa.h" +#include "lib/testsupport/testsupport.h" + +/* Opaque structure to hold an X509 certificate. */ +typedef struct tor_x509_cert_t tor_x509_cert_t; + +#ifdef ENABLE_NSS +typedef struct CERTCertificateStr tor_x509_cert_impl_t; +#elif defined(ENABLE_OPENSSL) +typedef struct x509_st tor_x509_cert_impl_t; +#endif + +#ifdef TOR_X509_PRIVATE +/** Structure that we use for a single certificate. */ +struct tor_x509_cert_t { + tor_x509_cert_impl_t *cert; +#ifdef ENABLE_OPENSSL + uint8_t *encoded; + size_t encoded_len; +#endif + unsigned pkey_digests_set : 1; + common_digests_t cert_digests; + common_digests_t pkey_digests; +}; +#endif + +void tor_tls_pick_certificate_lifetime(time_t now, + unsigned cert_lifetime, + time_t *start_time_out, + time_t *end_time_out); + +#ifdef TOR_UNIT_TESTS +tor_x509_cert_t *tor_x509_cert_replace_expiration( + const tor_x509_cert_t *inp, + time_t new_expiration_time, + crypto_pk_t *signing_key); +#endif + +tor_x509_cert_t *tor_x509_cert_dup(const tor_x509_cert_t *cert); + +void tor_x509_cert_free_(tor_x509_cert_t *cert); +#define tor_x509_cert_free(c) \ + FREE_AND_NULL(tor_x509_cert_t, tor_x509_cert_free_, (c)) +tor_x509_cert_t *tor_x509_cert_decode(const uint8_t *certificate, + size_t certificate_len); +void tor_x509_cert_get_der(const tor_x509_cert_t *cert, + const uint8_t **encoded_out, size_t *size_out); + +const common_digests_t *tor_x509_cert_get_id_digests( + const tor_x509_cert_t *cert); +const common_digests_t *tor_x509_cert_get_cert_digests( + const tor_x509_cert_t *cert); + +crypto_pk_t *tor_tls_cert_get_key(tor_x509_cert_t *cert); + +int tor_tls_cert_is_valid(int severity, + const tor_x509_cert_t *cert, + const tor_x509_cert_t *signing_cert, + time_t now, + int check_rsa_1024); + +#endif diff --git a/src/lib/tls/x509_internal.h b/src/lib/tls/x509_internal.h new file mode 100644 index 0000000000..139ecedd23 --- /dev/null +++ b/src/lib/tls/x509_internal.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2003, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_X509_INTERNAL_H +#define TOR_X509_INTERNAL_H + +/** + * \file x509.h + * \brief Internal headers for tortls.c + **/ + +#include "lib/crypt_ops/crypto_rsa.h" +#include "lib/testsupport/testsupport.h" + +/** + * How skewed do we allow our clock to be with respect to certificates that + * seem to be expired? (seconds) + */ +#define TOR_X509_PAST_SLOP (2*24*60*60) +/** + * How skewed do we allow our clock to be with respect to certificates that + * seem to come from the future? (seconds) + */ +#define TOR_X509_FUTURE_SLOP (30*24*60*60) + +MOCK_DECL(tor_x509_cert_impl_t *, tor_tls_create_certificate, + (crypto_pk_t *rsa, + crypto_pk_t *rsa_sign, + const char *cname, + const char *cname_sign, + unsigned int cert_lifetime)); +MOCK_DECL(tor_x509_cert_t *, tor_x509_cert_new, + (tor_x509_cert_impl_t *x509_cert)); + +int tor_x509_check_cert_lifetime_internal(int severity, + const tor_x509_cert_impl_t *cert, + time_t now, + int past_tolerance, + int future_tolerance); + +void tor_x509_cert_impl_free_(tor_x509_cert_impl_t *cert); +#define tor_x509_cert_impl_free(cert) \ + FREE_AND_NULL(tor_x509_cert_impl_t, tor_x509_cert_impl_free_, (cert)) +tor_x509_cert_impl_t *tor_x509_cert_impl_dup_(tor_x509_cert_impl_t *cert); +#ifdef ENABLE_OPENSSL +int tor_x509_cert_set_cached_der_encoding(tor_x509_cert_t *cert); +#else +#define tor_x509_cert_set_cached_der_encoding(cert) (0) +#endif + +#endif diff --git a/src/lib/tls/x509_nss.c b/src/lib/tls/x509_nss.c new file mode 100644 index 0000000000..5bb7940c90 --- /dev/null +++ b/src/lib/tls/x509_nss.c @@ -0,0 +1,450 @@ +/* Copyright (c) 2003, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file x509_nss.c + * \brief Wrapper functions to present a consistent interface to + * X.509 functions from NSS. + **/ + +#define TOR_X509_PRIVATE +#include "lib/tls/x509.h" +#include "lib/tls/x509_internal.h" +#include "lib/tls/tortls.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/crypt_ops/crypto_nss_mgt.h" +#include "lib/log/util_bug.h" +#include "lib/encoding/time_fmt.h" +#include "lib/string/printf.h" + +#include <pk11pub.h> +#include <cryptohi.h> +#include <cert.h> +#include <keyhi.h> +#include <time.h> + +/* Units of PRTime per second. + * + * (PRTime is based in microseconds since the Unix + * epoch.) */ +#define PRTIME_PER_SEC (1000*1000) + +static tor_x509_cert_impl_t *tor_x509_cert_decode_internal( + const uint8_t *certificate, int certificate_len); + +static tor_x509_cert_impl_t * +tor_tls_create_certificate_internal(crypto_pk_t *rsa, + crypto_pk_t *rsa_sign, + CERTName *subject_dn, + CERTName *issuer_dn, + time_t start_time, + time_t end_time) +{ + if (! crypto_pk_key_is_private(rsa_sign)) { + return NULL; + } + + const SECKEYPublicKey *subject_key = crypto_pk_get_nss_pubkey(rsa); + const SECKEYPrivateKey *signing_key = crypto_pk_get_nss_privkey(rsa_sign); + SECStatus s; + + CERTSubjectPublicKeyInfo *subject_spki = NULL; + CERTCertificateRequest *request = NULL; + CERTValidity *validity = NULL; + CERTCertificate *cert = NULL; + SECItem der = { .data = NULL, .len = 0 }; + SECItem signed_der = { .data = NULL, .len = 0 }; + + CERTCertificate *result_cert = NULL; + + validity = CERT_CreateValidity(((PRTime)start_time) * PRTIME_PER_SEC, + ((PRTime)end_time) * PRTIME_PER_SEC); + if (BUG(! validity)) { + /* LCOV_EXCL_START */ + crypto_nss_log_errors(LOG_WARN, "creating a validity object"); + goto err; + /* LCOV_EXCL_STOP */ + } + + unsigned long serial_number; + crypto_rand((char*)&serial_number, sizeof(serial_number)); + + subject_spki = SECKEY_CreateSubjectPublicKeyInfo(subject_key); + if (!subject_spki) + goto err; + + /* Make a CSR ... */ + // XXX do we need to set any attributes? + request = CERT_CreateCertificateRequest(subject_dn, + subject_spki, + NULL /* attributes */); + if (!request) + goto err; + + /* Put it into a certificate ... */ + cert = CERT_CreateCertificate(serial_number, + issuer_dn, + validity, + request); + if (!cert) + goto err; + + /* version 3 cert */ + *cert->version.data = 2; /* 2 means version 3. */ + cert->version.len = 1; + + // XXX do we need to set anything else on the cert? + + /* Sign it. */ + KeyType privkey_type = SECKEY_GetPrivateKeyType(signing_key); + SECOidTag oid_tag = SEC_GetSignatureAlgorithmOidTag(privkey_type, + SEC_OID_SHA256); + if (oid_tag == SEC_OID_UNKNOWN) + goto err; + s = SECOID_SetAlgorithmID(cert->arena, &cert->signature, oid_tag, NULL); + if (s != SECSuccess) + goto err; + + void *tmp; + tmp = SEC_ASN1EncodeItem(cert->arena, &der, cert, + SEC_ASN1_GET(CERT_CertificateTemplate)); + if (!tmp) + goto err; + + s = SEC_DerSignDataWithAlgorithmID(cert->arena, + &signed_der, + der.data, der.len, + (SECKEYPrivateKey *)signing_key,//const + &cert->signature); + + if (s != SECSuccess) + goto err; + + /* Re-parse it, to make sure all the certificates we actually use + * appear via being decoded. */ + result_cert = tor_x509_cert_decode_internal(signed_der.data, signed_der.len); + +#if 1 + { + // Can we check the cert we just signed? + tor_assert(result_cert); + SECKEYPublicKey *issuer_pk = (SECKEYPublicKey *) + crypto_pk_get_nss_pubkey(rsa_sign); + SECStatus cert_ok = CERT_VerifySignedDataWithPublicKey( + &result_cert->signatureWrap, issuer_pk, NULL); + tor_assert(cert_ok == SECSuccess); + } +#endif + + err: + if (subject_spki) + SECKEY_DestroySubjectPublicKeyInfo(subject_spki); + if (request) + CERT_DestroyCertificateRequest(request); + if (validity) + CERT_DestroyValidity(validity); + + // unnecessary, since these are allocated in the cert's arena. + //SECITEM_FreeItem(&der, PR_FALSE); + //SECITEM_FreeItem(&signed_der, PR_FALSE); + if (cert) + CERT_DestroyCertificate(cert); + + return result_cert; +} + +MOCK_IMPL(tor_x509_cert_impl_t *, +tor_tls_create_certificate,(crypto_pk_t *rsa, + crypto_pk_t *rsa_sign, + const char *cname, + const char *cname_sign, + unsigned int cert_lifetime)) +{ + tor_assert(rsa); + tor_assert(rsa_sign); + tor_assert(cname); + tor_assert(cname_sign); + + char *cname_rfc_1485 = NULL, *cname_sign_rfc_1485 = NULL; + CERTName *subject_dn = NULL, *issuer_dn = NULL; + time_t start_time; + time_t end_time; + CERTCertificate *result = NULL; + + tor_asprintf(&cname_rfc_1485, "CN=%s", cname); + tor_asprintf(&cname_sign_rfc_1485, "CN=%s", cname_sign); + + subject_dn = CERT_AsciiToName(cname_rfc_1485); + issuer_dn = CERT_AsciiToName(cname_sign_rfc_1485); + if (!subject_dn || !issuer_dn) + goto err; + + tor_tls_pick_certificate_lifetime(time(NULL), cert_lifetime, + &start_time, &end_time); + + result = tor_tls_create_certificate_internal(rsa, + rsa_sign, + subject_dn, + issuer_dn, + start_time, + end_time); + err: + tor_free(cname_rfc_1485); + tor_free(cname_sign_rfc_1485); + if (subject_dn) + CERT_DestroyName(subject_dn); + if (issuer_dn) + CERT_DestroyName(issuer_dn); + + return result; +} + +/** Set *<b>encoded_out</b> and *<b>size_out</b> to <b>cert</b>'s encoded DER + * representation and length, respectively. */ +void +tor_x509_cert_get_der(const tor_x509_cert_t *cert, + const uint8_t **encoded_out, size_t *size_out) +{ + tor_assert(cert); + tor_assert(cert->cert); + tor_assert(encoded_out); + tor_assert(size_out); + + const SECItem *item = &cert->cert->derCert; + *encoded_out = item->data; + *size_out = (size_t)item->len; +} + +void +tor_x509_cert_impl_free_(tor_x509_cert_impl_t *cert) +{ + if (cert) + CERT_DestroyCertificate(cert); +} + +tor_x509_cert_impl_t * +tor_x509_cert_impl_dup_(tor_x509_cert_impl_t *cert) +{ + if (cert) + return CERT_DupCertificate(cert); + else + return NULL; +} + +/** + * As tor_x509_cert_decode, but return the NSS certificate type +*/ +static tor_x509_cert_impl_t * +tor_x509_cert_decode_internal(const uint8_t *certificate, + int certificate_len) +{ + tor_assert(certificate); + if (certificate_len > INT_MAX) + return NULL; + + SECItem der = { .type = siBuffer, + .data = (unsigned char *)certificate, + .len = certificate_len }; + CERTCertDBHandle *certdb = CERT_GetDefaultCertDB(); + tor_assert(certdb); + return CERT_NewTempCertificate(certdb, + &der, + NULL /* nickname */, + PR_FALSE, /* isPerm */ + PR_TRUE /* CopyDER */); +} + +tor_x509_cert_t * +tor_x509_cert_decode(const uint8_t *certificate, + size_t certificate_len) +{ + CERTCertificate *cert = tor_x509_cert_decode_internal(certificate, + (int)certificate_len); + if (! cert) { + crypto_nss_log_errors(LOG_INFO, "decoding certificate"); + return NULL; + } + + tor_x509_cert_t *newcert = tor_x509_cert_new(cert); + + return newcert; +} + +crypto_pk_t * +tor_tls_cert_get_key(tor_x509_cert_t *cert) +{ + tor_assert(cert); + CERTSubjectPublicKeyInfo *spki = &cert->cert->subjectPublicKeyInfo; + SECKEYPublicKey *pub = SECKEY_ExtractPublicKey(spki); // we own this pointer + if (pub == NULL) + return NULL; + + if (SECKEY_GetPublicKeyType(pub) != rsaKey) { + SECKEY_DestroyPublicKey(pub); + return NULL; + } + + return crypto_pk_new_from_nss_pubkey(pub); +} + +int +tor_tls_cert_is_valid(int severity, + const tor_x509_cert_t *cert, + const tor_x509_cert_t *signing_cert, + time_t now, + int check_rsa_1024) +{ + int result = 0; + + tor_assert(cert); + tor_assert(signing_cert); + + SECKEYPublicKey *pk = CERT_ExtractPublicKey(signing_cert->cert); + if (pk == NULL) { + log_fn(severity, LD_CRYPTO, + "Invalid certificate: could not extract issuer key"); + goto fail; + } + + SECStatus s = CERT_VerifySignedDataWithPublicKey(&cert->cert->signatureWrap, + pk, NULL); + if (s != SECSuccess) { + log_fn(severity, LD_CRYPTO, + "Invalid certificate: could not validate signature."); + goto fail; + } + + if (tor_x509_check_cert_lifetime_internal(severity, + cert->cert, + now, + TOR_X509_PAST_SLOP, + TOR_X509_FUTURE_SLOP) < 0) + goto fail; + + if (check_rsa_1024) { + /* We require that this is a 1024-bit RSA key, for legacy reasons .:p */ + if (SECKEY_GetPublicKeyType(pk) != rsaKey || + SECKEY_PublicKeyStrengthInBits(pk) != 1024) { + log_fn(severity, LD_CRYPTO, "Invalid certificate: Key is not RSA1024."); + goto fail; + } + } else { + /* We require that this key is at least minimally strong. */ + unsigned min_bits = (SECKEY_GetPublicKeyType(pk) == ecKey) ? 128: 1024; + if (SECKEY_PublicKeyStrengthInBits(pk) < min_bits) { + log_fn(severity, LD_CRYPTO, "Invalid certificate: Key is too weak."); + goto fail; + } + } + + /* The certificate is valid. */ + result = 1; + + fail: + if (pk) + SECKEY_DestroyPublicKey(pk); + return result; +} + +static void +log_cert_lifetime(int severity, + const char *status, + time_t now, + PRTime notBefore, + PRTime notAfter) +{ + log_fn(severity, LD_GENERAL, + "Certificate %s. Either their clock is set wrong, or your clock " + "is incorrect.", status); + + char nowbuf[ISO_TIME_LEN+1]; + char nbbuf[ISO_TIME_LEN+1]; + char nabuf[ISO_TIME_LEN+1]; + + format_iso_time(nowbuf, now); + format_iso_time(nbbuf, notBefore / PRTIME_PER_SEC); + format_iso_time(nabuf, notAfter / PRTIME_PER_SEC); + + log_fn(severity, LD_GENERAL, + "(The certificate is valid from %s until %s. Your time is %s.)", + nbbuf, nabuf, nowbuf); +} + +int +tor_x509_check_cert_lifetime_internal(int severity, + const tor_x509_cert_impl_t *cert, + time_t now, + int past_tolerance, + int future_tolerance) +{ + tor_assert(cert); + + PRTime notBefore=0, notAfter=0; + int64_t t; + SECStatus r = CERT_GetCertTimes(cert, ¬Before, ¬After); + if (r != SECSuccess) { + log_fn(severity, LD_CRYPTO, + "Couldn't get validity times from certificate"); + return -1; + } + + t = ((int64_t)now) + future_tolerance; + t *= PRTIME_PER_SEC; + if (notBefore > t) { + log_cert_lifetime(severity, "not yet valid", now, + notBefore, notAfter); + return -1; + } + + t = ((int64_t)now) - past_tolerance; + t *= PRTIME_PER_SEC; + if (notAfter < t) { + log_cert_lifetime(severity, "already expired", now, + notBefore, notAfter); + return -1; + } + + return 0; +} + +#ifdef TOR_UNIT_TESTS +tor_x509_cert_t * +tor_x509_cert_replace_expiration(const tor_x509_cert_t *inp, + time_t new_expiration_time, + crypto_pk_t *signing_key) +{ + tor_assert(inp); + tor_assert(signing_key); + + PRTime notBefore=0, notAfter=0; + SECStatus r = CERT_GetCertTimes(inp->cert, ¬Before, ¬After); + if (r != SECSuccess) + return NULL; + + time_t start_time = notBefore / PRTIME_PER_SEC; + if (new_expiration_time < start_time) { + /* This prevents an NSS error. */ + start_time = new_expiration_time - 10; + } + + crypto_pk_t *subject_key = tor_tls_cert_get_key((tor_x509_cert_t *)inp); + if (!subject_key) + return NULL; + + CERTCertificate *newcert; + + newcert = tor_tls_create_certificate_internal(subject_key, + signing_key, + &inp->cert->subject, + &inp->cert->issuer, + start_time, + new_expiration_time); + + crypto_pk_free(subject_key); + + return newcert ? tor_x509_cert_new(newcert) : NULL; +} +#endif diff --git a/src/lib/tls/x509_openssl.c b/src/lib/tls/x509_openssl.c new file mode 100644 index 0000000000..dd74e84418 --- /dev/null +++ b/src/lib/tls/x509_openssl.c @@ -0,0 +1,461 @@ +/* Copyright (c) 2003, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file x509_openssl.c + * \brief Wrapper functions to present a consistent interface to + * X.509 functions from OpenSSL. + **/ + +#define TOR_X509_PRIVATE +#include "lib/tls/x509.h" +#include "lib/tls/x509_internal.h" +#include "lib/tls/tortls.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/crypt_ops/compat_openssl.h" + +/* Some versions of OpenSSL declare SSL_get_selected_srtp_profile twice in + * srtp.h. Suppress the GCC warning so we can build with -Wredundant-decl. */ +DISABLE_GCC_WARNING(redundant-decls) + +#include <openssl/opensslv.h> + +#ifdef OPENSSL_NO_EC +#error "We require OpenSSL with ECC support" +#endif + +#include <openssl/err.h> +#include <openssl/asn1.h> +#include <openssl/bio.h> +#include <openssl/bn.h> +#include <openssl/rsa.h> + +ENABLE_GCC_WARNING(redundant-decls) + +#include "lib/log/log.h" +#include "lib/log/util_bug.h" +#include "lib/ctime/di_ops.h" +#include "lib/encoding/time_fmt.h" + +#include <stdlib.h> +#include <string.h> + +#ifdef OPENSSL_1_1_API +#define X509_get_notBefore_const(cert) \ + X509_get0_notBefore(cert) +#define X509_get_notAfter_const(cert) \ + X509_get0_notAfter(cert) +#ifndef X509_get_notBefore +#define X509_get_notBefore(cert) \ + X509_getm_notBefore(cert) +#endif +#ifndef X509_get_notAfter +#define X509_get_notAfter(cert) \ + X509_getm_notAfter(cert) +#endif +#else /* ! OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) */ +#define X509_get_notBefore_const(cert) \ + ((const ASN1_TIME*) X509_get_notBefore((X509 *)cert)) +#define X509_get_notAfter_const(cert) \ + ((const ASN1_TIME*) X509_get_notAfter((X509 *)cert)) +#endif + +/** Return a newly allocated X509 name with commonName <b>cname</b>. */ +static X509_NAME * +tor_x509_name_new(const char *cname) +{ + int nid; + X509_NAME *name; + /* LCOV_EXCL_BR_START : these branches will only fail on OOM errors */ + if (!(name = X509_NAME_new())) + return NULL; + if ((nid = OBJ_txt2nid("commonName")) == NID_undef) goto error; + if (!(X509_NAME_add_entry_by_NID(name, nid, MBSTRING_ASC, + (unsigned char*)cname, -1, -1, 0))) + goto error; + /* LCOV_EXCL_BR_STOP */ + return name; + + /* LCOV_EXCL_START : these lines will only execute on out of memory errors*/ + error: + X509_NAME_free(name); + return NULL; + /* LCOV_EXCL_STOP */ +} + +/** Generate and sign an X509 certificate with the public key <b>rsa</b>, + * signed by the private key <b>rsa_sign</b>. The commonName of the + * certificate will be <b>cname</b>; the commonName of the issuer will be + * <b>cname_sign</b>. The cert will be valid for <b>cert_lifetime</b> + * seconds, starting from some time in the past. + * + * Return a certificate on success, NULL on failure. + */ +MOCK_IMPL(X509 *, +tor_tls_create_certificate,(crypto_pk_t *rsa, + crypto_pk_t *rsa_sign, + const char *cname, + const char *cname_sign, + unsigned int cert_lifetime)) +{ + /* OpenSSL generates self-signed certificates with random 64-bit serial + * numbers, so let's do that too. */ +#define SERIAL_NUMBER_SIZE 8 + + time_t start_time, end_time; + BIGNUM *serial_number = NULL; + unsigned char serial_tmp[SERIAL_NUMBER_SIZE]; + EVP_PKEY *sign_pkey = NULL, *pkey=NULL; + X509 *x509 = NULL; + X509_NAME *name = NULL, *name_issuer=NULL; + + tor_tls_init(); + + time_t now = time(NULL); + + tor_tls_pick_certificate_lifetime(now, cert_lifetime, + &start_time, &end_time); + + tor_assert(rsa); + tor_assert(cname); + tor_assert(rsa_sign); + tor_assert(cname_sign); + if (!(sign_pkey = crypto_pk_get_openssl_evp_pkey_(rsa_sign,1))) + goto error; + if (!(pkey = crypto_pk_get_openssl_evp_pkey_(rsa,0))) + goto error; + if (!(x509 = X509_new())) + goto error; + if (!(X509_set_version(x509, 2))) + goto error; + + { /* our serial number is 8 random bytes. */ + crypto_rand((char *)serial_tmp, sizeof(serial_tmp)); + if (!(serial_number = BN_bin2bn(serial_tmp, sizeof(serial_tmp), NULL))) + goto error; + if (!(BN_to_ASN1_INTEGER(serial_number, X509_get_serialNumber(x509)))) + goto error; + } + + if (!(name = tor_x509_name_new(cname))) + goto error; + if (!(X509_set_subject_name(x509, name))) + goto error; + if (!(name_issuer = tor_x509_name_new(cname_sign))) + goto error; + if (!(X509_set_issuer_name(x509, name_issuer))) + goto error; + + if (!X509_time_adj(X509_get_notBefore(x509),0,&start_time)) + goto error; + if (!X509_time_adj(X509_get_notAfter(x509),0,&end_time)) + goto error; + if (!X509_set_pubkey(x509, pkey)) + goto error; + + if (!X509_sign(x509, sign_pkey, EVP_sha256())) + goto error; + + goto done; + error: + if (x509) { + X509_free(x509); + x509 = NULL; + } + done: + tls_log_errors(NULL, LOG_WARN, LD_NET, "generating certificate"); + if (sign_pkey) + EVP_PKEY_free(sign_pkey); + if (pkey) + EVP_PKEY_free(pkey); + if (serial_number) + BN_clear_free(serial_number); + if (name) + X509_NAME_free(name); + if (name_issuer) + X509_NAME_free(name_issuer); + return x509; + +#undef SERIAL_NUMBER_SIZE +} + +/** Set the 'encoded' and 'encoded_len' fields of "cert" from cert->cert. */ +int +tor_x509_cert_set_cached_der_encoding(tor_x509_cert_t *cert) +{ + unsigned char *buf = NULL; + int length = i2d_X509(cert->cert, &buf); + + if (length <= 0 || buf == NULL) { + return -1; + } + cert->encoded_len = (size_t) length; + cert->encoded = tor_malloc(length); + memcpy(cert->encoded, buf, length); + OPENSSL_free(buf); + return 0; +} + +void +tor_x509_cert_impl_free_(tor_x509_cert_impl_t *cert) +{ + if (cert) + X509_free(cert); +} + +tor_x509_cert_impl_t * +tor_x509_cert_impl_dup_(tor_x509_cert_impl_t *cert) +{ + if (cert) + return X509_dup(cert); + else + return NULL; +} + +/** Set *<b>encoded_out</b> and *<b>size_out</b> to <b>cert</b>'s encoded DER + * representation and length, respectively. */ +void +tor_x509_cert_get_der(const tor_x509_cert_t *cert, + const uint8_t **encoded_out, size_t *size_out) +{ + tor_assert(cert); + tor_assert(encoded_out); + tor_assert(size_out); + *encoded_out = cert->encoded; + *size_out = cert->encoded_len; +} + +/** Read a DER-encoded X509 cert, of length exactly <b>certificate_len</b>, + * from a <b>certificate</b>. Return a newly allocated tor_x509_cert_t on + * success and NULL on failure. */ +tor_x509_cert_t * +tor_x509_cert_decode(const uint8_t *certificate, size_t certificate_len) +{ + X509 *x509; + const unsigned char *cp = (const unsigned char *)certificate; + tor_x509_cert_t *newcert; + tor_assert(certificate); + check_no_tls_errors(); + + if (certificate_len > INT_MAX) + goto err; + + x509 = d2i_X509(NULL, &cp, (int)certificate_len); + + if (!x509) + goto err; /* Couldn't decode */ + if (cp - certificate != (int)certificate_len) { + X509_free(x509); + goto err; /* Didn't use all the bytes */ + } + newcert = tor_x509_cert_new(x509); + if (!newcert) { + goto err; + } + if (newcert->encoded_len != certificate_len || + fast_memneq(newcert->encoded, certificate, certificate_len)) { + /* Cert wasn't in DER */ + tor_x509_cert_free(newcert); + goto err; + } + return newcert; + err: + tls_log_errors(NULL, LOG_INFO, LD_CRYPTO, "decoding a certificate"); + return NULL; +} + +/** + * Return a newly allocated copy of the public key that a certificate + * certifies. Watch out! This returns NULL if the cert's key is not RSA. + */ +crypto_pk_t * +tor_tls_cert_get_key(tor_x509_cert_t *cert) +{ + crypto_pk_t *result = NULL; + EVP_PKEY *pkey = X509_get_pubkey(cert->cert); + RSA *rsa; + if (!pkey) + return NULL; + rsa = EVP_PKEY_get1_RSA(pkey); + if (!rsa) { + EVP_PKEY_free(pkey); + return NULL; + } + result = crypto_new_pk_from_openssl_rsa_(rsa); + EVP_PKEY_free(pkey); + return result; +} + +/** Check whether <b>cert</b> is well-formed, currently live, and correctly + * signed by the public key in <b>signing_cert</b>. If <b>check_rsa_1024</b>, + * make sure that it has an RSA key with 1024 bits; otherwise, just check that + * the key is long enough. Return 1 if the cert is good, and 0 if it's bad or + * we couldn't check it. */ +int +tor_tls_cert_is_valid(int severity, + const tor_x509_cert_t *cert, + const tor_x509_cert_t *signing_cert, + time_t now, + int check_rsa_1024) +{ + check_no_tls_errors(); + EVP_PKEY *cert_key; + int r, key_ok = 0; + + if (!signing_cert || !cert) + goto bad; + + EVP_PKEY *signing_key = X509_get_pubkey(signing_cert->cert); + if (!signing_key) + goto bad; + r = X509_verify(cert->cert, signing_key); + EVP_PKEY_free(signing_key); + if (r <= 0) + goto bad; + + /* okay, the signature checked out right. Now let's check the check the + * lifetime. */ + if (tor_x509_check_cert_lifetime_internal(severity, cert->cert, now, + TOR_X509_PAST_SLOP, + TOR_X509_FUTURE_SLOP) < 0) + goto bad; + + cert_key = X509_get_pubkey(cert->cert); + if (check_rsa_1024 && cert_key) { + RSA *rsa = EVP_PKEY_get1_RSA(cert_key); +#ifdef OPENSSL_1_1_API + if (rsa && RSA_bits(rsa) == 1024) { +#else + if (rsa && BN_num_bits(rsa->n) == 1024) { +#endif + key_ok = 1; + } else { + log_fn(severity, LD_CRYPTO, "Invalid certificate: Key is not RSA1024."); + } + + if (rsa) + RSA_free(rsa); + } else if (cert_key) { + int min_bits = 1024; +#ifdef EVP_PKEY_EC + if (EVP_PKEY_base_id(cert_key) == EVP_PKEY_EC) + min_bits = 128; +#endif + if (EVP_PKEY_bits(cert_key) >= min_bits) + key_ok = 1; + } + EVP_PKEY_free(cert_key); + if (!key_ok) + goto bad; + + /* XXXX compare DNs or anything? */ + + return 1; + bad: + tls_log_errors(NULL, LOG_INFO, LD_CRYPTO, "checking a certificate"); + return 0; +} + +/** Warn that a certificate lifetime extends through a certain range. */ +static void +log_cert_lifetime(int severity, const X509 *cert, const char *problem, + time_t now) +{ + BIO *bio = NULL; + BUF_MEM *buf; + char *s1=NULL, *s2=NULL; + char mytime[33]; + struct tm tm; + size_t n; + + if (problem) + tor_log(severity, LD_GENERAL, + "Certificate %s. Either their clock is set wrong, or your clock " + "is wrong.", + problem); + + if (!(bio = BIO_new(BIO_s_mem()))) { + log_warn(LD_GENERAL, "Couldn't allocate BIO!"); goto end; + } + if (!(ASN1_TIME_print(bio, X509_get_notBefore_const(cert)))) { + tls_log_errors(NULL, LOG_WARN, LD_NET, "printing certificate lifetime"); + goto end; + } + BIO_get_mem_ptr(bio, &buf); + s1 = tor_strndup(buf->data, buf->length); + + (void)BIO_reset(bio); + if (!(ASN1_TIME_print(bio, X509_get_notAfter_const(cert)))) { + tls_log_errors(NULL, LOG_WARN, LD_NET, "printing certificate lifetime"); + goto end; + } + BIO_get_mem_ptr(bio, &buf); + s2 = tor_strndup(buf->data, buf->length); + + n = strftime(mytime, 32, "%b %d %H:%M:%S %Y UTC", tor_gmtime_r(&now, &tm)); + if (n > 0) { + tor_log(severity, LD_GENERAL, + "(certificate lifetime runs from %s through %s. Your time is %s.)", + s1,s2,mytime); + } else { + tor_log(severity, LD_GENERAL, + "(certificate lifetime runs from %s through %s. " + "Couldn't get your time.)", + s1, s2); + } + + end: + /* Not expected to get invoked */ + tls_log_errors(NULL, LOG_WARN, LD_NET, "getting certificate lifetime"); + if (bio) + BIO_free(bio); + tor_free(s1); + tor_free(s2); +} + +/** Helper: check whether <b>cert</b> is expired give or take + * <b>past_tolerance</b> seconds, or not-yet-valid give or take + * <b>future_tolerance</b> seconds. (Relative to the current time + * <b>now</b>.) If it is live, return 0. If it is not live, log a message + * and return -1. */ +int +tor_x509_check_cert_lifetime_internal(int severity, const X509 *cert, + time_t now, + int past_tolerance, int future_tolerance) +{ + time_t t; + + t = now + future_tolerance; + if (X509_cmp_time(X509_get_notBefore_const(cert), &t) > 0) { + log_cert_lifetime(severity, cert, "not yet valid", now); + return -1; + } + t = now - past_tolerance; + if (X509_cmp_time(X509_get_notAfter_const(cert), &t) < 0) { + log_cert_lifetime(severity, cert, "already expired", now); + return -1; + } + + return 0; +} + +#ifdef TOR_UNIT_TESTS +/* Testing only: return a new x509 cert with the same contents as <b>inp</b>, + but with the expiration time <b>new_expiration_time</b>, signed with + <b>signing_key</b>. */ +STATIC tor_x509_cert_t * +tor_x509_cert_replace_expiration(const tor_x509_cert_t *inp, + time_t new_expiration_time, + crypto_pk_t *signing_key) +{ + X509 *newc = X509_dup(inp->cert); + X509_time_adj(X509_get_notAfter(newc), 0, &new_expiration_time); + EVP_PKEY *pk = crypto_pk_get_openssl_evp_pkey_(signing_key, 1); + tor_assert(X509_sign(newc, pk, EVP_sha256())); + EVP_PKEY_free(pk); + return tor_x509_cert_new(newc); +} +#endif /* defined(TOR_UNIT_TESTS) */ |