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
1 change: 1 addition & 0 deletions .wolfssl_known_macro_extras
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,7 @@ WOLFSSL_ALLOW_MAX_FRAGMENT_ADJUST
WOLFSSL_ALLOW_MD5_CERT_SIGS
WOLFSSL_ALLOW_NO_CN_IN_SAN
WOLFSSL_ALLOW_NO_SUITES
WOLFSSL_ALLOW_PARTIAL_WILDCARDS
WOLFSSL_ALLOW_SERVER_SC_EXT
WOLFSSL_ALLOW_SINGLE_LABEL_HOSTNAME
WOLFSSL_ALLOW_TLS_SHA1
Expand Down
65 changes: 51 additions & 14 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -15445,13 +15445,14 @@ static int PatternHasWildcardInALabel(const char* pattern, word32 patternLen)
* immediately to the left of a registry/public suffix), and
* "foo.*.example.com" (wildcard not in the left-most label), while still
* accepting the legitimate "*.example.com" form. Partial left-most wildcards
* such as "a*" or "a*b*" retain their existing matching behavior - they are
* not bare wildcard labels and are not subject to the two-label requirement.
* such as "a*" or "a*b*" are rejected under WOLFSSL_NO_PARTIAL_WILDCARDS, the
* default; otherwise they are not subject to the two-label requirement.
* pattern/patternLen must already have any single trailing FQDN dot stripped.
*
* Returns 1 if the pattern has no wildcard or its wildcard placement is
* acceptable, 0 otherwise. */
static int WildcardPlacementOK(const char* pattern, word32 patternLen)
static int WildcardPlacementOK(const char* pattern, word32 patternLen,
unsigned int flags)
{
word32 i;
int sawWildcard = 0;
Expand All @@ -15475,6 +15476,12 @@ static int WildcardPlacementOK(const char* pattern, word32 patternLen)
if (!sawWildcard)
return 1;

/* WOLFSSL_NO_PARTIAL_WILDCARDS: the wildcard must be the whole left-most
* label, as RFC 9525 sec. 6.3 requires. */
if ((flags & WOLFSSL_NO_PARTIAL_WILDCARDS) != 0 &&
!(pattern[0] == '*' && (patternLen == 1 || pattern[1] == '.')))
return 0;

/* A left-most label that is exactly "*" (a bare wildcard label) requires at
* least two further labels. This rejects a bare "*" (0 dots) and "*.tld"
* (1 dot) but still allows "*.example.com" (2 dots). */
Expand Down Expand Up @@ -15525,6 +15532,20 @@ int MatchDomainName(const char* pattern, int patternLen, const char* str,
if (pattern[patternLen-1] == '.')
--patternLen;

/* WOLFSSL_NO_WILDCARDS: compare case-insensitively, no '*' handling. */
if ((flags & WOLFSSL_NO_WILDCARDS) != 0) {
word32 i;

if ((word32)patternLen != strLen)
return 0;
for (i = 0; i < strLen; i++) {
if (XTOLOWER((unsigned char)pattern[i]) !=
XTOLOWER((unsigned char)str[i]))
return 0;
}
return 1;
}

/* RFC 6125 sec. 6.4.3 / RFC 9525 sec. 6.3: do not perform wildcard
* matching when the pattern has a wildcard embedded in an A-label, nor
* when the reference identifier (hostname) contains any A-label. The
Expand All @@ -15541,11 +15562,19 @@ int MatchDomainName(const char* pattern, int patternLen, const char* str,
}
}

#ifndef WOLFSSL_ALLOW_PARTIAL_WILDCARDS
/* RFC 9525 sec. 6.3 requires the wildcard to be the complete content of
* the left-most label. RFC 6125 sec. 6.4.3 rule 3, which it obsoletes,
* permitted a partial label such as "baz*.example.net"; define
* WOLFSSL_ALLOW_PARTIAL_WILDCARDS to keep matching those. */
flags |= WOLFSSL_NO_PARTIAL_WILDCARDS;
#endif

/* RFC 6125 sec. 6.4.3 / RFC 9525 sec. 6.3 + CA/Browser Forum BR
* sec. 3.2.2.6: reject a pattern whose wildcard is not confined to the
* left-most label, or that has fewer than two labels to the right of the
* wildcard (e.g. "*", "*.com", "foo.*.example.com"). */
if (!WildcardPlacementOK(pattern, (word32)patternLen))
if (!WildcardPlacementOK(pattern, (word32)patternLen, flags))
return 0;

while (patternLen > 0) {
Expand Down Expand Up @@ -19623,22 +19652,23 @@ static int RpkIsTrusted(WOLFSSL* ssl, const byte* spki, word32 spkiSz)
/* Match one configured name against the leaf certificate, using the same
* rules as the wolfSSL_check_domain_name() check in ProcessPeerCerts().
* Returns 0 on match, DOMAIN_NAME_MISMATCH otherwise. */
static int CheckPeerHostName(DecodedCert* dCert, const char* name)
static int CheckPeerHostName(DecodedCert* dCert, const char* name,
unsigned int flags)
{
word32 nameLen = (word32)XSTRLEN(name);

#ifndef WOLFSSL_ALLOW_NO_CN_IN_SAN
/* Per RFC 5280 section 4.2.1.6, subject alternative names take
* precedence over the subject common name. */
if (dCert->altNames != NULL) {
if (CheckForAltNames(dCert, name, nameLen, NULL, 0, 0) != 1) {
if (CheckForAltNames(dCert, name, nameLen, NULL, flags, 0) != 1) {
return DOMAIN_NAME_MISMATCH;
}
return 0;
}
#ifndef WOLFSSL_HOSTNAME_VERIFY_ALT_NAME_ONLY
if (MatchDomainName(dCert->subjectCN, dCert->subjectCNLen, name,
nameLen, 0) == 0)
nameLen, flags) == 0)
#endif
{
return DOMAIN_NAME_MISMATCH;
Expand All @@ -19647,10 +19677,10 @@ static int CheckPeerHostName(DecodedCert* dCert, const char* name)
#else /* WOLFSSL_ALLOW_NO_CN_IN_SAN */
#ifndef WOLFSSL_HOSTNAME_VERIFY_ALT_NAME_ONLY
if (MatchDomainName(dCert->subjectCN, dCert->subjectCNLen, name,
nameLen, 0) == 0)
nameLen, flags) == 0)
#endif
{
if (CheckForAltNames(dCert, name, nameLen, NULL, 0, 0) != 1) {
if (CheckForAltNames(dCert, name, nameLen, NULL, flags, 0) != 1) {
return DOMAIN_NAME_MISMATCH;
}
}
Expand Down Expand Up @@ -21195,6 +21225,13 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,

if (!ssl->options.verifyNone && !UsingChainVerifyCb(ssl) &&
domainName) {
#ifdef OPENSSL_EXTRA
/* Honor X509_VERIFY_PARAM_set_hostflags(). */
unsigned int hostFlags = (ssl->param != NULL) ?
Comment on lines +21230 to +21231
ssl->param->hostFlags : 0;
#else
unsigned int hostFlags = 0;
#endif
#ifndef WOLFSSL_ALLOW_NO_CN_IN_SAN
/* Per RFC 5280 section 4.2.1.6, "Whenever such identities
* are to be bound into a certificate, the subject
Expand All @@ -21204,7 +21241,7 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
args->dCert,
domainName,
(word32)XSTRLEN((const char *)domainName),
NULL, 0, 0) != 1) {
NULL, hostFlags, 0) != 1) {
WOLFSSL_MSG("DomainName match on alt names failed");
/* try to get peer key still */
ret = DOMAIN_NAME_MISMATCH;
Expand All @@ -21218,7 +21255,7 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
args->dCert->subjectCNLen,
domainName,
(word32)XSTRLEN((const char *)domainName),
0) == 0)
hostFlags) == 0)
#endif
{
WOLFSSL_MSG("DomainName match failed");
Expand All @@ -21234,14 +21271,14 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
args->dCert->subjectCNLen,
domainName,
(word32)XSTRLEN((const char *)domainName),
0) == 0)
hostFlags) == 0)
#endif
{
if (CheckForAltNames(
args->dCert,
domainName,
(word32)XSTRLEN((const char *)domainName),
NULL, 0, 0) != 1) {
NULL, hostFlags, 0) != 1) {
WOLFSSL_MSG("DomainName match failed");
/* try to get peer key still */
ret = DOMAIN_NAME_MISMATCH;
Expand All @@ -21260,7 +21297,7 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
(ssl->param != NULL) &&
(ssl->param->hostName[0] != '\0')) {
if (CheckPeerHostName(args->dCert,
ssl->param->hostName) != 0) {
ssl->param->hostName, ssl->param->hostFlags) != 0) {
WOLFSSL_MSG("DomainName match on verify param failed");
ret = DOMAIN_NAME_MISMATCH;
WOLFSSL_ERROR_VERBOSE(ret);
Expand Down
10 changes: 1 addition & 9 deletions src/x509.c
Original file line number Diff line number Diff line change
Expand Up @@ -17268,16 +17268,8 @@ int wolfSSL_X509_check_host(WOLFSSL_X509 *x, const char *chk, size_t chklen,
return WOLFSSL_FAILURE;
}

if (flags & WOLFSSL_NO_WILDCARDS) {
WOLFSSL_MSG("X509_CHECK_FLAG_NO_WILDCARDS not yet implemented");
return WOLFSSL_FAILURE;
}
if (flags & WOLFSSL_NO_PARTIAL_WILDCARDS) {
WOLFSSL_MSG("X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS not yet implemented");
return WOLFSSL_FAILURE;
}
if (flags & WOLFSSL_MULTI_LABEL_WILDCARDS) {
WOLFSSL_MSG("X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS not yet implemented");
WOLFSSL_MSG("X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS not yet implemented");
return WOLFSSL_FAILURE;
}

Expand Down
122 changes: 94 additions & 28 deletions tests/api/test_ossl_x509.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
#include <tests/api/api.h>
#include <tests/api/test_ossl_x509.h>

/* RFC 9525 sec. 6.3 rejects a wildcard that is not the complete left-most
* label; RFC 6125 sec. 6.4.3 rule 3 matched one. */
#ifdef WOLFSSL_ALLOW_PARTIAL_WILDCARDS
#define PARTIAL_WILDCARD_OK 1
#else
#define PARTIAL_WILDCARD_OK 0
#endif

int test_x509_get_key_id(void)
{
EXPECT_DECLS;
Expand Down Expand Up @@ -415,10 +423,13 @@ int test_wolfSSL_X509_check_host(void)
WOLFSSL_LEFT_MOST_WILDCARD_ONLY, NULL),
WC_NO_ERR_TRACE(WOLFSSL_FAILURE));

