TLS record protection: Add helper function for nonce derivation
The computation of the per-record nonce for AEAD record protection
varies with the AEAD algorithm and the TLS version in use.
This commit introduces a helper function for the nonce computation
to ease readability of the quite monolithic record encrytion routine.
Signed-off-by: Hanno Becker <hanno.becker@arm.com>
diff --git a/library/ssl_msg.c b/library/ssl_msg.c
index 57b39c7..420e539 100644
--- a/library/ssl_msg.c
+++ b/library/ssl_msg.c
@@ -536,6 +536,78 @@
}
#endif /* MBEDTLS_SSL_PROTO_SSL3 */
+#define SSL_RECORD_AEAD_NONCE_UNKNOWN 0u
+#define SSL_RECORD_AEAD_NONCE_CONCAT 1u
+#define SSL_RECORD_AEAD_NONCE_XOR 2u
+
+static int ssl_transform_get_nonce_mode( mbedtls_ssl_transform const *transform )
+{
+#if defined(MBEDTLS_CHACHAPOLY_C)
+ if( transform->ivlen == 12 && transform->fixed_ivlen == 12 )
+ {
+ return( SSL_RECORD_AEAD_NONCE_XOR );
+ }
+#endif /* MBEDTLS_CHACHAPOLY_C */
+
+#if defined(MBEDTLS_GCM_C) || defined(MBEDTLS_CCM_C)
+ if( transform->ivlen == 12 && transform->fixed_ivlen == 4 )
+ {
+ return( SSL_RECORD_AEAD_NONCE_CONCAT );
+ }
+#endif /* MBEDTLS_GCM_C || MBEDTLS_CCM_C */
+
+ return( SSL_RECORD_AEAD_NONCE_UNKNOWN );
+}
+
+/* Preconditions:
+ * - If mode == SSL_RECORD_AEAD_NONCE_CONCAT, then
+ * dst_nonce_len == fixed_iv_len + dynamic_iv_len
+ * - If mode == SSL_RECORD_AEAD_NONCE_XOR, then
+ * dst_nonce_len == fixed_iv_len &&
+ * dynamic_iv_len < dst_nonce
+ */
+static int ssl_build_record_nonce( unsigned char *dst_nonce,
+ size_t dst_nonce_len,
+ unsigned char const *fixed_iv,
+ size_t fixed_iv_len,
+ unsigned char const *dynamic_iv,
+ size_t dynamic_iv_len,
+ unsigned mode )
+{
+ int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
+
+ ((void) dst_nonce_len);
+
+ /* Start with Fixed IV || 0 */
+ memcpy( dst_nonce, fixed_iv, fixed_iv_len );
+ dst_nonce += fixed_iv_len;
+
+ if( mode == SSL_RECORD_AEAD_NONCE_CONCAT )
+ {
+ /* Nonce := Fixed IV || Dynamic IV */
+ memcpy( dst_nonce, dynamic_iv, dynamic_iv_len );
+ ret = 0;
+ }
+ else if( mode == SSL_RECORD_AEAD_NONCE_XOR )
+ {
+ /* Nonce := Fixed IV XOR ( 0 || Dynamic IV ) */
+ unsigned char i;
+
+ /* This is safe by the second precondition above. */
+ dst_nonce -= dynamic_iv_len;
+ for( i = 0; i < dynamic_iv_len; i++ )
+ dst_nonce[i] ^= dynamic_iv[i];
+
+ ret = 0;
+ }
+ else
+ {
+ ret = MBEDTLS_ERR_SSL_INTERNAL_ERROR;
+ }
+
+ return( ret );
+}
+
int mbedtls_ssl_encrypt_buf( mbedtls_ssl_context *ssl,
mbedtls_ssl_transform *transform,
mbedtls_record *rec,
@@ -759,7 +831,13 @@
{
int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
unsigned char iv[12];
- size_t explicit_iv_len = transform->ivlen - transform->fixed_ivlen;
+ unsigned char *dynamic_iv;
+ size_t dynamic_iv_len;
+
+ unsigned const nonce_mode
+ = ssl_transform_get_nonce_mode( transform );
+ unsigned const dynamic_iv_is_explicit
+ = nonce_mode == SSL_RECORD_AEAD_NONCE_CONCAT;
/* Check that there's space for the authentication tag. */
if( post_avail < transform->taglen )
@@ -769,31 +847,28 @@
}
/*
- * Generate IV
+ * Build nonce for AEAD encryption.
+ *
+ * Note: In the case of CCM and GCM in TLS 1.2, the dynamic
+ * part of the IV is prepended to the ciphertext and
+ * can be chosen freely - in particular, it need not
+ * agree with the record sequence number.
+ * However, since ChaChaPoly as well as all AEAD modes
+ * in TLS 1.3 use the record sequence number as the
+ * dynamic part of the nonce, we uniformly use the
+ * record sequence number here in all cases.
*/
- if( transform->ivlen == 12 && transform->fixed_ivlen == 4 )
- {
- /* GCM and CCM: fixed || explicit (=seqnum) */
- memcpy( iv, transform->iv_enc, transform->fixed_ivlen );
- memcpy( iv + transform->fixed_ivlen, rec->ctr,
- explicit_iv_len );
- }
- else if( transform->ivlen == 12 && transform->fixed_ivlen == 12 )
- {
- /* ChachaPoly: fixed XOR sequence number */
- unsigned char i;
+ dynamic_iv = rec->ctr;
+ dynamic_iv_len = sizeof( rec->ctr );
- memcpy( iv, transform->iv_enc, transform->fixed_ivlen );
-
- for( i = 0; i < 8; i++ )
- iv[i+4] ^= rec->ctr[i];
- }
- else
- {
- /* Reminder if we ever add an AEAD mode with a different size */
- MBEDTLS_SSL_DEBUG_MSG( 1, ( "should never happen" ) );
- return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
- }
+ ret = ssl_build_record_nonce( iv, sizeof( iv ),
+ transform->iv_enc,
+ transform->fixed_ivlen,
+ dynamic_iv,
+ dynamic_iv_len,
+ nonce_mode );
+ if( ret != 0 )
+ return( ret );
/*
* Build additional data for AEAD encryption.
@@ -805,7 +880,8 @@
MBEDTLS_SSL_DEBUG_BUF( 4, "IV used (internal)",
iv, transform->ivlen );
MBEDTLS_SSL_DEBUG_BUF( 4, "IV used (transmitted)",
- data - explicit_iv_len, explicit_iv_len );
+ data - dynamic_iv_len * dynamic_iv_is_explicit,
+ dynamic_iv_len * dynamic_iv_is_explicit );
MBEDTLS_SSL_DEBUG_BUF( 4, "additional data used for AEAD",
add_data, add_data_len );
MBEDTLS_SSL_DEBUG_MSG( 3, ( "before encrypt: msglen = %d, "
@@ -826,24 +902,28 @@
MBEDTLS_SSL_DEBUG_RET( 1, "mbedtls_cipher_auth_encrypt", ret );
return( ret );
}
-
- /*
- * Prefix record content with explicit IV.
- */
- if( rec->data_offset < explicit_iv_len )
- {
- MBEDTLS_SSL_DEBUG_MSG( 1, ( "Buffer provided for encrypted record not large enough" ) );
- return( MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL );
- }
- memcpy( data - explicit_iv_len, rec->ctr, explicit_iv_len );
-
MBEDTLS_SSL_DEBUG_BUF( 4, "after encrypt: tag",
data + rec->data_len, transform->taglen );
-
- /* Account for tag and explicit IV. */
- rec->data_len += transform->taglen + explicit_iv_len;
- rec->data_offset -= explicit_iv_len;
+ /* Account for authentication tag. */
+ rec->data_len += transform->taglen;
post_avail -= transform->taglen;
+
+ /*
+ * Prefix record content with dynamic IV in case it is explicit.
+ */
+ if( dynamic_iv_is_explicit == 1 )
+ {
+ if( rec->data_offset < dynamic_iv_len )
+ {
+ MBEDTLS_SSL_DEBUG_MSG( 1, ( "Buffer provided for encrypted record not large enough" ) );
+ return( MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL );
+ }
+
+ memcpy( data - dynamic_iv_len, dynamic_iv, dynamic_iv_len );
+ rec->data_offset -= dynamic_iv_len;
+ rec->data_len += dynamic_iv_len;
+ }
+
auth_done++;
}
else
@@ -1080,60 +1160,63 @@
mode == MBEDTLS_MODE_CHACHAPOLY )
{
unsigned char iv[12];
- size_t explicit_iv_len = transform->ivlen - transform->fixed_ivlen;
+ unsigned const nonce_mode = ssl_transform_get_nonce_mode( transform );
+ unsigned char *dynamic_iv;
+ size_t dynamic_iv_len;
/*
- * Prepare IV from explicit and implicit data.
+ * Extract dynamic part of nonce for AEAD decryption.
+ *
+ * Note: In the case of CCM and GCM in TLS 1.2, the dynamic
+ * part of the IV is prepended to the ciphertext and
+ * can be chosen freely - in particular, it need not
+ * agree with the record sequence number.
*/
-
- /* Check that there's enough space for the explicit IV
- * (at the beginning of the record) and the MAC (at the
- * end of the record). */
- if( rec->data_len < explicit_iv_len + transform->taglen )
+ dynamic_iv_len = sizeof( rec->ctr );
+ if( nonce_mode == SSL_RECORD_AEAD_NONCE_XOR )
{
- MBEDTLS_SSL_DEBUG_MSG( 1, ( "msglen (%d) < explicit_iv_len (%d) "
- "+ taglen (%d)", rec->data_len,
- explicit_iv_len, transform->taglen ) );
+ dynamic_iv = rec->ctr;
+ }
+ else
+ {
+ if( rec->data_len < dynamic_iv_len )
+ {
+ MBEDTLS_SSL_DEBUG_MSG( 1, ( "msglen (%d) < explicit_iv_len (%d) ",
+ rec->data_len,
+ dynamic_iv_len ) );
+ return( MBEDTLS_ERR_SSL_INVALID_MAC );
+ }
+ dynamic_iv = data;
+
+ data += dynamic_iv_len;
+ rec->data_offset += dynamic_iv_len;
+ rec->data_len -= dynamic_iv_len;
+ }
+
+ /* Check that there's space for the authentication tag. */
+ if( rec->data_len < transform->taglen )
+ {
+ MBEDTLS_SSL_DEBUG_MSG( 1, ( "msglen (%d) < taglen (%d) " ) );
return( MBEDTLS_ERR_SSL_INVALID_MAC );
}
+ rec->data_len -= transform->taglen;
-#if defined(MBEDTLS_GCM_C) || defined(MBEDTLS_CCM_C)
- if( transform->ivlen == 12 && transform->fixed_ivlen == 4 )
- {
- /* GCM and CCM: fixed || explicit */
+ /*
+ * Prepare nonce from dynamic and static parts.
+ */
+ ret = ssl_build_record_nonce( iv, sizeof( iv ),
+ transform->iv_dec,
+ transform->fixed_ivlen,
+ dynamic_iv,
+ dynamic_iv_len,
+ nonce_mode );
+ if( ret != 0 )
+ return( ret );
- /* Fixed */
- memcpy( iv, transform->iv_dec, transform->fixed_ivlen );
- /* Explicit */
- memcpy( iv + transform->fixed_ivlen, data, 8 );
- }
- else
-#endif /* MBEDTLS_GCM_C || MBEDTLS_CCM_C */
-#if defined(MBEDTLS_CHACHAPOLY_C)
- if( transform->ivlen == 12 && transform->fixed_ivlen == 12 )
- {
- /* ChachaPoly: fixed XOR sequence number */
- unsigned char i;
-
- memcpy( iv, transform->iv_dec, transform->fixed_ivlen );
-
- for( i = 0; i < 8; i++ )
- iv[i+4] ^= rec->ctr[i];
- }
- else
-#endif /* MBEDTLS_CHACHAPOLY_C */
- {
- /* Reminder if we ever add an AEAD mode with a different size */
- MBEDTLS_SSL_DEBUG_MSG( 1, ( "should never happen" ) );
- return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
- }
-
- /* Group changes to data, data_len, and add_data, because
- * add_data depends on data_len. */
- data += explicit_iv_len;
- rec->data_offset += explicit_iv_len;
- rec->data_len -= explicit_iv_len + transform->taglen;
-
+ /*
+ * Build additional data for AEAD encryption.
+ * This depends on the TLS version.
+ */
ssl_extract_add_data_from_record( add_data, &add_data_len, rec,
transform->minor_ver );
MBEDTLS_SSL_DEBUG_BUF( 4, "additional data used for AEAD",