Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/dtls.c
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,14 @@ int TLSX_ConnectionID_Parse(WOLFSSL* ssl, const byte* input, word16 length,
XMEMCPY(id->id, input + OPAQUE8_LEN, cidSz);
id->length = cidSz;
info->tx = id;
/* Invalidate the cached AEAD record overhead because the TX CID
* changes record framing. Today this only fires during the initial
* extension exchange (before the cache can be populated). When
* mid-connection CID change is added (DTLS 1.3), add a regression
* test that primes the cache, changes the CID, and re-asserts that
* wolfssl_local_GetRecordSize() agrees with BuildMessage(sizeOnly=1)
* after the change. */
ssl->recordSzOverhead = 0;
}

info->negotiated = 1;
Expand Down
29 changes: 29 additions & 0 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -42646,6 +42646,27 @@ int wolfssl_local_GetRecordSize(WOLFSSL *ssl, int payloadSz, int isEncrypted)
return BAD_FUNC_ARG;

if (isEncrypted) {
/* AEAD overhead is constant per cache key (cipher, version, CID, DTLS
* 1.3 epoch); use the cached value when available. DTLS 1.3 pads
* records up to Dtls13MinimumRecordLength() (RFC 9147 5.5), so:
* - on read: only return the cached overhead when the resulting
* record would not be padded;
* - on populate: only store the overhead when BuildMessage returned
* a record strictly above the minimum, which guarantees no
* padding was applied. */
#ifdef WOLFSSL_DTLS13
int isDtls13 = ssl->options.dtls && ssl->options.tls1_3;
#endif

if (ssl->specs.cipher_type == aead && ssl->recordSzOverhead != 0
#ifdef WOLFSSL_DTLS13
&& (!isDtls13 || payloadSz + (int)ssl->recordSzOverhead
>= Dtls13MinimumRecordLength(ssl))
#endif
) {
return payloadSz + (int)ssl->recordSzOverhead;
}

recordSz = BuildMessage(ssl, NULL, 0, NULL, payloadSz, application_data,
0, 1, 0, CUR_ORDER);
/* use a safe upper bound in case of error */
Expand All @@ -42656,6 +42677,14 @@ int wolfssl_local_GetRecordSize(WOLFSSL *ssl, int payloadSz, int isEncrypted)
recordSz += DTLS_RECORD_EXTRA;
}
}
else if (ssl->specs.cipher_type == aead && recordSz > payloadSz
#ifdef WOLFSSL_DTLS13
&& (!isDtls13 || recordSz > Dtls13MinimumRecordLength(ssl))
#endif
) {
/* Populate cache only on success; never from the fallback. */
ssl->recordSzOverhead = (word32)(recordSz - payloadSz);
}
}
else {
recordSz = payloadSz + RECORD_HEADER_SZ;
Expand Down
6 changes: 6 additions & 0 deletions src/keys.c
Original file line number Diff line number Diff line change
Expand Up @@ -3499,6 +3499,12 @@ int SetKeysSide(WOLFSSL* ssl, enum encrypt_side side)

(void)copy;

/* Cipher activation invalidates the cached AEAD record overhead. Covers
* TLS 1.2 / TLS 1.3 handshake completion, secure renegotiation, early
* data flips, and DTLS 1.3 epoch transitions (Dtls13SetEpochKeys() calls
* SetKeysSide() at the bottom). */
ssl->recordSzOverhead = 0;

#ifdef HAVE_SECURE_RENEGOTIATION
if (ssl->secure_renegotiation &&
ssl->secure_renegotiation->cache_status != SCR_CACHE_NULL) {
Expand Down
1 change: 1 addition & 0 deletions src/ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -10135,6 +10135,7 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out,
ssl->options.acceptState = ACCEPT_BEGIN;
ssl->options.handShakeState = NULL_STATE;
ssl->options.handShakeDone = 0;
ssl->recordSzOverhead = 0;
ssl->options.processReply = 0; /* doProcessInit */
ssl->options.havePeerVerify = 0;
ssl->options.havePeerCert = 0;
Expand Down
197 changes: 197 additions & 0 deletions tests/api/test_tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -1121,3 +1121,200 @@ int test_tls12_peerauth_failsafe(void)
#endif
return EXPECT_RESULT();
}

#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES)
/* Cipher-name substrings that need extra setup (PSK callback, ECDSA cert,
* SRP, etc.) which the default test_memio_setup() doesn't provide. */
static int record_size_skip_cipher(const char *name)
{
/* "ECDH-" matches static-ECDH ciphers ("ECDH-RSA-*", "ECDH-ECDSA-*")
* and not ECDHE-* because of the trailing '-'. */
static const char* const deny[] = {
"PSK", "SRP", "ANON", "NULL", "ECDSA", "ECDH-", "SM"
};
size_t i;
for (i = 0; i < XELEM_CNT(deny); i++) {
if (XSTRSTR(name, deny[i]) != NULL)
return 1;
}
return 0;
}

/* Cross-check wolfssl_local_GetRecordSize() against BuildMessage(sizeOnly=1)
* with the cache cold, then call it a second time and assert both calls
* return the same size — that exercises the cached path for AEAD ciphers
* without duplicating the BuildMessage arithmetic. */
static int record_size_check_ssl(WOLFSSL *ssl)
{
EXPECT_DECLS;
static const int payloads[] = { 1, 16, 256, 1300, 4096 };
size_t k;

for (k = 0; k < XELEM_CNT(payloads); k++) {
int payloadSz = payloads[k];
int expectedSz = BuildMessage(ssl, NULL, 0, NULL, payloadSz,
application_data, 0, 1, 0, CUR_ORDER);
int firstSz, secondSz;

ssl->recordSzOverhead = 0;
firstSz = wolfssl_local_GetRecordSize(ssl, payloadSz, 1);
secondSz = wolfssl_local_GetRecordSize(ssl, payloadSz, 1);
ExpectIntEQ(firstSz, expectedSz);
ExpectIntEQ(secondSz, expectedSz);
}
return EXPECT_RESULT();
}

/* Returns 1 if `suite` is selectable for the given client/server method
* pair, 0 otherwise. wolfSSL rejects some ciphers for DTLS at
* set_cipher_list time (e.g. RFC 7465 forbids RC4 in DTLS); skip those
* silently rather than failing the cross-check. */
static int record_size_cipher_selectable(method_provider client_method,
method_provider server_method, const char *suite)
{
WOLFSSL_CTX *ctx_c = wolfSSL_CTX_new(client_method());
WOLFSSL_CTX *ctx_s = wolfSSL_CTX_new(server_method());
int ok = (ctx_c != NULL && ctx_s != NULL &&
wolfSSL_CTX_set_cipher_list(ctx_c, suite) == WOLFSSL_SUCCESS &&
wolfSSL_CTX_set_cipher_list(ctx_s, suite) == WOLFSSL_SUCCESS);
if (ctx_c) wolfSSL_CTX_free(ctx_c);
if (ctx_s) wolfSSL_CTX_free(ctx_s);
return ok;
}

/* Run the cross-check on a memio pair using the given (de)multiplexing
* methods and cipher suite. Optionally enable DTLS-CID with peer CIDs of
* different sizes so the test covers CID-extended record framing. */
static int record_size_run_pair(method_provider client_method,
method_provider server_method, const char *suite, int useCid)
{
EXPECT_DECLS;
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
struct test_memio_ctx test_ctx;

(void)useCid;
if (!record_size_cipher_selectable(client_method, server_method, suite))
return TEST_SUCCESS; /* not valid for this protocol -- skip */

XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_ctx.c_ciphers = test_ctx.s_ciphers = suite;
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
client_method, server_method), 0);
#ifdef WOLFSSL_DTLS_CID
if (useCid) {
/* Different sizes on each side to exercise asymmetric framing. */
static unsigned char client_cid[] = { 1, 2, 3, 4, 5, 6 };
static unsigned char server_cid[] = { 7, 8, 9 };
ExpectIntEQ(wolfSSL_dtls_cid_use(ssl_c), 1);
ExpectIntEQ(wolfSSL_dtls_cid_set(ssl_c, server_cid,
sizeof(server_cid)), 1);
ExpectIntEQ(wolfSSL_dtls_cid_use(ssl_s), 1);
ExpectIntEQ(wolfSSL_dtls_cid_set(ssl_s, client_cid,
sizeof(client_cid)), 1);
}
#endif
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 30, NULL), 0);
ExpectIntEQ(record_size_check_ssl(ssl_c), TEST_SUCCESS);
ExpectIntEQ(record_size_check_ssl(ssl_s), TEST_SUCCESS);

wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
return EXPECT_RESULT();
}
#endif /* HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES */

int test_record_size_matches_build_message(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES)
const CipherSuiteInfo *suites = GetCipherNames();
int n = GetCipherNamesSize();
int i;

for (i = 0; i < n; i++) {
const char *name = suites[i].name;
/* Names prefixed "TLS13-" are TLS 1.3 suites regardless of
* cipherSuite0, which may be either TLS13_BYTE or ECC_BYTE (for
* the integrity-only TLS_SHA*_SHA* suites). */
int isTls13 = (XSTRNCMP(name, "TLS13-", 6) == 0);
if (record_size_skip_cipher(name))
continue;

if (isTls13) {
#ifdef WOLFSSL_TLS13
ExpectIntEQ(record_size_run_pair(wolfTLSv1_3_client_method,
wolfTLSv1_3_server_method, name, 0), TEST_SUCCESS);
#endif
#ifdef WOLFSSL_DTLS13
ExpectIntEQ(record_size_run_pair(wolfDTLSv1_3_client_method,
wolfDTLSv1_3_server_method, name, 0), TEST_SUCCESS);
#if defined(WOLFSSL_DTLS_CID)
ExpectIntEQ(record_size_run_pair(wolfDTLSv1_3_client_method,
wolfDTLSv1_3_server_method, name, 1), TEST_SUCCESS);
#endif
#endif
}
else {
#ifndef WOLFSSL_NO_TLS12
ExpectIntEQ(record_size_run_pair(wolfTLSv1_2_client_method,
wolfTLSv1_2_server_method, name, 0), TEST_SUCCESS);
#endif
#if defined(WOLFSSL_DTLS) && !defined(WOLFSSL_NO_TLS12)
ExpectIntEQ(record_size_run_pair(wolfDTLSv1_2_client_method,
wolfDTLSv1_2_server_method, name, 0), TEST_SUCCESS);
#if defined(WOLFSSL_DTLS_CID)
ExpectIntEQ(record_size_run_pair(wolfDTLSv1_2_client_method,
wolfDTLSv1_2_server_method, name, 1), TEST_SUCCESS);
#endif
#endif
}
}
#endif /* HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES */
return EXPECT_RESULT();
}