/* A SAN with no wildcard is unaffected by either wildcard restriction. */
ExpectIntEQ(wolfSSL_X509_check_host(x509, altName, XSTRLEN(altName),
WOLFSSL_NO_WILDCARDS, NULL), WC_NO_ERR_TRACE(WOLFSSL_FAILURE));
WOLFSSL_NO_WILDCARDS, NULL), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_X509_check_host(x509, altName, XSTRLEN(altName),
WOLFSSL_NO_PARTIAL_WILDCARDS, NULL), WC_NO_ERR_TRACE(WOLFSSL_FAILURE));
WOLFSSL_NO_PARTIAL_WILDCARDS, NULL), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_X509_check_host(x509, badAltName, XSTRLEN(badAltName),
WOLFSSL_NO_WILDCARDS, NULL), WC_NO_ERR_TRACE(WOLFSSL_FAILURE));
ExpectIntEQ(wolfSSL_X509_check_host(x509, altName, XSTRLEN(altName),
WOLFSSL_MULTI_LABEL_WILDCARDS, NULL), WC_NO_ERR_TRACE(WOLFSSL_FAILURE));

Expand Down Expand Up @@ -1555,27 +1566,30 @@ int test_wolfSSL_X509_name_match1(void)
const char *name5 = "aaaaa";
int nameLen5 = 6;
const char badName[] = { 'a', 'a', '\0', 'a', 'a', 'a' };
/* The cert's "a*" alt name is a partial-label wildcard. */
const int partialExp = PARTIAL_WILDCARD_OK ? WOLFSSL_SUCCESS
: WOLFSSL_FAILURE;

