diff options
author | Michael Brown <mcb30@ipxe.org> | 2022-11-07 23:42:02 +0000 |
---|---|---|
committer | Michael Brown <mcb30@ipxe.org> | 2022-11-08 15:14:04 +0000 |
commit | 634a86093af9a6d134be8662f25616f4edfec683 (patch) | |
tree | 3f14462272fe936ce39ff5c506e653bae1b6f599 | |
parent | c453b4c284dbedb5de0663f6b30878b425a7a3e8 (diff) | |
download | ipxe-634a86093af9a6d134be8662f25616f4edfec683.zip ipxe-634a86093af9a6d134be8662f25616f4edfec683.tar.gz ipxe-634a86093af9a6d134be8662f25616f4edfec683.tar.bz2 |
[tls] Allow for arbitrary-length initialisation vectors
Restructure the encryption and decryption operations to allow for the
use of ciphers where the initialisation vector is constructed by
concatenating the fixed IV (derived as part of key expansion) with a
record IV (prepended to the ciphertext).
Signed-off-by: Michael Brown <mcb30@ipxe.org>
-rw-r--r-- | src/include/ipxe/crypto.h | 4 | ||||
-rw-r--r-- | src/net/tls.c | 330 |
2 files changed, 148 insertions, 186 deletions
diff --git a/src/include/ipxe/crypto.h b/src/include/ipxe/crypto.h index ba09c94..a15d5eb 100644 --- a/src/include/ipxe/crypto.h +++ b/src/include/ipxe/crypto.h @@ -249,6 +249,10 @@ static inline int is_stream_cipher ( struct cipher_algorithm *cipher ) { return ( cipher->blocksize == 1 ); } +static inline int is_block_cipher ( struct cipher_algorithm *cipher ) { + return ( cipher->blocksize > 1 ); +} + static inline int is_auth_cipher ( struct cipher_algorithm *cipher ) { return cipher->authsize; } diff --git a/src/net/tls.c b/src/net/tls.c index 06d01ae..fdaa219 100644 --- a/src/net/tls.c +++ b/src/net/tls.c @@ -86,14 +86,10 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define EINFO_EINVAL_HANDSHAKE \ __einfo_uniqify ( EINFO_EINVAL, 0x08, \ "Invalid Handshake record" ) -#define EINVAL_STREAM __einfo_error ( EINFO_EINVAL_STREAM ) -#define EINFO_EINVAL_STREAM \ - __einfo_uniqify ( EINFO_EINVAL, 0x09, \ - "Invalid stream-ciphered record" ) -#define EINVAL_BLOCK __einfo_error ( EINFO_EINVAL_BLOCK ) -#define EINFO_EINVAL_BLOCK \ +#define EINVAL_IV __einfo_error ( EINFO_EINVAL_IV ) +#define EINFO_EINVAL_IV \ __einfo_uniqify ( EINFO_EINVAL, 0x0a, \ - "Invalid block-ciphered record" ) + "Invalid initialisation vector" ) #define EINVAL_PADDING __einfo_error ( EINFO_EINVAL_PADDING ) #define EINFO_EINVAL_PADDING \ __einfo_uniqify ( EINFO_EINVAL, 0x0b, \ @@ -2581,86 +2577,26 @@ static void tls_hmac ( struct tls_cipherspec *cipherspec, } /** - * Allocate and assemble stream-ciphered record from data and MAC portions + * Calculate HMAC over list of I/O buffers * - * @v tls TLS connection - * @ret data Data - * @ret len Length of data - * @ret digest MAC digest - * @ret plaintext_len Length of plaintext record - * @ret plaintext Allocated plaintext record - */ -static void * __malloc -tls_assemble_stream ( struct tls_connection *tls, const void *data, size_t len, - void *digest, size_t *plaintext_len ) { - size_t mac_len = tls->tx_cipherspec.suite->mac_len; - void *plaintext; - void *content; - void *mac; - - /* Calculate stream-ciphered struct length */ - *plaintext_len = ( len + mac_len ); - - /* Allocate stream-ciphered struct */ - plaintext = malloc ( *plaintext_len ); - if ( ! plaintext ) - return NULL; - content = plaintext; - mac = ( content + len ); - - /* Fill in stream-ciphered struct */ - memcpy ( content, data, len ); - memcpy ( mac, digest, mac_len ); - - return plaintext; -} - -/** - * Allocate and assemble block-ciphered record from data and MAC portions - * - * @v tls TLS connection - * @ret data Data - * @ret len Length of data - * @ret digest MAC digest - * @ret plaintext_len Length of plaintext record - * @ret plaintext Allocated plaintext record - */ -static void * tls_assemble_block ( struct tls_connection *tls, - const void *data, size_t len, - void *digest, size_t *plaintext_len ) { - size_t blocksize = tls->tx_cipherspec.suite->cipher->blocksize; - size_t mac_len = tls->tx_cipherspec.suite->mac_len; - size_t iv_len = blocksize; - size_t padding_len; - void *plaintext; - void *iv; - void *content; - void *mac; - void *padding; - - /* Sanity check */ - assert ( iv_len == tls->tx_cipherspec.suite->record_iv_len ); - - /* Calculate block-ciphered struct length */ - padding_len = ( ( blocksize - 1 ) & -( iv_len + len + mac_len + 1 ) ); - *plaintext_len = ( iv_len + len + mac_len + padding_len + 1 ); - - /* Allocate block-ciphered struct */ - plaintext = malloc ( *plaintext_len ); - if ( ! plaintext ) - return NULL; - iv = plaintext; - content = ( iv + iv_len ); - mac = ( content + len ); - padding = ( mac + mac_len ); - - /* Fill in block-ciphered struct */ - tls_generate_random ( tls, iv, iv_len ); - memcpy ( content, data, len ); - memcpy ( mac, digest, mac_len ); - memset ( padding, padding_len, ( padding_len + 1 ) ); + * @v cipherspec Cipher specification + * @v authhdr Authentication header + * @v list List of I/O buffers + * @v mac HMAC to fill in + */ +static void tls_hmac_list ( struct tls_cipherspec *cipherspec, + struct tls_auth_header *authhdr, + struct list_head *list, void *hmac ) { + struct digest_algorithm *digest = cipherspec->suite->digest; + uint8_t ctx[ hmac_ctxsize ( digest ) ]; + struct io_buffer *iobuf; - return plaintext; + tls_hmac_init ( cipherspec, ctx, authhdr ); + list_for_each_entry ( iobuf, list, list ) { + tls_hmac_update ( cipherspec, ctx, iobuf->data, + iob_len ( iobuf ) ); + } + tls_hmac_final ( cipherspec, ctx, hmac ); } /** @@ -2678,32 +2614,43 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, struct tls_cipher_suite *suite = cipherspec->suite; struct cipher_algorithm *cipher = suite->cipher; struct digest_algorithm *digest = suite->digest; + struct { + uint8_t fixed[suite->fixed_iv_len]; + uint8_t record[suite->record_iv_len]; + } __attribute__ (( packed )) iv; struct tls_auth_header authhdr; struct tls_header *tlshdr; void *plaintext = NULL; - size_t plaintext_len; + size_t plaintext_len = len; struct io_buffer *ciphertext = NULL; size_t ciphertext_len; + size_t padding_len; uint8_t mac[digest->digestsize]; + void *tmp; int rc; - /* Construct header */ + /* Construct initialisation vector */ + memcpy ( iv.fixed, cipherspec->fixed_iv, sizeof ( iv.fixed ) ); + tls_generate_random ( tls, iv.record, sizeof ( iv.record ) ); + + /* Construct authentication data */ authhdr.seq = cpu_to_be64 ( tls->tx_seq ); authhdr.header.type = type; authhdr.header.version = htons ( tls->version ); authhdr.header.length = htons ( len ); - /* Calculate MAC */ - tls_hmac ( cipherspec, &authhdr, data, len, mac ); - - /* Allocate and assemble plaintext struct */ - if ( is_stream_cipher ( cipher ) ) { - plaintext = tls_assemble_stream ( tls, data, len, mac, - &plaintext_len ); + /* Calculate padding length */ + plaintext_len += suite->mac_len; + if ( is_block_cipher ( cipher ) ) { + padding_len = ( ( ( cipher->blocksize - 1 ) & + -( plaintext_len + 1 ) ) + 1 ); } else { - plaintext = tls_assemble_block ( tls, data, len, mac, - &plaintext_len ); + padding_len = 0; } + plaintext_len += padding_len; + + /* Allocate plaintext */ + plaintext = malloc ( plaintext_len ); if ( ! plaintext ) { DBGC ( tls, "TLS %p could not allocate %zd bytes for " "plaintext\n", tls, plaintext_len ); @@ -2711,11 +2658,26 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, goto done; } + /* Assemble plaintext */ + tmp = plaintext; + memcpy ( tmp, data, len ); + tmp += len; + if ( suite->mac_len ) + tls_hmac ( cipherspec, &authhdr, data, len, mac ); + memcpy ( tmp, mac, suite->mac_len ); + tmp += suite->mac_len; + memset ( tmp, ( padding_len - 1 ), padding_len ); + tmp += padding_len; + assert ( tmp == ( plaintext + plaintext_len ) ); DBGC2 ( tls, "Sending plaintext data:\n" ); DBGC2_HD ( tls, plaintext, plaintext_len ); + /* Set initialisation vector */ + cipher_setiv ( cipher, cipherspec->cipher_ctx, &iv, sizeof ( iv ) ); + /* Allocate ciphertext */ - ciphertext_len = ( sizeof ( *tlshdr ) + plaintext_len ); + ciphertext_len = ( sizeof ( *tlshdr ) + sizeof ( iv.record ) + + plaintext_len ); ciphertext = xfer_alloc_iob ( &tls->cipherstream, ciphertext_len ); if ( ! ciphertext ) { DBGC ( tls, "TLS %p could not allocate %zd bytes for " @@ -2728,9 +2690,12 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, tlshdr = iob_put ( ciphertext, sizeof ( *tlshdr ) ); tlshdr->type = type; tlshdr->version = htons ( tls->version ); - tlshdr->length = htons ( plaintext_len ); + tlshdr->length = htons ( ciphertext_len - sizeof ( *tlshdr ) ); + memcpy ( iob_put ( ciphertext, sizeof ( iv.record ) ), iv.record, + sizeof ( iv.record ) ); cipher_encrypt ( cipher, cipherspec->cipher_ctx, plaintext, iob_put ( ciphertext, plaintext_len ), plaintext_len ); + assert ( iob_len ( ciphertext ) == ciphertext_len ); /* Free plaintext as soon as possible to conserve memory */ free ( plaintext ); @@ -2754,89 +2719,38 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, } /** - * Split stream-ciphered record into data and MAC portions - * - * @v tls TLS connection - * @v rx_data List of received data buffers - * @v mac MAC to fill in - * @ret rc Return status code - */ -static int tls_split_stream ( struct tls_connection *tls, - struct list_head *rx_data, void **mac ) { - size_t mac_len = tls->rx_cipherspec.suite->mac_len; - struct io_buffer *iobuf; - - /* Extract MAC */ - iobuf = list_last_entry ( rx_data, struct io_buffer, list ); - assert ( iobuf != NULL ); - if ( iob_len ( iobuf ) < mac_len ) { - DBGC ( tls, "TLS %p received underlength MAC\n", tls ); - DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); - return -EINVAL_STREAM; - } - iob_unput ( iobuf, mac_len ); - *mac = iobuf->tail; - - return 0; -} - -/** - * Split block-ciphered record into data and MAC portions + * Verify block padding * * @v tls TLS connection - * @v rx_data List of received data buffers - * @v mac MAC to fill in + * @v iobuf Last received I/O buffer + * @ret len Padding length, or negative error * @ret rc Return status code */ -static int tls_split_block ( struct tls_connection *tls, - struct list_head *rx_data, void **mac ) { - size_t mac_len = tls->rx_cipherspec.suite->mac_len; - size_t iv_len = tls->rx_cipherspec.suite->cipher->blocksize; - struct io_buffer *iobuf; - uint8_t *padding_final; +static int tls_verify_padding ( struct tls_connection *tls, + struct io_buffer *iobuf ) { uint8_t *padding; - size_t padding_len; - - /* Sanity check */ - assert ( iv_len == tls->rx_cipherspec.suite->record_iv_len ); - - /* Extract initialisation vector */ - iobuf = list_first_entry ( rx_data, struct io_buffer, list ); - if ( iob_len ( iobuf ) < iv_len ) { - DBGC ( tls, "TLS %p received underlength IV\n", tls ); - DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); - return -EINVAL_BLOCK; - } - iob_pull ( iobuf, iv_len ); + unsigned int pad; + unsigned int i; + size_t len; /* Extract and verify padding */ - iobuf = list_last_entry ( rx_data, struct io_buffer, list ); - padding_final = ( iobuf->tail - 1 ); - padding_len = *padding_final; - if ( ( padding_len + 1 ) > iob_len ( iobuf ) ) { + padding = ( iobuf->tail - 1 ); + pad = *padding; + len = ( pad + 1 ); + if ( len > iob_len ( iobuf ) ) { DBGC ( tls, "TLS %p received underlength padding\n", tls ); DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); - return -EINVAL_BLOCK; + return -EINVAL_PADDING; } - iob_unput ( iobuf, ( padding_len + 1 ) ); - for ( padding = iobuf->tail ; padding < padding_final ; padding++ ) { - if ( *padding != padding_len ) { + for ( i = 0 ; i < pad ; i++ ) { + if ( *(--padding) != pad ) { DBGC ( tls, "TLS %p received bad padding\n", tls ); - DBGC_HD ( tls, padding, padding_len ); + DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); return -EINVAL_PADDING; } } - /* Extract MAC */ - if ( iob_len ( iobuf ) < mac_len ) { - DBGC ( tls, "TLS %p received underlength MAC\n", tls ); - DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); - return -EINVAL_BLOCK; - } - iob_unput ( iobuf, mac_len ); - *mac = iobuf->tail; - - return 0; + return len; } /** @@ -2854,47 +2768,91 @@ static int tls_new_ciphertext ( struct tls_connection *tls, struct tls_cipher_suite *suite = cipherspec->suite; struct cipher_algorithm *cipher = suite->cipher; struct digest_algorithm *digest = suite->digest; + size_t len = ntohs ( tlshdr->length ); + struct { + uint8_t fixed[suite->fixed_iv_len]; + uint8_t record[suite->record_iv_len]; + } __attribute__ (( packed )) iv; struct tls_auth_header authhdr; - uint8_t ctx[ hmac_ctxsize ( digest ) ]; uint8_t verify_mac[digest->digestsize]; + struct io_buffer *first; + struct io_buffer *last; struct io_buffer *iobuf; void *mac; - size_t len = 0; + size_t check_len; + int pad_len; int rc; + /* Locate first and last data buffers */ + assert ( ! list_empty ( rx_data ) ); + first = list_first_entry ( rx_data, struct io_buffer, list ); + last = list_last_entry ( rx_data, struct io_buffer, list ); + + /* Extract initialisation vector */ + if ( iob_len ( first ) < sizeof ( iv.record ) ) { + DBGC ( tls, "TLS %p received underlength IV\n", tls ); + DBGC_HD ( tls, first->data, iob_len ( first ) ); + return -EINVAL_IV; + } + memcpy ( iv.fixed, cipherspec->fixed_iv, sizeof ( iv.fixed ) ); + memcpy ( iv.record, first->data, sizeof ( iv.record ) ); + iob_pull ( first, sizeof ( iv.record ) ); + len -= sizeof ( iv.record ); + + /* Construct authentication data */ + authhdr.seq = cpu_to_be64 ( tls->rx_seq ); + authhdr.header.type = tlshdr->type; + authhdr.header.version = tlshdr->version; + authhdr.header.length = htons ( len ); + + /* Set initialisation vector */ + cipher_setiv ( cipher, cipherspec->cipher_ctx, &iv, sizeof ( iv ) ); + /* Decrypt the received data */ + check_len = 0; list_for_each_entry ( iobuf, &tls->rx_data, list ) { cipher_decrypt ( cipher, cipherspec->cipher_ctx, iobuf->data, iobuf->data, iob_len ( iobuf ) ); + check_len += iob_len ( iobuf ); } + assert ( check_len == len ); - /* Split record into content and MAC */ - if ( is_stream_cipher ( cipher ) ) { - if ( ( rc = tls_split_stream ( tls, rx_data, &mac ) ) != 0 ) - return rc; - } else { - if ( ( rc = tls_split_block ( tls, rx_data, &mac ) ) != 0 ) + /* Strip block padding, if applicable */ + if ( is_block_cipher ( cipher ) ) { + pad_len = tls_verify_padding ( tls, last ); + if ( pad_len < 0 ) { + rc = pad_len; return rc; + } + iob_unput ( last, pad_len ); + len -= pad_len; + } + + /* Extract decrypted MAC */ + if ( iob_len ( last ) < suite->mac_len ) { + DBGC ( tls, "TLS %p received underlength MAC\n", tls ); + DBGC_HD ( tls, last->data, iob_len ( last ) ); + return -EINVAL_MAC; } + iob_unput ( last, suite->mac_len ); + len -= suite->mac_len; + mac = last->tail; - /* Calculate total length */ + /* Dump received data */ DBGC2 ( tls, "Received plaintext data:\n" ); + check_len = 0; list_for_each_entry ( iobuf, rx_data, list ) { DBGC2_HD ( tls, iobuf->data, iob_len ( iobuf ) ); - len += iob_len ( iobuf ); + check_len += iob_len ( iobuf ); } + assert ( check_len == len ); - /* Verify MAC */ - authhdr.seq = cpu_to_be64 ( tls->rx_seq ); - authhdr.header.type = tlshdr->type; - authhdr.header.version = tlshdr->version; + /* Generate MAC */ authhdr.header.length = htons ( len ); - tls_hmac_init ( cipherspec, ctx, &authhdr ); - list_for_each_entry ( iobuf, rx_data, list ) { - tls_hmac_update ( cipherspec, ctx, iobuf->data, - iob_len ( iobuf ) ); - } - tls_hmac_final ( cipherspec, ctx, verify_mac ); + if ( suite->mac_len ) + tls_hmac_list ( cipherspec, &authhdr, rx_data, verify_mac ); + + /* Verify MAC */ if ( memcmp ( mac, verify_mac, suite->mac_len ) != 0 ) { DBGC ( tls, "TLS %p failed MAC verification\n", tls ); return -EINVAL_MAC; |