[tls] Encrypt data in place to reduce memory usage

Provide a custom xfer_alloc_iob() handler to ensure that transmit I/O
buffers contain sufficient headroom for the TLS record header and
record initialisation vector, and sufficient tailroom for the MAC,
block cipher padding, and authentication tag.  This allows us to use
in-place encryption for the actual data within the I/O buffer, which
essentially halves the amount of memory that needs to be allocated for
a TLS data transmission.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
pull/1437/head
Michael Brown 2025-03-31 00:15:27 +01:00
parent d92551a320
commit 7fe467a46d
1 changed files with 122 additions and 90 deletions

View File

@ -196,6 +196,10 @@ FILE_LICENCE ( GPL2_OR_LATER );
static LIST_HEAD ( tls_sessions ); static LIST_HEAD ( tls_sessions );
static void tls_tx_resume_all ( struct tls_session *session ); static void tls_tx_resume_all ( struct tls_session *session );
static struct io_buffer * tls_alloc_iob ( struct tls_connection *tls,
size_t len );
static int tls_send_record ( struct tls_connection *tls, unsigned int type,
struct io_buffer *iobuf );
static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type,
const void *data, size_t len ); const void *data, size_t len );
static void tls_clear_cipher ( struct tls_connection *tls, static void tls_clear_cipher ( struct tls_connection *tls,
@ -1126,9 +1130,6 @@ static void tls_restart ( struct tls_connection *tls ) {
static int tls_send_handshake ( struct tls_connection *tls, static int tls_send_handshake ( struct tls_connection *tls,
const void *data, size_t len ) { const void *data, size_t len ) {
/* Add to handshake digest */
tls_add_handshake ( tls, data, len );
/* Send record */ /* Send record */
return tls_send_plaintext ( tls, TLS_TYPE_HANDSHAKE, data, len ); return tls_send_plaintext ( tls, TLS_TYPE_HANDSHAKE, data, len );
} }
@ -1333,8 +1334,8 @@ static int tls_send_certificate ( struct tls_connection *tls ) {
} __attribute__ (( packed )) *certificates; } __attribute__ (( packed )) *certificates;
struct x509_link *link; struct x509_link *link;
struct x509_certificate *cert; struct x509_certificate *cert;
struct io_buffer *iobuf;
size_t len; size_t len;
int rc;
/* Calculate length of client certificates */ /* Calculate length of client certificates */
len = 0; len = 0;
@ -1348,33 +1349,28 @@ static int tls_send_certificate ( struct tls_connection *tls ) {
/* Allocate storage for Certificate record (which may be too /* Allocate storage for Certificate record (which may be too
* large for the stack). * large for the stack).
*/ */
certificates = zalloc ( sizeof ( *certificates ) + len ); iobuf = tls_alloc_iob ( tls, ( sizeof ( *certificates ) + len ) );
if ( ! certificates ) if ( ! iobuf )
return -ENOMEM_CERTIFICATE; return -ENOMEM_CERTIFICATE;
/* Populate record */ /* Populate record */
certificates = iob_put ( iobuf, sizeof ( *certificates ) );
certificates->type_length = certificates->type_length =
( cpu_to_le32 ( TLS_CERTIFICATE ) | ( cpu_to_le32 ( TLS_CERTIFICATE ) |
htonl ( sizeof ( *certificates ) + len - htonl ( sizeof ( *certificates ) + len -
sizeof ( certificates->type_length ) ) ); sizeof ( certificates->type_length ) ) );
tls_set_uint24 ( &certificates->length, len ); tls_set_uint24 ( &certificates->length, len );
certificate = &certificates->certificates[0];
list_for_each_entry ( link, &tls->client.chain->links, list ) { list_for_each_entry ( link, &tls->client.chain->links, list ) {
cert = link->cert; cert = link->cert;
certificate = iob_put ( iobuf, sizeof ( *certificate ) );
tls_set_uint24 ( &certificate->length, cert->raw.len ); tls_set_uint24 ( &certificate->length, cert->raw.len );
memcpy ( certificate->data, cert->raw.data, cert->raw.len ); memcpy ( iob_put ( iobuf, cert->raw.len ), cert->raw.data,
certificate = ( ( ( void * ) certificate->data ) + cert->raw.len );
cert->raw.len );
} }
/* Transmit record */ /* Transmit record */
rc = tls_send_handshake ( tls, certificates, return tls_send_record ( tls, TLS_TYPE_HANDSHAKE,
( sizeof ( *certificates ) + len ) ); iob_disown ( iobuf ) );
/* Free record */
free ( certificates );
return rc;
} }
/** /**
@ -2927,17 +2923,58 @@ static void tls_hmac_list ( struct tls_cipherspec *cipherspec,
tls_hmac_final ( cipherspec, ctx, hmac ); tls_hmac_final ( cipherspec, ctx, hmac );
} }
/**
* Allocate I/O buffer for transmitted record
*
* @v tls TLS connection
* @v len I/O buffer payload length
* @ret iobuf I/O buffer
*/
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;
/* 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;
/* Allocate I/O buffer */
iobuf = xfer_alloc_iob ( &tls->cipherstream,
( pre_len + padded_len + post_len ) );
if ( ! iobuf )
return NULL;
/* Reserve space */
iob_reserve ( iobuf, pre_len );
return iobuf;
}
/** /**
* Send plaintext record * Send plaintext record
* *
* @v tls TLS connection * @v tls TLS connection
* @v type Record type * @v type Record type
* @v data Plaintext record * @v iobuf I/O buffer
* @v len Length of plaintext record
* @ret rc Return status code * @ret rc Return status code
*/ */
static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, static int tls_send_record ( struct tls_connection *tls, unsigned int type,
const void *data, size_t len ) { struct io_buffer *iobuf ) {
struct tls_cipherspec *cipherspec = &tls->tx.cipherspec.active; struct tls_cipherspec *cipherspec = &tls->tx.cipherspec.active;
struct tls_cipher_suite *suite = cipherspec->suite; struct tls_cipher_suite *suite = cipherspec->suite;
struct cipher_algorithm *cipher = suite->cipher; struct cipher_algorithm *cipher = suite->cipher;
@ -2948,15 +2985,14 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type,
} __attribute__ (( packed )) iv; } __attribute__ (( packed )) iv;
struct tls_auth_header authhdr; struct tls_auth_header authhdr;
struct tls_header *tlshdr; struct tls_header *tlshdr;
void *plaintext;
size_t plaintext_len;
struct io_buffer *ciphertext;
size_t ciphertext_len;
size_t padding_len;
uint8_t mac[digest->digestsize]; uint8_t mac[digest->digestsize];
void *tmp; size_t pad_len;
int rc; int rc;
/* Add to handshake digest if applicable */
if ( type == TLS_TYPE_HANDSHAKE )
tls_add_handshake ( tls, iobuf->data, iob_len ( iobuf ) );
/* Construct initialisation vector */ /* Construct initialisation vector */
memcpy ( iv.fixed, cipherspec->fixed_iv, sizeof ( iv.fixed ) ); memcpy ( iv.fixed, cipherspec->fixed_iv, sizeof ( iv.fixed ) );
if ( ( rc = tls_generate_random ( tls, iv.record, if ( ( rc = tls_generate_random ( tls, iv.record,
@ -2968,40 +3004,25 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type,
authhdr.seq = cpu_to_be64 ( tls->tx.seq ); authhdr.seq = cpu_to_be64 ( tls->tx.seq );
authhdr.header.type = type; authhdr.header.type = type;
authhdr.header.version = htons ( tls->version ); authhdr.header.version = htons ( tls->version );
authhdr.header.length = htons ( len ); authhdr.header.length = htons ( iob_len ( iobuf ) );
/* Calculate padding length */ /* Append MAC, if applicable */
plaintext_len = ( len + suite->mac_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 );
}
/* Append padding, if applicable */
if ( is_block_cipher ( cipher ) ) { if ( is_block_cipher ( cipher ) ) {
padding_len = ( ( ( cipher->blocksize - 1 ) & pad_len = ( ( ( cipher->blocksize - 1 ) &
-( plaintext_len + 1 ) ) + 1 ); -( iob_len ( iobuf ) + 1 ) ) + 1 );
} else { memset ( iob_put ( iobuf, pad_len ), ( pad_len - 1 ), pad_len );
padding_len = 0; assert ( ! ( iob_len ( iobuf ) & ( cipher->blocksize - 1 ) ) );
} }
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 );
rc = -ENOMEM_TX_PLAINTEXT;
goto err_plaintext;
}
/* 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 ( tls, "Sending plaintext data:\n" );
DBGC2_HD ( tls, plaintext, plaintext_len ); DBGC2_HDA ( tls, 0, iobuf->data, iob_len ( iobuf ) );
/* Set initialisation vector */ /* Set initialisation vector */
cipher_setiv ( cipher, cipherspec->cipher_ctx, &iv, sizeof ( iv ) ); cipher_setiv ( cipher, cipherspec->cipher_ctx, &iv, sizeof ( iv ) );
@ -3012,37 +3033,23 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type,
NULL, sizeof ( authhdr ) ); NULL, sizeof ( authhdr ) );
} }
/* Allocate ciphertext */ /* Encrypt data to be transmitted and append authentication tag */
ciphertext_len = ( sizeof ( *tlshdr ) + sizeof ( iv.record ) + cipher_encrypt ( cipher, cipherspec->cipher_ctx, iobuf->data,
plaintext_len + cipher->authsize ); iobuf->data, iob_len ( iobuf ) );
ciphertext = xfer_alloc_iob ( &tls->cipherstream, ciphertext_len ); cipher_auth ( cipher, cipherspec->cipher_ctx,
if ( ! ciphertext ) { iob_put ( iobuf, cipher->authsize ) );
DBGC ( tls, "TLS %p could not allocate %zd bytes for "
"ciphertext\n", tls, ciphertext_len );
rc = -ENOMEM_TX_CIPHERTEXT;
goto err_ciphertext;
}
/* Assemble ciphertext */ /* Prepend record header and initialisation vector */
tlshdr = iob_put ( ciphertext, sizeof ( *tlshdr ) ); memcpy ( iob_push ( iobuf, sizeof ( iv.record ) ), iv.record,
sizeof ( iv.record ) );
tlshdr = iob_push ( iobuf, sizeof ( *tlshdr ) );
tlshdr->type = type; tlshdr->type = type;
tlshdr->version = htons ( tls->version ); tlshdr->version = htons ( tls->version );
tlshdr->length = htons ( ciphertext_len - sizeof ( *tlshdr ) ); tlshdr->length = htons ( iob_len ( iobuf ) - 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 );
cipher_auth ( cipher, cipherspec->cipher_ctx,
iob_put ( ciphertext, cipher->authsize ) );
assert ( iob_len ( ciphertext ) == ciphertext_len );
/* Free plaintext as soon as possible to conserve memory */
free ( plaintext );
plaintext = NULL;
/* Send ciphertext */ /* Send ciphertext */
if ( ( rc = xfer_deliver_iob ( &tls->cipherstream, if ( ( rc = xfer_deliver_iob ( &tls->cipherstream,
iob_disown ( ciphertext ) ) ) != 0 ) { iob_disown ( iobuf ) ) ) != 0 ) {
DBGC ( tls, "TLS %p could not deliver ciphertext: %s\n", DBGC ( tls, "TLS %p could not deliver ciphertext: %s\n",
tls, strerror ( rc ) ); tls, strerror ( rc ) );
goto err_deliver; goto err_deliver;
@ -3051,19 +3058,42 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type,
/* Update TX state machine to next record */ /* Update TX state machine to next record */
tls->tx.seq += 1; tls->tx.seq += 1;
assert ( plaintext == NULL ); assert ( iobuf == NULL );
assert ( ciphertext == NULL );
return 0; return 0;
err_deliver: err_deliver:
free_iob ( ciphertext );
err_ciphertext:
free ( plaintext );
err_plaintext:
err_random: err_random:
free_iob ( iobuf );
return rc; return rc;
} }
/**
* Send plaintext record
*
* @v tls TLS connection
* @v type Record type
* @v data Plaintext record
* @v len Length of plaintext record
* @ret rc Return status code
*/
static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type,
const void *data, size_t len ) {
struct io_buffer *iobuf;
int rc;
/* Allocate I/O buffer */
iobuf = tls_alloc_iob ( tls, len );
if ( ! iobuf )
return -ENOMEM_TX_PLAINTEXT;
memcpy ( iob_put ( iobuf, len ), data, len );
/* Transmit I/O buffer */
if ( ( rc = tls_send_record ( tls, type, iob_disown ( iobuf ) ) ) != 0 )
return rc;
return 0;
}
/** /**
* Verify block padding * Verify block padding
* *
@ -3281,8 +3311,9 @@ static int tls_plainstream_deliver ( struct tls_connection *tls,
goto done; goto done;
} }
if ( ( rc = tls_send_plaintext ( tls, TLS_TYPE_DATA, iobuf->data, /* Send data record */
iob_len ( iobuf ) ) ) != 0 ) if ( ( rc = tls_send_record ( tls, TLS_TYPE_DATA,
iob_disown ( iobuf ) ) ) != 0 )
goto done; goto done;
done: done:
@ -3310,6 +3341,7 @@ static int tls_progress ( struct tls_connection *tls,
/** TLS plaintext stream interface operations */ /** TLS plaintext stream interface operations */
static struct interface_operation tls_plainstream_ops[] = { static struct interface_operation tls_plainstream_ops[] = {
INTF_OP ( xfer_alloc_iob, struct tls_connection *, tls_alloc_iob ),
INTF_OP ( xfer_deliver, struct tls_connection *, INTF_OP ( xfer_deliver, struct tls_connection *,
tls_plainstream_deliver ), tls_plainstream_deliver ),
INTF_OP ( xfer_window, struct tls_connection *, INTF_OP ( xfer_window, struct tls_connection *,