Skip to content

Commit 508ea15

Browse files
committed
Cache AEAD record overhead on WOLFSSL
wolfssl_local_GetRecordSize() runs BuildMessage(sizeOnly=1) on every wolfSSL_write hot path. For AEAD ciphers the overhead is constant per connection framing state, so cache (recordSz - payloadSz) on WOLFSSL and invalidate in SetKeysSide(), at cidInfo->tx assignment, and in wolfSSL_clear(). BuildMessage stays the single source of truth. Add test_record_size_matches_build_message (cross-checks every built cipher over TLS/DTLS +/- CID against BuildMessage) and test_record_size_cache_invalidated_on_renegotiation.
1 parent 58c41b6 commit 508ea15

7 files changed

Lines changed: 262 additions & 5 deletions

File tree

src/dtls.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,6 +1311,14 @@ int TLSX_ConnectionID_Parse(WOLFSSL* ssl, const byte* input, word16 length,
13111311
XMEMCPY(id->id, input + OPAQUE8_LEN, cidSz);
13121312
id->length = cidSz;
13131313
info->tx = id;
1314+
/* Invalidate the cached AEAD record overhead because the TX CID
1315+
* changes record framing. Today this only fires during the initial
1316+
* extension exchange (before the cache can be populated). When
1317+
* mid-connection CID change is added (DTLS 1.3), add a regression
1318+
* test that primes the cache, changes the CID, and re-asserts that
1319+
* wolfssl_local_GetRecordSize() agrees with BuildMessage(sizeOnly=1)
1320+
* after the change. */
1321+
ssl->recordSzOverhead = 0;
13141322
}
13151323

13161324
info->negotiated = 1;

src/internal.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42646,6 +42646,27 @@ int wolfssl_local_GetRecordSize(WOLFSSL *ssl, int payloadSz, int isEncrypted)
4264642646
return BAD_FUNC_ARG;
4264742647

