Skip to content
Merged
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
46 changes: 46 additions & 0 deletions tests/api/test_asn.c
Original file line number Diff line number Diff line change
Expand Up @@ -1909,6 +1909,52 @@ int test_wolfssl_local_MatchUriNameConstraint(void)
ExpectIntEQ(uriNC("https://host.com.evil.com", "host.com"), 0);
ExpectIntEQ(uriNC("https://other.com", "host.com"), 0);

/* Only the initial scheme delimiter can introduce an authority. A URI
* embedded in an authority-less URI does not supply its host. */
ExpectIntEQ(uriNC("urn:example:opaque", "host.com"), 0);
ExpectIntEQ(uriNC("urn:example:https://host.com/x",
"host.com"), 0);
ExpectIntEQ(uriNC("foo:/path/https://host.com/x",
"host.com"), 0);
ExpectIntEQ(uriNC("urn:example?next=https://host.com/x",
"host.com"), 0);
ExpectIntEQ(uriNC("urn:example#https://host.com/x",
"host.com"), 0);
ExpectIntEQ(uriNC("sip:victim.example?next=https://host.com/x",
"host.com"), 0);
ExpectIntEQ(uriNC("urn:example?next=https://a.host.com/x",
".host.com"), 0);

/* With a real authority, later URLs must not affect host matching. */
ExpectIntEQ(uriNC("https://other.com/https://host.com/x",
"host.com"), 0);
ExpectIntEQ(uriNC("https://other.com?next=https://host.com/x",
"host.com"), 0);
ExpectIntEQ(uriNC("https://other.com#https://host.com/x",
"host.com"), 0);
ExpectIntEQ(uriNC("https://host.com?next=https://other.com/x",
"host.com"), 1);

/* Scheme syntax, not a whitelist:
* ALPHA *(ALPHA / DIGIT / "+" / "-" / "."). */
ExpectIntEQ(uriNC("a://host.com", "host.com"), 1);
ExpectIntEQ(uriNC("Custom+v2-test.1://host.com", "host.com"), 1);
ExpectIntEQ(uriNC("://host.com", "host.com"), 0);
ExpectIntEQ(uriNC("1https://host.com", "host.com"), 0);
ExpectIntEQ(uriNC("ht/tps://host.com", "host.com"), 0);
ExpectIntEQ(uriNC("ht_tps://host.com", "host.com"), 0);
ExpectIntEQ(uriNC(" https://host.com", "host.com"), 0);
ExpectIntEQ(uriNC("https", "host.com"), 0);
ExpectIntEQ(uriNC("https:", "host.com"), 0);
ExpectIntEQ(uriNC("https:/", "host.com"), 0);
/* Explicit lengths must bound the scheme and separator scans. */
ExpectIntEQ(wolfssl_local_MatchUriNameConstraint("abc://host.com", 3,
"host.com", 8), 0);
ExpectIntEQ(wolfssl_local_MatchUriNameConstraint("abc://host.com", 4,
"host.com", 8), 0);
ExpectIntEQ(wolfssl_local_MatchUriNameConstraint("abc://host.com", 5,
"host.com", 8), 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. */
Expand Down
74 changes: 41 additions & 33 deletions tests/unit-mcdc/test_asn_ext_whitebox.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
* 1. wolfssl_local_MatchBaseName() ................ :18555,:18607,:18626
* 2. URI host classification (UriHostIsDecOctet/
* UriHostIsIpv4Address/UriRegNameHasNonEmptyLabels/
* GetUriHost) ...................................... :18664-:18794
* GetUriSchemeEnd/GetUriHost), including scheme syntax and the
* four-operand authority guard (older helpers: :18664-:18794)
* 3. wolfssl_local_MatchDnsConstraintWildcard() ... :18944,:18954,:18978
* 4. wolfssl_local_MatchIpSubnet() ........................... :19038
* 5. MatchOtherNameConstraint() ............................... :19063
Expand Down Expand Up @@ -352,12 +353,13 @@ static void wb_match_dir_attr(void)
* UriHostIsDecOctet(): :18664 (NULL/sSz<=0/sSz>3), :18667 (leading zero)
* UriHostIsIpv4Address(): :18687 (NULL/hostSz<=0), :18699 (non-digit)
* UriRegNameHasNonEmptyLabels(): :18711-:18712 (NULL/leading-dot/trailing-dot)
* GetUriHost(): :18736-:18737 (bad args), :18744 ("://" scan),
* GetUriSchemeEnd(): NULL, bounded scan, colon, ALPHA, initial letter,
* DIGIT and '+'/'-'/'.' decisions
* GetUriHost(): :18736-:18737 (bad args), scheme/authority guard
* (p == NULL || uriEnd - p < 3 || p[1] != '/' || p[2] != '/'),
* :18772 (bracket scan), :18794 (trailing-dot re-check)
* Driven indirectly through wolfssl_local_MatchUriNameConstraint() (the
* only external entry point reaching these file-static helpers) since none of
* them are directly link-visible on their own; MatchUriNameConstraint IS
* WOLFSSL_LOCAL/global so it is callable directly here.
* Driven through wolfssl_local_MatchUriNameConstraint(), plus direct calls
* to file-static helpers for scheme decisions and otherwise masked operands.
* ------------------------------------------------------------------------- */
#ifndef IGNORE_NAME_CONSTRAINTS
static void wb_uri_host_helpers(void)
Expand Down Expand Up @@ -410,15 +412,40 @@ static void wb_uri_host_helpers(void)
== 0, "base==NULL (short-circuits before GetUriHost host/hostSz "
"args, but exercises the same bad-args style entry)");

