Add AAA server domain name suffix matching constraint

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>
This commit is contained in:
Jouni Malinen 2013-10-06 18:02:16 -07:00 committed by Jouni Malinen
parent be7963b3c2
commit 01f809c7db
6 changed files with 154 additions and 5 deletions

View file

@ -40,7 +40,8 @@ enum tls_fail_reason {
TLS_FAIL_SUBJECT_MISMATCH = 5, TLS_FAIL_SUBJECT_MISMATCH = 5,
TLS_FAIL_ALTSUBJECT_MISMATCH = 6, TLS_FAIL_ALTSUBJECT_MISMATCH = 6,
TLS_FAIL_BAD_CERTIFICATE = 7, 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 { union tls_event_data {
@ -96,6 +97,8 @@ struct tls_config {
* %NULL to allow all subjects * %NULL to allow all subjects
* @altsubject_match: String to match in the alternative subject of the peer * @altsubject_match: String to match in the alternative subject of the peer
* certificate or %NULL to allow all alternative subjects * 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 * @client_cert: File or reference name for client X.509 certificate in PEM or
* DER format * DER format
* @client_cert_blob: client_cert as inlined data or %NULL if not used * @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 *ca_path;
const char *subject_match; const char *subject_match;
const char *altsubject_match; const char *altsubject_match;
const char *suffix_match;
const char *client_cert; const char *client_cert;
const u8 *client_cert_blob; const u8 *client_cert_blob;
size_t client_cert_blob_len; size_t client_cert_blob_len;

View file

@ -79,7 +79,7 @@ struct tls_connection {
ENGINE *engine; /* functional reference to the engine */ ENGINE *engine; /* functional reference to the engine */
EVP_PKEY *private_key; /* the private key if using engine */ EVP_PKEY *private_key; /* the private key if using engine */
#endif /* OPENSSL_NO_ENGINE */ #endif /* OPENSSL_NO_ENGINE */
char *subject_match, *altsubject_match; char *subject_match, *altsubject_match, *suffix_match;
int read_alerts, write_alerts, failed; int read_alerts, write_alerts, failed;
tls_session_ticket_cb session_ticket_cb; 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); tls_engine_deinit(conn);
os_free(conn->subject_match); os_free(conn->subject_match);
os_free(conn->altsubject_match); os_free(conn->altsubject_match);
os_free(conn->suffix_match);
os_free(conn->session_ticket); os_free(conn->session_ticket);
os_free(conn); 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) static enum tls_fail_reason openssl_tls_fail_reason(int err)
{ {
switch (err) { switch (err) {
@ -1241,7 +1333,7 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
SSL *ssl; SSL *ssl;
struct tls_connection *conn; struct tls_connection *conn;
struct tls_context *context; struct tls_context *context;
char *match, *altmatch; char *match, *altmatch, *suffix_match;
const char *err_str; const char *err_str;
err_cert = X509_STORE_CTX_get_current_cert(x509_ctx); 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; context = conn->context;
match = conn->subject_match; match = conn->subject_match;
altmatch = conn->altsubject_match; altmatch = conn->altsubject_match;
suffix_match = conn->suffix_match;
if (!preverify_ok && !conn->ca_cert_verify) if (!preverify_ok && !conn->ca_cert_verify)
preverify_ok = 1; 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, openssl_tls_fail_event(conn, err_cert, err, depth, buf,
"AltSubject mismatch", "AltSubject mismatch",
TLS_FAIL_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 } else
openssl_tls_cert_event(conn, err_cert, depth, buf); 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, static int tls_connection_set_subject_match(struct tls_connection *conn,
const char *subject_match, const char *subject_match,
const char *altsubject_match) const char *altsubject_match,
const char *suffix_match)
{ {
os_free(conn->subject_match); os_free(conn->subject_match);
conn->subject_match = NULL; conn->subject_match = NULL;
@ -1624,6 +1726,14 @@ static int tls_connection_set_subject_match(struct tls_connection *conn,
return -1; 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; 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, if (tls_connection_set_subject_match(conn,
params->subject_match, params->subject_match,
params->altsubject_match)) params->altsubject_match,
params->suffix_match))
return -1; return -1;
if (params->engine && params->ca_cert_id) { if (params->engine && params->ca_cert_id) {

View file

@ -207,6 +207,24 @@ struct eap_peer_config {
*/ */
u8 *altsubject_match; 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) * ca_cert2 - File path to CA certificate file (PEM/DER) (Phase 2)
* *
@ -302,6 +320,14 @@ struct eap_peer_config {
*/ */
u8 *altsubject_match2; 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 * eap_methods - Allowed EAP methods
* *

View 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->dh_file = (char *) config->dh_file;
params->subject_match = (char *) config->subject_match; params->subject_match = (char *) config->subject_match;
params->altsubject_match = (char *) config->altsubject_match; params->altsubject_match = (char *) config->altsubject_match;
params->suffix_match = config->domain_suffix_match;
params->engine = config->engine; params->engine = config->engine;
params->engine_id = config->engine_id; params->engine_id = config->engine_id;
params->pin = config->pin; 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->dh_file = (char *) config->dh_file2;
params->subject_match = (char *) config->subject_match2; params->subject_match = (char *) config->subject_match2;
params->altsubject_match = (char *) config->altsubject_match2; params->altsubject_match = (char *) config->altsubject_match2;
params->suffix_match = config->domain_suffix_match2;
params->engine = config->engine2; params->engine = config->engine2;
params->engine_id = config->engine2_id; params->engine_id = config->engine2_id;
params->pin = config->pin2; params->pin = config->pin2;

View file

@ -1579,6 +1579,7 @@ static const struct parse_data ssid_fields[] = {
{ STRe(dh_file) }, { STRe(dh_file) },
{ STRe(subject_match) }, { STRe(subject_match) },
{ STRe(altsubject_match) }, { STRe(altsubject_match) },
{ STRe(domain_suffix_match) },
{ STRe(ca_cert2) }, { STRe(ca_cert2) },
{ STRe(ca_path2) }, { STRe(ca_path2) },
{ STRe(client_cert2) }, { STRe(client_cert2) },
@ -1587,6 +1588,7 @@ static const struct parse_data ssid_fields[] = {
{ STRe(dh_file2) }, { STRe(dh_file2) },
{ STRe(subject_match2) }, { STRe(subject_match2) },
{ STRe(altsubject_match2) }, { STRe(altsubject_match2) },
{ STRe(domain_suffix_match2) },
{ STRe(phase1) }, { STRe(phase1) },
{ STRe(phase2) }, { STRe(phase2) },
{ STRe(pcsc) }, { 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->dh_file);
os_free(eap->subject_match); os_free(eap->subject_match);
os_free(eap->altsubject_match); os_free(eap->altsubject_match);
os_free(eap->domain_suffix_match);
os_free(eap->ca_cert2); os_free(eap->ca_cert2);
os_free(eap->ca_path2); os_free(eap->ca_path2);
os_free(eap->client_cert2); 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->dh_file2);
os_free(eap->subject_match2); os_free(eap->subject_match2);
os_free(eap->altsubject_match2); os_free(eap->altsubject_match2);
os_free(eap->domain_suffix_match2);
os_free(eap->phase1); os_free(eap->phase1);
os_free(eap->phase2); os_free(eap->phase2);
os_free(eap->pcsc); os_free(eap->pcsc);

View file

@ -667,6 +667,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid)
STR(dh_file); STR(dh_file);
STR(subject_match); STR(subject_match);
STR(altsubject_match); STR(altsubject_match);
STR(domain_suffix_match);
STR(ca_cert2); STR(ca_cert2);
STR(ca_path2); STR(ca_path2);
STR(client_cert2); STR(client_cert2);
@ -675,6 +676,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid)
STR(dh_file2); STR(dh_file2);
STR(subject_match2); STR(subject_match2);
STR(altsubject_match2); STR(altsubject_match2);
STR(domain_suffix_match2);
STR(phase1); STR(phase1);
STR(phase2); STR(phase2);
STR(pcsc); STR(pcsc);