From 00de317aad4699f3bec014fc82ca1330e0fb8ab0 Mon Sep 17 00:00:00 2001 From: Tobias Frauenschlaeger Date: Sat, 22 Aug 2026 22:02:02 +0000 Subject: [PATCH 1/6] Guard the unit tests that need TLS 1.2 on WOLFSSL_NO_TLS12 test_tls13_downgrade_sentinel() builds a TLS 1.2 server, and a TLS 1.1 one when old TLS is enabled, to make the peer produce the downgrade sentinel a TLS 1.3 client has to reject. Its guard did not mention WOLFSSL_NO_TLS12, so a build without TLS 1.2 failed to compile the unit tests at wolfTLSv1_2_server_method(). There is nothing to downgrade to in such a build, so require TLS 1.2 for the whole test. It still runs everywhere it did before. The status_request_v2 block at the end of test_TLSX_CSR_parse() builds a TLS 1.2 server context the same way and was guarded only on the extension. RFC 6961 is defined for TLS 1.2 and below, so require TLS 1.2 there too. test_tls13_bounds.c has the opposite problem. Its file-level guard requires TLS 1.2 for the legacy-version tests, but the four test_tls13_mutual_auth_* entry points that call test_tls13b_mutual_auth_round() do not, so a build without TLS 1.2 could not link them. Those handshakes are pure TLS 1.3, so the helper moves into its own block without the TLS 1.2 condition rather than being skipped, and now runs in such a build. The key-type condition on the new block keeps it from going unused where none of the four callers compile. --- tests/api/test_tls13.c | 4 +++- tests/api/test_tls13_bounds.c | 10 ++++++++++ tests/api/test_tls_parse.c | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index b387e5beb92..9b136a6bee0 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -9243,7 +9243,9 @@ int test_tls13_post_handshake_auth_late_allow(void) int test_tls13_downgrade_sentinel(void) { EXPECT_DECLS; -#if defined(WOLFSSL_TLS13) && \ +/* The sentinel marks a downgrade to TLS 1.2 or below, so the test needs a + * server of that version to produce one. */ +#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; diff --git a/tests/api/test_tls13_bounds.c b/tests/api/test_tls13_bounds.c index 054ccb4f8b0..ba503e82aba 100644 --- a/tests/api/test_tls13_bounds.c +++ b/tests/api/test_tls13_bounds.c @@ -417,6 +417,16 @@ static int test_tls13b_ch_find_ext(const byte* rec, int rec_sz, word16 type, } return -1; } + +#endif /* guards */ + +#if defined(WOLFSSL_TLS13) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + (!defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ + defined(HAVE_ED448)) + /* A mutually authenticated TLS 1.3 handshake with a chosen key type on both * ends. DoTls13CertificateVerify()'s peer-key / peerSigAlgo dispatch has one * arm per algorithm and the group only ever ran the RSA one, so each arm's diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index baff3de2ee6..863ae6f3d91 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -1395,7 +1395,7 @@ int test_TLSX_CSR_parse(void) #endif #if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_TLS) && \ - !defined(NO_WOLFSSL_SERVER) + !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) { WOLFSSL_CTX* ctx2 = test_tls_parse_server_ctx( wolfTLSv1_2_server_method()); From 817d39b4c69e8c874d3c391ddb62c4001f614c90 Mon Sep 17 00:00:00 2001 From: Tobias Frauenschlaeger Date: Sat, 22 Aug 2026 22:21:24 +0000 Subject: [PATCH 2/6] Make --disable-tlsv12 compile TLS 1.2 out The option set ENABLED_TLSV12 to no, which the summary printed and a couple of derived settings read, but nothing defined WOLFSSL_NO_TLS12, so every line of the TLS 1.2 implementation was still compiled and still reachable. The one place configure.ac adds that define sits in the FIPS lean-aesgcm bundle and is guarded by "TLS 1.2 is still on and the user did not ask for it", which is false exactly when --disable-tlsv12 was passed. Only the CMake build honoured the switch. Define it once the bundles have had their say, so --enable-tinytls13 and the other bundles that turn the version off get it as well. The case guard keeps the FIPS bundles from adding it twice. Add three os-check entries for the configurations this makes reachable: --disable-tlsv12 on its own, the same with DTLS 1.3, and the same with the sniffer. The one existing entry that passes the option, dtls13-client-minimal, already defined WOLFSSL_NO_TLS12 through CPPFLAGS and strips most of the crypto along with it, so it never covered the option's own effect. Compiling the version out reaches code that assumed it was always there. ProcessCSR_ex parses one certificate_status message and TLS 1.3 reads the chain's per-certificate entries through it, so it moves out of the version guard; only its TLS 1.2 wrapper stays behind. BuildMessage's connection-ID size is read by the TLS 1.2 record path alone, so it is declared with it. The tests and examples that hard-code a TLS 1.2 method, or a helper that only those call, are skipped without the version. CMake rejected three of the four combinations configure does but let WOLFSSL_OLD_TLS=yes through, defined NO_OLD_TLS behind the user's back and reported the option as still on. Reject it there too, and drop the NO_OLD_TLS the block used to add, which the old-TLS option itself now always supplies. --- .github/configs/os-check-linux.json | 13 +++++ CMakeLists.txt | 34 ++++++++++--- ChangeLog.md | 23 +++++++++ configure.ac | 36 ++++++++++++++ examples/client/client.c | 3 +- examples/echoclient/echoclient.c | 10 ++-- examples/echoserver/echoserver.c | 10 ++-- examples/server/server.c | 3 +- src/internal.c | 17 +++++-- src/sniffer.c | 52 ++++++++++++++++--- tests/api.c | 54 +++++++++++++------- tests/api/test_dtls.c | 77 +++++++++++++++++++++++++---- tests/api/test_tls.c | 5 +- tests/api/test_tls_ext.c | 11 +++-- tests/api/test_x509.c | 4 +- 15 files changed, 293 insertions(+), 59 deletions(-) diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index d3ec20c7331..ed82f14879d 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -425,6 +425,19 @@ "--disable-sha512", "--disable-sha3", "--enable-aesgcm=small", "--enable-sp-math", "--enable-sp=smallec256", "--disable-sp-asm", "CPPFLAGS=-DNO_WOLFSSL_SERVER -DWOLFSSL_NO_TLS12 -DNO_SESSION_CACHE -DWOLFSSL_AES_NO_UNROLL -DUSE_SLOW_SHA256 -DWOLFSSL_NO_ASYNC_IO -DWOLFSSL_DTLS_ONLY"]}, +{"name": "no-tlsv12-ocspstapling", + "comment": "--disable-tlsv12 with the two feature axes that reach TLS 1.2-only code from a TLS 1.3 build: OCSP stapling, whose chain-status path calls ProcessCSR_ex, and the OpenSSL compatibility layer, which is where most of the tests that hard-code a TLS 1.2 method live. Nothing else covers the plain option - dtls13-client-minimal above reaches the define through CPPFLAGS and strips most of the crypto with it, and psk.yml's static-psk-lowresource-tls13 pairs it with --disable-asn.", + "configure": ["--disable-tlsv12", "--enable-ocspstapling", "--enable-opensslextra"]}, +{"name": "no-tlsv12-ocspstapling2", + "comment": "status_request_v2 without status_request, which is the one stapling shape where the TLS 1.3 chain-status path is not compiled: ProcessCSR_ex then has only its TLS 1.2 caller, so compiling TLS 1.2 out left it with none and -Wunused-function broke the build. no-tlsv12-ocspstapling above enables v1 and cannot reach it.", + "configure": ["--disable-tlsv12", "--enable-ocspstapling2"]}, +{"name": "no-tlsv12-dtls13", + "comment": "DTLS 1.3 with the TLS 1.2 handshake compiled out, both sides. dtls13-client-minimal above covers the same version pair but is client-only through NO_WOLFSSL_SERVER, so the DTLS 1.3 server path under WOLFSSL_NO_TLS12 has no other coverage. This is also the one combination configure still accepts once TLS 1.2 is gone: DTLS without --enable-dtls13 is rejected, because DTLS 1.2 is the TLS 1.2 handshake. Connection ID and session tickets are on because both pull in DTLS 1.2-only test and record-layer code that the version gating has to reach.", + "configure": ["--disable-tlsv12", "--enable-dtls", "--enable-dtls13", + "--enable-dtlscid", "--enable-session-ticket"]}, +{"name": "no-tlsv12-sniffer", + "comment": "The sniffer picks its master-secret derivation, resumption key derivation, Finished parsing and record decryption by version, and with WOLFSSL_NO_TLS12 only the TLS 1.3 halves of those are compiled in. sniffer-curves-enckeys keeps TLS 1.2 enabled, so it never builds them that way; scripts/sniffer-testsuite.test probes snifftest for the versions it was built with and runs the TLS 1.3 legs here.", + "configure": ["--enable-sniffer", "--disable-tlsv12"]}, {"name": "opensslextra-no-filesystem-no-bio", "minutes": 0.9, "configure": ["--enable-opensslextra", "--disable-filesystem", "CPPFLAGS=-DNO_BIO"]}, {"name": "no-examples-no-malloc", "minutes": 0.8, diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b48daa5a1e..7baf2ec254b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1918,12 +1918,6 @@ add_option("WOLFSSL_TLSV12" "Enable TLS versions 1.2 (default: enabled)" "yes" "yes;no") -if(NOT WOLFSSL_TLSV12) - list(APPEND WOLFSSL_DEFINITIONS - "-DWOLFSSL_NO_TLS12" - "-DNO_OLD_TLS") -endif() - # TODO: - TLSv1.0 # - SSLv3 # - Stack size @@ -2571,6 +2565,34 @@ if (WOLFSSL_TLS13) ) endif() +if(NOT WOLFSSL_TLSV12) + # Compiling the pre-TLS-1.3 handshake out leaves the versions built on + # top of it with nothing to run on, so reject them the way configure does. + if(WOLFSSL_OLD_TLS) + message(FATAL_ERROR + "WOLFSSL_TLSV12=no cannot be combined with WOLFSSL_OLD_TLS=yes: " + "TLS 1.0 and 1.1 use the TLS 1.2 handshake.") + endif() + if(NOT WOLFSSL_TLS13) + message(FATAL_ERROR + "WOLFSSL_TLSV12=no needs WOLFSSL_TLS13=yes: no TLS version would " + "be left to negotiate.") + endif() + if(WOLFSSL_MCAST) + message(FATAL_ERROR + "WOLFSSL_TLSV12=no cannot be combined with WOLFSSL_MCAST: " + "multicast rides on DTLS 1.2 and its NULL cipher suite.") + endif() + if(WOLFSSL_DTLS AND NOT WOLFSSL_DTLS13) + message(FATAL_ERROR + "WOLFSSL_TLSV12=no with WOLFSSL_DTLS=yes needs WOLFSSL_DTLS13=yes: " + "DTLS 1.2 uses the TLS 1.2 handshake.") + endif() + # NO_OLD_TLS is already in the list: the check above leaves + # WOLFSSL_OLD_TLS off, which is what adds it. + list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_NO_TLS12") +endif() + # Session Ticket Extension add_option("WOLFSSL_SESSION_TICKET" "Enable Session Ticket (default: disabled)" diff --git a/ChangeLog.md b/ChangeLog.md index f3107ffb0bf..e0b6c62a64a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -2,6 +2,29 @@ ## Behavioral Changes +* **Behavioral change (`--disable-tlsv12` compiles TLS 1.2 out)**: the option + set the summary line and a few derived settings, but never defined + `WOLFSSL_NO_TLS12`, so the whole TLS 1.2 implementation was still built and a + peer could still negotiate it. The only place the define was added is a FIPS + bundle, whose condition skips it when the user asked for the option, so no + autotools configuration reached it; the CMake `WOLFSSL_TLSV12=no` path was + unaffected. The option now defines it, which is what the bundles that turn + the version off, `--enable-tinytls13` among them, have been documented as + doing. A build that passes `--disable-tlsv12` and still expects to negotiate + TLS 1.2 has to stop passing it. Because the pre-TLS-1.3 handshake is now + compiled out, configure rejects the combinations that depend on it: + `--enable-oldtls`, which builds on the TLS 1.2 handshake; TLS 1.3 off, which + would leave no version to negotiate; and DTLS without DTLS 1.3, for the same + reason on the datagram side; and multicast, which rides on DTLS 1.2 and its + NULL cipher suite. CMake rejects the same four for `-DWOLFSSL_TLSV12=no`; + it used to accept `-DWOLFSSL_OLD_TLS=yes` beside it and define `NO_OLD_TLS` + anyway, so the reported option and the build disagreed. The sniffer, the + examples and the test suite pick their code paths by version where they used + to assume TLS 1.2 was present, so `--disable-tlsv12`, that with + `--enable-ocspstapling --enable-opensslextra`, `--enable-sniffer + --disable-tlsv12` and `--enable-dtls --enable-dtls13 --enable-dtlscid + --enable-session-ticket --disable-tlsv12` now build and test cleanly. + * **Behavioral change (`wc_PufReadSram` health tests the raw SRAM readout)**: the raw readout is now health tested before the context accepts it, and a readout that cannot be SRAM power-on noise is rejected with `PUF_READ_E` diff --git a/configure.ac b/configure.ac index 5e303026b33..6a9cea2500e 100644 --- a/configure.ac +++ b/configure.ac @@ -9424,6 +9424,18 @@ then AM_CFLAGS="$AM_CFLAGS -DNO_SESSION_CACHE" fi +# Compile out TLS 1.2 itself, not just its derived settings. Runs after the +# bundles that turn the version off, so --enable-tinytls13 and friends land +# here too. The check keeps the option idempotent for the FIPS bundles that +# add the define themselves. +if test "$ENABLED_TLSV12" = "no" +then + case "$AM_CFLAGS" in + *-DWOLFSSL_NO_TLS12*) ;; + *) AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_NO_TLS12" ;; + esac +fi + # PKCS7 AC_ARG_ENABLE([pkcs7], [AS_HELP_STRING([--enable-pkcs7],[Enable PKCS7 (default: disabled)])], @@ -13106,6 +13118,30 @@ AS_IF([(test "x$ENABLED_DTLS" = "xno") && \ [AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_DTLS" ENABLED_DTLS=yes]) +# --disable-tlsv12 compiles the pre-TLS-1.3 handshake out, so the versions and +# features built on top of it cannot be left on, and something has to be left +# to negotiate. Runs here because old TLS, DTLS and the version flags are all +# final by this point. +if test "$ENABLED_TLS" != "no" && test "$ENABLED_TLSV12" = "no" +then + if test "$ENABLED_OLD_TLS" = "yes" + then + AC_MSG_ERROR([cannot disable tlsv12 while old TLS is enabled: TLS 1.0 and 1.1 use the TLS 1.2 handshake. Add --disable-oldtls, and drop --enable-tlsv10 or --enable-sslv3 if passed, since they turn it back on.]) + fi + if test "$ENABLED_TLS13" = "no" + then + AC_MSG_ERROR([cannot disable tlsv12 with tls13 disabled: no TLS version would be left to negotiate.]) + fi + if test "$ENABLED_MCAST" = "yes" + then + AC_MSG_ERROR([cannot disable tlsv12 with mcast enabled: multicast rides on DTLS 1.2 and its NULL cipher suite. Add --disable-mcast.]) + fi + if test "$ENABLED_DTLS" = "yes" && test "$ENABLED_DTLS13" = "no" + then + AC_MSG_ERROR([cannot disable tlsv12 while DTLS is enabled without dtls13: DTLS 1.2 uses the TLS 1.2 handshake. Add --enable-dtls13, or drop what enables DTLS (sctp, srtp, mcast and strongswan turn it on too).]) + fi +fi + # Multicast requires the null cipher AS_IF([test "x$ENABLED_NULL_CIPHER" = "xno" && \ test "x$ENABLED_MCAST" = "xyes"], diff --git a/examples/client/client.c b/examples/client/client.c index 0887423d82b..81470f32256 100644 --- a/examples/client/client.c +++ b/examples/client/client.c @@ -3418,7 +3418,8 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) method = wolfDTLSv1_3_client_method_ex; break; #endif /* WOLFSSL_DTLS13 */ - #if defined(OPENSSL_EXTRA) || defined(WOLFSSL_EITHER_SIDE) + #if (defined(OPENSSL_EXTRA) || defined(WOLFSSL_EITHER_SIDE)) && \ + !defined(WOLFSSL_NO_TLS12) case -3: method = wolfDTLSv1_2_method_ex; break; diff --git a/examples/echoclient/echoclient.c b/examples/echoclient/echoclient.c index 9e4267484bb..c3a9c5e08b1 100644 --- a/examples/echoclient/echoclient.c +++ b/examples/echoclient/echoclient.c @@ -134,7 +134,8 @@ void echoclient_test(void* args) #endif #if !defined(NO_TLS) - #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SNIFFER) + #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SNIFFER) && \ + !defined(WOLFSSL_NO_TLS12) method = wolfTLSv1_2_client_method(); #else method = wolfSSLv23_client_method(); @@ -174,10 +175,13 @@ void echoclient_test(void* args) err_sys("can't load ca buffer"); #endif -#if defined(WOLFSSL_SNIFFER) +#if defined(WOLFSSL_SNIFFER) && !defined(WOLFSSL_NO_TLS12) /* Only set if not running testsuite */ if (XSTRSTR(argv[0], "testsuite") == NULL) { - /* don't use EDH, can't sniff tmp keys */ + /* don't use EDH, can't sniff tmp keys. A TLS 1.3 sniffer needs a key + * log file or static ephemeral keys instead, so this static RSA suite + * is only pinned where TLS 1.2 exists. Advisory: a build without the + * suite's ciphers keeps the default list. */ SSL_CTX_set_cipher_list(ctx, "AES256-SHA"); } #endif diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index 65985b77350..b73ed3b15a7 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -151,7 +151,8 @@ THREAD_RETURN WOLFSSL_THREAD echoserver_test(void* args) tcp_listen(&sockfd, &port, useAnyAddr, 0, 0); #if !defined(NO_TLS) - #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SNIFFER) + #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SNIFFER) && \ + !defined(WOLFSSL_NO_TLS12) method = wolfTLSv1_2_server_method(); #else method = wolfSSLv23_server_method(); @@ -264,10 +265,13 @@ THREAD_RETURN WOLFSSL_THREAD echoserver_test(void* args) } #endif -#if defined(WOLFSSL_SNIFFER) +#if defined(WOLFSSL_SNIFFER) && !defined(WOLFSSL_NO_TLS12) /* Only set if not running testsuite */ if (XSTRSTR(argv[0], "testsuite") == NULL) { - /* don't use EDH, can't sniff tmp keys */ + /* don't use EDH, can't sniff tmp keys. A TLS 1.3 sniffer needs a key + * log file or static ephemeral keys instead, so this static RSA suite + * is only pinned where TLS 1.2 exists. Advisory: a build without the + * suite's ciphers keeps the default list. */ wolfSSL_CTX_set_cipher_list(ctx, "AES256-SHA"); } #endif diff --git a/examples/server/server.c b/examples/server/server.c index aebd9a9c524..a0de5cb90f6 100644 --- a/examples/server/server.c +++ b/examples/server/server.c @@ -2784,7 +2784,8 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) method = wolfDTLSv1_3_server_method_ex; break; #endif - #if defined(OPENSSL_EXTRA) || defined(WOLFSSL_EITHER_SIDE) + #if (defined(OPENSSL_EXTRA) || defined(WOLFSSL_EITHER_SIDE)) && \ + !defined(WOLFSSL_NO_TLS12) case -3: method = wolfDTLSv1_2_method_ex; break; diff --git a/src/internal.c b/src/internal.c index e32fe421eb7..a4f5c46e7f0 100644 --- a/src/internal.c +++ b/src/internal.c @@ -15777,8 +15777,15 @@ int CopyDecodedAcertToX509(WOLFSSL_X509_ACERT* x509, DecodedAcert* dAcert) } #endif /* WOLFSSL_ACERT */ +/* ProcessCSR() below needs this block under TLS 1.2, and TLS 1.3 reaches + * ProcessCSR_ex() from ProcessPeerCertsChainOCSPStatusCheck(), which is built + * for status_request only. Naming both keeps a status_request_v2-only build + * with TLS 1.2 compiled out from having no caller left. */ #if (defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \ - defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2)) && !defined(WOLFSSL_NO_TLS12) + defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2)) && \ + (!defined(WOLFSSL_NO_TLS12) || \ + (defined(HAVE_OCSP) && defined(WOLFSSL_TLS13) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST))) #if !defined(NO_WOLFSSL_CLIENT) || !defined(WOLFSSL_NO_CLIENT_AUTH) #ifndef NO_WOLFSSL_SERVER static int CsrDoStatusVerifyCb(WOLFSSL* ssl, byte* input, word32 inputSz, word32 idx, @@ -15803,8 +15810,10 @@ static int CsrDoStatusVerifyCb(WOLFSSL* ssl, byte* input, word32 inputSz, word32 } return ret; } -#endif +#endif /* !NO_WOLFSSL_SERVER */ +/* Parses one certificate_status message. TLS 1.3 reads the per-certificate + * entries of the chain through this, so it is not tied to TLS 1.2. */ static int ProcessCSR_ex(WOLFSSL* ssl, byte* input, word32* inOutIdx, word32 status_length, int idx) { @@ -15927,11 +15936,13 @@ static int ProcessCSR_ex(WOLFSSL* ssl, byte* input, word32* inOutIdx, return ret; } +#ifndef WOLFSSL_NO_TLS12 static int ProcessCSR(WOLFSSL* ssl, byte* input, word32* inOutIdx, word32 status_length) { return ProcessCSR_ex(ssl, input, inOutIdx, status_length, 0); } +#endif /* !WOLFSSL_NO_TLS12 */ #endif #endif @@ -26609,7 +26620,7 @@ int BuildMessage(WOLFSSL* ssl, byte* output, int outSz, const byte* input, const byte* encInput = NULL; #endif -#ifdef WOLFSSL_DTLS_CID +#if defined(WOLFSSL_DTLS_CID) && !defined(WOLFSSL_NO_TLS12) byte cidSz = 0; #endif diff --git a/src/sniffer.c b/src/sniffer.c index 989e85697ee..51110647f0a 100644 --- a/src/sniffer.c +++ b/src/sniffer.c @@ -542,7 +542,7 @@ typedef struct Flags { #endif byte gotFinished; /* processed finished */ byte secRenegEn; /* secure renegotiation enabled */ -#if !defined(HAVE_ENCRYPT_THEN_MAC) || defined(WOLFSSL_AEAD_ONLY) +#if !defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) byte etmUnsupported; /* peer negotiated RFC 7366, we cannot */ #endif #ifdef WOLFSSL_ASYNC_CRYPT @@ -2638,7 +2638,7 @@ static void FreeSetupKeysArgs(WOLFSSL* ssl, void* pArgs) } /* Process Keys */ -#if !defined(HAVE_ENCRYPT_THEN_MAC) || defined(WOLFSSL_AEAD_ONLY) +#if !defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) /* RFC 7366 only covers block ciphers and a peer must not negotiate it for an * AEAD or stream suite, so a session that asked for it is still readable here * unless the negotiated suite turns out to be a block cipher. @@ -3396,7 +3396,7 @@ static int SetupKeys(const byte* input, int* sslBytes, SnifferSession* session, ret = WOLFSSL_FATAL_ERROR; break; } - #if !defined(HAVE_ENCRYPT_THEN_MAC) || defined(WOLFSSL_AEAD_ONLY) + #if !defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) if (CheckEncryptThenMac(session, error) != 0) { ret = WOLFSSL_FATAL_ERROR; break; } @@ -3424,10 +3424,20 @@ static int SetupKeys(const byte* input, int* sslBytes, SnifferSession* session, else #endif /* WOLFSSL_TLS13 */ { +#ifndef WOLFSSL_NO_TLS12 ret = MakeMasterSecret(session->sslServer); ret += MakeMasterSecret(session->sslClient); ret += SetKeysSide(session->sslServer, ENCRYPT_AND_DECRYPT_SIDE); ret += SetKeysSide(session->sslClient, ENCRYPT_AND_DECRYPT_SIDE); +#else + /* No master secret is computed here, so installing cipher state + * would be wrong. */ + SetError(UNSUPPORTED_TLS_VER_STR, error, session, + FATAL_ERROR_STATE); + session->verboseErr = 1; + ret = WOLFSSL_FATAL_ERROR; + break; +#endif } if (ret != 0) { SetError(BAD_DERIVE_STR, error, session, FATAL_ERROR_STATE); @@ -3850,7 +3860,7 @@ static int DoResume(SnifferSession* session, char* error) return WOLFSSL_FATAL_ERROR; } -#if !defined(HAVE_ENCRYPT_THEN_MAC) || defined(WOLFSSL_AEAD_ONLY) +#if !defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) if (CheckEncryptThenMac(session, error) != 0) return WOLFSSL_FATAL_ERROR; #endif @@ -3875,6 +3885,7 @@ static int DoResume(SnifferSession* session, char* error) else #endif { +#ifndef WOLFSSL_NO_TLS12 if (IsTLS(session->sslServer)) { ret = DeriveTlsKeys(session->sslServer); ret += DeriveTlsKeys(session->sslClient); @@ -3887,6 +3898,12 @@ static int DoResume(SnifferSession* session, char* error) } ret += SetKeysSide(session->sslServer, ENCRYPT_AND_DECRYPT_SIDE); ret += SetKeysSide(session->sslClient, ENCRYPT_AND_DECRYPT_SIDE); +#else + /* No keys were derived, so installing cipher state would be wrong. */ + SetError(UNSUPPORTED_TLS_VER_STR, error, session, FATAL_ERROR_STATE); + session->verboseErr = 1; + return WOLFSSL_FATAL_ERROR; +#endif } if (ret != 0) { @@ -4010,7 +4027,7 @@ static int ProcessServerHello(int msgSz, const byte* input, int* sslBytes, #if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) session->sslServer->options.encThenMac = 0; session->sslClient->options.encThenMac = 0; -#else +#elif !defined(WOLFSSL_AEAD_ONLY) session->flags.etmUnsupported = 0; #endif @@ -4138,7 +4155,7 @@ static int ProcessServerHello(int msgSz, const byte* input, int* sslBytes, session->sslServer->options.encThenMac = 1; session->sslClient->options.encThenMac = 1; break; - #else + #elif !defined(WOLFSSL_AEAD_ONLY) case EXT_ENCRYPT_THEN_MAC: /* The session negotiated RFC 7366, but this build cannot * strip the MAC ahead of decryption. Only a block cipher @@ -4786,8 +4803,14 @@ static int ProcessFinished(const byte* input, int size, int* sslBytes, else #endif { +#ifndef WOLFSSL_NO_TLS12 ret = DoFinished(ssl, input, &inOutIdx, (word32)size, (word32)*sslBytes, SNIFF); +#else + SetError(UNSUPPORTED_TLS_VER_STR, error, session, FATAL_ERROR_STATE); + session->verboseErr = 1; + return WOLFSSL_FATAL_ERROR; +#endif } *sslBytes -= (int)inOutIdx; @@ -5132,6 +5155,9 @@ static int DoHandShake(const byte* input, int* sslBytes, /* For ciphers that use AEAD use the encrypt routine to * bypass the auth tag checking */ +/* The record layout below TLS 1.3 carries an explicit IV and its own + * additional data, so this path exists only where TLS 1.2 does. */ +#ifndef WOLFSSL_NO_TLS12 static int DecryptDo(WOLFSSL* ssl, byte* plain, const byte* input, word16 sz) { @@ -5384,13 +5410,16 @@ static int DecryptTls(WOLFSSL* ssl, byte* plain, const byte* input, return ret; } +#endif /* !WOLFSSL_NO_TLS12 */ /* Decrypt input message into output, adjust output steam if needed */ static const byte* DecryptMessage(WOLFSSL* ssl, const byte* input, word32 sz, byte* output, int* error, int* advance, RecordLayerHeader* rh) { +#ifndef WOLFSSL_AEAD_ONLY int ivExtra = 0; +#endif int ret; word32 macExtra = 0; @@ -5416,8 +5445,13 @@ static const byte* DecryptMessage(WOLFSSL* ssl, const byte* input, word32 sz, else #endif { +#ifndef WOLFSSL_NO_TLS12 XMEMCPY(&ssl->curRL, rh, RECORD_HEADER_SZ); ret = DecryptTls(ssl, output, input, sz - macExtra); +#else + *error = VERSION_ERROR; + return NULL; +#endif } #ifdef WOLFSSL_ASYNC_CRYPT /* for async the symmetric operations are blocking */ @@ -5438,11 +5472,13 @@ static const byte* DecryptMessage(WOLFSSL* ssl, const byte* input, word32 sz, ssl->curSize = sz; ssl->keys.encryptSz = sz; +#ifndef WOLFSSL_AEAD_ONLY if (ssl->options.tls1_1 && ssl->specs.cipher_type == block) { output += ssl->specs.block_size; /* go past TLSv1.1 IV */ ivExtra = ssl->specs.block_size; *advance = ssl->specs.block_size; } +#endif if (ssl->specs.cipher_type == aead) { *advance = ssl->specs.aead_mac_size; @@ -5453,6 +5489,7 @@ static const byte* DecryptMessage(WOLFSSL* ssl, const byte* input, word32 sz, else ssl->keys.padSz = ssl->specs.hash_size; +#ifndef WOLFSSL_AEAD_ONLY if (ssl->specs.cipher_type == block) { /* last pad bytes indicates length */ word32 pad = 0; @@ -5462,6 +5499,7 @@ static const byte* DecryptMessage(WOLFSSL* ssl, const byte* input, word32 sz, } ssl->keys.padSz += pad; } +#endif #ifdef WOLFSSL_TLS13 if (IsAtLeastTLSv1_3(ssl->version)) { @@ -6313,6 +6351,7 @@ static int FindNextRecordInAssembly(SnifferSession* session, return 0; } +#ifndef WOLFSSL_AEAD_ONLY else if (ssl->specs.cipher_type == block) { int ivPos = (int)(curr->end - curr->begin - ssl->specs.block_size + 1); @@ -6329,6 +6368,7 @@ static int FindNextRecordInAssembly(SnifferSession* session, #endif } } +#endif /* !WOLFSSL_AEAD_ONLY */ Trace(DROPPING_LOST_FRAG_STR); #ifdef WOLFSSL_SNIFFER_STATS diff --git a/tests/api.c b/tests/api.c index 3618a957733..845910c1279 100644 --- a/tests/api.c +++ b/tests/api.c @@ -6872,7 +6872,7 @@ int test_wolfSSL_client_server_nofail_memio(test_ssl_cbf* client_cb, #ifdef HAVE_IO_TESTS_DEPENDENCIES #ifdef WOLFSSL_SESSION_EXPORT -#ifdef WOLFSSL_DTLS +#if defined(WOLFSSL_DTLS) && !defined(WOLFSSL_NO_TLS12) /* set up function for sending session information */ static int test_export(WOLFSSL* inSsl, byte* buf, word32 sz, void* userCtx) { @@ -8085,7 +8085,8 @@ THREAD_RETURN WOLFSSL_THREAD run_wolfssl_server(void* args) #ifdef WOLFSSL_ENCRYPTED_KEYS wolfSSL_CTX_set_default_passwd_cb(ctx, PasswordCallBack); #endif -#if defined(WOLFSSL_SESSION_EXPORT) && defined(WOLFSSL_DTLS) +#if defined(WOLFSSL_SESSION_EXPORT) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) if (callbacks->method == wolfDTLSv1_2_server_method) { if (wolfSSL_CTX_dtls_set_export(ctx, test_export) != WOLFSSL_SUCCESS) goto cleanup; @@ -10052,7 +10053,8 @@ static int test_wolfSSL_UseMaxFragment(void) wolfSSL_CTX_free(ctx); #if defined(OPENSSL_EXTRA) && defined(HAVE_MAX_FRAGMENT) && \ - defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(WOLFSSL_NO_TLS12) /* check negotiated max fragment size */ { WOLFSSL *ssl_c = NULL; @@ -12828,7 +12830,7 @@ static int test_wolfSSL_mcast(void) EXPECT_DECLS; #if defined(WOLFSSL_DTLS) && defined(WOLFSSL_MULTICAST) && \ (defined(WOLFSSL_TLS13) || defined(WOLFSSL_SNIFFER)) && \ - !defined(NO_WOLFSSL_CLIENT) + !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte preMasterSecret[512]; @@ -12857,7 +12859,7 @@ static int test_wolfSSL_mcast(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); #endif /* WOLFSSL_DTLS && WOLFSSL_MULTICAST && (WOLFSSL_TLS13 || - * WOLFSSL_SNIFFER) */ + * WOLFSSL_SNIFFER) && !NO_WOLFSSL_CLIENT && !WOLFSSL_NO_TLS12 */ return EXPECT_RESULT(); } @@ -15431,7 +15433,8 @@ static int test_wolfSSL_set1_host(void) #if defined(OPENSSL_ALL) && !defined(NO_RSA) && !defined(NO_CERTS) && \ !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ - defined(HAVE_ECC) && !defined(NO_TLS) && defined(HAVE_AESGCM) + defined(HAVE_ECC) && !defined(NO_TLS) && defined(HAVE_AESGCM) && \ + !defined(WOLFSSL_NO_TLS12) static int test_wolfSSL_get_client_ciphers_ctx_ready(WOLFSSL_CTX* ctx) { EXPECT_DECLS; @@ -15474,7 +15477,8 @@ static int test_wolfSSL_get_client_ciphers(void) EXPECT_DECLS; #if defined(OPENSSL_ALL) && !defined(NO_RSA) && !defined(NO_CERTS) && \ !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ - defined(HAVE_ECC) && !defined(NO_TLS) && defined(HAVE_AESGCM) + defined(HAVE_ECC) && !defined(NO_TLS) && defined(HAVE_AESGCM) && \ + !defined(WOLFSSL_NO_TLS12) test_ssl_cbf server_cb; test_ssl_cbf client_cb; @@ -15553,7 +15557,8 @@ static int test_wolfSSL_CTX_set_client_CA_list(void) ExpectIntEQ(sk_X509_NAME_find(names, name), i); } -#if !defined(SINGLE_THREADED) && defined(SESSION_CERTS) +#if !defined(SINGLE_THREADED) && defined(SESSION_CERTS) && \ + !defined(WOLFSSL_NO_TLS12) { tcp_ready ready; func_args server_args; @@ -18721,6 +18726,7 @@ static int test_wolfSSL_Tls13_ECH_tamper_client(void) #if defined(HAVE_IO_TESTS_DEPENDENCIES) && \ defined(OPENSSL_EXTRA) && !defined(NO_CERTS) && \ defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) +#ifndef WOLFSSL_NO_TLS12 static int post_auth_version_cb(WOLFSSL* ssl) { EXPECT_DECLS; @@ -18749,6 +18755,7 @@ static int post_auth_version_client_cb(WOLFSSL* ssl) #endif return EXPECT_RESULT(); } +#endif /* !WOLFSSL_NO_TLS12 */ static int post_auth_cb(WOLFSSL* ssl) { @@ -18784,6 +18791,7 @@ static int test_wolfSSL_Tls13_postauth(void) test_ssl_cbf server_cbf; test_ssl_cbf client_cbf; +#ifndef WOLFSSL_NO_TLS12 /* test version failure doing post auth with TLS 1.2 connection */ XMEMSET(&server_cbf, 0, sizeof(server_cbf)); XMEMSET(&client_cbf, 0, sizeof(client_cbf)); @@ -18795,6 +18803,7 @@ static int test_wolfSSL_Tls13_postauth(void) ExpectIntEQ(test_wolfSSL_client_server_nofail_memio(&client_cbf, &server_cbf, NULL), TEST_SUCCESS); +#endif /* !WOLFSSL_NO_TLS12 */ /* tests on post auth with TLS 1.3 */ XMEMSET(&server_cbf, 0, sizeof(server_cbf)); @@ -24029,7 +24038,8 @@ static int test_wolfSSL_OPENSSL_hexstr2buf(void) static int test_wolfSSL_sk_CIPHER_description(void) { EXPECT_DECLS; -#if !defined(NO_RSA) && !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) +#if !defined(NO_RSA) && !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(WOLFSSL_NO_TLS12) const long flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_COMPRESSION; int i; int numCiphers = 0; @@ -37114,7 +37124,8 @@ static int test_short_session_id(void) #if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ - defined(HAVE_IO_TESTS_DEPENDENCIES) && defined(HAVE_SECURE_RENEGOTIATION) + defined(HAVE_IO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SECURE_RENEGOTIATION) && !defined(WOLFSSL_NO_TLS12) static WOLFSSL_SESSION* test_wolfSSL_SCR_after_resumption_session = NULL; @@ -37735,7 +37746,17 @@ static int test_revoked_loaded_int_cert(void) -#if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +/* The parameter table in test_self_signed_stapling() must not come out empty: + * status_request_v2 is a TLS v1.2-only extension, and TLS v1.3 can only be + * exercised through status_request v1, so a build that has just one of the two + * with the matching version compiled out has nothing left to run. */ +#if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + ((defined(WOLFSSL_TLS13) && defined(HAVE_CERTIFICATE_STATUS_REQUEST)) || \ + (!defined(WOLFSSL_NO_TLS12) && \ + (defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2)))) +#define TEST_SELF_SIGNED_STAPLING + #ifdef HAVE_CERTIFICATE_STATUS_REQUEST static int test_self_signed_stapling_client_v1_ctx_ready(WOLFSSL_CTX* ctx) { @@ -37747,7 +37768,7 @@ static int test_self_signed_stapling_client_v1_ctx_ready(WOLFSSL_CTX* ctx) } #endif -#ifdef HAVE_CERTIFICATE_STATUS_REQUEST_V2 +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(WOLFSSL_NO_TLS12) static int test_self_signed_stapling_client_v2_ctx_ready(WOLFSSL_CTX* ctx) { EXPECT_DECLS; @@ -37767,23 +37788,18 @@ static int test_self_signed_stapling_client_v2_multi_ctx_ready(WOLFSSL_CTX* ctx) } #endif -#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) \ - || defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) static int test_self_signed_stapling_server_ctx_ready(WOLFSSL_CTX* ctx) { EXPECT_DECLS; ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx), 1); return EXPECT_RESULT(); } -#endif -#endif +#endif /* TEST_SELF_SIGNED_STAPLING */ static int test_self_signed_stapling(void) { EXPECT_DECLS; -#if (defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \ - defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2)) && \ - !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +#ifdef TEST_SELF_SIGNED_STAPLING test_ssl_cbf client_cbf; test_ssl_cbf server_cbf; size_t i; diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index f91ea99eb1c..a5fc5bf47fa 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -48,7 +48,8 @@ int test_dtls12_basic_connection_id(void) { EXPECT_DECLS; -#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS_CID) +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS_CID) && !defined(WOLFSSL_NO_TLS12) unsigned char client_cid[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; unsigned char server_cid[] = { 0, 1, 2, 3, 4, 5 }; unsigned char readBuf[40]; @@ -1116,6 +1117,8 @@ int test_dtls13_cid_msg_malformed(void) int test_dtls_version_checking(void) { +/* The test drives a DTLS 1.2 handshake, which needs TLS 1.2. */ +#ifndef WOLFSSL_NO_TLS12 EXPECT_DECLS; #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; @@ -1161,6 +1164,9 @@ int test_dtls_version_checking(void) wolfSSL_CTX_free(ctx_s); #endif /* HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES && WOLFSSL_DTLS */ return EXPECT_RESULT(); +#else + return TEST_SKIPPED; +#endif } #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) @@ -1341,6 +1347,7 @@ int test_dtls_drop_invalid_record_during_handshake(void) { EXPECT_DECLS; +#ifndef WOLFSSL_NO_TLS12 /* Client drops a corrupted server flight: unknown type, then over-length. */ ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method, 0, 1), TEST_SUCCESS); @@ -1353,6 +1360,7 @@ int test_dtls_drop_invalid_record_during_handshake(void) wolfDTLSv1_2_server_method, 1, 0), TEST_SUCCESS); ExpectIntEQ(test_dtls_drop_invalid_record(wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method, 1, 1), TEST_SUCCESS); +#endif #ifdef WOLFSSL_DTLS13 /* Same silent-drop behavior on the DTLS 1.3 receive path (all four @@ -1583,6 +1591,8 @@ int test_dtls13_oversized_msg_length(void) #if !defined(WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS) int test_dtls12_short_read(void) { +/* The test drives a DTLS 1.2 handshake, which needs TLS 1.2. */ +#ifndef WOLFSSL_NO_TLS12 EXPECT_DECLS; WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; WOLFSSL *ssl_c = NULL, *ssl_s = NULL; @@ -1634,6 +1644,9 @@ int test_dtls12_short_read(void) } return EXPECT_RESULT(); +#else + return TEST_SKIPPED; +#endif } #else int test_dtls12_short_read(void) @@ -1645,6 +1658,8 @@ int test_dtls12_short_read(void) #if !defined(WOLFSSL_DTLS_RECORDS_CAN_SPAN_DATAGRAMS) int test_dtls12_record_length_mismatch(void) { +/* The test drives a DTLS 1.2 handshake, which needs TLS 1.2. */ +#ifndef WOLFSSL_NO_TLS12 EXPECT_DECLS; WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; WOLFSSL *ssl_c = NULL, *ssl_s = NULL; @@ -1681,11 +1696,24 @@ int test_dtls12_record_length_mismatch(void) ExpectIntEQ(ret, TEST_SUCCESS); return EXPECT_RESULT(); +#else + return TEST_SKIPPED; +#endif } int test_dtls_record_cross_boundaries(void) { +/* A record must not span datagrams in either DTLS version, so run whichever + * one the build has. */ +#if !defined(WOLFSSL_NO_TLS12) || defined(WOLFSSL_DTLS13) EXPECT_DECLS; +#ifdef WOLFSSL_NO_TLS12 + #define TEST_DTLS_CLIENT_METHOD wolfDTLSv1_3_client_method + #define TEST_DTLS_SERVER_METHOD wolfDTLSv1_3_server_method +#else + #define TEST_DTLS_CLIENT_METHOD wolfDTLSv1_2_client_method + #define TEST_DTLS_SERVER_METHOD wolfDTLSv1_2_server_method +#endif WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; WOLFSSL *ssl_c = NULL, *ssl_s = NULL; struct test_memio_ctx test_ctx; @@ -1696,11 +1724,20 @@ int test_dtls_record_cross_boundaries(void) /* Setup DTLS contexts */ ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, - wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), + TEST_DTLS_CLIENT_METHOD, TEST_DTLS_SERVER_METHOD), 0); /* Complete handshake */ ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* The handshake does not drain the transport, and DTLS 1.3 can leave an + * ACK behind, so start from an empty buffer to keep the two records that + * follow at indices 0 and 1. DTLS 1.2 leaves nothing, and that is still + * worth checking rather than clearing away. */ +#ifdef WOLFSSL_NO_TLS12 + test_memio_clear_buffer(&test_ctx, 0); +#else + ExpectIntEQ(test_ctx.s_len, 0); +#endif /* create a first record in the buffer */ wolfSSL_SetLoggingPrefix("client"); @@ -1744,7 +1781,12 @@ int test_dtls_record_cross_boundaries(void) wolfSSL_CTX_free(ctx_s); wolfSSL_CTX_free(ctx_c); +#undef TEST_DTLS_CLIENT_METHOD +#undef TEST_DTLS_SERVER_METHOD return EXPECT_RESULT(); +#else + return TEST_SKIPPED; +#endif } #else int test_dtls12_record_length_mismatch(void) @@ -1759,6 +1801,8 @@ int test_dtls_record_cross_boundaries(void) int test_dtls_short_ciphertext(void) { +/* The test drives a DTLS 1.2 handshake, which needs TLS 1.2. */ +#ifndef WOLFSSL_NO_TLS12 EXPECT_DECLS; WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; WOLFSSL *ssl_c = NULL, *ssl_s = NULL; @@ -1801,6 +1845,9 @@ int test_dtls_short_ciphertext(void) ExpectIntEQ(ret, TEST_SUCCESS); return EXPECT_RESULT(); +#else + return TEST_SKIPPED; +#endif } #else int test_dtls_drop_invalid_record_during_handshake(void) @@ -2960,7 +3007,8 @@ int test_dtls_set_session_min_downgrade(void) { EXPECT_DECLS; #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ - defined(WOLFSSL_DTLS13) && defined(HAVE_SESSION_TICKET) + defined(WOLFSSL_DTLS13) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; WOLFSSL *ssl_c = NULL, *ssl_s = NULL; WOLFSSL_SESSION *sess = NULL; @@ -7629,7 +7677,7 @@ int test_wolfSSL_dtls_srtp_keying_material(void) #if defined(WOLFSSL_DTLS) && defined(WOLFSSL_MULTICAST) && \ (defined(WOLFSSL_TLS13) || defined(WOLFSSL_SNIFFER)) && \ - !defined(NO_WOLFSSL_CLIENT) + !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) static int test_dtls_mcast_highwater_cb(unsigned short peerId, unsigned int maxSeq, unsigned int curSeq, void* ctx) { @@ -7646,7 +7694,7 @@ int test_wolfSSL_mcast_peers(void) EXPECT_DECLS; #if defined(WOLFSSL_DTLS) && defined(WOLFSSL_MULTICAST) && \ (defined(WOLFSSL_TLS13) || defined(WOLFSSL_SNIFFER)) && \ - !defined(NO_WOLFSSL_CLIENT) + !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; int hwCtx = 0; @@ -7891,7 +7939,7 @@ int test_wolfSSL_set_mtu_compat(void) EXPECT_DECLS; #if defined(WOLFSSL_DTLS) && defined(OPENSSL_EXTRA) && \ (defined(WOLFSSL_SCTP) || defined(WOLFSSL_DTLS_MTU)) && \ - !defined(NO_WOLFSSL_CLIENT) + !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -7944,7 +7992,7 @@ int test_wolfSSL_CTX_mcast_set_member_id(void) EXPECT_DECLS; #if defined(WOLFSSL_DTLS) && defined(WOLFSSL_MULTICAST) && \ (defined(WOLFSSL_TLS13) || defined(WOLFSSL_SNIFFER)) && \ - !defined(NO_WOLFSSL_CLIENT) + !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; ExpectIntEQ(wolfSSL_CTX_mcast_set_member_id(NULL, 0), @@ -7967,7 +8015,7 @@ int test_wolfSSL_mcast_read(void) EXPECT_DECLS; #if defined(WOLFSSL_DTLS) && defined(WOLFSSL_MULTICAST) && \ (defined(WOLFSSL_TLS13) || defined(WOLFSSL_SNIFFER)) && \ - !defined(NO_WOLFSSL_CLIENT) + !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; word16 id = 0; @@ -8080,8 +8128,16 @@ int test_wolfSSL_dtls_got_timeout(void) int test_wolfSSL_DTLS_SetCookieSecret(void) { EXPECT_DECLS; +/* Only the argument handling is under test, which is the same in either DTLS + * version, so run whichever one the build has. */ #if defined(WOLFSSL_DTLS) && !defined(NO_WOLFSSL_SERVER) && \ + (!defined(WOLFSSL_NO_TLS12) || defined(WOLFSSL_DTLS13)) && \ (defined(NO_CERTS) || !defined(NO_RSA)) +#ifdef WOLFSSL_NO_TLS12 + #define TEST_DTLS_COOKIE_METHOD wolfDTLSv1_3_server_method +#else + #define TEST_DTLS_COOKIE_METHOD wolfDTLSv1_2_server_method +#endif WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte secret1[32]; @@ -8094,7 +8150,7 @@ int test_wolfSSL_DTLS_SetCookieSecret(void) ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(NULL, secret1, sizeof(secret1)), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_2_server_method())); + ExpectNotNull(ctx = wolfSSL_CTX_new(TEST_DTLS_COOKIE_METHOD())); #ifndef NO_CERTS /* A server WOLFSSL needs a key and certificate set on the context. */ ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, CERT_FILETYPE), @@ -8117,6 +8173,7 @@ int test_wolfSSL_DTLS_SetCookieSecret(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); +#undef TEST_DTLS_COOKIE_METHOD #endif return EXPECT_RESULT(); } @@ -8126,7 +8183,7 @@ int test_wolfSSL_set_secret(void) EXPECT_DECLS; #if defined(WOLFSSL_DTLS) && defined(WOLFSSL_MULTICAST) && \ (defined(WOLFSSL_TLS13) || defined(WOLFSSL_SNIFFER)) && \ - !defined(NO_WOLFSSL_CLIENT) + !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte preMasterSecret[16]; diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index c9c0c8a44b6..67233e17e7a 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -1390,8 +1390,11 @@ int test_tls12_ec_point_formats_no_uncompressed_non_ecc(void) int test_dtls_fallback_scsv(void) { EXPECT_DECLS; +/* The fallback is to DTLS 1.2, so the server needs that version to downgrade + * to for the alert to be the one under test. */ #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ - defined(WOLFSSL_DTLS) && defined(WOLFSSL_DTLS13) + defined(WOLFSSL_DTLS) && defined(WOLFSSL_DTLS13) && \ + !defined(WOLFSSL_NO_TLS12) const byte clientHello[] = { /* DTLS record header: handshake, DTLS 1.2, epoch 0, seq 0, length 56 */ 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index 1b9a51be8d2..7fca5a37720 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -929,8 +929,9 @@ int test_certificate_authorities_certificate_request(void) { #ifdef WOLFSSL_DTLS13 {wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method, 1}, #endif -#if defined(WOLFSSL_DTLS) && (defined(OPENSSL_ALL) || \ - defined(WOLFSSL_NGINX) || defined(HAVE_LIGHTY)) +#if defined(WOLFSSL_DTLS) && !defined(WOLFSSL_NO_TLS12) && \ + (defined(OPENSSL_ALL) || defined(WOLFSSL_NGINX) || \ + defined(HAVE_LIGHTY)) {wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method, 1}, #endif }; @@ -1074,7 +1075,8 @@ static int certificate_authorities_server_cb(WOLFSSL *ssl, void *_arg) { #if defined(HAVE_TRUSTED_CA) && !defined(NO_SHA) && \ defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ - !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) + !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(WOLFSSL_NO_TLS12) /* Walk the TLSX list to find an extension by type. Avoids calling the * WOLFSSL_LOCAL TLSX_Find which is not available in shared library builds. */ static TLSX* test_TLSX_find_ext(TLSX* list, TLSX_Type type) @@ -1093,7 +1095,8 @@ int test_TLSX_TCA_Find(void) EXPECT_DECLS; #if defined(HAVE_TRUSTED_CA) && !defined(NO_SHA) && \ defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ - !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) + !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(WOLFSSL_NO_TLS12) /* Two different 20-byte SHA1 ids */ byte id_A[WC_SHA_DIGEST_SIZE]; byte id_B[WC_SHA_DIGEST_SIZE]; diff --git a/tests/api/test_x509.c b/tests/api/test_x509.c index faa710891da..6b13580076a 100644 --- a/tests/api/test_x509.c +++ b/tests/api/test_x509.c @@ -460,7 +460,7 @@ int test_x509_set_serialNumber(void) #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ (defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)) && \ !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ - !defined(NO_WOLFSSL_SERVER) + !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) /* Verify callback that accepts all certificates regardless of errors. */ static int accept_all_verify_cb(int preverify, WOLFSSL_X509_STORE_CTX* store) @@ -558,7 +558,7 @@ int test_x509_time_field_overread_via_tls(void) #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ (defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)) && \ !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ - !defined(NO_WOLFSSL_SERVER) + !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) struct test_memio_ctx test_ctx; WOLFSSL_CTX* ctx_c = NULL; WOLFSSL_CTX* ctx_s = NULL; From 3b103bdaaa388d29ab2fd1938415a6cbb7890ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Mon, 31 Aug 2026 12:54:14 +0200 Subject: [PATCH 3/6] Build dtls_bench without the server or the client side examples/benchmark/dtls_bench.c calls wolfSSL_accept and wolfSSL_connect unconditionally, so a build that compiles either side out fails to link: Undefined symbols: "_wolfSSL_accept", referenced from: _main in dtls_bench.o That is the os-check dtls13-client-minimal entry, which passes CPPFLAGS=-DNO_WOLFSSL_SERVER; os-check.yml does not pass --disable-examples, so the benchmark is always built. Compile each DTLS leg out with its own side, the way examples/server/server.c does, and report the missing side from main(). pick_method() and set_mtu() serve only those two legs and go with them, and the send sink is used by the client leg alone, so none of them are left unused. The plain UDP legs need neither side and keep working. --- examples/benchmark/dtls_bench.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/examples/benchmark/dtls_bench.c b/examples/benchmark/dtls_bench.c index 5df106a1ad2..21b22f480cb 100644 --- a/examples/benchmark/dtls_bench.c +++ b/examples/benchmark/dtls_bench.c @@ -124,6 +124,7 @@ static double now_sec(void) return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9; } +#ifndef NO_WOLFSSL_CLIENT /* Post-handshake send sink. Pretends every byte was transmitted but does * nothing; the kernel's UDP/IP path never runs. Used via -z to measure the * pure wolfSSL encrypt-and-frame ceiling, free of any I/O cost. */ @@ -132,6 +133,7 @@ static int dtls_bench_sink_send(WOLFSSL* ssl, char* buf, int sz, void* ctx) (void)ssl; (void)buf; (void)ctx; return sz; } +#endif /* !NO_WOLFSSL_CLIENT */ static void print_stats(const char* dir, long long bytes, double sec) { @@ -338,6 +340,9 @@ static int bind_to_iface(int fd, const char* ifname) #endif } +/* Only the DTLS legs below need these; the plain-UDP baseline does not. */ +#if !defined(NO_WOLFSSL_SERVER) || !defined(NO_WOLFSSL_CLIENT) + static WOLFSSL_METHOD* pick_method(int version, int isServer) { if (version == 13) { @@ -404,6 +409,8 @@ static int set_mtu(WOLFSSL* ssl, int mtu) #endif } +#endif /* !NO_WOLFSSL_SERVER || !NO_WOLFSSL_CLIENT */ + /* ----- Plain-UDP baseline (-n) ----- */ static int udp_server(const cfg_t* c) @@ -523,6 +530,8 @@ static int udp_client(const cfg_t* c) /* ----- DTLS server ----- */ +#ifndef NO_WOLFSSL_SERVER + static int dtls_server(const cfg_t* c) { int ret = 1; @@ -658,8 +667,12 @@ static int dtls_server(const cfg_t* c) return ret; } +#endif /* !NO_WOLFSSL_SERVER */ + /* ----- DTLS client ----- */ +#ifndef NO_WOLFSSL_CLIENT + static int dtls_client(const cfg_t* c) { int ret = 1; @@ -797,6 +810,8 @@ static int dtls_client(const cfg_t* c) return ret; } +#endif /* !NO_WOLFSSL_CLIENT */ + int main(int argc, char** argv) { cfg_t c; @@ -807,7 +822,20 @@ int main(int argc, char** argv) if (c.plainUdp) { return c.isServer ? udp_server(&c) : udp_client(&c); } - return c.isServer ? dtls_server(&c) : dtls_client(&c); + if (c.isServer) { +#ifndef NO_WOLFSSL_SERVER + return dtls_server(&c); +#else + fprintf(stderr, "DTLS server not compiled in!\n"); + return 1; +#endif + } +#ifndef NO_WOLFSSL_CLIENT + return dtls_client(&c); +#else + fprintf(stderr, "DTLS client not compiled in!\n"); + return 1; +#endif } #else /* DTLS_BENCH_ENABLED */ From f0b78fb9ba6f791f537de15cbd5e3212423ebb70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Mon, 31 Aug 2026 17:57:11 +0200 Subject: [PATCH 4/6] Complete a TLS 1.3 handshake with the ECC PK callbacks myEccKeyGen() generated the key share straight into the library's key object, and myEccSharedSecret() is handed only the peer's key, so a TLS 1.3 client had no private key left to reach and every handshake ended with: wolfSSL_connect error -170, ECC input argument wrong type, invalid input TEST_PK_PRIVKEY already kept the key on the application's side, which is what a real PK-callback application does anyway; it just was not the default. Keep it whenever the connection is TLS or DTLS v1.3 as well, and have the shared secret callback pick the key by the same question its key gen asked, not by a flag key gen has not set yet. The union holding it stops being TEST_PK_PRIVKEY-only, and its comment stops naming TLS v1.2 as the version that needs it. Neither example zeroed its PkCbInfo, so hasKeyGen started as stack garbage. Only the TEST_PK_PRIVKEY paths read it before, which hid that; zero it in both. scripts/pkcallbacks.test only ever ran the build's default version, and that is TLS v1.2 wherever it is compiled in, so none of the above was covered. Run the default and then each version the build has. --- examples/client/client.c | 10 +++ examples/server/server.c | 10 +++ scripts/pkcallbacks.test | 31 +++++-- wolfssl/test.h | 174 +++++++++++++++++++++++++++++---------- 4 files changed, 176 insertions(+), 49 deletions(-) diff --git a/examples/client/client.c b/examples/client/client.c index 81470f32256..16c40612290 100644 --- a/examples/client/client.c +++ b/examples/client/client.c @@ -2470,6 +2470,12 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) ((func_args*)args)->return_code = -1; /* error state */ +#ifdef HAVE_PK_CALLBACKS + /* The ECC callbacks read keyGenCnt whether or not certificates are + * compiled in, so this cannot sit inside the NO_CERTS block below. */ + XMEMSET(&pkCbInfo, 0, sizeof(pkCbInfo)); +#endif + #ifndef NO_RSA verifyCert = caCertFile; ourCert = cliCertFile; @@ -5117,6 +5123,10 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) exit: +#ifdef HAVE_PK_CALLBACKS + CleanupPkCallbackContexts(&pkCbInfo); +#endif + #ifdef WOLFSSL_WOLFSENTRY_HOOKS wolfsentry_ret = wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(&wolfsentry, NULL)); diff --git a/examples/server/server.c b/examples/server/server.c index a0de5cb90f6..41ada9fc131 100644 --- a/examples/server/server.c +++ b/examples/server/server.c @@ -1878,6 +1878,12 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) ((func_args*)args)->return_code = -1; /* error state */ +#ifdef HAVE_PK_CALLBACKS + /* The ECC callbacks read keyGenCnt whether or not certificates are + * compiled in, so this cannot sit inside the NO_CERTS block below. */ + XMEMSET(&pkCbInfo, 0, sizeof(pkCbInfo)); +#endif + #ifndef NO_RSA verifyCert = cliCertFile; ourCert = svrCertFile; @@ -4196,6 +4202,10 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) exit: +#ifdef HAVE_PK_CALLBACKS + CleanupPkCallbackContexts(&pkCbInfo); +#endif + #ifdef WOLFSSL_WOLFSENTRY_HOOKS wolfsentry_ret = wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(&wolfsentry, NULL)); diff --git a/scripts/pkcallbacks.test b/scripts/pkcallbacks.test index 753bf7d621c..b350356e0ca 100755 --- a/scripts/pkcallbacks.test +++ b/scripts/pkcallbacks.test @@ -105,15 +105,24 @@ restore_file_system() { } trap restore_file_system EXIT +# $1 is the version flag pair to run with, empty for the build's default. run_test() { - echo -e "\nStarting example server for pkcallbacks test...\n" + version_args="$1" + + echo -e "\nStarting example server for pkcallbacks test ${version_args:-default}...\n" + + # Back to the port zero hack for every run: the previous run left its + # allocated port here, and reusing a now-known port is exactly what the + # hack exists to avoid when another make check is running alongside. + pk_port=0 remove_ready_file # starts the server on pk_port, -R generates ready file to be used as a # mutex lock, -P does pkcallbacks. We capture the processid # into the variable server_pid - $TIMEOUT_KILL_2M ./examples/server/server -P -R "$ready_file" -p $pk_port & + $TIMEOUT_KILL_2M ./examples/server/server -P -R "$ready_file" -p $pk_port \ + $version_args & server_pid=$! counter=0 @@ -137,12 +146,13 @@ run_test() { pk_port=`cat "$ready_file"` # starts client on pk_port with pkcallbacks, captures the output from client - capture_out=$(./examples/client/client -P -p $pk_port 2>&1) + capture_out=$(./examples/client/client -P -p $pk_port $version_args 2>&1) client_result=$? if [ $client_result != 0 ] then echo -e "client failed!" + echo "$capture_out" do_cleanup exit 1 fi @@ -161,8 +171,19 @@ run_test() { ######### begin program ######### -# run the test -run_test +# The build's default version, then each version explicitly. Running only the +# default used to hide that the ECC PK callbacks could not complete a TLS v1.3 +# handshake: the default is TLS v1.2 wherever it is compiled in, so the 1.3 +# path was never taken here. +run_test "" + +if ./examples/client/client -V | grep -q 3; then + run_test "-v 3" +fi + +if ./examples/client/client -V | grep -q 4; then + run_test "-v 4" +fi # If we get to this, success echo "Success!" diff --git a/wolfssl/test.h b/wolfssl/test.h index 055b5b43916..57906a3b793 100644 --- a/wolfssl/test.h +++ b/wolfssl/test.h @@ -3432,39 +3432,116 @@ static WC_INLINE int wolfSSL_PrintStatsConn(WOLFSSL_MEM_CONN_STATS* stats) #ifdef HAVE_PK_CALLBACKS +/* How many generated ECC keys one connection can have to hold at once. A TLS + * v1.3 client offers a key share per group it is willing to start with, and + * the example client offers SM2 alongside secp256r1 where both are built, so + * one is not enough; a HelloRetryRequest then adds the group the server + * names. */ +#define PKCB_MAX_ECC_KEYGEN 4 + typedef struct PkCbInfo { const char* ourKey; -#ifdef TEST_PK_PRIVKEY - union { - #ifdef HAVE_ECC - /* only ECC PK callback with TLS v1.2 needs this */ - ecc_key ecc; - #endif - } keyGen; - int hasKeyGen; +#ifdef HAVE_ECC + /* Our own ephemeral keys, kept out of the library's key objects. TLS v1.3 + * generates each key share in the key gen callback and computes the shared + * secret in a later callback that is handed only the peer's key, so the + * private halves have to live here until the group is settled. Held by + * pointer: an ecc_key is several kilobytes, and this struct is a stack + * local in the example client and server. */ + ecc_key* keyGen[PKCB_MAX_ECC_KEYGEN]; + int keyGenCnt; #endif } PkCbInfo; #ifdef HAVE_ECC -static WC_INLINE int myEccKeyGen(WOLFSSL* ssl, ecc_key* key, word32 keySz, - int ecc_curve, void* ctx) +/* The key we generated for a curve, or NULL if we have none for it. */ +static WC_INLINE ecc_key* myEccKeptKey(PkCbInfo* cbInfo, int ecc_curve) { - int ret; - PkCbInfo* cbInfo = (PkCbInfo*)ctx; - ecc_key* new_key; + int i; + + if (cbInfo == NULL) + return NULL; + + for (i = 0; i < cbInfo->keyGenCnt; i++) { + if (cbInfo->keyGen[i]->dp != NULL && + cbInfo->keyGen[i]->dp->id == ecc_curve) { + return cbInfo->keyGen[i]; + } + } + + return NULL; +} +static WC_INLINE void myEccFreeKeptKeys(PkCbInfo* cbInfo) +{ + int i; + + if (cbInfo == NULL) + return; + + for (i = 0; i < cbInfo->keyGenCnt; i++) { + wc_ecc_free(cbInfo->keyGen[i]); + XFREE(cbInfo->keyGen[i], NULL, DYNAMIC_TYPE_ECC); + cbInfo->keyGen[i] = NULL; + } + cbInfo->keyGenCnt = 0; +} + +/* Whether this connection has to keep its own private key. TEST_PK_PRIVKEY + * asks for it on every version to model an application that never hands the + * library a private key; TLS v1.3 needs it either way, because the shared + * secret callback only receives the peer's key. Test the 1.3 versions rather + * than ordering the enum: the DTLS values sort above the TLS ones, so DTLS + * v1.2 would otherwise be taken for a 1.3. */ +static WC_INLINE int myEccKeepPrivKey(WOLFSSL* ssl) +{ #ifdef TEST_PK_PRIVKEY - new_key = cbInfo ? &cbInfo->keyGen.ecc : key; + (void)ssl; + return 1; #else - new_key = key; + int version = wolfSSL_GetVersion(ssl); + + return version == WOLFSSL_TLSV1_3 || version == WOLFSSL_DTLSV1_3; #endif +} - (void)ssl; - (void)cbInfo; +static WC_INLINE int myEccKeyGen(WOLFSSL* ssl, ecc_key* key, word32 keySz, + int ecc_curve, void* ctx) +{ + int ret; + PkCbInfo* cbInfo = (PkCbInfo*)ctx; + ecc_key* new_key = key; WOLFSSL_PKMSG("PK ECC KeyGen: keySz %u, Curve ID %d\n", keySz, ecc_curve); + if (cbInfo != NULL && myEccKeepPrivKey(ssl)) { + /* A key we already kept for this curve is stale - a TLS v1.3 client + * regenerates its key share when the server names a group in a + * HelloRetryRequest - so release it and take its slot back. */ + new_key = myEccKeptKey(cbInfo, ecc_curve); + if (new_key != NULL) { + wc_ecc_free(new_key); + } + else if (cbInfo->keyGenCnt < PKCB_MAX_ECC_KEYGEN) { + new_key = (ecc_key*)XMALLOC(sizeof(ecc_key), NULL, + DYNAMIC_TYPE_ECC); + if (new_key == NULL) { + WOLFSSL_PKMSG("PK ECC KeyGen: out of memory\n"); + return MEMORY_E; + } + /* Zero it before it is counted: a wc_ecc_init() failure below + * still leaves it for myEccFreeKeptKeys() to release. */ + XMEMSET(new_key, 0, sizeof(ecc_key)); + cbInfo->keyGen[cbInfo->keyGenCnt++] = new_key; + } + else { + WOLFSSL_PKMSG("PK ECC KeyGen: no room to keep curve %d\n", + ecc_curve); + return MEMORY_E; + } + } + ret = wc_ecc_init(new_key); if (ret == 0) { WC_RNG *rng = wolfSSL_GetRNG(ssl); @@ -3472,7 +3549,6 @@ static WC_INLINE int myEccKeyGen(WOLFSSL* ssl, ecc_key* key, word32 keySz, /* create new key */ ret = wc_ecc_make_key_ex(rng, (int) keySz, new_key, ecc_curve); - #ifdef TEST_PK_PRIVKEY if (ret == 0 && new_key != key) { byte qx[MAX_ECC_BYTES], qy[MAX_ECC_BYTES]; word32 qxLen = sizeof(qx), qyLen = sizeof(qy); @@ -3486,10 +3562,6 @@ static WC_INLINE int myEccKeyGen(WOLFSSL* ssl, ecc_key* key, word32 keySz, (void)qxLen; (void)qyLen; } - if (ret == 0 && cbInfo != NULL) { - cbInfo->hasKeyGen = 1; - } - #endif } WOLFSSL_PKMSG("PK ECC KeyGen: ret %d\n", ret); @@ -3620,32 +3692,39 @@ static WC_INLINE int myEccSharedSecret(WOLFSSL* ssl, ecc_key* otherKey, /* for client: create and export public key */ if (side == WOLFSSL_CLIENT_END) { - #ifdef TEST_PK_PRIVKEY - privKey = cbInfo ? &cbInfo->keyGen.ecc : &tmpKey; - #else - privKey = &tmpKey; - #endif pubKey = otherKey; /* TLS v1.2 and older we must generate a key here for the client only. - * TLS v1.3 calls key gen early with key share. Test the 1.3 versions - * rather than ordering the enum: the DTLS values sort above the TLS - * ones, so DTLS v1.2 would otherwise be taken for a 1.3 and skipped. */ + * TLS v1.3 calls key gen early with each key share it offers, and + * myEccKeepPrivKey() made that callback leave the private halves in + * cbInfo for us; the peer's key names the group the server settled on. + * Test the 1.3 versions rather than ordering the enum: the DTLS values + * sort above the TLS ones, so DTLS v1.2 would otherwise be taken for a + * 1.3 and skipped. */ if (version != WOLFSSL_TLSV1_3 && version != WOLFSSL_DTLSV1_3) { - ret = myEccKeyGen(ssl, privKey, 0, otherKey->dp->id, ctx); + ret = myEccKeyGen(ssl, &tmpKey, 0, otherKey->dp->id, ctx); if (ret == 0) { + privKey = myEccKeptKey(cbInfo, otherKey->dp->id); + if (privKey == NULL) + privKey = &tmpKey; ret = wc_ecc_export_x963(privKey, pubKeyDer, pubKeySz); } } + else { + privKey = myEccKeptKey(cbInfo, otherKey->dp->id); + if (privKey == NULL) { + WOLFSSL_PKMSG("PK ECC PMS: no key kept for curve %d\n", + otherKey->dp->id); + ret = ECC_CURVE_OID_E; + } + } } /* for server: import public key */ else if (side == WOLFSSL_SERVER_END) { - #ifdef TEST_PK_PRIVKEY - privKey = cbInfo ? &cbInfo->keyGen.ecc : otherKey; - #else - privKey = otherKey; - #endif + privKey = myEccKeptKey(cbInfo, otherKey->dp->id); + if (privKey == NULL) + privKey = otherKey; pubKey = &tmpKey; ret = wc_ecc_import_x963_ex(pubKeyDer, *pubKeySz, pubKey, @@ -3655,7 +3734,7 @@ static WC_INLINE int myEccSharedSecret(WOLFSSL* ssl, ecc_key* otherKey, ret = BAD_FUNC_ARG; } - if (privKey == NULL || pubKey == NULL) { + if (ret == 0 && (privKey == NULL || pubKey == NULL)) { ret = BAD_FUNC_ARG; } @@ -3678,13 +3757,6 @@ static WC_INLINE int myEccSharedSecret(WOLFSSL* ssl, ecc_key* otherKey, #endif } -#ifdef TEST_PK_PRIVKEY - if (cbInfo && cbInfo->hasKeyGen) { - wc_ecc_free(&cbInfo->keyGen.ecc); - cbInfo->hasKeyGen = 0; - } -#endif - wc_ecc_free(&tmpKey); WOLFSSL_PKMSG("PK ECC PMS: ret %d, PubKeySz %u, OutLen %u\n", ret, *pubKeySz, *outlen); @@ -4708,6 +4780,11 @@ static WC_INLINE void SetupPkCallbacks(WOLFSSL_CTX* ctx) static WC_INLINE void SetupPkCallbackContexts(WOLFSSL* ssl, void* myCtx) { #ifdef HAVE_ECC + /* The kept keys belong to one connection: whatever a previous one left + * behind is stale, and a TLS v1.2 handshake would otherwise derive + * with it instead of the key the library just handed us. */ + myEccFreeKeptKeys((PkCbInfo*)myCtx); + wolfSSL_SetEccKeyGenCtx(ssl, myCtx); wolfSSL_SetEccSignCtx(ssl, myCtx); wolfSSL_SetEccVerifyCtx(ssl, myCtx); @@ -4761,6 +4838,15 @@ static WC_INLINE void SetupPkCallbackContexts(WOLFSSL* ssl, void* myCtx) #endif } +/* Release what the callbacks kept for the last connection. */ +static WC_INLINE void CleanupPkCallbackContexts(void* myCtx) +{ + (void)myCtx; + #ifdef HAVE_ECC + myEccFreeKeptKeys((PkCbInfo*)myCtx); + #endif +} + #endif /* HAVE_PK_CALLBACKS */ #ifdef USE_WOLFSSL_IO From f899dfd877ba22e870d8120bfd0e1b4c8ae1ee84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Mon, 31 Aug 2026 17:57:21 +0200 Subject: [PATCH 5/6] Keep stapling best-effort when a chain responder is unreachable The chain OCSP loops suppress the responder errors that mean "no status to staple", so that a responder problem cannot take the handshake down with it. OCSP_INVALID_STATUS was missing from that list, and it is what CheckOcspRequest() returns when it cannot reach the responder at all - the most ordinary failure of the set. A TLS 1.3 server built with WOLFSSL_TLS_OCSP_MULTI therefore dropped every handshake whenever the responder for any intermediate was down, having already obtained the leaf status the peer actually asked about. Add it to the three chain lists. The leaf list in CreateOcspResponse() keeps failing hard, and says why: that is the certificate the peer asked about, and test_ocsp_callback_fails() pins the behaviour. OCSP_CERT_REVOKED stays fatal everywhere, chain included. Found through testsuite's OCSP responder test, which starts its responder for a single request and so cannot answer the chain request TLS 1.3 adds. --- src/internal.c | 21 ++++++++++++++++----- src/tls.c | 6 +++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/internal.c b/src/internal.c index a4f5c46e7f0..4d238584936 100644 --- a/src/internal.c +++ b/src/internal.c @@ -27545,7 +27545,9 @@ int CreateOcspResponse(WOLFSSL* ssl, OcspRequest** ocspRequest, /* Suppressing soft-fail responder errors. OCSP_CERT_REVOKED is an * explicit positive assertion of revocation and must not be ignored. * OCSP_NO_URL just means there is no responder to staple from; - * stapling stays best-effort. */ + * stapling stays best-effort. OCSP_INVALID_STATUS is not suppressed + * here the way it is for the chain: this is the leaf the peer asked + * about, so failing to reach its responder fails the handshake. */ if (ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) || ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL) || ret == WC_NO_ERR_TRACE(OCSP_NO_URL)) { @@ -28592,10 +28594,15 @@ int SendCertificateStatus(WOLFSSL* ssl) * OCSP_CERT_REVOKED is an explicit positive * assertion of revocation and must not be * ignored. OCSP_NO_URL just means there is no - * responder to staple from; stapling stays - * best-effort. */ + * responder to staple from, and + * OCSP_INVALID_STATUS covers every other result + * the stapler could not turn into a usable + * response - an unreachable responder, or a + * cached entry with no raw response kept; + * stapling stays best-effort. */ if (ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) || ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL) || + ret == WC_NO_ERR_TRACE(OCSP_INVALID_STATUS) || ret == WC_NO_ERR_TRACE(OCSP_NO_URL)) { ret = 0; } @@ -28621,10 +28628,14 @@ int SendCertificateStatus(WOLFSSL* ssl) /* Suppressing soft-fail responder errors. * OCSP_CERT_REVOKED is an explicit positive assertion of * revocation and must not be ignored. OCSP_NO_URL just - * means there is no responder to staple from; stapling - * stays best-effort. */ + * means there is no responder to staple from, and + * OCSP_INVALID_STATUS covers every other result the + * stapler could not turn into a usable response - an + * unreachable responder, or a cached entry with no raw + * response kept; stapling stays best-effort. */ if (ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) || ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL) || + ret == WC_NO_ERR_TRACE(OCSP_INVALID_STATUS) || ret == WC_NO_ERR_TRACE(OCSP_NO_URL)) { ret = 0; } diff --git a/src/tls.c b/src/tls.c index f600e08b10e..49b4e80661e 100644 --- a/src/tls.c +++ b/src/tls.c @@ -3745,9 +3745,13 @@ int ProcessChainOCSPRequest(WOLFSSL* ssl) /* Suppressing soft-fail responder errors. OCSP_CERT_REVOKED * is an explicit positive assertion of revocation and must * not be ignored. OCSP_NO_URL just means there is no - * responder to staple from; stapling stays best-effort. */ + * responder to staple from, and OCSP_INVALID_STATUS covers + * every other result the stapler could not turn into a usable + * response - an unreachable responder, or a cached entry with + * no raw response kept; stapling stays best-effort. */ if (ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) || ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL) || + ret == WC_NO_ERR_TRACE(OCSP_INVALID_STATUS) || ret == WC_NO_ERR_TRACE(OCSP_NO_URL)) { ret = 0; } From a633e3b70ee3f49b6eaaae30bbc8e3f91a2cc105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Tue, 8 Sep 2026 17:29:12 +0200 Subject: [PATCH 6/6] Incorporate review feedback Guard the --disable-tlsv12 consistency checks on TLS being enabled, the way configure.ac already does: a wolfCrypt-only build has no handshake to be left without, so demanding TLS 1.3 of it is wrong. Before this, cmake -DWOLFSSL_TLS=no -DWOLFSSL_TLSV12=no -DWOLFSSL_TLS13=no failed to configure. WOLFSSL_TLS was declared 1450 lines below the checks, so testing it in place would have read an empty cache variable and silently disabled all four checks; move its declaration up beside the other TLS version options instead. Carry over the other half of the configure.ac rule while here: a TLS 1.3-only build without session tickets has no session to cache, so it needs NO_SESSION_CACHE. That define had no options.h.in entry, which would have left the library compiled with it and every application without it. --- CMakeLists.txt | 76 +++++++++++++++++++++++++--------------------- cmake/options.h.in | 2 ++ 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7baf2ec254b..9736e31f674 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1913,6 +1913,12 @@ else() endif() endif() +# TLS (enabled by default; disable for wolfCrypt-only) +add_option("WOLFSSL_TLS" "Enable TLS (default: enabled)" "yes" "yes;no") +if(NOT WOLFSSL_TLS) + list(APPEND WOLFSSL_DEFINITIONS "-DNO_TLS") +endif() + # TLSv1.2 add_option("WOLFSSL_TLSV12" "Enable TLS versions 1.2 (default: enabled)" @@ -2565,34 +2571,6 @@ if (WOLFSSL_TLS13) ) endif() -if(NOT WOLFSSL_TLSV12) - # Compiling the pre-TLS-1.3 handshake out leaves the versions built on - # top of it with nothing to run on, so reject them the way configure does. - if(WOLFSSL_OLD_TLS) - message(FATAL_ERROR - "WOLFSSL_TLSV12=no cannot be combined with WOLFSSL_OLD_TLS=yes: " - "TLS 1.0 and 1.1 use the TLS 1.2 handshake.") - endif() - if(NOT WOLFSSL_TLS13) - message(FATAL_ERROR - "WOLFSSL_TLSV12=no needs WOLFSSL_TLS13=yes: no TLS version would " - "be left to negotiate.") - endif() - if(WOLFSSL_MCAST) - message(FATAL_ERROR - "WOLFSSL_TLSV12=no cannot be combined with WOLFSSL_MCAST: " - "multicast rides on DTLS 1.2 and its NULL cipher suite.") - endif() - if(WOLFSSL_DTLS AND NOT WOLFSSL_DTLS13) - message(FATAL_ERROR - "WOLFSSL_TLSV12=no with WOLFSSL_DTLS=yes needs WOLFSSL_DTLS13=yes: " - "DTLS 1.2 uses the TLS 1.2 handshake.") - endif() - # NO_OLD_TLS is already in the list: the check above leaves - # WOLFSSL_OLD_TLS off, which is what adds it. - list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_NO_TLS12") -endif() - # Session Ticket Extension add_option("WOLFSSL_SESSION_TICKET" "Enable Session Ticket (default: disabled)" @@ -2608,6 +2586,42 @@ if(WOLFSSL_SESSION_TICKET) "-DHAVE_SESSION_TICKET") endif() +if(NOT WOLFSSL_TLSV12) + # A wolfCrypt-only build has no handshake to be left without, so only a + # build that keeps TLS has to agree on which versions remain. + if(WOLFSSL_TLS) + # Compiling the pre-TLS-1.3 handshake out leaves the versions built on + # top of it with nothing to run on, so reject them the way configure does. + if(WOLFSSL_OLD_TLS) + message(FATAL_ERROR + "WOLFSSL_TLSV12=no cannot be combined with WOLFSSL_OLD_TLS=yes: " + "TLS 1.0 and 1.1 use the TLS 1.2 handshake.") + endif() + if(NOT WOLFSSL_TLS13) + message(FATAL_ERROR + "WOLFSSL_TLSV12=no needs WOLFSSL_TLS13=yes: no TLS version " + "would be left to negotiate.") + endif() + if(WOLFSSL_MCAST) + message(FATAL_ERROR + "WOLFSSL_TLSV12=no cannot be combined with WOLFSSL_MCAST: " + "multicast rides on DTLS 1.2 and its NULL cipher suite.") + endif() + if(WOLFSSL_DTLS AND NOT WOLFSSL_DTLS13) + message(FATAL_ERROR + "WOLFSSL_TLSV12=no with WOLFSSL_DTLS=yes needs " + "WOLFSSL_DTLS13=yes: DTLS 1.2 uses the TLS 1.2 handshake.") + endif() + endif() + # A TLS 1.3-only build without session tickets has no session to cache. + if(WOLFSSL_TLS13 AND NOT WOLFSSL_SESSION_TICKET) + list(APPEND WOLFSSL_DEFINITIONS "-DNO_SESSION_CACHE") + endif() + # NO_OLD_TLS is already in the list: the check above leaves + # WOLFSSL_OLD_TLS off, which is what adds it. + list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_NO_TLS12") +endif() + add_option("WOLFSSL_TICKET_NONCE_MALLOC" "Enable dynamic allocation of ticket nonces (default: disabled)" "no" "yes;no") @@ -4015,12 +4029,6 @@ if(NOT WOLFSSL_STATICMEMORY STREQUAL "no") endif() endif() -# TLS (enabled by default; disable for wolfCrypt-only) -add_option("WOLFSSL_TLS" "Enable TLS (default: enabled)" "yes" "yes;no") -if(NOT WOLFSSL_TLS) - list(APPEND WOLFSSL_DEFINITIONS "-DNO_TLS") -endif() - # SHA-256 (enabled by default) add_option("WOLFSSL_SHA256" "Enable SHA-256 (default: enabled)" "yes" "yes;no") if(NOT WOLFSSL_SHA256) diff --git a/cmake/options.h.in b/cmake/options.h.in index 0dba372e988..a411feb55c0 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -233,6 +233,8 @@ extern "C" { #cmakedefine NO_RC4 #undef NO_RSA #cmakedefine NO_RSA +#undef NO_SESSION_CACHE +#cmakedefine NO_SESSION_CACHE #undef NO_SESSION_CACHE_REF #cmakedefine NO_SESSION_CACHE_REF #undef NO_SHA