4264842648
if (isEncrypted) {
42649+
/* AEAD overhead is constant per cache key (cipher, version, CID, DTLS
42650+
* 1.3 epoch); use the cached value when available. DTLS 1.3 pads
42651+
* records up to Dtls13MinimumRecordLength() (RFC 9147 5.5), so:
42652+
* - on read: only return the cached overhead when the resulting
42653+
* record would not be padded;
42654+
* - on populate: only store the overhead when BuildMessage returned
42655+
* a record strictly above the minimum, which guarantees no
42656+
* padding was applied. */
42657+
#ifdef WOLFSSL_DTLS13
42658+
int isDtls13 = ssl->options.dtls && ssl->options.tls1_3;
42659+
#endif
42660+
42661+
if (ssl->specs.cipher_type == aead && ssl->recordSzOverhead != 0
42662+
#ifdef WOLFSSL_DTLS13
42663+
&& (!isDtls13 || payloadSz + (int)ssl->recordSzOverhead
42664+
>= Dtls13MinimumRecordLength(ssl))
42665+
#endif
42666+
) {
42667+
return payloadSz + (int)ssl->recordSzOverhead;
42668+
}
42669+
4264942670
recordSz = BuildMessage(ssl, NULL, 0, NULL, payloadSz, application_data,
4265042671
0, 1, 0, CUR_ORDER);
4265142672
/* use a safe upper bound in case of error */
@@ -42656,6 +42677,14 @@ int wolfssl_local_GetRecordSize(WOLFSSL *ssl, int payloadSz, int isEncrypted)
4265642677
recordSz += DTLS_RECORD_EXTRA;
4265742678
}
4265842679
}
42680+
else if (ssl->specs.cipher_type == aead && recordSz > payloadSz
42681+
#ifdef WOLFSSL_DTLS13
42682+
&& (!isDtls13 || recordSz > Dtls13MinimumRecordLength(ssl))
42683+
#endif
42684+
) {
42685+
/* Populate cache only on success; never from the fallback. */
42686+
ssl->recordSzOverhead = (word32)(recordSz - payloadSz);
42687+
}
4265942688
}
4266042689
else {
4266142690
recordSz = payloadSz + RECORD_HEADER_SZ;

src/keys.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3499,6 +3499,12 @@ int SetKeysSide(WOLFSSL* ssl, enum encrypt_side side)
34993499

35003500
(void)copy;
35013501

3502+
/* Cipher activation invalidates the cached AEAD record overhead. Covers
3503+
* TLS 1.2 / TLS 1.3 handshake completion, secure renegotiation, early
3504+
* data flips, and DTLS 1.3 epoch transitions (Dtls13SetEpochKeys() calls
3505+
* SetKeysSide() at the bottom). */
3506+
ssl->recordSzOverhead = 0;
3507+
35023508
#ifdef HAVE_SECURE_RENEGOTIATION
35033509
if (ssl->secure_renegotiation &&
35043510
ssl->secure_renegotiation->cache_status != SCR_CACHE_NULL) {

src/ssl.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10135,6 +10135,7 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out,
1013510135
ssl->options.acceptState = ACCEPT_BEGIN;
1013610136
ssl->options.handShakeState = NULL_STATE;
1013710137
ssl->options.handShakeDone = 0;
10138+
ssl->recordSzOverhead = 0;
1013810139
ssl->options.processReply = 0; /* doProcessInit */
1013910140
ssl->options.havePeerVerify = 0;
1014010141
ssl->options.havePeerCert = 0;

tests/api/test_tls.c

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,3 +1121,200 @@ int test_tls12_peerauth_failsafe(void)
11211121
#endif
11221122
return EXPECT_RESULT();
11231123
}
1124+
1125+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES)
1126+
/* Cipher-name substrings that need extra setup (PSK callback, ECDSA cert,
1127+
* SRP, etc.) which the default test_memio_setup() doesn't provide. */
1128+
static int record_size_skip_cipher(const char *name)
1129+
{
1130+
/* "ECDH-" matches static-ECDH ciphers ("ECDH-RSA-*", "ECDH-ECDSA-*")
1131+
* and not ECDHE-* because of the trailing '-'. */
1132+
static const char* const deny[] = {
1133+
"PSK", "SRP", "ANON", "NULL", "ECDSA", "ECDH-", "SM"
1134+
};
1135+
size_t i;
1136+
for (i = 0; i < XELEM_CNT(deny); i++) {
1137+
if (XSTRSTR(name, deny[i]) != NULL)
1138+
return 1;
1139+
}
1140+
return 0;
1141+
}
1142+
1143+
/* Cross-check wolfssl_local_GetRecordSize() against BuildMessage(sizeOnly=1)
1144+
* with the cache cold, then call it a second time and assert both calls
1145+
* return the same size — that exercises the cached path for AEAD ciphers
1146+
* without duplicating the BuildMessage arithmetic. */
1147+
static int record_size_check_ssl(WOLFSSL *ssl)
1148+
{
1149+
EXPECT_DECLS;
1150+
static const int payloads[] = { 1, 16, 256, 1300, 4096 };
1151+
size_t k;
1152+
1153+
for (k = 0; k < XELEM_CNT(payloads); k++) {
1154+
int payloadSz = payloads[k];
1155+
int expectedSz = BuildMessage(ssl, NULL, 0, NULL, payloadSz,
1156+
application_data, 0, 1, 0, CUR_ORDER);
1157+
int firstSz, secondSz;
1158+
1159+
ssl->recordSzOverhead = 0;
1160+
firstSz = wolfssl_local_GetRecordSize(ssl, payloadSz, 1);
1161+
secondSz = wolfssl_local_GetRecordSize(ssl, payloadSz, 1);
1162+
ExpectIntEQ(firstSz, expectedSz);
1163+
ExpectIntEQ(secondSz, expectedSz);
1164+
}
1165+
return EXPECT_RESULT();
1166+
}
1167+
1168+
/* Returns 1 if `suite` is selectable for the given client/server method
1169+
* pair, 0 otherwise. wolfSSL rejects some ciphers for DTLS at
1170+
* set_cipher_list time (e.g. RFC 7465 forbids RC4 in DTLS); skip those
1171+
* silently rather than failing the cross-check. */
1172+
static int record_size_cipher_selectable(method_provider client_method,
1173+
method_provider server_method, const char *suite)
1174+
{
1175+
WOLFSSL_CTX *ctx_c = wolfSSL_CTX_new(client_method());
1176+
WOLFSSL_CTX *ctx_s = wolfSSL_CTX_new(server_method());
1177+
int ok = (ctx_c != NULL && ctx_s != NULL &&
1178+
wolfSSL_CTX_set_cipher_list(ctx_c, suite) == WOLFSSL_SUCCESS &&
1179+
wolfSSL_CTX_set_cipher_list(ctx_s, suite) == WOLFSSL_SUCCESS);
1180+
if (ctx_c) wolfSSL_CTX_free(ctx_c);
1181+
if (ctx_s) wolfSSL_CTX_free(ctx_s);
1182+
return ok;
1183+
}
1184+
1185+
/* Run the cross-check on a memio pair using the given (de)multiplexing
1186+
* methods and cipher suite. Optionally enable DTLS-CID with peer CIDs of
1187+
* different sizes so the test covers CID-extended record framing. */
1188+
static int record_size_run_pair(method_provider client_method,
1189+
method_provider server_method, const char *suite, int useCid)
1190+
{
1191+
EXPECT_DECLS;
1192+
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
1193+
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
1194+
struct test_memio_ctx test_ctx;
1195+
1196+
(void)useCid;
1197+
if (!record_size_cipher_selectable(client_method, server_method, suite))
1198+
return TEST_SUCCESS; /* not valid for this protocol -- skip */
1199+
1200+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
1201+
test_ctx.c_ciphers = test_ctx.s_ciphers = suite;
1202+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
1203+
client_method, server_method), 0);
1204+
#ifdef WOLFSSL_DTLS_CID
1205+
if (useCid) {
1206+
/* Different sizes on each side to exercise asymmetric framing. */
1207+
static unsigned char client_cid[] = { 1, 2, 3, 4, 5, 6 };
1208+
static unsigned char server_cid[] = { 7, 8, 9 };
1209+
ExpectIntEQ(wolfSSL_dtls_cid_use(ssl_c), 1);
1210+
ExpectIntEQ(wolfSSL_dtls_cid_set(ssl_c, server_cid,
1211+
sizeof(server_cid)), 1);
1212+
ExpectIntEQ(wolfSSL_dtls_cid_use(ssl_s), 1);
1213+
ExpectIntEQ(wolfSSL_dtls_cid_set(ssl_s, client_cid,
1214+
sizeof(client_cid)), 1);
1215+
}
1216+
#endif
1217+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 30, NULL), 0);
1218+
ExpectIntEQ(record_size_check_ssl(ssl_c), TEST_SUCCESS);
1219+
ExpectIntEQ(record_size_check_ssl(ssl_s), TEST_SUCCESS);
1220+
1221+
wolfSSL_free(ssl_c);
1222+
wolfSSL_free(ssl_s);
1223+
wolfSSL_CTX_free(ctx_c);
1224+
wolfSSL_CTX_free(ctx_s);
1225+
return EXPECT_RESULT();
1226+
}
1227+
#endif /* HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES */
1228+
1229+
int test_record_size_matches_build_message(void)
1230+
{
1231+
EXPECT_DECLS;
1232+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES)
1233+
const CipherSuiteInfo *suites = GetCipherNames();
1234+
int n = GetCipherNamesSize();
1235+
int i;
1236+
1237+
for (i = 0; i < n; i++) {
1238+
const char *name = suites[i].name;
1239+
/* Names prefixed "TLS13-" are TLS 1.3 suites regardless of
1240+
* cipherSuite0, which may be either TLS13_BYTE or ECC_BYTE (for
1241+
* the integrity-only TLS_SHA*_SHA* suites). */
1242+
int isTls13 = (XSTRNCMP(name, "TLS13-", 6) == 0);
1243+
if (record_size_skip_cipher(name))
1244+
continue;
1245+
1246+
if (isTls13) {
1247+
#ifdef WOLFSSL_TLS13
1248+
ExpectIntEQ(record_size_run_pair(wolfTLSv1_3_client_method,
1249+
wolfTLSv1_3_server_method, name, 0), TEST_SUCCESS);
1250+
#endif
1251+
#ifdef WOLFSSL_DTLS13
1252+
ExpectIntEQ(record_size_run_pair(wolfDTLSv1_3_client_method,
1253+
wolfDTLSv1_3_server_method, name, 0), TEST_SUCCESS);
1254+
#if defined(WOLFSSL_DTLS_CID)
1255+
ExpectIntEQ(record_size_run_pair(wolfDTLSv1_3_client_method,
1256+
wolfDTLSv1_3_server_method, name, 1), TEST_SUCCESS);
1257+
#endif
1258+
#endif
1259+
}
1260+
else {
1261+
#ifndef WOLFSSL_NO_TLS12
1262+
ExpectIntEQ(record_size_run_pair(wolfTLSv1_2_client_method,
1263+
wolfTLSv1_2_server_method, name, 0), TEST_SUCCESS);
1264+
#endif
1265+
#if defined(WOLFSSL_DTLS) && !defined(WOLFSSL_NO_TLS12)
1266+
ExpectIntEQ(record_size_run_pair(wolfDTLSv1_2_client_method,
1267+
wolfDTLSv1_2_server_method, name, 0), TEST_SUCCESS);
1268+
#if defined(WOLFSSL_DTLS_CID)
1269+
ExpectIntEQ(record_size_run_pair(wolfDTLSv1_2_client_method,
1270+
wolfDTLSv1_2_server_method, name, 1), TEST_SUCCESS);
1271+
#endif
1272+
#endif
1273+
}
1274+
}
1275+
#endif /* HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES */
1276+
return EXPECT_RESULT();
1277+
}
1278+
1279+
int test_record_size_cache_invalidated_on_renegotiation(void)
1280+
{
1281+
EXPECT_DECLS;
1282+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
1283+
defined(HAVE_SECURE_RENEGOTIATION) && !defined(WOLFSSL_NO_TLS12) && \
1284+
defined(BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
1285+
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
1286+
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
1287+
struct test_memio_ctx test_ctx;
1288+
byte readBuf[16];
1289+
int sz;
1290+
1291+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
1292+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
1293+
wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0);
1294+
ExpectIntEQ(wolfSSL_UseSecureRenegotiation(ssl_c), WOLFSSL_SUCCESS);
1295+
ExpectIntEQ(wolfSSL_UseSecureRenegotiation(ssl_s), WOLFSSL_SUCCESS);
1296+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
1297+
1298+
sz = wolfssl_local_GetRecordSize(ssl_c, 256, 1);
1299+
ExpectIntEQ(sz, BuildMessage(ssl_c, NULL, 0, NULL, 256,
1300+
application_data, 0, 1, 0, CUR_ORDER));
1301+
ExpectIntNE(ssl_c->recordSzOverhead, 0);
1302+
1303+
ExpectIntEQ(wolfSSL_Rehandshake(ssl_c), -1);
1304+
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
1305+
ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1);
1306+
ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ);
1307+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
1308+
1309+
/* SetKeysSide() during renegotiation must have cleared the cache. */
1310+
sz = wolfssl_local_GetRecordSize(ssl_c, 256, 1);
1311+
ExpectIntEQ(sz, BuildMessage(ssl_c, NULL, 0, NULL, 256,
1312+
application_data, 0, 1, 0, CUR_ORDER));
1313+
1314+
wolfSSL_free(ssl_c);
1315+
wolfSSL_free(ssl_s);
1316+
wolfSSL_CTX_free(ctx_c);
1317+
wolfSSL_CTX_free(ctx_s);
1318+
#endif
1319+
return EXPECT_RESULT();
1320+
}

