[tls] Support fragmentation of transmitted records

Large transmitted records may arise if we have long client certificate
chains or if a client sends a large block of data (such as a large
HTTP POST payload).  Fragment records as needed to comply with the
value that we advertise via the max_fragment_length extension.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
pull/448/merge
Michael Brown 2025-03-31 16:36:33 +01:00
parent f115cfcf99
commit a289b4b8c2
2 changed files with 135 additions and 73 deletions

View File

@ -465,6 +465,17 @@ struct tls_connection {
struct tls_server server;
};
/** Advertised maximum fragment length */
#define TLS_MAX_FRAGMENT_LENGTH_VALUE TLS_MAX_FRAGMENT_LENGTH_4096
/** TX maximum fragment length
*
* TLS requires us to limit our transmitted records to the maximum
* fragment length that we attempt to negotiate, even if the server
* does not respect this choice.
*/
#define TLS_TX_BUFSIZE 4096
/** RX I/O buffer size
*
* The maximum fragment length extension is optional, and many common

View File

@ -1256,7 +1256,7 @@ static int tls_client_hello ( struct tls_connection *tls,
max_fragment_length_ext->type = htons ( TLS_MAX_FRAGMENT_LENGTH );
max_fragment_length_ext->len
= htons ( sizeof ( max_fragment_length_ext->data ) );
max_fragment_length_ext->data.max = TLS_MAX_FRAGMENT_LENGTH_4096;
max_fragment_length_ext->data.max = TLS_MAX_FRAGMENT_LENGTH_VALUE;
/* Construct supported signature algorithms extension */
signature_algorithms_ext = &extensions->signature_algorithms;
@ -2923,7 +2923,34 @@ static void tls_hmac_list ( struct tls_cipherspec *cipherspec,
}
/**
* Allocate I/O buffer for transmitted record
* Calculate maximum additional length required for transmitted record(s)
*
* @v tls TLS connection
* @v len I/O buffer payload length
* @ret reserve Maximum additional length to reserve
*/
static size_t tls_iob_reserved ( struct tls_connection *tls, size_t len ) {
struct tls_cipherspec *cipherspec = &tls->tx.cipherspec.active;
struct tls_cipher_suite *suite = cipherspec->suite;
struct cipher_algorithm *cipher = suite->cipher;
struct tls_header *tlshdr;
unsigned int count;
size_t each;
/* Calculate number of records (allowing for zero-length records) */
count = ( len ? ( ( len + TLS_TX_BUFSIZE - 1 ) / TLS_TX_BUFSIZE ) : 1 );
/* Calculate maximum additional length per record */
each = ( sizeof ( *tlshdr ) + suite->record_iv_len + suite->mac_len +
( is_block_cipher ( cipher ) ? cipher->blocksize : 0 ) +
cipher->authsize );
/* Calculate maximum total additional length */
return ( count * each );
}
/**
* Allocate I/O buffer for transmitted record(s)
*
* @v tls TLS connection
* @v len I/O buffer payload length
@ -2931,41 +2958,25 @@ static void tls_hmac_list ( struct tls_cipherspec *cipherspec,
*/
static struct io_buffer * tls_alloc_iob ( struct tls_connection *tls,
size_t len ) {
struct tls_cipherspec *cipherspec = &tls->tx.cipherspec.active;
struct tls_cipher_suite *suite = cipherspec->suite;
struct cipher_algorithm *cipher = suite->cipher;
struct tls_header *tlshdr;
struct io_buffer *iobuf;
size_t pre_len;
size_t padded_len;
size_t post_len;
size_t reserve;
/* Calculate length of padded data */
padded_len = ( len + suite->mac_len );
if ( is_block_cipher ( cipher ) ) {
padded_len = ( ( padded_len + 1 + cipher->blocksize - 1 ) &
~( cipher->blocksize - 1 ) );
assert ( padded_len > ( len + suite->mac_len ) );
}
/* Calculate lengths before and after padded data */
pre_len = ( sizeof ( *tlshdr ) + suite->record_iv_len );
post_len = cipher->authsize;
/* Calculate maximum additional length to reserve */
reserve = tls_iob_reserved ( tls, len );
/* Allocate I/O buffer */
iobuf = xfer_alloc_iob ( &tls->cipherstream,
( pre_len + padded_len + post_len ) );
iobuf = xfer_alloc_iob ( &tls->cipherstream, ( reserve + len ) );
if ( ! iobuf )
return NULL;
/* Reserve space */
iob_reserve ( iobuf, pre_len );
iob_reserve ( iobuf, reserve );
return iobuf;
}
/**
* Send plaintext record
* Send plaintext record(s)
*
* @v tls TLS connection
* @v type Record type
@ -2980,71 +2991,114 @@ static int tls_send_record ( struct tls_connection *tls, unsigned int type,
struct digest_algorithm *digest = suite->digest;
struct {
uint8_t fixed[suite->fixed_iv_len];
uint8_t record[suite->record_iv_len];
uint8_t rec[suite->record_iv_len];
} __attribute__ (( packed )) iv;
struct tls_auth_header authhdr;
struct tls_header *tlshdr;
uint8_t mac[digest->digestsize];
const void *plaintext;
const void *encrypt;
void *ciphertext;
size_t record_len;
size_t encrypt_len;
size_t pad_len;
size_t len;
int rc;
/* Record plaintext pointer and length */
plaintext = iobuf->data;
len = iob_len ( iobuf );
/* Add to handshake digest if applicable */
if ( type == TLS_TYPE_HANDSHAKE )
tls_add_handshake ( tls, iobuf->data, iob_len ( iobuf ) );
tls_add_handshake ( tls, plaintext, len );
/* Construct initialisation vector */
/* Start constructing ciphertext at start of reserved space */
iob_push ( iobuf, tls_iob_reserved ( tls, len ) );
iob_unput ( iobuf, iob_len ( iobuf ) );
/* Construct records */
do {
/* Limit length of this record (may be zero) */
record_len = len;
if ( record_len > TLS_TX_BUFSIZE )
record_len = TLS_TX_BUFSIZE;
/* Construct and set initialisation vector */
memcpy ( iv.fixed, cipherspec->fixed_iv, sizeof ( iv.fixed ) );
if ( ( rc = tls_generate_random ( tls, iv.record,
sizeof ( iv.record ) ) ) != 0 ) {
if ( ( rc = tls_generate_random ( tls, iv.rec,
sizeof ( iv.rec ) ) ) != 0 ) {
goto err_random;
}
cipher_setiv ( cipher, cipherspec->cipher_ctx, &iv,
sizeof ( iv ) );
/* Construct authentication data */
/* Construct and process authentication data */
authhdr.seq = cpu_to_be64 ( tls->tx.seq );
authhdr.header.type = type;
authhdr.header.version = htons ( tls->version );
authhdr.header.length = htons ( iob_len ( iobuf ) );
/* Append MAC, if applicable */
authhdr.header.length = htons ( record_len );
if ( suite->mac_len ) {
tls_hmac ( cipherspec, &authhdr, iobuf->data,
iob_len ( iobuf ), mac );
memcpy ( iob_put ( iobuf, suite->mac_len ), mac,
suite->mac_len );
tls_hmac ( cipherspec, &authhdr, plaintext, record_len,
mac );
}
if ( is_auth_cipher ( cipher ) ) {
cipher_encrypt ( cipher, cipherspec->cipher_ctx,
&authhdr, NULL, sizeof ( authhdr ) );
}
/* Append padding, if applicable */
/* Calculate encryption length */
encrypt_len = ( record_len + suite->mac_len );
if ( is_block_cipher ( cipher ) ) {
pad_len = ( ( ( cipher->blocksize - 1 ) &
-( iob_len ( iobuf ) + 1 ) ) + 1 );
-( encrypt_len + 1 ) ) + 1 );
} else {
pad_len = 0;
}
encrypt_len += pad_len;
/* Add record header */
tlshdr = iob_put ( iobuf, sizeof ( *tlshdr ) );
tlshdr->type = type;
tlshdr->version = htons ( tls->version );
tlshdr->length = htons ( sizeof ( iv.rec ) + encrypt_len +
cipher->authsize );
/* Add record initialisation vector, if applicable */
memcpy ( iob_put ( iobuf, sizeof ( iv.rec ) ), iv.rec,
sizeof ( iv.rec ) );
/* Copy plaintext data if necessary */
ciphertext = iob_put ( iobuf, record_len );
assert ( ciphertext <= plaintext );
if ( encrypt_len > record_len ) {
memmove ( ciphertext, plaintext, record_len );
encrypt = ciphertext;
} else {
encrypt = plaintext;
}
/* Add MAC, if applicable */
memcpy ( iob_put ( iobuf, suite->mac_len ), mac,
suite->mac_len );
/* Add padding, if applicable */
memset ( iob_put ( iobuf, pad_len ), ( pad_len - 1 ), pad_len );
assert ( ! ( iob_len ( iobuf ) & ( cipher->blocksize - 1 ) ) );
}
/* Encrypt data and append authentication tag */
DBGC2 ( tls, "Sending plaintext data:\n" );
DBGC2_HDA ( tls, 0, iobuf->data, iob_len ( iobuf ) );
/* Set initialisation vector */
cipher_setiv ( cipher, cipherspec->cipher_ctx, &iv, sizeof ( iv ) );
/* Process authentication data, if applicable */
if ( is_auth_cipher ( cipher ) ) {
cipher_encrypt ( cipher, cipherspec->cipher_ctx, &authhdr,
NULL, sizeof ( authhdr ) );
}
/* Encrypt data to be transmitted and append authentication tag */
cipher_encrypt ( cipher, cipherspec->cipher_ctx, iobuf->data,
iobuf->data, iob_len ( iobuf ) );
DBGC2_HDA ( tls, 0, encrypt, encrypt_len );
cipher_encrypt ( cipher, cipherspec->cipher_ctx, encrypt,
ciphertext, encrypt_len );
cipher_auth ( cipher, cipherspec->cipher_ctx,
iob_put ( iobuf, cipher->authsize ) );
/* Prepend record header and initialisation vector */
memcpy ( iob_push ( iobuf, sizeof ( iv.record ) ), iv.record,
sizeof ( iv.record ) );
tlshdr = iob_push ( iobuf, sizeof ( *tlshdr ) );
tlshdr->type = type;
tlshdr->version = htons ( tls->version );
tlshdr->length = htons ( iob_len ( iobuf ) - sizeof ( *tlshdr ) );
/* Move to next record */
tls->tx.seq += 1;
plaintext += record_len;
len -= record_len;
} while ( len );
/* Send ciphertext */
if ( ( rc = xfer_deliver_iob ( &tls->cipherstream,
@ -3054,9 +3108,6 @@ static int tls_send_record ( struct tls_connection *tls, unsigned int type,
goto err_deliver;
}
/* Update TX state machine to next record */
tls->tx.seq += 1;
assert ( iobuf == NULL );
return 0;