Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions tests/api/test_asn.c
Original file line number Diff line number Diff line change
Expand Up @@ -1888,6 +1888,9 @@ int test_wolfssl_local_MatchUriNameConstraint(void)
EXPECT_DECLS;

#if !defined(NO_CERTS) && !defined(NO_ASN) && !defined(IGNORE_NAME_CONSTRAINTS)
/* "https://%61" + up to 249 'a' + ".com./" */
char longUri[11 + 249 + 7];

/*
* Exact host match (no leading dot in the constraint).
*/
Expand Down Expand Up @@ -1970,6 +1973,61 @@ int test_wolfssl_local_MatchUriNameConstraint(void)
/* Empty interior labels are not valid DNS host labels. */
ExpectIntEQ(uriNC("https://a..host.com/", ".host.com"), 0);

/* RFC 5280 7.4: escaped unreserved characters are decoded before the
* host is compared. */
ExpectIntEQ(uriNC("https://h%6Fst.example.com/", "host.example.com"), 1);
ExpectIntEQ(uriNC("https://h%6fst.example.com/", "host.example.com"), 1);
ExpectIntEQ(uriNC("https://h%6Fst.example.com/", ".example.com"), 1);
ExpectIntEQ(uriNC("https://h%6Fst.example.com/", "hest.example.com"), 0);
ExpectIntEQ(uriNC("https://host.example.com%2E/", "host.example.com"), 1);
/* A host that decodes to an IPv4address is not a reg-name. */
ExpectIntEQ(uriNC("https://%31%32.31.2.3/", ".31.2.3"), 0);
/* Malformed escapes. */
ExpectIntEQ(uriNC("https://h%6/", "h"), 0);
ExpectIntEQ(uriNC("https://h%/", "h"), 0);
ExpectIntEQ(uriNC("https://h%ZZst.example.com/", ".example.com"), 0);
ExpectIntEQ(uriNC("https://h%6Zst.example.com/", ".example.com"), 0);
/* Escapes of reserved and non-ASCII characters are not decoded, so the
* host is not a valid reg-name host. */
ExpectIntEQ(uriNC("https://evil.com%2F.example.com/", ".example.com"), 0);
ExpectIntEQ(uriNC("https://evil.com%2F.example.com/", "evil.com"), 0);
ExpectIntEQ(uriNC("https://evil.com%40www.example.com/",
".example.com"), 0);
/* This string is split to prevent a codespell error */
ExpectIntEQ(uriNC("https://c""af%C3%A9.example.com/", ".example.com"), 0);
ExpectIntEQ(uriNC("https://h%00st.example.com/", ".example.com"), 0);
/* Decoding is a single pass: "%25" yields '%', not a second escape. */
ExpectIntEQ(uriNC("https://%2577ww.example.com/", ".example.com"), 0);
/* The host may hold only letters, digits, '-', '.', '_' and '~' (RFC 3986
* unreserved), raw or decoded. */
ExpectIntEQ(uriNC("https://h!st.example.com/", ".example.com"), 0);
ExpectIntEQ(uriNC("https://h_st.example.com/", ".example.com"), 1);
ExpectIntEQ(uriNC("https://h%5Fst.example.com/", ".example.com"), 1);
ExpectIntEQ(uriNC("https://www.ex_ample.com/", ".ex_ample.com"), 1);
ExpectIntEQ(uriNC("https://h~st.example.com/", ".example.com"), 1);
ExpectIntEQ(uriNC("https://h%7Est.example.com/", ".example.com"), 1);
/* A decoded host is limited to URI_HOST_MAX_SZ. */
XMEMCPY(longUri, "https://%61", 11);
XMEMSET(longUri + 11, 'a', 248);
XMEMCPY(longUri + 11 + 248, ".com/", 6);
ExpectIntEQ(uriNC(longUri, ".com"), 1);
XMEMCPY(longUri + 11 + 248, ".com./", 7);
ExpectIntEQ(uriNC(longUri, ".com"), 1);
XMEMSET(longUri + 11, 'a', 249);
XMEMCPY(longUri + 11 + 249, ".com./", 7);
ExpectIntEQ(uriNC(longUri, ".com"), 0);
/* An IDN host in ACE form is LDH. */
ExpectIntEQ(uriNC("https://xn--caf-dma.example.com/", ".example.com"), 1);
/* A base that is not a valid reg-name host is not decoded and cannot
* match the host. */
ExpectIntEQ(uriNC("https://host.example.com/", "h%6Fst.example.com"), 0);
ExpectIntEQ(uriNC("https://h%6Fst.example.com/", "h%6Fst.example.com"), 0);
ExpectIntEQ(uriNC("https://example..com/", "example..com"), 0);
ExpectIntEQ(uriNC("https://a..example.com/", "..example.com"), 0);
ExpectIntEQ(uriNC("https://example.com../", "example.com.."), 0);
ExpectIntEQ(uriNC("https://www.example.com/", "."), 0);
ExpectIntEQ(uriNC("https://www.example.com/", ".."), 0);

