Skip to content

Commit 72a8adb

Browse files
committed
wolfcrypt/src/asn.c, wolfssl/wolfcrypt/asn.h: add wolfssl_local_IsValidFQDN()
tests/api/test_ossl_x509.c, tests/api/test_ossl_x509.h: add test_wolfssl_local_IsValidFQDN(). src/internal.c: in MatchDomainName(), when WOLFSSL_LEFT_MOST_WILDCARD_ONLY, do pattern matching and case folding only if target string validates as an FQDN.
1 parent 353a379 commit 72a8adb

5 files changed

Lines changed: 194 additions & 0 deletions

File tree

src/internal.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13334,6 +13334,22 @@ int MatchDomainName(const char* pattern, int patternLen, const char* str,
1333413334
return 1;
1333513335
#endif
1333613336

13337+
if (leftWildcardOnly && (! wolfssl_local_IsValidFQDN(str, strLen))) {
13338+
/* Not a valid FQDN -- require byte-exact match, no case folding, no
13339+
* wildcard interpretation. This is appropriate for an IPv4 match, for
13340+
* example, but also matches improvised names like "localhost", albeit
13341+
* case-sensitively.
13342+
*/
13343+
return (((word32)patternLen == strLen) &&
13344+
(XMEMCMP(pattern, str, patternLen) == 0));
13345+
}
13346+
13347+
/* strip trailing dots if necessary (FQDN designator). */
13348+
if (str[strLen-1] == '.')
13349+
--strLen;
13350+
if (pattern[patternLen-1] == '.')
13351+
--patternLen;
13352+
1333713353
while (patternLen > 0) {
1333813354
/* Get the next pattern char to evaluate */
1333913355
char p = (char)XTOLOWER((unsigned char)*pattern);

tests/api/test_ossl_x509.c

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,11 +1081,19 @@ int test_wolfSSL_X509_check_ip_asc(void)
10811081
ExpectIntEQ(wolfSSL_X509_check_ip_asc(cn_lit, "127.0.0.1", 0), 0);
10821082
/* CN=*.0.0.1 with no SAN must NOT wildcard-match "127.0.0.1". */
10831083
ExpectIntEQ(wolfSSL_X509_check_ip_asc(cn_wild, "127.0.0.1", 0), 0);
1084+
10841085
/* CN-based hostname matching must still work for hostname checks
10851086
* (sanity check that the fix didn't over-correct). */
10861087
ExpectIntEQ(wolfSSL_X509_check_host(cn_wild, "1.0.0.1",
10871088
XSTRLEN("1.0.0.1"), 0, NULL), 1);
10881089

1090+
/* However, when WOLFSSL_LEFT_MOST_WILDCARD_ONLY, CN-based hostname
1091+
* matching must not apply wildcards when the supplied hostname isn't a
1092+
* well-formed FQDN.
1093+
*/
1094+
ExpectIntEQ(wolfSSL_X509_check_host(cn_wild, "1.0.0.1",
1095+
XSTRLEN("1.0.0.1"), WOLFSSL_LEFT_MOST_WILDCARD_ONLY, NULL), 0);
1096+
10891097
wolfSSL_X509_free(cn_wild);
10901098
wolfSSL_X509_free(cn_lit);
10911099
}
@@ -1610,6 +1618,94 @@ int test_wolfSSL_X509_name_match3(void)
16101618
return EXPECT_RESULT();
16111619
}
16121620

