diff --git a/src/internal.c b/src/internal.c index c61dfb341..6cb48bd0d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -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; @@ -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); @@ -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--; } @@ -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; @@ -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; @@ -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; @@ -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"); } @@ -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 { @@ -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); } @@ -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); @@ -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); @@ -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; @@ -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; @@ -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 */ diff --git a/src/x509.c b/src/x509.c index be1d36f2b..72563c4e6 100644 --- a/src/x509.c +++ b/src/x509.c @@ -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)) { @@ -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: diff --git a/tests/api.c b/tests/api.c index 321bfdc30..19b510746 100644 --- a/tests/api.c +++ b/tests/api.c @@ -55653,20 +55653,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(); } @@ -63360,6 +63382,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 @@ -63480,6 +63508,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 @@ -63602,6 +63650,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) */ @@ -63715,6 +63778,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)); @@ -63725,6 +63790,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); diff --git a/wolfssl/internal.h b/wolfssl/internal.h index c7b7b6097..af76dcfd1 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -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 @@ -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 diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index fed17f656..bf5067d84 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -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