@@ -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,183 @@ int test_wolfSSL_X509_name_match3(void)
16101618 return EXPECT_RESULT ();
16111619}
16121620
1621+ int test_wolfssl_local_IsValidFQDN (void ) {
1622+ EXPECT_DECLS ;
1623+ #if !defined(NO_ASN ) && !defined(WOLFCRYPT_ONLY ) && !defined(NO_CERTS )
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+ #endif /* !NO_ASN && !WOLFCRYPT_ONLY && !NO_CERTS */
1707+ return EXPECT_RESULT ();
1708+ }
1709+
1710+ /* Verify that MatchDomainName() refuses to expand wildcards across IDNA
1711+ * A-labels (xn-- prefix) per RFC 6125 sec. 6.4.3 / RFC 9525 sec. 6.3.
1712+ *
1713+ * MatchDomainName() is WOLFSSL_LOCAL but visible to the test binary because
1714+ * tests link against the in-tree library. */
1715+ int test_wolfSSL_MatchDomainName_idn (void )
1716+ {
1717+ EXPECT_DECLS ;
1718+ #if !defined(NO_CERTS )
1719+ static const struct {
1720+ const char * pattern ;
1721+ const char * host ;
1722+ unsigned int flags ;
1723+ int expected ; /* 1 = match, 0 = no match */
1724+ const char * note ;
1725+ } cases [] = {
1726+ /* Partial wildcard whose literal prefix overlaps "xn--" must NOT
1727+ * match an A-label hostname. */
1728+ { "x*.example.com" , "xn--rger-koa.example.com" , 0 , 0 ,
1729+ "partial wildcard vs A-label" },
1730+ /* Wildcard embedded inside an A-label pattern must NOT match. */
1731+ { "xn--*.example.com" , "xn--rger-koa.example.com" , 0 , 0 ,
1732+ "wildcard inside A-label pattern" },
1733+ /* Full left-most wildcard MUST NOT match an A-label hostname
1734+ * (RFC 9525 sec. 6.3 strengthens RFC 6125 SHOULD NOT to MUST NOT). */
1735+ { "*.example.com" , "xn--rger-koa.example.com" , 0 , 0 ,
1736+ "full wildcard vs A-label hostname" },
1737+ /* A-label appearing in an inner label still disables wildcard
1738+ * matching against the entire reference identifier. */
1739+ { "*.example.com" , "foo.xn--bar.example.com" , 0 , 0 ,
1740+ "wildcard with A-label in inner label" },
1741+ /* Case-insensitive A-label detection: "XN--" is also an A-label. */
1742+ { "x*.example.com" , "XN--rger-koa.example.com" , 0 , 0 ,
1743+ "uppercase A-label prefix" },
1744+ /* Control: full wildcard SHOULD continue to match plain ASCII. */
1745+ { "*.example.com" , "foo.example.com" , 0 , 1 ,
1746+ "wildcard matches non-IDN" },
1747+ /* Control: exact A-label match (no wildcard in pattern) must work. */
1748+ { "xn--rger-koa.example.com" , "xn--rger-koa.example.com" , 0 , 1 ,
1749+ "exact A-label match" },
1750+ /* Control: a label that merely begins with 'x' (not 'xn--') is not
1751+ * an A-label and must still wildcard-match. */
1752+ { "*.example.com" , "xyz.example.com" , 0 , 1 ,
1753+ "non-A-label x-prefix" },
1754+ /* Control: partial wildcard against a non-A-label still works. */
1755+ { "x*.example.com" , "xyz.example.com" , 0 , 1 ,
1756+ "partial wildcard non-IDN" },
1757+
1758+ /* Trailing-dot normalization: absolute-form FQDN ("example.com.")
1759+ * must match the same FQDN with or without the trailing dot, on
1760+ * either side of the comparison. RFC 1035 / RFC 6125. */
1761+ { "example.com" , "example.com." , 0 , 1 ,
1762+ "trailing dot on host" },
1763+ { "example.com." , "example.com" , 0 , 1 ,
1764+ "trailing dot on pattern" },
1765+ { "example.com." , "example.com." , 0 , 1 ,
1766+ "trailing dot on both" },
1767+ { "*.example.com" , "foo.example.com." , 0 , 1 ,
1768+ "trailing dot on host with wildcard pattern" },
1769+ /* Trailing dot must not cause an A-label gate to misfire. */
1770+ { "*.example.com" , "xn--rger-koa.example.com." , 0 , 0 ,
1771+ "trailing dot on A-label host" },
1772+ /* Same trailing-dot normalization under WOLFSSL_LEFT_MOST_WILDCARD_ONLY. */
1773+ { "*.example.com" , "foo.example.com." ,
1774+ WOLFSSL_LEFT_MOST_WILDCARD_ONLY , 1 ,
1775+ "trailing dot, leftWildcardOnly" },
1776+ };
1777+ size_t i ;
1778+
1779+ for (i = 0 ; i < sizeof (cases ) / sizeof (cases [0 ]); i ++ ) {
1780+ int got = MatchDomainName (
1781+ cases [i ].pattern , (int )XSTRLEN (cases [i ].pattern ),
1782+ cases [i ].host , (word32 )XSTRLEN (cases [i ].host ),
1783+ cases [i ].flags );
1784+ ExpectIntEQ (got , cases [i ].expected );
1785+ if (! EXPECT_SUCCESS ()) {
1786+ fprintf (stderr ,
1787+ "MatchDomainName(\"%s\", \"%s\", flags=0x%x) = %d, "
1788+ "expected %d (%s)\n" ,
1789+ cases [i ].pattern , cases [i ].host , cases [i ].flags ,
1790+ got , cases [i ].expected , cases [i ].note );
1791+ break ;
1792+ }
1793+ }
1794+ #endif /* !NO_CERTS */
1795+ return EXPECT_RESULT ();
1796+ }
1797+
16131798int test_wolfSSL_X509_max_altnames (void )
16141799{
16151800 EXPECT_DECLS ;
0 commit comments