diff --git a/tests/api/test_certman.c b/tests/api/test_certman.c index dfce1233417..9cb129f09e8 100644 --- a/tests/api/test_certman.c +++ b/tests/api/test_certman.c @@ -1584,6 +1584,127 @@ int test_wolfSSL_CertManagerNameConstraint5(void) return EXPECT_RESULT(); } +int test_wolfSSL_CertManagerNameConstraint_DNS_CN(void) +{ + EXPECT_DECLS; +#if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \ + !defined(NO_WOLFSSL_CM_VERIFY) && !defined(NO_RSA) && \ + defined(OPENSSL_EXTRA) && defined(WOLFSSL_CERT_GEN) && \ + defined(WOLFSSL_CERT_EXT) && defined(WOLFSSL_ALT_NAMES) && \ + !defined(NO_SHA256) + /* Test that DNS name constraints are enforced against the Subject CN + * when no SAN extension is present. The CA cert (cert-ext-ncdns.der) + * permits only DNS:wolfssl.com and DNS:example.com. A leaf cert with + * CN=evil.attacker.com and no SAN should be REJECTED. */ + WOLFSSL_CERT_MANAGER* cm = NULL; + WOLFSSL_EVP_PKEY *priv = NULL; + WOLFSSL_X509_NAME* name = NULL; + const char* ca_cert = "./certs/test/cert-ext-ncdns.der"; + const char* server_cert = "./certs/test/server-goodcn.pem"; + + byte *der = NULL; + int derSz; + byte *pt; + WOLFSSL_X509 *x509 = NULL; + WOLFSSL_X509 *ca = NULL; + + pt = (byte*)server_key_der_2048; + ExpectNotNull(priv = wolfSSL_d2i_PrivateKey(EVP_PKEY_RSA, NULL, + (const unsigned char**)&pt, sizeof_server_key_der_2048)); + + ExpectNotNull(cm = wolfSSL_CertManagerNew()); + ExpectNotNull(ca = wolfSSL_X509_load_certificate_file(ca_cert, + WOLFSSL_FILETYPE_ASN1)); + ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(ca, &derSz))); + ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, der, derSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + + /* Sanity check: cert with SAN=evil.attacker.com is correctly rejected */ + ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(server_cert, + WOLFSSL_FILETYPE_PEM)); + ExpectNotNull(name = wolfSSL_X509_get_subject_name(ca)); + ExpectIntEQ(wolfSSL_X509_set_issuer_name(x509, name), WOLFSSL_SUCCESS); + name = NULL; + + ExpectNotNull(name = X509_NAME_new()); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "countryName", MBSTRING_UTF8, + (byte*)"US", 2, -1, 0), SSL_SUCCESS); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "commonName", MBSTRING_UTF8, + (byte*)"evil.attacker.com", 17, -1, 0), + SSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_subject_name(x509, name), WOLFSSL_SUCCESS); + X509_NAME_free(name); + name = NULL; + + ExpectIntEQ(wolfSSL_X509_add_altname(x509, "evil.attacker.com", + ASN_DNS_TYPE), WOLFSSL_SUCCESS); + ExpectIntGT(wolfSSL_X509_sign(x509, priv, EVP_sha256()), 0); + ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(x509, &derSz))); + ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz, + WOLFSSL_FILETYPE_ASN1), WC_NO_ERR_TRACE(ASN_NAME_INVALID_E)); + wolfSSL_X509_free(x509); + x509 = NULL; + + /* NOW the actual vulnerability test: cert with CN=evil.attacker.com + * but NO SAN. The DNS name constraint should still reject this, since + * wolfSSL's hostname verification falls back to CN when no SAN exists. */ + ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(server_cert, + WOLFSSL_FILETYPE_PEM)); + ExpectNotNull(name = wolfSSL_X509_get_subject_name(ca)); + ExpectIntEQ(wolfSSL_X509_set_issuer_name(x509, name), WOLFSSL_SUCCESS); + name = NULL; + + ExpectNotNull(name = X509_NAME_new()); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "countryName", MBSTRING_UTF8, + (byte*)"US", 2, -1, 0), SSL_SUCCESS); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "commonName", MBSTRING_UTF8, + (byte*)"evil.attacker.com", 17, -1, 0), + SSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_subject_name(x509, name), WOLFSSL_SUCCESS); + X509_NAME_free(name); + name = NULL; + + /* Do NOT add any SAN this is the bypass vector */ + ExpectIntGT(wolfSSL_X509_sign(x509, priv, EVP_sha256()), 0); + ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(x509, &derSz))); + /* Should be ASN_NAME_INVALID_E because CN violates the constraint */ + ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz, + WOLFSSL_FILETYPE_ASN1), WC_NO_ERR_TRACE(ASN_NAME_INVALID_E)); + wolfSSL_X509_free(x509); + x509 = NULL; + + /* Positive test: CN matches a permitted name (wolfssl.com) and no SAN is + * present. The CN fallback should accept this cert. */ + ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(server_cert, + WOLFSSL_FILETYPE_PEM)); + ExpectNotNull(name = wolfSSL_X509_get_subject_name(ca)); + ExpectIntEQ(wolfSSL_X509_set_issuer_name(x509, name), WOLFSSL_SUCCESS); + name = NULL; + + ExpectNotNull(name = X509_NAME_new()); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "countryName", MBSTRING_UTF8, + (byte*)"US", 2, -1, 0), SSL_SUCCESS); + ExpectIntEQ(X509_NAME_add_entry_by_txt(name, "commonName", MBSTRING_UTF8, + (byte*)"wolfssl.com", 11, -1, 0), + SSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_subject_name(x509, name), WOLFSSL_SUCCESS); + X509_NAME_free(name); + name = NULL; + + /* No SAN added; CN=wolfssl.com matches the permitted DNS constraint. */ + ExpectIntGT(wolfSSL_X509_sign(x509, priv, EVP_sha256()), 0); + ExpectNotNull((der = (byte*)wolfSSL_X509_get_der(x509, &derSz))); + ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, derSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + + wolfSSL_CertManagerFree(cm); + wolfSSL_X509_free(x509); + wolfSSL_X509_free(ca); + wolfSSL_EVP_PKEY_free(priv); +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_CertManagerCRL(void) { EXPECT_DECLS; diff --git a/tests/api/test_certman.h b/tests/api/test_certman.h index 1588c81eccf..45ab7aadcd5 100644 --- a/tests/api/test_certman.h +++ b/tests/api/test_certman.h @@ -35,6 +35,7 @@ int test_wolfSSL_CertManagerNameConstraint2(void); int test_wolfSSL_CertManagerNameConstraint3(void); int test_wolfSSL_CertManagerNameConstraint4(void); int test_wolfSSL_CertManagerNameConstraint5(void); +int test_wolfSSL_CertManagerNameConstraint_DNS_CN(void); int test_wolfSSL_CertManagerCRL(void); int test_wolfSSL_CRL_reason_extensions_cleanup(void); int test_wolfSSL_CRL_static_revoked_list(void); @@ -54,6 +55,7 @@ int test_various_pathlen_chains(void); TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint3), \ TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint4), \ TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint5), \ + TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint_DNS_CN), \ TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerCRL), \ TEST_DECL_GROUP("certman", test_wolfSSL_CRL_reason_extensions_cleanup), \ TEST_DECL_GROUP("certman", test_wolfSSL_CRL_static_revoked_list), \ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 3198b4de077..55ab6ba415d 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -17658,9 +17658,16 @@ static int ConfirmNameConstraints(Signer* signer, DecodedCert* cert) XMEMSET(&subjectDnsName, 0, sizeof(DNS_entry)); switch (nameType) { case ASN_DNS_TYPE: - /* Should it also consider CN in subject? It could use - * subjectDnsName too */ name = cert->altNames; + + /* When no SAN is present, apply DNS name constraints to the + * Subject CN. */ + if (cert->subjectCN != NULL && cert->altNames == NULL) { + subjectDnsName.next = NULL; + subjectDnsName.type = ASN_DNS_TYPE; + subjectDnsName.len = cert->subjectCNLen; + subjectDnsName.name = cert->subjectCN; + } break; case ASN_IP_TYPE: /* IP addresses are stored in altNames with type ASN_IP_TYPE */