Skip to content
Open
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
16 changes: 16 additions & 0 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -13334,6 +13334,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);
Expand Down
97 changes: 97 additions & 0 deletions tests/api/test_ossl_x509.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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) */
{"[email protected]", 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;
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 @@ -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);
Expand Down Expand Up @@ -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), \
Expand Down
75 changes: 75 additions & 0 deletions wolfcrypt/src/asn.c
Original file line number Diff line number Diff line change
Expand Up @@ -17820,6 +17820,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.
Expand Down
5 changes: 5 additions & 0 deletions wolfssl/wolfcrypt/asn.h
Original file line number Diff line number Diff line change
Expand Up @@ -3119,6 +3119,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)) \
Expand Down
Loading