tls: add sha256 hmac and prf code

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2017-01-16 04:25:01 +01:00
parent 6c73aaff38
commit 936e83e694
3 changed files with 225 additions and 35 deletions

View File

@ -146,6 +146,11 @@ typedef struct tls_state {
psRsaKey_t server_rsa_pub_key; psRsaKey_t server_rsa_pub_key;
sha256_ctx_t handshake_sha256_ctx;
uint8_t client_and_server_rand32[2 * 32];
uint8_t master_secret[48];
// RFC 5246 // RFC 5246
// |6.2.1. Fragmentation // |6.2.1. Fragmentation
// | The record layer fragments information blocks into TLSPlaintext // | The record layer fragments information blocks into TLSPlaintext
@ -187,9 +192,18 @@ tls_state_t *new_tls_state(void)
{ {
tls_state_t *tls = xzalloc(sizeof(*tls)); tls_state_t *tls = xzalloc(sizeof(*tls));
tls->fd = -1; tls->fd = -1;
sha256_begin(&tls->handshake_sha256_ctx);
return tls; return tls;
} }
static void xwrite_and_hash(tls_state_t *tls, const void *buf, unsigned size)
{
xwrite(tls->fd, buf, size);
/* hash does not include record headers */
if (size > 5)
sha256_hash(&tls->handshake_sha256_ctx, (uint8_t*)buf + 5, size - 5);
}
static unsigned get24be(const uint8_t *p) static unsigned get24be(const uint8_t *p)
{ {
return 0x100*(0x100*p[0] + p[1]) + p[2]; return 0x100*(0x100*p[0] + p[1]) + p[2];
@ -494,6 +508,156 @@ static void find_key_in_der_cert(tls_state_t *tls, uint8_t *der, int len)
dbg("server_rsa_pub_key.size:%d\n", tls->server_rsa_pub_key.size); dbg("server_rsa_pub_key.size:%d\n", tls->server_rsa_pub_key.size);
} }
enum {
SHA256_INSIZE = 64,
SHA256_OUTSIZE = 32,
};
static void hash_sha256(uint8_t out[SHA256_OUTSIZE], const void *data, unsigned size)
{
sha256_ctx_t ctx;
sha256_begin(&ctx);
sha256_hash(&ctx, data, size);
sha256_end(&ctx, out);
}
// RFC 2104: HMAC(key, text) based on a hash H (say, sha256) is:
// ipad = [0x36 x INSIZE]
// opad = [0x5c x INSIZE]
// HMAC(key, text) = H((key XOR opad) + H((key XOR ipad) + text))
//
// H(key XOR opad) and H(key XOR ipad) can be precomputed
// if we often need HMAC hmac with the same key.
//
// text is often given in disjoint pieces.
static void hmac_sha256_precomputed_v(uint8_t out[SHA256_OUTSIZE],
sha256_ctx_t *hashed_key_xor_ipad,
sha256_ctx_t *hashed_key_xor_opad,
va_list va)
{
uint8_t *text;
/* hashed_key_xor_ipad contains unclosed "H((key XOR ipad) +" state */
/* hashed_key_xor_opad contains unclosed "H((key XOR opad) +" state */
/* calculate out = H((key XOR ipad) + text) */
while ((text = va_arg(va, uint8_t*)) != NULL) {
unsigned text_size = va_arg(va, unsigned);
sha256_hash(hashed_key_xor_ipad, text, text_size);
}
sha256_end(hashed_key_xor_ipad, out);
/* out = H((key XOR opad) + out) */
sha256_hash(hashed_key_xor_opad, out, SHA256_OUTSIZE);
sha256_end(hashed_key_xor_opad, out);
}
static void hmac_sha256(uint8_t out[SHA256_OUTSIZE], uint8_t *key, unsigned key_size, ...)
{
sha256_ctx_t hashed_key_xor_ipad;
sha256_ctx_t hashed_key_xor_opad;
uint8_t key_xor_ipad[SHA256_INSIZE];
uint8_t key_xor_opad[SHA256_INSIZE];
uint8_t tempkey[SHA256_OUTSIZE];
va_list va;
int i;
va_start(va, key_size);
// "The authentication key can be of any length up to INSIZE, the
// block length of the hash function. Applications that use keys longer
// than INSIZE bytes will first hash the key using H and then use the
// resultant OUTSIZE byte string as the actual key to HMAC."
if (key_size > SHA256_INSIZE) {
hash_sha256(tempkey, key, key_size);
key = tempkey;
key_size = SHA256_OUTSIZE;
}
for (i = 0; i < key_size; i++) {
key_xor_ipad[i] = key[i] ^ 0x36;
key_xor_opad[i] = key[i] ^ 0x5c;
}
for (; i < SHA256_INSIZE; i++) {
key_xor_ipad[i] = 0x36;
key_xor_opad[i] = 0x5c;
}
sha256_begin(&hashed_key_xor_ipad);
sha256_hash(&hashed_key_xor_ipad, key_xor_ipad, SHA256_INSIZE);
sha256_begin(&hashed_key_xor_opad);
sha256_hash(&hashed_key_xor_opad, key_xor_opad, SHA256_INSIZE);
hmac_sha256_precomputed_v(out, &hashed_key_xor_ipad, &hashed_key_xor_opad, va);
va_end(va);
}
// RFC 5246:
// 5. HMAC and the Pseudorandom Function
//...
// In this section, we define one PRF, based on HMAC. This PRF with the
// SHA-256 hash function is used for all cipher suites defined in this
// document and in TLS documents published prior to this document when
// TLS 1.2 is negotiated.
//...
// P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
// HMAC_hash(secret, A(2) + seed) +
// HMAC_hash(secret, A(3) + seed) + ...
// where + indicates concatenation.
// A() is defined as:
// A(0) = seed
// A(1) = HMAC_hash(secret, A(0)) = HMAC_hash(secret, seed)
// A(i) = HMAC_hash(secret, A(i-1))
// P_hash can be iterated as many times as necessary to produce the
// required quantity of data. For example, if P_SHA256 is being used to
// create 80 bytes of data, it will have to be iterated three times
// (through A(3)), creating 96 bytes of output data; the last 16 bytes
// of the final iteration will then be discarded, leaving 80 bytes of
// output data.
//
// TLS's PRF is created by applying P_hash to the secret as:
//
// PRF(secret, label, seed) = P_<hash>(secret, label + seed)
//
// The label is an ASCII string.
static void tls_prf_hmac_sha256(
uint8_t *outbuf, unsigned outbuf_size,
uint8_t *secret, unsigned secret_size,
const char *label,
uint8_t *seed, unsigned seed_size)
{
uint8_t a[SHA256_OUTSIZE];
uint8_t *out_p = outbuf;
unsigned label_size = strlen(label);
/* In P_hash() calculation, "seed" is "label + seed": */
#define SEED label, label_size, seed, seed_size
#define SECRET secret, secret_size
#define A a, (int)(sizeof(a))
/* A(1) = HMAC_hash(secret, seed) */
hmac_sha256(a, SECRET, SEED, NULL);
for(;;) {
/* HMAC_hash(secret, A(1) + seed) */
if (outbuf_size <= SHA256_OUTSIZE) {
/* Last, possibly incomplete, block */
/* (use a[] as temp buffer) */
hmac_sha256(a, SECRET, A, SEED, NULL);
memcpy(out_p, a, outbuf_size);
return;
}
/* Not last block. Store directly to result buffer */
hmac_sha256(out_p, SECRET, A, SEED, NULL);
out_p += SHA256_OUTSIZE;
outbuf_size -= SHA256_OUTSIZE;
/* A(2) = HMAC_hash(secret, A(1)) */
hmac_sha256(a, SECRET, A, NULL);
}
#undef A
#undef SECRET
#undef SEED
}
/* /*
* TLS Handshake routines * TLS Handshake routines
*/ */
@ -535,7 +699,9 @@ static void send_client_hello(tls_state_t *tls)
hello.comprtypes_len = 1; hello.comprtypes_len = 1;
//hello.comprtypes[0] = 0; //hello.comprtypes[0] = 0;
xwrite(tls->fd, &hello, sizeof(hello)); xwrite_and_hash(tls, &hello, sizeof(hello));
memcpy(tls->client_and_server_rand32, hello.rand32, sizeof(hello.rand32));
#if 0 /* dump */ #if 0 /* dump */
for (;;) { for (;;) {
uint8_t buf[16*1024]; uint8_t buf[16*1024];
@ -589,6 +755,7 @@ static void get_server_hello(tls_state_t *tls)
tls_error_die(tls); tls_error_die(tls);
} }
dbg("got SERVER_HELLO\n"); dbg("got SERVER_HELLO\n");
memcpy(tls->client_and_server_rand32 + 32, hp->rand32, sizeof(hp->rand32));
} }
static void get_server_cert(tls_state_t *tls) static void get_server_cert(tls_state_t *tls)
@ -665,7 +832,23 @@ static void send_client_key_exchange(tls_state_t *tls)
data_param_ignored data_param_ignored
); );
xwrite(tls->fd, &record, sizeof(record)); xwrite_and_hash(tls, &record, sizeof(record));
// RFC 5246
// For all key exchange methods, the same algorithm is used to convert
// the pre_master_secret into the master_secret. The pre_master_secret
// should be deleted from memory once the master_secret has been
// computed.
// master_secret = PRF(pre_master_secret, "master secret",
// ClientHello.random + ServerHello.random)
// [0..47];
// The master secret is always exactly 48 bytes in length. The length
// of the premaster secret will vary depending on key exchange method.
tls_prf_hmac_sha256(tls->master_secret, sizeof(tls->master_secret),
rsa_premaster, sizeof(rsa_premaster),
"master secret",
tls->client_and_server_rand32, sizeof(tls->client_and_server_rand32)
);
} }
static void send_change_cipher_spec(tls_state_t *tls) static void send_change_cipher_spec(tls_state_t *tls)
@ -674,42 +857,12 @@ static void send_change_cipher_spec(tls_state_t *tls)
RECORD_TYPE_CHANGE_CIPHER_SPEC, TLS_MAJ, TLS_MIN, 00, 01, RECORD_TYPE_CHANGE_CIPHER_SPEC, TLS_MAJ, TLS_MIN, 00, 01,
01 01
}; };
/* Not "xwrite_and_hash": this is not a handshake message */
xwrite(tls->fd, rec, sizeof(rec)); xwrite(tls->fd, rec, sizeof(rec));
} }
static void send_client_finished(tls_state_t *tls) static void send_client_finished(tls_state_t *tls)
{ {
// RFC 5246 on pseudorandom function (PRF):
//
// 5. HMAC and the Pseudorandom Function
//...
// In this section, we define one PRF, based on HMAC. This PRF with the
// SHA-256 hash function is used for all cipher suites defined in this
// document and in TLS documents published prior to this document when
// TLS 1.2 is negotiated.
//...
// P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
// HMAC_hash(secret, A(2) + seed) +
// HMAC_hash(secret, A(3) + seed) + ...
// where + indicates concatenation.
// A() is defined as:
// A(0) = seed
// A(i) = HMAC_hash(secret, A(i-1))
// P_hash can be iterated as many times as necessary to produce the
// required quantity of data. For example, if P_SHA256 is being used to
// create 80 bytes of data, it will have to be iterated three times
// (through A(3)), creating 96 bytes of output data; the last 16 bytes
// of the final iteration will then be discarded, leaving 80 bytes of
// output data.
//
// TLS's PRF is created by applying P_hash to the secret as:
//
// PRF(secret, label, seed) = P_<hash>(secret, label + seed)
//
// The label is an ASCII string.
tls->fd = 0;
// 7.4.9. Finished // 7.4.9. Finished
// A Finished message is always sent immediately after a change // A Finished message is always sent immediately after a change
// cipher spec message to verify that the key exchange and // cipher spec message to verify that the key exchange and
@ -747,6 +900,39 @@ static void send_client_finished(tls_state_t *tls)
// suite. Any cipher suite which does not explicitly specify // suite. Any cipher suite which does not explicitly specify
// verify_data_length has a verify_data_length equal to 12. This // verify_data_length has a verify_data_length equal to 12. This
// includes all existing cipher suites. // includes all existing cipher suites.
struct client_finished {
struct record_hdr xhdr;
uint8_t type;
uint8_t len24_hi, len24_mid, len24_lo;
uint8_t prf_result[12];
};
struct client_finished record;
uint8_t handshake_hash[SHA256_OUTSIZE];
sha256_ctx_t ctx;
memset(&record, 0, sizeof(record));
record.xhdr.type = RECORD_TYPE_HANDSHAKE;
record.xhdr.proto_maj = TLS_MAJ;
record.xhdr.proto_min = TLS_MIN;
record.xhdr.len16_hi = (sizeof(record) - sizeof(record.xhdr)) >> 8;
record.xhdr.len16_lo = (sizeof(record) - sizeof(record.xhdr)) & 0xff;
record.type = HANDSHAKE_FINISHED;
//record.len24_hi = 0;
record.len24_mid = (sizeof(record) - sizeof(record.xhdr) - 4) >> 8;
record.len24_lo = (sizeof(record) - sizeof(record.xhdr) - 4) & 0xff;
//FIXME ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this code is repeatable
ctx = tls->handshake_sha256_ctx; /* struct copy */
sha256_end(&ctx, handshake_hash);
tls_prf_hmac_sha256(record.prf_result, sizeof(record.prf_result),
tls->master_secret, sizeof(tls->master_secret),
"client finished",
handshake_hash, sizeof(handshake_hash)
);
//(1) TODO: well, this should be encrypted on send, really.
//(2) do we really need to also hash it?
xwrite_and_hash(tls, &record, sizeof(record));
} }
static void get_change_cipher_spec(tls_state_t *tls) static void get_change_cipher_spec(tls_state_t *tls)
@ -831,6 +1017,10 @@ static void tls_handshake(tls_state_t *tls)
} }
} }
// To run a test server using openssl:
// openssl s_server -key key.pem -cert server.pem -debug -tls1_2 -no_tls1 -no_tls1_1
// openssl req -x509 -newkey rsa:$((4096/4*3)) -keyout key.pem -out server.pem -nodes -days 99999 -subj '/CN=localhost'
int tls_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int tls_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int tls_main(int argc UNUSED_PARAM, char **argv) int tls_main(int argc UNUSED_PARAM, char **argv)
{ {

View File

@ -59,7 +59,7 @@ void tls_get_random(void *buf, unsigned len);
#define matrixCryptoGetPrngData(buf, len, userPtr) (tls_get_random(buf, len), PS_SUCCESS) #define matrixCryptoGetPrngData(buf, len, userPtr) (tls_get_random(buf, len), PS_SUCCESS)
#define psFree(p, pool) free(p) #define psFree(p, pool) free(p)
#define psTraceCrypto(msg) bb_error_msg_and_die(msg) #define psTraceCrypto(...) bb_error_msg_and_die(__VA_ARGS__)
/* Secure zerofill */ /* Secure zerofill */
#define memset_s(A,B,C,D) memset((A),(C),(D)) #define memset_s(A,B,C,D) memset((A),(C),(D))

View File

@ -187,7 +187,7 @@ int32 psRsaEncryptPub(psPool_t *pool, psRsaKey_t *key,
size = key->size; size = key->size;
if (outlen < size) { if (outlen < size) {
psTraceCrypto("Error on bad outlen parameter to psRsaEncryptPub\n"); psTraceCrypto("Error on bad outlen parameter to psRsaEncryptPub: outlen:%d < size:%d", outlen, size);
return PS_ARG_FAIL; return PS_ARG_FAIL;
} }