From abbf17abccbf832365d9acf1c280369ba7d5f8b2 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 20 Jan 2017 03:15:09 +0100 Subject: [PATCH] tls: add the i/o loop - largish rework of i/o buffering Signed-off-by: Denys Vlasenko --- networking/tls.c | 274 ++++++++++++++++++++++++++++------------------- 1 file changed, 166 insertions(+), 108 deletions(-) diff --git a/networking/tls.c b/networking/tls.c index 092735884..71be2def8 100644 --- a/networking/tls.c +++ b/networking/tls.c @@ -23,9 +23,10 @@ //usage:#define tls_full_usage "\n\n" #include "tls.h" +#include "common_bufsiz.h" #define TLS_DEBUG 1 -#define TLS_DEBUG_HASH 0 +#define TLS_DEBUG_HASH 1 #define TLS_DEBUG_DER 0 #if TLS_DEBUG @@ -150,9 +151,8 @@ // works against "openssl s_server -cipher NULL" // and against wolfssl-3.9.10-stable/examples/server/server.c: //#define CIPHER_ID TLS_RSA_WITH_NULL_SHA256 // for testing (does everything except encrypting) -// "works", meaning -// "can send encrypted FINISHED to wolfssl-3.9.10-stable/examples/server/server.c", -// don't yet read its encrypted answers: +// works against wolfssl-3.9.10-stable/examples/server/server.c +// (getting back and decrypt ok first application data message) #define CIPHER_ID TLS_RSA_WITH_AES_256_CBC_SHA256 // ok, no SERVER_KEY_EXCHANGE enum { @@ -162,6 +162,11 @@ enum { AES_BLOCKSIZE = 16, AES128_KEYSIZE = 16, AES256_KEYSIZE = 32, + + MAX_TLS_RECORD = (1 << 14), + OUTBUF_PFX = 8 + AES_BLOCKSIZE, /* header + IV */ + OUTBUF_SFX = SHA256_OUTSIZE + AES_BLOCKSIZE, /* MAC + padding */ + MAX_OTBUF = MAX_TLS_RECORD - OUTBUF_PFX - OUTBUF_SFX, }; struct record_hdr { @@ -195,6 +200,9 @@ typedef struct tls_state { // exceed 2^64-1. uint64_t write_seq64_be; + int outbuf_size; + uint8_t *outbuf; + // RFC 5246 // |6.2.1. Fragmentation // | The record layer fragments information blocks into TLSPlaintext @@ -225,7 +233,6 @@ typedef struct tls_state { //needed? uint64_t align____; uint8_t inbuf[20*1024]; - uint8_t outbuf[20*1024]; } tls_state_t; @@ -462,6 +469,17 @@ static void tls_error_die(tls_state_t *tls) xfunc_die(); } +static void *tls_get_outbuf(tls_state_t *tls, int len) +{ + if (len > MAX_OTBUF) + xfunc_die(); + if (tls->outbuf_size < len + OUTBUF_PFX + OUTBUF_SFX) { + tls->outbuf_size = len + OUTBUF_PFX + OUTBUF_SFX; + tls->outbuf = xrealloc(tls->outbuf, tls->outbuf_size); + } + return tls->outbuf + OUTBUF_PFX; +} + // RFC 5246 // 6.2.3.1. Null or Standard Stream Cipher // @@ -501,39 +519,41 @@ static void tls_error_die(tls_state_t *tls) // -------- ----------- ---------- -------------- // SHA HMAC-SHA1 20 20 // SHA256 HMAC-SHA256 32 32 -static void xwrite_and_hash(tls_state_t *tls, /*const*/ void *buf, unsigned size) +static void xwrite_encrypted(tls_state_t *tls, unsigned size, unsigned type) { - uint8_t mac_hash[SHA256_OUTSIZE]; - struct record_hdr *xhdr = buf; + uint8_t *buf = tls->outbuf + OUTBUF_PFX; + struct record_hdr *xhdr; - if (!tls->encrypt_on_write) { - xwrite(tls->fd, buf, size); - dbg("wrote %u bytes\n", size); - /* Handshake hash does not include record headers */ - if (size > 5 && xhdr->type == RECORD_TYPE_HANDSHAKE) { - sha256_hash_dbg(">> sha256:%s", &tls->handshake_sha256_ctx, (uint8_t*)buf + 5, size - 5); - } - return; - } + xhdr = (void*)(buf - sizeof(*xhdr)); + if (CIPHER_ID != TLS_RSA_WITH_NULL_SHA256) + xhdr = (void*)(buf - sizeof(*xhdr) - AES_BLOCKSIZE); /* place for IV */ + + xhdr->type = type; + xhdr->proto_maj = TLS_MAJ; + xhdr->proto_min = TLS_MIN; + /* fake unencrypted record header len for MAC calculation */ + xhdr->len16_hi = size >> 8; + xhdr->len16_lo = size & 0xff; + /* Calculate MAC signature */ //TODO: convert hmac_sha256 to precomputed - hmac_sha256(mac_hash, + hmac_sha256(buf + size, tls->client_write_MAC_key, sizeof(tls->client_write_MAC_key), &tls->write_seq64_be, sizeof(tls->write_seq64_be), + xhdr, sizeof(*xhdr), buf, size, NULL); tls->write_seq64_be = SWAP_BE64(1 + SWAP_BE64(tls->write_seq64_be)); + size += SHA256_OUTSIZE; + if (CIPHER_ID == TLS_RSA_WITH_NULL_SHA256) { /* No encryption, only signing */ - xhdr->len16_lo += SHA256_OUTSIZE; -//FIXME: overflow into len16_hi? - xwrite(tls->fd, buf, size); - xhdr->len16_lo -= SHA256_OUTSIZE; - dbg("wrote %u bytes\n", size); - - xwrite(tls->fd, mac_hash, sizeof(mac_hash)); - dbg("wrote %u bytes of hash\n", (int)sizeof(mac_hash)); + xhdr->len16_hi = size >> 8; + xhdr->len16_lo = size & 0xff; + dump_hex(">> %s\n", xhdr, sizeof(*xhdr) + size); + xwrite(tls->fd, xhdr, sizeof(*xhdr) + size); + dbg("wrote %u bytes (NULL crypt, SHA256 hash)\n", size); return; } @@ -579,13 +599,8 @@ static void xwrite_and_hash(tls_state_t *tls, /*const*/ void *buf, unsigned size uint8_t padding_length; /* Build IV+content+MAC+padding in outbuf */ - tls_get_random(tls->outbuf, AES_BLOCKSIZE); /* IV */ - p = tls->outbuf + AES_BLOCKSIZE; - size -= sizeof(*xhdr); - dbg("before crypt: 5 hdr + %u data + %u hash bytes\n", size, (int)sizeof(mac_hash)); - p = mempcpy(p, buf + sizeof(*xhdr), size); /* content */ - p = mempcpy(p, mac_hash, sizeof(mac_hash)); /* MAC */ - size += sizeof(mac_hash); + tls_get_random(buf - AES_BLOCKSIZE, AES_BLOCKSIZE); /* IV */ + dbg("before crypt: 5 hdr + %u data + %u hash bytes\n", size, SHA256_OUTSIZE); // RFC is talking nonsense: // Padding that is added to force the length of the plaintext to be // an integral multiple of the block cipher's block length. @@ -601,6 +616,7 @@ static void xwrite_and_hash(tls_state_t *tls, /*const*/ void *buf, unsigned size // If you need no bytes to reach BLOCKSIZE, you have to pad a full // BLOCKSIZE with bytes of value (BLOCKSIZE-1). // It's ok to have more than minimum padding, but we do minimum. + p = buf + size; padding_length = (~size) & (AES_BLOCKSIZE - 1); do { *p++ = padding_length; /* padding */ @@ -608,12 +624,12 @@ static void xwrite_and_hash(tls_state_t *tls, /*const*/ void *buf, unsigned size } while ((size & (AES_BLOCKSIZE - 1)) != 0); /* Encrypt content+MAC+padding in place */ - psAesInit(&ctx, tls->outbuf, /* IV */ + psAesInit(&ctx, buf - AES_BLOCKSIZE, /* IV */ tls->client_write_key, sizeof(tls->client_write_key) ); psAesEncrypt(&ctx, - tls->outbuf + AES_BLOCKSIZE, /* plaintext */ - tls->outbuf + AES_BLOCKSIZE, /* ciphertext */ + buf, /* plaintext */ + buf, /* ciphertext */ size ); @@ -623,14 +639,33 @@ static void xwrite_and_hash(tls_state_t *tls, /*const*/ void *buf, unsigned size size += AES_BLOCKSIZE; /* + IV */ xhdr->len16_hi = size >> 8; xhdr->len16_lo = size & 0xff; - xwrite(tls->fd, xhdr, sizeof(*xhdr)); - xwrite(tls->fd, tls->outbuf, size); + dump_hex(">> %s\n", xhdr, sizeof(*xhdr) + size); + xwrite(tls->fd, xhdr, sizeof(*xhdr) + size); dbg("wrote %u bytes\n", (int)sizeof(*xhdr) + size); -//restore xhdr->len16_hi = ; -//restore xhdr->len16_lo = ; } } +static void xwrite_and_update_handshake_hash(tls_state_t *tls, unsigned size) +{ + if (!tls->encrypt_on_write) { + uint8_t *buf = tls->outbuf + OUTBUF_PFX; + struct record_hdr *xhdr = (void*)(buf - sizeof(*xhdr)); + + xhdr->type = RECORD_TYPE_HANDSHAKE; + xhdr->proto_maj = TLS_MAJ; + xhdr->proto_min = TLS_MIN; + xhdr->len16_hi = size >> 8; + xhdr->len16_lo = size & 0xff; + dump_hex(">> %s\n", xhdr, sizeof(*xhdr) + size); + xwrite(tls->fd, xhdr, sizeof(*xhdr) + size); + dbg("wrote %u bytes\n", (int)sizeof(*xhdr) + size); + /* Handshake hash does not include record headers */ + sha256_hash_dbg(">> sha256:%s", &tls->handshake_sha256_ctx, buf, size); + return; + } + xwrite_encrypted(tls, size, RECORD_TYPE_HANDSHAKE); +} + static int xread_tls_block(tls_state_t *tls) { struct record_hdr *xhdr; @@ -668,7 +703,7 @@ static int xread_tls_block(tls_state_t *tls) sz = target - sizeof(*xhdr); /* Needs to be decrypted? */ - if (tls->min_encrypted_len_on_read) { + if (tls->min_encrypted_len_on_read > SHA256_OUTSIZE) { psCipherContext_t ctx; uint8_t *p = tls->inbuf + sizeof(*xhdr); int padding_len; @@ -698,6 +733,10 @@ static int xread_tls_block(tls_state_t *tls) /* Skip IV */ memmove(tls->inbuf + 5, tls->inbuf + 5 + AES_BLOCKSIZE, sz); } + } else { + /* if nonzero, then it's TLS_RSA_WITH_NULL_SHA256: drop MAC */ + /* else: no encryption yet on input, subtract zero = NOP */ + sz -= tls->min_encrypted_len_on_read; } /* RFC 5246 is not saying it explicitly, but sha256 hash @@ -943,39 +982,24 @@ static int xread_tls_handshake_block(tls_state_t *tls, int min_len) return len; } -static void fill_handshake_record_hdr(struct record_hdr *xhdr, unsigned len) +static ALWAYS_INLINE void fill_handshake_record_hdr(void *buf, unsigned type, unsigned len) { struct handshake_hdr { - struct record_hdr xhdr; uint8_t type; uint8_t len24_hi, len24_mid, len24_lo; - } *h = (void*)xhdr; - - h->xhdr.type = RECORD_TYPE_HANDSHAKE; - h->xhdr.proto_maj = TLS_MAJ; - h->xhdr.proto_min = TLS_MIN; - len -= 5; - h->xhdr.len16_hi = len >> 8; - // can be optimized to do one store instead of four: - // uint32_t v = htonl(0x100*(RECORD_TYPE_HANDSHAKE + 0x100*(TLS_MAJ + 0x100*(TLS_MIN)))) - // | ((len >> 8) << 24); // little-endian specific, don't use in this form - // *(uint32_t *)xhdr = v; - - h->xhdr.len16_lo = len & 0xff; + } *h = buf; len -= 4; + h->type = type; h->len24_hi = len >> 16; h->len24_mid = len >> 8; h->len24_lo = len & 0xff; - - memset(h + 1, 0, len); } //TODO: implement RFC 5746 (Renegotiation Indication Extension) - some servers will refuse to work with us otherwise static void send_client_hello(tls_state_t *tls) { struct client_hello { - struct record_hdr xhdr; uint8_t type; uint8_t len24_hi, len24_mid, len24_lo; uint8_t proto_maj, proto_min; @@ -987,25 +1011,25 @@ static void send_client_hello(tls_state_t *tls) uint8_t comprtypes_len; uint8_t comprtypes[1]; /* actually variable */ }; - struct client_hello record; - - fill_handshake_record_hdr(&record.xhdr, sizeof(record)); - record.type = HANDSHAKE_CLIENT_HELLO; - record.proto_maj = TLS_MAJ; /* the "requested" version of the protocol, */ - record.proto_min = TLS_MIN; /* can be higher than one in record headers */ - tls_get_random(record.rand32, sizeof(record.rand32)); - /* record.session_id_len = 0; - already is */ - /* record.cipherid_len16_hi = 0; */ - record.cipherid_len16_lo = 2 * 1; - record.cipherid[0] = CIPHER_ID >> 8; - record.cipherid[1] = CIPHER_ID & 0xff; - record.comprtypes_len = 1; - /* record.comprtypes[0] = 0; */ + struct client_hello *record = tls_get_outbuf(tls, sizeof(*record)); + + fill_handshake_record_hdr(record, HANDSHAKE_CLIENT_HELLO, sizeof(*record)); + record->proto_maj = TLS_MAJ; /* the "requested" version of the protocol, */ + record->proto_min = TLS_MIN; /* can be higher than one in record headers */ + tls_get_random(record->rand32, sizeof(record->rand32)); +memset(record->rand32, 0x11, sizeof(record->rand32)); + memcpy(tls->client_and_server_rand32, record->rand32, sizeof(record->rand32)); + record->session_id_len = 0; + record->cipherid_len16_hi = 0; + record->cipherid_len16_lo = 2 * 1; + record->cipherid[0] = CIPHER_ID >> 8; + record->cipherid[1] = CIPHER_ID & 0xff; + record->comprtypes_len = 1; + record->comprtypes[0] = 0; //dbg (make it repeatable): memset(record.rand32, 0x11, sizeof(record.rand32)); dbg(">> CLIENT_HELLO\n"); - xwrite_and_hash(tls, &record, sizeof(record)); - memcpy(tls->client_and_server_rand32, record.rand32, sizeof(record.rand32)); + xwrite_and_update_handshake_hash(tls, sizeof(*record)); } static void get_server_hello(tls_state_t *tls) @@ -1099,21 +1123,19 @@ static void get_server_cert(tls_state_t *tls) static void send_client_key_exchange(tls_state_t *tls) { struct client_key_exchange { - struct record_hdr xhdr; uint8_t type; uint8_t len24_hi, len24_mid, len24_lo; /* keylen16 exists for RSA (in TLS, not in SSL), but not for some other key types */ uint8_t keylen16_hi, keylen16_lo; uint8_t key[4 * 1024]; // size?? }; - struct client_key_exchange record; +//FIXME: better size estimate + struct client_key_exchange *record = tls_get_outbuf(tls, sizeof(*record)); uint8_t rsa_premaster[SSL_HS_RSA_PREMASTER_SIZE]; int len; - fill_handshake_record_hdr(&record.xhdr, sizeof(record) - sizeof(record.key)); - record.type = HANDSHAKE_CLIENT_KEY_EXCHANGE; - tls_get_random(rsa_premaster, sizeof(rsa_premaster)); +memset(rsa_premaster, 0x44, sizeof(rsa_premaster)); // RFC 5246 // "Note: The version number in the PreMasterSecret is the version // offered by the client in the ClientHello.client_version, not the @@ -1123,22 +1145,20 @@ static void send_client_key_exchange(tls_state_t *tls) len = psRsaEncryptPub(/*pool:*/ NULL, /* psRsaKey_t* */ &tls->server_rsa_pub_key, rsa_premaster, /*inlen:*/ sizeof(rsa_premaster), - record.key, sizeof(record.key), + record->key, sizeof(record->key), data_param_ignored ); - /* length fields need fixing */ - record.keylen16_hi = len >> 8; - record.keylen16_lo = len & 0xff; + record->keylen16_hi = len >> 8; + record->keylen16_lo = len & 0xff; len += 2; - /* record.len24_hi = 0; - already is */ - record.len24_mid = len >> 8; - record.len24_lo = len & 0xff; + record->type = HANDSHAKE_CLIENT_KEY_EXCHANGE; + record->len24_hi = 0; + record->len24_mid = len >> 8; + record->len24_lo = len & 0xff; len += 4; - record.xhdr.len16_hi = len >> 8; - record.xhdr.len16_lo = len & 0xff; dbg(">> CLIENT_KEY_EXCHANGE\n"); - xwrite_and_hash(tls, &record, sizeof(record.xhdr) + len); + xwrite_and_update_handshake_hash(tls, len); // RFC 5246 // For all key exchange methods, the same algorithm is used to convert @@ -1224,7 +1244,6 @@ static const uint8_t rec_CHANGE_CIPHER_SPEC[] = { static void send_change_cipher_spec(tls_state_t *tls) { - /* Not "xwrite_and_hash": this is not a handshake message */ dbg(">> CHANGE_CIPHER_SPEC\n"); xwrite(tls->fd, rec_CHANGE_CIPHER_SPEC, sizeof(rec_CHANGE_CIPHER_SPEC)); } @@ -1269,19 +1288,17 @@ static void send_change_cipher_spec(tls_state_t *tls) static void send_client_finished(tls_state_t *tls) { struct finished { - struct record_hdr xhdr; uint8_t type; uint8_t len24_hi, len24_mid, len24_lo; uint8_t prf_result[12]; }; - struct finished record; + struct finished *record = tls_get_outbuf(tls, sizeof(*record)); uint8_t handshake_hash[SHA256_OUTSIZE]; - fill_handshake_record_hdr(&record.xhdr, sizeof(record)); - record.type = HANDSHAKE_FINISHED; + fill_handshake_record_hdr(record, HANDSHAKE_FINISHED, sizeof(*record)); sha256_peek(&tls->handshake_sha256_ctx, handshake_hash); - prf_hmac_sha256(record.prf_result, sizeof(record.prf_result), + prf_hmac_sha256(record->prf_result, sizeof(record->prf_result), tls->master_secret, sizeof(tls->master_secret), "client finished", handshake_hash, sizeof(handshake_hash) @@ -1289,13 +1306,10 @@ static void send_client_finished(tls_state_t *tls) dump_hex("from secret: %s\n", tls->master_secret, sizeof(tls->master_secret)); dump_hex("from labelSeed: %s", "client finished", sizeof("client finished")-1); dump_hex("%s\n", handshake_hash, sizeof(handshake_hash)); - dump_hex("=> digest: %s\n", record.prf_result, sizeof(record.prf_result)); - -//(1) TODO: well, this should be encrypted on send, really. -//(2) do we really need to also hash it? + dump_hex("=> digest: %s\n", record->prf_result, sizeof(record->prf_result)); dbg(">> FINISHED\n"); - xwrite_and_hash(tls, &record, sizeof(record)); + xwrite_encrypted(tls, sizeof(*record), RECORD_TYPE_HANDSHAKE); } static void tls_handshake(tls_state_t *tls) @@ -1376,8 +1390,11 @@ static void tls_handshake(tls_state_t *tls) if (len != 1 || memcmp(tls->inbuf, rec_CHANGE_CIPHER_SPEC, 6) != 0) tls_error_die(tls); dbg("<< CHANGE_CIPHER_SPEC\n"); - /* all incoming packets now should be encrypted and have IV + MAC + padding */ - tls->min_encrypted_len_on_read = AES_BLOCKSIZE + SHA256_OUTSIZE + AES_BLOCKSIZE; + if (CIPHER_ID == TLS_RSA_WITH_NULL_SHA256) + tls->min_encrypted_len_on_read = SHA256_OUTSIZE; + else + /* all incoming packets now should be encrypted and have IV + MAC + padding */ + tls->min_encrypted_len_on_read = AES_BLOCKSIZE + SHA256_OUTSIZE + AES_BLOCKSIZE; /* Get (encrypted) FINISHED from the server */ len = xread_tls_block(tls); @@ -1387,6 +1404,12 @@ static void tls_handshake(tls_state_t *tls) /* application data can be sent/received */ } +static void tls_xwrite(tls_state_t *tls, int len) +{ + dbg(">> DATA\n"); + xwrite_encrypted(tls, len, RECORD_TYPE_APPLICATION_DATA); +} + // To run a test server using openssl: // openssl req -x509 -newkey rsa:$((4096/4*3)) -keyout key.pem -out server.pem -nodes -days 99999 -subj '/CN=localhost' // openssl s_server -key key.pem -cert server.pem -debug -tls1_2 -no_tls1 -no_tls1_1 @@ -1400,8 +1423,8 @@ int tls_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int tls_main(int argc UNUSED_PARAM, char **argv) { tls_state_t *tls; - len_and_sockaddr *lsa; - int fd; + fd_set readfds, testfds; + int cfd; // INIT_G(); // getopt32(argv, "myopts") @@ -1409,13 +1432,48 @@ int tls_main(int argc UNUSED_PARAM, char **argv) if (!argv[1]) bb_show_usage(); - lsa = xhost2sockaddr(argv[1], 443); - fd = xconnect_stream(lsa); + cfd = create_and_connect_stream_or_die(argv[1], 443); tls = new_tls_state(); - tls->fd = fd; + tls->fd = cfd; tls_handshake(tls); + /* Select loop copying stdin to cfd, and cfd to stdout */ + FD_ZERO(&readfds); + FD_SET(cfd, &readfds); + FD_SET(STDIN_FILENO, &readfds); + +#define iobuf bb_common_bufsiz1 + setup_common_bufsiz(); + for (;;) { + int nread; + + testfds = readfds; + + if (select(cfd + 1, &testfds, NULL, NULL, NULL) < 0) + bb_perror_msg_and_die("select"); + + if (FD_ISSET(STDIN_FILENO, &testfds)) { + void *buf = tls_get_outbuf(tls, COMMON_BUFSIZE); + nread = safe_read(STDIN_FILENO, buf, COMMON_BUFSIZE); + if (nread < 1) { +//&& errno != EAGAIN + /* Close outgoing half-connection so they get EOF, + * but leave incoming alone so we can see response */ +// shutdown(cfd, SHUT_WR); + FD_CLR(STDIN_FILENO, &readfds); + } + tls_xwrite(tls, nread); + } + if (FD_ISSET(cfd, &testfds)) { + nread = xread_tls_block(tls); + if (nread < 1) +//if eof, just close stdout, but not exit! + return EXIT_SUCCESS; + xwrite(STDOUT_FILENO, tls->inbuf + 5, nread); + } + } + return EXIT_SUCCESS; } /* Unencryped SHA256 example: -- 2.25.1