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
32 changes: 32 additions & 0 deletions certs/test/gen-ext-certs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,38 @@ EOF
gen_cert
rm -f ./certs/test/cert-ext-ncdns.cfg

# excluded;DNS: with a zero-length base, the CA/Browser Forum encoding for a
# CA that may not issue DNS names. The OpenSSL config syntax cannot express an
# empty dNSName, so the extension is given as DER.
OUT=certs/test/cert-ext-nc-empty-dns
KEYFILE=certs/test/cert-ext-nc-empty-dns-key.der
CONFIG=certs/test/cert-ext-nc-empty-dns.cfg
tee >$CONFIG <<EOF
[ req ]
distinguished_name = req_distinguished_name
prompt = no
x509_extensions = v3_ca

[ req_distinguished_name ]
C = AU
ST = Queensland
L = Brisbane
O = wolfSSL Inc
OU = Engineering
CN = www.wolfssl.com

[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
nameConstraints = critical,DER:30:06:A1:04:30:02:82:00
nsComment = "Testing empty excluded dNSName constraint"

EOF
gen_cert
rm -f ./certs/test/cert-ext-nc-empty-dns.cfg

OUT=certs/test/cert-ext-nc-combined
KEYFILE=certs/test/cert-ext-nc-combined-key.der
CONFIG=certs/test/cert-ext-nc-combined.cfg
Expand Down
2 changes: 2 additions & 0 deletions certs/test/include.am
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ EXTRA_DIST += \
certs/test/cert-ext-ncrid.pem \
certs/test/cert-ext-ncdns.der \
certs/test/cert-ext-ncdns.pem \
certs/test/cert-ext-nc-empty-dns.der \
certs/test/cert-ext-nc-empty-dns.pem \
certs/test/cert-ext-ncmulti.der \
certs/test/cert-ext-ncmulti.pem \
certs/test/cert-ext-ncmixed.der \
Expand Down
14 changes: 11 additions & 3 deletions src/x509.c
Original file line number Diff line number Diff line change
Expand Up @@ -6070,7 +6070,8 @@ static int MatchIpName(const char* name, int nameSz, WOLFSSL_GENERAL_NAME* gn)
/* Helper to check if name string matches a single GENERAL_NAME constraint.
* permitted selects subtree semantics for wildcard DNS names: containment
* for permitted subtrees, intersection for excluded subtrees.
* Returns 1 if matches, 0 if not. */
* Returns 1 if matches, 0 if not, and a negative error if the name cannot be
* compared, which the caller must treat as failing the constraint. */
static int MatchNameConstraint(int type, const char* name, int nameSz,
WOLFSSL_GENERAL_NAME* gn, int permitted)
{
Expand All @@ -6093,6 +6094,11 @@ static int MatchNameConstraint(int type, const char* name, int nameSz,
}
baseStr = (const char*)wolfSSL_ASN1_STRING_get0_data(gn->d.ia5);
baseLen = wolfSSL_ASN1_STRING_length(gn->d.ia5);
if (type == WOLFSSL_GEN_DNS && baseLen == 0) {
/* RFC 5280 4.2.1.10: an empty dNSName base covers every DNS
* name. */
return 1;
}
if (baseStr == NULL || baseLen <= 0) {
return 0;
}
Expand Down Expand Up @@ -6194,7 +6200,8 @@ int wolfSSL_NAME_CONSTRAINTS_check_name(WOLFSSL_NAME_CONSTRAINTS* nc,
}
hasPermittedType = 1;

if (MatchNameConstraint(type, name, nameSz, gn, 1)) {
/* A name that cannot be compared is not shown to be permitted. */
if (MatchNameConstraint(type, name, nameSz, gn, 1) == 1) {
matchedPermitted = 1;
break;
}
Expand All @@ -6221,7 +6228,8 @@ int wolfSSL_NAME_CONSTRAINTS_check_name(WOLFSSL_NAME_CONSTRAINTS* nc,
continue;
}

if (MatchNameConstraint(type, name, nameSz, gn, 0)) {
/* A name that cannot be compared is not shown to be outside. */
if (MatchNameConstraint(type, name, nameSz, gn, 0) != 0) {
WOLFSSL_MSG("Name in excluded subtrees");
return 0;
}
Expand Down
85 changes: 71 additions & 14 deletions tests/api/test_asn.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@
#include <wolfssl/wolfcrypt/asn_public.h>
#include <wolfssl/wolfcrypt/random.h>
#include <wolfssl/wolfcrypt/rsa.h>

/* A lone "." dNSName constraint base covers every DNS name, except with
* OPENSSL_COMPATIBLE_DEFAULTS where it matches no name. */
#ifdef OPENSSL_COMPATIBLE_DEFAULTS
#define NC_DNS_DOT_BASE_MATCH 0
#else
#define NC_DNS_DOT_BASE_MATCH 1
#endif
#ifdef HAVE_ED25519
#include <wolfssl/wolfcrypt/ed25519.h>
#endif
Expand Down Expand Up @@ -1048,21 +1056,52 @@ int test_wolfssl_local_MatchBaseName(void)
/* user@sub.domain.com should NOT match domain.com (exact domain only) */
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"user@sub.domain.com", 19, "domain.com", 10), 0);
/* @ at start is invalid */
/* A mailbox without a local-part or host-part cannot be compared */
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"@domain.com", 11, ".domain.com", 11),
WC_NO_ERR_TRACE(ASN_PARSE_E));
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"user@", 5, ".domain.com", 11),
WC_NO_ERR_TRACE(ASN_PARSE_E));
/* Validated before the name-shorter-than-base return */
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"u@", 2, "evil.com", 8), WC_NO_ERR_TRACE(ASN_PARSE_E));
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"x@evil.com@", 11, "evil.com", 8),
WC_NO_ERR_TRACE(ASN_PARSE_E));
/* A host-part starting with '.' cannot be compared */
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"x@.evil.com", 11, "evil.com", 8),
WC_NO_ERR_TRACE(ASN_PARSE_E));
/* A local-part starting with '.' is judged by its host-part */
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
".x@evil.com", 11, "evil.com", 8), 1);
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"@domain.com", 11, ".domain.com", 11), 0);
/* @ at end is invalid */
".x@good.com", 11, "evil.com", 8), 0);
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"user@", 5, ".domain.com", 11), 0);
/* double @ is invalid */
".x@sub.evil.com", 15, ".evil.com", 9), 1);
/* An '@' outside a quoted local-part cannot be compared */
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"user@@domain.com", 16, ".domain.com", 11), 0);
/* multiple @ is invalid */
"user@@domain.com", 16, ".domain.com", 11),
WC_NO_ERR_TRACE(ASN_PARSE_E));
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"user@domain@extra.com", 21, ".domain.com", 11),
WC_NO_ERR_TRACE(ASN_PARSE_E));
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"user@domain@extra.com", 21, "extra.com", 9),
WC_NO_ERR_TRACE(ASN_PARSE_E));
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"x@evil.com@good.com", 19, "good.com", 8),
WC_NO_ERR_TRACE(ASN_PARSE_E));
/* RFC 5322 quoted local-part containing @ */
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"user@domain@extra.com", 21, ".domain.com", 11), 0);
"\"x@y\"@evil.com", 14, "evil.com", 8), 1);
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"\"x@y\"@good.com", 14, "evil.com", 8), 0);
/* No @ in email name */
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"userdomain.com", 14, ".domain.com", 11), 0);
"userdomain.com", 14, ".domain.com", 11),
WC_NO_ERR_TRACE(ASN_PARSE_E));
/* Email domain doesn't match constraint */
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"user@other.com", 14, ".domain.com", 11), 0);
Expand Down Expand Up @@ -1725,14 +1764,26 @@ int test_wolfssl_local_MatchBaseName(void)
/* Empty/zero size */
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE,
"", 0, "domain.com", 10), 0);
/* RFC 5280 4.2.1.10: an empty dNSName base covers every DNS name. */
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE,
"domain.com", 10, "", 0), 1);
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE,
"domain.com", 10, "", 0), 0);
"domain.com", 10, ".", 1), NC_DNS_DOT_BASE_MATCH);
ExpectIntEQ(wolfssl_local_MatchDnsConstraintWildcard(
"*.domain.com", 12, "", 0, 0), 1);
ExpectIntEQ(wolfssl_local_MatchDnsConstraintWildcard(
"*.domain.com", 12, ".", 1, 1), NC_DNS_DOT_BASE_MATCH);
/* An empty rfc822Name base names no host. */
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_RFC822_TYPE,
"user@domain.com", 15, "", 0), 0);
/* Invalid type */
ExpectIntEQ(wolfssl_local_MatchBaseName(0xFF,
"domain.com", 10, "domain.com", 10), 0);
/* Name starting with dot */
/* Name starting with dot, whatever the base */
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE,
".", 1, ".", 1), 0);
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE,
".domain.com", 11, "", 0), 0);
/* Name shorter than base */
ExpectIntEQ(wolfssl_local_MatchBaseName(ASN_DNS_TYPE,
"a.com", 5, "domain.com", 10), 0);
Expand Down Expand Up @@ -1858,13 +1909,16 @@ int test_wolfssl_local_MatchDnsConstraintWildcard(void)
NULL, 3, 1), 0);
ExpectIntEQ(wolfssl_local_MatchDnsConstraintWildcard("*.com", 0,
"com", 3, 1), 0);
/* A zero-length base covers every DNS name (RFC 5280 4.2.1.10). */
ExpectIntEQ(wolfssl_local_MatchDnsConstraintWildcard("*.com", 5,
"com", 0, 1), 0);
"com", 0, 1), 1);
/* Name beginning with a dot is invalid. */
ExpectIntEQ(dnsWildPermitted(".x.com", "com"), 0);
ExpectIntEQ(dnsWildExcluded(".x.com", "com"), 0);
/* Base that is only dots collapses to nothing. */
ExpectIntEQ(dnsWildExcluded("*.example.com", "."), 0);
/* A lone dot is the empty base unless OpenSSL compatible. */
ExpectIntEQ(dnsWildExcluded("*.example.com", "."),
NC_DNS_DOT_BASE_MATCH);
/* A base of more than one dot has an empty label. */
ExpectIntEQ(dnsWildExcluded("*.example.com", ".."), 0);
/* SAN has an empty interior label ("*..com"), but only the right-most
* "com" label overlaps the base "com" -- the empty label sits outside the
Expand Down Expand Up @@ -1892,6 +1946,9 @@ int test_wolfssl_local_MatchUriNameConstraint(void)
* Exact host match (no leading dot in the constraint).
*/
ExpectIntEQ(uriNC("https://host.com/path", "host.com"), 1);
/* A lone "." base is a leading-dot base with no labels. */
ExpectIntEQ(uriNC("https://host.com/path", "."),
NC_DNS_DOT_BASE_MATCH);
ExpectIntEQ(uriNC("https://host.com", "host.com"), 1);
ExpectIntEQ(uriNC("https://host.com:8443/x", "host.com"), 1);
ExpectIntEQ(uriNC("ftp://user@host.com/x", "host.com"), 1);
Expand Down
128 changes: 128 additions & 0 deletions tests/api/test_certman.c
Original file line number Diff line number Diff line change
Expand Up @@ -1644,9 +1644,15 @@ int test_wolfSSL_CertManagerNameConstraint_DNS_CN(void)
* constraint, so these names are themselves unconstrained. */
static const byte rid_dummy[] = { 0x2A, 0x03, 0x04 }; /* OID 1.2.3.4 */
static const byte ip_dummy[] = { 203, 0, 113, 7 };
/* CNs that hostname verification can match although they hold no '.'
* or are IP literals; each is outside the permitted dNSName subtrees. */
static const char* const hostCns[] = {
"localhost", "intranet", "2001:db8::1", "*"
};