ExpectNotNull(x509 = wolfSSL_X509_load_certificate_buffer(
cert_der, certSize, WOLFSSL_FILETYPE_ASN1));

/* Ensure that "a*" matches "aaaaa" */
/* "a*" matches "aaaaa" only when RFC 6125 matching is restored */
ExpectIntEQ(wolfSSL_X509_check_host(x509, name1, nameLen1,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), WOLFSSL_SUCCESS);
/* Ensure that "a*" matches "a" */
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), partialExp);
/* "a*" matches "a" only when RFC 6125 matching is restored */
ExpectIntEQ(wolfSSL_X509_check_host(x509, name2, nameLen2,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), WOLFSSL_SUCCESS);
/* Ensure that "a*" matches "abbbb" */
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), partialExp);
/* "a*" matches "abbbb" only when RFC 6125 matching is restored */
ExpectIntEQ(wolfSSL_X509_check_host(x509, name3, nameLen3,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), WOLFSSL_SUCCESS);
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), partialExp);
/* Ensure that "a*" does not match "bbb" */
ExpectIntNE(wolfSSL_X509_check_host(x509, name4, nameLen4,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), 1);
/* OpenSSL-compatible len handling should still accept the positive case. */
ExpectIntEQ(wolfSSL_X509_check_host(x509, name5, 0,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), WOLFSSL_SUCCESS);
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), partialExp);
ExpectIntEQ(wolfSSL_X509_check_host(x509, name5, nameLen5,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), WOLFSSL_SUCCESS);
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), partialExp);
/* Embedded NUL in the compared host name must be rejected. */
ExpectIntNE(wolfSSL_X509_check_host(x509, badName, 6,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), WOLFSSL_SUCCESS);
Expand Down Expand Up @@ -1705,19 +1719,22 @@ int test_wolfSSL_X509_name_match2(void)
int nameLen4 = (int)(XSTRLEN(name4));
const char *name5 = "acc\0bcc";
int nameLen5 = 7;
/* The cert's "a*b*" alt name is a partial-label wildcard. */
const int partialExp = PARTIAL_WILDCARD_OK ? WOLFSSL_SUCCESS
: WOLFSSL_FAILURE;

