Extend domain_match and domain_suffix_match to allow list of values

These wpa_supplicant network profile parameters could be used to specify
a single match string that would be used against the dNSName items in
subjectAltName or CN. There may be use cases where more than one
alternative match string would be useful, so extend these to allow a
semicolon delimited list of values to be used (e.g.,
"example.org;example.com"). If any of the specified values matches any
of the dNSName/CN values in the server certificate, consider the
certificate as meeting this requirement.

Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
This commit is contained in:
Jouni Malinen 2019-04-09 16:18:19 +03:00 committed by Jouni Malinen
parent dcc0ccd5b0
commit 242e857285
6 changed files with 138 additions and 37 deletions

View file

@ -120,12 +120,19 @@ struct tls_config {
* %NULL to allow all subjects * %NULL to allow all subjects
* @altsubject_match: String to match in the alternative subject of the peer * @altsubject_match: String to match in the alternative subject of the peer
* certificate or %NULL to allow all alternative subjects * certificate or %NULL to allow all alternative subjects
* @suffix_match: String to suffix match in the dNSName or CN of the peer * @suffix_match: Semicolon deliminated string of values to suffix match against
* certificate or %NULL to allow all domain names. This may allow subdomains an * the dNSName or CN of the peer certificate or %NULL to allow all domain names.
* wildcard certificates. Each domain name label must have a full match. * This may allow subdomains and wildcard certificates. Each domain name label
* must have a full case-insensitive match.
* @domain_match: String to match in the dNSName or CN of the peer * @domain_match: String to match in the dNSName or CN of the peer
* certificate or %NULL to allow all domain names. This requires a full, * certificate or %NULL to allow all domain names. This requires a full,
* case-insensitive match. * case-insensitive match.
*
* More than one match string can be provided by using semicolons to
* separate the strings (e.g., example.org;example.com). When multiple
* strings are specified, a match with any one of the values is
* considered a sufficient match for the certificate, i.e., the
* conditions are ORed together.
* @client_cert: File or reference name for client X.509 certificate in PEM or * @client_cert: File or reference name for client X.509 certificate in PEM or
* DER format * DER format
* @client_cert_blob: client_cert as inlined data or %NULL if not used * @client_cert_blob: client_cert as inlined data or %NULL if not used

View file

@ -1086,6 +1086,52 @@ ocsp_error:
} }
static int tls_match_suffix_helper(gnutls_x509_crt_t cert, const char *match,
int full)
{
int res = -1;
#if GNUTLS_VERSION_NUMBER >= 0x030300
if (full)
res = gnutls_x509_crt_check_hostname2(
cert, match,
GNUTLS_VERIFY_DO_NOT_ALLOW_WILDCARDS);
#endif /* >= 3.3.0 */
if (res == -1)
res = gnutls_x509_crt_check_hostname(cert, match);
wpa_printf(MSG_DEBUG, "TLS: Match domain against %s%s --> res=%d",
full ? "": "suffix ", match, res);
return res;
}
static int tls_match_suffix(gnutls_x509_crt_t cert, const char *match,
int full)
{
char *values, *token, *context = NULL;
int ret = 0;
if (!os_strchr(match, ';'))
return tls_match_suffix_helper(cert, match, full);
values = os_strdup(match);
if (!values)
return 0;
/* Process each match alternative separately until a match is found */
while ((token = str_token(values, ";", &context))) {
if (tls_match_suffix_helper(cert, token, full)) {
ret = 1;
break;
}
}
os_free(values);
return ret;
}
static int tls_connection_verify_peer(gnutls_session_t session) static int tls_connection_verify_peer(gnutls_session_t session)
{ {
struct tls_connection *conn; struct tls_connection *conn;
@ -1281,8 +1327,7 @@ static int tls_connection_verify_peer(gnutls_session_t session)
if (i == 0) { if (i == 0) {
if (conn->suffix_match && if (conn->suffix_match &&
!gnutls_x509_crt_check_hostname( !tls_match_suffix(cert, conn->suffix_match, 0)) {
cert, conn->suffix_match)) {
wpa_printf(MSG_WARNING, wpa_printf(MSG_WARNING,
"TLS: Domain suffix match '%s' not found", "TLS: Domain suffix match '%s' not found",
conn->suffix_match); conn->suffix_match);
@ -1298,9 +1343,7 @@ static int tls_connection_verify_peer(gnutls_session_t session)
#if GNUTLS_VERSION_NUMBER >= 0x030300 #if GNUTLS_VERSION_NUMBER >= 0x030300
if (conn->domain_match && if (conn->domain_match &&
!gnutls_x509_crt_check_hostname2( !tls_match_suffix(cert, conn->domain_match, 1)) {
cert, conn->domain_match,
GNUTLS_VERIFY_DO_NOT_ALLOW_WILDCARDS)) {
wpa_printf(MSG_WARNING, wpa_printf(MSG_WARNING,
"TLS: Domain match '%s' not found", "TLS: Domain match '%s' not found",
conn->domain_match); conn->domain_match);

View file

@ -1735,9 +1735,9 @@ static int tls_match_altsubject(X509 *cert, const char *match)
#ifndef CONFIG_NATIVE_WINDOWS #ifndef CONFIG_NATIVE_WINDOWS
static int domain_suffix_match(const u8 *val, size_t len, const char *match, static int domain_suffix_match(const u8 *val, size_t len, const char *match,
int full) size_t match_len, int full)
{ {
size_t i, match_len; size_t i;
/* Check for embedded nuls that could mess up suffix matching */ /* Check for embedded nuls that could mess up suffix matching */
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
@ -1747,7 +1747,6 @@ static int domain_suffix_match(const u8 *val, size_t len, const char *match,
} }
} }
match_len = os_strlen(match);
if (match_len > len || (full && match_len != len)) if (match_len > len || (full && match_len != len))
return 0; return 0;
@ -1980,12 +1979,10 @@ static int tls_match_dn_field(X509 *cert, const char *match)
} }
static int tls_match_suffix(X509 *cert, const char *match, int full) #ifndef CONFIG_NATIVE_WINDOWS
static int tls_match_suffix_helper(X509 *cert, const char *match,
size_t match_len, int full)
{ {
#ifdef CONFIG_NATIVE_WINDOWS
/* wincrypt.h has conflicting X509_NAME definition */
return -1;
#else /* CONFIG_NATIVE_WINDOWS */
GENERAL_NAME *gen; GENERAL_NAME *gen;
void *ext; void *ext;
int i; int i;
@ -2007,8 +2004,8 @@ static int tls_match_suffix(X509 *cert, const char *match, int full)
gen->d.dNSName->data, gen->d.dNSName->data,
gen->d.dNSName->length); gen->d.dNSName->length);
if (domain_suffix_match(gen->d.dNSName->data, if (domain_suffix_match(gen->d.dNSName->data,
gen->d.dNSName->length, match, full) == gen->d.dNSName->length,
1) { match, match_len, full) == 1) {
wpa_printf(MSG_DEBUG, "TLS: %s in dNSName found", wpa_printf(MSG_DEBUG, "TLS: %s in dNSName found",
full ? "Match" : "Suffix match"); full ? "Match" : "Suffix match");
sk_GENERAL_NAME_pop_free(ext, GENERAL_NAME_free); sk_GENERAL_NAME_pop_free(ext, GENERAL_NAME_free);
@ -2039,8 +2036,8 @@ static int tls_match_suffix(X509 *cert, const char *match, int full)
continue; continue;
wpa_hexdump_ascii(MSG_DEBUG, "TLS: Certificate commonName", wpa_hexdump_ascii(MSG_DEBUG, "TLS: Certificate commonName",
cn->data, cn->length); cn->data, cn->length);
if (domain_suffix_match(cn->data, cn->length, match, full) == 1) if (domain_suffix_match(cn->data, cn->length,
{ match, match_len, full) == 1) {
wpa_printf(MSG_DEBUG, "TLS: %s in commonName found", wpa_printf(MSG_DEBUG, "TLS: %s in commonName found",
full ? "Match" : "Suffix match"); full ? "Match" : "Suffix match");
return 1; return 1;
@ -2050,6 +2047,25 @@ static int tls_match_suffix(X509 *cert, const char *match, int full)
wpa_printf(MSG_DEBUG, "TLS: No CommonName %smatch found", wpa_printf(MSG_DEBUG, "TLS: No CommonName %smatch found",
full ? "": "suffix "); full ? "": "suffix ");
return 0; return 0;
}
#endif /* CONFIG_NATIVE_WINDOWS */
static int tls_match_suffix(X509 *cert, const char *match, int full)
{
#ifdef CONFIG_NATIVE_WINDOWS
/* wincrypt.h has conflicting X509_NAME definition */
return -1;
#else /* CONFIG_NATIVE_WINDOWS */
const char *token, *last = NULL;
/* Process each match alternative separately until a match is found */
while ((token = cstr_token(match, ";", &last))) {
if (tls_match_suffix_helper(cert, token, last - token, full))
return 1;
}
return 0;
#endif /* CONFIG_NATIVE_WINDOWS */ #endif /* CONFIG_NATIVE_WINDOWS */
} }

View file

@ -643,9 +643,9 @@ static int tls_match_alt_subject(WOLFSSL_X509 *cert, const char *match)
static int domain_suffix_match(const char *val, size_t len, const char *match, static int domain_suffix_match(const char *val, size_t len, const char *match,
int full) size_t match_len, int full)
{ {
size_t i, match_len; size_t i;
/* Check for embedded nuls that could mess up suffix matching */ /* Check for embedded nuls that could mess up suffix matching */
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
@ -656,7 +656,6 @@ static int domain_suffix_match(const char *val, size_t len, const char *match,
} }
} }
match_len = os_strlen(match);
if (match_len > len || (full && match_len != len)) if (match_len > len || (full && match_len != len))
return 0; return 0;
@ -674,7 +673,8 @@ static int domain_suffix_match(const char *val, size_t len, const char *match,
} }
static int tls_match_suffix(WOLFSSL_X509 *cert, const char *match, int full) static int tls_match_suffix_helper(WOLFSSL_X509 *cert, const char *match,
size_t match_len, int full)
{ {
WOLFSSL_ASN1_OBJECT *gen; WOLFSSL_ASN1_OBJECT *gen;
void *ext; void *ext;
@ -697,7 +697,7 @@ static int tls_match_suffix(WOLFSSL_X509 *cert, const char *match, int full)
gen->obj, os_strlen((char *)gen->obj)); gen->obj, os_strlen((char *)gen->obj));
if (domain_suffix_match((const char *) gen->obj, if (domain_suffix_match((const char *) gen->obj,
os_strlen((char *) gen->obj), match, os_strlen((char *) gen->obj), match,
full) == 1) { match_len, full) == 1) {
wpa_printf(MSG_DEBUG, "TLS: %s in dNSName found", wpa_printf(MSG_DEBUG, "TLS: %s in dNSName found",
full ? "Match" : "Suffix match"); full ? "Match" : "Suffix match");
wolfSSL_sk_ASN1_OBJECT_free(ext); wolfSSL_sk_ASN1_OBJECT_free(ext);
@ -729,8 +729,8 @@ static int tls_match_suffix(WOLFSSL_X509 *cert, const char *match, int full)
continue; continue;
wpa_hexdump_ascii(MSG_DEBUG, "TLS: Certificate commonName", wpa_hexdump_ascii(MSG_DEBUG, "TLS: Certificate commonName",
cn->data, cn->length); cn->data, cn->length);
if (domain_suffix_match(cn->data, cn->length, match, full) == 1) if (domain_suffix_match(cn->data, cn->length,
{ match, match_len, full) == 1) {
wpa_printf(MSG_DEBUG, "TLS: %s in commonName found", wpa_printf(MSG_DEBUG, "TLS: %s in commonName found",
full ? "Match" : "Suffix match"); full ? "Match" : "Suffix match");
return 1; return 1;
@ -743,6 +743,20 @@ static int tls_match_suffix(WOLFSSL_X509 *cert, const char *match, int full)
} }
static int tls_match_suffix(WOLFSSL_X509 *cert, const char *match, int full)
{
const char *token, *last = NULL;
/* Process each match alternative separately until a match is found */
while ((token = cstr_token(match, ";", &last))) {
if (tls_match_suffix_helper(cert, token, last - token, full))
return 1;
}
return 0;
}
static enum tls_fail_reason wolfssl_tls_fail_reason(int err) static enum tls_fail_reason wolfssl_tls_fail_reason(int err)
{ {
switch (err) { switch (err) {

View file

@ -259,18 +259,21 @@ struct eap_peer_config {
/** /**
* domain_suffix_match - Constraint for server domain name * domain_suffix_match - Constraint for server domain name
* *
* If set, this FQDN is used as a suffix match requirement for the * If set, this semicolon deliminated list of FQDNs is used as suffix
* server certificate in SubjectAltName dNSName element(s). If a * match requirements for the server certificate in SubjectAltName
* matching dNSName is found, this constraint is met. If no dNSName * dNSName element(s). If a matching dNSName is found against any of the
* values are present, this constraint is matched against SubjectName CN * specified values, this constraint is met. If no dNSName values are
* using same suffix match comparison. Suffix match here means that the * present, this constraint is matched against SubjectName CN using same
* host/domain name is compared one label at a time starting from the * suffix match comparison. Suffix match here means that the host/domain
* top-level domain and all the labels in domain_suffix_match shall be * name is compared case-insentively one label at a time starting from
* included in the certificate. The certificate may include additional * the top-level domain and all the labels in domain_suffix_match shall
* sub-level labels in addition to the required labels. * be included in the certificate. The certificate may include
* additional sub-level labels in addition to the required labels.
* *
* For example, domain_suffix_match=example.com would match * For example, domain_suffix_match=example.com would match
* test.example.com but would not match test-example.com. * test.example.com but would not match test-example.com. Multiple
* match options can be specified in following manner:
* example.org;example.com.
*/ */
char *domain_suffix_match; char *domain_suffix_match;
@ -286,6 +289,12 @@ struct eap_peer_config {
* no subdomains or wildcard matches are allowed. Case-insensitive * no subdomains or wildcard matches are allowed. Case-insensitive
* comparison is used, so "Example.com" matches "example.com", but would * comparison is used, so "Example.com" matches "example.com", but would
* not match "test.Example.com". * not match "test.Example.com".
*
* More than one match string can be provided by using semicolons to
* separate the strings (e.g., example.org;example.com). When multiple
* strings are specified, a match with any one of the values is
* considered a sufficient match for the certificate, i.e., the
* conditions are ORed together.
*/ */
char *domain_match; char *domain_match;

View file

@ -1180,6 +1180,12 @@ fast_reauth=1
# certificate may include additional sub-level labels in addition to the # certificate may include additional sub-level labels in addition to the
# required labels. # required labels.
# #
# More than one match string can be provided by using semicolons to
# separate the strings (e.g., example.org;example.com). When multiple
# strings are specified, a match with any one of the values is considered
# a sufficient match for the certificate, i.e., the conditions are ORed
# together.
#
# For example, domain_suffix_match=example.com would match # For example, domain_suffix_match=example.com would match
# test.example.com but would not match test-example.com. # test.example.com but would not match test-example.com.
# domain_match: Constraint for server domain name # domain_match: Constraint for server domain name
@ -1192,6 +1198,12 @@ fast_reauth=1
# no subdomains or wildcard matches are allowed. Case-insensitive # no subdomains or wildcard matches are allowed. Case-insensitive
# comparison is used, so "Example.com" matches "example.com", but would # comparison is used, so "Example.com" matches "example.com", but would
# not match "test.Example.com". # not match "test.Example.com".
#
# More than one match string can be provided by using semicolons to
# separate the strings (e.g., example.org;example.com). When multiple
# strings are specified, a match with any one of the values is considered
# a sufficient match for the certificate, i.e., the conditions are ORed
# together.
# phase1: Phase1 (outer authentication, i.e., TLS tunnel) parameters # phase1: Phase1 (outer authentication, i.e., TLS tunnel) parameters
# (string with field-value pairs, e.g., "peapver=0" or # (string with field-value pairs, e.g., "peapver=0" or
# "peapver=1 peaplabel=1") # "peapver=1 peaplabel=1")