Merge pull request #716 from mpg/ct-varlen-hmac
Add constant-flow variable-length HMAC function
diff --git a/include/mbedtls/check_config.h b/include/mbedtls/check_config.h
index f2148a8..15cc21b 100644
--- a/include/mbedtls/check_config.h
+++ b/include/mbedtls/check_config.h
@@ -186,6 +186,16 @@
#error "MBEDTLS_ENTROPY_FORCE_SHA256 defined, but not all prerequisites"
#endif
+#if defined(__has_feature)
+#if __has_feature(memory_sanitizer)
+#define MBEDTLS_HAS_MEMSAN
+#endif
+#endif
+#if defined(MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN) && !defined(MBEDTLS_HAS_MEMSAN)
+#error "MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN requires building with MemorySanitizer"
+#endif
+#undef MBEDTLS_HAS_MEMSAN
+
#if defined(MBEDTLS_TEST_NULL_ENTROPY) && \
( !defined(MBEDTLS_ENTROPY_C) || !defined(MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES) )
#error "MBEDTLS_TEST_NULL_ENTROPY defined, but not all prerequisites"
diff --git a/include/mbedtls/config.h b/include/mbedtls/config.h
index e00c546..3bb631f 100644
--- a/include/mbedtls/config.h
+++ b/include/mbedtls/config.h
@@ -1907,6 +1907,22 @@
//#define MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH
/**
+ * \def MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN
+ *
+ * Enable testing of the constant-flow nature of some sensitive functions with
+ * clang's MemorySanitizer. This causes some existing tests to also test
+ * non-functional properties of the code under test.
+ *
+ * This setting requires compiling with clang -fsanitize=memory.
+ *
+ * \warning This macro is only used for extended testing; it is not considered
+ * part of the library's API, so it may change or disappear at any time.
+ *
+ * Uncomment to enable testing of the constant-flow nature of selected code.
+ */
+//#define MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN
+
+/**
* \def MBEDTLS_TEST_HOOKS
*
* Enable features for invasive testing such as introspection functions and
diff --git a/include/mbedtls/ssl_internal.h b/include/mbedtls/ssl_internal.h
index c68038c..6f7b458 100644
--- a/include/mbedtls/ssl_internal.h
+++ b/include/mbedtls/ssl_internal.h
@@ -146,12 +146,26 @@
#define MBEDTLS_SSL_COMPRESSION_ADD 0
#endif
+/* This macro determines whether CBC is supported. */
+#if defined(MBEDTLS_CIPHER_MODE_CBC) && \
+ ( defined(MBEDTLS_AES_C) || \
+ defined(MBEDTLS_CAMELLIA_C) || \
+ defined(MBEDTLS_ARIA_C) || \
+ defined(MBEDTLS_DES_C) )
+#define MBEDTLS_SSL_SOME_SUITES_USE_CBC
+#endif
+
+/* This macro determines whether the CBC construct used in TLS 1.0-1.2 (as
+ * opposed to the very different CBC construct used in SSLv3) is supported. */
+#if defined(MBEDTLS_SSL_SOME_SUITES_USE_CBC) && \
+ ( defined(MBEDTLS_SSL_PROTO_TLS1) || \
+ defined(MBEDTLS_SSL_PROTO_TLS1_1) || \
+ defined(MBEDTLS_SSL_PROTO_TLS1_2) )
+#define MBEDTLS_SSL_SOME_SUITES_USE_TLS_CBC
+#endif
+
#if defined(MBEDTLS_ARC4_C) || defined(MBEDTLS_CIPHER_NULL_CIPHER) || \
- ( defined(MBEDTLS_CIPHER_MODE_CBC) && \
- ( defined(MBEDTLS_AES_C) || \
- defined(MBEDTLS_CAMELLIA_C) || \
- defined(MBEDTLS_ARIA_C) || \
- defined(MBEDTLS_DES_C) ) )
+ defined(MBEDTLS_SSL_SOME_SUITES_USE_CBC)
#define MBEDTLS_SSL_SOME_MODES_USE_MAC
#endif
diff --git a/library/ssl_invasive.h b/library/ssl_invasive.h
new file mode 100644
index 0000000..f04b816
--- /dev/null
+++ b/library/ssl_invasive.h
@@ -0,0 +1,78 @@
+/**
+ * \file ssl_invasive.h
+ *
+ * \brief SSL module: interfaces for invasive testing only.
+ *
+ * The interfaces in this file are intended for testing purposes only.
+ * They SHOULD NOT be made available in library integrations except when
+ * building the library for testing.
+ */
+/*
+ * Copyright (C) 2020, ARM Limited, All Rights Reserved
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file is part of mbed TLS (https://tls.mbed.org)
+ */
+#ifndef MBEDTLS_SSL_INVASIVE_H
+#define MBEDTLS_SSL_INVASIVE_H
+
+#include "common.h"
+#include "mbedtls/md.h"
+
+#if defined(MBEDTLS_TEST_HOOKS) && \
+ defined(MBEDTLS_SSL_SOME_SUITES_USE_TLS_CBC)
+/** \brief Compute the HMAC of variable-length data with constant flow.
+ *
+ * This function computes the HMAC of the concatenation of \p add_data and \p
+ * data, and does with a code flow and memory access pattern that does not
+ * depend on \p data_len_secret, but only on \p min_data_len and \p
+ * max_data_len. In particular, this function always reads exactly \p
+ * max_data_len bytes from \p data.
+ *
+ * \param ctx The HMAC context. It must have keys configured
+ * with mbedtls_md_hmac_starts() and use one of the
+ * following hashes: SHA-384, SHA-256, SHA-1 or MD-5.
+ * It is reset using mbedtls_md_hmac_reset() after
+ * the computation is complete to prepare for the
+ * next computation.
+ * \param add_data The additional data prepended to \p data. This
+ * must point to a readable buffer of \p add_data_len
+ * bytes.
+ * \param add_data_len The length of \p add_data in bytes.
+ * \param data The data appended to \p add_data. This must point
+ * to a readable buffer of \p max_data_len bytes.
+ * \param data_len_secret The length of the data to process in \p data.
+ * This must be no less than \p min_data_len and no
+ * greater than \p max_data_len.
+ * \param min_data_len The minimal length of \p data in bytes.
+ * \param max_data_len The maximal length of \p data in bytes.
+ * \param output The HMAC will be written here. This must point to
+ * a writable buffer of sufficient size to hold the
+ * HMAC value.
+ *
+ * \retval 0
+ * Success.
+ * \retval MBEDTLS_ERR_PLATFORM_HW_ACCEL_FAILED
+ * The hardware accelerator failed.
+ */
+int mbedtls_ssl_cf_hmac(
+ mbedtls_md_context_t *ctx,
+ const unsigned char *add_data, size_t add_data_len,
+ const unsigned char *data, size_t data_len_secret,
+ size_t min_data_len, size_t max_data_len,
+ unsigned char *output );
+#endif /* MBEDTLS_TEST_HOOKS && MBEDTLS_SSL_SOME_SUITES_USE_TLS_CBC */
+
+#endif /* MBEDTLS_SSL_INVASIVE_H */
diff --git a/library/ssl_msg.c b/library/ssl_msg.c
index d32afac..32c1b87 100644
--- a/library/ssl_msg.c
+++ b/library/ssl_msg.c
@@ -47,6 +47,8 @@
#include "mbedtls/platform_util.h"
#include "mbedtls/version.h"
+#include "ssl_invasive.h"
+
#include <string.h>
#if defined(MBEDTLS_USE_PSA_CRYPTO)
@@ -320,7 +322,7 @@
defined(MBEDTLS_SSL_PROTO_TLS1_2) )
/* This function makes sure every byte in the memory region is accessed
* (in ascending addresses order) */
-static void ssl_read_memory( unsigned char *p, size_t len )
+static void ssl_read_memory( const unsigned char *p, size_t len )
{
unsigned char acc = 0;
volatile unsigned char force;
@@ -609,10 +611,7 @@
/* The PRNG is used for dynamic IV generation that's used
* for CBC transformations in TLS 1.1 and TLS 1.2. */
-#if !( defined(MBEDTLS_CIPHER_MODE_CBC) && \
- ( defined(MBEDTLS_AES_C) || \
- defined(MBEDTLS_ARIA_C) || \
- defined(MBEDTLS_CAMELLIA_C) ) && \
+#if !( defined(MBEDTLS_SSL_SOME_SUITES_USE_CBC) && \
( defined(MBEDTLS_SSL_PROTO_TLS1_1) || defined(MBEDTLS_SSL_PROTO_TLS1_2) ) )
((void) f_rng);
((void) p_rng);
@@ -910,8 +909,7 @@
}
else
#endif /* MBEDTLS_GCM_C || MBEDTLS_CCM_C || MBEDTLS_CHACHAPOLY_C */
-#if defined(MBEDTLS_CIPHER_MODE_CBC) && \
- ( defined(MBEDTLS_AES_C) || defined(MBEDTLS_CAMELLIA_C) || defined(MBEDTLS_ARIA_C) )
+#if defined(MBEDTLS_SSL_SOME_SUITES_USE_CBC)
if( mode == MBEDTLS_MODE_CBC )
{
int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
@@ -1050,8 +1048,7 @@
#endif /* MBEDTLS_SSL_ENCRYPT_THEN_MAC */
}
else
-#endif /* MBEDTLS_CIPHER_MODE_CBC &&
- ( MBEDTLS_AES_C || MBEDTLS_CAMELLIA_C || MBEDTLS_ARIA_C ) */
+#endif /* MBEDTLS_SSL_SOME_SUITES_USE_CBC) */
{
MBEDTLS_SSL_DEBUG_MSG( 1, ( "should never happen" ) );
return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
@@ -1069,6 +1066,135 @@
return( 0 );
}
+#if defined(MBEDTLS_SSL_SOME_SUITES_USE_TLS_CBC)
+/*
+ * Constant-flow conditional memcpy:
+ * - if c1 == c2, equivalent to memcpy(dst, src, len),
+ * - otherwise, a no-op,
+ * but with execution flow independent of the values of c1 and c2.
+ *
+ * Use only bit operations to avoid branches that could be used by some
+ * compilers on some platforms to translate comparison operators.
+ */
+static void mbedtls_ssl_cf_memcpy_if_eq( unsigned char *dst,
+ const unsigned char *src,
+ size_t len,
+ size_t c1, size_t c2 )
+{
+ /* diff = 0 if c1 == c2, non-zero otherwise */
+ const size_t diff = c1 ^ c2;
+
+ /* MSVC has a warning about unary minus on unsigned integer types,
+ * but this is well-defined and precisely what we want to do here. */
+#if defined(_MSC_VER)
+#pragma warning( push )
+#pragma warning( disable : 4146 )
+#endif
+
+ /* diff_msb's most significant bit is equal to c1 != c2 */
+ const size_t diff_msb = ( diff | -diff );
+
+ /* diff1 = c1 != c2 */
+ const size_t diff1 = diff_msb >> ( sizeof( diff_msb ) * 8 - 1 );
+
+ /* mask = c1 != c2 ? 0xff : 0x00 */
+ const unsigned char mask = (unsigned char) -diff1;
+
+#if defined(_MSC_VER)
+#pragma warning( pop )
+#endif
+
+ /* dst[i] = c1 != c2 ? dst[i] : src[i] */
+ for( size_t i = 0; i < len; i++ )
+ dst[i] = ( dst[i] & mask ) | ( src[i] & ~mask );
+}
+
+/*
+ * Compute HMAC of variable-length data with constant flow.
+ *
+ * Only works with MD-5, SHA-1, SHA-256 and SHA-384.
+ * (Otherwise, computation of block_size needs to be adapted.)
+ */
+MBEDTLS_STATIC_TESTABLE int mbedtls_ssl_cf_hmac(
+ mbedtls_md_context_t *ctx,
+ const unsigned char *add_data, size_t add_data_len,
+ const unsigned char *data, size_t data_len_secret,
+ size_t min_data_len, size_t max_data_len,
+ unsigned char *output )
+{
+ /*
+ * This function breaks the HMAC abstraction and uses the md_clone()
+ * extension to the MD API in order to get constant-flow behaviour.
+ *
+ * HMAC(msg) is defined as HASH(okey + HASH(ikey + msg)) where + means
+ * concatenation, and okey/ikey are the XOR of the key with some fixed bit
+ * patterns (see RFC 2104, sec. 2), which are stored in ctx->hmac_ctx.
+ *
+ * We'll first compute inner_hash = HASH(ikey + msg) by hashing up to
+ * minlen, then cloning the context, and for each byte up to maxlen
+ * finishing up the hash computation, keeping only the correct result.
+ *
+ * Then we only need to compute HASH(okey + inner_hash) and we're done.
+ */
+ const mbedtls_md_type_t md_alg = mbedtls_md_get_type( ctx->md_info );
+ /* TLS 1.0-1.2 only support SHA-384, SHA-256, SHA-1, MD-5,
+ * all of which have the same block size except SHA-384. */
+ const size_t block_size = md_alg == MBEDTLS_MD_SHA384 ? 128 : 64;
+ const unsigned char * const ikey = ctx->hmac_ctx;
+ const unsigned char * const okey = ikey + block_size;
+ const size_t hash_size = mbedtls_md_get_size( ctx->md_info );
+
+ unsigned char aux_out[MBEDTLS_MD_MAX_SIZE];
+ mbedtls_md_context_t aux;
+ size_t offset;
+ int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
+
+ mbedtls_md_init( &aux );
+
+#define MD_CHK( func_call ) \
+ do { \
+ ret = (func_call); \
+ if( ret != 0 ) \
+ goto cleanup; \
+ } while( 0 )
+
+ MD_CHK( mbedtls_md_setup( &aux, ctx->md_info, 0 ) );
+
+ /* After hmac_start() of hmac_reset(), ikey has already been hashed,
+ * so we can start directly with the message */
+ MD_CHK( mbedtls_md_update( ctx, add_data, add_data_len ) );
+ MD_CHK( mbedtls_md_update( ctx, data, min_data_len ) );
+
+ /* For each possible length, compute the hash up to that point */
+ for( offset = min_data_len; offset <= max_data_len; offset++ )
+ {
+ MD_CHK( mbedtls_md_clone( &aux, ctx ) );
+ MD_CHK( mbedtls_md_finish( &aux, aux_out ) );
+ /* Keep only the correct inner_hash in the output buffer */
+ mbedtls_ssl_cf_memcpy_if_eq( output, aux_out, hash_size,
+ offset, data_len_secret );
+
+ if( offset < max_data_len )
+ MD_CHK( mbedtls_md_update( ctx, data + offset, 1 ) );
+ }
+
+ /* Now compute HASH(okey + inner_hash) */
+ MD_CHK( mbedtls_md_starts( ctx ) );
+ MD_CHK( mbedtls_md_update( ctx, okey, block_size ) );
+ MD_CHK( mbedtls_md_update( ctx, output, hash_size ) );
+ MD_CHK( mbedtls_md_finish( ctx, output ) );
+
+ /* Done, get ready for next time */
+ MD_CHK( mbedtls_md_hmac_reset( ctx ) );
+
+#undef MD_CHK
+
+cleanup:
+ mbedtls_md_free( &aux );
+ return( ret );
+}
+#endif /* MBEDTLS_SSL_SOME_SUITES_USE_TLS_CBC */
+
int mbedtls_ssl_decrypt_buf( mbedtls_ssl_context const *ssl,
mbedtls_ssl_transform *transform,
mbedtls_record *rec )
@@ -1239,8 +1365,7 @@
}
else
#endif /* MBEDTLS_GCM_C || MBEDTLS_CCM_C */
-#if defined(MBEDTLS_CIPHER_MODE_CBC) && \
- ( defined(MBEDTLS_AES_C) || defined(MBEDTLS_CAMELLIA_C) || defined(MBEDTLS_ARIA_C) )
+#if defined(MBEDTLS_SSL_SOME_SUITES_USE_CBC)
if( mode == MBEDTLS_MODE_CBC )
{
size_t minlen = 0;
@@ -1493,8 +1618,7 @@
rec->data_len -= padlen;
}
else
-#endif /* MBEDTLS_CIPHER_MODE_CBC &&
- ( MBEDTLS_AES_C || MBEDTLS_CAMELLIA_C || MBEDTLS_ARIA_C ) */
+#endif /* MBEDTLS_SSL_SOME_SUITES_USE_CBC */
{
MBEDTLS_SSL_DEBUG_MSG( 1, ( "should never happen" ) );
return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
@@ -1547,38 +1671,6 @@
if( transform->minor_ver > MBEDTLS_SSL_MINOR_VERSION_0 )
{
/*
- * Process MAC and always update for padlen afterwards to make
- * total time independent of padlen.
- *
- * Known timing attacks:
- * - Lucky Thirteen (http://www.isg.rhul.ac.uk/tls/TLStiming.pdf)
- *
- * To compensate for different timings for the MAC calculation
- * depending on how much padding was removed (which is determined
- * by padlen), process extra_run more blocks through the hash
- * function.
- *
- * The formula in the paper is
- * extra_run = ceil( (L1-55) / 64 ) - ceil( (L2-55) / 64 )
- * where L1 is the size of the header plus the decrypted message
- * plus CBC padding and L2 is the size of the header plus the
- * decrypted message. This is for an underlying hash function
- * with 64-byte blocks.
- * We use ( (Lx+8) / 64 ) to handle 'negative Lx' values
- * correctly. We round down instead of up, so -56 is the correct
- * value for our calculations instead of -55.
- *
- * Repeat the formula rather than defining a block_size variable.
- * This avoids requiring division by a variable at runtime
- * (which would be marginally less efficient and would require
- * linking an extra division function in some builds).
- */
- size_t j, extra_run = 0;
- /* This size is enough to server either as input to
- * md_process() or as output to md_finish() */
- unsigned char tmp[MBEDTLS_MD_MAX_BLOCK_SIZE];
-
- /*
* The next two sizes are the minimum and maximum values of
* in_msglen over all padlen values.
*
@@ -1591,58 +1683,16 @@
const size_t max_len = rec->data_len + padlen;
const size_t min_len = ( max_len > 256 ) ? max_len - 256 : 0;
- memset( tmp, 0, sizeof( tmp ) );
-
- switch( mbedtls_md_get_type( transform->md_ctx_dec.md_info ) )
+ ret = mbedtls_ssl_cf_hmac( &transform->md_ctx_dec,
+ add_data, add_data_len,
+ data, rec->data_len, min_len, max_len,
+ mac_expect );
+ if( ret != 0 )
{
-#if defined(MBEDTLS_MD5_C) || defined(MBEDTLS_SHA1_C) || \
- defined(MBEDTLS_SHA256_C)
- case MBEDTLS_MD_MD5:
- case MBEDTLS_MD_SHA1:
- case MBEDTLS_MD_SHA256:
- /* 8 bytes of message size, 64-byte compression blocks */
- extra_run =
- ( add_data_len + rec->data_len + padlen + 8 ) / 64 -
- ( add_data_len + rec->data_len + 8 ) / 64;
- break;
-#endif
-#if defined(MBEDTLS_SHA512_C)
- case MBEDTLS_MD_SHA384:
- /* 16 bytes of message size, 128-byte compression blocks */
- extra_run =
- ( add_data_len + rec->data_len + padlen + 16 ) / 128 -
- ( add_data_len + rec->data_len + 16 ) / 128;
- break;
-#endif
- default:
- MBEDTLS_SSL_DEBUG_MSG( 1, ( "should never happen" ) );
- return( MBEDTLS_ERR_SSL_INTERNAL_ERROR );
+ MBEDTLS_SSL_DEBUG_RET( 1, "mbedtls_ssl_cf_hmac", ret );
+ return( ret );
}
- extra_run &= correct * 0xFF;
-
- mbedtls_md_hmac_update( &transform->md_ctx_dec, add_data,
- add_data_len );
- mbedtls_md_hmac_update( &transform->md_ctx_dec, data,
- rec->data_len );
- /* Make sure we access everything even when padlen > 0. This
- * makes the synchronisation requirements for just-in-time
- * Prime+Probe attacks much tighter and hopefully impractical. */
- ssl_read_memory( data + rec->data_len, padlen );
- mbedtls_md_hmac_finish( &transform->md_ctx_dec, mac_expect );
-
- /* Dummy calls to compression function.
- * Call mbedtls_md_process at least once due to cache attacks
- * that observe whether md_process() was called of not.
- * Respect the usual start-(process|update)-finish sequence for
- * the sake of hardware accelerators that might require it. */
- mbedtls_md_starts( &transform->md_ctx_dec );
- for( j = 0; j < extra_run + 1; j++ )
- mbedtls_md_process( &transform->md_ctx_dec, tmp );
- mbedtls_md_finish( &transform->md_ctx_dec, tmp );
-
- mbedtls_md_hmac_reset( &transform->md_ctx_dec );
-
/* Make sure we access all the memory that could contain the MAC,
* before we check it in the next code block. This makes the
* synchronisation requirements for just-in-time Prime+Probe
diff --git a/library/version_features.c b/library/version_features.c
index 64e9e86..2470d8d 100644
--- a/library/version_features.c
+++ b/library/version_features.c
@@ -557,6 +557,9 @@
#if defined(MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH)
"MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH",
#endif /* MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH */
+#if defined(MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN)
+ "MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN",
+#endif /* MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN */
#if defined(MBEDTLS_TEST_HOOKS)
"MBEDTLS_TEST_HOOKS",
#endif /* MBEDTLS_TEST_HOOKS */
diff --git a/programs/test/query_config.c b/programs/test/query_config.c
index 98b065b..23dc515 100644
--- a/programs/test/query_config.c
+++ b/programs/test/query_config.c
@@ -1538,6 +1538,14 @@
}
#endif /* MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH */
+#if defined(MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN)
+ if( strcmp( "MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN", config ) == 0 )
+ {
+ MACRO_EXPANSION_TO_STR( MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN );
+ return( 0 );
+ }
+#endif /* MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN */
+
#if defined(MBEDTLS_TEST_HOOKS)
if( strcmp( "MBEDTLS_TEST_HOOKS", config ) == 0 )
{
diff --git a/scripts/config.py b/scripts/config.py
index 3d297dc..793e9df 100755
--- a/scripts/config.py
+++ b/scripts/config.py
@@ -194,6 +194,7 @@
'MBEDTLS_RSA_NO_CRT', # influences the use of RSA in X.509 and TLS
'MBEDTLS_SHA512_NO_SHA384', # removes a feature
'MBEDTLS_SSL_HW_RECORD_ACCEL', # build dependency (hook functions)
+ 'MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN', # build dependency (clang+memsan)
'MBEDTLS_TEST_NULL_ENTROPY', # removes a feature
'MBEDTLS_X509_ALLOW_UNSUPPORTED_CRITICAL_EXTENSION', # influences the use of X.509 in TLS
'MBEDTLS_ZLIB_SUPPORT', # build dependency (libz)
diff --git a/tests/include/test/constant_flow.h b/tests/include/test/constant_flow.h
new file mode 100644
index 0000000..98bee7e
--- /dev/null
+++ b/tests/include/test/constant_flow.h
@@ -0,0 +1,51 @@
+/**
+ * \file constant_flow.h
+ *
+ * \brief This file contains tools to ensure tested code has constant flow.
+ */
+
+/*
+ * Copyright (C) 2020, ARM Limited, All Rights Reserved
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file is part of mbed TLS (https://tls.mbed.org)
+ */
+
+#ifndef TEST_CONSTANT_FLOW_H
+#define TEST_CONSTANT_FLOW_H
+
+#if !defined(MBEDTLS_CONFIG_FILE)
+#include "mbedtls/config.h"
+#else
+#include MBEDTLS_CONFIG_FILE
+#endif
+
+#if defined(MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN)
+#include <sanitizer/msan_interface.h>
+
+/* Use macros to avoid messing up with origin tracking */
+#define TEST_CF_SECRET __msan_allocated_memory
+// void __msan_allocated_memory(const volatile void* data, size_t size);
+#define TEST_CF_PUBLIC __msan_unpoison
+// void __msan_unpoison(const volatile void *a, size_t size);
+
+#else /* MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN */
+
+#define TEST_CF_SECRET(ptr, size)
+#define TEST_CF_PUBLIC(ptr, size)
+
+#endif /* MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN */
+
+#endif /* TEST_CONSTANT_FLOW_H */
diff --git a/tests/scripts/all.sh b/tests/scripts/all.sh
index ec61d19..0a7c441 100755
--- a/tests/scripts/all.sh
+++ b/tests/scripts/all.sh
@@ -1074,6 +1074,24 @@
if_build_succeeded env OPENSSL_CMD="$OPENSSL_NEXT" tests/compat.sh -e '^$' -f 'ARIA\|CHACHA'
}
+component_test_memsan_constant_flow () {
+ # This tests both (1) accesses to undefined memory, and (2) branches or
+ # memory access depending on secret values. To distinguish between those:
+ # - unset MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN - does the failure persist?
+ # - or alternatively, change the build type to MemSanDbg, which enables
+ # origin tracking and nicer stack traces (which are useful for debugging
+ # anyway), and check if the origin was TEST_CF_SECRET() or something else.
+ msg "build: cmake MSan (clang), full config with constant flow testing"
+ scripts/config.py full
+ scripts/config.py set MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN
+ scripts/config.py unset MBEDTLS_AESNI_C # memsan doesn't grok asm
+ CC=clang cmake -D CMAKE_BUILD_TYPE:String=MemSan .
+ make
+
+ msg "test: main suites (Msan + constant flow)"
+ make test
+}
+
component_test_default_no_deprecated () {
# Test that removing the deprecated features from the default
# configuration leaves something consistent.
diff --git a/tests/scripts/check-names.sh b/tests/scripts/check-names.sh
index e2019cc..1a8253c 100755
--- a/tests/scripts/check-names.sh
+++ b/tests/scripts/check-names.sh
@@ -98,6 +98,7 @@
printf "Likely typos: "
sort -u actual-macros enum-consts > _caps
HEADERS=$( ls include/mbedtls/*.h include/psa/*.h | egrep -v 'compat-1\.3\.h' )
+HEADERS="$HEADERS library/*.h"
HEADERS="$HEADERS 3rdparty/everest/include/everest/everest.h 3rdparty/everest/include/everest/x25519.h"
LIBRARY="$( ls library/*.c )"
LIBRARY="$LIBRARY 3rdparty/everest/library/everest.c 3rdparty/everest/library/x25519.c"
diff --git a/tests/scripts/list-macros.sh b/tests/scripts/list-macros.sh
index 786aef9..cf6afc5 100755
--- a/tests/scripts/list-macros.sh
+++ b/tests/scripts/list-macros.sh
@@ -25,6 +25,7 @@
fi
HEADERS=$( ls include/mbedtls/*.h include/psa/*.h | egrep -v 'compat-1\.3\.h' )
+HEADERS="$HEADERS library/*.h"
HEADERS="$HEADERS 3rdparty/everest/include/everest/everest.h 3rdparty/everest/include/everest/x25519.h"
sed -n -e 's/.*#define \([a-zA-Z0-9_]*\).*/\1/p' $HEADERS \
diff --git a/tests/suites/test_suite_ssl.data b/tests/suites/test_suite_ssl.data
index 50bf49d..3afa39a 100644
--- a/tests/suites/test_suite_ssl.data
+++ b/tests/suites/test_suite_ssl.data
@@ -10529,3 +10529,19 @@
Session serialization, load buffer size: large ticket, cert
depends_on:MBEDTLS_SSL_SESSION_TICKETS:MBEDTLS_SSL_CLI_C:MBEDTLS_X509_USE_C:MBEDTLS_PEM_PARSE_C:MBEDTLS_ECDSA_C:MBEDTLS_ECP_DP_SECP256R1_ENABLED:MBEDTLS_SHA256_C:MBEDTLS_FS_IO
ssl_serialize_session_load_buf_size:1023:"data_files/server5.crt"
+
+Constant-flow HMAC: MD5
+depends_on:MBEDTLS_MD5_C
+ssl_cf_hmac:MBEDTLS_MD_MD5
+
+Constant-flow HMAC: SHA1
+depends_on:MBEDTLS_SHA1_C
+ssl_cf_hmac:MBEDTLS_MD_SHA1
+
+Constant-flow HMAC: SHA256
+depends_on:MBEDTLS_SHA256_C
+ssl_cf_hmac:MBEDTLS_MD_SHA256
+
+Constant-flow HMAC: SHA384
+depends_on:MBEDTLS_SHA512_C:!MBEDTLS_SHA512_NO_SHA384
+ssl_cf_hmac:MBEDTLS_MD_SHA384
diff --git a/tests/suites/test_suite_ssl.function b/tests/suites/test_suite_ssl.function
index cbc660c..f7c9be0 100644
--- a/tests/suites/test_suite_ssl.function
+++ b/tests/suites/test_suite_ssl.function
@@ -7,6 +7,10 @@
#include <mbedtls/timing.h>
#include <mbedtls/debug.h>
+#include <ssl_invasive.h>
+
+#include <test/constant_flow.h>
+
typedef struct log_pattern
{
const char *pattern;
@@ -4263,3 +4267,97 @@
goto exit;
}
/* END_CASE */
+
+/* BEGIN_CASE depends_on:MBEDTLS_SSL_SOME_SUITES_USE_TLS_CBC:MBEDTLS_TEST_HOOKS */
+void ssl_cf_hmac( int hash )
+{
+ /*
+ * Test the function mbedtls_ssl_cf_hmac() against a reference
+ * implementation.
+ */
+ mbedtls_md_context_t ctx, ref_ctx;
+ const mbedtls_md_info_t *md_info;
+ size_t out_len, block_size;
+ size_t min_in_len, in_len, max_in_len, i;
+ /* TLS additional data is 13 bytes (hence the "lucky 13" name) */
+ unsigned char add_data[13];
+ unsigned char ref_out[MBEDTLS_MD_MAX_SIZE];
+ unsigned char *data = NULL;
+ unsigned char *out = NULL;
+ unsigned char rec_num = 0;
+
+ mbedtls_md_init( &ctx );
+ mbedtls_md_init( &ref_ctx );
+
+ md_info = mbedtls_md_info_from_type( hash );
+ TEST_ASSERT( md_info != NULL );
+ out_len = mbedtls_md_get_size( md_info );
+ TEST_ASSERT( out_len != 0 );
+ block_size = hash == MBEDTLS_MD_SHA384 ? 128 : 64;
+
+ /* Use allocated out buffer to catch overwrites */
+ ASSERT_ALLOC( out, out_len );
+
+ /* Set up contexts with the given hash and a dummy key */
+ TEST_EQUAL( 0, mbedtls_md_setup( &ctx, md_info, 1 ) );
+ TEST_EQUAL( 0, mbedtls_md_setup( &ref_ctx, md_info, 1 ) );
+ memset( ref_out, 42, sizeof( ref_out ) );
+ TEST_EQUAL( 0, mbedtls_md_hmac_starts( &ctx, ref_out, out_len ) );
+ TEST_EQUAL( 0, mbedtls_md_hmac_starts( &ref_ctx, ref_out, out_len ) );
+ memset( ref_out, 0, sizeof( ref_out ) );
+
+ /*
+ * Test all possible lengths up to a point. The difference between
+ * max_in_len and min_in_len is at most 255, and make sure they both vary
+ * by at least one block size.
+ */
+ for( max_in_len = 0; max_in_len <= 255 + block_size; max_in_len++ )
+ {
+ test_set_step( max_in_len * 10000 );
+
+ /* Use allocated in buffer to catch overreads */
+ ASSERT_ALLOC( data, max_in_len );
+
+ min_in_len = max_in_len > 255 ? max_in_len - 255 : 0;
+ for( in_len = min_in_len; in_len <= max_in_len; in_len++ )
+ {
+ test_set_step( max_in_len * 10000 + in_len );
+
+ /* Set up dummy data and add_data */
+ rec_num++;
+ memset( add_data, rec_num, sizeof( add_data ) );
+ for( i = 0; i < in_len; i++ )
+ data[i] = ( i & 0xff ) ^ rec_num;
+
+ /* Get the function's result */
+ TEST_CF_SECRET( &in_len, sizeof( in_len ) );
+ TEST_EQUAL( 0, mbedtls_ssl_cf_hmac( &ctx, add_data, sizeof( add_data ),
+ data, in_len,
+ min_in_len, max_in_len,
+ out ) );
+ TEST_CF_PUBLIC( &in_len, sizeof( in_len ) );
+ TEST_CF_PUBLIC( out, out_len );
+
+ /* Compute the reference result */
+ TEST_EQUAL( 0, mbedtls_md_hmac_update( &ref_ctx, add_data,
+ sizeof( add_data ) ) );
+ TEST_EQUAL( 0, mbedtls_md_hmac_update( &ref_ctx, data, in_len ) );
+ TEST_EQUAL( 0, mbedtls_md_hmac_finish( &ref_ctx, ref_out ) );
+ TEST_EQUAL( 0, mbedtls_md_hmac_reset( &ref_ctx ) );
+
+ /* Compare */
+ ASSERT_COMPARE( out, out_len, ref_out, out_len );
+ }
+
+ mbedtls_free( data );
+ data = NULL;
+ }
+
+exit:
+ mbedtls_md_free( &ref_ctx );
+ mbedtls_md_free( &ctx );
+
+ mbedtls_free( data );
+ mbedtls_free( out );
+}
+/* END_CASE */
diff --git a/visualc/VS2010/mbedTLS.vcxproj b/visualc/VS2010/mbedTLS.vcxproj
index 4422b7a..578289f 100644
--- a/visualc/VS2010/mbedTLS.vcxproj
+++ b/visualc/VS2010/mbedTLS.vcxproj
@@ -232,6 +232,7 @@
<ClInclude Include="..\..\include\psa\crypto_struct.h" />
<ClInclude Include="..\..\include\psa\crypto_types.h" />
<ClInclude Include="..\..\include\psa\crypto_values.h" />
+ <ClInclude Include="..\..\tests\include\test\constant_flow.h" />
<ClInclude Include="..\..\tests\include\test\helpers.h" />
<ClInclude Include="..\..\tests\include\test\macros.h" />
<ClInclude Include="..\..\tests\include\test\psa_crypto_helpers.h" />
@@ -245,6 +246,7 @@
<ClInclude Include="..\..\library\psa_crypto_service_integration.h" />
<ClInclude Include="..\..\library\psa_crypto_slot_management.h" />
<ClInclude Include="..\..\library\psa_crypto_storage.h" />
+ <ClInclude Include="..\..\library\ssl_invasive.h" />
<ClInclude Include="..\..\3rdparty\everest\include\everest\everest.h" />
<ClInclude Include="..\..\3rdparty\everest\include\everest\Hacl_Curve25519.h" />
<ClInclude Include="..\..\3rdparty\everest\include\everest\kremlib.h" />