byte *der = NULL;
int derSz;
int i;
byte *pt;
WOLFSSL_X509 *x509 = NULL;
WOLFSSL_X509 *ca = NULL;
Expand Down Expand Up @@ -1739,6 +1745,36 @@ int test_wolfSSL_CertManagerNameConstraint_DNS_CN(void)
wolfSSL_X509_free(x509);
x509 = NULL;

/* Negative cases: leaf with no SAN and a single-label, IPv6 or lone
* wildcard CN. Each stays subject to the DNS constraints. */
for (i = 0; i < (int)(sizeof(hostCns) / sizeof(hostCns[0])); i++) {
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, (const byte*)hostCns[i],
(int)XSTRLEN(hostCns[i]), -1, 0), SSL_SUCCESS);
ExpectIntEQ(wolfSSL_X509_set_subject_name(x509, name),
WOLFSSL_SUCCESS);
X509_NAME_free(name);
name = NULL;

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;
}

/* Positive case: leaf with an in-scope CN and no SAN. */
ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(server_cert,
WOLFSSL_FILETYPE_PEM));
Expand Down Expand Up @@ -1844,6 +1880,98 @@ int test_wolfSSL_CertManagerNameConstraint_DNS_CN(void)
return EXPECT_RESULT();
}

int test_wolfSSL_CertManagerNameConstraint_EmptyDNS(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) && !defined(IGNORE_NAME_CONSTRAINTS)
/* CA (cert-ext-nc-empty-dns.der) has excluded;DNS: with a zero-length
* base, which covers every DNS name (RFC 5280 4.2.1.10). */
WOLFSSL_CERT_MANAGER* cm = NULL;
WOLFSSL_EVP_PKEY *priv = NULL;
WOLFSSL_X509_NAME* name = NULL;
const char* ca_cert = "./certs/test/cert-ext-nc-empty-dns.der";
const char* server_cert = "./certs/test/server-goodcn.pem";
/* Leaf subject CN, SAN to add (NULL for none), SAN type and result. */
static const struct {
const char* cn;
const char* san;
int sanType;
int expect;
} cases[] = {
{ "John Doe", "user@wolfssl.com", ASN_RFC822_TYPE,
WOLFSSL_SUCCESS },
{ "John Doe", NULL, 0,
WOLFSSL_SUCCESS },
{ "John Doe", "www.wolfssl.com", ASN_DNS_TYPE,
WC_NO_ERR_TRACE(ASN_NAME_INVALID_E) },
{ "www.wolfssl.com", NULL, 0,
WC_NO_ERR_TRACE(ASN_NAME_INVALID_E) },
{ "localhost", NULL, 0,
WC_NO_ERR_TRACE(ASN_NAME_INVALID_E) },
{ "2001:db8::1", NULL, 0,
WC_NO_ERR_TRACE(ASN_NAME_INVALID_E) }
};
byte *der = NULL;
int derSz;
int i;
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));