WB_NOTE("GetUriHost(): \"://\" scheme scan [:18744]");
/* No "://" anywhere in the (long enough) buffer -> scan runs to
* completion without a match, hostStart stays NULL. */
WB_NOTE("GetUriSchemeEnd(): NULL, length, colon and scheme characters");
WB_CHECK(GetUriSchemeEnd(NULL, 5) == NULL, "uri==NULL");
WB_CHECK(GetUriSchemeEnd("a:", 0) == NULL, "empty segment");
WB_CHECK(GetUriSchemeEnd("a:", 1) == NULL, "colon outside segment");
WB_CHECK(GetUriSchemeEnd(":", 1) == NULL, "colon cannot start scheme");
WB_CHECK(GetUriSchemeEnd("1:", 2) == NULL, "first byte must be ALPHA");
WB_CHECK(GetUriSchemeEnd("A:", 2) != NULL, "uppercase ALPHA");
WB_CHECK(GetUriSchemeEnd("a:", 2) != NULL, "lowercase ALPHA");
WB_CHECK(GetUriSchemeEnd("a@:", 3) == NULL, "byte below 'A'");
WB_CHECK(GetUriSchemeEnd("a[:", 3) == NULL, "byte above 'Z', below 'a'");
WB_CHECK(GetUriSchemeEnd("a{:", 3) == NULL, "byte above 'z'");
WB_CHECK(GetUriSchemeEnd("a/:", 3) == NULL, "byte below '0'");
WB_CHECK(GetUriSchemeEnd("a0:", 3) != NULL, "DIGIT");
WB_CHECK(GetUriSchemeEnd("a;:", 3) == NULL, "byte above '9'");
WB_CHECK(GetUriSchemeEnd("a+:", 3) != NULL, "plus allowed after ALPHA");
WB_CHECK(GetUriSchemeEnd("a-:", 3) != NULL, "minus allowed after ALPHA");
WB_CHECK(GetUriSchemeEnd("a.:", 3) != NULL, "dot allowed after ALPHA");

WB_NOTE("GetUriHost(): scheme + four-operand authority guard");
/* No colon in the bounded segment -> GetUriSchemeEnd() returns NULL. */
WB_CHECK(wolfssl_local_MatchUriNameConstraint("not-a-uri-at-all", 16,
".host.com", 9) == 0, "no \"://\" present");
/* "://" present and matched mid-scan (true branch of the 3-byte
* lookahead comparison). */
WB_CHECK(wolfssl_local_MatchUriNameConstraint("s://a.host.com/x", 16,
".host.com", 9) == 1, "\"://\" found (baseline true)");
"host.com", 8) == 0, "no scheme colon present");
/* Valid scheme followed by both slashes: all guard operands false. */
WB_CHECK(wolfssl_local_MatchUriNameConstraint("abc://host.com", 14,
"host.com", 8) == 1, "scheme + authority (baseline)");
WB_CHECK(wolfssl_local_MatchUriNameConstraint("abc://host.com", 4,
"host.com", 8) == 0, "scheme colon is the last byte");
WB_CHECK(wolfssl_local_MatchUriNameConstraint("abc://host.com", 5,
"host.com", 8) == 0, "only one byte after scheme colon");
/* Exact-host constraints ensure subtree rejection cannot mask a bug. */
WB_CHECK(wolfssl_local_MatchUriNameConstraint("ab:cd://host.com/x", 18,
"host.com", 8) == 0, "scheme colon not followed by '/'");
WB_CHECK(wolfssl_local_MatchUriNameConstraint("a:/b://host.com/x", 17,
"host.com", 8) == 0, "scheme colon followed by only one '/'");

