diff --git a/src/x509.c b/src/x509.c index fd87b1da4a..c3c66826b9 100644 --- a/src/x509.c +++ b/src/x509.c @@ -5670,95 +5670,12 @@ static int MatchIpName(const char* name, int nameSz, WOLFSSL_GENERAL_NAME* gn) constraintData, constraintLen); } -/* Extract host from URI for name constraint matching. - * URI format: scheme://[userinfo@]host[:port][/path][?query][#fragment] - * IPv6 literals are enclosed in brackets: scheme://[ipv6addr]:port/path - * Returns pointer to host start and sets hostLen, or NULL on failure. */ -static const char* ExtractHostFromUri(const char* uri, int uriLen, int* hostLen) -{ - const char* hostStart; - const char* hostEnd; - const char* p; - const char* uriEnd; - - if (uri == NULL || uriLen <= 0 || hostLen == NULL) { - return NULL; - } - - uriEnd = uri + uriLen; - - /* Find "://" to skip scheme */ - hostStart = NULL; - for (p = uri; p < uriEnd - 2; p++) { - if (p[0] == ':' && p[1] == '/' && p[2] == '/') { - hostStart = p + 3; - break; - } - } - if (hostStart == NULL || hostStart >= uriEnd) { - return NULL; - } - - /* Skip userinfo if present (look for @ before any /, ?, #) - * userinfo can contain ':' (ex: user:pass@host), don't stop at ':' - * For IPv6, also don't stop at '[' in userinfo */ - for (p = hostStart; p < uriEnd; p++) { - if (*p == '@') { - hostStart = p + 1; - break; - } - if (*p == '/' || *p == '?' || *p == '#') { - /* No userinfo found */ - break; - } - /* If '[' before '@', found IPv6 literal, not userinfo */ - if (*p == '[') { - break; - } - } - if (hostStart >= uriEnd) { - return NULL; - } - - /* Check for IPv6 literal */ - if (*hostStart == '[') { - /* Find closing bracket, skip opening one */ - hostStart++; - hostEnd = hostStart; - while (hostEnd < uriEnd && *hostEnd != ']') { - hostEnd++; - } - if (hostEnd >= uriEnd) { - /* No closing bracket found, malformed */ - return NULL; - } - /* hostEnd points to closing bracket, extract content between */ - *hostLen = (int)(hostEnd - hostStart); - if (*hostLen <= 0) { - return NULL; - } - return hostStart; - } - - /* Regular hostname, find end */ - hostEnd = hostStart; - while (hostEnd < uriEnd && *hostEnd != ':' && *hostEnd != '/' && - *hostEnd != '?' && *hostEnd != '#') { - hostEnd++; - } - - *hostLen = (int)(hostEnd - hostStart); - if (*hostLen <= 0) { - return NULL; - } - - return hostStart; -} - /* Helper to check if name string matches a single GENERAL_NAME constraint. + * permitted selects subtree semantics for wildcard DNS names: containment + * for permitted subtrees, intersection for excluded subtrees. * Returns 1 if matches, 0 if not. */ static int MatchNameConstraint(int type, const char* name, int nameSz, - WOLFSSL_GENERAL_NAME* gn) + WOLFSSL_GENERAL_NAME* gn, int permitted) { const char* baseStr; int baseLen; @@ -5788,21 +5705,13 @@ static int MatchNameConstraint(int type, const char* name, int nameSz, nameSz, baseStr, baseLen); } else if (type == WOLFSSL_GEN_URI) { - const char* host; - int hostLen; - - /* For URI, extract host and match against DNS-style */ - host = ExtractHostFromUri(name, nameSz, &hostLen); - if (host == NULL) { - return 0; - } - return wolfssl_local_MatchBaseName(ASN_DNS_TYPE, host, hostLen, + return wolfssl_local_MatchUriNameConstraint(name, nameSz, baseStr, baseLen); } else { /* WOLFSSL_GEN_DNS uses DNS-style matching */ - return wolfssl_local_MatchBaseName(ASN_DNS_TYPE, name, nameSz, - baseStr, baseLen); + return wolfssl_local_MatchDnsNameConstraint(name, nameSz, + baseStr, baseLen, permitted); } default: @@ -5811,6 +5720,29 @@ static int MatchNameConstraint(int type, const char* name, int nameSz, } } +static int NameConstraintsHasType(const WOLFSSL_STACK* sk, int type) +{ + int i; + int num; + + if (sk == NULL) { + return 0; + } + + num = wolfSSL_sk_GENERAL_SUBTREE_num(sk); + for (i = 0; i < num; i++) { + WOLFSSL_GENERAL_SUBTREE* subtree; + + subtree = wolfSSL_sk_GENERAL_SUBTREE_value(sk, i); + if (subtree != NULL && subtree->base != NULL && + subtree->base->type == type) { + return 1; + } + } + + return 0; +} + /* * Check if a name string satisfies given name constraints. * @@ -5841,6 +5773,14 @@ int wolfSSL_NAME_CONSTRAINTS_check_name(WOLFSSL_NAME_CONSTRAINTS* nc, return 0; } + if (type == WOLFSSL_GEN_URI && + (NameConstraintsHasType(nc->permittedSubtrees, type) || + NameConstraintsHasType(nc->excludedSubtrees, type)) && + !wolfssl_local_UriNameHasDnsHost(name, nameSz)) { + WOLFSSL_MSG("URI name constraint applied to URI without DNS host"); + return 0; + } + /* Check permitted subtrees */ if (nc->permittedSubtrees != NULL) { num = wolfSSL_sk_GENERAL_SUBTREE_num(nc->permittedSubtrees); @@ -5857,7 +5797,7 @@ int wolfSSL_NAME_CONSTRAINTS_check_name(WOLFSSL_NAME_CONSTRAINTS* nc, } hasPermittedType = 1; - if (MatchNameConstraint(type, name, nameSz, gn)) { + if (MatchNameConstraint(type, name, nameSz, gn, 1)) { matchedPermitted = 1; break; } @@ -5884,7 +5824,7 @@ int wolfSSL_NAME_CONSTRAINTS_check_name(WOLFSSL_NAME_CONSTRAINTS* nc, continue; } - if (MatchNameConstraint(type, name, nameSz, gn)) { + if (MatchNameConstraint(type, name, nameSz, gn, 0)) { WOLFSSL_MSG("Name in excluded subtrees"); return 0; } diff --git a/tests/api.c b/tests/api.c index dda533a3a7..47c8ac5304 100644 --- a/tests/api.c +++ b/tests/api.c @@ -23304,6 +23304,46 @@ static int test_NameConstraints_DnsUriWildcard(void) sanSz = build_simple_san(san, sizeof(san), URI, "https://www.host.com/"); ExpectIntGT((int)sanSz, 0); ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz), 0); + + /* (11) RFC 5280 requires a DNS host when URI constraints are applied. + * Fail closed even for excluded-only constraints where a boolean + * non-match would otherwise pass. */ + ncSz = build_simple_nameConstraints(nc, sizeof(nc), 1, URI, + "blocked.com"); + sanSz = build_simple_san(san, sizeof(san), URI, "https://12.31.2.3/"); + ExpectIntGT((int)ncSz, 0); + ExpectIntGT((int)sanSz, 0); + ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz), + WC_NO_ERR_TRACE(ASN_NAME_INVALID_E)); + + sanSz = build_simple_san(san, sizeof(san), URI, "https://[v1.addr.]/"); + ExpectIntGT((int)sanSz, 0); + ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz), + WC_NO_ERR_TRACE(ASN_NAME_INVALID_E)); + + /* An IPv4address host with the absolute-FQDN trailing dot is still not + * a DNS host. */ + sanSz = build_simple_san(san, sizeof(san), URI, "https://12.31.2.3./"); + ExpectIntGT((int)sanSz, 0); + ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz), + WC_NO_ERR_TRACE(ASN_NAME_INVALID_E)); + + /* (12) One trailing dot on the constraint base is the absolute-FQDN + * marker: DNS:example.com. denotes the same subtree as + * DNS:example.com, so the permitted form accepts the bare SAN and + * the excluded form rejects it. */ + ncSz = build_simple_nameConstraints(nc, sizeof(nc), 0, DNS, + "example.com."); + sanSz = build_simple_san(san, sizeof(san), DNS, "example.com"); + ExpectIntGT((int)ncSz, 0); + ExpectIntGT((int)sanSz, 0); + ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz), 0); + + ncSz = build_simple_nameConstraints(nc, sizeof(nc), 1, DNS, + "example.com."); + ExpectIntGT((int)ncSz, 0); + ExpectIntEQ(verify_with_otherName_chain(nc, ncSz, 1, san, sanSz), + WC_NO_ERR_TRACE(ASN_NAME_INVALID_E)); #endif return EXPECT_RESULT(); } diff --git a/tests/api/test_asn.c b/tests/api/test_asn.c index bfa9562636..7abcaa5d72 100644 --- a/tests/api/test_asn.c +++ b/tests/api/test_asn.c @@ -1153,6 +1153,21 @@ int test_wolfssl_local_MatchUriNameConstraint(void) ExpectIntEQ(uriNC("https://host.com.evil.com", "host.com"), 0); ExpectIntEQ(uriNC("https://other.com", "host.com"), 0); + /* A single trailing dot is the absolute-FQDN marker: "host.com." and + * "host.com" denote the same host and must compare equal, matching the + * DNS name-constraint path. */ + ExpectIntEQ(uriNC("https://host.com./", "host.com"), 1); + ExpectIntEQ(uriNC("https://host.com.:8443/x", "host.com"), 1); + ExpectIntEQ(uriNC("https://host.com", "host.com."), 1); + ExpectIntEQ(uriNC("https://host.com./", "host.com."), 1); + ExpectIntEQ(uriNC("https://v1.addr./", "v1.addr"), 1); + ExpectIntEQ(uriNC("https://v1.addr/", "v1.addr."), 1); + /* Only ONE trailing dot is the marker; an empty last label is not. */ + ExpectIntEQ(uriNC("https://host.com../", "host.com"), 0); + ExpectIntEQ(uriNC("https://a.host.com../", ".host.com"), 0); + /* Empty interior labels are not valid DNS host labels. */ + ExpectIntEQ(uriNC("https://a..host.com/", ".host.com"), 0); + /* * Leading-dot constraint: proper subtree of hosts (apex excluded). */ @@ -1164,10 +1179,18 @@ int test_wolfssl_local_MatchUriNameConstraint(void) ExpectIntEQ(uriNC("https://evilhost.com", ".host.com"), 0); /* - * IPv6 literal host extraction ([..]) then exact match. + * RFC 5280 URI constraints require a DNS host. IP-literals / IPvFuture + * hosts in brackets and IPv4address hosts are not DNS reg-names. */ - ExpectIntEQ(uriNC("https://[2001:db8::1]:443/x", "2001:db8::1"), 1); + ExpectIntEQ(uriNC("https://[2001:db8::1]:443/x", "2001:db8::1"), 0); ExpectIntEQ(uriNC("https://[2001:db8::1]", "2001:db8::2"), 0); + ExpectIntEQ(uriNC("https://[v1.addr.]/", "v1.addr"), 0); + ExpectIntEQ(uriNC("https://[v1.addr.]/", "v1.addr."), 0); + ExpectIntEQ(uriNC("https://12.31.2.3/", "12.31.2.3"), 0); + /* An IPv4address host is still not a DNS reg-name when written with the + * absolute-FQDN trailing dot. */ + ExpectIntEQ(uriNC("https://12.31.2.3./", "12.31.2.3"), 0); + ExpectIntEQ(uriNC("https://12.31.2.3./", "12.31.2.3."), 0); /* * Malformed / degenerate URIs and inputs (reject). diff --git a/tests/api/test_ossl_x509_ext.c b/tests/api/test_ossl_x509_ext.c index 05d2370a87..c1293899d5 100644 --- a/tests/api/test_ossl_x509_ext.c +++ b/tests/api/test_ossl_x509_ext.c @@ -2039,8 +2039,8 @@ int test_wolfSSL_NAME_CONSTRAINTS_uri(void) ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, "https://user:pass@www.wolfssl.com/path", 38), 1); - /* IPv6 literal URIs, host extracted without brackets. - * These don't match .wolfssl.com constraint (different host type) */ + /* URI constraints require a DNS reg-name host, so IP-literals do not + * match the .wolfssl.com constraint. */ ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, "https://[::1]:8080/path", 23), 0); ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI, @@ -2243,6 +2243,71 @@ int test_wolfSSL_NAME_CONSTRAINTS_check_name(void) x509 = NULL; nc = NULL; + /* Wildcard names against an excluded DNS subtree. Build the constraints + * programmatically: excluded;DNS:foo.example.com */ + ExpectNotNull(nc = wolfSSL_NAME_CONSTRAINTS_new()); + if (EXPECT_SUCCESS()) { + GENERAL_SUBTREE* subtree = NULL; + + ExpectNotNull(nc->excludedSubtrees = wolfSSL_sk_new_null()); + if (EXPECT_SUCCESS()) { + nc->excludedSubtrees->type = STACK_TYPE_GENERAL_SUBTREE; + } + ExpectNotNull(subtree = wolfSSL_GENERAL_SUBTREE_new()); + if (EXPECT_SUCCESS()) { + ExpectNotNull(subtree->base = wolfSSL_GENERAL_NAME_new()); + } + if (EXPECT_SUCCESS()) { + subtree->base->type = GEN_DNS; + ExpectIntEQ(wolfSSL_ASN1_STRING_set(subtree->base->d.ia5, + "foo.example.com", 15), WOLFSSL_SUCCESS); + } + if (EXPECT_SUCCESS()) { + ExpectIntGT(wolfSSL_sk_push(nc->excludedSubtrees, subtree), 0); + } + if (EXPECT_FAIL()) { + wolfSSL_GENERAL_SUBTREE_free(subtree); + } + } + + if (EXPECT_SUCCESS()) { + /* Literal names inside the excluded subtree are rejected. */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "foo.example.com", 15), 0); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "a.foo.example.com", 17), 0); + /* Names outside the subtree pass; there are no permitted subtrees. */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "bar.example.com", 15), 1); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "*.other.com", 11), 1); + /* A wildcard's '*' can expand to "foo", so "*.example.com" covers + * the excluded "foo.example.com" and must be rejected. */ + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "*.example.com", 13), 0); + } + + if (EXPECT_SUCCESS()) { + /* One trailing dot on the base is the absolute-FQDN marker: + * excluded;DNS:foo.example.com. covers the same subtree, so the + * bare name is still rejected. */ + GENERAL_SUBTREE* subtree = NULL; + + ExpectNotNull(subtree = + wolfSSL_sk_GENERAL_SUBTREE_value(nc->excludedSubtrees, 0)); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(wolfSSL_ASN1_STRING_set(subtree->base->d.ia5, + "foo.example.com.", 16), WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "foo.example.com", 15), 0); + ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_DNS, + "bar.example.com", 15), 1); + } + + NAME_CONSTRAINTS_free(nc); + nc = NULL; + /* Test IP address constraint checking with cert-ext-ncip.pem * This cert has permitted IP 192.168.1.0/255.255.255.0 */ if ((f = XFOPEN("./certs/test/cert-ext-ncip.pem", "rb")) == XBADFILE) { diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 25548bdbc4..b503d1699b 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -18121,19 +18121,92 @@ int wolfssl_local_MatchBaseName(int type, const char* name, int nameSz, return 1; } -int wolfssl_local_MatchUriNameConstraint(const char* uri, int uriSz, - const char* base, int baseSz) +/* RFC 3986 host classification for URI name-constraint checks. */ +typedef enum UriHostType { + URI_HOST_REG_NAME = 0, + URI_HOST_IP_LITERAL, + URI_HOST_IPV4 +} UriHostType; + +static int UriHostIsDecOctet(const char* s, int sSz) +{ + int i; + int val = 0; + + if (s == NULL || sSz <= 0 || sSz > 3) { + return 0; + } + if (sSz > 1 && s[0] == '0') { + return 0; + } + + for (i = 0; i < sSz; i++) { + if (s[i] < '0' || s[i] > '9') { + return 0; + } + val = (val * 10) + (s[i] - '0'); + } + + return val <= 255; +} + +static int UriHostIsIpv4Address(const char* host, int hostSz) +{ + int i; + int partStart = 0; + int partCount = 0; + + if (host == NULL || hostSz <= 0) { + return 0; + } + + for (i = 0; i <= hostSz; i++) { + if (i == hostSz || host[i] == '.') { + if (!UriHostIsDecOctet(host + partStart, i - partStart)) { + return 0; + } + partCount++; + partStart = i + 1; + } + else if (host[i] < '0' || host[i] > '9') { + return 0; + } + } + + return partCount == 4; +} + +static int UriRegNameHasNonEmptyLabels(const char* host, int hostSz) +{ + int i; + + if (host == NULL || hostSz <= 0 || host[0] == '.' || + host[hostSz - 1] == '.') { + return 0; + } + + for (i = 1; i < hostSz; i++) { + if (host[i] == '.' && host[i - 1] == '.') { + return 0; + } + } + + return 1; +} + +static int GetUriHost(const char* uri, int uriSz, const char** host, + int* hostSz, UriHostType* hostType) { const char* hostStart; const char* hostEnd; const char* p; const char* uriEnd; - int hostSz; /* Need at least 3 bytes for the "://" scheme separator; rejecting short * inputs early also keeps the loop bound (uriEnd - 2) from forming a * pointer before `uri`. */ - if (uri == NULL || uriSz < 3 || base == NULL || baseSz <= 0) { + if (uri == NULL || uriSz < 3 || host == NULL || hostSz == NULL || + hostType == NULL) { return 0; } @@ -18174,7 +18247,8 @@ int wolfssl_local_MatchUriNameConstraint(const char* uri, int uriSz, if (hostEnd >= uriEnd) { return 0; } - hostSz = (int)(hostEnd - hostStart); + *hostSz = (int)(hostEnd - hostStart); + *hostType = URI_HOST_IP_LITERAL; } else { hostEnd = hostStart; @@ -18182,10 +18256,63 @@ int wolfssl_local_MatchUriNameConstraint(const char* uri, int uriSz, *hostEnd != '?' && *hostEnd != '#') { hostEnd++; } - hostSz = (int)(hostEnd - hostStart); + *hostSz = (int)(hostEnd - hostStart); + /* One trailing dot is the absolute-FQDN marker and not part of the + * host: strip it before classifying so that "12.31.2.3." is + * recognized as an IPv4 address and "host.com." denotes the same + * reg-name as "host.com". */ + if (*hostSz > 0 && hostStart[*hostSz - 1] == '.') { + (*hostSz)--; + if (*hostSz <= 0 || hostStart[*hostSz - 1] == '.') { + return 0; + } + } + *hostType = UriHostIsIpv4Address(hostStart, *hostSz) ? + URI_HOST_IPV4 : URI_HOST_REG_NAME; + if (*hostType == URI_HOST_REG_NAME && + !UriRegNameHasNonEmptyLabels(hostStart, *hostSz)) { + return 0; + } } - if (hostSz <= 0) { + if (*hostSz <= 0) { + return 0; + } + *host = hostStart; + + return 1; +} + +int wolfssl_local_UriNameHasDnsHost(const char* uri, int uriSz) +{ + const char* host = NULL; + int hostSz = 0; + UriHostType hostType = URI_HOST_REG_NAME; + + if (!GetUriHost(uri, uriSz, &host, &hostSz, &hostType)) { + return 0; + } + + (void)host; + (void)hostSz; + return hostType == URI_HOST_REG_NAME; +} + +int wolfssl_local_MatchUriNameConstraint(const char* uri, int uriSz, + const char* base, int baseSz) +{ + const char* hostStart = NULL; + int hostSz = 0; + UriHostType hostType = URI_HOST_REG_NAME; + + if (base == NULL || baseSz <= 0 || + !GetUriHost(uri, uriSz, &hostStart, &hostSz, &hostType)) { + return 0; + } + /* RFC 5280 URI constraints apply only to host names specified as fully + * qualified domain names. RFC 3986 IP-literals and IPv4address hosts are + * not DNS reg-names. */ + if (hostType != URI_HOST_REG_NAME) { return 0; } @@ -18200,6 +18327,15 @@ int wolfssl_local_MatchUriNameConstraint(const char* uri, int uriSz, } else { int i; + /* GetUriHost already stripped the host's absolute-FQDN trailing dot; + * treat one trailing dot on the base the same way so that "host.com." + * and "host.com" compare equal. */ + if (base[baseSz - 1] == '.') { + baseSz--; + } + if (baseSz <= 0) { + return 0; + } if (hostSz != baseSz) { return 0; } @@ -18415,6 +18551,23 @@ static int DnsNameHasWildcard(const char* name, int nameSz) return 0; } +/* Match a DNS name against a DNS name-constraint base. A wildcard name + * denotes a set of names: permitted subtrees require containment, excluded + * subtrees intersection (selected by `permitted`). Literal names use plain + * base-name matching, which normalizes the absolute-FQDN trailing dot + * before its own length check. + * Returns 1 on match, 0 otherwise. */ +int wolfssl_local_MatchDnsNameConstraint(const char* name, int nameSz, + const char* base, int baseSz, int permitted) +{ + if (DnsNameHasWildcard(name, nameSz)) { + return wolfssl_local_MatchDnsConstraintWildcard(name, nameSz, + base, baseSz, permitted); + } + return wolfssl_local_MatchBaseName(ASN_DNS_TYPE, name, nameSz, base, + baseSz); +} + /* Search through the list to find if the name is permitted. * name The DNS name to search for * dnsList The list to search through @@ -18465,12 +18618,10 @@ static int PermittedListOk(DNS_entry* name, Base_entry* dnsList, byte nameType) break; } } - else if (nameType == ASN_DNS_TYPE && - DnsNameHasWildcard(name->name, name->len)) { - /* Wildcard DNS SAN: a '*' can expand to a longer label, so the - * byte-length guard used for literal names below is invalid. - * Permit only if every expansion stays inside the subtree. */ - if (wolfssl_local_MatchDnsConstraintWildcard(name->name, + else if (nameType == ASN_DNS_TYPE) { + /* Permit only if every expansion of a wildcard stays inside + * the subtree. */ + if (wolfssl_local_MatchDnsNameConstraint(name->name, name->len, current->name, current->nameSz, 1)) { match = 1; break; @@ -18539,12 +18690,10 @@ static int IsInExcludedList(DNS_entry* name, Base_entry* dnsList, byte nameType) break; } } - else if (nameType == ASN_DNS_TYPE && - DnsNameHasWildcard(name->name, name->len)) { - /* Wildcard DNS SAN: a '*' can expand to a longer label, so the - * byte-length guard used for literal names below is invalid. - * Exclude if any expansion can fall inside the subtree. */ - if (wolfssl_local_MatchDnsConstraintWildcard(name->name, + else if (nameType == ASN_DNS_TYPE) { + /* Exclude if any expansion of a wildcard can fall inside the + * subtree. */ + if (wolfssl_local_MatchDnsNameConstraint(name->name, name->len, current->name, current->nameSz, 0)) { ret = 1; break; @@ -18564,12 +18713,25 @@ static int IsInExcludedList(DNS_entry* name, Base_entry* dnsList, byte nameType) } +static int NameConstraintListHasType(Base_entry* list, byte nameType) +{ + while (list != NULL) { + if (list->type == nameType) { + return 1; + } + list = list->next; + } + + return 0; +} + 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_RID_TYPE}; int i; + int uriConstraintsApply; if (signer == NULL || cert == NULL) return 0; @@ -18578,6 +18740,10 @@ static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert) !signer->extNameConstraintHasUnsupported) return 1; + uriConstraintsApply = + NameConstraintListHasType(signer->excludedNames, ASN_URI_TYPE) || + NameConstraintListHasType(signer->permittedNames, ASN_URI_TYPE); + for (i=0; i < (int)sizeof(nameTypes); i++) { byte nameType = nameTypes[i]; DNS_entry* name = NULL; @@ -18660,6 +18826,14 @@ static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert) while (name != NULL) { /* Only check entries that match the current nameType. */ if (name->type == nameType) { + if (nameType == ASN_URI_TYPE && uriConstraintsApply && + !wolfssl_local_UriNameHasDnsHost(name->name, + name->len)) { + WOLFSSL_MSG("URI name constraint applied to URI without " + "DNS host"); + return 0; + } + if (IsInExcludedList(name, signer->excludedNames, nameType) == 1) { WOLFSSL_MSG("Excluded name was found!"); diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 45993f0638..8871aea8d4 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -3222,10 +3222,15 @@ WOLFSSL_TEST_VIS int wolfssl_local_MatchIpSubnet(const byte* ip, int ipSz, WOLFSSL_TEST_VIS int wolfssl_local_MatchUriNameConstraint(const char* uri, int uriSz, const char* base, int baseSz); +WOLFSSL_LOCAL int wolfssl_local_UriNameHasDnsHost(const char* uri, + int uriSz); WOLFSSL_TEST_VIS int wolfssl_local_MatchDnsConstraintWildcard( const char* name, int nameSz, const char* base, int baseSz, int permitted); +WOLFSSL_LOCAL int wolfssl_local_MatchDnsNameConstraint(const char* name, + int nameSz, const char* base, + int baseSz, int permitted); #endif #if ((defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_IMPORT)) \