From 117ab3e8d2ae64933b09e3ee2419bdcccdd73733 Mon Sep 17 00:00:00 2001 From: Mike Gelfand Date: Thu, 4 Dec 2014 19:18:08 +0000 Subject: [PATCH] #4400, #5462: Move DH helpers to crypto-utils On a way to factoring out OpenSSL support to a standalone file to ease addition of other crypto libraries support in the future, move helpers providing DH key exchange to crypto-utils.{c,h}. OpenSSL-related functionality (DH context management) is moved to crypto-utils-openssl.c. Since we know in advance that DH secret key management code will be the same for most of backends, implement common functionality in separate crypto-utils-fallback.c. Add new tr_dh_ctx_t and tr_dh_secret_t types and functions to be implemented by crypto backends: * tr_dh_new - allocate DH context, * tr_dh_free - free the context, * tr_dh_make_key - generate private/public keypair, * tr_dh_agree - perform DH key exchange and generate secret key, * tr_dh_secret_derive - calculate secret key hash, * tr_dh_secret_free - free the secret key, * tr_dh_align_key - align some DH key in the buffer allocated for it. Make DH secret key not accessible in plain form outside the crypto backend. This allows for implementations where the key is managed by the underlying library and is not even exposed to our backend. --- libtransmission/CMakeLists.txt | 1 + libtransmission/Makefile.am | 1 + libtransmission/crypto-test.c | 4 +- libtransmission/crypto-utils-fallback.c | 77 ++++++++++++++++ libtransmission/crypto-utils-openssl.c | 100 ++++++++++++++++++++ libtransmission/crypto-utils.c | 23 +++++ libtransmission/crypto-utils.h | 55 +++++++++++ libtransmission/crypto.c | 118 ++++++------------------ libtransmission/crypto.h | 18 ++-- libtransmission/handshake.c | 25 ++--- 10 files changed, 314 insertions(+), 108 deletions(-) create mode 100644 libtransmission/crypto-utils-fallback.c diff --git a/libtransmission/CMakeLists.txt b/libtransmission/CMakeLists.txt index 30ff96022..5da288eb3 100644 --- a/libtransmission/CMakeLists.txt +++ b/libtransmission/CMakeLists.txt @@ -15,6 +15,7 @@ set(${PROJECT_NAME}_SOURCES ConvertUTF.c crypto.c crypto-utils.c + crypto-utils-fallback.c crypto-utils-openssl.c error.c fdlimit.c diff --git a/libtransmission/Makefile.am b/libtransmission/Makefile.am index 8ddc21d30..712ce217a 100644 --- a/libtransmission/Makefile.am +++ b/libtransmission/Makefile.am @@ -29,6 +29,7 @@ libtransmission_a_SOURCES = \ ConvertUTF.c \ crypto.c \ crypto-utils.c \ + crypto-utils-fallback.c \ crypto-utils-openssl.c \ error.c \ fdlimit.c \ diff --git a/libtransmission/crypto-test.c b/libtransmission/crypto-test.c index df283b008..697686897 100644 --- a/libtransmission/crypto-test.c +++ b/libtransmission/crypto-test.c @@ -73,8 +73,8 @@ test_encrypt_decrypt (void) tr_cryptoConstruct (&a, hash, false); tr_cryptoConstruct (&b, hash, true); - tr_cryptoComputeSecret (&a, tr_cryptoGetMyPublicKey (&b, &i)); - tr_cryptoComputeSecret (&b, tr_cryptoGetMyPublicKey (&a, &i)); + check (tr_cryptoComputeSecret (&a, tr_cryptoGetMyPublicKey (&b, &i))); + check (tr_cryptoComputeSecret (&b, tr_cryptoGetMyPublicKey (&a, &i))); tr_cryptoEncryptInit (&a); tr_cryptoEncrypt (&a, sizeof (test1), test1, buf11); diff --git a/libtransmission/crypto-utils-fallback.c b/libtransmission/crypto-utils-fallback.c new file mode 100644 index 000000000..e06bc9207 --- /dev/null +++ b/libtransmission/crypto-utils-fallback.c @@ -0,0 +1,77 @@ +/* + * This file Copyright (C) Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + * $Id$ + */ + +/* This file is designed specifically to be included by other source files to + implement missing (or duplicate) functionality without exposing internal + details in header files. */ + +#include + +#include "transmission.h" +#include "crypto-utils.h" +#include "utils.h" + +/*** +**** +***/ + +#ifdef TR_CRYPTO_DH_SECRET_FALLBACK + +/* Most Diffie-Hellman backends handle secret key in the very same way: by + manually allocating memory for it and storing the value in plain form. */ + +struct tr_dh_secret +{ + size_t key_length; + uint8_t key[]; +}; + +static struct tr_dh_secret * +tr_dh_secret_new (size_t key_length) +{ + struct tr_dh_secret * handle = tr_malloc (sizeof (struct tr_dh_secret) + key_length); + handle->key_length = key_length; + return handle; +} + +static void +tr_dh_secret_align (struct tr_dh_secret * handle, + size_t current_key_length) +{ + tr_dh_align_key (handle->key, current_key_length, handle->key_length); +} + +bool +tr_dh_secret_derive (tr_dh_secret_t raw_handle, + const void * prepend_data, + size_t prepend_data_size, + const void * append_data, + size_t append_data_size, + uint8_t * hash) +{ + struct tr_dh_secret * handle = raw_handle; + + assert (handle != NULL); + assert (hash != NULL); + + return tr_sha1 (hash, + prepend_data == NULL ? "" : prepend_data, + prepend_data == NULL ? 0 : (int) prepend_data_size, + handle->key, (int) handle->key_length, + append_data, append_data == NULL ? 0 : (int) append_data_size, + NULL); +} + +void +tr_dh_secret_free (tr_dh_secret_t handle) +{ + tr_free (handle); +} + +#endif /* TR_CRYPTO_DH_SECRET_FALLBACK */ diff --git a/libtransmission/crypto-utils-openssl.c b/libtransmission/crypto-utils-openssl.c index 0270b17b3..2245f153f 100644 --- a/libtransmission/crypto-utils-openssl.c +++ b/libtransmission/crypto-utils-openssl.c @@ -9,6 +9,8 @@ #include +#include +#include #include #include #include @@ -18,6 +20,9 @@ #include "log.h" #include "utils.h" +#define TR_CRYPTO_DH_SECRET_FALLBACK +#include "crypto-utils-fallback.c" + /*** **** ***/ @@ -193,6 +198,101 @@ tr_rc4_process (tr_rc4_ctx_t handle, **** ***/ +tr_dh_ctx_t +tr_dh_new (const uint8_t * prime_num, + size_t prime_num_length, + const uint8_t * generator_num, + size_t generator_num_length) +{ + DH * handle = DH_new (); + + assert (prime_num != NULL); + assert (generator_num != NULL); + + if (!check_pointer (handle->p = BN_bin2bn (prime_num, prime_num_length, NULL)) || + !check_pointer (handle->g = BN_bin2bn (generator_num, generator_num_length, NULL))) + { + DH_free (handle); + handle = NULL; + } + + return handle; +} + +void +tr_dh_free (tr_dh_ctx_t handle) +{ + if (handle == NULL) + return; + + DH_free (handle); +} + +bool +tr_dh_make_key (tr_dh_ctx_t raw_handle, + size_t private_key_length, + uint8_t * public_key, + size_t * public_key_length) +{ + DH * handle = raw_handle; + int dh_size, my_public_key_length; + + assert (handle != NULL); + assert (public_key != NULL); + + handle->length = private_key_length * 8; + + if (!check_result (DH_generate_key (handle))) + return false; + + my_public_key_length = BN_bn2bin (handle->pub_key, public_key); + dh_size = DH_size (handle); + + tr_dh_align_key (public_key, my_public_key_length, dh_size); + + if (public_key_length != NULL) + *public_key_length = dh_size; + + return true; +} + +tr_dh_secret_t +tr_dh_agree (tr_dh_ctx_t handle, + const uint8_t * other_public_key, + size_t other_public_key_length) +{ + struct tr_dh_secret * ret; + int dh_size, secret_key_length; + BIGNUM * other_key; + + assert (handle != NULL); + assert (other_public_key != NULL); + + if (!check_pointer (other_key = BN_bin2bn (other_public_key, other_public_key_length, NULL))) + return NULL; + + dh_size = DH_size (handle); + ret = tr_dh_secret_new (dh_size); + + secret_key_length = DH_compute_key (ret->key, other_key, handle); + if (check_result_neq (secret_key_length, -1)) + { + tr_dh_secret_align (ret, secret_key_length); + } + else + { + tr_dh_secret_free (ret); + ret = NULL; + } + + BN_free (other_key); + return ret; +} + +/*** +**** +***/ + bool tr_rand_buffer (void * buffer, size_t length) diff --git a/libtransmission/crypto-utils.c b/libtransmission/crypto-utils.c index f6f0273ab..a11cdee53 100644 --- a/libtransmission/crypto-utils.c +++ b/libtransmission/crypto-utils.c @@ -10,6 +10,7 @@ #include #include #include /* abs (), srand (), rand () */ +#include /* memmove (), memset () */ #include "transmission.h" #include "crypto-utils.h" @@ -19,6 +20,28 @@ **** ***/ +void +tr_dh_align_key (uint8_t * key_buffer, + size_t key_size, + size_t buffer_size) +{ + assert (key_size <= buffer_size); + + /* DH can generate key sizes that are smaller than the size of + key buffer with exponentially decreasing probability, in which case + the msb's of key buffer need to be zeroed appropriately. */ + if (key_size < buffer_size) + { + const size_t offset = buffer_size - key_size; + memmove (key_buffer + offset, key_buffer, key_size); + memset (key_buffer, 0, offset); + } +} + +/*** +**** +***/ + bool tr_sha1 (uint8_t * hash, const void * data1, diff --git a/libtransmission/crypto-utils.h b/libtransmission/crypto-utils.h index 024728780..3091443b6 100644 --- a/libtransmission/crypto-utils.h +++ b/libtransmission/crypto-utils.h @@ -24,6 +24,10 @@ typedef void * tr_sha1_ctx_t; /** @brief Opaque RC4 context type. */ typedef void * tr_rc4_ctx_t; + /** @brief Opaque DH context type. */ +typedef void * tr_dh_ctx_t; + /** @brief Opaque DH secret key type. */ +typedef void * tr_dh_secret_t; /** * @brief Generate a SHA1 hash from one or more chunks of memory. @@ -76,6 +80,57 @@ void tr_rc4_process (tr_rc4_ctx_t handle, void * output, size_t length); +/** + * @brief Allocate and initialize new Diffie-Hellman (DH) key exchange context. + */ +tr_dh_ctx_t tr_dh_new (const uint8_t * prime_num, + size_t prime_num_length, + const uint8_t * generator_num, + size_t generator_num_length); + +/** + * @brief Free DH key exchange context. + */ +void tr_dh_free (tr_dh_ctx_t handle); + +/** + * @brief Generate private and public DH keys, export public key. + */ +bool tr_dh_make_key (tr_dh_ctx_t handle, + size_t private_key_length, + uint8_t * public_key, + size_t * public_key_length); + +/** + * @brief Perform DH key exchange, generate secret key. + */ +tr_dh_secret_t tr_dh_agree (tr_dh_ctx_t handle, + const uint8_t * other_public_key, + size_t other_public_key_length); + +/** + * @brief Calculate SHA1 hash of DH secret key, prepending and/or appending + * given data to the key during calculation. + */ +bool tr_dh_secret_derive (tr_dh_secret_t handle, + const void * prepend_data, + size_t prepend_data_size, + const void * append_data, + size_t append_data_size, + uint8_t * hash); + +/** + * @brief Free DH secret key returned by @ref tr_dh_agree. + */ +void tr_dh_secret_free (tr_dh_secret_t handle); + +/** + * @brief Align DH key (big-endian number) to required length (internal, do not use). + */ +void tr_dh_align_key (uint8_t * key_buffer, + size_t key_size, + size_t buffer_size); + /** * @brief Returns a random number in the range of [0...upper_bound). */ diff --git a/libtransmission/crypto.c b/libtransmission/crypto.c index 974bd3d54..d7ece4432 100644 --- a/libtransmission/crypto.c +++ b/libtransmission/crypto.c @@ -8,30 +8,18 @@ */ #include -#include #include /* memcpy (), memmove (), memset (), strcmp () */ -#include -#include -#include - #include "transmission.h" #include "crypto.h" #include "crypto-utils.h" -#include "log.h" #include "utils.h" -#define MY_NAME "tr_crypto" - /** *** **/ -#define KEY_LEN 96 - #define PRIME_LEN 96 - -#define DH_PRIVKEY_LEN_MIN 16 #define DH_PRIVKEY_LEN 20 static const uint8_t dh_P[PRIME_LEN] = @@ -52,53 +40,17 @@ static const uint8_t dh_G[] = { 2 }; *** **/ -#define logErrorFromSSL(...) \ - do { \ - if (tr_logLevelIsActive (TR_LOG_ERROR)) { \ - char buf[512]; \ - ERR_error_string_n (ERR_get_error (), buf, sizeof (buf)); \ - tr_logAddMessage (__FILE__, __LINE__, TR_LOG_ERROR, MY_NAME, "%s", buf); \ - } \ - } while (0) - static void ensureKeyExists (tr_crypto * crypto) { if (crypto->dh == NULL) { - int len, offset; - DH * dh = DH_new (); + size_t public_key_length; - dh->p = BN_bin2bn (dh_P, sizeof (dh_P), NULL); - if (dh->p == NULL) - logErrorFromSSL (); + crypto->dh = tr_dh_new (dh_P, sizeof (dh_P), dh_G, sizeof (dh_G)); + tr_dh_make_key (crypto->dh, DH_PRIVKEY_LEN, crypto->myPublicKey, &public_key_length); - dh->g = BN_bin2bn (dh_G, sizeof (dh_G), NULL); - if (dh->g == NULL) - logErrorFromSSL (); - - /* private DH value: strong random BN of DH_PRIVKEY_LEN*8 bits */ - dh->priv_key = BN_new (); - do - { - if (BN_rand (dh->priv_key, DH_PRIVKEY_LEN * 8, -1, 0) != 1) - logErrorFromSSL (); - } - while (BN_num_bits (dh->priv_key) < DH_PRIVKEY_LEN_MIN * 8); - - if (!DH_generate_key (dh)) - logErrorFromSSL (); - - /* DH can generate key sizes that are smaller than the size of - P with exponentially decreasing probability, in which case - the msb's of myPublicKey need to be zeroed appropriately. */ - len = BN_num_bytes (dh->pub_key); - offset = KEY_LEN - len; - assert (len <= KEY_LEN); - memset (crypto->myPublicKey, 0, offset); - BN_bn2bin (dh->pub_key, crypto->myPublicKey + offset); - - crypto->dh = dh; + assert (public_key_length == KEY_LEN); } } @@ -107,7 +59,6 @@ tr_cryptoConstruct (tr_crypto * crypto, const uint8_t * torrentHash, bool isInco { memset (crypto, 0, sizeof (tr_crypto)); - crypto->dh = NULL; crypto->isIncoming = isIncoming; tr_cryptoSetTorrentHash (crypto, torrentHash); } @@ -115,8 +66,8 @@ tr_cryptoConstruct (tr_crypto * crypto, const uint8_t * torrentHash, bool isInco void tr_cryptoDestruct (tr_crypto * crypto) { - if (crypto->dh != NULL) - DH_free (crypto->dh); + tr_dh_secret_free (crypto->mySecret); + tr_dh_free (crypto->dh); tr_rc4_free (crypto->enc_key); tr_rc4_free (crypto->dec_key); } @@ -125,37 +76,13 @@ tr_cryptoDestruct (tr_crypto * crypto) *** **/ -const uint8_t* +bool tr_cryptoComputeSecret (tr_crypto * crypto, const uint8_t * peerPublicKey) { - DH * dh; - int len; - uint8_t secret[KEY_LEN]; - BIGNUM * bn = BN_bin2bn (peerPublicKey, KEY_LEN, NULL); - ensureKeyExists (crypto); - dh = crypto->dh; - - assert (DH_size (dh) == KEY_LEN); - - len = DH_compute_key (secret, bn, dh); - if (len == -1) - { - logErrorFromSSL (); - } - else - { - int offset; - assert (len <= KEY_LEN); - offset = KEY_LEN - len; - memset (crypto->mySecret, 0, offset); - memcpy (crypto->mySecret + offset, secret, len); - crypto->mySecretIsSet = true; - } - - BN_free (bn); - return crypto->mySecret; + crypto->mySecret = tr_dh_agree (crypto->dh, peerPublicKey, KEY_LEN); + return crypto->mySecret != NULL; } const uint8_t* @@ -179,16 +106,14 @@ initRC4 (tr_crypto * crypto, uint8_t buf[SHA_DIGEST_LENGTH]; assert (crypto->torrentHashIsSet); - assert (crypto->mySecretIsSet); if (*setme == NULL) *setme = tr_rc4_new (); - if (tr_sha1 (buf, - key, 4, - crypto->mySecret, KEY_LEN, - crypto->torrentHash, SHA_DIGEST_LENGTH, - NULL)) + if (tr_cryptoSecretKeySha1 (crypto, + key, 4, + crypto->torrentHash, SHA_DIGEST_LENGTH, + buf)) tr_rc4_set_key (*setme, buf, SHA_DIGEST_LENGTH); } @@ -246,6 +171,23 @@ tr_cryptoEncrypt (tr_crypto * crypto, tr_rc4_process (crypto->enc_key, buf_in, buf_out, buf_len); } +bool +tr_cryptoSecretKeySha1 (const tr_crypto * crypto, + const void * prepend_data, + size_t prepend_data_size, + const void * append_data, + size_t append_data_size, + uint8_t * hash) +{ + assert (crypto != NULL); + assert (crypto->mySecret != NULL); + + return tr_dh_secret_derive (crypto->mySecret, + prepend_data, prepend_data_size, + append_data, append_data_size, + hash); +} + /** *** **/ diff --git a/libtransmission/crypto.h b/libtransmission/crypto.h index 2a0baf554..d7c093338 100644 --- a/libtransmission/crypto.h +++ b/libtransmission/crypto.h @@ -24,11 +24,9 @@ *** @{ **/ -#include /* DH */ - enum { - KEY_LEN = 96 + KEY_LEN = 96 }; /** @brief Holds state information for encrypted peer communications */ @@ -36,13 +34,12 @@ typedef struct { tr_rc4_ctx_t dec_key; tr_rc4_ctx_t enc_key; - DH * dh; + tr_dh_ctx_t dh; uint8_t myPublicKey[KEY_LEN]; - uint8_t mySecret[KEY_LEN]; + tr_dh_secret_t mySecret; uint8_t torrentHash[SHA_DIGEST_LENGTH]; bool isIncoming; bool torrentHashIsSet; - bool mySecretIsSet; } tr_crypto; @@ -59,7 +56,7 @@ const uint8_t* tr_cryptoGetTorrentHash (const tr_crypto * crypto); bool tr_cryptoHasTorrentHash (const tr_crypto * crypto); -const uint8_t* tr_cryptoComputeSecret (tr_crypto * crypto, +bool tr_cryptoComputeSecret (tr_crypto * crypto, const uint8_t * peerPublicKey); const uint8_t* tr_cryptoGetMyPublicKey (const tr_crypto * crypto, @@ -79,6 +76,13 @@ void tr_cryptoEncrypt (tr_crypto * crypto, const void * buf_in, void * buf_out); +bool tr_cryptoSecretKeySha1 (const tr_crypto * crypto, + const void * prepend_data, + size_t prepend_data_size, + const void * append_data, + size_t append_data_size, + uint8_t * hash); + /* @} */ /** diff --git a/libtransmission/handshake.c b/libtransmission/handshake.c index 25c91b862..c307f2f3d 100644 --- a/libtransmission/handshake.c +++ b/libtransmission/handshake.c @@ -101,7 +101,6 @@ struct tr_handshake tr_peerIo * io; tr_crypto * crypto; tr_session * session; - uint8_t mySecret[KEY_LEN]; handshake_state_t state; tr_encryption_mode encryptionMode; uint16_t pad_c_len; @@ -385,11 +384,18 @@ getCryptoSelect (const tr_handshake * handshake, return 0; } +static void +computeRequestHash (const tr_handshake * handshake, + const char * name, + uint8_t * hash) +{ + tr_cryptoSecretKeySha1 (handshake->crypto, name, 4, NULL, 0, hash); +} + static int readYb (tr_handshake * handshake, struct evbuffer * inbuf) { int isEncrypted; - const uint8_t * secret; uint8_t yb[KEY_LEN]; struct evbuffer * outbuf; size_t needlen = HANDSHAKE_NAME_LEN; @@ -420,8 +426,7 @@ readYb (tr_handshake * handshake, struct evbuffer * inbuf) /* compute the secret */ evbuffer_remove (inbuf, yb, KEY_LEN); - secret = tr_cryptoComputeSecret (handshake->crypto, yb); - memcpy (handshake->mySecret, secret, KEY_LEN); + tr_cryptoComputeSecret (handshake->crypto, yb); /* now send these: HASH ('req1', S), HASH ('req2', SKEY) xor HASH ('req3', S), * ENCRYPT (VC, crypto_provide, len (PadC), PadC, len (IA)), ENCRYPT (IA) */ @@ -430,7 +435,7 @@ readYb (tr_handshake * handshake, struct evbuffer * inbuf) /* HASH ('req1', S) */ { uint8_t req1[SHA_DIGEST_LENGTH]; - tr_sha1 (req1, "req1", 4, secret, KEY_LEN, NULL); + computeRequestHash (handshake, "req1", req1); evbuffer_add (outbuf, req1, SHA_DIGEST_LENGTH); } @@ -442,7 +447,7 @@ readYb (tr_handshake * handshake, struct evbuffer * inbuf) uint8_t buf[SHA_DIGEST_LENGTH]; tr_sha1 (req2, "req2", 4, tr_cryptoGetTorrentHash (handshake->crypto), SHA_DIGEST_LENGTH, NULL); - tr_sha1 (req3, "req3", 4, secret, KEY_LEN, NULL); + computeRequestHash (handshake, "req3", req3); for (i=0; icrypto, ya); - memcpy (handshake->mySecret, secret, KEY_LEN); - tr_sha1 (handshake->myReq1, "req1", 4, secret, KEY_LEN, NULL); + tr_cryptoComputeSecret (handshake->crypto, ya); + computeRequestHash (handshake, "req1", handshake->myReq1); /* send our public key to the peer */ dbgmsg (handshake, "sending B->A: Diffie Hellman Yb, PadB"); @@ -810,7 +813,7 @@ readCryptoProvide (tr_handshake * handshake, * by building the latter and xor'ing it with what the peer sent us */ dbgmsg (handshake, "reading obfuscated torrent hash..."); evbuffer_remove (inbuf, req2, SHA_DIGEST_LENGTH); - tr_sha1 (req3, "req3", 4, handshake->mySecret, KEY_LEN, NULL); + computeRequestHash (handshake, "req3", req3); for (i=0; isession, obfuscatedTorrentHash)))