#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.
This commit is contained in:
Mike Gelfand 2014-12-04 19:18:08 +00:00
parent 7e23390fa2
commit 117ab3e8d2
10 changed files with 314 additions and 108 deletions

View File

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

View File

@ -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 \

View File

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

View File

@ -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 <assert.h>
#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 */

View File

@ -9,6 +9,8 @@
#include <assert.h>
#include <openssl/bn.h>
#include <openssl/dh.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
@ -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)

View File

@ -10,6 +10,7 @@
#include <assert.h>
#include <stdarg.h>
#include <stdlib.h> /* abs (), srand (), rand () */
#include <string.h> /* 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,

View File

@ -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).
*/

View File

@ -8,30 +8,18 @@
*/
#include <assert.h>
#include <stdarg.h>
#include <string.h> /* memcpy (), memmove (), memset (), strcmp () */
#include <openssl/bn.h>
#include <openssl/dh.h>
#include <openssl/err.h>
#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);
}
/**
***
**/

View File

@ -24,11 +24,9 @@
*** @{
**/
#include <openssl/dh.h> /* 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);
/* @} */
/**

View File

@ -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; i<SHA_DIGEST_LENGTH; ++i)
buf[i] = req2[i] ^ req3[i];
@ -728,7 +733,6 @@ readYa (tr_handshake * handshake,
uint8_t ya[KEY_LEN];
uint8_t * walk, outbuf[KEY_LEN + PadB_MAXLEN];
const uint8_t * myKey;
const uint8_t * secret;
int len;
dbgmsg (handshake, "in readYa... need %d, have %"TR_PRIuSIZE,
@ -738,9 +742,8 @@ readYa (tr_handshake * handshake,
/* read the incoming peer's public key */
evbuffer_remove (inbuf, ya, KEY_LEN);
secret = tr_cryptoComputeSecret (handshake->crypto, 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; i<SHA_DIGEST_LENGTH; ++i)
obfuscatedTorrentHash[i] = req2[i] ^ req3[i];
if ((tor = tr_torrentFindFromObfuscatedHash (handshake->session, obfuscatedTorrentHash)))