tests/api/test_tls.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ int test_tls12_etm_failed_resumption(void);
3535
int test_tls_set_curves_list_ecc_fallback(void);
3636
int test_tls12_corrupted_finished(void);
3737
int test_tls12_peerauth_failsafe(void);
38+
int test_record_size_matches_build_message(void);
39+
int test_record_size_cache_invalidated_on_renegotiation(void);
3840

3941
#define TEST_TLS_DECLS \
4042
TEST_DECL_GROUP("tls", test_utils_memio_move_message), \
@@ -49,6 +51,9 @@ int test_tls12_peerauth_failsafe(void);
4951
TEST_DECL_GROUP("tls", test_tls12_etm_failed_resumption), \
5052
TEST_DECL_GROUP("tls", test_tls_set_curves_list_ecc_fallback), \
5153
TEST_DECL_GROUP("tls", test_tls12_corrupted_finished), \
52-
TEST_DECL_GROUP("tls", test_tls12_peerauth_failsafe)
54+
TEST_DECL_GROUP("tls", test_tls12_peerauth_failsafe), \
55+
TEST_DECL_GROUP("tls", test_record_size_matches_build_message), \
56+
TEST_DECL_GROUP("tls", \
57+
test_record_size_cache_invalidated_on_renegotiation)
5358

