From 1f75deb7678c8743c27e7165a07ab4a4707c0bb6 Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Fri, 10 Apr 2026 08:50:28 -0500 Subject: [PATCH 1/5] Fix TLSX_Parse to check dup ECH --- src/tls.c | 3 ++ tests/api/test_tls13.c | 102 +++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 2 + 3 files changed, 107 insertions(+) diff --git a/src/tls.c b/src/tls.c index 18aad71d39..99863d7aa0 100644 --- a/src/tls.c +++ b/src/tls.c @@ -17047,6 +17047,9 @@ int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, byte msgType, #ifdef WOLFSSL_QUIC || (type == TLSX_KEY_QUIC_TP_PARAMS_DRAFT) #endif + #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + || (type == TLSX_ECH) + #endif #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_DUAL_ALG_CERTS) || (type == TLSX_CKS) #endif diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 63ddffb7aa..4915537e13 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -2922,6 +2922,108 @@ int test_tls13_duplicate_extension(void) } +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ + (!defined(NO_RSA) || defined(HAVE_ECC)) +static int DupEchSend(WOLFSSL* ssl, char* buf, int sz, void* ctx) +{ + (void)ssl; + (void)buf; + (void)sz; + (void)ctx; + + return sz; +} +static int DupEchRecv(WOLFSSL* ssl, char* buf, int sz, void* ctx) +{ + WOLFSSL_BUFFER_INFO* msg = (WOLFSSL_BUFFER_INFO*)ctx; + int len = (int)msg->length; + + (void)ssl; + (void)sz; + + if (len > sz) + len = sz; + XMEMCPY(buf, msg->buffer, len); + msg->buffer += len; + msg->length -= len; + + return len; +} +#endif + +/* Test detection of duplicate ECH extension (type 0xfe0d) in ClientHello. + * ECH has a semaphore mapping in TLSX_ToSemaphore() and needs to be included + * in the duplicate-detection gate in TLSX_Parse(). RFC 8446 section 4.2 + * requires rejecting messages with duplicate extensions. + */ +int test_tls13_duplicate_ech_extension(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ + (!defined(NO_RSA) || defined(HAVE_ECC)) + /* TLS 1.3 ClientHello with two ECH extensions (type 0xfe0d). + * Extensions block contains: supported_versions + ECH + ECH (dup). */ + const unsigned char clientHelloDupEch[] = { + 0x16, 0x03, 0x03, 0x00, 0x40, 0x01, 0x00, 0x00, + 0x3c, 0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x02, 0x13, 0x01, + 0x01, 0x00, 0x00, 0x11, 0x00, 0x2b, 0x00, 0x03, + 0x02, 0x03, 0x04, 0xfe, 0x0d, 0x00, 0x01, 0x00, + 0xfe, 0x0d, 0x00, 0x01, 0x00 + }; + WOLFSSL_BUFFER_INFO msg; + const char* testCertFile; + const char* testKeyFile; + WOLFSSL_CTX *ctx = NULL; + WOLFSSL *ssl = NULL; + +#ifndef NO_RSA + testCertFile = svrCertFile; + testKeyFile = svrKeyFile; +#elif defined(HAVE_ECC) + testCertFile = eccCertFile; + testKeyFile = eccKeyFile; +#endif + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + + ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx, testCertFile, + CERT_FILETYPE)); + ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx, testKeyFile, + CERT_FILETYPE)); + + /* Read from 'msg'. */ + wolfSSL_SetIORecv(ctx, DupEchRecv); + /* No where to send to - dummy sender. */ + wolfSSL_SetIOSend(ctx, DupEchSend); + + ssl = wolfSSL_new(ctx); + ExpectNotNull(ssl); + + msg.buffer = (unsigned char*)clientHelloDupEch; + msg.length = (unsigned int)sizeof(clientHelloDupEch); + wolfSSL_SetIOReadCtx(ssl, &msg); + + ExpectIntNE(wolfSSL_accept(ssl), WOLFSSL_SUCCESS); + /* Can return duplicate ext error or socket error if the peer closed + * down while sending alert. */ + if (wolfSSL_get_error(ssl, 0) != WC_NO_ERR_TRACE(SOCKET_ERROR_E)) { + ExpectIntEQ(wolfSSL_get_error(ssl, 0), + WC_NO_ERR_TRACE(DUPLICATE_TLS_EXT_E)); + } + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + + int test_key_share_mismatch(void) { EXPECT_DECLS; diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index c8eaa3b7fa..c20711539a 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -36,6 +36,7 @@ int test_tls13_ch2_different_cs(void); int test_tls13_sg_missing(void); int test_tls13_ks_missing(void); int test_tls13_duplicate_extension(void); +int test_tls13_duplicate_ech_extension(void); int test_key_share_mismatch(void); int test_tls13_middlebox_compat_empty_session_id(void); int test_tls13_plaintext_alert(void); @@ -58,6 +59,7 @@ int test_tls13_short_session_ticket(void); TEST_DECL_GROUP("tls13", test_tls13_sg_missing), \ TEST_DECL_GROUP("tls13", test_tls13_ks_missing), \ TEST_DECL_GROUP("tls13", test_tls13_duplicate_extension), \ + TEST_DECL_GROUP("tls13", test_tls13_duplicate_ech_extension), \ TEST_DECL_GROUP("tls13", test_key_share_mismatch), \ TEST_DECL_GROUP("tls13", test_tls13_middlebox_compat_empty_session_id), \ TEST_DECL_GROUP("tls13", test_tls13_plaintext_alert), \ From d2a8735abceae05ec6cc278415d29040c0de6b4e Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Fri, 10 Apr 2026 10:03:00 -0500 Subject: [PATCH 2/5] Add WOLFSSL_MAX_EMPTY_RECORDS --- src/internal.c | 20 +++++ tests/api/test_tls13.c | 150 +++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 2 + wolfssl/error-ssl.h | 4 +- wolfssl/internal.h | 1 + wolfssl/wolfcrypt/settings.h | 4 + 6 files changed, 180 insertions(+), 1 deletion(-) diff --git a/src/internal.c b/src/internal.c index 267c75a5d6..53fb2ee052 100644 --- a/src/internal.c +++ b/src/internal.c @@ -21862,6 +21862,23 @@ int DoApplicationData(WOLFSSL* ssl, byte* input, word32* inOutIdx, int sniff) } #endif + /* Rate-limit empty application data records to prevent DoS */ + if (dataSz == 0) { + if (++ssl->options.emptyRecordCount >= WOLFSSL_MAX_EMPTY_RECORDS) { + WOLFSSL_MSG("Too many empty records"); +#ifdef WOLFSSL_EXTRA_ALERTS + if (sniff == NO_SNIFF) { + SendAlert(ssl, alert_fatal, unexpected_message); + } +#endif + WOLFSSL_ERROR_VERBOSE(EMPTY_RECORD_LIMIT_E); + return EMPTY_RECORD_LIMIT_E; + } + } + else { + ssl->options.emptyRecordCount = 0; + } + /* read data */ if (dataSz) { int rawSz = dataSz; /* keep raw size for idx adjustment */ @@ -27573,6 +27590,9 @@ const char* wolfSSL_ERR_reason_error_string(unsigned long e) case ALERT_COUNT_E: return "Alert Count exceeded error"; + case EMPTY_RECORD_LIMIT_E: + return "Too many empty records error"; + case EXT_MISSING: return "Required TLS extension missing"; diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 4915537e13..1a2ec9a51c 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -3687,6 +3687,156 @@ int test_tls13_pqc_hybrid_truncated_keyshare(void) * (32 bytes) does not cause an unsigned integer underflow / OOB read in * SetTicket. Uses a full memio handshake, then injects a crafted * NewSessionTicket with a 5-byte ticket into the client's read path. */ +int test_tls13_empty_record_limit(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + int recSz; + int numRecs = WOLFSSL_MAX_EMPTY_RECORDS + 1; + byte rec[128]; /* buffer for one encrypted record */ + byte *allRecs = NULL; + int i; + char buf[64]; + + /* Test 1: Exceeding the empty record limit returns an error. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Consume any post-handshake messages (e.g. NewSessionTicket). */ + wolfSSL_read(ssl_c, buf, sizeof(buf)); + test_memio_clear_buffer(&test_ctx, 0); + test_memio_clear_buffer(&test_ctx, 1); + + /* Get the size of an encrypted zero-length app data record. */ + recSz = BuildTls13Message(ssl_c, NULL, 0, NULL, 0, + application_data, 0, 1, 0); + ExpectIntGT(recSz, 0); + ExpectIntLE(recSz, (int)sizeof(rec)); + + /* Build all empty records into one contiguous buffer. */ + if (EXPECT_SUCCESS()) { + allRecs = (byte*)XMALLOC((size_t)(recSz * numRecs), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(allRecs); + } + + for (i = 0; i < numRecs && EXPECT_SUCCESS(); i++) { + XMEMSET(rec, 0, sizeof(rec)); + ExpectIntEQ(BuildTls13Message(ssl_c, rec, (int)sizeof(rec), rec + + RECORD_HEADER_SZ, 0, application_data, 0, 0, 0), + recSz); + XMEMCPY(allRecs + i * recSz, rec, (size_t)recSz); + } + + /* Inject all records as a single message. */ + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)allRecs, + recSz * numRecs), 0); + + /* The server's wolfSSL_read should fail with EMPTY_RECORD_LIMIT_E. */ + ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf)), + WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(EMPTY_RECORD_LIMIT_E)); + + XFREE(allRecs, NULL, DYNAMIC_TYPE_TMP_BUFFER); + allRecs = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + + /* Test 2: Counter resets on non-empty record. + * Send (limit - 1) empty records, then 1 non-empty, then (limit - 1) + * more empty records. Should succeed without hitting the limit. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_read(ssl_c, buf, sizeof(buf)); + test_memio_clear_buffer(&test_ctx, 0); + test_memio_clear_buffer(&test_ctx, 1); + + recSz = BuildTls13Message(ssl_c, NULL, 0, NULL, 0, + application_data, 0, 1, 0); + ExpectIntGT(recSz, 0); + + { + int emptyBefore = WOLFSSL_MAX_EMPTY_RECORDS - 1; + int emptyAfter = WOLFSSL_MAX_EMPTY_RECORDS - 1; + int dataRecSz; + byte dataRec[128]; + byte payload[1] = { 'a' }; + int totalSz; + + dataRecSz = BuildTls13Message(ssl_c, NULL, 0, NULL, 1, + application_data, 0, 1, 0); + ExpectIntGT(dataRecSz, 0); + + totalSz = recSz * (emptyBefore + emptyAfter) + dataRecSz; + if (EXPECT_SUCCESS()) { + allRecs = (byte*)XMALLOC((size_t)totalSz, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(allRecs); + } + + /* Build (limit - 1) empty records */ + for (i = 0; i < emptyBefore && EXPECT_SUCCESS(); i++) { + XMEMSET(rec, 0, sizeof(rec)); + ExpectIntEQ(BuildTls13Message(ssl_c, rec, (int)sizeof(rec), + rec + RECORD_HEADER_SZ, 0, application_data, + 0, 0, 0), recSz); + XMEMCPY(allRecs + i * recSz, rec, (size_t)recSz); + } + + /* Build 1 non-empty record */ + XMEMSET(dataRec, 0, sizeof(dataRec)); + XMEMCPY(dataRec + RECORD_HEADER_SZ, payload, sizeof(payload)); + ExpectIntEQ(BuildTls13Message(ssl_c, dataRec, (int)sizeof(dataRec), + dataRec + RECORD_HEADER_SZ, 1, application_data, + 0, 0, 0), dataRecSz); + XMEMCPY(allRecs + emptyBefore * recSz, dataRec, (size_t)dataRecSz); + + /* Build (limit - 1) more empty records */ + for (i = 0; i < emptyAfter && EXPECT_SUCCESS(); i++) { + XMEMSET(rec, 0, sizeof(rec)); + ExpectIntEQ(BuildTls13Message(ssl_c, rec, (int)sizeof(rec), + rec + RECORD_HEADER_SZ, 0, application_data, + 0, 0, 0), recSz); + XMEMCPY(allRecs + emptyBefore * recSz + dataRecSz + i * recSz, + rec, (size_t)recSz); + } + + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, + (const char*)allRecs, totalSz), 0); + } + + /* wolfSSL_read should return the 1-byte payload. The counter resets + * on the non-empty record so neither batch of (limit - 1) empties + * triggers the error. */ + ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf)), 1); + ExpectIntEQ(buf[0], 'a'); + + XFREE(allRecs, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + int test_tls13_short_session_ticket(void) { EXPECT_DECLS; diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index c20711539a..9fef422b63 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -44,6 +44,7 @@ int test_tls13_warning_alert_is_fatal(void); int test_tls13_cert_req_sigalgs(void); int test_tls13_derive_keys_no_key(void); int test_tls13_pqc_hybrid_truncated_keyshare(void); +int test_tls13_empty_record_limit(void); int test_tls13_short_session_ticket(void); #define TEST_TLS13_DECLS \ @@ -67,6 +68,7 @@ int test_tls13_short_session_ticket(void); TEST_DECL_GROUP("tls13", test_tls13_cert_req_sigalgs), \ TEST_DECL_GROUP("tls13", test_tls13_derive_keys_no_key), \ TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_truncated_keyshare), \ + TEST_DECL_GROUP("tls13", test_tls13_empty_record_limit), \ TEST_DECL_GROUP("tls13", test_tls13_short_session_ticket) #endif /* WOLFCRYPT_TEST_TLS13_H */ diff --git a/wolfssl/error-ssl.h b/wolfssl/error-ssl.h index 832ae9f440..4559a41f39 100644 --- a/wolfssl/error-ssl.h +++ b/wolfssl/error-ssl.h @@ -240,7 +240,9 @@ enum wolfSSL_ErrorCodes { SESSION_TICKET_NONCE_OVERFLOW = -517, /* Session ticket nonce overflow */ - WOLFSSL_LAST_E = -517 + EMPTY_RECORD_LIMIT_E = -518, /* Too many empty records received */ + + WOLFSSL_LAST_E = -518 /* codes -1000 to -1999 are reserved for wolfCrypt. */ }; diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 5e512f76eb..4d72477df8 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -5218,6 +5218,7 @@ struct Options { byte asyncState; /* sub-state for enum asyncState */ byte buildMsgState; /* sub-state for enum buildMsgState */ byte alertCount; /* detect warning dos attempt */ + byte emptyRecordCount; /* detect empty record dos attempt */ #ifdef WOLFSSL_MULTICAST word16 mcastID; /* Multicast group ID */ #endif diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index b7552d1e52..b7cc37e3b4 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -4144,6 +4144,10 @@ extern void uITRON4_free(void *p) ; #define WOLFSSL_ALERT_COUNT_MAX 5 #endif +#ifndef WOLFSSL_MAX_EMPTY_RECORDS + #define WOLFSSL_MAX_EMPTY_RECORDS 32 +#endif + /* Enable blinding by default for C-only, non-small curve25519 implementation */ #if defined(HAVE_CURVE25519) && !defined(CURVE25519_SMALL) && \ !defined(FREESCALE_LTC_ECC) && !defined(WOLFSSL_ARMASM) && \ From d6fa2b1a6f2094c705cc9456618f91f20301114f Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Fri, 10 Apr 2026 10:31:35 -0500 Subject: [PATCH 3/5] Fix ParseCertRelative to check pathlen on self-issued certs --- tests/api.c | 222 ++++++++++++++++++++++++++++++++++++++++++++ wolfcrypt/src/asn.c | 49 ++++++++-- 2 files changed, 261 insertions(+), 10 deletions(-) diff --git a/tests/api.c b/tests/api.c index 0d40dbb636..998d86dcc0 100644 --- a/tests/api.c +++ b/tests/api.c @@ -21402,6 +21402,226 @@ static int test_MakeCertWithPathLen(void) return EXPECT_RESULT(); } +static int test_PathLenSelfIssued(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_CERT_REQ) && !defined(NO_ASN_TIME) && \ + defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && \ + defined(WOLFSSL_CERT_EXT) && !defined(NO_CERTS) && \ + (!defined(NO_WOLFSSL_CLIENT) || !defined(WOLFSSL_NO_CLIENT_AUTH)) + Cert cert; + DecodedCert decodedCert; + byte rootDer[FOURK_BUF]; + byte icaDer[FOURK_BUF]; + byte entityDer[FOURK_BUF]; + int rootDerSz = 0; + int icaDerSz = 0; + int entityDerSz = 0; + WC_RNG rng; + ecc_key rootKey; + ecc_key icaKey; + ecc_key entityKey; + WOLFSSL_CERT_MANAGER* cm = NULL; + + XMEMSET(&rng, 0, sizeof(WC_RNG)); + XMEMSET(&rootKey, 0, sizeof(ecc_key)); + XMEMSET(&icaKey, 0, sizeof(ecc_key)); + XMEMSET(&entityKey, 0, sizeof(ecc_key)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_ecc_init(&rootKey), 0); + ExpectIntEQ(wc_ecc_init(&icaKey), 0); + ExpectIntEQ(wc_ecc_init(&entityKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, 32, &rootKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, 32, &icaKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, 32, &entityKey), 0); + + /* Step 1: Create root CA with pathLen=0 */ + ExpectIntEQ(wc_InitCert(&cert), 0); + (void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.state, "MT", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.locality, "Bozeman", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.org, "TestCA", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.unit, "Test", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.commonName, "TestRootCA", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.email, "root@test.com", CTC_NAME_SIZE); + cert.selfSigned = 1; + cert.isCA = 1; + cert.pathLen = 0; + cert.pathLenSet = 1; + cert.sigType = CTC_SHA256wECDSA; + cert.keyUsage = KEYUSE_KEY_CERT_SIGN | KEYUSE_CRL_SIGN; + ExpectIntEQ(wc_SetSubjectKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &rootKey), + 0); + + ExpectIntGE(wc_MakeCert(&cert, rootDer, FOURK_BUF, NULL, &rootKey, &rng), + 0); + ExpectIntGE(rootDerSz = wc_SignCert(cert.bodySz, cert.sigType, rootDer, + FOURK_BUF, NULL, &rootKey, &rng), 0); + + /* Step 2: Create self-issued intermediate (same subject DN as root, + * different key, signed by root) - this should be blocked by pathLen=0 */ + ExpectIntEQ(wc_InitCert(&cert), 0); + cert.selfSigned = 0; + cert.isCA = 1; + cert.sigType = CTC_SHA256wECDSA; + cert.keyUsage = KEYUSE_KEY_CERT_SIGN | KEYUSE_CRL_SIGN; + /* Set both subject and issuer from the root cert so they match */ + ExpectIntEQ(wc_SetSubjectBuffer(&cert, rootDer, rootDerSz), 0); + ExpectIntEQ(wc_SetIssuerBuffer(&cert, rootDer, rootDerSz), 0); + ExpectIntEQ(wc_SetAuthKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &rootKey), 0); + ExpectIntEQ(wc_SetSubjectKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &icaKey), + 0); + + ExpectIntGE(wc_MakeCert(&cert, icaDer, FOURK_BUF, NULL, &icaKey, &rng), 0); + ExpectIntGE(icaDerSz = wc_SignCert(cert.bodySz, cert.sigType, icaDer, + FOURK_BUF, NULL, &rootKey, &rng), 0); + + /* Step 3: Create entity cert signed by the intermediate */ + ExpectIntEQ(wc_InitCert(&cert), 0); + cert.selfSigned = 0; + cert.isCA = 0; + cert.sigType = CTC_SHA256wECDSA; + (void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.state, "MT", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.locality, "Bozeman", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.org, "TestEntity", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.commonName, "entity.test", CTC_NAME_SIZE); + ExpectIntEQ(wc_SetIssuerBuffer(&cert, icaDer, icaDerSz), 0); + ExpectIntEQ(wc_SetAuthKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &icaKey), 0); + + ExpectIntGE(wc_MakeCert(&cert, entityDer, FOURK_BUF, NULL, &entityKey, + &rng), 0); + ExpectIntGE(entityDerSz = wc_SignCert(cert.bodySz, cert.sigType, entityDer, + FOURK_BUF, NULL, &icaKey, &rng), 0); + + /* Step 4: Load root CA into cert manager */ + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, rootDer, rootDerSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + + /* Step 5: Parse the self-issued intermediate as a chain cert. + * This simulates TLS chain verification where the intermediate is + * received as part of the certificate chain. + * Root CA has pathLen=0, so it should NOT be allowed to sign any + * intermediate CA (including self-issued ones). + * BUG: wolfSSL sets selfSigned=1 for this cert (issuer==subject DN), + * which causes the pathLen enforcement to be entirely skipped. */ + wc_InitDecodedCert(&decodedCert, icaDer, (word32)icaDerSz, NULL); + ExpectIntEQ(wc_ParseCert(&decodedCert, CHAIN_CERT_TYPE, VERIFY, + cm), WC_NO_ERR_TRACE(ASN_PATHLEN_INV_E)); + wc_FreeDecodedCert(&decodedCert); + + wolfSSL_CertManagerFree(cm); + wc_ecc_free(&entityKey); + wc_ecc_free(&icaKey); + wc_ecc_free(&rootKey); + wc_FreeRng(&rng); +#endif + return EXPECT_RESULT(); +} + +static int test_PathLenNoKeyUsage(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_CERT_REQ) && !defined(NO_ASN_TIME) && \ + defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && \ + defined(WOLFSSL_CERT_EXT) && !defined(NO_CERTS) && \ + (!defined(NO_WOLFSSL_CLIENT) || !defined(WOLFSSL_NO_CLIENT_AUTH)) + Cert cert; + DecodedCert decodedCert; + byte rootDer[FOURK_BUF]; + byte icaDer[FOURK_BUF]; + int rootDerSz = 0; + int icaDerSz = 0; + WC_RNG rng; + ecc_key rootKey; + ecc_key icaKey; + WOLFSSL_CERT_MANAGER* cm = NULL; + + XMEMSET(&rng, 0, sizeof(WC_RNG)); + XMEMSET(&rootKey, 0, sizeof(ecc_key)); + XMEMSET(&icaKey, 0, sizeof(ecc_key)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_ecc_init(&rootKey), 0); + ExpectIntEQ(wc_ecc_init(&icaKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, 32, &rootKey), 0); + ExpectIntEQ(wc_ecc_make_key(&rng, 32, &icaKey), 0); + + /* Step 1: Create root CA with pathLen=0 and KeyUsage */ + ExpectIntEQ(wc_InitCert(&cert), 0); + (void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.state, "MT", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.locality, "Bozeman", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.org, "TestCA2", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.unit, "Test", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.commonName, "TestRootCA2", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.email, "root@test2.com", CTC_NAME_SIZE); + cert.selfSigned = 1; + cert.isCA = 1; + cert.pathLen = 0; + cert.pathLenSet = 1; + cert.sigType = CTC_SHA256wECDSA; + cert.keyUsage = KEYUSE_KEY_CERT_SIGN | KEYUSE_CRL_SIGN; + ExpectIntEQ(wc_SetSubjectKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &rootKey), + 0); + + ExpectIntGE(wc_MakeCert(&cert, rootDer, FOURK_BUF, NULL, &rootKey, &rng), + 0); + ExpectIntGE(rootDerSz = wc_SignCert(cert.bodySz, cert.sigType, rootDer, + FOURK_BUF, NULL, &rootKey, &rng), 0); + + /* Step 2: Create intermediate CA WITHOUT KeyUsage extension. + * Per RFC 5280, when KeyUsage is absent all uses are valid. + * The root's pathLen=0 should still block this intermediate CA. + * BUG: pathLen check requires extKeyUsageSet which is false when + * KeyUsage is absent, so the check is skipped entirely. */ + ExpectIntEQ(wc_InitCert(&cert), 0); + cert.selfSigned = 0; + cert.isCA = 1; + cert.sigType = CTC_SHA256wECDSA; + /* Intentionally do NOT set keyUsage - test that pathLen is still enforced */ + cert.keyUsage = 0; + (void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.state, "MT", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.locality, "Bozeman", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.org, "TestICA", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.unit, "Test", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.commonName, "TestICA-NoKU", CTC_NAME_SIZE); + (void)XSTRNCPY(cert.subject.email, "ica@test2.com", CTC_NAME_SIZE); + ExpectIntEQ(wc_SetIssuerBuffer(&cert, rootDer, rootDerSz), 0); + ExpectIntEQ(wc_SetAuthKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &rootKey), 0); + ExpectIntEQ(wc_SetSubjectKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &icaKey), + 0); + + ExpectIntGE(wc_MakeCert(&cert, icaDer, FOURK_BUF, NULL, &icaKey, &rng), 0); + ExpectIntGE(icaDerSz = wc_SignCert(cert.bodySz, cert.sigType, icaDer, + FOURK_BUF, NULL, &rootKey, &rng), 0); + + /* Step 3: Load root CA into cert manager */ + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, rootDer, rootDerSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + + /* Step 4: Parse the intermediate (no KeyUsage) as a chain cert. + * Root CA has pathLen=0, this intermediate CA should be rejected. + * The intermediate does NOT have the KeyUsage extension, but per + * RFC 5280 4.2.1.3 all key uses are valid when the extension is + * absent, so pathLen must still be enforced. */ + wc_InitDecodedCert(&decodedCert, icaDer, (word32)icaDerSz, NULL); + ExpectIntEQ(wc_ParseCert(&decodedCert, CHAIN_CERT_TYPE, VERIFY, + cm), WC_NO_ERR_TRACE(ASN_PATHLEN_INV_E)); + wc_FreeDecodedCert(&decodedCert); + + wolfSSL_CertManagerFree(cm); + wc_ecc_free(&icaKey); + wc_ecc_free(&rootKey); + wc_FreeRng(&rng); +#endif + return EXPECT_RESULT(); +} + static int test_MakeCertWith0Ser(void) { EXPECT_DECLS; @@ -35273,6 +35493,8 @@ TEST_CASE testCases[] = { TEST_DECL(test_wc_ParseCert), TEST_DECL(test_wc_ParseCert_Error), TEST_DECL(test_MakeCertWithPathLen), + TEST_DECL(test_PathLenSelfIssued), + TEST_DECL(test_PathLenNoKeyUsage), TEST_DECL(test_MakeCertWith0Ser), TEST_DECL(test_MakeCertWithCaFalse), #ifdef WOLFSSL_CERT_SIGN_CB diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index b095b5c204..cf1c275209 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -22425,16 +22425,24 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, void* cm, if (cert->pathLengthSet) cert->maxPathLen = cert->pathLength; - if (!cert->selfSigned) { - /* Need to perform a pathlen check on anything that will be used - * to sign certificates later on. Otherwise, pathLen doesn't - * mean anything. - * Nothing to check if we don't have the issuer of this cert. */ - if (type != CERT_TYPE && cert->isCA && cert->extKeyUsageSet && - (cert->extKeyUsage & KEYUSE_KEY_CERT_SIGN) != 0 && cert->ca) { + /* RFC 5280 6.1.4: Check issuer's pathLen constraint. + * Need to perform a pathlen check on anything that will be used + * to sign certificates later on. Otherwise, pathLen doesn't + * mean anything. + * Nothing to check if we don't have the issuer of this cert. + * + * Per RFC 5280, when the KeyUsage extension is absent, all key + * uses are implicitly valid (including keyCertSign), so pathLen + * enforcement must not be gated on KeyUsage presence. */ + if (type != CERT_TYPE && cert->isCA && cert->ca && + (!cert->extKeyUsageSet || + (cert->extKeyUsage & KEYUSE_KEY_CERT_SIGN) != 0)) { + if (!cert->selfSigned) { + /* RFC 5280 6.1.4(l): Non-self-issued cert decrements and + * checks the issuer's max_path_length. */ if (cert->ca->maxPathLen == 0) { - /* This cert CAN NOT be used as an intermediate cert. The - * issuer does not allow it. */ + /* This cert CAN NOT be used as an intermediate cert. + * The issuer does not allow it. */ cert->maxPathLen = 0; if (verify != NO_VERIFY) { WOLFSSL_MSG("\tNon-entity cert, maxPathLen is 0"); @@ -22444,7 +22452,28 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, void* cm, } } else { - cert->maxPathLen = (byte)min(cert->ca->maxPathLen - 1U, + cert->maxPathLen = (word16)min(cert->ca->maxPathLen - 1U, + cert->maxPathLen); + } + } + else { + /* RFC 5280 6.1.4(l): Self-issued certs do NOT decrement + * max_path_length, but the issuer's constraint still + * applies. A self-issued cert from a CA with maxPathLen=0 + * cannot act as an intermediate CA. */ + if (cert->ca->maxPathLen == 0) { + cert->maxPathLen = 0; + if (verify != NO_VERIFY) { + WOLFSSL_MSG("\tSelf-issued cert, maxPathLen is 0"); + WOLFSSL_MSG("\tmaxPathLen status: ERROR"); + WOLFSSL_ERROR_VERBOSE(ASN_PATHLEN_INV_E); + return ASN_PATHLEN_INV_E; + } + } + else { + /* Self-issued: honor issuer's constraint without + * decrementing. */ + cert->maxPathLen = (word16)min(cert->ca->maxPathLen, cert->maxPathLen); } } From 2a801ea2c59dfe13f818801c2d7bc80dba0d2ebc Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Fri, 10 Apr 2026 13:07:37 -0500 Subject: [PATCH 4/5] Fix from review --- wolfssl/wolfcrypt/settings.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index b7cc37e3b4..334bc7c723 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -4143,10 +4143,16 @@ extern void uITRON4_free(void *p) ; #ifndef WOLFSSL_ALERT_COUNT_MAX #define WOLFSSL_ALERT_COUNT_MAX 5 #endif +#if WOLFSSL_ALERT_COUNT_MAX > 255 + #error "WOLFSSL_ALERT_COUNT_MAX must be <= 255 (stored in a byte)" +#endif #ifndef WOLFSSL_MAX_EMPTY_RECORDS #define WOLFSSL_MAX_EMPTY_RECORDS 32 #endif +#if WOLFSSL_MAX_EMPTY_RECORDS > 255 + #error "WOLFSSL_MAX_EMPTY_RECORDS must be <= 255 (stored in a byte)" +#endif /* Enable blinding by default for C-only, non-small curve25519 implementation */ #if defined(HAVE_CURVE25519) && !defined(CURVE25519_SMALL) && \ From 58885b02905bc15583bed5c897cc8c591b638b5b Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Fri, 10 Apr 2026 15:43:20 -0500 Subject: [PATCH 5/5] Fixes from review --- src/tls13.c | 8 ++- tests/api/test_tls13.c | 139 +++++++++++++++++++++-------------------- 2 files changed, 76 insertions(+), 71 deletions(-) diff --git a/src/tls13.c b/src/tls13.c index b963ab0e3d..233a5b71a8 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -2638,9 +2638,10 @@ static int EncryptTls13(WOLFSSL* ssl, byte* output, const byte* input, #endif #ifdef WOLFSSL_CIPHER_TEXT_CHECK - if (ssl->specs.bulk_cipher_algorithm != wolfssl_cipher_null) { + if (ssl->specs.bulk_cipher_algorithm != wolfssl_cipher_null && + dataSz >= WOLFSSL_CIPHER_CHECK_SZ) { XMEMCPY(ssl->encrypt.sanityCheck, input, - min(dataSz, sizeof(ssl->encrypt.sanityCheck))); + sizeof(ssl->encrypt.sanityCheck)); } #endif @@ -2824,8 +2825,9 @@ static int EncryptTls13(WOLFSSL* ssl, byte* output, const byte* input, #ifdef WOLFSSL_CIPHER_TEXT_CHECK if (ssl->specs.bulk_cipher_algorithm != wolfssl_cipher_null && + dataSz >= WOLFSSL_CIPHER_CHECK_SZ && XMEMCMP(output, ssl->encrypt.sanityCheck, - min(dataSz, sizeof(ssl->encrypt.sanityCheck))) == 0) { + sizeof(ssl->encrypt.sanityCheck)) == 0) { WOLFSSL_MSG("EncryptTls13 sanity check failed! Glitch?"); return ENCRYPT_ERROR; diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 1a2ec9a51c..ae708970cf 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -2927,28 +2927,28 @@ int test_tls13_duplicate_extension(void) (!defined(NO_RSA) || defined(HAVE_ECC)) static int DupEchSend(WOLFSSL* ssl, char* buf, int sz, void* ctx) { - (void)ssl; - (void)buf; - (void)sz; - (void)ctx; + (void)ssl; + (void)buf; + (void)sz; + (void)ctx; - return sz; + return sz; } static int DupEchRecv(WOLFSSL* ssl, char* buf, int sz, void* ctx) { - WOLFSSL_BUFFER_INFO* msg = (WOLFSSL_BUFFER_INFO*)ctx; - int len = (int)msg->length; + WOLFSSL_BUFFER_INFO* msg = (WOLFSSL_BUFFER_INFO*)ctx; + int len = (int)msg->length; - (void)ssl; - (void)sz; + (void)ssl; + (void)sz; - if (len > sz) - len = sz; - XMEMCPY(buf, msg->buffer, len); - msg->buffer += len; - msg->length -= len; + if (len > sz) + len = sz; + XMEMCPY(buf, msg->buffer, len); + msg->buffer += len; + msg->length -= len; - return len; + return len; } #endif @@ -2959,68 +2959,68 @@ static int DupEchRecv(WOLFSSL* ssl, char* buf, int sz, void* ctx) */ int test_tls13_duplicate_ech_extension(void) { - EXPECT_DECLS; + EXPECT_DECLS; #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && \ !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ (!defined(NO_RSA) || defined(HAVE_ECC)) - /* TLS 1.3 ClientHello with two ECH extensions (type 0xfe0d). - * Extensions block contains: supported_versions + ECH + ECH (dup). */ - const unsigned char clientHelloDupEch[] = { - 0x16, 0x03, 0x03, 0x00, 0x40, 0x01, 0x00, 0x00, - 0x3c, 0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x00, 0x00, 0x02, 0x13, 0x01, - 0x01, 0x00, 0x00, 0x11, 0x00, 0x2b, 0x00, 0x03, - 0x02, 0x03, 0x04, 0xfe, 0x0d, 0x00, 0x01, 0x00, - 0xfe, 0x0d, 0x00, 0x01, 0x00 - }; - WOLFSSL_BUFFER_INFO msg; - const char* testCertFile; - const char* testKeyFile; - WOLFSSL_CTX *ctx = NULL; - WOLFSSL *ssl = NULL; + /* TLS 1.3 ClientHello with two ECH extensions (type 0xfe0d). + * Extensions block contains: supported_versions + ECH + ECH (dup). */ + const unsigned char clientHelloDupEch[] = { + 0x16, 0x03, 0x03, 0x00, 0x40, 0x01, 0x00, 0x00, + 0x3c, 0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x02, 0x13, 0x01, + 0x01, 0x00, 0x00, 0x11, 0x00, 0x2b, 0x00, 0x03, + 0x02, 0x03, 0x04, 0xfe, 0x0d, 0x00, 0x01, 0x00, + 0xfe, 0x0d, 0x00, 0x01, 0x00 + }; + WOLFSSL_BUFFER_INFO msg; + const char* testCertFile; + const char* testKeyFile; + WOLFSSL_CTX *ctx = NULL; + WOLFSSL *ssl = NULL; #ifndef NO_RSA - testCertFile = svrCertFile; - testKeyFile = svrKeyFile; + testCertFile = svrCertFile; + testKeyFile = svrKeyFile; #elif defined(HAVE_ECC) - testCertFile = eccCertFile; - testKeyFile = eccKeyFile; + testCertFile = eccCertFile; + testKeyFile = eccKeyFile; #endif - ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); - ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx, testCertFile, - CERT_FILETYPE)); - ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx, testKeyFile, - CERT_FILETYPE)); + ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx, testCertFile, + CERT_FILETYPE)); + ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx, testKeyFile, + CERT_FILETYPE)); - /* Read from 'msg'. */ - wolfSSL_SetIORecv(ctx, DupEchRecv); - /* No where to send to - dummy sender. */ - wolfSSL_SetIOSend(ctx, DupEchSend); + /* Read from 'msg'. */ + wolfSSL_SetIORecv(ctx, DupEchRecv); + /* No where to send to - dummy sender. */ + wolfSSL_SetIOSend(ctx, DupEchSend); - ssl = wolfSSL_new(ctx); - ExpectNotNull(ssl); + ssl = wolfSSL_new(ctx); + ExpectNotNull(ssl); - msg.buffer = (unsigned char*)clientHelloDupEch; - msg.length = (unsigned int)sizeof(clientHelloDupEch); - wolfSSL_SetIOReadCtx(ssl, &msg); + msg.buffer = (unsigned char*)clientHelloDupEch; + msg.length = (unsigned int)sizeof(clientHelloDupEch); + wolfSSL_SetIOReadCtx(ssl, &msg); - ExpectIntNE(wolfSSL_accept(ssl), WOLFSSL_SUCCESS); - /* Can return duplicate ext error or socket error if the peer closed - * down while sending alert. */ - if (wolfSSL_get_error(ssl, 0) != WC_NO_ERR_TRACE(SOCKET_ERROR_E)) { - ExpectIntEQ(wolfSSL_get_error(ssl, 0), - WC_NO_ERR_TRACE(DUPLICATE_TLS_EXT_E)); - } + ExpectIntNE(wolfSSL_accept(ssl), WOLFSSL_SUCCESS); + /* Can return duplicate ext error or socket error if the peer closed + * down while sending alert. */ + if (wolfSSL_get_error(ssl, 0) != WC_NO_ERR_TRACE(SOCKET_ERROR_E)) { + ExpectIntEQ(wolfSSL_get_error(ssl, 0), + WC_NO_ERR_TRACE(DUPLICATE_TLS_EXT_E)); + } - wolfSSL_free(ssl); - wolfSSL_CTX_free(ctx); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); #endif - return EXPECT_RESULT(); + return EXPECT_RESULT(); } @@ -3801,12 +3801,15 @@ int test_tls13_empty_record_limit(void) } /* Build 1 non-empty record */ - XMEMSET(dataRec, 0, sizeof(dataRec)); - XMEMCPY(dataRec + RECORD_HEADER_SZ, payload, sizeof(payload)); - ExpectIntEQ(BuildTls13Message(ssl_c, dataRec, (int)sizeof(dataRec), - dataRec + RECORD_HEADER_SZ, 1, application_data, - 0, 0, 0), dataRecSz); - XMEMCPY(allRecs + emptyBefore * recSz, dataRec, (size_t)dataRecSz); + if (EXPECT_SUCCESS()) { + XMEMSET(dataRec, 0, sizeof(dataRec)); + XMEMCPY(dataRec + RECORD_HEADER_SZ, payload, sizeof(payload)); + ExpectIntEQ(BuildTls13Message(ssl_c, dataRec, (int)sizeof(dataRec), + dataRec + RECORD_HEADER_SZ, 1, application_data, + 0, 0, 0), dataRecSz); + XMEMCPY(allRecs + emptyBefore * recSz, dataRec, + (size_t)dataRecSz); + } /* Build (limit - 1) more empty records */ for (i = 0; i < emptyAfter && EXPECT_SUCCESS(); i++) {