From b5cff8dccad9fe269225de6b576f7fe1bb3c7a75 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Mon, 4 May 2026 04:13:51 +0000 Subject: [PATCH 1/4] harden TLS handshake validation --- src/internal.c | 20 +++++++++++++++----- src/tls.c | 8 ++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/internal.c b/src/internal.c index 8a72a675d8..3c8822bec2 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1270,7 +1270,8 @@ static int ImportKeyState(WOLFSSL* ssl, const byte* exp, word32 len, byte ver, } sz = exp[idx++]; - if (sz > sizeof(keys->client_write_IV) || (sz * 2) + idx > len) { + if (sz > sizeof(keys->client_write_IV) || + (sz * 2) + idx + AEAD_MAX_EXP_SZ + OPAQUE8_LEN > len) { WOLFSSL_MSG("Buffer not large enough for write IV import"); return BUFFER_E; } @@ -16898,10 +16899,15 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, ret = KEYUSE_ENCIPHER_E; WOLFSSL_ERROR_VERBOSE(ret); } - if ((ssl->specs.kea != rsa_kea) && - (ssl->specs.sig_algo == rsa_sa_algo || - (ssl->specs.sig_algo == ecc_dsa_sa_algo && - !ssl->specs.static_ecdh)) && + /* TLS 1.3 decouples sig algorithm from cipher suite, so + * specs.sig_algo is any_sa_algo. RFC 8446 4.4.2.4 still + * requires digital_signature when keyUsage is present on + * the cert that drives CertificateVerify. */ + if (((ssl->specs.kea != rsa_kea) && + (IsAtLeastTLSv1_3(ssl->version) || + ssl->specs.sig_algo == rsa_sa_algo || + (ssl->specs.sig_algo == ecc_dsa_sa_algo && + !ssl->specs.static_ecdh))) && (args->dCert->extKeyUsage & KEYUSE_DIGITAL_SIG) == 0) { WOLFSSL_MSG("KeyUse Digital Sig not set"); ret = KEYUSE_SIGNATURE_E; @@ -24240,6 +24246,8 @@ static int BuildMD5_CertVerify(const WOLFSSL* ssl, byte* digest) #ifdef WOLFSSL_SMALL_STACK wc_Md5* md5 = (wc_Md5*)XMALLOC(sizeof(wc_Md5), ssl->heap, DYNAMIC_TYPE_HASHCTX); + if (md5 == NULL) + return MEMORY_E; #else wc_Md5 md5[1]; #endif @@ -24284,6 +24292,8 @@ static int BuildSHA_CertVerify(const WOLFSSL* ssl, byte* digest) #ifdef WOLFSSL_SMALL_STACK wc_Sha* sha = (wc_Sha*)XMALLOC(sizeof(wc_Sha), ssl->heap, DYNAMIC_TYPE_HASHCTX); + if (sha == NULL) + return MEMORY_E; #else wc_Sha sha[1]; #endif diff --git a/src/tls.c b/src/tls.c index 347761b263..506a7c17a8 100644 --- a/src/tls.c +++ b/src/tls.c @@ -9291,6 +9291,14 @@ static int TLSX_KeyShare_ProcessDh(WOLFSSL* ssl, KeyShareEntry* keyShareEntry) } #endif + /* RFC 8446 Section 4.2.8.1: FFDHE key_exchange values are left-padded with + * zeros to the size of the named-group prime. Reject any peer key share + * whose byte length does not match the expected prime size. */ + if (keyShareEntry->keLen != pSz) { + WOLFSSL_ERROR_VERBOSE(PEER_KEY_ERROR); + return PEER_KEY_ERROR; + } + /* if DhKey is not setup, do it now */ if (keyShareEntry->key == NULL) { keyShareEntry->key = (DhKey*)XMALLOC(sizeof(DhKey), ssl->heap, From 81b66c9cd8eb2c76dfa571f31ad610cdf43fe2a8 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Mon, 4 May 2026 04:13:49 +0000 Subject: [PATCH 2/4] harden SSL config defaults --- src/ssl.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ssl.c b/src/ssl.c index bc7e6074c8..9b79027f91 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -17545,7 +17545,10 @@ int wolfSSL_set_alpn_protos(WOLFSSL* ssl, unsigned int ptIdx; unsigned int sz; unsigned int idx = 0; - int alpn_opt = WOLFSSL_ALPN_CONTINUE_ON_MISMATCH; + /* RFC 7301: a server that does not select any of the client's offered + * protocols MUST send no_application_protocol. Match that contract on + * the OpenSSL-compat surface rather than silently continuing. */ + int alpn_opt = WOLFSSL_ALPN_FAILED_ON_MISMATCH; int ret; WOLFSSL_ENTER("wolfSSL_set_alpn_protos"); From 0e08253b0dd3ead7d198cc03754376708bd892f6 Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Mon, 4 May 2026 04:13:35 +0000 Subject: [PATCH 3/4] fix logic errors in stale code --- src/ocsp.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/ocsp.c b/src/ocsp.c index 8c779c6247..2c4887c97f 100644 --- a/src/ocsp.c +++ b/src/ocsp.c @@ -602,8 +602,9 @@ static int CheckOcspResponderChain(OcspEntry* single, byte* issuerHash, * in OCSP request */ - /* End loop if no more issuers found or if we have found a self - * signed cert (ca == prev) */ + /* Walk up the issuer chain from the responder's direct issuer toward a + * self-signed root, capturing prev before reassigning ca so the + * (ca == prev) self-signed termination check is meaningful. */ ca = GetCAByName(cm, single->issuerHash); #if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) if (ca == NULL && pendingCAs != NULL) { @@ -612,8 +613,7 @@ static int CheckOcspResponderChain(OcspEntry* single, byte* issuerHash, #else (void)pendingCAs; #endif - for (; ca != NULL && ca != prev; - prev = ca) { + while (ca != NULL) { if (XMEMCMP(issuerHash, ca->issuerNameHash, OCSP_DIGEST_SIZE) == 0) { WOLFSSL_MSG("\tOCSP Response signed by authorized " "responder delegated by issuer " @@ -621,12 +621,19 @@ static int CheckOcspResponderChain(OcspEntry* single, byte* issuerHash, passed = 1; break; } - ca = GetCAByName(cm, ca->issuerNameHash); + prev = ca; + ca = GetCAByName(cm, prev->issuerNameHash); #if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) + /* Look up by the in-flight name (prev's issuer), not by + * single->issuerHash; the latter would re-anchor the pendingCAs + * walk to the bottom of the chain on every iteration. */ if (ca == NULL && pendingCAs != NULL) { - ca = findSignerByName(pendingCAs, single->issuerHash); + ca = findSignerByName(pendingCAs, prev->issuerNameHash); } #endif + if (ca == prev) { + break; + } } return passed; } From 18c627c2bf28079aa195ec50437787546ab890fa Mon Sep 17 00:00:00 2001 From: Jeremiah Mackey Date: Mon, 4 May 2026 04:22:28 +0000 Subject: [PATCH 4/4] tests --- tests/api.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/api.c b/tests/api.c index f175b287b8..5dc274bbea 100644 --- a/tests/api.c +++ b/tests/api.c @@ -2303,6 +2303,83 @@ static int test_wolfSSL_set_cipher_list_tls13_with_version(void) return EXPECT_RESULT(); } +static int test_wolfSSL_set_alpn_protos_default_fails(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ALPN) && defined(OPENSSL_EXTRA) && !defined(NO_BIO) && \ + !defined(NO_WOLFSSL_CLIENT) + { + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + unsigned char p[] = { 6, 's', 'p', 'd', 'y', '/', '3' }; + TLSX* ext = NULL; + ALPN* alpn = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); +#ifdef WOLFSSL_ERROR_CODE_OPENSSL + ExpectIntEQ(wolfSSL_set_alpn_protos(ssl, p, sizeof(p)), 0); +#else + ExpectIntEQ(wolfSSL_set_alpn_protos(ssl, p, sizeof(p)), + WOLFSSL_SUCCESS); +#endif + if (ssl != NULL) { + ext = TLSX_Find(ssl->extensions, + TLSX_APPLICATION_LAYER_PROTOCOL); + ExpectNotNull(ext); + if (ext != NULL) { + alpn = (ALPN*)ext->data; + ExpectNotNull(alpn); + if (alpn != NULL) { + ExpectTrue((alpn->options + & WOLFSSL_ALPN_FAILED_ON_MISMATCH) != 0); + ExpectIntEQ(alpn->options + & WOLFSSL_ALPN_CONTINUE_ON_MISMATCH, 0); + } + } + } + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + } +#if !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(SINGLE_THREADED) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + { + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + unsigned char client_protos[] = { 6, 's', 'p', 'd', 'y', '/', '3' }; + const char* server_protos = "http/2"; + WOLFSSL_ALERT_HISTORY h; + + 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); + +#ifdef WOLFSSL_ERROR_CODE_OPENSSL + ExpectIntEQ(wolfSSL_set_alpn_protos(ssl_c, client_protos, + sizeof(client_protos)), 0); +#else + ExpectIntEQ(wolfSSL_set_alpn_protos(ssl_c, client_protos, + sizeof(client_protos)), WOLFSSL_SUCCESS); +#endif + ExpectIntEQ(wolfSSL_UseALPN(ssl_s, (char*)server_protos, + (word32)XSTRLEN(server_protos), + WOLFSSL_ALPN_FAILED_ON_MISMATCH), WOLFSSL_SUCCESS); + + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_s, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_tx.code, no_application_protocol); + ExpectIntEQ(h.last_tx.level, alert_fatal); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif +#endif + return EXPECT_RESULT(); +} static int test_wolfSSL_CTX_use_certificate(void) { @@ -39568,6 +39645,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_set_cipher_list_tls13_keeps_tls12), TEST_DECL(test_wolfSSL_set_cipher_list_tls12_with_version), TEST_DECL(test_wolfSSL_set_cipher_list_tls13_with_version), + TEST_DECL(test_wolfSSL_set_alpn_protos_default_fails), TEST_DECL(test_wolfSSL_CTX_use_certificate), TEST_DECL(test_wolfSSL_CTX_use_certificate_file), TEST_DECL(test_wolfSSL_CTX_use_certificate_buffer),