Backport of:

commit cebee30f3170b5104a41bd27ac5f98615ed57656
Author: Jouni Malinen <j@w1.fi>
Date:   Wed Jan 14 15:31:28 2015 +0200

    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 <j@w1.fi>

================================================================================
--- wpa_supplicant-0.7.1/src/crypto/tls.h
+++ wpa_supplicant-0.7.1/src/crypto/tls.h
@@ -47,7 +47,8 @@
 	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
 };
 
 union tls_event_data {
@@ -95,6 +96,9 @@
  * 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
+ * @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
@@ -136,6 +140,7 @@
 	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;
--- wpa_supplicant-0.7.1/src/crypto/tls_internal.c
+++ wpa_supplicant-0.7.1/src/crypto/tls_internal.c
@@ -172,6 +172,11 @@
 	if (cred == NULL)
 		return -1;
 
+	if (params->domain_match) {
+		wpa_printf(MSG_INFO, "TLS: domain_match not supported");
+		return -1;
+	}
+
 	if (tlsv1_set_ca_cert(cred, params->ca_cert,
 			      params->ca_cert_blob, params->ca_cert_blob_len,
 			      params->ca_path)) {
--- wpa_supplicant-0.7.1/src/crypto/tls_openssl.c
+++ wpa_supplicant-0.7.1/src/crypto/tls_openssl.c
@@ -66,7 +66,7 @@
 	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;
@@ -954,6 +954,7 @@
 	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);
 }
@@ -1044,7 +1045,8 @@
 }
 
 
-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;
 
@@ -1057,7 +1059,7 @@
 	}
 
 	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,
@@ -1075,20 +1077,22 @@
 }
 
 
-static int tls_match_suffix(X509 *cert, const char *match)
+static int tls_match_suffix(X509 *cert, const char *match, int full)
 {
 	GENERAL_NAME *gen;
 	void *ext;
 	int i;
+	int j;
 	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);
 
-	for (i = 0; ext && i < sk_GENERAL_NAME_num(ext); i++) {
-		gen = sk_GENERAL_NAME_value(ext, i);
+	for (j = 0; ext && j < sk_GENERAL_NAME_num(ext); j++) {
+		gen = sk_GENERAL_NAME_value(ext, j);
 		if (gen->type != GEN_DNS)
 			continue;
 		dns_name++;
@@ -1096,8 +1100,10 @@
 				  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;
 		}
 	}
@@ -1124,13 +1130,16 @@
 			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");
-			return 1;
-		}
+		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 %s match found",
+		   full ? "": "suffix ");
 	return 0;
 }
 
@@ -1260,7 +1269,7 @@
 	int err, depth;
 	SSL *ssl;
 	struct tls_connection *conn;
-	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);
@@ -1274,6 +1283,7 @@
 	match = conn ? conn->subject_match : NULL;
 	altmatch = conn ? conn->altsubject_match : NULL;
 	suffix_match = conn ? conn->suffix_match : NULL;
+	domain_match = conn ? conn->domain_match : NULL;
 
 	if (!preverify_ok && !conn->ca_cert_verify)
 		preverify_ok = 1;
@@ -1336,13 +1346,21 @@
 				       "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);
 
@@ -1572,7 +1590,8 @@
 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;
@@ -1598,6 +1617,14 @@
 			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;
 }
 
@@ -2734,7 +2761,8 @@
 	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 (params->engine && params->ca_cert_id) {
--- wpa_supplicant-0.7.1/src/eap_peer/eap_config.h
+++ wpa_supplicant-0.7.1/src/eap_peer/eap_config.h
@@ -228,6 +228,21 @@
 	 */
 	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 @@
 	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
 	 *
 	 * (vendor=EAP_VENDOR_IETF,method=EAP_TYPE_NONE) terminated list of
--- wpa_supplicant-0.7.1/src/eap_peer/eap_tls_common.c
+++ wpa_supplicant-0.7.1/src/eap_peer/eap_tls_common.c
@@ -69,6 +69,7 @@
 	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;
@@ -91,6 +92,7 @@
 	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;
--- wpa_supplicant-0.7.1/wpa_supplicant/config.c
+++ wpa_supplicant-0.7.1/wpa_supplicant/config.c
@@ -1413,6 +1413,7 @@
 	{ STRe(subject_match) },
 	{ STRe(altsubject_match) },
 	{ STRe(domain_suffix_match) },
+	{ STRe(domain_match) },
 	{ STRe(ca_cert2) },
 	{ STRe(ca_path2) },
 	{ STRe(client_cert2) },
@@ -1422,6 +1423,7 @@
 	{ STRe(subject_match2) },
 	{ STRe(altsubject_match2) },
 	{ STRe(domain_suffix_match2) },
+	{ STRe(domain_match2) },
 	{ STRe(phase1) },
 	{ STRe(phase2) },
 	{ STRe(pcsc) },
@@ -1582,6 +1584,7 @@
 	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);
@@ -1591,6 +1594,7 @@
 	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);
--- wpa_supplicant-0.7.1/wpa_supplicant/config_file.c
+++ wpa_supplicant-0.7.1/wpa_supplicant/config_file.c
@@ -757,6 +757,7 @@
 	STR(subject_match);
 	STR(altsubject_match);
 	STR(domain_suffix_match);
+	STR(domain_match);
 	STR(ca_cert2);
 	STR(ca_path2);
 	STR(client_cert2);
@@ -766,6 +767,7 @@
 	STR(subject_match2);
 	STR(altsubject_match2);
 	STR(domain_suffix_match2);
+	STR(domain_match2);
 	STR(phase1);
 	STR(phase2);
 	STR(pcsc);
--- wpa_supplicant-0.7.1/wpa_supplicant/wpa_supplicant.conf
+++ wpa_supplicant-0.7.1/wpa_supplicant/wpa_supplicant.conf
@@ -447,6 +447,10 @@
 #	sertificate is only accepted if it contains this string in the subject.
 #	The subject string is in following format:
 #	/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_match should be used instead.
+#	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
@@ -455,6 +459,14 @@
 #	Example: EMAIL:server@example.com
 #	Example: DNS:server.example.com;DNS:server2.example.com
 #	Following types are supported: EMAIL, DNS, URI
+# 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. 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")
