@@ -1705,6 +1705,200 @@ int test_wolfSSL_CertManagerNameConstraint_DNS_CN(void)
17051705 return EXPECT_RESULT ();
17061706}
17071707
1708+ int test_wolfSSL_CertManagerNameConstraint_IP_SAN (void )
1709+ {
1710+ EXPECT_DECLS ;
1711+ #if !defined(NO_FILESYSTEM ) && !defined(NO_CERTS ) && \
1712+ !defined(NO_WOLFSSL_CM_VERIFY ) && !defined(NO_RSA ) && \
1713+ defined(OPENSSL_EXTRA ) && defined(WOLFSSL_CERT_GEN ) && \
1714+ defined(WOLFSSL_CERT_EXT ) && defined(WOLFSSL_ALT_NAMES ) && \
1715+ !defined(NO_SHA256 ) && !defined(IGNORE_NAME_CONSTRAINTS )
1716+ /* Regression test for TALOS-2026-2409 (CVE-2026-28739).
1717+ *
1718+ * The CA at cert-ext-ncip.der declares a permittedSubtrees iPAddress
1719+ * constraint of 192.168.1.0/255.255.255.0. A leaf with an iPAddress
1720+ * SAN outside that subnet must be rejected. Prior to the fix, default
1721+ * builds (without WOLFSSL_IP_ALT_NAME) silently skipped iPAddress SANs
1722+ * during parsing, so the constraint loop saw no IP entries and the
1723+ * leaf was accepted.
1724+ *
1725+ * The bypass only existed when WOLFSSL_IP_ALT_NAME was undefined (the
1726+ * default). To exercise the regression target, this test must run in a
1727+ * configuration without --enable-ip-alt-name and without
1728+ * --enable-opensslall (which implies WOLFSSL_IP_ALT_NAME via
1729+ * settings.h). With WOLFSSL_IP_ALT_NAME defined the same assertions
1730+ * still hold, but the negative case there is enforcement of an
1731+ * already-working path rather than the regression itself.
1732+ *
1733+ * Scope: this test exercises the permittedSubtrees code path. The
1734+ * excludedSubtrees path uses the same parsing plumbing
1735+ * (DecodeGeneralName -> SetDNSEntry into cert->altNames) and the same
1736+ * ConfirmNameConstraints walk; the TALOS bug was strictly about
1737+ * iPAddress entries being absent from cert->altNames, so once that is
1738+ * fixed both directions are restored. The pre-existing
1739+ * test_wolfSSL_NAME_CONSTRAINTS_excluded test exercises the excluded
1740+ * direction more broadly. */
1741+ WOLFSSL_CERT_MANAGER * cm = NULL ;
1742+ WOLFSSL_EVP_PKEY * priv = NULL ;
1743+ WOLFSSL_X509_NAME * name = NULL ;
1744+ const char * ca_cert = "./certs/test/cert-ext-ncip.der" ;
1745+ const char * server_cert = "./certs/test/server-goodcn.pem" ;
1746+ /* Raw IPv4 bytes for SAN values (not dotted-quad strings). */
1747+ static const byte ip_inside [] = { 192 , 168 , 1 , 10 }; /* permitted */
1748+ static const byte ip_outside [] = { 10 , 0 , 0 , 1 }; /* violates */
1749+
1750+ byte * der = NULL ;
1751+ int derSz ;
1752+ byte * pt ;
1753+ WOLFSSL_X509 * x509 = NULL ;
1754+ WOLFSSL_X509 * ca = NULL ;
1755+
1756+ pt = (byte * )server_key_der_2048 ;
1757+ ExpectNotNull (priv = wolfSSL_d2i_PrivateKey (EVP_PKEY_RSA , NULL ,
1758+ (const unsigned char * * )& pt , sizeof_server_key_der_2048 ));
1759+
1760+ ExpectNotNull (cm = wolfSSL_CertManagerNew ());
1761+ ExpectNotNull (ca = wolfSSL_X509_load_certificate_file (ca_cert ,
1762+ WOLFSSL_FILETYPE_ASN1 ));
1763+ ExpectNotNull ((der = (byte * )wolfSSL_X509_get_der (ca , & derSz )));
1764+ ExpectIntEQ (wolfSSL_CertManagerLoadCABuffer (cm , der , derSz ,
1765+ WOLFSSL_FILETYPE_ASN1 ), WOLFSSL_SUCCESS );
1766+
1767+ /* Negative case: leaf with IP SAN outside permitted subnet. Must be
1768+ * rejected with ASN_NAME_INVALID_E. */
1769+ ExpectNotNull (x509 = wolfSSL_X509_load_certificate_file (server_cert ,
1770+ WOLFSSL_FILETYPE_PEM ));
1771+ ExpectNotNull (name = wolfSSL_X509_get_subject_name (ca ));
1772+ ExpectIntEQ (wolfSSL_X509_set_issuer_name (x509 , name ), WOLFSSL_SUCCESS );
1773+ name = NULL ;
1774+
1775+ /* Use add_altname_ex with raw IP bytes so the test runs in default
1776+ * builds where add_altname (string form) requires WOLFSSL_IP_ALT_NAME. */
1777+ ExpectIntEQ (wolfSSL_X509_add_altname_ex (x509 , (const char * )ip_outside ,
1778+ sizeof (ip_outside ), ASN_IP_TYPE ), WOLFSSL_SUCCESS );
1779+ ExpectIntGT (wolfSSL_X509_sign (x509 , priv , EVP_sha256 ()), 0 );
1780+ ExpectNotNull ((der = (byte * )wolfSSL_X509_get_der (x509 , & derSz )));
1781+ ExpectIntEQ (wolfSSL_CertManagerVerifyBuffer (cm , der , derSz ,
1782+ WOLFSSL_FILETYPE_ASN1 ), WC_NO_ERR_TRACE (ASN_NAME_INVALID_E ));
1783+ wolfSSL_X509_free (x509 );
1784+ x509 = NULL ;
1785+
1786+ /* Positive case: leaf with IP SAN inside the permitted subnet must be
1787+ * accepted. Confirms the fix does not over-reject. */
1788+ ExpectNotNull (x509 = wolfSSL_X509_load_certificate_file (server_cert ,
1789+ WOLFSSL_FILETYPE_PEM ));
1790+ ExpectNotNull (name = wolfSSL_X509_get_subject_name (ca ));
1791+ ExpectIntEQ (wolfSSL_X509_set_issuer_name (x509 , name ), WOLFSSL_SUCCESS );
1792+ name = NULL ;
1793+
1794+ ExpectIntEQ (wolfSSL_X509_add_altname_ex (x509 , (const char * )ip_inside ,
1795+ sizeof (ip_inside ), ASN_IP_TYPE ), WOLFSSL_SUCCESS );
1796+ ExpectIntGT (wolfSSL_X509_sign (x509 , priv , EVP_sha256 ()), 0 );
1797+ ExpectNotNull ((der = (byte * )wolfSSL_X509_get_der (x509 , & derSz )));
1798+ ExpectIntEQ (wolfSSL_CertManagerVerifyBuffer (cm , der , derSz ,
1799+ WOLFSSL_FILETYPE_ASN1 ), WOLFSSL_SUCCESS );
1800+
1801+ wolfSSL_CertManagerFree (cm );
1802+ wolfSSL_X509_free (x509 );
1803+ wolfSSL_X509_free (ca );
1804+ wolfSSL_EVP_PKEY_free (priv );
1805+ #endif
1806+ return EXPECT_RESULT ();
1807+ }
1808+
1809+ int test_wolfSSL_X509_check_host_IP_only_SAN_CN_fallback (void )
1810+ {
1811+ EXPECT_DECLS ;
1812+ #if !defined(NO_FILESYSTEM ) && !defined(NO_CERTS ) && !defined(NO_RSA ) && \
1813+ defined(OPENSSL_EXTRA ) && defined(WOLFSSL_CERT_GEN ) && \
1814+ defined(WOLFSSL_CERT_EXT ) && defined(WOLFSSL_ALT_NAMES ) && \
1815+ !defined(NO_SHA256 )
1816+ /* Companion regression test for the CheckForAltNames CN-fallback
1817+ * preservation introduced alongside TALOS-2026-2409.
1818+ *
1819+ * Once iPAddress SAN entries are unconditionally added to altNames
1820+ * (so name constraints can be enforced), a leaf that presents only
1821+ * iPAddress SANs would suppress CN fallback in CheckForAltNames in
1822+ * default builds, where the iPAddress matching path is compiled out.
1823+ * That would silently break TLS hostname verification for callers
1824+ * that previously relied on the CN fallback. The fix in
1825+ * src/internal.c treats iPAddress entries as absent for the
1826+ * *checkCN decision when WOLFSSL_IP_ALT_NAME is undefined.
1827+ *
1828+ * This test pins both directions:
1829+ * - default build (no WOLFSSL_IP_ALT_NAME): IP-only-SAN cert with a
1830+ * matching CN must succeed via CN fallback.
1831+ * - WOLFSSL_IP_ALT_NAME defined: the same cert must fail because
1832+ * the SAN presence suppresses CN fallback (RFC 6125 compliant).
1833+ * Independently, a cert with a non-matching DNS SAN must always fail
1834+ * regardless of build flags, since DNS SAN presence unambiguously
1835+ * suppresses CN fallback. */
1836+ WOLFSSL_EVP_PKEY * priv = NULL ;
1837+ WOLFSSL_X509_NAME * name = NULL ;
1838+ const char * server_cert = "./certs/test/server-goodcn.pem" ;
1839+ const char hostName [] = "cnhost.local" ;
1840+ static const byte ip_san [] = { 10 , 0 , 0 , 1 };
1841+ byte * pt ;
1842+ WOLFSSL_X509 * leafIp = NULL ;
1843+ WOLFSSL_X509 * leafDns = NULL ;
1844+
1845+ pt = (byte * )server_key_der_2048 ;
1846+ ExpectNotNull (priv = wolfSSL_d2i_PrivateKey (EVP_PKEY_RSA , NULL ,
1847+ (const unsigned char * * )& pt , sizeof_server_key_der_2048 ));
1848+
1849+ /* Leaf with CN matching hostName and only an iPAddress SAN. */
1850+ ExpectNotNull (leafIp = wolfSSL_X509_load_certificate_file (server_cert ,
1851+ WOLFSSL_FILETYPE_PEM ));
1852+ ExpectNotNull (name = X509_NAME_new ());
1853+ ExpectIntEQ (X509_NAME_add_entry_by_txt (name , "commonName" , MBSTRING_UTF8 ,
1854+ (byte * )hostName , (int )XSTRLEN (hostName ), -1 , 0 ), SSL_SUCCESS );
1855+ ExpectIntEQ (wolfSSL_X509_set_subject_name (leafIp , name ), WOLFSSL_SUCCESS );
1856+ X509_NAME_free (name );
1857+ name = NULL ;
1858+ ExpectIntEQ (wolfSSL_X509_add_altname_ex (leafIp , (const char * )ip_san ,
1859+ sizeof (ip_san ), ASN_IP_TYPE ), WOLFSSL_SUCCESS );
1860+ ExpectIntGT (wolfSSL_X509_sign (leafIp , priv , EVP_sha256 ()), 0 );
1861+
1862+ #ifndef WOLFSSL_IP_ALT_NAME
1863+ /* Default build: iPAddress entries are present in altNames for
1864+ * constraint enforcement but treated as absent for *checkCN, so the
1865+ * lookup falls back to the Subject CN, which matches. */
1866+ ExpectIntEQ (wolfSSL_X509_check_host (leafIp , hostName , XSTRLEN (hostName ),
1867+ 0 , NULL ), WOLFSSL_SUCCESS );
1868+ #else
1869+ /* IP_ALT_NAME build: SAN presence suppresses CN fallback per RFC 6125.
1870+ * The hostName ("cnhost.local") cannot match the iPAddress entry, so
1871+ * the check must fail. */
1872+ ExpectIntEQ (wolfSSL_X509_check_host (leafIp , hostName , XSTRLEN (hostName ),
1873+ 0 , NULL ), WC_NO_ERR_TRACE (WOLFSSL_FAILURE ));
1874+ #endif
1875+
1876+ /* Leaf with CN matching hostName but a non-matching DNS SAN. CN
1877+ * fallback must be suppressed in every build (DNS SAN unambiguously
1878+ * counts toward *checkCN), so the check must fail. This pins the
1879+ * other side of the boundary so a future change that broadly skips
1880+ * altNames in *checkCN does not silently regress. */
1881+ ExpectNotNull (leafDns = wolfSSL_X509_load_certificate_file (server_cert ,
1882+ WOLFSSL_FILETYPE_PEM ));
1883+ ExpectNotNull (name = X509_NAME_new ());
1884+ ExpectIntEQ (X509_NAME_add_entry_by_txt (name , "commonName" , MBSTRING_UTF8 ,
1885+ (byte * )hostName , (int )XSTRLEN (hostName ), -1 , 0 ), SSL_SUCCESS );
1886+ ExpectIntEQ (wolfSSL_X509_set_subject_name (leafDns , name ), WOLFSSL_SUCCESS );
1887+ X509_NAME_free (name );
1888+ name = NULL ;
1889+ ExpectIntEQ (wolfSSL_X509_add_altname (leafDns , "other.example" ,
1890+ ASN_DNS_TYPE ), WOLFSSL_SUCCESS );
1891+ ExpectIntGT (wolfSSL_X509_sign (leafDns , priv , EVP_sha256 ()), 0 );
1892+ ExpectIntEQ (wolfSSL_X509_check_host (leafDns , hostName , XSTRLEN (hostName ),
1893+ 0 , NULL ), WC_NO_ERR_TRACE (WOLFSSL_FAILURE ));
1894+
1895+ wolfSSL_X509_free (leafIp );
1896+ wolfSSL_X509_free (leafDns );
1897+ wolfSSL_EVP_PKEY_free (priv );
1898+ #endif
1899+ return EXPECT_RESULT ();
1900+ }
1901+
17081902int test_wolfSSL_CertManagerCRL (void )
17091903{
17101904 EXPECT_DECLS ;
0 commit comments