From abf80529ca2b42aa1e101dfc50465f2a36908294 Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 13 Aug 2026 16:28:37 -0700 Subject: [PATCH 1/4] Forbid supported_versions from being sent when using TLS <1.3. Fixes F-9230. --- src/tls.c | 13 +++++++- tests/api/test_tls13.c | 76 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 2 ++ 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/tls.c b/src/tls.c index 895bcb3b99..ff219a71f3 100644 --- a/src/tls.c +++ b/src/tls.c @@ -18888,7 +18888,6 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, #ifdef WOLFSSL_TLS13 case TLSX_SUPPORTED_VERSIONS: - WOLFSSL_MSG("Skipping Supported Versions - already processed"); #ifdef WOLFSSL_DEBUG_TLS WOLFSSL_BUFFER(input + offset, size); #endif @@ -18897,6 +18896,18 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, msgType != hello_retry_request) return EXT_NOT_ALLOWED; + /* RFC 8446 Section 4.2.1: "A server which negotiates a version + * of TLS prior to TLS 1.3 MUST set ServerHello.version and MUST + * NOT send the "supported_versions" extension." If TLS version + * is <1.3, supported_versions is invalid. */ + if (msgType == server_hello && + !IsAtLeastTLSv1_3(ssl->version)) { + WOLFSSL_MSG("Supported Versions in older ServerHello"); + WOLFSSL_ERROR_VERBOSE(VERSION_ERROR); + return VERSION_ERROR; + } + + WOLFSSL_MSG("Skipping Supported Versions - already processed"); break; case TLSX_COOKIE: diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 5667d543db..a865f31d18 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -8252,6 +8252,82 @@ int test_tls13_downgrade_sentinel(void) return EXPECT_RESULT(); } +/* Test that a client does not treat a ServerHello carrying supported_versions + * as an older-version ServerHello because of its legacy_version. RFC 8446 + * Section 4.2.1: "A server which negotiates a version of TLS prior to TLS 1.3 + * MUST set ServerHello.version and MUST NOT send the "supported_versions" + * extension", and a client "MUST ignore the ServerHello.legacy_version value + * and MUST use only the "supported_versions" extension to determine the + * selected version". A server selecting TLS 1.3 must set legacy_version to + * 0x0303 (Section 4.1.3). */ +int test_tls13_serverhello_legacy_version(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_NO_TLS12) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL_ALERT_HISTORY h; + /* legacy_version follows the record (5) and handshake (4) headers. */ + int verOff = 9; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&h, 0, sizeof(h)); + /* Client allows downgrading, so the legacy dispatch is reachable. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLS_client_method, wolfTLSv1_3_server_method), 0); + + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + + /* Claim TLS 1.0 in legacy_version. supported_versions still selects + * TLS 1.3. */ + if (EXPECT_SUCCESS()) { + ExpectIntGT(test_ctx.c_len, verOff + 1); + test_ctx.c_buff[verOff + 0] = SSLv3_MAJOR; + test_ctx.c_buff[verOff + 1] = TLSv1_MINOR; + } + + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(VERSION_ERROR)); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_tx.code, wolfssl_alert_protocol_version); + ExpectIntEQ(h.last_tx.level, alert_fatal); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + + /* A ServerHello that really is older still downgrades: no + * supported_versions extension to contradict its legacy_version. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ctx_c = NULL; + ctx_s = NULL; + ssl_c = NULL; + ssl_s = NULL; + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLS_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), TLS1_2_VERSION); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /* Test that a TLS 1.3 client rejects ServerHello cipher suites that are not * TLS 1.3 suites or were not offered by the client. */ int test_tls13_serverhello_bad_cipher_suites(void) diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index dfd4b740c7..66bf1e53fc 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -71,6 +71,7 @@ int test_tls13_zero_inner_content_type(void); int test_tls13_post_handshake_auth_no_ext(void); int test_tls13_post_handshake_auth_late_allow(void); int test_tls13_downgrade_sentinel(void); +int test_tls13_serverhello_legacy_version(void); int test_tls13_serverhello_bad_cipher_suites(void); int test_tls13_psk_no_cert_bad_binder(void); int test_tls13_psk_age_no_identity_oracle(void); @@ -157,6 +158,7 @@ int test_tls13_pha_status_request(void); TEST_DECL_GROUP("tls13", test_tls13_post_handshake_auth_no_ext), \ TEST_DECL_GROUP("tls13", test_tls13_post_handshake_auth_late_allow), \ TEST_DECL_GROUP("tls13", test_tls13_downgrade_sentinel), \ + TEST_DECL_GROUP("tls13", test_tls13_serverhello_legacy_version), \ TEST_DECL_GROUP("tls13", test_tls13_serverhello_bad_cipher_suites), \ TEST_DECL_GROUP("tls13", test_tls13_psk_no_cert_bad_binder), \ TEST_DECL_GROUP("tls13", test_tls13_psk_age_no_identity_oracle), \ From d03ed1cb7f173702090136e9a8716659082d46f8 Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 13 Aug 2026 16:29:17 -0700 Subject: [PATCH 2/4] Forbid sending more than one cert when using RPK. Fixes F-9231. --- src/internal.c | 49 ++++++++++++++++++++++------ tests/api/test_tls13.c | 73 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 2 ++ 3 files changed, 115 insertions(+), 9 deletions(-) diff --git a/src/internal.c b/src/internal.c index fd100102fa..68406a6768 100644 --- a/src/internal.c +++ b/src/internal.c @@ -16508,6 +16508,29 @@ int LoadCertByIssuer(WOLFSSL_X509_STORE* store, X509_NAME* issuer, int type) #endif +#if defined(HAVE_RPK) +/* Certificate type negotiated for the peer's certificate: the server cert type + * received (client side) or the client cert type selected (server side). When + * no type was negotiated the default is X.509 (RFC 7250 Section 4.1). + * + * @param [in] ssl SSL/TLS object. + * @return the negotiated WOLFSSL_CERT_TYPE_* value. + */ +static int GetPeerCertType(const WOLFSSL* ssl) +{ + if (ssl->options.side == WOLFSSL_CLIENT_END) { + if (ssl->options.rpkState.received_ServerCertTypeCnt == 1) + return ssl->options.rpkState.received_ServerCertTypes[0]; + } + else if (ssl->options.side == WOLFSSL_SERVER_END) { + if (ssl->options.rpkState.sending_ClientCertTypeCnt == 1) + return ssl->options.rpkState.sending_ClientCertTypes[0]; + } + + return WOLFSSL_CERT_TYPE_X509; +} +#endif /* HAVE_RPK */ + static int ProcessPeerCertParse(WOLFSSL* ssl, ProcPeerCertArgs* args, int certType, int verify, byte** pSubjectHash, int* pAlreadySigner) { @@ -16634,15 +16657,7 @@ PRAGMA_GCC_DIAG_POP * un-negotiated bare key is rejected. The negotiated type is the received * server cert type (client) or the selected client cert type (server). */ if (ret == 0) { - cType = WOLFSSL_CERT_TYPE_X509; - if (ssl->options.side == WOLFSSL_CLIENT_END) { - if (ssl->options.rpkState.received_ServerCertTypeCnt == 1) - cType = ssl->options.rpkState.received_ServerCertTypes[0]; - } - else if (ssl->options.side == WOLFSSL_SERVER_END) { - if (ssl->options.rpkState.sending_ClientCertTypeCnt == 1) - cType = ssl->options.rpkState.sending_ClientCertTypes[0]; - } + cType = GetPeerCertType(ssl); if ((cType == WOLFSSL_CERT_TYPE_RPK && !args->dCert->isRPK) || (cType != WOLFSSL_CERT_TYPE_RPK && args->dCert->isRPK)) { @@ -17997,6 +18012,22 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, args->count = args->totalCerts; args->certIdx = 0; /* select peer cert (first one) */ + #if defined(HAVE_RPK) && defined(WOLFSSL_TLS13) + /* RFC 8446 Section 4.4.2: "If the RawPublicKey certificate type was + * negotiated, then the certificate_list MUST contain no more than + * one CertificateEntry". Only TLS 1.3 needs the check: a TLS 1.2 + * Certificate carries the bare SubjectPublicKeyInfo, which the + * length handling above already limits to a single entry. */ + if (ssl->options.tls1_3 && args->count > 1 && + GetPeerCertType(ssl) == WOLFSSL_CERT_TYPE_RPK) { + WOLFSSL_MSG("Multiple certs with raw public key negotiated"); + ret = UNSUPPORTED_CERTIFICATE; + WOLFSSL_ERROR_VERBOSE(ret); + DoCertFatalAlert(ssl, ret); + goto exit_ppc; + } + #endif /* HAVE_RPK && WOLFSSL_TLS13 */ + if (args->count == 0) { /* Empty certificate message. */ if ((ssl->options.side == WOLFSSL_SERVER_END) && diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index a865f31d18..5b73da5fe3 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -4351,6 +4351,79 @@ int test_tls13_rpk_unoffered_cert_type(void) return EXPECT_RESULT(); } +/* RFC 8446 Section 4.4.2: "If the RawPublicKey certificate type was negotiated, + * then the certificate_list MUST contain no more than one CertificateEntry, + * which contains an ASN1_subjectPublicKeyInfo value as defined in [RFC7250], + * Section 3." Make the server send a second raw public key and check that the + * client rejects the Certificate message. */ +int test_tls13_rpk_multiple_certs(void) +{ + EXPECT_DECLS; +#if defined(HAVE_RPK) && defined(WOLFSSL_TLS13) && !defined(NO_TLS) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_FILESYSTEM) && !defined(NO_SHA256) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL_ALERT_HISTORY h; + byte* spki = NULL; + size_t spkiSz = 0; + DerBuffer* chain = NULL; + char certType[] = { WOLFSSL_CERT_TYPE_RPK, WOLFSSL_CERT_TYPE_X509 }; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&h, 0, sizeof(h)); + ExpectIntEQ( + test_rpk_memio_setup( + &test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method, + clntRpkCertFile, WOLFSSL_FILETYPE_ASN1, + svrRpkCertFile, WOLFSSL_FILETYPE_ASN1, + cliKeyFile, CERT_FILETYPE, + svrKeyFile, CERT_FILETYPE) + , 0); + + ExpectIntEQ(wolfSSL_set_server_cert_type(ssl_c, certType, + (int)sizeof(certType)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_server_cert_type(ssl_s, certType, + (int)sizeof(certType)), WOLFSSL_SUCCESS); + + /* Append a second entry to the server's certificate_list: the same raw + * public key again, in the length-prefixed form the chain is sent in. */ + ExpectIntEQ(load_file(svrRpkCertFile, &spki, &spkiSz), 0); + ExpectIntEQ(AllocDer(&chain, (word32)spkiSz + CERT_HEADER_SZ, + CERT_TYPE, NULL), 0); + if (EXPECT_SUCCESS()) { + chain->buffer[0] = (byte)(spkiSz >> 16); + chain->buffer[1] = (byte)(spkiSz >> 8); + chain->buffer[2] = (byte)spkiSz; + XMEMCPY(chain->buffer + CERT_HEADER_SZ, spki, spkiSz); + ssl_s->buffers.certChain = chain; + ssl_s->buffers.certChainCnt = 1; + ssl_s->buffers.weOwnCertChain = 1; + chain = NULL; /* owned by ssl_s now */ + } + + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WC_NO_ERR_TRACE(UNSUPPORTED_CERTIFICATE)); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_tx.code, unsupported_certificate); + ExpectIntEQ(h.last_tx.level, alert_fatal); + + FreeDer(&chain); + XFREE(spki, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif /* HAVE_RPK && WOLFSSL_TLS13 && memio && !NO_SHA256 && client+server */ + return EXPECT_RESULT(); +} + #if defined(HAVE_IO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 66bf1e53fc..52874eaf71 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -33,6 +33,7 @@ int test_tls13_pha(void); int test_tls13_rpk_untrusted(void); int test_tls13_rpk_trust(void); int test_tls13_rpk_unoffered_cert_type(void); +int test_tls13_rpk_multiple_certs(void); int test_tls13_pq_groups(void); int test_tls13_multi_pqc_key_share(void); int test_tls13_early_data(void); @@ -120,6 +121,7 @@ int test_tls13_pha_status_request(void); TEST_DECL_GROUP("tls13", test_tls13_rpk_untrusted), \ TEST_DECL_GROUP("tls13", test_tls13_rpk_trust), \ TEST_DECL_GROUP("tls13", test_tls13_rpk_unoffered_cert_type), \ + TEST_DECL_GROUP("tls13", test_tls13_rpk_multiple_certs), \ TEST_DECL_GROUP("tls13", test_tls13_pq_groups), \ TEST_DECL_GROUP("tls13", test_tls13_multi_pqc_key_share), \ TEST_DECL_GROUP("tls13", test_tls13_early_data), \ From 957c76733f3666522266d35f9e128459c9c375c2 Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 13 Aug 2026 16:30:07 -0700 Subject: [PATCH 3/4] Ensure max_early_data_size matches the expected value of 0xffffffff when using QUIC. Fixes F-9233. --- src/tls.c | 13 +++++++++ tests/quic.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/tls.c b/src/tls.c index ff219a71f3..f4cbbf5304 100644 --- a/src/tls.c +++ b/src/tls.c @@ -13176,6 +13176,19 @@ static int TLSX_EarlyData_Parse(WOLFSSL* ssl, const byte* input, word16 length, return BUFFER_E; ato32(input, &maxSz); +#ifdef WOLFSSL_QUIC + /* RFC 9001 Section 4.6.1: "Servers MUST NOT send the early_data + * extension with a max_early_data_size field set to any value other + * than 0xffffffff. A client MUST treat receipt of a NewSessionTicket + * that contains an early_data extension with any other value as a + * connection error of type PROTOCOL_VIOLATION." */ + if (WOLFSSL_IS_QUIC(ssl) && maxSz != WOLFSSL_MAX_32BIT) { + WOLFSSL_MSG("QUIC ticket early data size not 0xffffffff"); + WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); + return INVALID_PARAMETER; + } +#endif /* WOLFSSL_QUIC */ + ssl->session->maxEarlyDataSz = maxSz; return 0; } diff --git a/tests/quic.c b/tests/quic.c index 1df10a75f5..57704f12ec 100644 --- a/tests/quic.c +++ b/tests/quic.c @@ -804,7 +804,7 @@ static int ctx_send_alert(WOLFSSL *ssl, WOLFSSL_ENCRYPTION_LEVEL level, uint8_t printf("[%s] send_alert: level=%d, err=%d\n", ctx->name, level, err); } ctx->alert_level = (int)level; - ctx->alert = alert; + ctx->alert = (int)err; return 1; } @@ -2029,6 +2029,76 @@ static int test_quic_resumption(int verbose) { } #ifdef WOLFSSL_EARLY_DATA +/* RFC 9001 Section 4.6.1: "Servers MUST NOT send the early_data extension with + * a max_early_data_size field set to any value other than 0xffffffff. A client + * MUST treat receipt of a NewSessionTicket that contains an early_data + * extension with any other value as a connection error of type + * PROTOCOL_VIOLATION." */ +static int test_quic_ticket_max_early_data(int verbose) { + EXPECT_DECLS; + WOLFSSL_CTX * ctx_c = NULL; + WOLFSSL_CTX * ctx_s = NULL; + QuicTestContext tclient, tserver; + QuicConversation conv; + /* A NewSessionTicket carrying only an early_data extension. */ + byte ticket[] = { + 0x04, /* NewSessionTicket */ + 0x00, 0x00, 0x1d, /* body length */ + 0x00, 0x00, 0x0e, 0x10, /* ticket_lifetime */ + 0x00, 0x00, 0x00, 0x00, /* ticket_age_add */ + 0x00, /* ticket_nonce<0..255> */ + 0x00, 0x08, /* ticket<1..2^16-1> */ + 't', 'e', 's', 't', 'i', 'c', 'k', 't', + 0x00, 0x08, /* extensions<0..2^16-2> */ + 0x00, 0x2a, 0x00, 0x04, /* early_data */ + 0xff, 0xff, 0xff, 0xff /* max_early_data_size */ + }; + /* Offset of max_early_data_size within the message. */ + const size_t edOff = sizeof(ticket) - OPAQUE32_LEN; + + ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile, + WOLFSSL_FILETYPE_PEM)); + ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile, + WOLFSSL_FILETYPE_PEM)); + + QuicTestContext_init(&tclient, ctx_c, "client", verbose); + QuicTestContext_init(&tserver, ctx_s, "server", verbose); + QuicConversation_init(&conv, &tclient, &tserver); + QuicConversation_do(&conv); + ExpectIntEQ(wolfSSL_get_error(tclient.ssl, 0), 0); + + /* The sentinel is the only value a QUIC server may send. */ + ExpectIntEQ(wolfSSL_provide_quic_data(tclient.ssl, + wolfssl_encryption_application, ticket, sizeof(ticket)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_process_quic_post_handshake(tclient.ssl), + WOLFSSL_SUCCESS); + ExpectNotNull(tclient.ssl); + ExpectTrue(tclient.ssl->session->maxEarlyDataSz == WOLFSSL_MAX_32BIT); + + /* Any other size must fail the connection. */ + ticket[edOff + 0] = 0x00; + ticket[edOff + 1] = 0x00; + ticket[edOff + 2] = 0x40; + ticket[edOff + 3] = 0x00; + ExpectIntEQ(wolfSSL_provide_quic_data(tclient.ssl, + wolfssl_encryption_application, ticket, sizeof(ticket)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_process_quic_post_handshake(tclient.ssl), + WC_NO_ERR_TRACE(INVALID_PARAMETER)); + ExpectIntEQ(tclient.alert, illegal_parameter); + + QuicTestContext_free(&tclient); + QuicTestContext_free(&tserver); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + printf(" test_quic_ticket_max_early_data: %s\n", + EXPECT_SUCCESS() ? pass : fail); + return EXPECT_RESULT(); +} + static int test_quic_early_data(int verbose) { EXPECT_DECLS; WOLFSSL_CTX * ctx_c = NULL; @@ -2346,6 +2416,8 @@ int QuicTest(void) if ((ret = test_quic_key_share(verbose)) != TEST_SUCCESS) goto leave; if ((ret = test_quic_resumption(verbose)) != TEST_SUCCESS) goto leave; #ifdef WOLFSSL_EARLY_DATA + if ((ret = test_quic_ticket_max_early_data(verbose)) != TEST_SUCCESS) + goto leave; if ((ret = test_quic_early_data(verbose)) != TEST_SUCCESS) goto leave; if ((ret = test_quic_big_early_data(verbose)) != TEST_SUCCESS) goto leave; #endif /* WOLFSSL_EARLY_DATA */ From eb11281b19345980346a2ebfd6f15509a2f941e8 Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 13 Aug 2026 18:22:34 -0700 Subject: [PATCH 4/4] Send correct alert type PROTOCOL_VIOLATION. Add defines for QUIC alert codes as they differ from TLS alert codes. --- src/internal.c | 3 +++ src/tls.c | 1 + tests/quic.c | 5 +++-- wolfssl/quic.h | 20 ++++++++++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/internal.c b/src/internal.c index 68406a6768..14f75f7cf5 100644 --- a/src/internal.c +++ b/src/internal.c @@ -29259,6 +29259,9 @@ static int SendAlert_ex(WOLFSSL* ssl, int severity, int type) #ifdef WOLFSSL_QUIC if (WOLFSSL_IS_QUIC(ssl)) { + /* Record for the duplicate-alert guards, as the TLS path below does. */ + ssl->alert_history.last_tx.code = type; + ssl->alert_history.last_tx.level = severity; ret = !ssl->quic.method->send_alert(ssl, ssl->quic.enc_level_write, (uint8_t)type); if (ret) { WOLFSSL_MSG("QUIC send_alert callback error"); diff --git a/src/tls.c b/src/tls.c index f4cbbf5304..aee7e7e50d 100644 --- a/src/tls.c +++ b/src/tls.c @@ -13184,6 +13184,7 @@ static int TLSX_EarlyData_Parse(WOLFSSL* ssl, const byte* input, word16 length, * connection error of type PROTOCOL_VIOLATION." */ if (WOLFSSL_IS_QUIC(ssl) && maxSz != WOLFSSL_MAX_32BIT) { WOLFSSL_MSG("QUIC ticket early data size not 0xffffffff"); + SendAlert(ssl, alert_fatal, WOLFSSL_QUIC_ERR_PROTOCOL_VIOLATION); WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); return INVALID_PARAMETER; } diff --git a/tests/quic.c b/tests/quic.c index 57704f12ec..73a46f1219 100644 --- a/tests/quic.c +++ b/tests/quic.c @@ -2078,7 +2078,8 @@ static int test_quic_ticket_max_early_data(int verbose) { ExpectNotNull(tclient.ssl); ExpectTrue(tclient.ssl->session->maxEarlyDataSz == WOLFSSL_MAX_32BIT); - /* Any other size must fail the connection. */ + /* Any other size is a connection error of type PROTOCOL_VIOLATION, handed + * to the QUIC stack through send_alert. */ ticket[edOff + 0] = 0x00; ticket[edOff + 1] = 0x00; ticket[edOff + 2] = 0x40; @@ -2088,7 +2089,7 @@ static int test_quic_ticket_max_early_data(int verbose) { WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_process_quic_post_handshake(tclient.ssl), WC_NO_ERR_TRACE(INVALID_PARAMETER)); - ExpectIntEQ(tclient.alert, illegal_parameter); + ExpectIntEQ(tclient.alert, WOLFSSL_QUIC_ERR_PROTOCOL_VIOLATION); QuicTestContext_free(&tclient); QuicTestContext_free(&tserver); diff --git a/wolfssl/quic.h b/wolfssl/quic.h index cdce23ca49..5a994796af 100644 --- a/wolfssl/quic.h +++ b/wolfssl/quic.h @@ -50,6 +50,26 @@ typedef struct wolfssl_quic_method_t WOLFSSL_QUIC_METHOD; #include +/* QUIC transport error codes (RFC 9000 Section 20.1). */ +#define WOLFSSL_QUIC_ERR_NO_ERROR 0x00 +#define WOLFSSL_QUIC_ERR_INTERNAL_ERROR 0x01 +#define WOLFSSL_QUIC_ERR_CONNECTION_REFUSED 0x02 +#define WOLFSSL_QUIC_ERR_FLOW_CONTROL_ERROR 0x03 +#define WOLFSSL_QUIC_ERR_STREAM_LIMIT_ERROR 0x04 +#define WOLFSSL_QUIC_ERR_STREAM_STATE_ERROR 0x05 +#define WOLFSSL_QUIC_ERR_FINAL_SIZE_ERROR 0x06 +#define WOLFSSL_QUIC_ERR_FRAME_ENCODING_ERROR 0x07 +#define WOLFSSL_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08 +#define WOLFSSL_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09 +#define WOLFSSL_QUIC_ERR_PROTOCOL_VIOLATION 0x0a +#define WOLFSSL_QUIC_ERR_INVALID_TOKEN 0x0b +#define WOLFSSL_QUIC_ERR_APPLICATION_ERROR 0x0c +#define WOLFSSL_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0d +#define WOLFSSL_QUIC_ERR_KEY_UPDATE_ERROR 0x0e +#define WOLFSSL_QUIC_ERR_AEAD_LIMIT_REACHED 0x0f +#define WOLFSSL_QUIC_ERR_NO_VIABLE_PATH 0x10 +/* 0x0100-0x01ff carry a TLS alert in the low byte. */ +#define WOLFSSL_QUIC_ERR_CRYPTO_ERROR 0x0100 /* All QUIC related callbacks to the application. */ struct wolfssl_quic_method_t {