ExpectNotNull(x509 = wolfSSL_X509_load_certificate_buffer(
cert_der, certSize, WOLFSSL_FILETYPE_ASN1));

/* Ensure that "a*b*" matches "ab" */
/* "a*b*" matches "ab" only when RFC 6125 matching is restored */
ExpectIntEQ(wolfSSL_X509_check_host(x509, name1, nameLen1,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), WOLFSSL_SUCCESS);
/* Ensure that "a*b*" matches "acccbccc" */
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), partialExp);
/* "a*b*" matches "acccbccc" only when RFC 6125 matching is restored */
ExpectIntEQ(wolfSSL_X509_check_host(x509, name2, nameLen2,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), WOLFSSL_SUCCESS);
/* Ensure that "a*b*" matches "accb" */
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), partialExp);
/* "a*b*" matches "accb" only when RFC 6125 matching is restored */
ExpectIntEQ(wolfSSL_X509_check_host(x509, name3, nameLen3,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), WOLFSSL_SUCCESS);
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), partialExp);
/* Ensure that "a*b*" does not match "accda" */
ExpectIntNE(wolfSSL_X509_check_host(x509, name4, nameLen4,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), WOLFSSL_SUCCESS);
Expand All @@ -1737,14 +1754,14 @@ int test_wolfSSL_X509_name_match2(void)
WOLFSSL_ALWAYS_CHECK_SUBJECT | WOLFSSL_LEFT_MOST_WILDCARD_ONLY,
NULL), WOLFSSL_FAILURE);

