diff --git a/src/crypto/tls.h b/src/crypto/tls.h index 9463f58f9..8bdb91ff2 100644 --- a/src/crypto/tls.h +++ b/src/crypto/tls.h @@ -120,12 +120,19 @@ struct tls_config { * %NULL to allow all subjects * @altsubject_match: String to match in the alternative subject of the peer * certificate or %NULL to allow all alternative subjects - * @suffix_match: String to suffix match in the dNSName or CN of the peer - * certificate or %NULL to allow all domain names. This may allow subdomains an - * wildcard certificates. Each domain name label must have a full match. + * @suffix_match: Semicolon deliminated string of values to suffix match against + * the dNSName or CN of the peer certificate or %NULL to allow all domain names. + * 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 * certificate or %NULL to allow all domain names. This requires a full, * 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 * DER format * @client_cert_blob: client_cert as inlined data or %NULL if not used diff --git a/src/crypto/tls_gnutls.c b/src/crypto/tls_gnutls.c index ccd28842a..daa01d9ed 100644 --- a/src/crypto/tls_gnutls.c +++ b/src/crypto/tls_gnutls.c @@ -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) { struct tls_connection *conn; @@ -1281,8 +1327,7 @@ static int tls_connection_verify_peer(gnutls_session_t session) if (i == 0) { if (conn->suffix_match && - !gnutls_x509_crt_check_hostname( - cert, conn->suffix_match)) { + !tls_match_suffix(cert, conn->suffix_match, 0)) { wpa_printf(MSG_WARNING, "TLS: Domain suffix match '%s' not found", conn->suffix_match); @@ -1298,9 +1343,7 @@ static int tls_connection_verify_peer(gnutls_session_t session) #if GNUTLS_VERSION_NUMBER >= 0x030300 if (conn->domain_match && - !gnutls_x509_crt_check_hostname2( - cert, conn->domain_match, - GNUTLS_VERIFY_DO_NOT_ALLOW_WILDCARDS)) { + !tls_match_suffix(cert, conn->domain_match, 1)) { wpa_printf(MSG_WARNING, "TLS: Domain match '%s' not found", conn->domain_match); diff --git a/src/crypto/tls_openssl.c b/src/crypto/tls_openssl.c index 984b85bda..47e074634 100644 --- a/src/crypto/tls_openssl.c +++ b/src/crypto/tls_openssl.c @@ -1735,9 +1735,9 @@ static int tls_match_altsubject(X509 *cert, const char *match) #ifndef CONFIG_NATIVE_WINDOWS 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 */ 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)) 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; void *ext; 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->length); if (domain_suffix_match(gen->d.dNSName->data, - gen->d.dNSName->length, match, full) == - 1) { + gen->d.dNSName->length, + match, match_len, full) == 1) { wpa_printf(MSG_DEBUG, "TLS: %s in dNSName found", full ? "Match" : "Suffix match"); 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; wpa_hexdump_ascii(MSG_DEBUG, "TLS: Certificate commonName", 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", full ? "Match" : "Suffix match"); 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", full ? "": "suffix "); 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 */ } diff --git a/src/crypto/tls_wolfssl.c b/src/crypto/tls_wolfssl.c index 9cf13a9bd..e9cb425c1 100644 --- a/src/crypto/tls_wolfssl.c +++ b/src/crypto/tls_wolfssl.c @@ -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, - 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 */ 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)) 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; 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)); if (domain_suffix_match((const char *) gen->obj, os_strlen((char *) gen->obj), match, - full) == 1) { + match_len, full) == 1) { wpa_printf(MSG_DEBUG, "TLS: %s in dNSName found", full ? "Match" : "Suffix match"); wolfSSL_sk_ASN1_OBJECT_free(ext); @@ -729,8 +729,8 @@ static int tls_match_suffix(WOLFSSL_X509 *cert, const char *match, int full) continue; wpa_hexdump_ascii(MSG_DEBUG, "TLS: Certificate commonName", 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", full ? "Match" : "Suffix match"); 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) { switch (err) { diff --git a/src/eap_peer/eap_config.h b/src/eap_peer/eap_config.h index 56278930d..3a88f2abd 100644 --- a/src/eap_peer/eap_config.h +++ b/src/eap_peer/eap_config.h @@ -259,18 +259,21 @@ struct eap_peer_config { /** * domain_suffix_match - Constraint for server domain name * - * If set, this FQDN is used as a suffix match requirement for the - * server certificate in SubjectAltName dNSName element(s). If a - * matching dNSName is found, this constraint is met. If no dNSName - * values are present, this constraint is matched against SubjectName CN - * using same suffix match comparison. Suffix match here means that the - * host/domain name is compared one label at a time starting from the - * top-level domain and all the labels in domain_suffix_match shall be - * included in the certificate. The certificate may include additional - * sub-level labels in addition to the required labels. + * If set, this semicolon deliminated list of FQDNs is used as suffix + * match requirements for the server certificate in SubjectAltName + * dNSName element(s). If a matching dNSName is found against any of the + * specified values, this constraint is met. If no dNSName values are + * present, this constraint is matched against SubjectName CN using same + * suffix match comparison. Suffix match here means that the host/domain + * name is compared case-insentively one label at a time starting from + * the top-level domain and all the labels in domain_suffix_match shall + * 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 - * 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; @@ -286,6 +289,12 @@ struct eap_peer_config { * no subdomains or wildcard matches are allowed. Case-insensitive * comparison is used, so "Example.com" matches "example.com", but would * 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; diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf index 9a472a5ba..a66253fbd 100644 --- a/wpa_supplicant/wpa_supplicant.conf +++ b/wpa_supplicant/wpa_supplicant.conf @@ -1180,6 +1180,12 @@ fast_reauth=1 # certificate may include additional sub-level labels in addition to the # 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 # test.example.com but would not match test-example.com. # domain_match: Constraint for server domain name @@ -1192,6 +1198,12 @@ fast_reauth=1 # no subdomains or wildcard matches are allowed. Case-insensitive # comparison is used, so "Example.com" matches "example.com", but would # 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 # (string with field-value pairs, e.g., "peapver=0" or # "peapver=1 peaplabel=1")