]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
Add AAA server domain name suffix matching constraint
authorJouni Malinen <jouni@qca.qualcomm.com>
Mon, 7 Oct 2013 01:02:16 +0000 (18:02 -0700)
committerJouni Malinen <j@w1.fi>
Fri, 18 Oct 2013 10:34:26 +0000 (13:34 +0300)
The new domain_suffix_match (and domain_suffix_match2 for Phase 2
EAP-TLS) can now be used to specify an additional constraint for the
server certificate domain name. If set, one of the dNSName values (or if
no dNSName is present, one of the commonName values) in the certificate
must have a suffix match with the specified value. Suffix match is done
based on full domain name labels, i.e., "example.com" matches
"test.example.com" but not "test-example.com".

Signed-hostap: Jouni Malinen <jouni@qca.qualcomm.com>

src/crypto/tls.h
src/crypto/tls_openssl.c
src/eap_peer/eap_config.h
src/eap_peer/eap_tls_common.c
wpa_supplicant/config.c
wpa_supplicant/config_file.c

index 2fdaa0264a62291a7792dd3aeb7f6125c7f4a369..feba13ffca8b537c06fcf9565153d5206f5a5320 100644 (file)
@@ -40,7 +40,8 @@ enum tls_fail_reason {
        TLS_FAIL_SUBJECT_MISMATCH = 5,
        TLS_FAIL_ALTSUBJECT_MISMATCH = 6,
        TLS_FAIL_BAD_CERTIFICATE = 7,
-       TLS_FAIL_SERVER_CHAIN_PROBE = 8
+       TLS_FAIL_SERVER_CHAIN_PROBE = 8,
+       TLS_FAIL_DOMAIN_SUFFIX_MISMATCH = 9
 };
 
 union tls_event_data {
@@ -96,6 +97,8 @@ 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
  * @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
@@ -137,6 +140,7 @@ struct tls_connection_params {
        const char *ca_path;
        const char *subject_match;
        const char *altsubject_match;
+       const char *suffix_match;
        const char *client_cert;
        const u8 *client_cert_blob;
        size_t client_cert_blob_len;
index 56011d1728433a62cbad6743202005c2be8e8327..3df2bd2ca60f9e5fa6ab0e843e35e7d2629affb9 100644 (file)
@@ -79,7 +79,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;
+       char *subject_match, *altsubject_match, *suffix_match;
        int read_alerts, write_alerts, failed;
 
        tls_session_ticket_cb session_ticket_cb;
@@ -1023,6 +1023,7 @@ void tls_connection_deinit(void *ssl_ctx, struct tls_connection *conn)
        tls_engine_deinit(conn);
        os_free(conn->subject_match);
        os_free(conn->altsubject_match);
+       os_free(conn->suffix_match);
        os_free(conn->session_ticket);
        os_free(conn);
 }
@@ -1113,6 +1114,97 @@ static int tls_match_altsubject(X509 *cert, const char *match)
 }
 
 