/* The CA itself must load in both ASN decoders. */
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);

for (i = 0; i < (int)(sizeof(cases) / sizeof(cases[0])); i++) {
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, (const byte*)cases[i].cn,
(int)XSTRLEN(cases[i].cn), -1, 0), SSL_SUCCESS);
ExpectIntEQ(wolfSSL_X509_set_subject_name(x509, name),
WOLFSSL_SUCCESS);
X509_NAME_free(name);
name = NULL;

if (cases[i].san != NULL) {
ExpectIntEQ(wolfSSL_X509_add_altname(x509, cases[i].san,
cases[i].sanType), 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), cases[i].expect);
wolfSSL_X509_free(x509);
x509 = NULL;
}

wolfSSL_CertManagerFree(cm);
wolfSSL_X509_free(ca);
wolfSSL_EVP_PKEY_free(priv);
#endif
return EXPECT_RESULT();
}

int test_wolfSSL_CertManagerNameConstraint_IP_SAN(void)
{
EXPECT_DECLS;
Expand Down
2 changes: 2 additions & 0 deletions tests/api/test_certman.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ 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_CertManagerNameConstraint_EmptyDNS(void);
int test_wolfSSL_CertManagerNameConstraint_IP_SAN(void);
int test_wolfSSL_CertManagerNameConstraint_RID_SAN(void);
int test_wolfSSL_X509_get_ext_d2i_RID_SAN(void);
Expand Down Expand Up @@ -82,6 +83,7 @@ int test_wolfSSL_trust_peer_cert_dates(void);
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_CertManagerNameConstraint_EmptyDNS), \
TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint_IP_SAN), \
TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerNameConstraint_RID_SAN), \
TEST_DECL_GROUP("certman", test_wolfSSL_X509_get_ext_d2i_RID_SAN), \
Expand Down
Loading
Loading