1621+
int test_wolfssl_local_IsValidFQDN(void) {
1622+
EXPECT_DECLS;
1623+
1624+
static const struct { const char *str; int is_FQDN; } test_cases[] = {
1625+
{"example.com", 1},
1626+
{"example.com.", 1}, /* trailing dot (absolute form) */
1627+
{"sub.example.com", 1},
1628+
{"a.b", 1}, /* minimal two-label */
1629+
{"xn--nxasmq5b.com", 1}, /* punycode / IDN (ACE form) */
1630+
{"test_underscore.example.com", 1}, /* underscore in non-TLD label */
1631+
{"_leading.example.com", 1}, /* underscore at start of label */
1632+
{"trailing_.example.com", 1},/* underscore at end of non-TLD label */
1633+
{"123.numericlabel.example.com", 1}, /* numeric labels are fine */
1634+
{"example.12a3", 1}, /* TLD with letters + digits */
1635+
{"ex--ample.com", 1}, /* double hyphen inside label (allowed) */
1636+
{"A.B.C", 1}, /* uppercase OK (case-insensitive rules) */
1637+
1638+
{"example", 0}, /* single label (not fully qualified) */
1639+
{"example.", 0}, /* becomes single label after dot strip */
1640+
{".example.com", 0}, /* leading dot -- empty first label */
1641+
{"example..com", 0}, /* empty label (consecutive dots) */
1642+
{"-example.com", 0}, /* label starts with '-' */
1643+
{"example-.com", 0}, /* label ends with '-' */
1644+
{"example.com-", 0}, /* final label ends with '-' */
1645+
{"example.com_", 0}, /* underscore in TLD (forbidden) */
1646+
{"example._com", 0}, /* underscore in TLD (forbidden) */
1647+
{"ex@mple.com", 0}, /* illegal character '@' */
1648+
{"example com.com", 0}, /* illegal character ' ' */
1649+
{"", 0}, /* empty string */
1650+
{NULL, 0}, /* NULL pointer */
1651+
{"com", 0}, /* single label */
1652+
{"123.456", 0}, /* all-numeric final label (no alpha) */
1653+
{"example.123", 0}, /* all-numeric TLD (no alpha) */
1654+
{"a", 0}, /* single label, too short */
1655+
{"example.123a", 1}, /* TLD with at least one letter -- valid */
1656+
};
1657+
1658+
int i;
1659+
for (i = 0; i < (int)(sizeof(test_cases) / sizeof(test_cases[0])); i++) {
1660+
ExpectIntEQ(wolfssl_local_IsValidFQDN(
1661+
test_cases[i].str,
1662+
test_cases[i].str ? (word32)strlen(test_cases[i].str) : 0),
1663+
test_cases[i].is_FQDN);
1664+
if (! EXPECT_SUCCESS()) {
1665+
fprintf(stderr, "wolfssl_local_IsValidFQDN() wrong result for "
1666+
"case %d \"%s\"\n", i, test_cases[i].str);
1667+
break;
1668+
}
1669+
}
1670+
1671+
/* Additional corner cases (length & label-size boundaries) */
1672+
{
1673+
char buf[300];
1674+
1675+
/* 253 chars (max allowed), with 63 byte labels (max allowed) - valid */
1676+
memset(buf, 'a', 251);
1677+
for (i=63; i < 251; i+=64)
1678+
buf[i] = '.';
1679+
buf[251] = '.';
1680+
buf[252] = 'b';
1681+
buf[253] = '\0';
1682+
ExpectIntEQ(wolfssl_local_IsValidFQDN(buf, (word32)strlen(buf)), 1);
1683+
1684+
/* 254 chars (one too long) - invalid */
1685+
memset(buf, 'a', 252);
1686+
for (i=63; i < 251; i+=64)
1687+
buf[i] = '.';
1688+
buf[252] = '.';
1689+
buf[253] = 'b';
1690+
buf[254] = '\0';
1691+
ExpectIntEQ(wolfssl_local_IsValidFQDN(buf, (word32)strlen(buf)), 0);
1692+
1693+
/* 64-char label (one too long) */
1694+
memset(buf, 'a', 64);
1695+
buf[64] = '.';
1696+
buf[65] = 'c';
1697+
buf[66] = 'o';
1698+
buf[67] = 'm';
1699+
buf[68] = '\0';
1700+
ExpectIntEQ(wolfssl_local_IsValidFQDN(buf, (word32)strlen(buf)), 0);
1701+
1702+
/* Explicit nameSz == 0 (even with non-NULL pointer) */
1703+
ExpectIntEQ(wolfssl_local_IsValidFQDN("example.com", 0), 0);
1704+
}
1705+
1706+
return EXPECT_RESULT();
1707+
}
1708+
16131709
int test_wolfSSL_X509_max_altnames(void)
16141710
{
16151711
EXPECT_DECLS;

tests/api/test_ossl_x509.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ int test_wolfSSL_X509_bad_altname(void);
4848
int test_wolfSSL_X509_name_match1(void);
4949
int test_wolfSSL_X509_name_match2(void);
5050
int test_wolfSSL_X509_name_match3(void);
51+
int test_wolfssl_local_IsValidFQDN(void);
5152
int test_wolfSSL_X509_max_altnames(void);
5253
int test_wolfSSL_X509_max_name_constraints(void);
5354
int test_wolfSSL_X509_check_ca(void);
@@ -79,6 +80,7 @@ int test_wolfSSL_X509_cmp(void);
7980
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_name_match1), \
8081
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_name_match2), \
8182
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_name_match3), \
83+
TEST_DECL_GROUP("ossl_x509", test_wolfssl_local_IsValidFQDN), \
8284
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_max_altnames), \
8385
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_max_name_constraints), \
8486
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_check_ca), \

