From 09e49decc69e55a8c76b59fb3d30b9c25a8c23ee Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 7 Aug 2026 16:29:27 +0000 Subject: [PATCH 1/5] Define WOLFSSL_SIGNER_DER_CERT for OPENSSL_ALL builds X509_STORE_get0_objects() can only enumerate a store's certificates when the CA table keeps their DER, so --enable-opensslall returned an empty stack after any number of successful X509_STORE_add_cert() calls. Define WOLFSSL_NO_SIGNER_DER_CERT to opt back out. --- .wolfssl_known_macro_extras | 1 + wolfssl/wolfcrypt/settings.h | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 5f6057546dc..80986703b5e 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -963,6 +963,7 @@ WOLFSSL_NO_RSA_KEY_CHECK WOLFSSL_NO_SERVER_GROUPS_EXT WOLFSSL_NO_SESSION_STATS WOLFSSL_NO_SIGALG +WOLFSSL_NO_SIGNER_DER_CERT WOLFSSL_NO_SOCKADDR_UN WOLFSSL_NO_STRICT_CIPHER_SUITE WOLFSSL_NO_TICKET_EXPIRE diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 696ece03230..e2abc8fb4a6 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -4939,6 +4939,14 @@ blinding by defining WC_BLINDING_NO_RNG_ACKNOWLEDGE_WEAKNESS." #define WOLFSSL_COPY_KEY #endif +/* X509_STORE_get0_objects() can only enumerate a store's certificates when the + * CA table keeps their DER. Costs one DER copy per loaded CA, so it stays + * optional; define WOLFSSL_NO_SIGNER_DER_CERT to opt back out. */ +#if defined(OPENSSL_ALL) && !defined(WOLFSSL_NO_SIGNER_DER_CERT) + #undef WOLFSSL_SIGNER_DER_CERT + #define WOLFSSL_SIGNER_DER_CERT +#endif + /* * Keeps the "Finished" messages after a TLS handshake for use as the so-called * "tls-unique" channel binding. See comment in internal.h around clientFinished From ef56ad2184ad6a55e0e520c8ef06e471043919f1 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 7 Aug 2026 10:39:25 +0000 Subject: [PATCH 2/5] Accept ffdhe group names in SSL_set1_groups_list() kNistCurves had no FFDHE entries, so an OpenSSL-style list containing one ("x25519:secp256r1:ffdhe2048") failed as a whole and left the default groups in place. SSL_set1_curves_list() is EC-only and still rejects them. --- src/ssl.c | 19 +++++++++++++++++++ tests/api/test_ssl_ext.c | 14 ++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/ssl.c b/src/ssl.c index d7cb7bb4c96..c303ca4deeb 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -9403,6 +9403,25 @@ const WOLF_EC_NIST_NAME kNistCurves[] = { #endif #endif /* WOLFSSL_MLKEM_KYBER */ #endif /* WOLFSSL_HAVE_MLKEM */ +#ifndef NO_DH + /* Finite field groups have no NID; OpenSSL identifies them by TLS group + * code point, so use that for both fields. */ + #ifdef HAVE_FFDHE_2048 + {CURVE_NAME("ffdhe2048"), WOLFSSL_FFDHE_2048, WOLFSSL_FFDHE_2048}, + #endif + #ifdef HAVE_FFDHE_3072 + {CURVE_NAME("ffdhe3072"), WOLFSSL_FFDHE_3072, WOLFSSL_FFDHE_3072}, + #endif + #ifdef HAVE_FFDHE_4096 + {CURVE_NAME("ffdhe4096"), WOLFSSL_FFDHE_4096, WOLFSSL_FFDHE_4096}, + #endif + #ifdef HAVE_FFDHE_6144 + {CURVE_NAME("ffdhe6144"), WOLFSSL_FFDHE_6144, WOLFSSL_FFDHE_6144}, + #endif + #ifdef HAVE_FFDHE_8192 + {CURVE_NAME("ffdhe8192"), WOLFSSL_FFDHE_8192, WOLFSSL_FFDHE_8192}, + #endif +#endif /* !NO_DH */ #ifdef WOLFSSL_SM2 {CURVE_NAME("SM2"), WC_NID_sm2, WOLFSSL_ECC_SM2P256V1}, #endif diff --git a/tests/api/test_ssl_ext.c b/tests/api/test_ssl_ext.c index 09bbfc0e0cc..d31e5460543 100644 --- a/tests/api/test_ssl_ext.c +++ b/tests/api/test_ssl_ext.c @@ -196,6 +196,20 @@ int test_wolfSSL_set1_groups_list_ext(void) #endif #endif +#if !defined(NO_DH) && defined(HAVE_FFDHE_2048) + /* Finite field groups are accepted by their OpenSSL names, on their own + * and mixed into a list of curves. */ + ExpectIntEQ(wolfSSL_CTX_set1_groups_list(ctx, "ffdhe2048"), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set1_groups_list(ssl, "P-256:ffdhe2048"), + WOLFSSL_SUCCESS); + /* set1_curves_list() is EC-only and still rejects them. */ + ExpectIntEQ(wolfSSL_CTX_set1_curves_list(ctx, "ffdhe2048"), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_set1_curves_list(ssl, "P-256:ffdhe2048"), + WOLFSSL_FAILURE); +#endif + wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); #endif From cfaf33e4767bbd8cac55be0d11e4ad2522d888f3 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 7 Aug 2026 10:44:38 +0000 Subject: [PATCH 3/5] Add SSL_get_negotiated_group() and SSL_group_to_name() Neither OpenSSL compat API existed; wolfSSL_get_curve_name() was the only way to see the negotiated group and it returns wolfSSL's spelling. SSL_group_to_name() reports the TLS group registry names OpenSSL uses, which differ from both the NIST curve names in kNistCurves ("P-256" is "secp256r1" there) and from wolfSSL_get_curve_name(), whose output is unchanged. --- src/ssl.c | 139 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls.c | 54 ++++++++++++++++ tests/api/test_tls.h | 2 + wolfssl/openssl/ssl.h | 3 + wolfssl/ssl.h | 7 +++ 5 files changed, 205 insertions(+) diff --git a/src/ssl.c b/src/ssl.c index c303ca4deeb..041008abacd 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -9584,6 +9584,145 @@ int set_curves_list(WOLFSSL* ssl, WOLFSSL_CTX *ctx, const char* names, return ret; } +/* Get the group used for key exchange. + * + * Mirrors OpenSSL's SSL_get_negotiated_group(): the NID is returned for groups + * wolfSSL gives one, the IANA code point otherwise. NID values are wolfSSL's + * WC_NID_* (not every one matches OpenSSL's), so compare against those rather + * than against literals. The result can be passed to wolfSSL_group_to_name(). + * + * @param [in] ssl SSL/TLS object. + * @return Group identifier on success. + * @return 0 when ssl is NULL or no group has been negotiated. + */ +int wolfSSL_get_negotiated_group(const WOLFSSL* ssl) +{ + word16 group; + const WOLF_EC_NIST_NAME* nist_name; + + WOLFSSL_ENTER("wolfSSL_get_negotiated_group"); + + if (ssl == NULL) + return 0; + + group = ssl->namedGroup; + +#ifdef HAVE_CURVE25519 + if ((group == 0) && (ssl->ecdhCurveOID == ECC_X25519_OID)) + group = WOLFSSL_ECC_X25519; +#endif +#ifdef HAVE_CURVE448 + if ((group == 0) && (ssl->ecdhCurveOID == ECC_X448_OID)) + group = WOLFSSL_ECC_X448; +#endif +#ifdef HAVE_ECC + if ((group == 0) && (ssl->ecdhCurveOID != 0)) + group = GetCurveByOID((int)ssl->ecdhCurveOID); +#endif + + if (group == 0) + return 0; + + for (nist_name = kNistCurves; nist_name->name != NULL; nist_name++) { + if (nist_name->curve == group) + return nist_name->nid; + } + + return (int)group; +} + +/* Names OpenSSL gives the TLS supported groups. These are the TLS group + * registry spellings, which differ from both the NIST curve names in + * kNistCurves ("P-256" is "secp256r1" here) and from wolfSSL_get_curve_name(). + */ +static const struct { + word16 group; + const char* name; +} kTlsGroupNames[] = { +#ifdef HAVE_ECC + {WOLFSSL_ECC_SECP256R1, "secp256r1"}, + {WOLFSSL_ECC_SECP384R1, "secp384r1"}, + {WOLFSSL_ECC_SECP521R1, "secp521r1"}, + {WOLFSSL_ECC_SECP224R1, "secp224r1"}, + {WOLFSSL_ECC_SECP192R1, "secp192r1"}, + {WOLFSSL_ECC_SECP256K1, "secp256k1"}, +#ifdef HAVE_ECC_BRAINPOOL + {WOLFSSL_ECC_BRAINPOOLP256R1, "brainpoolP256r1"}, + {WOLFSSL_ECC_BRAINPOOLP384R1, "brainpoolP384r1"}, + {WOLFSSL_ECC_BRAINPOOLP512R1, "brainpoolP512r1"}, + {WOLFSSL_ECC_BRAINPOOLP256R1TLS13, "brainpoolP256r1tls13"}, + {WOLFSSL_ECC_BRAINPOOLP384R1TLS13, "brainpoolP384r1tls13"}, + {WOLFSSL_ECC_BRAINPOOLP512R1TLS13, "brainpoolP512r1tls13"}, +#endif +#endif /* HAVE_ECC */ +#ifdef HAVE_CURVE25519 + {WOLFSSL_ECC_X25519, "x25519"}, +#endif +#ifdef HAVE_CURVE448 + {WOLFSSL_ECC_X448, "x448"}, +#endif +#ifndef NO_DH + {WOLFSSL_FFDHE_2048, "ffdhe2048"}, + {WOLFSSL_FFDHE_3072, "ffdhe3072"}, + {WOLFSSL_FFDHE_4096, "ffdhe4096"}, + {WOLFSSL_FFDHE_6144, "ffdhe6144"}, + {WOLFSSL_FFDHE_8192, "ffdhe8192"}, +#endif +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_NO_ML_KEM) + {WOLFSSL_ML_KEM_512, "MLKEM512"}, + {WOLFSSL_ML_KEM_768, "MLKEM768"}, + {WOLFSSL_ML_KEM_1024, "MLKEM1024"}, +#if defined(HAVE_ECC) && defined(WOLFSSL_PQC_HYBRIDS) + {WOLFSSL_SECP256R1MLKEM768, "SecP256r1MLKEM768"}, + {WOLFSSL_SECP384R1MLKEM1024, "SecP384r1MLKEM1024"}, +#ifdef HAVE_CURVE25519 + {WOLFSSL_X25519MLKEM768, "X25519MLKEM768"}, +#endif +#endif +#endif /* WOLFSSL_HAVE_MLKEM && !WOLFSSL_NO_ML_KEM */ + {0, NULL} +}; + +/* Get the name of a group. + * + * @param [in] ssl SSL/TLS object. Unused, present for OpenSSL compatibility. + * @param [in] id NID or IANA code point of the group. + * @return Name of the group on success. + * @return NULL when the group is unknown. + */ +const char* wolfSSL_group_to_name(const WOLFSSL* ssl, int id) +{ + const WOLF_EC_NIST_NAME* nist_name; + int group = id; + int i; + + WOLFSSL_ENTER("wolfSSL_group_to_name"); + + (void)ssl; + + /* wolfSSL_get_negotiated_group() reports a NID where one exists, so map + * NIDs back to their code point before looking the name up. */ + for (nist_name = kNistCurves; nist_name->name != NULL; nist_name++) { + if (nist_name->nid == id) { + group = (int)nist_name->curve; + break; + } + } + + for (i = 0; kTlsGroupNames[i].name != NULL; i++) { + if ((int)kTlsGroupNames[i].group == group) + return kTlsGroupNames[i].name; + } + + /* Not a group OpenSSL names - fall back to the wolfSSL name. */ + for (nist_name = kNistCurves; nist_name->name != NULL; nist_name++) { + if ((int)nist_name->curve == group) + return nist_name->name; + } + + return NULL; +} + #endif /* (HAVE_ECC || HAVE_CURVE25519 || HAVE_CURVE448) */ #endif /* OPENSSL_EXTRA || HAVE_CURL */ diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index ed8ab7c6c2f..3a89e26e556 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -297,6 +297,60 @@ int test_tls_record_overflow_alert(void) return EXPECT_RESULT(); } +/* SSL_get_negotiated_group() and SSL_group_to_name() report the group that was + * negotiated, using the names OpenSSL gives the TLS supported groups. */ +int test_tls_get_negotiated_group(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(OPENSSL_EXTRA) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_ECC) && !defined(NO_ECC_SECP) && \ + (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = 0; + int p256[] = {WOLFSSL_ECC_SECP256R1}; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, p256, 1), WOLFSSL_SUCCESS); + + /* Nothing negotiated yet. */ + ExpectIntEQ(wolfSSL_get_negotiated_group(NULL), 0); + ExpectIntEQ(wolfSSL_get_negotiated_group(ssl_c), 0); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + ExpectIntNE((group = wolfSSL_get_negotiated_group(ssl_c)), 0); + ExpectIntEQ(group, wolfSSL_get_negotiated_group(ssl_s)); + /* OpenSSL names this group secp256r1, not by its NIST name P-256. */ + ExpectStrEQ(wolfSSL_group_to_name(ssl_c, group), "secp256r1"); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + + /* Groups can also be named directly by their IANA code point. */ + ExpectStrEQ(wolfSSL_group_to_name(NULL, WOLFSSL_ECC_SECP256R1), + "secp256r1"); +#ifdef HAVE_CURVE25519 + ExpectStrEQ(wolfSSL_group_to_name(NULL, WOLFSSL_ECC_X25519), "x25519"); +#endif +#if !defined(NO_DH) && defined(HAVE_FFDHE_2048) + ExpectStrEQ(wolfSSL_group_to_name(NULL, WOLFSSL_FFDHE_2048), "ffdhe2048"); +#endif +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_NO_ML_KEM) && \ + !defined(WOLFSSL_NO_ML_KEM_768) + /* OpenSSL spells the standalone ML-KEM groups without underscores. */ + ExpectStrEQ(wolfSSL_group_to_name(NULL, WOLFSSL_ML_KEM_768), "MLKEM768"); +#endif + ExpectNull(wolfSSL_group_to_name(NULL, 9999)); +#endif + return EXPECT_RESULT(); +} + int test_tls12_curve_intersection(void) { EXPECT_DECLS; #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index f009bb986ee..ac9e714cf3b 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -26,6 +26,7 @@ int test_utils_memio_move_message(void); int test_tls12_unexpected_ccs(void); int test_tls13_unexpected_ccs(void); int test_tls_record_overflow_alert(void); +int test_tls_get_negotiated_group(void); int test_tls12_curve_intersection(void); int test_tls12_dhe_rsa_pss_sigalg(void); int test_tls12_ske_sig_param_binding(void); @@ -67,6 +68,7 @@ int test_wolfSSL_get_shared_ciphers(void); TEST_DECL_GROUP("tls", test_tls12_unexpected_ccs), \ TEST_DECL_GROUP("tls", test_tls13_unexpected_ccs), \ TEST_DECL_GROUP("tls", test_tls_record_overflow_alert), \ + TEST_DECL_GROUP("tls", test_tls_get_negotiated_group), \ TEST_DECL_GROUP("tls", test_tls12_curve_intersection), \ TEST_DECL_GROUP("tls", test_tls12_dhe_rsa_pss_sigalg), \ TEST_DECL_GROUP("tls", test_tls12_ske_sig_param_binding), \ diff --git a/wolfssl/openssl/ssl.h b/wolfssl/openssl/ssl.h index 5c95da71316..fc69471d2f3 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -412,6 +412,9 @@ typedef STACK_OF(ACCESS_DESCRIPTION) AUTHORITY_INFO_ACCESS; #define SSL_CTX_set1_groups_list wolfSSL_CTX_set1_groups_list #define SSL_set1_groups_list wolfSSL_set1_groups_list +#define SSL_get_negotiated_group wolfSSL_get_negotiated_group +#define SSL_group_to_name wolfSSL_group_to_name + #define SSL_set_ex_data wolfSSL_set_ex_data #define SSL_get_shutdown wolfSSL_get_shutdown #define SSL_get_finished wolfSSL_get_finished diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index c168ec24d9c..7cfbf69d5e4 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -1500,6 +1500,13 @@ WOLFSSL_API int wolfSSL_set1_groups_list(WOLFSSL *ssl, const char *list); #endif #endif +#if defined(OPENSSL_EXTRA) || defined(HAVE_CURL) +#if defined(HAVE_ECC) || defined(HAVE_CURVE25519) || defined(HAVE_CURVE448) +WOLFSSL_API int wolfSSL_get_negotiated_group(const WOLFSSL* ssl); +WOLFSSL_API const char* wolfSSL_group_to_name(const WOLFSSL* ssl, int id); +#endif +#endif + #ifdef WOLFSSL_TLS13 WOLFSSL_API int wolfSSL_send_hrr_cookie(WOLFSSL* ssl, const unsigned char* secret, unsigned int secretSz); From b57eaf99875c9c02c5ac1f23226a0a452f0d101d Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 7 Aug 2026 10:52:32 +0000 Subject: [PATCH 4/5] Report peer name mismatches through the verify callback The leaf domain and IP checks driven by the X509_VERIFY_PARAM ran in DoVerifyCallback() after it had already decided whether to invoke the application callback and built the store context from cert_err. An application therefore saw preverify=1 and error=0 for a name mismatch that then failed the handshake anyway, so it could not apply its own policy. Do both checks in ProcessPeerCerts() alongside the existing wolfSSL_check_domain_name() one, sourcing the expected name from either the WOLFSSL buffers or the verify param. Side effect: SSL_VERIFY_NONE now suppresses these checks like it already did for the non-OpenSSL path. --- src/internal.c | 90 ++++++++++++++----------------------------- tests/api/test_tls.c | 91 ++++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls.h | 2 + 3 files changed, 122 insertions(+), 61 deletions(-) diff --git a/src/internal.c b/src/internal.c index 3de4b1d060c..3b32215cde1 100644 --- a/src/internal.c +++ b/src/internal.c @@ -15963,59 +15963,6 @@ int DoVerifyCallback(WOLFSSL_CERT_MANAGER* cm, WOLFSSL* ssl, int cert_err, if (args->certIdx > 0) { use_cb = 1; } -#endif -#if defined(OPENSSL_EXTRA) - /* Perform domain and IP check only for the leaf certificate */ - if (args->certIdx == 0) { - /* perform domain name check on the peer certificate */ - if (args->dCertInit && args->dCert && (ssl != NULL) && - ssl->param && ssl->param->hostName[0]) { - /* If altNames names is present, then subject common name is ignored */ - if (args->dCert->altNames != NULL) { - if (CheckForAltNames(args->dCert, ssl->param->hostName, - (word32)XSTRLEN(ssl->param->hostName), NULL, 0, 0) != 1) { - if (cert_err == 0) { - ret = DOMAIN_NAME_MISMATCH; - WOLFSSL_ERROR_VERBOSE(ret); - } - } - } - #ifndef WOLFSSL_HOSTNAME_VERIFY_ALT_NAME_ONLY - else { - if (args->dCert->subjectCN) { - if (MatchDomainName( - args->dCert->subjectCN, - args->dCert->subjectCNLen, - ssl->param->hostName, - (word32)XSTRLEN(ssl->param->hostName), 0) == 0) { - if (cert_err == 0) { - ret = DOMAIN_NAME_MISMATCH; - WOLFSSL_ERROR_VERBOSE(ret); - } - } - } - } - #else - else { - if (cert_err == 0) { - ret = DOMAIN_NAME_MISMATCH; - WOLFSSL_ERROR_VERBOSE(ret); - } - } - #endif /* !WOLFSSL_HOSTNAME_VERIFY_ALT_NAME_ONLY */ - } - - /* perform IP address check on the peer certificate */ - if ((args->dCertInit != 0) && (args->dCert != NULL) && (ssl != NULL) && - (ssl->param != NULL) && (XSTRLEN(ssl->param->ipasc) > 0)) { - if (CheckIPAddr(args->dCert, ssl->param->ipasc) != 0) { - if (cert_err == 0) { - ret = IPADDR_MISMATCH; - WOLFSSL_ERROR_VERBOSE(ret); - } - } - } - } #endif /* if verify callback has been set */ if ((use_cb && (ssl != NULL) && ((ssl->verifyCallback != NULL) @@ -18749,6 +18696,14 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, #endif domainName = (char*)ssl->buffers.domainName.buffer; + #ifdef OPENSSL_EXTRA + /* X509_VERIFY_PARAM_set1_host() is the OpenSSL way of naming + * the expected peer. */ + if ((domainName == NULL) && (ssl->param != NULL) && + (ssl->param->hostName[0] != '\0')) { + domainName = ssl->param->hostName; + } + #endif #if !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_ECH) /* RFC 9849 s6.1.7: ECH offered but rejected by the server... * verify cert is valid for ECHConfig.public_name */ @@ -18823,16 +18778,29 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, #endif /* WOLFSSL_ALL_NO_CN_IN_SAN */ } -#ifndef OPENSSL_EXTRA - if (!ssl->options.verifyNone && ssl->buffers.ipasc.buffer) { - if (CheckIPAddr(args->dCert, - (const char*)ssl->buffers.ipasc.buffer) != 0) { - WOLFSSL_MSG("IPAddr match on alt names failed"); - ret = IPADDR_MISMATCH; - WOLFSSL_ERROR_VERBOSE(ret); + { + const char* ipasc = NULL; + + if (ssl->buffers.ipasc.buffer != NULL) { + ipasc = (const char*)ssl->buffers.ipasc.buffer; + } + #ifdef OPENSSL_EXTRA + /* X509_VERIFY_PARAM_set1_ip() sets the expected address + * here rather than on the WOLFSSL buffers. */ + else if ((ssl->param != NULL) && + (ssl->param->ipasc[0] != '\0')) { + ipasc = ssl->param->ipasc; + } + #endif + + if (!ssl->options.verifyNone && (ipasc != NULL)) { + if (CheckIPAddr(args->dCert, ipasc) != 0) { + WOLFSSL_MSG("IPAddr match on alt names failed"); + ret = IPADDR_MISMATCH; + WOLFSSL_ERROR_VERBOSE(ret); + } } } -#endif /* decode peer key */ if (ProcessPeerCertDecodeKey(ssl, args, &ret)) diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 3a89e26e556..3e2523de05a 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -297,6 +297,97 @@ int test_tls_record_overflow_alert(void) return EXPECT_RESULT(); } +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(OPENSSL_EXTRA) && \ + !defined(NO_RSA) && !defined(NO_CERTS) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) + +static int test_peer_name_cb_err = 0; +static int test_peer_name_cb_preverify = -1; + +static int test_peer_name_verify_cb(int preverify, + WOLFSSL_X509_STORE_CTX* store) +{ + /* Only record the first failure - later certificates in the chain would + * otherwise overwrite it. */ + if (!preverify && (test_peer_name_cb_err == 0)) { + test_peer_name_cb_preverify = preverify; + test_peer_name_cb_err = wolfSSL_X509_STORE_CTX_get_error(store); + } + return preverify; +} + +/* mode: 0 = check_ip_address(), 1 = set1_ip_asc(), 2 = set1_host() */ +static int test_peer_name_mismatch(int mode, const char* name, int expectErr) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + test_peer_name_cb_err = 0; + test_peer_name_cb_preverify = -1; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfSSLv23_client_method, wolfSSLv23_server_method), 0); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_PEER, test_peer_name_verify_cb); + + if (mode == 0) { + ExpectIntEQ(wolfSSL_check_ip_address(ssl_c, name), WOLFSSL_SUCCESS); + } + else if (mode == 1) { + ExpectIntEQ(wolfSSL_X509_VERIFY_PARAM_set1_ip_asc( + wolfSSL_get0_param(ssl_c), name), WOLFSSL_SUCCESS); + } + else { + ExpectIntEQ(wolfSSL_X509_VERIFY_PARAM_set1_host( + wolfSSL_get0_param(ssl_c), name, 0), WOLFSSL_SUCCESS); + } + + if (expectErr == 0) { + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(test_peer_name_cb_err, 0); + } + else { + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* The mismatch has to reach the application's verify callback so it + * can apply its own policy, not just fail the handshake. */ + ExpectIntEQ(test_peer_name_cb_preverify, 0); + ExpectIntEQ(test_peer_name_cb_err, expectErr); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + + return EXPECT_RESULT(); +} +#endif + +/* A peer name mismatch must be reported through SSL_set_verify()'s callback, + * whichever API named the expected peer. */ +int test_tls_peer_name_mismatch_verify_cb(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(OPENSSL_EXTRA) && \ + !defined(NO_RSA) && !defined(NO_CERTS) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_SHA256) +#ifdef WOLFSSL_IP_ALT_NAME + /* certs/server-cert.pem carries IP:127.0.0.1 and DNS:example.com. */ + ExpectIntEQ(test_peer_name_mismatch(0, "127.0.0.2", + WC_NO_ERR_TRACE(IPADDR_MISMATCH)), TEST_SUCCESS); + ExpectIntEQ(test_peer_name_mismatch(1, "127.0.0.2", + WC_NO_ERR_TRACE(IPADDR_MISMATCH)), TEST_SUCCESS); + ExpectIntEQ(test_peer_name_mismatch(0, "127.0.0.1", 0), TEST_SUCCESS); +#endif + ExpectIntEQ(test_peer_name_mismatch(2, "wrong.example.com", + WC_NO_ERR_TRACE(DOMAIN_NAME_MISMATCH)), TEST_SUCCESS); + ExpectIntEQ(test_peer_name_mismatch(2, "example.com", 0), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + /* SSL_get_negotiated_group() and SSL_group_to_name() report the group that was * negotiated, using the names OpenSSL gives the TLS supported groups. */ int test_tls_get_negotiated_group(void) diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index ac9e714cf3b..581b0735386 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -26,6 +26,7 @@ int test_utils_memio_move_message(void); int test_tls12_unexpected_ccs(void); int test_tls13_unexpected_ccs(void); int test_tls_record_overflow_alert(void); +int test_tls_peer_name_mismatch_verify_cb(void); int test_tls_get_negotiated_group(void); int test_tls12_curve_intersection(void); int test_tls12_dhe_rsa_pss_sigalg(void); @@ -68,6 +69,7 @@ int test_wolfSSL_get_shared_ciphers(void); TEST_DECL_GROUP("tls", test_tls12_unexpected_ccs), \ TEST_DECL_GROUP("tls", test_tls13_unexpected_ccs), \ TEST_DECL_GROUP("tls", test_tls_record_overflow_alert), \ + TEST_DECL_GROUP("tls", test_tls_peer_name_mismatch_verify_cb), \ TEST_DECL_GROUP("tls", test_tls_get_negotiated_group), \ TEST_DECL_GROUP("tls", test_tls12_curve_intersection), \ TEST_DECL_GROUP("tls", test_tls12_dhe_rsa_pss_sigalg), \ From b0edc73c9f75b2175ebc911f3bdb58596afb961a Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 7 Aug 2026 10:56:05 +0000 Subject: [PATCH 5/5] Fix SSL_get_peer_tmp_key() for TLS 1.3 The TLS 1.3 key share paths freed the peer's ephemeral key as soon as the shared secret was derived, ignoring wolfSSL_KeepHandshakeResources(), so the call failed for every TLS 1.3 group - including the ECC ones, not just the X25519/X448 ones it had no branch for. Only TLS 1.2 ECDHE worked. Keep the peer key when the application asked for the handshake resources, and return X25519 and X448 keys as well. The kept keys are freed with the other peer keys on teardown. --- src/ssl.c | 42 ++++++++++++++++++++++++----- src/tls.c | 23 ++++++++++++---- tests/api/test_tls.c | 64 ++++++++++++++++++++++++++++++++++++++++++++ tests/api/test_tls.h | 2 ++ 4 files changed, 120 insertions(+), 11 deletions(-) diff --git a/src/ssl.c b/src/ssl.c index 041008abacd..6d43cb8d8b4 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -4830,13 +4830,43 @@ int wolfSSL_get_peer_tmp_key(const WOLFSSL* ssl, WOLFSSL_EVP_PKEY** pkey) } #endif - *pkey = ret; -#ifdef HAVE_ECC - if (ret != NULL) - return WOLFSSL_SUCCESS; - else +#ifdef HAVE_CURVE25519 + if ((ret == NULL) && (ssl->peerX25519Key != NULL) && + ssl->peerX25519KeyPresent) { + byte pub[CURVE25519_PUB_KEY_SIZE]; + word32 pubSz = (word32)sizeof(pub); + + /* Raw X25519 keys are little-endian (RFC 7748). */ + if (wc_curve25519_export_public_ex(ssl->peerX25519Key, pub, &pubSz, + EC25519_LITTLE_ENDIAN) != 0) { + WOLFSSL_MSG("get curve25519 public key failed"); + return WOLFSSL_FAILURE; + } + ret = wolfSSL_EVP_PKEY_new_raw_public_key(WC_EVP_PKEY_X25519, NULL, + pub, (size_t)pubSz); + } #endif - return WOLFSSL_FAILURE; + +#ifdef HAVE_CURVE448 + if ((ret == NULL) && (ssl->peerX448Key != NULL) && + ssl->peerX448KeyPresent) { + byte pub[CURVE448_PUB_KEY_SIZE]; + word32 pubSz = (word32)sizeof(pub); + + /* Raw X448 keys are little-endian (RFC 7748). */ + if (wc_curve448_export_public_ex(ssl->peerX448Key, pub, &pubSz, + EC448_LITTLE_ENDIAN) != 0) { + WOLFSSL_MSG("get curve448 public key failed"); + return WOLFSSL_FAILURE; + } + ret = wolfSSL_EVP_PKEY_new_raw_public_key(WC_EVP_PKEY_X448, NULL, + pub, (size_t)pubSz); + } +#endif + + *pkey = ret; + + return (ret != NULL) ? WOLFSSL_SUCCESS : WOLFSSL_FAILURE; } #endif /* !NO_WOLFSSL_SERVER */ diff --git a/src/tls.c b/src/tls.c index 1fe31d43af0..8809747396c 100644 --- a/src/tls.c +++ b/src/tls.c @@ -9759,8 +9759,9 @@ static int TLSX_KeyShare_ProcessX25519_ex(WOLFSSL* ssl, * falls through to the cleanup code below. */ } - /* done with key share, release resources */ - if (ssl->peerX25519Key != NULL) { + /* done with key share, release resources unless the peer key was asked + * for - wolfSSL_get_peer_tmp_key() needs it after the handshake */ + if ((ssl->peerX25519Key != NULL) && !ssl->options.keepResources) { wc_curve25519_free(ssl->peerX25519Key); XFREE(ssl->peerX25519Key, ssl->heap, DYNAMIC_TYPE_TLSX); ssl->peerX25519Key = NULL; @@ -9872,8 +9873,18 @@ static int TLSX_KeyShare_ProcessX448_ex(WOLFSSL* ssl, ssOutput, ssOutSz, EC448_LITTLE_ENDIAN); } - wc_curve448_free(peerX448Key); - XFREE(peerX448Key, ssl->heap, DYNAMIC_TYPE_TLSX); + /* Keep the peer key when it was asked for - wolfSSL_get_peer_tmp_key() + * needs it after the handshake. Freed with the other peer keys on + * teardown. */ + if ((ret == 0) && ssl->options.keepResources) { + FreeKey(ssl, DYNAMIC_TYPE_CURVE448, (void**)&ssl->peerX448Key); + ssl->peerX448Key = peerX448Key; + ssl->peerX448KeyPresent = 1; + } + else { + wc_curve448_free(peerX448Key); + XFREE(peerX448Key, ssl->heap, DYNAMIC_TYPE_TLSX); + } wc_curve448_free((curve448_key*)keyShareEntry->key); XFREE(keyShareEntry->key, ssl->heap, DYNAMIC_TYPE_PRIVATE_KEY); keyShareEntry->key = NULL; @@ -10046,11 +10057,13 @@ static int TLSX_KeyShare_ProcessEcc_ex(WOLFSSL* ssl, #endif } - /* done with key share, release resources */ + /* done with key share, release resources unless the peer key was asked + * for - wolfSSL_get_peer_tmp_key() needs it after the handshake */ if (ssl->peerEccKey != NULL #ifdef HAVE_PK_CALLBACKS && ssl->ctx->EccSharedSecretCb == NULL #endif + && !ssl->options.keepResources ) { wc_ecc_free(ssl->peerEccKey); XFREE(ssl->peerEccKey, ssl->heap, DYNAMIC_TYPE_ECC); diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index 3e2523de05a..d93f396f2f1 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -388,6 +388,70 @@ int test_tls_peer_name_mismatch_verify_cb(void) return EXPECT_RESULT(); } +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(OPENSSL_EXTRA) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_RSA) && !defined(NO_CERTS) +static int test_peer_tmp_key_group(int group, int tls13) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL_EVP_PKEY* pkey = NULL; + int groups[1]; + + groups[0] = group; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + tls13 ? wolfTLSv1_3_client_method : wolfTLSv1_2_client_method, + tls13 ? wolfTLSv1_3_server_method : wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_KeepHandshakeResources(ssl_c), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, groups, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + ExpectIntEQ(wolfSSL_get_peer_tmp_key(ssl_c, &pkey), WOLFSSL_SUCCESS); + ExpectNotNull(pkey); + wolfSSL_EVP_PKEY_free(pkey); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + + return EXPECT_RESULT(); +} +#endif + +/* SSL_get_peer_tmp_key() has to return the peer's ephemeral key for every key + * exchange group, not just TLS 1.2 ECDHE. */ +int test_tls_get_peer_tmp_key(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(OPENSSL_EXTRA) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_RSA) && !defined(NO_CERTS) && !defined(NO_SHA256) +#if defined(HAVE_ECC) && !defined(NO_ECC_SECP) && \ + (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) +#ifndef WOLFSSL_NO_TLS12 + ExpectIntEQ(test_peer_tmp_key_group(WOLFSSL_ECC_SECP256R1, 0), + TEST_SUCCESS); +#endif +#ifdef WOLFSSL_TLS13 + ExpectIntEQ(test_peer_tmp_key_group(WOLFSSL_ECC_SECP256R1, 1), + TEST_SUCCESS); +#endif +#endif /* HAVE_ECC */ +#if defined(WOLFSSL_TLS13) && defined(HAVE_CURVE25519) && !defined(HAVE_FIPS) + ExpectIntEQ(test_peer_tmp_key_group(WOLFSSL_ECC_X25519, 1), TEST_SUCCESS); +#endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_CURVE448) && !defined(HAVE_FIPS) + ExpectIntEQ(test_peer_tmp_key_group(WOLFSSL_ECC_X448, 1), TEST_SUCCESS); +#endif +#endif + return EXPECT_RESULT(); +} + /* SSL_get_negotiated_group() and SSL_group_to_name() report the group that was * negotiated, using the names OpenSSL gives the TLS supported groups. */ int test_tls_get_negotiated_group(void) diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index 581b0735386..ca00e832778 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -27,6 +27,7 @@ int test_tls12_unexpected_ccs(void); int test_tls13_unexpected_ccs(void); int test_tls_record_overflow_alert(void); int test_tls_peer_name_mismatch_verify_cb(void); +int test_tls_get_peer_tmp_key(void); int test_tls_get_negotiated_group(void); int test_tls12_curve_intersection(void); int test_tls12_dhe_rsa_pss_sigalg(void); @@ -70,6 +71,7 @@ int test_wolfSSL_get_shared_ciphers(void); TEST_DECL_GROUP("tls", test_tls13_unexpected_ccs), \ TEST_DECL_GROUP("tls", test_tls_record_overflow_alert), \ TEST_DECL_GROUP("tls", test_tls_peer_name_mismatch_verify_cb), \ + TEST_DECL_GROUP("tls", test_tls_get_peer_tmp_key), \ TEST_DECL_GROUP("tls", test_tls_get_negotiated_group), \ TEST_DECL_GROUP("tls", test_tls12_curve_intersection), \ TEST_DECL_GROUP("tls", test_tls12_dhe_rsa_pss_sigalg), \