/*
* Leading-dot constraint: proper subtree of hosts (apex excluded).
*/
Expand Down
78 changes: 78 additions & 0 deletions tests/api/test_ossl_x509_ext.c
Original file line number Diff line number Diff line change
Expand Up @@ -2179,6 +2179,19 @@ int test_wolfSSL_NAME_CONSTRAINTS_uri(void)
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://[fe80::1%25eth0]:443/", 29), 0);

/* Escaped unreserved characters are decoded before comparison
* (RFC 5280 7.4). URI constraints require a valid reg-name host, so
* malformed escapes and other invalid hosts are rejected, even though
* the raw suffix matches. */
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://w%77w.wolfssl.com/", 26), 1);
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://w%ZZw.wolfssl.com/", 26), 0);
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://w%2Fw.wolfssl.com/", 26), 0);
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://w!w.wolfssl.com/", 24), 0);

/* IPv6 with userinfo */
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://user@[::1]:8080/", 24), 0);
Expand Down Expand Up @@ -2436,6 +2449,71 @@ int test_wolfSSL_NAME_CONSTRAINTS_check_name(void)
"bar.example.com", 15), 1);
}

if (EXPECT_SUCCESS()) {
/* A percent-encoded host decodes to the permitted host:
* permitted;URI:host.example.com */
GENERAL_SUBTREE* subtree = NULL;

ExpectNotNull(subtree =
wolfSSL_sk_GENERAL_SUBTREE_value(nc->excludedSubtrees, 0));
if (EXPECT_SUCCESS()) {
subtree->base->type = GEN_URI;
ExpectIntEQ(wolfSSL_ASN1_STRING_set(subtree->base->d.ia5,
"host.example.com", 16), WOLFSSL_SUCCESS);
nc->permittedSubtrees = nc->excludedSubtrees;
nc->excludedSubtrees = NULL;
}
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://h%6Fst.example.com/", 27), 1);

/* A URI base that is not a valid reg-name host matches no valid host,
* so permitted;URI:h%6Fst.example.com satisfies nothing. */
if (EXPECT_SUCCESS()) {
ExpectIntEQ(wolfSSL_ASN1_STRING_set(subtree->base->d.ia5,
"h%6Fst.example.com", 18), WOLFSSL_SUCCESS);
}
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://host.example.com/", 25), 0);
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://h%6Fst.example.com/", 27), 0);

/* RFC 5280 4.2.1.10: a URI without a DNS host is rejected under
* any URI constraint, even one it is outside of:
* excluded;URI:host.example.com */
if (EXPECT_SUCCESS()) {
nc->excludedSubtrees = nc->permittedSubtrees;
nc->permittedSubtrees = NULL;
ExpectIntEQ(wolfSSL_ASN1_STRING_set(subtree->base->d.ia5,
"host.example.com", 16), WOLFSSL_SUCCESS);
}
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://other.example.com/", 26), 1);
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://12.31.2.3/", 18), 0);
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://[2001:db8::1]/", 22), 0);
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://h%5Fst.example.com/", 27), 1);
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"urn:example:host.example.com", 28), 0);
/* A percent-encoded host decodes to the excluded host. */
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://h%6Fst.example.com/", 27), 0);