wolfcrypt/src/asn.c

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17820,6 +17820,81 @@ static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert)
1782017820

1782117821
#endif /* IGNORE_NAME_CONSTRAINTS */
1782217822

17823+
#ifndef WOLFCRYPT_ONLY
17824+
/* Returns 1 if name is a syntactically valid DNS FQDN per RFC 952/1123.
17825+
*
17826+
* Rules enforced:
17827+
* - Total effective length (excluding optional trailing dot) in [1, 253]
17828+
* - Each label is 1-63 octets of [a-zA-Z0-9-], with _ allowed in all but
17829+
* the last label.
17830+
* - No label starts or ends with '-'
17831+
* - At least two labels (single-label names are not "fully qualified")
17832+
* - Final label (TLD) contains at least one letter (rejects all-numeric
17833+
* strings that could be confused with IPv4 literals, and matches the
17834+
* ICANN constraint that TLDs are alphabetic)
17835+
* - Optional trailing dot is accepted (absolute FQDN form)
17836+
* - Internationalized names are valid in their ACE/punycode (xn--) form
17837+
*/
17838+
int wolfssl_local_IsValidFQDN(const char* name, word32 nameSz)
17839+
{
17840+
word32 i;
17841+
int labelLen = 0;
17842+
int labelCount = 0;
17843+
int curLabelHasAlpha = 0;
17844+
int curLabelHasUnderscore = 0;
17845+
17846+
if (name == NULL || nameSz == 0)
17847+
return 0;
17848+
17849+
/* Strip a single optional trailing dot before measuring. "example.com."
17850+
* is the absolute form of the same FQDN.
17851+
*/
17852+
if (name[nameSz - 1] == '.')
17853+
--nameSz;
17854+
17855+
if (nameSz < 1 || nameSz > 253)
17856+
return 0;
17857+
17858+
for (i = 0; i < nameSz; i++) {
17859+
byte c = (byte)name[i];
17860+
17861+
if (c == '.') {
17862+
if (labelLen == 0 || name[i - 1] == '-')
17863+
return 0;
17864+
++labelCount;
17865+
labelLen = 0;
17866+
curLabelHasAlpha = 0;
17867+
curLabelHasUnderscore = 0;
17868+
continue;
17869+
}
17870+
17871+
if (++labelLen > 63)
17872+
return 0;
17873+
17874+
if (c == '-') {
17875+
if (labelLen == 1)
17876+
return 0;
17877+
}
17878+
else if (((c | 0x20) >= 'a') && ((c | 0x20) <= 'z')) {
17879+
curLabelHasAlpha = 1;
17880+
}
17881+
else if (c == '_') {
17882+
curLabelHasUnderscore = 1;
17883+
}
17884+
else if ((c < '0') || (c > '9')) {
17885+
return 0;
17886+
}
17887+
}
17888+
17889+
/* Final label (no trailing dot in the effective range to close it) */
17890+
if ((labelLen == 0) || (name[nameSz - 1] == '-') || curLabelHasUnderscore)
17891+
return 0;
17892+
++labelCount;
17893+
17894+
return ((labelCount > 1) && curLabelHasAlpha);
17895+
}
17896+
#endif /* !WOLFCRYPT_ONLY */
17897+
1782317898
#ifdef WOLFSSL_ASN_TEMPLATE
1782417899
#if defined(WOLFSSL_SEP) || defined(WOLFSSL_FPKI)
1782517900
/* ASN.1 template for OtherName of an X.509 certificate.

wolfssl/wolfcrypt/asn.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3119,6 +3119,11 @@ WOLFSSL_TEST_VIS int wolfssl_local_MatchIpSubnet(const byte* ip, int ipSz,
31193119
int constraintSz);
31203120
#endif
31213121

3122+
#ifndef WOLFCRYPT_ONLY
3123+
WOLFSSL_TEST_VIS int wolfssl_local_IsValidFQDN(const char* name,
3124+
word32 nameSz);
3125+
#endif
3126+
31223127
#if ((defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_IMPORT)) \
31233128
|| (defined(HAVE_CURVE25519) && defined(HAVE_CURVE25519_KEY_IMPORT)) \
31243129
|| (defined(HAVE_ED448) && defined(HAVE_ED448_KEY_IMPORT)) \

0 commit comments

Comments
 (0)