Merge pull request #10183 from douzzer/20260409-IsValidFQDN

20260409-IsValidFQDN
This commit is contained in:
David Garske
2026-05-05 11:22:51 -07:00
committed by GitHub
5 changed files with 195 additions and 0 deletions
+16
View File
@@ -13344,6 +13344,22 @@ int MatchDomainName(const char* pattern, int patternLen, const char* str,
return 1;
#endif
if (leftWildcardOnly && (! wolfssl_local_IsValidFQDN(str, strLen))) {
/* Not a valid FQDN -- require byte-exact match, no case folding, no
* wildcard interpretation. This is appropriate for an IPv4 match, for
* example, but also matches improvised names like "localhost", albeit
* case-sensitively.
*/
return (((word32)patternLen == strLen) &&
(XMEMCMP(pattern, str, patternLen) == 0));
}
/* strip trailing dots if necessary (FQDN designator). */
if (str[strLen-1] == '.')
--strLen;
if (pattern[patternLen-1] == '.')
--patternLen;
while (patternLen > 0) {
/* Get the next pattern char to evaluate */
char p = (char)XTOLOWER((unsigned char)*pattern);
+97
View File
@@ -1081,11 +1081,19 @@ int test_wolfSSL_X509_check_ip_asc(void)
ExpectIntEQ(wolfSSL_X509_check_ip_asc(cn_lit, "127.0.0.1", 0), 0);
/* CN=*.0.0.1 with no SAN must NOT wildcard-match "127.0.0.1". */
ExpectIntEQ(wolfSSL_X509_check_ip_asc(cn_wild, "127.0.0.1", 0), 0);
/* CN-based hostname matching must still work for hostname checks
* (sanity check that the fix didn't over-correct). */
ExpectIntEQ(wolfSSL_X509_check_host(cn_wild, "1.0.0.1",
XSTRLEN("1.0.0.1"), 0, NULL), 1);
/* However, when WOLFSSL_LEFT_MOST_WILDCARD_ONLY, CN-based hostname
* matching must not apply wildcards when the supplied hostname isn't a
* well-formed FQDN.
*/
ExpectIntEQ(wolfSSL_X509_check_host(cn_wild, "1.0.0.1",
XSTRLEN("1.0.0.1"), WOLFSSL_LEFT_MOST_WILDCARD_ONLY, NULL), 0);
wolfSSL_X509_free(cn_wild);
wolfSSL_X509_free(cn_lit);
}
@@ -1610,6 +1618,95 @@ int test_wolfSSL_X509_name_match3(void)
return EXPECT_RESULT();
}
int test_wolfssl_local_IsValidFQDN(void) {
EXPECT_DECLS;
#if !defined(NO_ASN) && !defined(WOLFCRYPT_ONLY) && !defined(NO_CERTS)
static const struct { const char *str; int is_FQDN; } test_cases[] = {
{"example.com", 1},
{"example.com.", 1}, /* trailing dot (absolute form) */
{"sub.example.com", 1},
{"a.b", 1}, /* minimal two-label */
{"xn--nxasmq5b.com", 1}, /* punycode / IDN (ACE form) */
{"test_underscore.example.com", 1}, /* underscore in non-TLD label */
{"_leading.example.com", 1}, /* underscore at start of label */
{"trailing_.example.com", 1},/* underscore at end of non-TLD label */
{"123.numericlabel.example.com", 1}, /* numeric labels are fine */
{"example.12a3", 1}, /* TLD with letters + digits */
{"ex--ample.com", 1}, /* double hyphen inside label (allowed) */
{"A.B.C", 1}, /* uppercase OK (case-insensitive rules) */
{"example", 0}, /* single label (not fully qualified) */
{"example.", 0}, /* becomes single label after dot strip */
{".example.com", 0}, /* leading dot -- empty first label */
{"example..com", 0}, /* empty label (consecutive dots) */
{"-example.com", 0}, /* label starts with '-' */
{"example-.com", 0}, /* label ends with '-' */
{"example.com-", 0}, /* final label ends with '-' */
{"example.com_", 0}, /* underscore in TLD (forbidden) */
{"example._com", 0}, /* underscore in TLD (forbidden) */
{"ex@mple.com", 0}, /* illegal character '@' */
{"example com.com", 0}, /* illegal character ' ' */
{"", 0}, /* empty string */
{NULL, 0}, /* NULL pointer */
{"com", 0}, /* single label */
{"123.456", 0}, /* all-numeric final label (no alpha) */
{"example.123", 0}, /* all-numeric TLD (no alpha) */
{"a", 0}, /* single label, too short */
{"example.123a", 1}, /* TLD with at least one letter -- valid */
};
int i;
for (i = 0; i < (int)(sizeof(test_cases) / sizeof(test_cases[0])); i++) {
ExpectIntEQ(wolfssl_local_IsValidFQDN(
test_cases[i].str,
test_cases[i].str ? (word32)strlen(test_cases[i].str) : 0),
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);
break;
}
}
/* Additional corner cases (length & label-size boundaries) */
{
char buf[300];
/* 253 chars (max allowed), with 63 byte labels (max allowed) - valid */
memset(buf, 'a', 251);
for (i=63; i < 251; i+=64)
buf[i] = '.';
buf[251] = '.';
buf[252] = 'b';
buf[253] = '\0';
ExpectIntEQ(wolfssl_local_IsValidFQDN(buf, (word32)strlen(buf)), 1);
/* 254 chars (one too long) - invalid */
memset(buf, 'a', 252);
for (i=63; i < 251; i+=64)
buf[i] = '.';
buf[252] = '.';
buf[253] = 'b';
buf[254] = '\0';
ExpectIntEQ(wolfssl_local_IsValidFQDN(buf, (word32)strlen(buf)), 0);
/* 64-char label (one too long) */
memset(buf, 'a', 64);
buf[64] = '.';
buf[65] = 'c';
buf[66] = 'o';
buf[67] = 'm';
buf[68] = '\0';
ExpectIntEQ(wolfssl_local_IsValidFQDN(buf, (word32)strlen(buf)), 0);
/* Explicit nameSz == 0 (even with non-NULL pointer) */
ExpectIntEQ(wolfssl_local_IsValidFQDN("example.com", 0), 0);
}
#endif /* !NO_ASN && !WOLFCRYPT_ONLY && !NO_CERTS */
return EXPECT_RESULT();
}
int test_wolfSSL_X509_max_altnames(void)
{
EXPECT_DECLS;
+2
View File
@@ -48,6 +48,7 @@ int test_wolfSSL_X509_bad_altname(void);
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_X509_max_altnames(void);
int test_wolfSSL_X509_max_name_constraints(void);
int test_wolfSSL_X509_check_ca(void);
@@ -79,6 +80,7 @@ int test_wolfSSL_X509_cmp(void);
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_name_match1), \
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_X509_max_altnames), \
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_max_name_constraints), \
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_check_ca), \
+75
View File
@@ -18027,6 +18027,81 @@ static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert)
#endif /* IGNORE_NAME_CONSTRAINTS */
#if !defined(WOLFCRYPT_ONLY) && !defined(NO_CERTS)
/* Returns 1 if name is a syntactically valid DNS FQDN per RFC 952/1123.
*
* Rules enforced:
* - Total effective length (excluding optional trailing dot) in [1, 253]
* - Each label is 1-63 octets of [a-zA-Z0-9-], with _ allowed in all but
* the last label.
* - No label starts or ends with '-'
* - At least two labels (single-label names are not "fully qualified")
* - Final label (TLD) contains at least one letter (rejects all-numeric
* strings that could be confused with IPv4 literals, and matches the
* ICANN constraint that TLDs are alphabetic)
* - Optional trailing dot is accepted (absolute FQDN form)
* - Internationalized names are valid in their ACE/punycode (xn--) form
*/
int wolfssl_local_IsValidFQDN(const char* name, word32 nameSz)
{
word32 i;
int labelLen = 0;
int labelCount = 0;
int curLabelHasAlpha = 0;
int curLabelHasUnderscore = 0;
if (name == NULL || nameSz == 0)
return 0;
/* Strip a single optional trailing dot before measuring. "example.com."
* is the absolute form of the same FQDN.
*/
if (name[nameSz - 1] == '.')
--nameSz;
if (nameSz < 1 || nameSz > 253)
return 0;
for (i = 0; i < nameSz; i++) {
byte c = (byte)name[i];
if (c == '.') {
if (labelLen == 0 || name[i - 1] == '-')
return 0;
++labelCount;
labelLen = 0;
curLabelHasAlpha = 0;
curLabelHasUnderscore = 0;
continue;
}
if (++labelLen > 63)
return 0;
if (c == '-') {
if (labelLen == 1)
return 0;
}
else if (((c | 0x20) >= 'a') && ((c | 0x20) <= 'z')) {
curLabelHasAlpha = 1;
}
else if (c == '_') {
curLabelHasUnderscore = 1;
}
else if ((c < '0') || (c > '9')) {
return 0;
}
}
/* Final label (no trailing dot in the effective range to close it) */
if ((labelLen == 0) || (name[nameSz - 1] == '-') || curLabelHasUnderscore)
return 0;
++labelCount;
return ((labelCount > 1) && curLabelHasAlpha);
}
#endif /* !WOLFCRYPT_ONLY && !NO_CERTS */
#ifdef WOLFSSL_ASN_TEMPLATE
#if defined(WOLFSSL_SEP) || defined(WOLFSSL_FPKI)
/* ASN.1 template for OtherName of an X.509 certificate.
+5
View File
@@ -3136,6 +3136,11 @@ WOLFSSL_TEST_VIS int wolfssl_local_MatchIpSubnet(const byte* ip, int ipSz,
int constraintSz);
#endif
#if !defined(WOLFCRYPT_ONLY) && !defined(NO_CERTS)
WOLFSSL_TEST_VIS int wolfssl_local_IsValidFQDN(const char* name,
word32 nameSz);
#endif
#if ((defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_IMPORT)) \
|| (defined(HAVE_CURVE25519) && defined(HAVE_CURVE25519_KEY_IMPORT)) \
|| (defined(HAVE_ED448) && defined(HAVE_ED448_KEY_IMPORT)) \