WB_NOTE("GetUriHost(): IP-literal '[' bracket scan [:18772]");
/* '[' opens an IP-literal host; scan for ']' runs to completion
Expand Down Expand Up @@ -463,25 +490,6 @@ static void wb_uri_host_helpers(void)
WB_CHECK(UriHostIsIpv4Address("1.2.3.4", 7) == 1,
":18753 both operands false (valid dotted quad)");

/* :18798 -- the three-byte "://" lookahead. Every URI used above has
* its ':' immediately followed by "//", so the 2nd and 3rd operands are
* pinned true. These two URIs each contain an earlier ':' that fails
* the lookahead at a different operand before the real "://" is found. */
{
/* Compared against the plain "s://host.com/x" form rather than a
* hard-coded 1: what this row has to show is that an earlier ':'
* which fails the lookahead does not change the outcome, and the
* base-matching semantics themselves are asserted elsewhere. */
int plain = wolfssl_local_MatchUriNameConstraint("s://host.com/x", 14,
".host.com", 9);
WB_CHECK(wolfssl_local_MatchUriNameConstraint("ab:cd://host.com/x", 18,
".host.com", 9) == plain,
":18798 2nd operand false (':' not followed by '/')");
WB_CHECK(wolfssl_local_MatchUriNameConstraint("a:/b://host.com/x", 17,
".host.com", 9) == plain,
":18798 3rd operand false (\":/\" not followed by '/')");
}

/* :18848 -- after stripping one trailing dot the host is empty. The
* "http://../x" row above lands on the 2nd operand (still ends in '.');
* a host of exactly "." lands on the 1st. */
Expand Down
52 changes: 42 additions & 10 deletions wolfcrypt/src/asn.c
Original file line number Diff line number Diff line change
Expand Up @@ -19400,6 +19400,39 @@ static int UriRegNameHasNonEmptyLabels(const char* host, int hostSz)
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)
{
int i;

if (uri == NULL) {
return NULL;
}
for (i = 0; i < uriSz; i++) {
char c = uri[i];

if (i > 0 && c == ':') {
return uri + i;
}
if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) {
continue;
}
/* The first character must be a letter. */
if (i == 0) {
return NULL;
}
if ('0' <= c && c <= '9') {
continue;
}
if (c == '+' || c == '-' || c == '.') {
continue;
}
return NULL;
}
return NULL;
}

static int GetUriHost(const char* uri, int uriSz, const char** host,
int* hostSz, UriHostType* hostType)
{
Expand All @@ -19408,23 +19441,22 @@ static int GetUriHost(const char* uri, int uriSz, const char** host,
const char* p;
const char* uriEnd;

/* 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`. */
/* Early-out for short inputs; the uriEnd - p < 3 guard below bounds
* the authority separator reads after the scheme colon. */
if (uri == NULL || uriSz < 3 || host == NULL || hostSz == NULL ||
hostType == NULL) {
return 0;
}

uriEnd = uri + uriSz;
hostStart = NULL;
for (p = uri; p < uriEnd - 2; p++) {
if (p[0] == ':' && p[1] == '/' && p[2] == '/') {
hostStart = p + 3;
break;
}
/* Only "//" immediately after the scheme colon introduces an authority;
* a later "://" in a path, query or fragment is not an authority. */
p = GetUriSchemeEnd(uri, uriSz);
if (p == NULL || uriEnd - p < 3 || p[1] != '/' || p[2] != '/') {
return 0;
}
if (hostStart == NULL || hostStart >= uriEnd) {
hostStart = p + 3;
if (hostStart >= uriEnd) {
return 0;
}

Expand Down
Loading