Add domain_match network profile parameter
authorJouni Malinen <j@w1.fi>
Wed, 14 Jan 2015 13:31:28 +0000 (15:31 +0200)
committerJouni Malinen <j@w1.fi>
Wed, 14 Jan 2015 13:45:18 +0000 (15:45 +0200)
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>
src/crypto/tls.h
src/crypto/tls_gnutls.c
src/crypto/tls_internal.c
src/crypto/tls_openssl.c
src/crypto/tls_schannel.c
src/eap_peer/eap_config.h
src/eap_peer/eap_tls_common.c
wpa_supplicant/config.c
wpa_supplicant/config_file.c
wpa_supplicant/wpa_supplicant.conf

index 202ada8..9ae95a6 100644 (file)
@@ -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;
index f2eacb5..65db6fc 100644 (file)
@@ -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() */
index 86375d1..0c955da 100644 (file)
@@ -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;
index e52fd39..e3ca068 100644 (file)
@@ -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) {
index a43b487..31a2c94 100644 (file)
@@ -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;
index 826ddca..903412d 100644 (file)
@@ -230,6 +230,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)
         *
         * This file can have one or more trusted CA certificates. If ca_cert2
@@ -333,6 +348,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
         *
         * (vendor=EAP_VENDOR_IETF,method=EAP_TYPE_NONE) terminated list of
index 3641a2c..8710781 100644 (file)
@@ -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;
index 389ad65..c389623 100644 (file)
@@ -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);
index 9d73a2f..ce5c2eb 100644 (file)
@@ -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);
index cb515c8..e562578 100644 (file)
@@ -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")