Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add left-most wildcard matching support to X509_check_host() #7966

Merged
merged 1 commit into from
Oct 8, 2024
Merged
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
49 changes: 34 additions & 15 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -12508,16 +12508,20 @@ int CipherRequires(byte first, byte second, int requirement)

#ifndef NO_CERTS


/* Match names with wildcards, each wildcard can represent a single name
component or fragment but not multiple names, i.e.,
*.z.com matches y.z.com but not x.y.z.com

If flags contains WOLFSSL_LEFT_MOST_WILDCARD_ONLY, wildcard only applies
to left-most name component, compatible with RFC 2830 identity checking.

return 1 on success */
int MatchDomainName(const char* pattern, int patternLen, const char* str,
word32 strLen)
word32 strLen, unsigned int flags)
{
int ret = 0;
byte wildcardEligible = 1;
byte leftWildcardOnly = flags & WOLFSSL_LEFT_MOST_WILDCARD_ONLY;

if (pattern == NULL || str == NULL || patternLen <= 0 || strLen == 0)
return 0;
Expand All @@ -12530,11 +12534,16 @@ int MatchDomainName(const char* pattern, int patternLen, const char* str,

pattern++;

if (p == '*') {
if ((p == '*') && wildcardEligible) {
char s;
/* We will always match '*' */
patternLen--;

/* Only single wildcard allowed with strict left only */
if (leftWildcardOnly) {
wildcardEligible = 0;
}

/* Consume any extra '*' chars until the next non '*' char. */
while (patternLen > 0) {
p = (char)XTOLOWER((unsigned char)*pattern);
Expand All @@ -12543,6 +12552,10 @@ int MatchDomainName(const char* pattern, int patternLen, const char* str,
return 0;
if (p != '*')
break;
if (leftWildcardOnly && (p == '*')) {
/* RFC2830 only allows single left-most wildcard */
return 0;
}

patternLen--;
}
Expand Down Expand Up @@ -12574,6 +12587,11 @@ int MatchDomainName(const char* pattern, int patternLen, const char* str,
}
}
else {
/* Past left-most wildcard location, not eligible if flag set*/
if (leftWildcardOnly && wildcardEligible) {
wildcardEligible = 0;
}

/* Simple case, pattern match exactly */
if (p != (char)XTOLOWER((unsigned char) *str))
return 0;
Expand Down Expand Up @@ -12605,7 +12623,7 @@ int MatchDomainName(const char* pattern, int patternLen, const char* str,
* -1 : No matches and wild pattern match failed.
*/
int CheckForAltNames(DecodedCert* dCert, const char* domain, word32 domainLen,
int* checkCN)
int* checkCN, unsigned int flags)
{
int match = 0;
DNS_entry* altName = NULL;
Expand Down Expand Up @@ -12636,7 +12654,7 @@ int CheckForAltNames(DecodedCert* dCert, const char* domain, word32 domainLen,
len = (word32)altName->len;
}

if (MatchDomainName(buf, (int)len, domain, domainLen)) {
if (MatchDomainName(buf, (int)len, domain, domainLen, flags)) {
match = 1;
if (checkCN != NULL) {
*checkCN = 0;
Expand Down Expand Up @@ -12665,13 +12683,14 @@ int CheckForAltNames(DecodedCert* dCert, const char* domain, word32 domainLen,
* domainNameLen The length of the domain name.
* returns DOMAIN_NAME_MISMATCH when no match found and 0 on success.
*/
int CheckHostName(DecodedCert* dCert, const char *domainName, size_t domainNameLen)
int CheckHostName(DecodedCert* dCert, const char *domainName,
size_t domainNameLen, unsigned int flags)
{
int checkCN;
int ret = WC_NO_ERR_TRACE(DOMAIN_NAME_MISMATCH);

if (CheckForAltNames(dCert, domainName, (word32)domainNameLen,
&checkCN) != 1) {
&checkCN, flags) != 1) {
ret = DOMAIN_NAME_MISMATCH;
WOLFSSL_MSG("DomainName match on alt names failed");
}
Expand All @@ -12682,7 +12701,7 @@ int CheckHostName(DecodedCert* dCert, const char *domainName, size_t domainNameL
#ifndef WOLFSSL_HOSTNAME_VERIFY_ALT_NAME_ONLY
if (checkCN == 1) {
if (MatchDomainName(dCert->subjectCN, dCert->subjectCNLen,
domainName, (word32)domainNameLen) == 1) {
domainName, (word32)domainNameLen, flags) == 1) {
ret = 0;
}
else {
Expand All @@ -12699,7 +12718,7 @@ int CheckIPAddr(DecodedCert* dCert, const char* ipasc)
{
WOLFSSL_MSG("Checking IPAddr");

return CheckHostName(dCert, ipasc, (size_t)XSTRLEN(ipasc));
return CheckHostName(dCert, ipasc, (size_t)XSTRLEN(ipasc), 0);
}


Expand Down Expand Up @@ -13843,7 +13862,7 @@ int DoVerifyCallback(WOLFSSL_CERT_MANAGER* cm, WOLFSSL* ssl, int cert_err,
/* If altNames names is present, then subject common name is ignored */
if (args->dCert->altNames != NULL) {
if (CheckForAltNames(args->dCert, ssl->param->hostName,
(word32)XSTRLEN(ssl->param->hostName), NULL) != 1) {
(word32)XSTRLEN(ssl->param->hostName), NULL, 0) != 1) {
if (cert_err == 0) {
ret = DOMAIN_NAME_MISMATCH;
WOLFSSL_ERROR_VERBOSE(ret);
Expand All @@ -13857,7 +13876,7 @@ int DoVerifyCallback(WOLFSSL_CERT_MANAGER* cm, WOLFSSL* ssl, int cert_err,
args->dCert->subjectCN,
args->dCert->subjectCNLen,
ssl->param->hostName,
(word32)XSTRLEN(ssl->param->hostName)) == 0) {
(word32)XSTRLEN(ssl->param->hostName), 0) == 0) {
if (cert_err == 0) {
ret = DOMAIN_NAME_MISMATCH;
WOLFSSL_ERROR_VERBOSE(ret);
Expand Down Expand Up @@ -15747,7 +15766,7 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
(ssl->buffers.domainName.buffer == NULL ? 0 :
(word32)XSTRLEN(
(const char *)ssl->buffers.domainName.buffer)),
NULL) != 1) {
NULL, 0) != 1) {
WOLFSSL_MSG("DomainName match on alt names failed");
/* try to get peer key still */
ret = DOMAIN_NAME_MISMATCH;
Expand All @@ -15762,7 +15781,7 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
(ssl->buffers.domainName.buffer == NULL ? 0 :
(word32)XSTRLEN(
(const char *)ssl->buffers.domainName.buffer)
)) == 0)
), 0) == 0)
{
WOLFSSL_MSG("DomainName match on common name failed");
ret = DOMAIN_NAME_MISMATCH;
Expand All @@ -15775,14 +15794,14 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
args->dCert->subjectCNLen,
(char*)ssl->buffers.domainName.buffer,
(ssl->buffers.domainName.buffer == NULL ? 0 :
(word32)XSTRLEN(ssl->buffers.domainName.buffer))) == 0)
(word32)XSTRLEN(ssl->buffers.domainName.buffer)), 0) == 0)
{
WOLFSSL_MSG("DomainName match on common name failed");
if (CheckForAltNames(args->dCert,
(char*)ssl->buffers.domainName.buffer,
(ssl->buffers.domainName.buffer == NULL ? 0 :
(word32)XSTRLEN(ssl->buffers.domainName.buffer)),
NULL) != 1) {
NULL, 0) != 1) {
WOLFSSL_MSG(
"DomainName match on alt names failed too");
/* try to get peer key still */
Expand Down
3 changes: 1 addition & 2 deletions src/x509.c
Original file line number Diff line number Diff line change
Expand Up @@ -14338,7 +14338,6 @@ int wolfSSL_X509_check_host(WOLFSSL_X509 *x, const char *chk, size_t chklen,
WOLFSSL_ENTER("wolfSSL_X509_check_host");

/* flags and peername not needed for Nginx. */
(void)flags;
(void)peername;

if ((x == NULL) || (chk == NULL)) {
Expand Down Expand Up @@ -14390,7 +14389,7 @@ int wolfSSL_X509_check_host(WOLFSSL_X509 *x, const char *chk, size_t chklen,
chklen--;
}

ret = CheckHostName(dCert, (char *)chk, chklen);
ret = CheckHostName(dCert, (char *)chk, chklen, flags);

out:

Expand Down
81 changes: 81 additions & 0 deletions tests/api.c
Original file line number Diff line number Diff line change
Expand Up @@ -55652,20 +55652,42 @@ static int test_wolfSSL_X509_check_host(void)
&& !defined(NO_SHA) && !defined(NO_RSA)
X509* x509 = NULL;
const char altName[] = "example.com";
const char badAltName[] = "a.example.com";

/* cliCertFile has subjectAltName set to 'example.com', '127.0.0.1' */
ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(cliCertFile,
SSL_FILETYPE_PEM));

ExpectIntEQ(X509_check_host(x509, altName, XSTRLEN(altName), 0, NULL),
WOLFSSL_SUCCESS);

ExpectIntEQ(X509_check_host(x509, badAltName, XSTRLEN(badAltName), 0, NULL),
WC_NO_ERR_TRACE(WOLFSSL_FAILURE));

ExpectIntEQ(X509_check_host(x509, NULL, 0, 0, NULL),
WC_NO_ERR_TRACE(WOLFSSL_FAILURE));

/* Check WOLFSSL_LEFT_MOST_WILDCARD_ONLY flag set */
ExpectIntEQ(X509_check_host(x509, altName, XSTRLEN(altName),
WOLFSSL_LEFT_MOST_WILDCARD_ONLY, NULL), WOLFSSL_SUCCESS);

ExpectIntEQ(X509_check_host(x509, NULL, 0,
WOLFSSL_LEFT_MOST_WILDCARD_ONLY, NULL),
WC_NO_ERR_TRACE(WOLFSSL_FAILURE));

ExpectIntEQ(X509_check_host(x509, badAltName, XSTRLEN(badAltName),
WOLFSSL_LEFT_MOST_WILDCARD_ONLY, NULL),
WC_NO_ERR_TRACE(WOLFSSL_FAILURE));

X509_free(x509);

ExpectIntEQ(X509_check_host(NULL, altName, XSTRLEN(altName), 0, NULL),
WC_NO_ERR_TRACE(WOLFSSL_FAILURE));

/* Check again with WOLFSSL_LEFT_MOST_WILDCARD_ONLY flag set */
ExpectIntEQ(X509_check_host(NULL, altName, XSTRLEN(altName),
WOLFSSL_LEFT_MOST_WILDCARD_ONLY, NULL),
WC_NO_ERR_TRACE(WOLFSSL_FAILURE));
#endif
return EXPECT_RESULT();
}
Expand Down Expand Up @@ -63359,6 +63381,12 @@ static int test_wolfSSL_X509_bad_altname(void)
* name of "a*\0*". Ensure that it does not match "aaaaa" */
ExpectIntNE(wolfSSL_X509_check_host(x509, name, nameLen,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), 1);

/* Also make sure WOLFSSL_LEFT_MOST_WILDCARD_ONLY fails too */
ExpectIntNE(wolfSSL_X509_check_host(x509, name, nameLen,
WOLFSSL_ALWAYS_CHECK_SUBJECT | WOLFSSL_LEFT_MOST_WILDCARD_ONLY,
NULL), 1);

X509_free(x509);

#endif
Expand Down Expand Up @@ -63479,6 +63507,26 @@ static int test_wolfSSL_X509_name_match(void)
ExpectIntNE(wolfSSL_X509_check_host(x509, name4, nameLen4,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), 1);

/* WOLFSSL_LEFT_MOST_WILDCARD_ONLY flag should fail on all cases, since
* 'a*' alt name does not have wildcard left-most */

/* Ensure that "a*" does not match "aaaaa" */
ExpectIntNE(wolfSSL_X509_check_host(x509, name1, nameLen1,
WOLFSSL_ALWAYS_CHECK_SUBJECT | WOLFSSL_LEFT_MOST_WILDCARD_ONLY,
NULL), WOLFSSL_SUCCESS);
/* Ensure that "a*" does not match "a" */
ExpectIntNE(wolfSSL_X509_check_host(x509, name2, nameLen2,
WOLFSSL_ALWAYS_CHECK_SUBJECT | WOLFSSL_LEFT_MOST_WILDCARD_ONLY,
NULL), WOLFSSL_SUCCESS);
/* Ensure that "a*" does not match "abbbb" */
ExpectIntNE(wolfSSL_X509_check_host(x509, name3, nameLen3,
WOLFSSL_ALWAYS_CHECK_SUBJECT | WOLFSSL_LEFT_MOST_WILDCARD_ONLY,
NULL), WOLFSSL_SUCCESS);
/* Ensure that "a*" does not match "bbb" */
ExpectIntNE(wolfSSL_X509_check_host(x509, name4, nameLen4,
WOLFSSL_ALWAYS_CHECK_SUBJECT | WOLFSSL_LEFT_MOST_WILDCARD_ONLY,
NULL), WOLFSSL_SUCCESS);

wolfSSL_X509_free(x509);

#endif
Expand Down Expand Up @@ -63601,6 +63649,21 @@ static int test_wolfSSL_X509_name_match2(void)
ExpectIntNE(wolfSSL_X509_check_host(x509, name4, nameLen4,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), WOLFSSL_SUCCESS);

/* WOLFSSL_LEFT_MOST_WILDCARD_ONLY flag should fail on all cases, since
* 'a*b*' alt name does not have wildcard left-most */
ExpectIntEQ(wolfSSL_X509_check_host(x509, name1, nameLen1,
WOLFSSL_ALWAYS_CHECK_SUBJECT | WOLFSSL_LEFT_MOST_WILDCARD_ONLY,
NULL), WOLFSSL_FAILURE);
ExpectIntEQ(wolfSSL_X509_check_host(x509, name2, nameLen2,
WOLFSSL_ALWAYS_CHECK_SUBJECT | WOLFSSL_LEFT_MOST_WILDCARD_ONLY,
NULL), WOLFSSL_FAILURE);
ExpectIntEQ(wolfSSL_X509_check_host(x509, name3, nameLen3,
WOLFSSL_ALWAYS_CHECK_SUBJECT | WOLFSSL_LEFT_MOST_WILDCARD_ONLY,
NULL), WOLFSSL_FAILURE);
ExpectIntEQ(wolfSSL_X509_check_host(x509, name4, nameLen4,
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) */
Expand Down Expand Up @@ -63714,6 +63777,8 @@ static int test_wolfSSL_X509_name_match3(void)
int nameLen1 = (int)(XSTRLEN(name1));
const char *name2 = "x.y.example.com";
int nameLen2 = (int)(XSTRLEN(name2));
const char *name3 = "example.com";
int nameLen3 = (int)(XSTRLEN(name3));

ExpectNotNull(x509 = wolfSSL_X509_load_certificate_buffer(
cert_der, certSize, WOLFSSL_FILETYPE_ASN1));
Expand All @@ -63724,6 +63789,22 @@ static int test_wolfSSL_X509_name_match3(void)
/* Ensure that "*.example.com" does NOT match "x.y.example.com" */
ExpectIntNE(wolfSSL_X509_check_host(x509, name2, nameLen2,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), WOLFSSL_SUCCESS);
/* Ensure that "*.example.com" does NOT match "example.com" */
ExpectIntNE(wolfSSL_X509_check_host(x509, name3, nameLen3,
WOLFSSL_ALWAYS_CHECK_SUBJECT, NULL), WOLFSSL_SUCCESS);

/* WOLFSSL_LEFT_MOST_WILDCARD_ONLY, should match "foo.example.com" */
ExpectIntEQ(wolfSSL_X509_check_host(x509, name1, nameLen1,
WOLFSSL_ALWAYS_CHECK_SUBJECT | WOLFSSL_LEFT_MOST_WILDCARD_ONLY,
NULL), WOLFSSL_SUCCESS);
/* WOLFSSL_LEFT_MOST_WILDCARD_ONLY, should NOT match "x.y.example.com" */
ExpectIntNE(wolfSSL_X509_check_host(x509, name2, nameLen2,
WOLFSSL_ALWAYS_CHECK_SUBJECT | WOLFSSL_LEFT_MOST_WILDCARD_ONLY,
NULL), WOLFSSL_SUCCESS);
/* WOLFSSL_LEFT_MOST_WILDCARD_ONLY, should NOT match "example.com" */
ExpectIntNE(wolfSSL_X509_check_host(x509, name3, nameLen3,
WOLFSSL_ALWAYS_CHECK_SUBJECT | WOLFSSL_LEFT_MOST_WILDCARD_ONLY,
NULL), WOLFSSL_SUCCESS);

wolfSSL_X509_free(x509);

Expand Down
10 changes: 7 additions & 3 deletions wolfssl/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -2240,9 +2240,13 @@ WOLFSSL_LOCAL void FreeAsyncCtx(WOLFSSL* ssl, byte freeAsync);
WOLFSSL_LOCAL void FreeKeyExchange(WOLFSSL* ssl);
WOLFSSL_LOCAL void FreeSuites(WOLFSSL* ssl);
WOLFSSL_LOCAL int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, word32 totalSz);
WOLFSSL_LOCAL int MatchDomainName(const char* pattern, int len, const char* str, word32 strLen);
WOLFSSL_LOCAL int MatchDomainName(const char* pattern, int len,
const char* str, word32 strLen,
unsigned int flags);
#if !defined(NO_CERTS) && !defined(NO_ASN)
WOLFSSL_LOCAL int CheckForAltNames(DecodedCert* dCert, const char* domain, word32 domainLen, int* checkCN);
WOLFSSL_LOCAL int CheckForAltNames(DecodedCert* dCert, const char* domain,
word32 domainLen, int* checkCN,
unsigned int flags);
WOLFSSL_LOCAL int CheckIPAddr(DecodedCert* dCert, const char* ipasc);
WOLFSSL_LOCAL void CopyDecodedName(WOLFSSL_X509_NAME* name, DecodedCert* dCert, int nameType);
#endif
Expand Down Expand Up @@ -6252,7 +6256,7 @@ WOLFSSL_API void SSL_ResourceFree(WOLFSSL* ssl); /* Micrium uses */

#ifndef NO_ASN
WOLFSSL_LOCAL int CheckHostName(DecodedCert* dCert, const char *domainName,
size_t domainNameLen);
size_t domainNameLen, unsigned int flags);
#endif
#endif

Expand Down
2 changes: 2 additions & 0 deletions wolfssl/ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,8 @@ struct WOLFSSL_X509_STORE {
#define WOLFSSL_NO_WILDCARDS 0x2
#define WOLFSSL_NO_PARTIAL_WILDCARDS 0x4
#define WOLFSSL_MULTI_LABEL_WILDCARDS 0x8
/* Custom to wolfSSL, OpenSSL compat goes up to 0x20 */
#define WOLFSSL_LEFT_MOST_WILDCARD_ONLY 0x40

#if defined(OPENSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL)
#define WOLFSSL_USE_CHECK_TIME 0x2
Expand Down