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
76 changes: 76 additions & 0 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -13336,6 +13336,66 @@ static int MatchIPv6(const char* pattern, int patternLen,
}
#endif /* WOLFSSL_IP_ALT_NAME && !WOLFSSL_USER_IO */

/* IDNA A-label prefix (Punycode-encoded internationalized labels), used to
* gate wildcard matching per RFC 6125 sec. 6.4.3 / RFC 9525 sec. 6.3. */
static int LabelIsALabel(const char* label, word32 labelLen)
{
if (labelLen < 4)
return 0;
return ((XTOLOWER((unsigned char)label[0]) == 'x') &&
(XTOLOWER((unsigned char)label[1]) == 'n') &&
(label[2] == '-') &&
(label[3] == '-'));
}

/* Returns 1 if any dot-separated label in name is an A-label. */
static int NameHasALabel(const char* name, word32 nameLen)
{
word32 labelStart = 0;
word32 i;

for (i = 0; i < nameLen; i++) {
if (name[i] == '.') {
if (LabelIsALabel(name + labelStart, i - labelStart))
return 1;
labelStart = i + 1;
}
}
if (labelStart < nameLen) {
if (LabelIsALabel(name + labelStart, nameLen - labelStart))
return 1;
}
return 0;
}

/* Returns 1 if any label of pattern that contains a wildcard ('*') is an
* A-label. RFC 6125 sec. 6.4.3 disallows wildcards embedded in A-labels. */
static int PatternHasWildcardInALabel(const char* pattern, word32 patternLen)
{
word32 labelStart = 0;
int labelHasWildcard = 0;
word32 i;

for (i = 0; i < patternLen; i++) {
if (pattern[i] == '.') {
if (labelHasWildcard &&
LabelIsALabel(pattern + labelStart, i - labelStart)) {
return 1;
}
labelStart = i + 1;
labelHasWildcard = 0;
}
else if (pattern[i] == '*') {
labelHasWildcard = 1;
}
}
if (labelHasWildcard &&
LabelIsALabel(pattern + labelStart, patternLen - labelStart)) {
return 1;
}
return 0;
}

/* Match names with wildcards, each wildcard can represent a single name
component or fragment but not multiple names, i.e.,
*.z.com matches y.z.com but not x.y.z.com
Expand Down Expand Up @@ -13376,6 +13436,22 @@ int MatchDomainName(const char* pattern, int patternLen, const char* str,
if (pattern[patternLen-1] == '.')
--patternLen;

/* RFC 6125 sec. 6.4.3 / RFC 9525 sec. 6.3: do not perform wildcard
* matching when the pattern has a wildcard embedded in an A-label, nor
* when the reference identifier (hostname) contains any A-label. The
* existing single-label glob would otherwise match across the
* Punycode-encoded form (e.g., "x*.example.com" matching
* "xn--rger-koa.example.com"), which has no semantic meaning. */
if (PatternHasWildcardInALabel(pattern, (word32)patternLen))
return 0;
if (NameHasALabel(str, strLen)) {
int i;
for (i = 0; i < patternLen; i++) {
if (pattern[i] == '*')
return 0;
}
}

while (patternLen > 0) {
/* Get the next pattern char to evaluate */
char p = (char)XTOLOWER((unsigned char)*pattern);
Expand Down
91 changes: 90 additions & 1 deletion tests/api/test_ossl_x509.c
Original file line number Diff line number Diff line change
Expand Up @@ -1662,7 +1662,8 @@ int test_wolfssl_local_IsValidFQDN(void) {
test_cases[i].is_FQDN);
if (! EXPECT_SUCCESS()) {
fprintf(stderr, "wolfssl_local_IsValidFQDN() wrong result for "
"case %d \"%s\"\n", i, test_cases[i].str);
"case %d \"%s\"\n", i,
test_cases[i].str ? test_cases[i].str : "(null)");
break;
}
}
Expand Down Expand Up @@ -1706,6 +1707,94 @@ int test_wolfssl_local_IsValidFQDN(void) {
return EXPECT_RESULT();
}