/* Ensure that "a*b*" matches "ab", testing openssl behavior replication
* on check len input handling, 0 for len is OK as it should then use
* strlen(name1) */
/* openssl behavior replication on check len input handling: 0 for len is
* OK as it should then use strlen(name1). The verdict must not depend on
* how the length was supplied. */
ExpectIntEQ(wolfSSL_X509_check_host(x509, name1, 0,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), WOLFSSL_SUCCESS);
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), partialExp);
/* Openssl also allows for len to include NULL terminator */
ExpectIntEQ(wolfSSL_X509_check_host(x509, name1, nameLen1 + 1,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), WOLFSSL_SUCCESS);
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), partialExp);
/* Ensure that check string with NULL terminator in middle is
* rejected */
ExpectIntNE(wolfSSL_X509_check_host(x509, name5, nameLen5,
Expand Down Expand Up @@ -2039,9 +2056,9 @@ int test_wolfSSL_MatchDomainName_idn(void)
* an A-label and must still wildcard-match. */
{ "*.example.com", "xyz.example.com", 0, 1,
"non-A-label x-prefix" },
/* Control: partial wildcard against a non-A-label still works. */
{ "x*.example.com", "xyz.example.com", 0, 1,
"partial wildcard non-IDN" },
/* Control: the A-label gate is not what rejects this one. */
{ "x*.example.com", "xyz.example.com", 0,
PARTIAL_WILDCARD_OK, "partial wildcard non-IDN" },

/* Trailing-dot normalization: absolute-form FQDN ("example.com.")
* must match the same FQDN with or without the trailing dot, on
Expand Down Expand Up @@ -2197,10 +2214,10 @@ int test_wolfSSL_MatchDomainName_wildcard(void)
* is consulted (matching OpenSSL). */
{ "*.co.uk", "foo.co.uk", 0, 1,
"two labels after wildcard" },
/* Partial left-most wildcards retain their existing behavior and are
* not subject to the bare-wildcard two-label requirement. */
{ "a*.example.com", "abc.example.com", 0, 1,
"partial left-most wildcard" },
/* Partial left-most wildcards are rejected per RFC 9525 sec. 6.3
* unless WOLFSSL_ALLOW_PARTIAL_WILDCARDS restores RFC 6125 rule 3. */
{ "a*.example.com", "abc.example.com", 0,
PARTIAL_WILDCARD_OK, "partial left-most wildcard" },
/* A wildcard never spans a label separator. */
{ "*.example.com", "foo.bar.example.com", 0, 0,
"wildcard does not cross a dot" },
Expand All @@ -2211,6 +2228,55 @@ int test_wolfSSL_MatchDomainName_wildcard(void)
{ "h*", "host",
WOLFSSL_LEFT_MOST_WILDCARD_ONLY, 0,
"single-label host, partial wildcard" },

/* --- WOLFSSL_NO_PARTIAL_WILDCARDS: the wildcard must be the whole
* left-most label (RFC 9525 sec. 6.3). --- */
{ "*.example.com", "foo.example.com",
WOLFSSL_NO_PARTIAL_WILDCARDS, 1, "whole-label wildcard kept" },
{ "foobar.example.com", "foobar.example.com",
WOLFSSL_NO_PARTIAL_WILDCARDS, 1, "exact name kept" },
{ "foo*.example.com", "foobar.example.com",
WOLFSSL_NO_PARTIAL_WILDCARDS, 0, "partial wildcard refused" },
{ "foo*.example.com", "foo.example.com",
WOLFSSL_NO_PARTIAL_WILDCARDS, 0,
"partial wildcard, empty span refused" },
{ "*foo.example.com", "barfoo.example.com",
WOLFSSL_NO_PARTIAL_WILDCARDS, 0,
"trailing partial wildcard refused" },
{ "a*", "aaaaa",
WOLFSSL_NO_PARTIAL_WILDCARDS, 0,
"single-label partial wildcard refused" },

/* --- WOLFSSL_NO_WILDCARDS: no wildcard interpretation at all. --- */
{ "foobar.example.com", "foobar.example.com",
WOLFSSL_NO_WILDCARDS, 1, "exact name kept" },
{ "FOOBAR.example.com", "foobar.example.com",
WOLFSSL_NO_WILDCARDS, 1, "exact name case folded" },
{ "*.example.com", "foo.example.com",
WOLFSSL_NO_WILDCARDS, 0, "whole-label wildcard refused" },
{ "foo*.example.com", "foobar.example.com",
WOLFSSL_NO_WILDCARDS, 0, "partial wildcard refused" },
{ "a*", "aaaaa",
WOLFSSL_NO_WILDCARDS, 0, "single-label wildcard refused" },
{ "foobar.example.com", "other.example.com",
WOLFSSL_NO_WILDCARDS, 0, "non-matching exact name" },

/* --- Default flags reject every partial-label form; defining
* WOLFSSL_ALLOW_PARTIAL_WILDCARDS brings RFC 6125 rule 3 back. --- */
{ "foo*.example.com", "foobar.example.com", 0,
PARTIAL_WILDCARD_OK, "partial wildcard" },
{ "foo*.example.com", "foo.example.com", 0,
PARTIAL_WILDCARD_OK, "partial wildcard, empty span" },
{ "a*", "aaaaa", 0,
PARTIAL_WILDCARD_OK, "zero-dot partial wildcard" },
{ "*localhost", "localhost", 0,
PARTIAL_WILDCARD_OK, "zero-dot partial wildcard 2" },
{ "f*o.example.com", "fXo.example.com", 0,
PARTIAL_WILDCARD_OK, "wildcard inside the left-most label" },

/* The whole-label form is unaffected by the macro. */
{ "*.example.com", "foo.example.com", 0, 1,
"whole-label wildcard" },
};
size_t i;

Expand Down
Loading
Loading