5459
#endif /* TESTS_API_TEST_TLS_H */

wolfssl/internal.h

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6602,6 +6602,10 @@ struct WOLFSSL {
66026602
#endif
66036603
#endif
66046604
#endif
6605+
/* Cached BuildMessage(sizeOnly) overhead (recordSz - payloadSz) for AEAD
6606+
* ciphers; 0 means uncached and is never a valid AEAD overhead. EtM does
6607+
* not apply to AEAD. */
6608+
word32 recordSzOverhead;
66056609
};
66066610

66076611
#if defined(WOLFSSL_SYS_CRYPTO_POLICY)
@@ -6876,7 +6880,7 @@ WOLFSSL_LOCAL int VerifyClientSuite(word16 havePSK, byte cipherSuite0,
68766880
byte cipherSuite);
68776881

68786882
WOLFSSL_LOCAL int SetTicket(WOLFSSL* ssl, const byte* ticket, word32 length);
6879-
WOLFSSL_LOCAL int wolfssl_local_GetRecordSize(WOLFSSL *ssl, int payloadSz,
6883+
WOLFSSL_TEST_VIS int wolfssl_local_GetRecordSize(WOLFSSL *ssl, int payloadSz,
68806884
int isEncrypted);
68816885
WOLFSSL_LOCAL int wolfssl_local_GetMaxPlaintextSize(WOLFSSL *ssl);
68826886
WOLFSSL_LOCAL int wolfSSL_GetMaxFragSize(WOLFSSL* ssl);
@@ -7167,8 +7171,12 @@ typedef struct CipherSuiteInfo {
71677171
byte flags;
71687172
} CipherSuiteInfo;
71697173

7170-
WOLFSSL_LOCAL const CipherSuiteInfo* GetCipherNames(void);
7171-
WOLFSSL_LOCAL int GetCipherNamesSize(void);
7174+
#ifdef WOLFSSL_API_PREFIX_MAP
7175+
#define GetCipherNames wolfSSL_GetCipherNames
7176+
#define GetCipherNamesSize wolfSSL_GetCipherNamesSize
7177+
#endif
7178+
WOLFSSL_TEST_VIS const CipherSuiteInfo* GetCipherNames(void);
7179+
WOLFSSL_TEST_VIS int GetCipherNamesSize(void);
71727180
WOLFSSL_LOCAL const char* GetCipherNameInternal(byte cipherSuite0, byte cipherSuite);
71737181
#if defined(OPENSSL_ALL) || defined(WOLFSSL_QT)
71747182
/* used in wolfSSL_sk_CIPHER_description */
@@ -7248,7 +7256,10 @@ WOLFSSL_LOCAL int InitHandshakeHashesAndCopy(WOLFSSL* ssl, HS_Hashes* source,
72487256
#ifndef WOLFSSL_NO_TLS12
72497257
WOLFSSL_LOCAL void FreeBuildMsgArgs(WOLFSSL* ssl, BuildMsgArgs* args);
72507258
#endif
7251-
WOLFSSL_LOCAL int BuildMessage(WOLFSSL* ssl, byte* output, int outSz,
7259+
#ifdef WOLFSSL_API_PREFIX_MAP
7260+
#define BuildMessage wolfSSL_BuildMessage
7261+
#endif
7262+
WOLFSSL_TEST_VIS int BuildMessage(WOLFSSL* ssl, byte* output, int outSz,
72527263
const byte* input, int inSz, int type, int hashOutput,
72537264
int sizeOnly, int asyncOkay, int epochOrder);
72547265

0 commit comments

Comments
 (0)