mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-05 15:50:51 +02:00
Merge pull request #10183 from douzzer/20260409-IsValidFQDN
20260409-IsValidFQDN
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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), \
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)) \
|
||||
|
||||
Reference in New Issue
Block a user