int test_record_size_cache_invalidated_on_renegotiation(void)
{
EXPECT_DECLS;
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
defined(HAVE_SECURE_RENEGOTIATION) && !defined(WOLFSSL_NO_TLS12) && \
defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
struct test_memio_ctx test_ctx;
byte readBuf[16];
int sz;

XMEMSET(&test_ctx, 0, sizeof(test_ctx));
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0);
ExpectIntEQ(wolfSSL_UseSecureRenegotiation(ssl_c), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_UseSecureRenegotiation(ssl_s), WOLFSSL_SUCCESS);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);

sz = wolfssl_local_GetRecordSize(ssl_c, 256, 1);
ExpectIntEQ(sz, BuildMessage(ssl_c, NULL, 0, NULL, 256,
application_data, 0, 1, 0, CUR_ORDER));
ExpectIntNE(ssl_c->recordSzOverhead, 0);

ExpectIntEQ(wolfSSL_Rehandshake(ssl_c), -1);
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1);
ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);

/* SetKeysSide() during renegotiation must have cleared the cache. */
Comment thread
julek-wolfssl marked this conversation as resolved.
sz = wolfssl_local_GetRecordSize(ssl_c, 256, 1);
ExpectIntEQ(sz, BuildMessage(ssl_c, NULL, 0, NULL, 256,
application_data, 0, 1, 0, CUR_ORDER));

wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}
7 changes: 6 additions & 1 deletion tests/api/test_tls.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ int test_tls12_etm_failed_resumption(void);
int test_tls_set_curves_list_ecc_fallback(void);
int test_tls12_corrupted_finished(void);
int test_tls12_peerauth_failsafe(void);
int test_record_size_matches_build_message(void);
int test_record_size_cache_invalidated_on_renegotiation(void);

#define TEST_TLS_DECLS \
TEST_DECL_GROUP("tls", test_utils_memio_move_message), \
Expand All @@ -49,6 +51,9 @@ int test_tls12_peerauth_failsafe(void);
TEST_DECL_GROUP("tls", test_tls12_etm_failed_resumption), \
TEST_DECL_GROUP("tls", test_tls_set_curves_list_ecc_fallback), \
TEST_DECL_GROUP("tls", test_tls12_corrupted_finished), \
TEST_DECL_GROUP("tls", test_tls12_peerauth_failsafe)
TEST_DECL_GROUP("tls", test_tls12_peerauth_failsafe), \
TEST_DECL_GROUP("tls", test_record_size_matches_build_message), \
TEST_DECL_GROUP("tls", \
test_record_size_cache_invalidated_on_renegotiation)

#endif /* TESTS_API_TEST_TLS_H */
12 changes: 8 additions & 4 deletions wolfssl/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -6602,6 +6602,10 @@ struct WOLFSSL {
#endif
#endif
#endif
/* Cached BuildMessage(sizeOnly) overhead (recordSz - payloadSz) for AEAD
* ciphers; 0 means uncached and is never a valid AEAD overhead. EtM does
* not apply to AEAD. */
word32 recordSzOverhead;
};

#if defined(WOLFSSL_SYS_CRYPTO_POLICY)
Expand Down Expand Up @@ -6876,7 +6880,7 @@ WOLFSSL_LOCAL int VerifyClientSuite(word16 havePSK, byte cipherSuite0,
byte cipherSuite);

WOLFSSL_LOCAL int SetTicket(WOLFSSL* ssl, const byte* ticket, word32 length);
WOLFSSL_LOCAL int wolfssl_local_GetRecordSize(WOLFSSL *ssl, int payloadSz,
WOLFSSL_TEST_VIS int wolfssl_local_GetRecordSize(WOLFSSL *ssl, int payloadSz,
int isEncrypted);
WOLFSSL_LOCAL int wolfssl_local_GetMaxPlaintextSize(WOLFSSL *ssl);
WOLFSSL_LOCAL int wolfSSL_GetMaxFragSize(WOLFSSL* ssl);
Expand Down Expand Up @@ -7167,8 +7171,8 @@ typedef struct CipherSuiteInfo {
byte flags;
} CipherSuiteInfo;

WOLFSSL_LOCAL const CipherSuiteInfo* GetCipherNames(void);
WOLFSSL_LOCAL int GetCipherNamesSize(void);
WOLFSSL_TEST_VIS const CipherSuiteInfo* GetCipherNames(void);
WOLFSSL_TEST_VIS int GetCipherNamesSize(void);
WOLFSSL_LOCAL const char* GetCipherNameInternal(byte cipherSuite0, byte cipherSuite);
#if defined(OPENSSL_ALL) || defined(WOLFSSL_QT)
/* used in wolfSSL_sk_CIPHER_description */
Expand Down Expand Up @@ -7248,7 +7252,7 @@ WOLFSSL_LOCAL int InitHandshakeHashesAndCopy(WOLFSSL* ssl, HS_Hashes* source,
#ifndef WOLFSSL_NO_TLS12
WOLFSSL_LOCAL void FreeBuildMsgArgs(WOLFSSL* ssl, BuildMsgArgs* args);
#endif
WOLFSSL_LOCAL int BuildMessage(WOLFSSL* ssl, byte* output, int outSz,
WOLFSSL_TEST_VIS int BuildMessage(WOLFSSL* ssl, byte* output, int outSz,
const byte* input, int inSz, int type, int hashOutput,
int sizeOnly, int asyncOkay, int epochOrder);

Expand Down
Loading