tls: add sha256 hmac and prf code
authorDenys Vlasenko <vda.linux@googlemail.com>
Mon, 16 Jan 2017 03:25:01 +0000 (04:25 +0100)
committerDenys Vlasenko <vda.linux@googlemail.com>
Mon, 16 Jan 2017 03:25:01 +0000 (04:25 +0100)
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
networking/tls.c
networking/tls.h
networking/tls_rsa.c

index 44c9da1952b2c4f9eae9d5e36462c80e0666192a..9bc75f824af788951a8458f530089a78669eca94 100644 (file)
@@ -146,6 +146,11 @@ typedef struct tls_state {
 
        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
        // |6.2.1. Fragmentation
        // |  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->fd = -1;
+       sha256_begin(&tls->handshake_sha256_ctx);
        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)
 {
        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);
 }
 
+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
  */
@@ -535,7 +699,9 @@ static void send_client_hello(tls_state_t *tls)
        hello.comprtypes_len = 1;
        //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 */
        for (;;) {
                uint8_t buf[16*1024];
@@ -589,6 +755,7 @@ static void get_server_hello(tls_state_t *tls)
                tls_error_die(tls);
        }
        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)
@@ -665,7 +832,23 @@ static void send_client_key_exchange(tls_state_t *tls)
                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)
@@ -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,
                01
        };
+       /* Not "xwrite_and_hash": this is not a handshake message */
        xwrite(tls->fd, rec, sizeof(rec));
 }
 
 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
 // A Finished message is always sent immediately after a change
 // 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
 // verify_data_length has a verify_data_length equal to 12.  This
 // 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)
@@ -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 UNUSED_PARAM, char **argv)
 {
index 20317ecc33b6d04467e8a74fa0994d5f69870c53..3a8a3d8265a41f8b82be1ddea1d92ea03d2d8456 100644 (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 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 */
 #define memset_s(A,B,C,D) memset((A),(C),(D))
index 3114435dd2e28e5a88c474c4bf1bc2e1dc184b82..df711865cbfe67031e1b5efe8743f70d6e026bcc 100644 (file)
@@ -187,7 +187,7 @@ int32 psRsaEncryptPub(psPool_t *pool, psRsaKey_t *key,
 
        size = key->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;
        }