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)))