/* The base is not decoded, so excluded;URI:h%6Fst.example.com excludes
* nothing. A URI without a DNS host is still rejected. */
if (EXPECT_SUCCESS()) {
ExpectIntEQ(wolfSSL_ASN1_STRING_set(subtree->base->d.ia5,
"h%6Fst.example.com", 18), WOLFSSL_SUCCESS);
}
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://host.example.com/", 25), 1);
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://h%6Fst.example.com/", 27), 1);
ExpectIntEQ(wolfSSL_NAME_CONSTRAINTS_check_name(nc, GEN_URI,
"https://12.31.2.3/", 18), 0);
}

NAME_CONSTRAINTS_free(nc);
nc = NULL;

Expand Down
13 changes: 7 additions & 6 deletions tests/unit-mcdc/test_asn_fault_whitebox.c
Original file line number Diff line number Diff line change
Expand Up @@ -563,25 +563,26 @@ static void wb_uri_host_helpers_null_args(void)
const char* host = NULL;
int hostSz = 0;
UriHostType hostType = URI_HOST_REG_NAME;
char hostBuf[URI_HOST_MAX_SZ];
static const char uri[] = "http://example.com/";
const int uriLen = (int)sizeof(uri) - 1;

ret = GetUriHost(uri, uriLen, &host, &hostSz, &hostType);
ret = GetUriHost(uri, uriLen, &host, &hostSz, &hostType, hostBuf);
WB_CHECK(ret == 1, "baseline (all false)");

ret = GetUriHost(NULL, uriLen, &host, &hostSz, &hostType);
ret = GetUriHost(NULL, uriLen, &host, &hostSz, &hostType, hostBuf);
WB_CHECK(ret == 0, "uri==NULL");

ret = GetUriHost(uri, 2, &host, &hostSz, &hostType);
ret = GetUriHost(uri, 2, &host, &hostSz, &hostType, hostBuf);
WB_CHECK(ret == 0, "uriSz<3");

ret = GetUriHost(uri, uriLen, NULL, &hostSz, &hostType);
ret = GetUriHost(uri, uriLen, NULL, &hostSz, &hostType, hostBuf);
WB_CHECK(ret == 0, "host==NULL");

ret = GetUriHost(uri, uriLen, &host, NULL, &hostType);
ret = GetUriHost(uri, uriLen, &host, NULL, &hostType, hostBuf);
WB_CHECK(ret == 0, "hostSz==NULL");

ret = GetUriHost(uri, uriLen, &host, &hostSz, NULL);
ret = GetUriHost(uri, uriLen, &host, &hostSz, NULL, hostBuf);
WB_CHECK(ret == 0, "hostType==NULL");
}
}
Expand Down
99 changes: 95 additions & 4 deletions wolfcrypt/src/asn.c
Original file line number Diff line number Diff line change
Expand Up @@ -20284,6 +20284,86 @@ static int UriRegNameHasNonEmptyLabels(const char* host, int hostSz)
return 1;
}

/* RFC 3986 2.3: unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" */
static int UriIsUnreserved(char c)
{
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') ||
('0' <= c && c <= '9') ||
c == '-' || c == '.' || c == '_' || c == '~';
}

/* Check that a percent-decoded URI host is RFC 3986 reg-name unreserved
* characters only. */
static int UriHostIsRegName(const char* s, int sSz)
{
int i;

for (i = 0; i < sSz; i++) {
if (!UriIsUnreserved(s[i])) {
return 0;
}
}

return 1;
}

/* Size of the buffer for a percent-decoded host.
* Holds the longest DNS name: 253 octets + the absolute-FQDN trailing dot. */
#define URI_HOST_MAX_SZ 254