/* Verify that MatchDomainName() refuses to expand wildcards across IDNA
* A-labels (xn-- prefix) per RFC 6125 sec. 6.4.3 / RFC 9525 sec. 6.3.
*
* MatchDomainName() is exposed for testing via the visibility mechanism
* declared in wolfssl/internal.h. */
int test_wolfSSL_MatchDomainName_idn(void)
{
EXPECT_DECLS;
#if !defined(NO_ASN) && !defined(WOLFCRYPT_ONLY) && !defined(NO_CERTS)
static const struct {
Comment thread
embhorn marked this conversation as resolved.
const char* pattern;
const char* host;
unsigned int flags;
int expected; /* 1 = match, 0 = no match */
const char* note;
} cases[] = {
/* Partial wildcard whose literal prefix overlaps "xn--" must NOT
* match an A-label hostname. */
{ "x*.example.com", "xn--rger-koa.example.com", 0, 0,
"partial wildcard vs A-label" },
/* Wildcard embedded inside an A-label pattern must NOT match. */
{ "xn--*.example.com", "xn--rger-koa.example.com", 0, 0,
"wildcard inside A-label pattern" },
/* Full left-most wildcard MUST NOT match an A-label hostname
* (RFC 9525 sec. 6.3 strengthens RFC 6125 SHOULD NOT to MUST NOT). */
{ "*.example.com", "xn--rger-koa.example.com", 0, 0,
"full wildcard vs A-label hostname" },
/* A-label appearing in an inner label still disables wildcard
* matching against the entire reference identifier. */
{ "*.example.com", "foo.xn--bar.example.com", 0, 0,
"wildcard with A-label in inner label" },
/* Case-insensitive A-label detection: "XN--" is also an A-label. */
{ "x*.example.com", "XN--rger-koa.example.com", 0, 0,
"uppercase A-label prefix" },
/* Control: full wildcard SHOULD continue to match plain ASCII. */
{ "*.example.com", "foo.example.com", 0, 1,
"wildcard matches non-IDN" },
/* Control: exact A-label match (no wildcard in pattern) must work. */
{ "xn--rger-koa.example.com", "xn--rger-koa.example.com", 0, 1,
"exact A-label match" },
/* Control: a label that merely begins with 'x' (not 'xn--') is not
* an A-label and must still wildcard-match. */
{ "*.example.com", "xyz.example.com", 0, 1,
"non-A-label x-prefix" },
/* Control: partial wildcard against a non-A-label still works. */
{ "x*.example.com", "xyz.example.com", 0, 1,
"partial wildcard non-IDN" },

/* Trailing-dot normalization: absolute-form FQDN ("example.com.")
* must match the same FQDN with or without the trailing dot, on
* either side of the comparison. RFC 1035 / RFC 6125. */
{ "example.com", "example.com.", 0, 1,
"trailing dot on host" },
{ "example.com.", "example.com", 0, 1,
"trailing dot on pattern" },
{ "example.com.", "example.com.", 0, 1,
"trailing dot on both" },
{ "*.example.com", "foo.example.com.", 0, 1,
"trailing dot on host with wildcard pattern" },
/* Trailing dot must not cause an A-label gate to misfire. */
{ "*.example.com", "xn--rger-koa.example.com.", 0, 0,
"trailing dot on A-label host" },
/* Same trailing-dot normalization under WOLFSSL_LEFT_MOST_WILDCARD_ONLY. */
{ "*.example.com", "foo.example.com.",
WOLFSSL_LEFT_MOST_WILDCARD_ONLY, 1,
"trailing dot, leftWildcardOnly" },
};
size_t i;

for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
int got = MatchDomainName(
cases[i].pattern, (int)XSTRLEN(cases[i].pattern),
cases[i].host, (word32)XSTRLEN(cases[i].host),
cases[i].flags);
ExpectIntEQ(got, cases[i].expected);
if (! EXPECT_SUCCESS()) {
fprintf(stderr,
"MatchDomainName(\"%s\", \"%s\", flags=0x%x) = %d, "
"expected %d (%s)\n",
cases[i].pattern, cases[i].host, cases[i].flags,
got, cases[i].expected, cases[i].note);
break;
}
}
#endif /* !NO_ASN && !WOLFCRYPT_ONLY && !NO_CERTS */
return EXPECT_RESULT();
}

int test_wolfSSL_X509_max_altnames(void)
{
EXPECT_DECLS;
Expand Down
2 changes: 2 additions & 0 deletions tests/api/test_ossl_x509.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ int test_wolfSSL_X509_name_match1(void);
int test_wolfSSL_X509_name_match2(void);
int test_wolfSSL_X509_name_match3(void);
int test_wolfssl_local_IsValidFQDN(void);
int test_wolfSSL_MatchDomainName_idn(void);
int test_wolfSSL_X509_max_altnames(void);
int test_wolfSSL_X509_max_name_constraints(void);
int test_wolfSSL_X509_check_ca(void);
Expand Down Expand Up @@ -81,6 +82,7 @@ int test_wolfSSL_X509_cmp(void);
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_name_match2), \
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_name_match3), \
TEST_DECL_GROUP("ossl_x509", test_wolfssl_local_IsValidFQDN), \
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_MatchDomainName_idn), \
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_max_altnames), \
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_max_name_constraints), \
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_check_ca), \
Expand Down
9 changes: 6 additions & 3 deletions wolfssl/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -2228,9 +2228,12 @@ WOLFSSL_LOCAL void FreeAsyncCtx(WOLFSSL* ssl, byte freeAsync);
WOLFSSL_LOCAL void FreeKeyExchange(WOLFSSL* ssl);
WOLFSSL_LOCAL void FreeSuites(WOLFSSL* ssl);
WOLFSSL_LOCAL int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, word32 totalSz);
WOLFSSL_LOCAL int MatchDomainName(const char* pattern, int len,
const char* str, word32 strLen,
unsigned int flags);
#ifdef WOLFSSL_API_PREFIX_MAP
#define MatchDomainName wolfSSL_MatchDomainName
#endif
WOLFSSL_TEST_VIS int MatchDomainName(const char* pattern, int len,
const char* str, word32 strLen,
unsigned int flags);
#if !defined(NO_CERTS) && !defined(NO_ASN)
WOLFSSL_LOCAL int CheckForAltNames(DecodedCert* dCert, const char* domain,
word32 domainLen, int* checkCN,
Expand Down
Loading