From 872a03a0566c829ed8eb0720d7a3ffdad15fb6c8 Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 20 May 2026 16:18:50 -0700 Subject: [PATCH 1/4] Disallow matching URI type in CheckForAltNames. Thanks to Haruki Oyama (Waseda University) for the report. --- src/internal.c | 11 +++++++ tests/api/test_certman.c | 64 ++++++++++++++++++++++++++++++++++++++++ tests/api/test_certman.h | 3 ++ wolfcrypt/src/asn.c | 4 +++ 4 files changed, 82 insertions(+) diff --git a/src/internal.c b/src/internal.c index 75c007c6572..ce5f2d7ab88 100644 --- a/src/internal.c +++ b/src/internal.c @@ -13696,6 +13696,17 @@ int CheckForAltNames(DecodedCert* dCert, const char* domain, word32 domainLen, continue; } + /* RFC 6125 Sec. 6.4 / RFC 9525 Sec. 6.3: a DNS-ID reference + * identifier is matched only against dNSName SAN entries, never + * uniformResourceIdentifier (even when the URI value resembles a + * hostname). URI-ID matching requires scheme and host parsing + * (RFC 9525 Sec. 6.5, Sec. 7.2). */ + if (!isIP && altName->type == ASN_URI_TYPE) { + WOLFSSL_MSG("\tAltName is uniformResourceIdentifier, " + "skipping for DNS hostname"); + continue; + } + if (MatchDomainName(buf, (int)len, domain, domainLen, flags)) { match = 1; if (checkCN != NULL) { diff --git a/tests/api/test_certman.c b/tests/api/test_certman.c index f6110105650..cf404524f5d 100644 --- a/tests/api/test_certman.c +++ b/tests/api/test_certman.c @@ -2109,6 +2109,70 @@ int test_wolfSSL_X509_check_host_IP_only_SAN_CN_fallback(void) return EXPECT_RESULT(); } +int test_wolfSSL_X509_check_host_URI_SAN_not_DNS_match(void) +{ + EXPECT_DECLS; +#if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && !defined(NO_RSA) && \ + defined(OPENSSL_EXTRA) && defined(WOLFSSL_CERT_GEN) && \ + defined(WOLFSSL_CERT_EXT) && defined(WOLFSSL_ALT_NAMES) && \ + !defined(NO_SHA256) + /* RFC 6125 Sec. 6.4 / RFC 9525 Sec. 6.3: DNS-ID reference identifiers + * must be matched only against dNSName SANs, not uniformResourceIdentifier. + * wolfSSL_X509_add_altname() is used to attach a bare-hostname URI SAN + * (the misissue shape that can reach altNames when certificate parsing is + * built without strict URI checks). URI SAN presence still suppresses CN + * fallback per RFC 6125 Sec. 6.4.4. */ + WOLFSSL_EVP_PKEY *priv = NULL; + WOLFSSL_X509_NAME* name = NULL; + const char* server_cert = "./certs/test/server-goodcn.pem"; + const char hostName[] = "cnhost.local"; + const char uriSan[] = "cnhost.local"; + byte *pt; + WOLFSSL_X509 *leafUri = NULL; + WOLFSSL_X509 *leafUriDns = NULL; + + pt = (byte*)server_key_der_2048; + ExpectNotNull(priv = wolfSSL_d2i_PrivateKey(EVP_PKEY_RSA, NULL, + (const unsigned char**)&pt, sizeof_server_key_der_2048)); + + ExpectNotNull(leafUri = wolfSSL_X509_load_certificate_file(server_cert, + WOLFSSL_FILETYPE_PEM)); + ExpectNotNull(name = X509_NAME_new()); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "commonName", MBSTRING_UTF8, + (byte*)hostName, (int)XSTRLEN(hostName), -1, 0), SSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_subject_name(leafUri, name), WOLFSSL_SUCCESS); + X509_NAME_free(name); + name = NULL; + ExpectIntEQ(wolfSSL_X509_add_altname(leafUri, uriSan, ASN_URI_TYPE), + WOLFSSL_SUCCESS); + ExpectIntGT(wolfSSL_X509_sign(leafUri, priv, EVP_sha256()), 0); + ExpectIntEQ(wolfSSL_X509_check_host(leafUri, hostName, XSTRLEN(hostName), + 0, NULL), WC_NO_ERR_TRACE(WOLFSSL_FAILURE)); + + ExpectNotNull(leafUriDns = wolfSSL_X509_load_certificate_file(server_cert, + WOLFSSL_FILETYPE_PEM)); + ExpectNotNull(name = X509_NAME_new()); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "commonName", MBSTRING_UTF8, + (byte*)hostName, (int)XSTRLEN(hostName), -1, 0), SSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_subject_name(leafUriDns, name), + WOLFSSL_SUCCESS); + X509_NAME_free(name); + name = NULL; + ExpectIntEQ(wolfSSL_X509_add_altname(leafUriDns, uriSan, ASN_URI_TYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_add_altname(leafUriDns, hostName, ASN_DNS_TYPE), + WOLFSSL_SUCCESS); + ExpectIntGT(wolfSSL_X509_sign(leafUriDns, priv, EVP_sha256()), 0); + ExpectIntEQ(wolfSSL_X509_check_host(leafUriDns, hostName, + XSTRLEN(hostName), 0, NULL), WOLFSSL_SUCCESS); + + wolfSSL_X509_free(leafUri); + wolfSSL_X509_free(leafUriDns); + wolfSSL_EVP_PKEY_free(priv); +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_CertManagerCRL(void) { EXPECT_DECLS; diff --git a/tests/api/test_certman.h b/tests/api/test_certman.h index 52bef8e1aa0..173c0f7e308 100644 --- a/tests/api/test_certman.h +++ b/tests/api/test_certman.h @@ -40,6 +40,7 @@ int test_wolfSSL_CertManagerNameConstraint_IP_SAN(void); int test_wolfSSL_CertManagerNameConstraint_RID_SAN(void); int test_wolfSSL_X509_get_ext_d2i_RID_SAN(void); int test_wolfSSL_X509_check_host_IP_only_SAN_CN_fallback(void); +int test_wolfSSL_X509_check_host_URI_SAN_not_DNS_match(void); int test_wolfSSL_CertManagerCRL(void); int test_wolfSSL_CRL_reason_extensions_cleanup(void); int test_wolfSSL_CRL_static_revoked_list(void); @@ -70,6 +71,8 @@ int test_wolfSSL_X509_V_ERR_strings(void); TEST_DECL_GROUP("certman", test_wolfSSL_X509_get_ext_d2i_RID_SAN), \ TEST_DECL_GROUP("certman", \ test_wolfSSL_X509_check_host_IP_only_SAN_CN_fallback), \ + TEST_DECL_GROUP("certman", \ + test_wolfSSL_X509_check_host_URI_SAN_not_DNS_match), \ TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerCRL), \ TEST_DECL_GROUP("certman", test_wolfSSL_CRL_reason_extensions_cleanup), \ TEST_DECL_GROUP("certman", test_wolfSSL_CRL_static_revoked_list), \ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index a4bd0d7ae24..127e0837d5f 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -18669,6 +18669,10 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag, * - CheckForAltNames (TLS hostname matching): skips ASN_RID_TYPE * unconditionally and excludes them from *checkCN, so a cert * with only registeredID SANs still falls back to CN. + * - CheckForAltNames (TLS hostname matching): skips ASN_URI_TYPE + * for DNS hostname checks (RFC 6125 Sec. 6.4 / RFC 9525 Sec. 6.3) + * but URI SAN presence still suppresses CN fallback (RFC 6125 + * Sec. 6.4.4) because URI-ID is a distinct presented identifier. * - DNS_to_GENERAL_NAME (used by wolfSSL_X509_get_ext) and the * ALT_NAMES_OID arm of wolfSSL_X509_get_ext_d2i: build a proper * ASN1_OBJECT in d.registeredID from raw OID bytes regardless From a28ea7ac1cb01d4172453d646872b96b0f5c79d0 Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 20 May 2026 16:31:32 -0700 Subject: [PATCH 2/4] NULL *response on error in wolfSSL_d2i_OCSP_RESPONSE. Thanks to Zou Dikai for the report. --- src/ocsp.c | 8 ++++++++ tests/api/test_ocsp.c | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/src/ocsp.c b/src/ocsp.c index b90fcc8af9b..7aae36d075c 100644 --- a/src/ocsp.c +++ b/src/ocsp.c @@ -1286,6 +1286,8 @@ OcspResponse* wolfSSL_d2i_OCSP_RESPONSE(OcspResponse** response, resp->source = (byte*)XMALLOC((size_t)len, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (resp->source == NULL) { XFREE(resp, NULL, DYNAMIC_TYPE_OCSP_REQUEST); + if (response != NULL && *response == resp) + *response = NULL; return NULL; } resp->single = (OcspEntry*)XMALLOC(sizeof(OcspEntry), NULL, @@ -1293,6 +1295,8 @@ OcspResponse* wolfSSL_d2i_OCSP_RESPONSE(OcspResponse** response, if (resp->single == NULL) { XFREE(resp->source, NULL, DYNAMIC_TYPE_TMP_BUFFER); XFREE(resp, NULL, DYNAMIC_TYPE_OCSP_REQUEST); + if (response != NULL && *response == resp) + *response = NULL; return NULL; } XMEMSET(resp->single, 0, sizeof(OcspEntry)); @@ -1303,6 +1307,8 @@ OcspResponse* wolfSSL_d2i_OCSP_RESPONSE(OcspResponse** response, XFREE(resp->source, NULL, DYNAMIC_TYPE_TMP_BUFFER); XFREE(resp->single, NULL, DYNAMIC_TYPE_OCSP_ENTRY); XFREE(resp, NULL, DYNAMIC_TYPE_OCSP_REQUEST); + if (response != NULL && *response == resp) + *response = NULL; return NULL; } XMEMSET(resp->single->status, 0, sizeof(CertStatus)); @@ -1315,6 +1321,8 @@ OcspResponse* wolfSSL_d2i_OCSP_RESPONSE(OcspResponse** response, /* for just converting from a DER to an internal structure the CA may * not yet be known to this function for signature verification */ wolfSSL_OCSP_RESPONSE_free(resp); + if (response != NULL && *response == resp) + *response = NULL; return NULL; } diff --git a/tests/api/test_ocsp.c b/tests/api/test_ocsp.c index bd4a0633bb1..7945c1433c4 100644 --- a/tests/api/test_ocsp.c +++ b/tests/api/test_ocsp.c @@ -247,6 +247,15 @@ int test_ocsp_basic_verify(void) ExpectNull( response = wolfSSL_d2i_OCSP_RESPONSE(NULL, &ptr, sizeof(resp_bad))); + /* reuse failure must clear caller pointer */ + ptr = (const unsigned char*)resp; + ExpectNotNull( + response = wolfSSL_d2i_OCSP_RESPONSE(&response, &ptr, sizeof(resp))); + ptr = (const unsigned char*)resp_bad; + ExpectNull( + wolfSSL_d2i_OCSP_RESPONSE(&response, &ptr, sizeof(resp_bad))); + ExpectNull(response); + ptr = (const unsigned char*)resp; ExpectNotNull( response = wolfSSL_d2i_OCSP_RESPONSE(NULL, &ptr, sizeof(resp))); From 1e338487db3fcb163d146a889998f1c0830f0d64 Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 20 May 2026 17:25:15 -0700 Subject: [PATCH 3/4] Code review feedback --- src/ocsp.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/ocsp.c b/src/ocsp.c index 7aae36d075c..e129a085959 100644 --- a/src/ocsp.c +++ b/src/ocsp.c @@ -1283,18 +1283,23 @@ OcspResponse* wolfSSL_d2i_OCSP_RESPONSE(OcspResponse** response, XMEMSET(resp, 0, sizeof(OcspResponse)); } + if (resp->source != NULL) + XFREE(resp->source, NULL, DYNAMIC_TYPE_TMP_BUFFER); resp->source = (byte*)XMALLOC((size_t)len, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (resp->source == NULL) { - XFREE(resp, NULL, DYNAMIC_TYPE_OCSP_REQUEST); + wolfSSL_OCSP_RESPONSE_free(resp); if (response != NULL && *response == resp) *response = NULL; return NULL; } + if (resp->single != NULL) { + FreeOcspEntry(resp->single, NULL); + XFREE(resp->single, NULL, DYNAMIC_TYPE_OCSP_ENTRY); + } resp->single = (OcspEntry*)XMALLOC(sizeof(OcspEntry), NULL, DYNAMIC_TYPE_OCSP_ENTRY); if (resp->single == NULL) { - XFREE(resp->source, NULL, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(resp, NULL, DYNAMIC_TYPE_OCSP_REQUEST); + wolfSSL_OCSP_RESPONSE_free(resp); if (response != NULL && *response == resp) *response = NULL; return NULL; @@ -1304,9 +1309,7 @@ OcspResponse* wolfSSL_d2i_OCSP_RESPONSE(OcspResponse** response, DYNAMIC_TYPE_OCSP_STATUS); resp->single->ownStatus = 1; if (resp->single->status == NULL) { - XFREE(resp->source, NULL, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(resp->single, NULL, DYNAMIC_TYPE_OCSP_ENTRY); - XFREE(resp, NULL, DYNAMIC_TYPE_OCSP_REQUEST); + wolfSSL_OCSP_RESPONSE_free(resp); if (response != NULL && *response == resp) *response = NULL; return NULL; From 44729807384db34e57fe254096793b4dedf39213 Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 27 May 2026 16:53:19 -0700 Subject: [PATCH 4/4] Code review feedback and minor fixes. Remove outdated RFC, refactor into single error case, guard against negative/0 len and NULL *data pointer, don't set ownStatus until status is confirmed non-NULL. --- src/internal.c | 9 ++++----- src/ocsp.c | 39 +++++++++++++++------------------------ wolfcrypt/src/asn.c | 6 +++--- 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/src/internal.c b/src/internal.c index ce5f2d7ab88..45a1f809fc9 100644 --- a/src/internal.c +++ b/src/internal.c @@ -13696,11 +13696,10 @@ int CheckForAltNames(DecodedCert* dCert, const char* domain, word32 domainLen, continue; } - /* RFC 6125 Sec. 6.4 / RFC 9525 Sec. 6.3: a DNS-ID reference - * identifier is matched only against dNSName SAN entries, never - * uniformResourceIdentifier (even when the URI value resembles a - * hostname). URI-ID matching requires scheme and host parsing - * (RFC 9525 Sec. 6.5, Sec. 7.2). */ + /* RFC 9525 Sec. 6.3: a DNS-ID reference identifier is matched only + * against dNSName SAN entries, never uniformResourceIdentifier + * (even when the URI value resembles a ostname). URI-ID matching + * requires scheme and host parsing (RFC 9525 Sec. 6.5, Sec. 7.2). */ if (!isIP && altName->type == ASN_URI_TYPE) { WOLFSSL_MSG("\tAltName is uniformResourceIdentifier, " "skipping for DNS hostname"); diff --git a/src/ocsp.c b/src/ocsp.c index e129a085959..14d5eb13547 100644 --- a/src/ocsp.c +++ b/src/ocsp.c @@ -1270,7 +1270,7 @@ OcspResponse* wolfSSL_d2i_OCSP_RESPONSE(OcspResponse** response, int length = 0; int ret; - if (data == NULL) + if (data == NULL || *data == NULL || len <= 0) return NULL; if (response != NULL) @@ -1286,36 +1286,24 @@ OcspResponse* wolfSSL_d2i_OCSP_RESPONSE(OcspResponse** response, if (resp->source != NULL) XFREE(resp->source, NULL, DYNAMIC_TYPE_TMP_BUFFER); resp->source = (byte*)XMALLOC((size_t)len, NULL, DYNAMIC_TYPE_TMP_BUFFER); - if (resp->source == NULL) { - wolfSSL_OCSP_RESPONSE_free(resp); - if (response != NULL && *response == resp) - *response = NULL; - return NULL; - } + if (resp->source == NULL) + goto error; + if (resp->single != NULL) { FreeOcspEntry(resp->single, NULL); XFREE(resp->single, NULL, DYNAMIC_TYPE_OCSP_ENTRY); } resp->single = (OcspEntry*)XMALLOC(sizeof(OcspEntry), NULL, DYNAMIC_TYPE_OCSP_ENTRY); - if (resp->single == NULL) { - wolfSSL_OCSP_RESPONSE_free(resp); - if (response != NULL && *response == resp) - *response = NULL; - return NULL; - } + if (resp->single == NULL) + goto error; XMEMSET(resp->single, 0, sizeof(OcspEntry)); resp->single->status = (CertStatus*)XMALLOC(sizeof(CertStatus), NULL, DYNAMIC_TYPE_OCSP_STATUS); + if (resp->single->status == NULL) + goto error; resp->single->ownStatus = 1; - if (resp->single->status == NULL) { - wolfSSL_OCSP_RESPONSE_free(resp); - if (response != NULL && *response == resp) - *response = NULL; - return NULL; - } XMEMSET(resp->single->status, 0, sizeof(CertStatus)); - XMEMCPY(resp->source, *data, (size_t)len); resp->maxIdx = (word32)len; @@ -1323,10 +1311,7 @@ OcspResponse* wolfSSL_d2i_OCSP_RESPONSE(OcspResponse** response, if (ret != 0 && ret != WC_NO_ERR_TRACE(ASN_OCSP_CONFIRM_E)) { /* for just converting from a DER to an internal structure the CA may * not yet be known to this function for signature verification */ - wolfSSL_OCSP_RESPONSE_free(resp); - if (response != NULL && *response == resp) - *response = NULL; - return NULL; + goto error; } if (GetSequence(*data, &idx, &length, (word32)len) >= 0) @@ -1336,6 +1321,12 @@ OcspResponse* wolfSSL_d2i_OCSP_RESPONSE(OcspResponse** response, *response = resp; return resp; + +error: + wolfSSL_OCSP_RESPONSE_free(resp); + if (response != NULL && *response == resp) + *response = NULL; + return NULL; } int wolfSSL_i2d_OCSP_RESPONSE(OcspResponse* response, diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 127e0837d5f..7ec41b4eeaf 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -18670,9 +18670,9 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag, * unconditionally and excludes them from *checkCN, so a cert * with only registeredID SANs still falls back to CN. * - CheckForAltNames (TLS hostname matching): skips ASN_URI_TYPE - * for DNS hostname checks (RFC 6125 Sec. 6.4 / RFC 9525 Sec. 6.3) - * but URI SAN presence still suppresses CN fallback (RFC 6125 - * Sec. 6.4.4) because URI-ID is a distinct presented identifier. + * for DNS hostname checks (RFC 9525 Sec. 6.3) but URI SAN presence + * still suppresses CN fallback because URI-ID is a distinct presented + * identifier. * - DNS_to_GENERAL_NAME (used by wolfSSL_X509_get_ext) and the * ALT_NAMES_OID arm of wolfSSL_X509_get_ext_d2i: build a proper * ASN1_OBJECT in d.registeredID from raw OID bytes regardless