/* RFC 5280 7.4 step 3: percent-encoding normalization (RFC 3987 5.3.2.3)
* decodes escaped unreserved characters.
* An escape of any other character cannot leave a valid reg-name host, so it
* fails like a malformed one.
* Returns 1 on success and 0 on failure. */
static int UriHostPercentDecode(const char** host, int* hostSz, char* buf)
{
const char* s = *host;
int i;
int j = 0;
signed char hi;
signed char lo;
char c;

for (i = 0; i < *hostSz; i++) {
if (s[i] == '%') {
break;
}
}
if (i == *hostSz) {
return 1;
}

for (i = 0; i < *hostSz; i++) {
c = s[i];

if (c == '%') {
if (i + 2 >= *hostSz) {
return 0;
}
hi = HexCharToByte(s[i + 1]);
lo = HexCharToByte(s[i + 2]);
if (hi < 0 || lo < 0) {
return 0;
}
c = (char)(((byte)hi << 4) | (byte)lo);
if (!UriIsUnreserved(c)) {
return 0;
}
i += 2;
}
if (j >= URI_HOST_MAX_SZ) {
return 0;
}
buf[j++] = c;
}

*host = buf;
*hostSz = j;

return 1;
}

/* Validate the RFC 3986 scheme and return its terminating colon, or NULL.
* scheme = ALPHA *(ALPHA / DIGIT / "+" / "-" / ".") */
static const char* GetUriSchemeEnd(const char* uri, int uriSz)
Expand Down Expand Up @@ -20317,8 +20397,9 @@ static const char* GetUriSchemeEnd(const char* uri, int uriSz)
return NULL;
}

/* buf must hold URI_HOST_MAX_SZ bytes. *host may point into it on return. */
static int GetUriHost(const char* uri, int uriSz, const char** host,
int* hostSz, UriHostType* hostType)
int* hostSz, UriHostType* hostType, char* buf)
{
const char* hostStart;
const char* hostEnd;
Expand Down Expand Up @@ -20379,6 +20460,9 @@ static int GetUriHost(const char* uri, int uriSz, const char** host,
hostEnd++;
}
*hostSz = (int)(hostEnd - hostStart);
if (!UriHostPercentDecode(&hostStart, hostSz, buf)) {
return 0;
}
/* 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
Expand All @@ -20392,7 +20476,8 @@ static int GetUriHost(const char* uri, int uriSz, const char** host,
*hostType = UriHostIsIpv4Address(hostStart, *hostSz) ?
URI_HOST_IPV4 : URI_HOST_REG_NAME;
if (*hostType == URI_HOST_REG_NAME &&
!UriRegNameHasNonEmptyLabels(hostStart, *hostSz)) {
(!UriRegNameHasNonEmptyLabels(hostStart, *hostSz) ||
!UriHostIsRegName(hostStart, *hostSz))) {
return 0;
}
}
Expand All @@ -20410,8 +20495,9 @@ int wolfssl_local_UriNameHasDnsHost(const char* uri, int uriSz)
const char* host = NULL;
int hostSz = 0;
UriHostType hostType = URI_HOST_REG_NAME;
char hostBuf[URI_HOST_MAX_SZ];

if (!GetUriHost(uri, uriSz, &host, &hostSz, &hostType)) {
if (!GetUriHost(uri, uriSz, &host, &hostSz, &hostType, hostBuf)) {
return 0;
}

Expand All @@ -20420,15 +20506,20 @@ int wolfssl_local_UriNameHasDnsHost(const char* uri, int uriSz)
return hostType == URI_HOST_REG_NAME;
}

/* Match the host of a URI against a URI name-constraint subtree.
*
* Returns 1 on match and 0 on no match, including when the URI has no valid
* reg-name host or the base is not a valid reg-name host. */
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;
char hostBuf[URI_HOST_MAX_SZ];

if (base == NULL || baseSz <= 0 ||
!GetUriHost(uri, uriSz, &hostStart, &hostSz, &hostType)) {
!GetUriHost(uri, uriSz, &hostStart, &hostSz, &hostType, hostBuf)) {
return 0;
}
/* RFC 5280 URI constraints apply only to host names specified as fully
Expand Down
Loading