From cebee30f3170b5104a41bd27ac5f98615ed57656 Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Wed, 14 Jan 2015 15:31:28 +0200 Subject: [PATCH] Add domain_match network profile parameter This is similar with domain_suffix_match, but required a full match of the domain name rather than allowing suffix match (subdomains) or wildcard certificates. Signed-off-by: Jouni Malinen --- src/crypto/tls.h | 10 ++++-- src/crypto/tls_gnutls.c | 36 +++++++++++++++++++ src/crypto/tls_internal.c | 5 +++ src/crypto/tls_openssl.c | 55 ++++++++++++++++++++++-------- src/crypto/tls_schannel.c | 5 +++ src/eap_peer/eap_config.h | 23 +++++++++++++ src/eap_peer/eap_tls_common.c | 2 ++ wpa_supplicant/config.c | 4 +++ wpa_supplicant/config_file.c | 2 ++ wpa_supplicant/wpa_supplicant.conf | 13 ++++++- 10 files changed, 138 insertions(+), 17 deletions(-) diff --git a/src/crypto/tls.h b/src/crypto/tls.h index 202ada8b2..9ae95a66c 100644 --- a/src/crypto/tls.h +++ b/src/crypto/tls.h @@ -41,7 +41,8 @@ enum tls_fail_reason { TLS_FAIL_ALTSUBJECT_MISMATCH = 6, TLS_FAIL_BAD_CERTIFICATE = 7, TLS_FAIL_SERVER_CHAIN_PROBE = 8, - TLS_FAIL_DOMAIN_SUFFIX_MISMATCH = 9 + TLS_FAIL_DOMAIN_SUFFIX_MISMATCH = 9, + TLS_FAIL_DOMAIN_MISMATCH = 10, }; @@ -107,7 +108,11 @@ struct tls_config { * @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 + * certificate or %NULL to allow all domain names. This may allow subdomains an + * wildcard certificates. Each domain name label must have a full 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. * @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 @@ -151,6 +156,7 @@ struct tls_connection_params { const char *subject_match; const char *altsubject_match; const char *suffix_match; + const char *domain_match; const char *client_cert; const u8 *client_cert_blob; size_t client_cert_blob_len; diff --git a/src/crypto/tls_gnutls.c b/src/crypto/tls_gnutls.c index f2eacb5d0..65db6fcc2 100644 --- a/src/crypto/tls_gnutls.c +++ b/src/crypto/tls_gnutls.c @@ -58,6 +58,7 @@ struct tls_connection { gnutls_certificate_credentials_t xcred; char *suffix_match; + char *domain_match; unsigned int flags; }; @@ -280,6 +281,7 @@ void tls_connection_deinit(void *ssl_ctx, struct tls_connection *conn) wpabuf_free(conn->push_buf); wpabuf_free(conn->pull_buf); os_free(conn->suffix_match); + os_free(conn->domain_match); os_free(conn); } @@ -363,6 +365,21 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn, return -1; } +#if GNUTLS_VERSION_NUMBER >= 0x030300 + os_free(conn->domain_match); + conn->domain_match = NULL; + if (params->domain_match) { + conn->domain_match = os_strdup(params->domain_match); + if (conn->domain_match == NULL) + return -1; + } +#else /* < 3.3.0 */ + if (params->domain_match) { + wpa_printf(MSG_INFO, "GnuTLS: domain_match not supported"); + return -1; + } +#endif /* >= 3.3.0 */ + conn->flags = params->flags; if (params->openssl_ciphers) { @@ -1111,6 +1128,25 @@ static int tls_connection_verify_peer(gnutls_session_t session) goto out; } +#if GNUTLS_VERSION_NUMBER >= 0x030300 + if (conn->domain_match && + !gnutls_x509_crt_check_hostname2( + cert, conn->domain_match, + GNUTLS_VERIFY_DO_NOT_ALLOW_WILDCARDS)) { + wpa_printf(MSG_WARNING, + "TLS: Domain match '%s' not found", + conn->domain_match); + gnutls_tls_fail_event( + conn, &certs[i], i, buf, + "Domain mismatch", + TLS_FAIL_DOMAIN_MISMATCH); + err = GNUTLS_A_BAD_CERTIFICATE; + gnutls_x509_crt_deinit(cert); + os_free(buf); + goto out; + } +#endif /* >= 3.3.0 */ + /* TODO: validate altsubject_match. * For now, any such configuration is rejected in * tls_connection_set_params() */ diff --git a/src/crypto/tls_internal.c b/src/crypto/tls_internal.c index 86375d111..0c955da29 100644 --- a/src/crypto/tls_internal.c +++ b/src/crypto/tls_internal.c @@ -205,6 +205,11 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn, return -1; } + if (params->domain_match) { + wpa_printf(MSG_INFO, "TLS: domain_match not supported"); + return -1; + } + if (params->openssl_ciphers) { wpa_printf(MSG_INFO, "GnuTLS: openssl_ciphers not supported"); return -1; diff --git a/src/crypto/tls_openssl.c b/src/crypto/tls_openssl.c index e52fd3976..e3ca0682d 100644 --- a/src/crypto/tls_openssl.c +++ b/src/crypto/tls_openssl.c @@ -96,7 +96,7 @@ struct tls_connection { ENGINE *engine; /* functional reference to the engine */ EVP_PKEY *private_key; /* the private key if using engine */ #endif /* OPENSSL_NO_ENGINE */ - char *subject_match, *altsubject_match, *suffix_match; + char *subject_match, *altsubject_match, *suffix_match, *domain_match; int read_alerts, write_alerts, failed; tls_session_ticket_cb session_ticket_cb; @@ -1098,6 +1098,7 @@ void tls_connection_deinit(void *ssl_ctx, struct tls_connection *conn) os_free(conn->subject_match); os_free(conn->altsubject_match); os_free(conn->suffix_match); + os_free(conn->domain_match); os_free(conn->session_ticket); os_free(conn); } @@ -1190,7 +1191,8 @@ 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) +static int domain_suffix_match(const u8 *val, size_t len, const char *match, + int full) { size_t i, match_len; @@ -1203,7 +1205,7 @@ static int domain_suffix_match(const u8 *val, size_t len, const char *match) } match_len = os_strlen(match); - if (match_len > len) + if (match_len > len || (full && match_len != len)) return 0; if (os_strncasecmp((const char *) val + len - match_len, match, @@ -1222,7 +1224,7 @@ static int domain_suffix_match(const u8 *val, size_t len, const char *match) #endif /* CONFIG_NATIVE_WINDOWS */ -static int tls_match_suffix(X509 *cert, const char *match) +static int tls_match_suffix(X509 *cert, const char *match, int full) { #ifdef CONFIG_NATIVE_WINDOWS /* wincrypt.h has conflicting X509_NAME definition */ @@ -1235,7 +1237,8 @@ static int tls_match_suffix(X509 *cert, const char *match) int dns_name = 0; X509_NAME *name; - wpa_printf(MSG_DEBUG, "TLS: Match domain against suffix %s", match); + wpa_printf(MSG_DEBUG, "TLS: Match domain against %s%s", + full ? "": "suffix ", match); ext = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); @@ -1248,8 +1251,10 @@ static int tls_match_suffix(X509 *cert, const char *match) gen->d.dNSName->data, gen->d.dNSName->length); if (domain_suffix_match(gen->d.dNSName->data, - gen->d.dNSName->length, match) == 1) { - wpa_printf(MSG_DEBUG, "TLS: Suffix match in dNSName found"); + gen->d.dNSName->length, match, full) == + 1) { + wpa_printf(MSG_DEBUG, "TLS: %s in dNSName found", + full ? "Match" : "Suffix match"); return 1; } } @@ -1276,13 +1281,16 @@ static int tls_match_suffix(X509 *cert, const char *match) continue; wpa_hexdump_ascii(MSG_DEBUG, "TLS: Certificate commonName", cn->data, cn->length); - if (domain_suffix_match(cn->data, cn->length, match) == 1) { - wpa_printf(MSG_DEBUG, "TLS: Suffix match in commonName found"); + if (domain_suffix_match(cn->data, cn->length, match, full) == 1) + { + wpa_printf(MSG_DEBUG, "TLS: %s in commonName found", + full ? "Match" : "Suffix match"); return 1; } } - wpa_printf(MSG_DEBUG, "TLS: No CommonName suffix match found"); + wpa_printf(MSG_DEBUG, "TLS: No CommonName %smatch found", + full ? "": "suffix "); return 0; #endif /* CONFIG_NATIVE_WINDOWS */ } @@ -1465,7 +1473,7 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) SSL *ssl; struct tls_connection *conn; struct tls_context *context; - char *match, *altmatch, *suffix_match; + char *match, *altmatch, *suffix_match, *domain_match; const char *err_str; err_cert = X509_STORE_CTX_get_current_cert(x509_ctx); @@ -1493,6 +1501,7 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) match = conn->subject_match; altmatch = conn->altsubject_match; suffix_match = conn->suffix_match; + domain_match = conn->domain_match; if (!preverify_ok && !conn->ca_cert_verify) preverify_ok = 1; @@ -1562,13 +1571,21 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) "AltSubject mismatch", TLS_FAIL_ALTSUBJECT_MISMATCH); } else if (depth == 0 && suffix_match && - !tls_match_suffix(err_cert, suffix_match)) { + !tls_match_suffix(err_cert, suffix_match, 0)) { wpa_printf(MSG_WARNING, "TLS: Domain suffix match '%s' not found", suffix_match); preverify_ok = 0; openssl_tls_fail_event(conn, err_cert, err, depth, buf, "Domain suffix mismatch", TLS_FAIL_DOMAIN_SUFFIX_MISMATCH); + } else if (depth == 0 && domain_match && + !tls_match_suffix(err_cert, domain_match, 1)) { + wpa_printf(MSG_WARNING, "TLS: Domain match '%s' not found", + domain_match); + preverify_ok = 0; + openssl_tls_fail_event(conn, err_cert, err, depth, buf, + "Domain mismatch", + TLS_FAIL_DOMAIN_MISMATCH); } else openssl_tls_cert_event(conn, err_cert, depth, buf); @@ -1832,7 +1849,8 @@ int tls_global_set_verify(void *ssl_ctx, int check_crl) static int tls_connection_set_subject_match(struct tls_connection *conn, const char *subject_match, const char *altsubject_match, - const char *suffix_match) + const char *suffix_match, + const char *domain_match) { os_free(conn->subject_match); conn->subject_match = NULL; @@ -1858,6 +1876,14 @@ static int tls_connection_set_subject_match(struct tls_connection *conn, return -1; } + os_free(conn->domain_match); + conn->domain_match = NULL; + if (domain_match) { + conn->domain_match = os_strdup(domain_match); + if (conn->domain_match == NULL) + return -1; + } + return 0; } @@ -3322,7 +3348,8 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn, if (tls_connection_set_subject_match(conn, params->subject_match, params->altsubject_match, - params->suffix_match)) + params->suffix_match, + params->domain_match)) return -1; if (engine_id && ca_cert_id) { diff --git a/src/crypto/tls_schannel.c b/src/crypto/tls_schannel.c index a43b48744..31a2c946d 100644 --- a/src/crypto/tls_schannel.c +++ b/src/crypto/tls_schannel.c @@ -707,6 +707,11 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn, return -1; } + if (params->domain_match) { + wpa_printf(MSG_INFO, "TLS: domain_match not supported"); + return -1; + } + if (params->openssl_ciphers) { wpa_printf(MSG_INFO, "GnuTLS: openssl_ciphers not supported"); return -1; diff --git a/src/eap_peer/eap_config.h b/src/eap_peer/eap_config.h index 826ddca3c..903412de1 100644 --- a/src/eap_peer/eap_config.h +++ b/src/eap_peer/eap_config.h @@ -229,6 +229,21 @@ struct eap_peer_config { */ char *domain_suffix_match; + /** + * domain_match - Constraint for server domain name + * + * If set, this FQDN is used as a full 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 full match comparison. This behavior is similar to + * domain_suffix_match, but has the requirement of a full match, i.e., + * 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". + */ + char *domain_match; + /** * ca_cert2 - File path to CA certificate file (PEM/DER) (Phase 2) * @@ -332,6 +347,14 @@ struct eap_peer_config { */ char *domain_suffix_match2; + /** + * domain_match2 - Constraint for server domain name + * + * This field is like domain_match, but used for phase 2 (inside + * EAP-TTLS/PEAP/FAST tunnel) authentication. + */ + char *domain_match2; + /** * eap_methods - Allowed EAP methods * diff --git a/src/eap_peer/eap_tls_common.c b/src/eap_peer/eap_tls_common.c index 3641a2c83..871078161 100644 --- a/src/eap_peer/eap_tls_common.c +++ b/src/eap_peer/eap_tls_common.c @@ -91,6 +91,7 @@ static void eap_tls_params_from_conf1(struct tls_connection_params *params, params->subject_match = (char *) config->subject_match; params->altsubject_match = (char *) config->altsubject_match; params->suffix_match = config->domain_suffix_match; + params->domain_match = config->domain_match; params->engine = config->engine; params->engine_id = config->engine_id; params->pin = config->pin; @@ -113,6 +114,7 @@ static void eap_tls_params_from_conf2(struct tls_connection_params *params, params->subject_match = (char *) config->subject_match2; params->altsubject_match = (char *) config->altsubject_match2; params->suffix_match = config->domain_suffix_match2; + params->domain_match = config->domain_match2; params->engine = config->engine2; params->engine_id = config->engine2_id; params->pin = config->pin2; diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index 389ad6506..c3896239f 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -1818,6 +1818,7 @@ static const struct parse_data ssid_fields[] = { { STRe(subject_match) }, { STRe(altsubject_match) }, { STRe(domain_suffix_match) }, + { STRe(domain_match) }, { STRe(ca_cert2) }, { STRe(ca_path2) }, { STRe(client_cert2) }, @@ -1827,6 +1828,7 @@ static const struct parse_data ssid_fields[] = { { STRe(subject_match2) }, { STRe(altsubject_match2) }, { STRe(domain_suffix_match2) }, + { STRe(domain_match2) }, { STRe(phase1) }, { STRe(phase2) }, { STRe(pcsc) }, @@ -2052,6 +2054,7 @@ static void eap_peer_config_free(struct eap_peer_config *eap) os_free(eap->subject_match); os_free(eap->altsubject_match); os_free(eap->domain_suffix_match); + os_free(eap->domain_match); os_free(eap->ca_cert2); os_free(eap->ca_path2); os_free(eap->client_cert2); @@ -2061,6 +2064,7 @@ static void eap_peer_config_free(struct eap_peer_config *eap) os_free(eap->subject_match2); os_free(eap->altsubject_match2); os_free(eap->domain_suffix_match2); + os_free(eap->domain_match2); os_free(eap->phase1); os_free(eap->phase2); os_free(eap->pcsc); diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index 9d73a2fb3..ce5c2eba0 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -691,6 +691,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid) STR(subject_match); STR(altsubject_match); STR(domain_suffix_match); + STR(domain_match); STR(ca_cert2); STR(ca_path2); STR(client_cert2); @@ -700,6 +701,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid) STR(subject_match2); STR(altsubject_match2); STR(domain_suffix_match2); + STR(domain_match2); STR(phase1); STR(phase2); STR(pcsc); diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf index cb515c80c..e56257818 100644 --- a/wpa_supplicant/wpa_supplicant.conf +++ b/wpa_supplicant/wpa_supplicant.conf @@ -873,7 +873,8 @@ fast_reauth=1 # /C=US/ST=CA/L=San Francisco/CN=Test AS/emailAddress=as@example.com # Note: Since this is a substring match, this cannot be used securily to # do a suffix match against a possible domain name in the CN entry. For -# such a use case, domain_suffix_match should be used instead. +# such a use case, domain_suffix_match or domain_match should be used +# instead. # altsubject_match: Semicolon separated string of entries to be matched against # the alternative subject name of the authentication server certificate. # If this string is set, the server sertificate is only accepted if it @@ -896,6 +897,16 @@ fast_reauth=1 # # 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 +# If set, this FQDN is used as a full 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 full match comparison. This behavior is similar to +# domain_suffix_match, but has the requirement of a full match, i.e., +# 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". # phase1: Phase1 (outer authentication, i.e., TLS tunnel) parameters # (string with field-value pairs, e.g., "peapver=0" or # "peapver=1 peaplabel=1")