Merge pull request #10354 from embhorn/zd21725

Fix IPSAN and registeredID handling
This commit is contained in:
David Garske
2026-05-08 12:15:37 -07:00
committed by GitHub
11 changed files with 740 additions and 53 deletions
Binary file not shown.
+25
View File
@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEOjCCAyKgAwIBAgIUBD6sBdFHNsQ5qoW3MIkJ/BKsB/4wDQYJKoZIhvcNAQEL
BQAwezELMAkGA1UEBhMCQVUxEzARBgNVBAgMClF1ZWVuc2xhbmQxETAPBgNVBAcM
CEJyaXNiYW5lMRQwEgYDVQQKDAt3b2xmU1NMIEluYzEUMBIGA1UECwwLRW5naW5l
ZXJpbmcxGDAWBgNVBAMMD3d3dy53b2xmc3NsLmNvbTAeFw0yNjA0MjkyMTAzMTZa
Fw0yOTAxMjMyMTAzMTZaMHsxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApRdWVlbnNs
YW5kMREwDwYDVQQHDAhCcmlzYmFuZTEUMBIGA1UECgwLd29sZlNTTCBJbmMxFDAS
BgNVBAsMC0VuZ2luZWVyaW5nMRgwFgYDVQQDDA93d3cud29sZnNzbC5jb20wggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAlQjhV0HycW230kVBJwFlxkWu
8rwkMLiVzi9O1vYciLx8n/uoZ3/+XJxRdfeKygfnNS+P4b17wC98q2SoF/zKXXu6
4CHlci5vLobYlXParBtTuV8/1xkNJU/hY2NRiwtkP61DuKUcXDSzrgCgY8X2fwtZ
aHhzpowYqQJtr8MZAS64EOPGzEC0aaNGM2mHbsS7F6bz6N2tc7x7LyG1/WZRDL1U
s+FtXxy8I3PRCQOJFNIQuWTDKtChlkq84dQaW8egwMFjeA9ENzAyloAyI5Whd7oT
0pdz4l0lyWoNwzlgpLSwaUJCCenYCLwzILNYIqeq68Th5mGDxdKW39nQT63XAgMB
AAGjgbUwgbIwHQYDVR0OBBYEFLMRMsmSmITiyfjQO24DQsofDo48MB8GA1UdIwQY
MBaAFLMRMsmSmITiyfjQO24DQsofDo48MBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD
VR0PAQH/BAQDAgGGMBYGA1UdHgEB/wQMMAqgCDAGiAQqAwQFMDQGCWCGSAGG+EIB
DQQnFiVUZXN0aW5nIHJlZ2lzdGVyZWRJRCBuYW1lIGNvbnN0cmFpbnRzMA0GCSqG
SIb3DQEBCwUAA4IBAQARs/sdZs71KWRoyWuTevfnS5h5PH9S07xl+hFcfSXpI6sx
gQoBMzEv8BBN3vLL4HOoNCwnqmFbwG5ogt/HuohAT6bXtdIkK1jNEP50t1pdDOmi
/3fYKDvjvtX2DDQe2K8o/qVsu63MtKkOuc1ZURtsVrlrFFW7D7FsuNxa1dS8fbGl
qrEnIXKemPmco5EIFDygXFIFkL/CP/LxHjTOuib68hBUDOqhMpMruSp7OfAt9Kfe
rsEVY5mv82DfaCjo9bw17dSHUFo4vC+wzB5E8Wg33Pxbn0v8aRTvAsPz22Mlkd9b
mxdLFh0mqPBj/UvWOK1xH0kXGsblNNAz5KeDceAZ
-----END CERTIFICATE-----
+29
View File
@@ -196,6 +196,35 @@ EOF
gen_cert
rm -f ./certs/test/cert-ext-ncip.cfg
OUT=certs/test/cert-ext-ncrid
KEYFILE=certs/test/cert-ext-ncrid-key.der
CONFIG=certs/test/cert-ext-ncrid.cfg
tee >$CONFIG <<EOF
[ req ]
distinguished_name = req_distinguished_name
prompt = no
x509_extensions = v3_ca
[ req_distinguished_name ]
C = AU
ST = Queensland
L = Brisbane
O = wolfSSL Inc
OU = Engineering
CN = www.wolfssl.com
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
nameConstraints = critical,permitted;RID:1.2.3.4.5
nsComment = "Testing registeredID name constraints"
EOF
gen_cert
rm -f ./certs/test/cert-ext-ncrid.cfg
OUT=certs/test/cert-ext-ia
KEYFILE=certs/test/cert-ext-ia-key.der
CONFIG=certs/test/cert-ext-ia.cfg
+2
View File
@@ -13,6 +13,8 @@ EXTRA_DIST += \
certs/test/cert-ext-nc-combined.pem \
certs/test/cert-ext-ncip.der \
certs/test/cert-ext-ncip.pem \
certs/test/cert-ext-ncrid.der \
certs/test/cert-ext-ncrid.pem \
certs/test/cert-ext-ncdns.der \
certs/test/cert-ext-ncdns.pem \
certs/test/cert-ext-ncmulti.der \
+30 -2
View File
@@ -13575,8 +13575,29 @@ int CheckForAltNames(DecodedCert* dCert, const char* domain, word32 domainLen,
if (dCert != NULL)
altName = dCert->altNames;
if (checkCN != NULL)
*checkCN = (altName == NULL) ? 1 : 0;
if (checkCN != NULL) {
/* CN fallback is suppressed when the cert presents any altName
* usable for hostname matching. Without WOLFSSL_IP_ALT_NAME the
* iPAddress branch below is compiled out, so iPAddress entries
* cannot match anything here; treat them as absent so a cert
* presenting only iPAddress SANs still falls back to CN as it
* did before iPAddress entries were unconditionally added to
* altNames for name-constraint enforcement. The same reasoning
* applies to registeredID entries: hostname matching against an
* OID is not meaningful, so they never count toward *checkCN. */
DNS_entry* a = altName;
*checkCN = 1;
for (; a != NULL; a = a->next) {
#ifndef WOLFSSL_IP_ALT_NAME
if (a->type == ASN_IP_TYPE)
continue;
#endif
if (a->type == ASN_RID_TYPE)
continue;
*checkCN = 0;
break;
}
}
for (; altName != NULL; altName = altName->next) {
WOLFSSL_MSG("\tindividual AltName check");
@@ -13599,6 +13620,13 @@ int CheckForAltNames(DecodedCert* dCert, const char* domain, word32 domainLen,
continue;
}
/* registeredID entries hold raw OID bytes; they are not eligible
* for hostname matching regardless of build flags. */
if (altName->type == ASN_RID_TYPE) {
WOLFSSL_MSG("\tAltName is registeredID, skipping for hostname");
continue;
}
if (MatchDomainName(buf, (int)len, domain, domainLen, flags)) {
match = 1;
if (checkCN != NULL) {
+66 -6
View File
@@ -650,8 +650,14 @@ static int DNS_to_GENERAL_NAME(WOLFSSL_GENERAL_NAME* gn, DNS_entry* dns)
/* @TODO extract dir name info from DNS_entry */
break;
#ifdef WOLFSSL_RID_ALT_NAME
case WOLFSSL_GEN_RID:
/* registeredID is parsed into altNames unconditionally so
* ConfirmNameConstraints can enforce RID name constraints
* (RFC 5280 Sec. 4.2.1.10). The body uses only the raw OID
* bytes carried in dns->name/dns->len and constructs a
* proper ASN1_OBJECT, so this case is independent of
* WOLFSSL_RID_ALT_NAME (which only gates the human-readable
* ridString form). */
gn->type = dns->type;
/* wolfSSL_GENERAL_NAME_new() mallocs this by default */
wolfSSL_ASN1_STRING_free(gn->d.ia5);
@@ -681,7 +687,6 @@ static int DNS_to_GENERAL_NAME(WOLFSSL_GENERAL_NAME* gn, DNS_entry* dns)
gn->d.registeredID->dynamic |= WOLFSSL_ASN1_DYNAMIC_DATA;
gn->d.registeredID->grp = oidCertExtType;
break;
#endif
case WOLFSSL_GEN_X400:
/* Unsupported: fall through */
@@ -2533,8 +2538,12 @@ void* wolfSSL_X509_get_ext_d2i(const WOLFSSL_X509* x509, int nid, int* c,
gn->d.iPAddress->type = WOLFSSL_V_ASN1_OCTET_STRING;
break;
#ifdef WOLFSSL_RID_ALT_NAME
case ASN_RID_TYPE:
/* Always handle registeredID: the union
* member d.registeredID is populated from
* raw OID body bytes. WOLFSSL_RID_ALT_NAME
* only gates the human-readable ridString,
* which this path does not need. */
gn->type = dns->type;
/* Free ia5 before using union for registeredID */
wolfSSL_ASN1_STRING_free(gn->d.ia5);
@@ -2567,7 +2576,6 @@ void* wolfSSL_X509_get_ext_d2i(const WOLFSSL_X509* x509, int nid, int* c,
WOLFSSL_ASN1_DYNAMIC_DATA;
gn->d.registeredID->grp = oidCertExtType;
break;
#endif /* WOLFSSL_RID_ALT_NAME */
default:
gn->type = dns->type;
@@ -4272,6 +4280,36 @@ char* wolfSSL_X509_get_next_altname(WOLFSSL_X509* cert)
return NULL;
}
/* In default builds iPAddress entries hold raw 4/16 octet payloads
* and registeredID entries hold raw OID body bytes (no human-readable
* ipString/ridString), so returning them as a C string would truncate
* at any embedded NUL byte. Such entries are still parsed into
* altNames for name-constraint enforcement; skip them here so
* string-iteration callers see the same set of entries as before.
*
* With WOLFSSL_MULTICIRCULATE_ALTNAMELIST, a list consisting only of
* skipped entries collapses to "no entries" on the first pass and
* resets to head on the next call; the cycle shape matches the
* pre-fix behavior where such entries were never parsed. */
#if !defined(WOLFSSL_IP_ALT_NAME) || !defined(WOLFSSL_RID_ALT_NAME)
while (cert->altNamesNext != NULL) {
int skip = 0;
#ifndef WOLFSSL_IP_ALT_NAME
if (cert->altNamesNext->type == ASN_IP_TYPE)
skip = 1;
#endif
#ifndef WOLFSSL_RID_ALT_NAME
if (cert->altNamesNext->type == ASN_RID_TYPE)
skip = 1;
#endif
if (!skip)
break;
cert->altNamesNext = cert->altNamesNext->next;
}
if (cert->altNamesNext == NULL)
return NULL;
#endif /* !WOLFSSL_IP_ALT_NAME || !WOLFSSL_RID_ALT_NAME */
/* unsafe cast required for ABI compatibility. */
ret = (char *)(wc_ptr_t)cert->altNamesNext->name;
#ifdef WOLFSSL_IP_ALT_NAME
@@ -4279,6 +4317,12 @@ char* wolfSSL_X509_get_next_altname(WOLFSSL_X509* cert)
if (cert->altNamesNext->type == ASN_IP_TYPE) {
ret = cert->altNamesNext->ipString;
}
#endif
#ifdef WOLFSSL_RID_ALT_NAME
/* return the registeredID as a string */
if (cert->altNamesNext->type == ASN_RID_TYPE) {
ret = cert->altNamesNext->ridString;
}
#endif
cert->altNamesNext = cert->altNamesNext->next;
@@ -6909,6 +6953,14 @@ static int X509_print_name_entry(WOLFSSL_BIO* bio,
len = XSNPRINTF(scratch, MAX_WIDTH, "IP Address:%s",
entry->ipString);
}
#else
else if (entry->type == ASN_IP_TYPE) {
/* iPAddress entries are now always parsed into altNames so
* name constraints can be enforced. Without the
* human-readable ipString field, emit a fixed label so this
* print path does not fail. */
len = XSNPRINTF(scratch, MAX_WIDTH, "IP Address:<unavailable>");
}
#endif /* OPENSSL_ALL || WOLFSSL_IP_ALT_NAME */
else if (entry->type == ASN_RFC822_TYPE) {
len = XSNPRINTF(scratch, MAX_WIDTH, "email:%s",
@@ -6921,12 +6973,20 @@ static int X509_print_name_entry(WOLFSSL_BIO* bio,
len = XSNPRINTF(scratch, MAX_WIDTH, "URI:%s",
entry->name);
}
#if defined(OPENSSL_ALL)
#ifdef WOLFSSL_RID_ALT_NAME
else if (entry->type == ASN_RID_TYPE) {
len = XSNPRINTF(scratch, MAX_WIDTH, "Registered ID:%s",
entry->ridString);
}
#endif
#else
else if (entry->type == ASN_RID_TYPE) {
/* registeredID entries are now always parsed into altNames
* so name constraints can be enforced. Without the
* human-readable ridString field, emit a fixed label so
* this print path does not fail. */
len = XSNPRINTF(scratch, MAX_WIDTH, "Registered ID:<unavailable>");
}
#endif /* WOLFSSL_RID_ALT_NAME */
else if (entry->type == ASN_OTHER_TYPE) {
len = XSNPRINTF(scratch, MAX_WIDTH,
"othername <unsupported>");
+61 -22
View File
@@ -24006,11 +24006,25 @@ static word32 build_otherName_san(byte* out, word32 outSz, const char* val7)
return (word32)(sizeof(prefix) + 7);
}
/* Build a SubjectAltName extension value with a single registeredID
* GeneralName for OID 1.2.3.4, to use as the leaf SAN in RID tests. */
static word32 build_registeredID_san(byte* out, word32 outSz)
{
static const byte ridSan[] = {
0x30, 0x05, /* SEQUENCE, 5 */
0x88, 0x03, /* [8] regId, 3 */
0x2A, 0x03, 0x04 /* OID 1.2.3.4 */
};
if (outSz < sizeof(ridSan))
return 0;
XMEMCPY(out, ridSan, sizeof(ridSan));
return (word32)sizeof(ridSan);
}
/* Build a NameConstraints extension value with a single excludedSubtree
* carrying a registeredID GeneralName for OID 1.2.3.4. registeredID is a
* GeneralName form wolfSSL does not enforce, so DecodeSubtree() must
* record it as 'unsupported' and ConfirmNameConstraints() must fail
* closed when the extension is critical (RFC 5280 4.2.1.10). */
* carrying a registeredID GeneralName for OID 1.2.3.4. wolfSSL enforces
* registeredID name constraints by byte-comparing OID bodies, so a leaf
* whose registeredID SAN matches this exclusion must be rejected. */
static word32 build_registeredID_nameConstraints(byte* out, word32 outSz)
{
static const byte ridNc[] = {
@@ -24187,10 +24201,12 @@ done:
* (excluded is enforced regardless of criticality)
* 4. Critical permitted subtree, leaf SAN matches -> accept
* 5. Critical permitted subtree, leaf SAN does NOT match -> reject
* 6. Critical nameConstraints carrying an unsupported form
* (registeredID), leaf has no relevant SAN -> reject
* (RFC 5280 4.2.1.10 fail-closed for unprocessed forms)
* 7. Same as (6) but non-critical -> accept
* 6. Critical excluded registeredID subtree, leaf SAN matches -> reject
* (registeredID is enforced by byte-comparison of OID bodies)
* 7. Non-critical excluded registeredID subtree, leaf SAN matches -> reject
* (excluded subtrees are enforced regardless of criticality)
* 8. Critical excluded registeredID subtree, leaf has no RID SAN -> accept
* (no match in excluded list, constraint is satisfied)
*/
static int test_NameConstraints_OtherName(void)
{
@@ -24203,16 +24219,19 @@ static int test_NameConstraints_OtherName(void)
defined(HAVE_OID_ENCODING) && !defined(IGNORE_NAME_CONSTRAINTS)
byte sanBlocked[64];
byte sanAllowed[64];
byte sanRegisteredID[16];
byte ncExcludedBlocked[64];
byte ncPermittedAllowed[64];
byte ncRegisteredID[16];
word32 sanBlockedSz, sanAllowedSz;
word32 sanBlockedSz, sanAllowedSz, sanRegisteredIDSz;
word32 ncExcludedBlockedSz, ncPermittedAllowedSz, ncRegisteredIDSz;
sanBlockedSz =
build_otherName_san(sanBlocked, sizeof(sanBlocked), "blocked");
sanAllowedSz =
build_otherName_san(sanAllowed, sizeof(sanAllowed), "allowed");
sanRegisteredIDSz =
build_registeredID_san(sanRegisteredID, sizeof(sanRegisteredID));
ncExcludedBlockedSz = build_otherName_nameConstraints(
ncExcludedBlocked, sizeof(ncExcludedBlocked), 1, "blocked");
ncPermittedAllowedSz = build_otherName_nameConstraints(
@@ -24221,6 +24240,7 @@ static int test_NameConstraints_OtherName(void)
ncRegisteredID, sizeof(ncRegisteredID));
ExpectIntGT((int)sanBlockedSz, 0);
ExpectIntGT((int)sanAllowedSz, 0);
ExpectIntGT((int)sanRegisteredIDSz, 0);
ExpectIntGT((int)ncExcludedBlockedSz, 0);
ExpectIntGT((int)ncPermittedAllowedSz, 0);
ExpectIntGT((int)ncRegisteredIDSz, 0);
@@ -24263,20 +24283,25 @@ static int test_NameConstraints_OtherName(void)
sanBlocked, sanBlockedSz),
WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
/* (6) Critical nameConstraints carrying a GeneralName form wolfSSL
* does not enforce (registeredID). RFC 5280 4.2.1.10 requires the
* verifier to either process the constraint or reject; we reject
* fail-closed. The leaf needs no SAN to exercise this path. */
/* (6) Critical excluded registeredID subtree, leaf SAN matches:
* registeredID is now enforced by byte-comparing OID bodies, so a
* matching excluded entry must be rejected. */
ExpectIntEQ(verify_with_otherName_chain(
ncRegisteredID, ncRegisteredIDSz, 1, NULL, 0),
ncRegisteredID, ncRegisteredIDSz, 1,
sanRegisteredID, sanRegisteredIDSz),
WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
/* (7) Same as (6) but non-critical: RFC 5280 only mandates the
* fail-closed reject when the extension is critical, so a
* non-critical unsupported constraint form is silently ignored
* and verification succeeds. */
/* (7) Non-critical excluded registeredID subtree, leaf SAN matches:
* excluded subtrees are enforced regardless of criticality. */
ExpectIntEQ(verify_with_otherName_chain(
ncRegisteredID, ncRegisteredIDSz, 0, NULL, 0),
ncRegisteredID, ncRegisteredIDSz, 0,
sanRegisteredID, sanRegisteredIDSz),
WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
/* (8) Critical excluded registeredID subtree, leaf has no RID SAN:
* no excluded match, so verification succeeds. */
ExpectIntEQ(verify_with_otherName_chain(
ncRegisteredID, ncRegisteredIDSz, 1, NULL, 0),
0);
#endif
return EXPECT_RESULT();
@@ -26733,11 +26758,25 @@ static int test_wolfSSL_X509_print(void)
/* Will print IP address subject alt name. */
ExpectIntEQ(BIO_get_mem_data(bio, NULL), 3350);
#endif
#elif defined(NO_ASN_TIME)
/* With NO_ASN_TIME defined, X509_print skips printing Validity. */
#elif defined(IGNORE_NAME_CONSTRAINTS)
/* DecodeGeneralName skips iPAddress entries when name constraints
* are disabled, so the IP SAN never reaches the print path. */
#if defined(NO_ASN_TIME)
ExpectIntEQ(BIO_get_mem_data(bio, NULL), 3213);
#else
#else
ExpectIntEQ(BIO_get_mem_data(bio, NULL), 3328);
#endif
#elif defined(NO_ASN_TIME)
/* With NO_ASN_TIME defined, X509_print skips printing Validity.
* iPAddress SAN now always parsed; prints as
* "IP Address:<unavailable>" (+26 bytes) without
* WOLFSSL_IP_ALT_NAME. */
ExpectIntEQ(BIO_get_mem_data(bio, NULL), 3239);
#else
/* iPAddress SAN now always parsed; prints as
* "IP Address:<unavailable>" (+26 bytes) without
* WOLFSSL_IP_ALT_NAME. */
ExpectIntEQ(BIO_get_mem_data(bio, NULL), 3354);
#endif
BIO_free(bio);
bio = NULL;
+404
View File
@@ -1705,6 +1705,410 @@ int test_wolfSSL_CertManagerNameConstraint_DNS_CN(void)
return EXPECT_RESULT();
}
int test_wolfSSL_CertManagerNameConstraint_IP_SAN(void)
{
EXPECT_DECLS;
#if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \
!defined(NO_WOLFSSL_CM_VERIFY) && !defined(NO_RSA) && \
defined(OPENSSL_EXTRA) && defined(WOLFSSL_CERT_GEN) && \
defined(WOLFSSL_CERT_EXT) && defined(WOLFSSL_ALT_NAMES) && \
!defined(NO_SHA256) && !defined(IGNORE_NAME_CONSTRAINTS)
/* Regression test for iPAddress name-constraint enforcement.
*
* The CA at cert-ext-ncip.der declares a permittedSubtrees iPAddress
* constraint of 192.168.1.0/255.255.255.0. A leaf with an iPAddress
* SAN outside that subnet must be rejected. Prior to the fix, default
* builds (without WOLFSSL_IP_ALT_NAME) silently skipped iPAddress SANs
* during parsing, so the constraint loop saw no IP entries and the
* leaf was accepted.
*
* The bypass only existed when WOLFSSL_IP_ALT_NAME was undefined (the
* default). To exercise the regression target, this test must run in a
* configuration without --enable-ip-alt-name and without
* --enable-opensslall (which implies WOLFSSL_IP_ALT_NAME via
* settings.h). With WOLFSSL_IP_ALT_NAME defined the same assertions
* still hold, but the negative case there is enforcement of an
* already-working path rather than the regression itself.
*
* Scope: this test exercises the permittedSubtrees code path. The
* excludedSubtrees path uses the same parsing plumbing
* (DecodeGeneralName -> SetDNSEntry into cert->altNames) and the same
* ConfirmNameConstraints walk; the TALOS bug was strictly about
* iPAddress entries being absent from cert->altNames, so once that is
* fixed both directions are restored. The pre-existing
* test_wolfSSL_NAME_CONSTRAINTS_excluded test exercises the excluded
* direction more broadly. */
WOLFSSL_CERT_MANAGER* cm = NULL;
WOLFSSL_EVP_PKEY *priv = NULL;
WOLFSSL_X509_NAME* name = NULL;
const char* ca_cert = "./certs/test/cert-ext-ncip.der";
const char* server_cert = "./certs/test/server-goodcn.pem";
/* Raw IPv4 bytes for SAN values (not dotted-quad strings). */
static const byte ip_inside[] = { 192, 168, 1, 10 }; /* permitted */
static const byte ip_outside[] = { 10, 0, 0, 1 }; /* violates */
byte *der = NULL;
int derSz;
byte *pt;
WOLFSSL_X509 *x509 = NULL;
WOLFSSL_X509 *ca = 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(cm = wolfSSL_CertManagerNew());
ExpectNotNull(ca = wolfSSL_X509_load_certificate_file(ca_cert,
WOLFSSL_FILETYPE_ASN1));
ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(ca, &derSz)));
ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, der, derSz,
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
/* Negative case: leaf with IP SAN outside permitted subnet. Must be
* rejected with ASN_NAME_INVALID_E. */
ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(server_cert,
WOLFSSL_FILETYPE_PEM));
ExpectNotNull(name = wolfSSL_X509_get_subject_name(ca));
ExpectIntEQ(wolfSSL_X509_set_issuer_name(x509, name), WOLFSSL_SUCCESS);
name = NULL;
/* Use add_altname_ex with raw IP bytes so the test runs in default
* builds where add_altname (string form) requires WOLFSSL_IP_ALT_NAME. */
ExpectIntEQ(wolfSSL_X509_add_altname_ex(x509, (const char*)ip_outside,
sizeof(ip_outside), ASN_IP_TYPE), WOLFSSL_SUCCESS);
ExpectIntGT(wolfSSL_X509_sign(x509, priv, EVP_sha256()), 0);
ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(x509, &derSz)));
ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz,
WOLFSSL_FILETYPE_ASN1), WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
wolfSSL_X509_free(x509);
x509 = NULL;
/* Positive case: leaf with IP SAN inside the permitted subnet must be
* accepted. Confirms the fix does not over-reject. */
ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(server_cert,
WOLFSSL_FILETYPE_PEM));
ExpectNotNull(name = wolfSSL_X509_get_subject_name(ca));
ExpectIntEQ(wolfSSL_X509_set_issuer_name(x509, name), WOLFSSL_SUCCESS);
name = NULL;
ExpectIntEQ(wolfSSL_X509_add_altname_ex(x509, (const char*)ip_inside,
sizeof(ip_inside), ASN_IP_TYPE), WOLFSSL_SUCCESS);
ExpectIntGT(wolfSSL_X509_sign(x509, priv, EVP_sha256()), 0);
ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(x509, &derSz)));
ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz,
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
wolfSSL_CertManagerFree(cm);
wolfSSL_X509_free(x509);
wolfSSL_X509_free(ca);
wolfSSL_EVP_PKEY_free(priv);
#endif
return EXPECT_RESULT();
}
int test_wolfSSL_CertManagerNameConstraint_RID_SAN(void)
{
EXPECT_DECLS;
#if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \
!defined(NO_WOLFSSL_CM_VERIFY) && !defined(NO_RSA) && \
defined(OPENSSL_EXTRA) && defined(WOLFSSL_CERT_GEN) && \
defined(WOLFSSL_CERT_EXT) && defined(WOLFSSL_ALT_NAMES) && \
!defined(NO_SHA256) && !defined(IGNORE_NAME_CONSTRAINTS)
/* Regression test for registeredID name-constraint enforcement.
*
* The CA at cert-ext-ncrid.der declares a permittedSubtrees
* registeredID constraint of OID 1.2.3.4.5. A leaf with a
* registeredID SAN outside that permitted set must be rejected.
*
* Pre-fix, three independent failures hid the registeredID type from
* enforcement in all build configurations:
* 1. ConfirmNameConstraints' nameTypes[] array did not include
* ASN_RID_TYPE, so the constraint loop never considered
* registeredID entries.
* 2. DecodeSubtree's accepted-tag set excluded
* ASN_CONTEXT_SPECIFIC|ASN_RID_TYPE, so registeredID subtrees
* in the CA's nameConstraints were dropped during parsing and
* never reached signer->permittedNames/excludedNames.
* 3. DecodeGeneralName gated registeredID SAN parsing on
* WOLFSSL_RID_ALT_NAME; without that define cert->altNames had
* no ASN_RID_TYPE entries.
* The bug was unconditional - WOLFSSL_RID_ALT_NAME alone did not
* fix it because layers 1 and 2 still discarded the data. */
WOLFSSL_CERT_MANAGER* cm = NULL;
WOLFSSL_EVP_PKEY *priv = NULL;
WOLFSSL_X509_NAME* name = NULL;
const char* ca_cert = "./certs/test/cert-ext-ncrid.der";
const char* server_cert = "./certs/test/server-goodcn.pem";
/* DER-encoded OID body bytes (no tag/length, since registeredID is
* IMPLICIT [8] OBJECT IDENTIFIER inside a SAN). The first arc encodes
* 40*X + Y in one byte for X<=2,Y<=39, then each subsequent arc is
* base-128 with continuation bits. For values <128 the encoding is a
* single byte. */
static const byte rid_inside[] = { 0x2A, 0x03, 0x04, 0x05 }; /* 1.2.3.4.5 - permitted */
static const byte rid_outside[] = { 0x2A, 0x03, 0x04, 0x63 }; /* 1.2.3.4.99 - violates */
byte *der = NULL;
int derSz;
byte *pt;
WOLFSSL_X509 *x509 = NULL;
WOLFSSL_X509 *ca = 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(cm = wolfSSL_CertManagerNew());
ExpectNotNull(ca = wolfSSL_X509_load_certificate_file(ca_cert,
WOLFSSL_FILETYPE_ASN1));
ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(ca, &derSz)));
ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, der, derSz,
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
/* Negative case: leaf with a registeredID SAN outside the permitted
* OID set. Must be rejected with ASN_NAME_INVALID_E. */
ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(server_cert,
WOLFSSL_FILETYPE_PEM));
ExpectNotNull(name = wolfSSL_X509_get_subject_name(ca));
ExpectIntEQ(wolfSSL_X509_set_issuer_name(x509, name), WOLFSSL_SUCCESS);
name = NULL;
ExpectIntEQ(wolfSSL_X509_add_altname_ex(x509, (const char*)rid_outside,
sizeof(rid_outside), ASN_RID_TYPE), WOLFSSL_SUCCESS);
ExpectIntGT(wolfSSL_X509_sign(x509, priv, EVP_sha256()), 0);
ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(x509, &derSz)));
ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz,
WOLFSSL_FILETYPE_ASN1), WC_NO_ERR_TRACE(ASN_NAME_INVALID_E));
wolfSSL_X509_free(x509);
x509 = NULL;
/* Positive case: leaf with a registeredID SAN matching the permitted
* OID exactly must be accepted. */
ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(server_cert,
WOLFSSL_FILETYPE_PEM));
ExpectNotNull(name = wolfSSL_X509_get_subject_name(ca));
ExpectIntEQ(wolfSSL_X509_set_issuer_name(x509, name), WOLFSSL_SUCCESS);
name = NULL;
ExpectIntEQ(wolfSSL_X509_add_altname_ex(x509, (const char*)rid_inside,
sizeof(rid_inside), ASN_RID_TYPE), WOLFSSL_SUCCESS);
ExpectIntGT(wolfSSL_X509_sign(x509, priv, EVP_sha256()), 0);
ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(x509, &derSz)));
ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz,
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
wolfSSL_CertManagerFree(cm);
wolfSSL_X509_free(x509);
wolfSSL_X509_free(ca);
wolfSSL_EVP_PKEY_free(priv);
#endif
return EXPECT_RESULT();
}
int test_wolfSSL_X509_get_ext_d2i_RID_SAN(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)
/* Regression test: a cert with a registeredID SAN must be exposable
* through the OPENSSL_EXTRA SAN APIs even when WOLFSSL_RID_ALT_NAME
* is undefined.
*
* The registeredID name-constraint fix adds registeredID entries to
* altNames unconditionally so name constraints can be enforced. The
* OPENSSL_EXTRA helpers DNS_to_GENERAL_NAME (used by
* wolfSSL_X509_get_ext) and the ALT_NAMES_OID arm of
* wolfSSL_X509_get_ext_d2i previously gated registeredID handling on
* WOLFSSL_RID_ALT_NAME, which is NOT auto-enabled by
* --enable-opensslextra. Without the fix, any cert with a
* registeredID SAN would cause those getters to return NULL or
* produce a malformed GENERAL_NAME (type==GEN_RID but
* d.dNSName-as-IA5STRING in the union). */
WOLFSSL_EVP_PKEY *priv = NULL;
const char* server_cert = "./certs/test/server-goodcn.pem";
static const byte rid_oid[] = { 0x2A, 0x03, 0x04, 0x05 };
byte *pt;
byte *der = NULL;
int derSz;
WOLFSSL_X509 *leaf = NULL;
WOLFSSL_X509 *parsed = NULL;
WOLFSSL_STACK *altNames = NULL;
WOLFSSL_X509_EXTENSION *ext = NULL;
WOLFSSL_GENERAL_NAME *gn = NULL;
int found = 0;
int i;
int loc;
pt = (byte*)server_key_der_2048;
ExpectNotNull(priv = wolfSSL_d2i_PrivateKey(EVP_PKEY_RSA, NULL,
(const unsigned char**)&pt, sizeof_server_key_der_2048));
/* Build and sign a leaf with a registeredID SAN. */
ExpectNotNull(leaf = wolfSSL_X509_load_certificate_file(server_cert,
WOLFSSL_FILETYPE_PEM));
ExpectIntEQ(wolfSSL_X509_add_altname_ex(leaf, (const char*)rid_oid,
sizeof(rid_oid), ASN_RID_TYPE), WOLFSSL_SUCCESS);
ExpectIntGT(wolfSSL_X509_sign(leaf, priv, EVP_sha256()), 0);
ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(leaf, &derSz)));
/* Re-parse the signed leaf so altNames are populated through
* DecodeGeneralName (the path the fix touches). */
ExpectNotNull(parsed = wolfSSL_X509_load_certificate_buffer(der, derSz,
WOLFSSL_FILETYPE_ASN1));
/* wolfSSL_X509_get_ext_d2i for SAN must return a non-NULL stack
* containing a registeredID GENERAL_NAME with the union populated
* via d.registeredID (not d.dNSName). */
ExpectNotNull(altNames = (WOLFSSL_STACK*)wolfSSL_X509_get_ext_d2i(parsed,
NID_subject_alt_name, NULL, NULL));
if (altNames != NULL) {
for (i = 0; i < wolfSSL_sk_num(altNames); i++) {
gn = (WOLFSSL_GENERAL_NAME*)wolfSSL_sk_value(altNames, i);
if (gn != NULL && gn->type == ASN_RID_TYPE) {
/* The union must hold a real ASN1_OBJECT, not the
* default IA5 string the bypass path would produce.
* Verify the encoded OID bytes match what we put on
* the leaf so a future mis-encoding (wrong tag, off-by
* -one length, swapped fields) is caught. */
ExpectNotNull(gn->d.registeredID);
if (gn->d.registeredID != NULL) {
/* DER-wrapped: ASN_OBJECT_ID + length-byte + body */
word32 expectedSz = 1 + 1 + sizeof(rid_oid);
ExpectIntEQ(gn->d.registeredID->objSz, expectedSz);
ExpectNotNull(gn->d.registeredID->obj);
if (gn->d.registeredID->obj != NULL &&
gn->d.registeredID->objSz == expectedSz) {
ExpectIntEQ(gn->d.registeredID->obj[0], ASN_OBJECT_ID);
ExpectIntEQ(gn->d.registeredID->obj[1],
(byte)sizeof(rid_oid));
ExpectIntEQ(XMEMCMP(gn->d.registeredID->obj + 2,
rid_oid, sizeof(rid_oid)), 0);
}
}
found = 1;
break;
}
}
}
ExpectIntEQ(found, 1);
if (altNames != NULL) {
wolfSSL_sk_pop_free(altNames,
(void (*)(void*))wolfSSL_GENERAL_NAME_free);
}
/* Also exercise wolfSSL_X509_get_ext, which routes through
* DNS_to_GENERAL_NAME. Pre-fix that helper would return WOLFSSL_FAILURE
* for any RID entry in default builds, causing X509_get_ext to return
* NULL even though the SAN extension is present. */
loc = wolfSSL_X509_get_ext_by_NID(parsed, NID_subject_alt_name, -1);
ExpectIntGE(loc, 0);
if (loc >= 0) {
ExpectNotNull(ext = wolfSSL_X509_get_ext(parsed, loc));
}
wolfSSL_X509_free(parsed);
wolfSSL_X509_free(leaf);
wolfSSL_EVP_PKEY_free(priv);
#endif
return EXPECT_RESULT();
}
int test_wolfSSL_X509_check_host_IP_only_SAN_CN_fallback(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)
/* Companion regression test for the CheckForAltNames CN-fallback
* preservation introduced alongside the iPAddress name-constraint
* enforcement fix.
*
* Once iPAddress SAN entries are unconditionally added to altNames
* (so name constraints can be enforced), a leaf that presents only
* iPAddress SANs would suppress CN fallback in CheckForAltNames in
* default builds, where the iPAddress matching path is compiled out.
* That would silently break TLS hostname verification for callers
* that previously relied on the CN fallback. The fix in
* src/internal.c treats iPAddress entries as absent for the
* *checkCN decision when WOLFSSL_IP_ALT_NAME is undefined.
*
* This test pins both directions:
* - default build (no WOLFSSL_IP_ALT_NAME): IP-only-SAN cert with a
* matching CN must succeed via CN fallback.
* - WOLFSSL_IP_ALT_NAME defined: the same cert must fail because
* the SAN presence suppresses CN fallback (RFC 6125 compliant).
* Independently, a cert with a non-matching DNS SAN must always fail
* regardless of build flags, since DNS SAN presence unambiguously
* suppresses CN fallback. */
WOLFSSL_EVP_PKEY *priv = NULL;
WOLFSSL_X509_NAME* name = NULL;
const char* server_cert = "./certs/test/server-goodcn.pem";
const char hostName[] = "cnhost.local";
static const byte ip_san[] = { 10, 0, 0, 1 };
byte *pt;
WOLFSSL_X509 *leafIp = NULL;
WOLFSSL_X509 *leafDns = 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));
/* Leaf with CN matching hostName and only an iPAddress SAN. */
ExpectNotNull(leafIp = 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(leafIp, name), WOLFSSL_SUCCESS);
X509_NAME_free(name);
name = NULL;
ExpectIntEQ(wolfSSL_X509_add_altname_ex(leafIp, (const char*)ip_san,
sizeof(ip_san), ASN_IP_TYPE), WOLFSSL_SUCCESS);
ExpectIntGT(wolfSSL_X509_sign(leafIp, priv, EVP_sha256()), 0);
#ifndef WOLFSSL_IP_ALT_NAME
/* Default build: iPAddress entries are present in altNames for
* constraint enforcement but treated as absent for *checkCN, so the
* lookup falls back to the Subject CN, which matches. */
ExpectIntEQ(wolfSSL_X509_check_host(leafIp, hostName, XSTRLEN(hostName),
0, NULL), WOLFSSL_SUCCESS);
#else
/* IP_ALT_NAME build: SAN presence suppresses CN fallback per RFC 6125.
* The hostName ("cnhost.local") cannot match the iPAddress entry, so
* the check must fail. */
ExpectIntEQ(wolfSSL_X509_check_host(leafIp, hostName, XSTRLEN(hostName),
0, NULL), WC_NO_ERR_TRACE(WOLFSSL_FAILURE));
#endif
/* Leaf with CN matching hostName but a non-matching DNS SAN. CN
* fallback must be suppressed in every build (DNS SAN unambiguously
* counts toward *checkCN), so the check must fail. This pins the
* other side of the boundary so a future change that broadly skips
* altNames in *checkCN does not silently regress. */
ExpectNotNull(leafDns = 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(leafDns, name), WOLFSSL_SUCCESS);
X509_NAME_free(name);
name = NULL;
ExpectIntEQ(wolfSSL_X509_add_altname(leafDns, "other.example",
ASN_DNS_TYPE), WOLFSSL_SUCCESS);
ExpectIntGT(wolfSSL_X509_sign(leafDns, priv, EVP_sha256()), 0);
ExpectIntEQ(wolfSSL_X509_check_host(leafDns, hostName, XSTRLEN(hostName),
0, NULL), WC_NO_ERR_TRACE(WOLFSSL_FAILURE));
wolfSSL_X509_free(leafIp);
wolfSSL_X509_free(leafDns);
wolfSSL_EVP_PKEY_free(priv);
#endif
return EXPECT_RESULT();
}
int test_wolfSSL_CertManagerCRL(void)
{
EXPECT_DECLS;
+9
View File
@@ -36,6 +36,10 @@ int test_wolfSSL_CertManagerNameConstraint3(void);
int test_wolfSSL_CertManagerNameConstraint4(void);
int test_wolfSSL_CertManagerNameConstraint5(void);
int test_wolfSSL_CertManagerNameConstraint_DNS_CN(void);
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_CertManagerCRL(void);
int test_wolfSSL_CRL_reason_extensions_cleanup(void);
int test_wolfSSL_CRL_static_revoked_list(void);
@@ -60,6 +64,11 @@ int test_wolfSSL_CertManagerRejectMD5Cert(void);
TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint4), \
TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint5), \
TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint_DNS_CN), \
TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint_IP_SAN), \
TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint_RID_SAN), \
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_CertManagerCRL), \
TEST_DECL_GROUP("certman", test_wolfSSL_CRL_reason_extensions_cleanup), \
TEST_DECL_GROUP("certman", test_wolfSSL_CRL_static_revoked_list), \
+99 -18
View File
@@ -18081,6 +18081,17 @@ static int PermittedListOk(DNS_entry* name, Base_entry* dnsList, byte nameType)
break;
}
}
else if (nameType == ASN_RID_TYPE) {
/* registeredID matches when the OID bodies are bytewise
* equal. RFC 5280 Sec. 4.2.1.10 does not define a
* subtree relation for OIDs, so use exact-match. */
if (name->len == current->nameSz &&
XMEMCMP(name->name, current->name,
(size_t)name->len) == 0) {
match = 1;
break;
}
}
else if (name->len >= current->nameSz &&
wolfssl_local_MatchBaseName(nameType, name->name, name->len,
current->name, current->nameSz)) {
@@ -18134,6 +18145,16 @@ static int IsInExcludedList(DNS_entry* name, Base_entry* dnsList, byte nameType)
break;
}
}
else if (nameType == ASN_RID_TYPE) {
/* registeredID matches when the OID bodies are bytewise
* equal. See PermittedListOk for the rationale. */
if (name->len == current->nameSz &&
XMEMCMP(name->name, current->name,
(size_t)name->len) == 0) {
ret = 1;
break;
}
}
else if (name->len >= current->nameSz &&
wolfssl_local_MatchBaseName(nameType, name->name, name->len,
current->name, current->nameSz)) {
@@ -18151,7 +18172,8 @@ static int IsInExcludedList(DNS_entry* name, Base_entry* dnsList, byte nameType)
static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert)
{
const byte nameTypes[] = {ASN_RFC822_TYPE, ASN_DNS_TYPE, ASN_DIR_TYPE,
ASN_IP_TYPE, ASN_URI_TYPE, ASN_OTHER_TYPE};
ASN_IP_TYPE, ASN_URI_TYPE, ASN_OTHER_TYPE,
ASN_RID_TYPE};
int i;
if (signer == NULL || cert == NULL)
@@ -18231,6 +18253,11 @@ static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert)
* against the issuing CA's subtree. */
name = cert->altOtherNamesRaw;
break;
case ASN_RID_TYPE:
/* registeredID entries also live on cert->altNames as
* raw OID body bytes. */
name = cert->altNames;
break;
default:
return 0;
}
@@ -18275,9 +18302,9 @@ static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert)
/* RFC 5280 4.2.1.10: "If a name constraints extension that is marked as
* critical imposes constraints on a particular name form ... the
* application MUST either process the constraint or reject the
* certificate." otherName is processed by byte-comparison above; any
* remaining unsupported forms (registeredID, x400Address, ediPartyName)
* trigger the fail-closed reject below. */
* certificate." otherName and registeredID are both processed by
* byte-comparison above; any remaining unsupported forms
* (x400Address, ediPartyName) trigger the fail-closed reject below. */
if (signer->extNameConstraintCrit &&
signer->extNameConstraintHasUnsupported) {
WOLFSSL_MSG("Critical nameConstraints contains unsupported "
@@ -18592,8 +18619,32 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag,
idx += (word32)len;
}
}
#ifdef WOLFSSL_IP_ALT_NAME
/* GeneralName choice: iPAddress */
/* GeneralName choice: iPAddress
*
* Always parse iPAddress into cert->altNames so ConfirmNameConstraints
* can enforce permitted/excluded iPAddress subtrees (RFC 5280
* Sec. 4.2.1.10). The entry holds raw 4/16 octet payloads;
* WOLFSSL_IP_ALT_NAME still gates the human-readable ipString
* generation in SetDNSEntry.
*
* Consequences for downstream consumers when WOLFSSL_IP_ALT_NAME is
* undefined:
* - wolfSSL_X509_get_next_altname (string iterator): explicitly
* skips iPAddress entries, since returning raw bytes as a C
* string would truncate at any embedded NUL. This preserves the
* pre-fix behavior for that getter.
* - CheckForAltNames (TLS hostname matching): the iPAddress branch
* is compiled out, so iPAddress entries cannot match anything;
* they are also excluded from the *checkCN decision so an
* IP-only-SAN cert still falls back to CN matching as before.
* - All other altNames walkers (e.g. ALT_NAMES_OID handling in
* wolfSSL_X509_get_ext_d2i, wolfssl_x509_alt_names_to_gn,
* FlattenAltNames in cert generation) now see iPAddress entries
* unconditionally. This is intentional and brings wolfSSL closer
* to OpenSSL's SAN-exposure semantics; the OPENSSL_EXTRA APIs
* surface the raw octets as OCTET_STRING already (see the
* ASN_IP_TYPE case under WOLFSSL_GEN_IPADD in src/x509.c).
*/
else if (tag == (ASN_CONTEXT_SPECIFIC | ASN_IP_TYPE)) {
ret = SetDNSEntry(cert->heap, (const char*)(input + idx), len,
ASN_IP_TYPE, &cert->altNames);
@@ -18601,9 +18652,27 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag,
idx += (word32)len;
}
}
#endif /* WOLFSSL_IP_ALT_NAME */
#ifdef WOLFSSL_RID_ALT_NAME
/* GeneralName choice: registeredID */
/* GeneralName choice: registeredID
*
* Always parse registeredID into cert->altNames so
* ConfirmNameConstraints can enforce permitted/excluded subtrees
* (RFC 5280 Sec. 4.2.1.10). The entry holds raw OID body bytes;
* WOLFSSL_RID_ALT_NAME only gates the human-readable ridString
* generation in SetDNSEntry. Downstream consumer treatment in
* default builds:
* - wolfSSL_X509_get_next_altname (string iterator): skips
* ASN_RID_TYPE entries (raw OID bytes are not a C string).
* - 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.
* - 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
* of WOLFSSL_RID_ALT_NAME, so OPENSSL_EXTRA-style callers see
* correctly-typed GENERAL_NAME entries.
* - X509_print_name_entry: emits "Registered ID:<unavailable>"
* when ridString is not generated, instead of failing the
* whole print operation. */
else if (tag == (ASN_CONTEXT_SPECIFIC | ASN_RID_TYPE)) {
ret = SetDNSEntry(cert->heap, (const char*)(input + idx), len,
ASN_RID_TYPE, &cert->altNames);
@@ -18611,7 +18680,6 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag,
idx += (word32)len;
}
}
#endif /* WOLFSSL_RID_ALT_NAME */
#endif /* IGNORE_NAME_CONSTRAINTS */
#ifndef IGNORE_NAME_CONSTRAINTS
/* GeneralName choice: otherName.
@@ -19898,24 +19966,29 @@ static int DecodeSubtree(const byte* input, word32 sz, Base_entry** head,
if (ret == 0) {
byte t = dataASN[SUBTREEASN_IDX_BASE].tag;
/* Check GeneralName tag is one of the types we can handle. */
/* Check GeneralName tag is one of the types we can handle.
* registeredID is included so that ConfirmNameConstraints can
* enforce permitted/excluded subtrees of OIDs (RFC 5280
* Sec. 4.2.1.10). */
if (t == (ASN_CONTEXT_SPECIFIC | ASN_DNS_TYPE) ||
t == (ASN_CONTEXT_SPECIFIC | ASN_RFC822_TYPE) ||
t == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | ASN_DIR_TYPE) ||
t == (ASN_CONTEXT_SPECIFIC | ASN_IP_TYPE) ||
t == (ASN_CONTEXT_SPECIFIC | ASN_URI_TYPE) ||
t == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED |
ASN_OTHER_TYPE)) {
ASN_OTHER_TYPE) ||
t == (ASN_CONTEXT_SPECIFIC | ASN_RID_TYPE)) {
/* Parse the general name and store a new entry. */
ret = DecodeSubtreeGeneralName(input +
GetASNItem_DataIdx(dataASN[SUBTREEASN_IDX_BASE], input),
dataASN[SUBTREEASN_IDX_BASE].length, t, head, heap);
}
else {
/* GeneralName form (e.g. registeredID, x400Address,
* ediPartyName) we do not enforce. Record so the caller can
* fail-closed when the nameConstraints extension is critical
* (RFC 5280 4.2.1.10). */
/* GeneralName form (e.g. x400Address, ediPartyName) we do
* not enforce. Record so the caller can fail-closed when the
* nameConstraints extension is critical (RFC 5280 4.2.1.10).
* registeredID is handled above; this branch covers truly
* unrecognised forms. */
*hasUnsupported = 1;
}
}
@@ -37582,7 +37655,15 @@ static int DecodeAcertGeneralName(const byte* input, word32* inOutIdx,
}
#if defined(WOLFSSL_QT) || defined(OPENSSL_ALL) || \
defined(WOLFSSL_IP_ALT_NAME)
/* GeneralName choice: iPAddress */
/* GeneralName choice: iPAddress
*
* Asymmetric with the X.509 DecodeGeneralName path on purpose:
* attribute-certificate names (RFC 5755) are not consumed by
* ConfirmNameConstraints, which only walks DecodedCert lists. These
* entries flow into AC holder/issuer name fields where the iPAddress
* is only consumed by callers that opt in (Qt, OpenSSL_ALL, or the
* IP-SAN compat layer). If iPAddress name-constraint enforcement is
* ever extended to attribute certificates, this gate must drop. */
else if (tag == (ASN_CONTEXT_SPECIFIC | ASN_IP_TYPE)) {
ret = SetDNSEntry(acert->heap, (const char*)(input + idx), len,
ASN_IP_TYPE, entries);
@@ -37590,7 +37671,7 @@ static int DecodeAcertGeneralName(const byte* input, word32* inOutIdx,
idx += (word32)len;
}
}
#endif /* WOLFSSL_QT || OPENSSL_ALL */
#endif /* WOLFSSL_QT || OPENSSL_ALL || WOLFSSL_IP_ALT_NAME */
#ifdef OPENSSL_ALL
/* GeneralName choice: registeredID */
+15 -5
View File
@@ -3486,7 +3486,10 @@ static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert)
length -= strLen;
idx += (word32)strLen;
}
#ifdef WOLFSSL_IP_ALT_NAME
/* iPAddress is parsed unconditionally so ConfirmNameConstraints
* can enforce permitted/excluded iPAddress subtrees
* (RFC 5280 Sec. 4.2.1.10). WOLFSSL_IP_ALT_NAME only gates the
* human-readable ipString form. */
else if (current_byte == (ASN_CONTEXT_SPECIFIC | ASN_IP_TYPE)) {
DNS_entry* ipAddr;
int strLen;
@@ -3521,6 +3524,7 @@ static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert)
XMEMCPY((void *)(wc_ptr_t)ipAddr->name, &input[idx], strLen);
((char *)(wc_ptr_t)ipAddr->name)[strLen] = '\0';
#ifdef WOLFSSL_IP_ALT_NAME
if (GenerateDNSEntryIPString(ipAddr, cert->heap) != 0) {
WOLFSSL_MSG("\tOut of Memory for IP string");
XFREE((void *)(wc_ptr_t)ipAddr->name, cert->heap,
@@ -3528,6 +3532,7 @@ static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert)
XFREE(ipAddr, cert->heap, DYNAMIC_TYPE_ALTNAME);
return MEMORY_E;
}
#endif /* WOLFSSL_IP_ALT_NAME */
AddAltName(cert, ipAddr);
if (strLen > length) {
@@ -3536,8 +3541,9 @@ static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert)
length -= strLen;
idx += (word32)strLen;
}
#endif /* WOLFSSL_IP_ALT_NAME */
#ifdef WOLFSSL_RID_ALT_NAME
/* registeredID is parsed unconditionally so ConfirmNameConstraints
* can enforce permitted/excluded subtrees (RFC 5280 Sec. 4.2.1.10).
* WOLFSSL_RID_ALT_NAME only gates the human-readable ridString. */
else if (current_byte == (ASN_CONTEXT_SPECIFIC | ASN_RID_TYPE)) {
DNS_entry* rid;
int strLen;
@@ -3573,6 +3579,7 @@ static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert)
XMEMCPY((void *)(wc_ptr_t)rid->name, &input[idx], strLen);
((char *)(wc_ptr_t)rid->name)[strLen] = '\0';
#ifdef WOLFSSL_RID_ALT_NAME
if (GenerateDNSEntryRIDString(rid, cert->heap) != 0) {
WOLFSSL_MSG("\tOut of Memory for registered Id string");
XFREE((void *)(wc_ptr_t)rid->name, cert->heap,
@@ -3580,6 +3587,7 @@ static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert)
XFREE(rid, cert->heap, DYNAMIC_TYPE_ALTNAME);
return MEMORY_E;
}
#endif /* WOLFSSL_RID_ALT_NAME */
AddAltName(cert, rid);
@@ -3589,7 +3597,6 @@ static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert)
length -= strLen;
idx += (word32)strLen;
}
#endif /* WOLFSSL_RID_ALT_NAME */
#endif /* IGNORE_NAME_CONSTRAINTS */
else if (current_byte ==
(ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | ASN_OTHER_TYPE)) {
@@ -4117,9 +4124,12 @@ static int DecodeSubtree(const byte* input, word32 sz, Base_entry** head,
/* Get type, LSB 4-bits */
bType = (byte)(b & ASN_TYPE_MASK);
/* registeredID is included so ConfirmNameConstraints can enforce
* permitted/excluded subtrees of OIDs (RFC 5280 Sec. 4.2.1.10). */
if (bType == ASN_DNS_TYPE || bType == ASN_RFC822_TYPE ||
bType == ASN_DIR_TYPE || bType == ASN_IP_TYPE ||
bType == ASN_URI_TYPE || bType == ASN_OTHER_TYPE) {
bType == ASN_URI_TYPE || bType == ASN_OTHER_TYPE ||
bType == ASN_RID_TYPE) {
Base_entry* entry;
/* directoryName is encoded as [4] CONSTRUCTED { Name } where