+static int domain_suffix_match(const u8 *val, size_t len, const char *match)
+{
+       size_t i, match_len;
+
+       /* Check for embedded nuls that could mess up suffix matching */
+       for (i = 0; i < len; i++) {
+               if (val[i] == '\0') {
+                       wpa_printf(MSG_DEBUG, "TLS: Embedded null in a string - reject");
+                       return 0;
+               }
+       }
+
+       match_len = os_strlen(match);
+       if (match_len > len)
+               return 0;
+
+       if (os_strncasecmp((const char *) val + len - match_len, match,
+                          match_len) != 0)
+               return 0; /* no match */
+
+       if (match_len == len)
+               return 1; /* exact match */
+
+       if (val[len - match_len - 1] == '.')
+               return 1; /* full label match completes suffix match */
+
+       wpa_printf(MSG_DEBUG, "TLS: Reject due to incomplete label match");
+       return 0;
+}
+
+
+static int tls_match_suffix(X509 *cert, const char *match)
+{
+       GENERAL_NAME *gen;
+       void *ext;
+       int i;
+       int dns_name = 0;
+       X509_NAME *name;
+
+       wpa_printf(MSG_DEBUG, "TLS: Match domain against suffix %s", 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);
+               if (gen->type != GEN_DNS)
+                       continue;
+               dns_name++;
+               wpa_hexdump_ascii(MSG_DEBUG, "TLS: Certificate dNSName",
+                                 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");
+                       return 1;
+               }
+       }
+
+       if (dns_name) {
+               wpa_printf(MSG_DEBUG, "TLS: None of the dNSName(s) matched");
+               return 0;
+       }
+
+       name = X509_get_subject_name(cert);
+       i = -1;
+       for (;;) {
+               X509_NAME_ENTRY *e;
+               ASN1_STRING *cn;
+
+               i = X509_NAME_get_index_by_NID(name, NID_commonName, i);
+               if (i == -1)
+                       break;
+               e = X509_NAME_get_entry(name, i);
+               if (e == NULL)
+                       continue;
+               cn = X509_NAME_ENTRY_get_data(e);
+               if (cn == NULL)
+                       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;
+               }
+       }
+
+       wpa_printf(MSG_DEBUG, "TLS: No CommonName suffix match found");
+       return 0;
+}
+
+
 static enum tls_fail_reason openssl_tls_fail_reason(int err)
 {
        switch (err) {
@@ -1241,7 +1333,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;
+       char *match, *altmatch, *suffix_match;
        const char *err_str;
 
        err_cert = X509_STORE_CTX_get_current_cert(x509_ctx);
@@ -1263,6 +1355,7 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
        context = conn->context;
        match = conn->subject_match;
        altmatch = conn->altsubject_match;
+       suffix_match = conn->suffix_match;
 
        if (!preverify_ok && !conn->ca_cert_verify)
                preverify_ok = 1;
@@ -1331,6 +1424,14 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
                openssl_tls_fail_event(conn, err_cert, err, depth, buf,
                                       "AltSubject mismatch",
                                       TLS_FAIL_ALTSUBJECT_MISMATCH);
+       } else if (depth == 0 && suffix_match &&
+                  !tls_match_suffix(err_cert, suffix_match)) {
+               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
                openssl_tls_cert_event(conn, err_cert, depth, buf);
 
@@ -1606,7 +1707,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 *altsubject_match,
+                                           const char *suffix_match)
 {
        os_free(conn->subject_match);
        conn->subject_match = NULL;
@@ -1624,6 +1726,14 @@ static int tls_connection_set_subject_match(struct tls_connection *conn,
                        return -1;
        }
 
+       os_free(conn->suffix_match);
+       conn->suffix_match = NULL;
+       if (suffix_match) {
+               conn->suffix_match = os_strdup(suffix_match);
+               if (conn->suffix_match == NULL)
+                       return -1;
+       }
+
        return 0;
 }
 
@@ -2981,7 +3091,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->altsubject_match,
+                                            params->suffix_match))
                return -1;
 
        if (params->engine && params->ca_cert_id) {
index 42f525b9e6ccbd9a466f767900b334138b4937d7..0392f871e83f54f8b53f7bafb1314f38a9f8473e 100644 (file)
@@ -207,6 +207,24 @@ struct eap_peer_config {
         */
        u8 *altsubject_match;
 
+       /**
+        * 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 SubjetName 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.
+        *
+        * For example, domain_suffix_match=example.com would match
+        * test.example.com but would not match test-example.com.
+        */
+       char *domain_suffix_match;
+
        /**
         * ca_cert2 - File path to CA certificate file (PEM/DER) (Phase 2)
         *
@@ -302,6 +320,14 @@ struct eap_peer_config {
         */
        u8 *altsubject_match2;
 
+       /**
+        * domain_suffix_match2 - Constraint for server domain name
+        *
+        * This field is like domain_suffix_match, but used for phase 2 (inside
+        * EAP-TTLS/PEAP/FAST tunnel) authentication.
+        */
+       char *domain_suffix_match2;
+
        /**
         * eap_methods - Allowed EAP methods
         *
index be8c301809e0ea346a2ec60fe3a9331c6b63a268..008af37b172a77d4fb4ba2ead02f979d80da32aa 100644 (file)
@@ -78,6 +78,7 @@ static void eap_tls_params_from_conf1(struct tls_connection_params *params,
        params->dh_file = (char *) config->dh_file;
        params->subject_match = (char *) config->subject_match;
        params->altsubject_match = (char *) config->altsubject_match;
+       params->suffix_match = config->domain_suffix_match;
        params->engine = config->engine;
        params->engine_id = config->engine_id;
        params->pin = config->pin;
@@ -99,6 +100,7 @@ static void eap_tls_params_from_conf2(struct tls_connection_params *params,
        params->dh_file = (char *) config->dh_file2;
        params->subject_match = (char *) config->subject_match2;
        params->altsubject_match = (char *) config->altsubject_match2;
+       params->suffix_match = config->domain_suffix_match2;
        params->engine = config->engine2;
        params->engine_id = config->engine2_id;
        params->pin = config->pin2;
index 2b173650e7a884e0832b1681ed2c741496fbd092..c4fc7b67026a05c148aa61812443309ba7421ad3 100644 (file)
@@ -1579,6 +1579,7 @@ static const struct parse_data ssid_fields[] = {
        { STRe(dh_file) },
        { STRe(subject_match) },
        { STRe(altsubject_match) },
+       { STRe(domain_suffix_match) },
        { STRe(ca_cert2) },
        { STRe(ca_path2) },
        { STRe(client_cert2) },
@@ -1587,6 +1588,7 @@ static const struct parse_data ssid_fields[] = {
        { STRe(dh_file2) },
        { STRe(subject_match2) },
        { STRe(altsubject_match2) },
+       { STRe(domain_suffix_match2) },
        { STRe(phase1) },
        { STRe(phase2) },
        { STRe(pcsc) },
@@ -1786,6 +1788,7 @@ static void eap_peer_config_free(struct eap_peer_config *eap)
        os_free(eap->dh_file);
        os_free(eap->subject_match);
        os_free(eap->altsubject_match);
+       os_free(eap->domain_suffix_match);
        os_free(eap->ca_cert2);
        os_free(eap->ca_path2);
        os_free(eap->client_cert2);
@@ -1794,6 +1797,7 @@ static void eap_peer_config_free(struct eap_peer_config *eap)
        os_free(eap->dh_file2);
        os_free(eap->subject_match2);
        os_free(eap->altsubject_match2);
+       os_free(eap->domain_suffix_match2);
        os_free(eap->phase1);
        os_free(eap->phase2);
        os_free(eap->pcsc);
index 0d2bd8cf2a31916c05564be12757bdc615d180bf..6512a8270d3e4509fcc65ee5b5e6b581e9c9a0ac 100644 (file)
@@ -667,6 +667,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid)
        STR(dh_file);
        STR(subject_match);
        STR(altsubject_match);
+       STR(domain_suffix_match);
        STR(ca_cert2);
        STR(ca_path2);
        STR(client_cert2);
@@ -675,6 +676,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid)
        STR(dh_file2);
        STR(subject_match2);
        STR(altsubject_match2);
+       STR(domain_suffix_match2);
        STR(phase1);
        STR(phase2);
        STR(pcsc);