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 2fdaa02..feba13f 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 56011d1..3df2bd2 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 42f525b..0392f87 100644 (file)
@@ -208,6 +208,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)
         *
         * This file can have one or more trusted CA certificates. If ca_cert2
@@ -303,6 +321,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
         *
         * (vendor=EAP_VENDOR_IETF,method=EAP_TYPE_NONE) terminated list of
index be8c301..008af37 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 2b17365..c4fc7b6 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 0d2bd8c..6512a82 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);