diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index 6a3bcf7b5..95f7db714 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -44,6 +44,10 @@ extern "C" { #define WPA_EVENT_EAP_PROPOSED_METHOD "CTRL-EVENT-EAP-PROPOSED-METHOD " /** EAP method selected */ #define WPA_EVENT_EAP_METHOD "CTRL-EVENT-EAP-METHOD " +/** EAP peer certificate from TLS */ +#define WPA_EVENT_EAP_PEER_CERT "CTRL-EVENT-EAP-PEER-CERT " +/** EAP TLS certificate chain validation error */ +#define WPA_EVENT_EAP_TLS_CERT_ERROR "CTRL-EVENT-EAP-TLS-CERT-ERROR " /** EAP authentication completed successfully */ #define WPA_EVENT_EAP_SUCCESS "CTRL-EVENT-EAP-SUCCESS " /** EAP authentication failed (EAP-Failure received) */ diff --git a/src/crypto/tls.h b/src/crypto/tls.h index 861ae2091..0928b5ba4 100644 --- a/src/crypto/tls.h +++ b/src/crypto/tls.h @@ -1,6 +1,6 @@ /* * SSL/TLS interface definition - * Copyright (c) 2004-2009, Jouni Malinen + * Copyright (c) 2004-2010, Jouni Malinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -28,11 +28,54 @@ struct tls_keys { size_t inner_secret_len; }; +enum tls_event { + TLS_CERT_CHAIN_FAILURE, + TLS_PEER_CERTIFICATE +}; + +/* + * Note: These are used as identifier with external programs and as such, the + * values must not be changed. + */ +enum tls_fail_reason { + TLS_FAIL_UNSPECIFIED = 0, + TLS_FAIL_UNTRUSTED = 1, + TLS_FAIL_REVOKED = 2, + TLS_FAIL_NOT_YET_VALID = 3, + TLS_FAIL_EXPIRED = 4, + TLS_FAIL_SUBJECT_MISMATCH = 5, + TLS_FAIL_ALTSUBJECT_MISMATCH = 6, + TLS_FAIL_BAD_CERTIFICATE = 7, + TLS_FAIL_SERVER_CHAIN_PROBE = 8 +}; + +union tls_event_data { + struct { + int depth; + const char *subject; + enum tls_fail_reason reason; + const char *reason_txt; + const struct wpabuf *cert; + } cert_fail; + + struct { + int depth; + const char *subject; + const struct wpabuf *cert; + const u8 *hash; + size_t hash_len; + } peer_cert; +}; + struct tls_config { const char *opensc_engine_path; const char *pkcs11_engine_path; const char *pkcs11_module_path; int fips_mode; + + void (*event_cb)(void *ctx, enum tls_event ev, + union tls_event_data *data); + void *cb_ctx; }; #define TLS_CONN_ALLOW_SIGN_RSA_MD5 BIT(0) diff --git a/src/crypto/tls_openssl.c b/src/crypto/tls_openssl.c index 76b23a661..a7ce57dd8 100644 --- a/src/crypto/tls_openssl.c +++ b/src/crypto/tls_openssl.c @@ -1,6 +1,6 @@ /* * SSL/TLS interface functions for OpenSSL - * Copyright (c) 2004-2009, Jouni Malinen + * Copyright (c) 2004-2010, Jouni Malinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -29,6 +29,7 @@ #endif /* OPENSSL_NO_ENGINE */ #include "common.h" +#include "crypto.h" #include "tls.h" #if OPENSSL_VERSION_NUMBER >= 0x0090800fL @@ -49,6 +50,15 @@ static int tls_openssl_ref_count = 0; +struct tls_global { + void (*event_cb)(void *ctx, enum tls_event ev, + union tls_event_data *data); + void *cb_ctx; +}; + +static struct tls_global *tls_global = NULL; + + struct tls_connection { SSL *ssl; BIO *ssl_in, *ssl_out; @@ -65,6 +75,12 @@ struct tls_connection { /* SessionTicket received from OpenSSL hello_extension_cb (server) */ u8 *session_ticket; size_t session_ticket_len; + + int ca_cert_verify:1; + int cert_probe:1; + int server_cert_only:1; + + u8 srv_cert_hash[32]; }; @@ -665,6 +681,14 @@ void * tls_init(const struct tls_config *conf) SSL_CTX *ssl; if (tls_openssl_ref_count == 0) { + tls_global = os_zalloc(sizeof(*tls_global)); + if (tls_global == NULL) + return NULL; + if (conf) { + tls_global->event_cb = conf->event_cb; + tls_global->cb_ctx = conf->cb_ctx; + } + #ifdef CONFIG_FIPS #ifdef OPENSSL_FIPS if (conf && conf->fips_mode) { @@ -750,6 +774,8 @@ void tls_deinit(void *ssl_ctx) ERR_remove_state(0); ERR_free_strings(); EVP_cleanup(); + os_free(tls_global); + tls_global = NULL; } } @@ -1016,6 +1042,124 @@ static int tls_match_altsubject(X509 *cert, const char *match) } +static enum tls_fail_reason openssl_tls_fail_reason(int err) +{ + switch (err) { + case X509_V_ERR_CERT_REVOKED: + return TLS_FAIL_REVOKED; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CRL_NOT_YET_VALID: + return TLS_FAIL_NOT_YET_VALID; + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_CRL_HAS_EXPIRED: + return TLS_FAIL_EXPIRED; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + case X509_V_ERR_UNABLE_TO_GET_CRL: + case X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER: + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + case X509_V_ERR_CERT_CHAIN_TOO_LONG: + case X509_V_ERR_PATH_LENGTH_EXCEEDED: + case X509_V_ERR_INVALID_CA: + return TLS_FAIL_UNTRUSTED; + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: + case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: + case X509_V_ERR_CERT_UNTRUSTED: + case X509_V_ERR_CERT_REJECTED: + return TLS_FAIL_BAD_CERTIFICATE; + default: + return TLS_FAIL_UNSPECIFIED; + } +} + + +static struct wpabuf * get_x509_cert(X509 *cert) +{ + struct wpabuf *buf; + u8 *tmp; + + int cert_len = i2d_X509(cert, NULL); + if (cert_len <= 0) + return NULL; + + buf = wpabuf_alloc(cert_len); + if (buf == NULL) + return NULL; + + tmp = wpabuf_put(buf, cert_len); + i2d_X509(cert, &tmp); + return buf; +} + + +static void openssl_tls_fail_event(struct tls_connection *conn, + X509 *err_cert, int err, int depth, + const char *subject, const char *err_str, + enum tls_fail_reason reason) +{ + union tls_event_data ev; + struct wpabuf *cert = NULL; + + if (tls_global->event_cb == NULL) + return; + + cert = get_x509_cert(err_cert); + os_memset(&ev, 0, sizeof(ev)); + ev.cert_fail.reason = reason != TLS_FAIL_UNSPECIFIED ? + reason : openssl_tls_fail_reason(err); + ev.cert_fail.depth = depth; + ev.cert_fail.subject = subject; + ev.cert_fail.reason_txt = err_str; + ev.cert_fail.cert = cert; + tls_global->event_cb(tls_global->cb_ctx, TLS_CERT_CHAIN_FAILURE, &ev); + wpabuf_free(cert); +} + + +static void openssl_tls_cert_event(struct tls_connection *conn, + X509 *err_cert, int depth, + const char *subject) +{ + struct wpabuf *cert = NULL; + union tls_event_data ev; +#ifdef CONFIG_SHA256 + u8 hash[32]; +#endif /* CONFIG_SHA256 */ + + if (tls_global->event_cb == NULL) + return; + + os_memset(&ev, 0, sizeof(ev)); + if (conn->cert_probe) { + cert = get_x509_cert(err_cert); + ev.peer_cert.cert = cert; + } +#ifdef CONFIG_SHA256 + if (cert) { + const u8 *addr[1]; + size_t len[1]; + addr[0] = wpabuf_head(cert); + len[0] = wpabuf_len(cert); + if (sha256_vector(1, addr, len, hash) == 0) { + ev.peer_cert.hash = hash; + ev.peer_cert.hash_len = sizeof(hash); + } + } +#endif /* CONFIG_SHA256 */ + ev.peer_cert.depth = depth; + ev.peer_cert.subject = subject; + tls_global->event_cb(tls_global->cb_ctx, TLS_PEER_CERTIFICATE, &ev); + wpabuf_free(cert); +} + + static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) { char buf[256]; @@ -1024,6 +1168,7 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) SSL *ssl; struct tls_connection *conn; char *match, *altmatch; + const char *err_str; err_cert = X509_STORE_CTX_get_current_cert(x509_ctx); err = X509_STORE_CTX_get_error(x509_ctx); @@ -1036,25 +1181,76 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) match = conn ? conn->subject_match : NULL; altmatch = conn ? conn->altsubject_match : NULL; + if (!preverify_ok && !conn->ca_cert_verify) + preverify_ok = 1; + if (!preverify_ok && depth > 0 && conn->server_cert_only) + preverify_ok = 1; + + err_str = X509_verify_cert_error_string(err); + +#ifdef CONFIG_SHA256 + if (preverify_ok && depth == 0 && conn->server_cert_only) { + struct wpabuf *cert; + cert = get_x509_cert(err_cert); + if (!cert) { + wpa_printf(MSG_DEBUG, "OpenSSL: Could not fetch " + "server certificate data"); + preverify_ok = 0; + } else { + u8 hash[32]; + const u8 *addr[1]; + size_t len[1]; + addr[0] = wpabuf_head(cert); + len[0] = wpabuf_len(cert); + if (sha256_vector(1, addr, len, hash) < 0 || + os_memcmp(conn->srv_cert_hash, hash, 32) != 0) { + err_str = "Server certificate mismatch"; + err = X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN; + preverify_ok = 0; + } + wpabuf_free(cert); + } + } +#endif /* CONFIG_SHA256 */ + if (!preverify_ok) { wpa_printf(MSG_WARNING, "TLS: Certificate verification failed," - " error %d (%s) depth %d for '%s'", err, - X509_verify_cert_error_string(err), depth, buf); - } else { - wpa_printf(MSG_DEBUG, "TLS: tls_verify_cb - " - "preverify_ok=%d err=%d (%s) depth=%d buf='%s'", - preverify_ok, err, - X509_verify_cert_error_string(err), depth, buf); - if (depth == 0 && match && os_strstr(buf, match) == NULL) { - wpa_printf(MSG_WARNING, "TLS: Subject '%s' did not " - "match with '%s'", buf, match); - preverify_ok = 0; - } else if (depth == 0 && altmatch && - !tls_match_altsubject(err_cert, altmatch)) { - wpa_printf(MSG_WARNING, "TLS: altSubjectName match " - "'%s' not found", altmatch); - preverify_ok = 0; - } + " error %d (%s) depth %d for '%s'", err, err_str, + depth, buf); + openssl_tls_fail_event(conn, err_cert, err, depth, buf, + err_str, TLS_FAIL_UNSPECIFIED); + return preverify_ok; + } + + wpa_printf(MSG_DEBUG, "TLS: tls_verify_cb - preverify_ok=%d " + "err=%d (%s) ca_cert_verify=%d depth=%d buf='%s'", + preverify_ok, err, err_str, + conn->ca_cert_verify, depth, buf); + if (depth == 0 && match && os_strstr(buf, match) == NULL) { + wpa_printf(MSG_WARNING, "TLS: Subject '%s' did not " + "match with '%s'", buf, match); + preverify_ok = 0; + openssl_tls_fail_event(conn, err_cert, err, depth, buf, + "Subject mismatch", + TLS_FAIL_SUBJECT_MISMATCH); + } else if (depth == 0 && altmatch && + !tls_match_altsubject(err_cert, altmatch)) { + wpa_printf(MSG_WARNING, "TLS: altSubjectName match " + "'%s' not found", altmatch); + preverify_ok = 0; + openssl_tls_fail_event(conn, err_cert, err, depth, buf, + "AltSubject mismatch", + TLS_FAIL_ALTSUBJECT_MISMATCH); + } else + openssl_tls_cert_event(conn, err_cert, depth, buf); + + if (conn->cert_probe && preverify_ok && depth == 0) { + wpa_printf(MSG_DEBUG, "OpenSSL: Reject server certificate " + "on probe-only run"); + preverify_ok = 0; + openssl_tls_fail_event(conn, err_cert, err, depth, buf, + "Server certificate chain probe", + TLS_FAIL_SERVER_CHAIN_PROBE); } return preverify_ok; @@ -1112,6 +1308,47 @@ static int tls_connection_ca_cert(void *_ssl_ctx, struct tls_connection *conn, return -1; } + SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, tls_verify_cb); + conn->ca_cert_verify = 1; + + if (ca_cert && os_strncmp(ca_cert, "probe://", 8) == 0) { + wpa_printf(MSG_DEBUG, "OpenSSL: Probe for server certificate " + "chain"); + conn->cert_probe = 1; + conn->ca_cert_verify = 0; + return 0; + } + + if (ca_cert && os_strncmp(ca_cert, "hash://", 7) == 0) { +#ifdef CONFIG_SHA256 + const char *pos = ca_cert + 7; + if (os_strncmp(pos, "server/sha256/", 14) != 0) { + wpa_printf(MSG_DEBUG, "OpenSSL: Unsupported ca_cert " + "hash value '%s'", ca_cert); + return -1; + } + pos += 14; + if (os_strlen(pos) != 32 * 2) { + wpa_printf(MSG_DEBUG, "OpenSSL: Unexpected SHA256 " + "hash length in ca_cert '%s'", ca_cert); + return -1; + } + if (hexstr2bin(pos, conn->srv_cert_hash, 32) < 0) { + wpa_printf(MSG_DEBUG, "OpenSSL: Invalid SHA256 hash " + "value in ca_cert '%s'", ca_cert); + return -1; + } + conn->server_cert_only = 1; + wpa_printf(MSG_DEBUG, "OpenSSL: Checking only server " + "certificate match"); + return 0; +#else /* CONFIG_SHA256 */ + wpa_printf(MSG_INFO, "No SHA256 included in the build - " + "cannot validate server certificate hash"); + return -1; +#endif /* CONFIG_SHA256 */ + } + if (ca_cert_blob) { X509 *cert = d2i_X509(NULL, (OPENSSL_d2i_TYPE) &ca_cert_blob, ca_cert_blob_len); @@ -1140,7 +1377,6 @@ static int tls_connection_ca_cert(void *_ssl_ctx, struct tls_connection *conn, X509_free(cert); wpa_printf(MSG_DEBUG, "OpenSSL: %s - added ca_cert_blob " "to certificate store", __func__); - SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, tls_verify_cb); return 0; } @@ -1149,7 +1385,6 @@ static int tls_connection_ca_cert(void *_ssl_ctx, struct tls_connection *conn, 0) { wpa_printf(MSG_DEBUG, "OpenSSL: Added CA certificates from " "system certificate store"); - SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, tls_verify_cb); return 0; } #endif /* CONFIG_NATIVE_WINDOWS */ @@ -1172,7 +1407,6 @@ static int tls_connection_ca_cert(void *_ssl_ctx, struct tls_connection *conn, "certificate(s) loaded"); tls_get_errors(ssl_ctx); } - SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, tls_verify_cb); #else /* OPENSSL_NO_STDIO */ wpa_printf(MSG_DEBUG, "OpenSSL: %s - OPENSSL_NO_STDIO", __func__); @@ -1181,7 +1415,7 @@ static int tls_connection_ca_cert(void *_ssl_ctx, struct tls_connection *conn, } else { /* No ca_cert configured - do not try to verify server * certificate */ - SSL_set_verify(conn->ssl, SSL_VERIFY_NONE, NULL); + conn->ca_cert_verify = 0; } return 0; @@ -1266,10 +1500,12 @@ int tls_connection_set_verify(void *ssl_ctx, struct tls_connection *conn, return -1; if (verify_peer) { + conn->ca_cert_verify = 1; SSL_set_verify(conn->ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_CLIENT_ONCE, tls_verify_cb); } else { + conn->ca_cert_verify = 0; SSL_set_verify(conn->ssl, SSL_VERIFY_NONE, NULL); } diff --git a/src/eap_peer/eap.c b/src/eap_peer/eap.c index b337d89fd..b9f186bf1 100644 --- a/src/eap_peer/eap.c +++ b/src/eap_peer/eap.c @@ -1,6 +1,6 @@ /* * EAP peer state machines (RFC 4137) - * Copyright (c) 2004-2008, Jouni Malinen + * Copyright (c) 2004-2010, Jouni Malinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -1160,6 +1160,60 @@ static void eap_sm_parseEapReq(struct eap_sm *sm, const struct wpabuf *req) } +static void eap_peer_sm_tls_event(void *ctx, enum tls_event ev, + union tls_event_data *data) +{ + struct eap_sm *sm = ctx; + char *hash_hex = NULL; + char *cert_hex = NULL; + + switch (ev) { + case TLS_CERT_CHAIN_FAILURE: + wpa_msg(sm->msg_ctx, MSG_INFO, WPA_EVENT_EAP_TLS_CERT_ERROR + "reason=%d depth=%d subject='%s' err='%s'", + data->cert_fail.reason, + data->cert_fail.depth, + data->cert_fail.subject, + data->cert_fail.reason_txt); + break; + case TLS_PEER_CERTIFICATE: + if (data->peer_cert.hash) { + size_t len = data->peer_cert.hash_len * 2 + 1; + hash_hex = os_malloc(len); + if (hash_hex) { + wpa_snprintf_hex(hash_hex, len, + data->peer_cert.hash, + data->peer_cert.hash_len); + } + } + wpa_msg(sm->msg_ctx, MSG_INFO, WPA_EVENT_EAP_PEER_CERT + "depth=%d subject='%s'%s%s", + data->peer_cert.depth, data->peer_cert.subject, + hash_hex ? " hash=" : "", hash_hex ? hash_hex : ""); + + if (data->peer_cert.cert) { + size_t len = wpabuf_len(data->peer_cert.cert) * 2 + 1; + cert_hex = os_malloc(len); + if (cert_hex == NULL) + break; + wpa_snprintf_hex(cert_hex, len, + wpabuf_head(data->peer_cert.cert), + wpabuf_len(data->peer_cert.cert)); + wpa_msg_ctrl(sm->msg_ctx, MSG_INFO, + WPA_EVENT_EAP_PEER_CERT + "depth=%d subject='%s' cert=%s", + data->peer_cert.depth, + data->peer_cert.subject, + cert_hex); + } + break; + } + + os_free(hash_hex); + os_free(cert_hex); +} + + /** * eap_peer_sm_init - Allocate and initialize EAP peer state machine * @eapol_ctx: Context data to be used with eapol_cb calls @@ -1197,6 +1251,8 @@ struct eap_sm * eap_peer_sm_init(void *eapol_ctx, #ifdef CONFIG_FIPS tlsconf.fips_mode = 1; #endif /* CONFIG_FIPS */ + tlsconf.event_cb = eap_peer_sm_tls_event; + tlsconf.cb_ctx = sm; sm->ssl_ctx = tls_init(&tlsconf); if (sm->ssl_ctx == NULL) { wpa_printf(MSG_WARNING, "SSL: Failed to initialize TLS " diff --git a/src/eap_peer/eap_config.h b/src/eap_peer/eap_config.h index 94245c3d0..b64b68f4b 100644 --- a/src/eap_peer/eap_config.h +++ b/src/eap_peer/eap_config.h @@ -85,6 +85,15 @@ struct eap_peer_config { * Alternatively, a named configuration blob can be used by setting * this to blob://blob_name. * + * Alternatively, this can be used to only perform matching of the + * server certificate (SHA-256 hash of the DER encoded X.509 + * certificate). In this case, the possible CA certificates in the + * server certificate chain are ignored and only the server certificate + * is verified. This is configured with the following format: + * hash:://server/sha256/cert_hash_in_hex + * For example: "hash://server/sha256/ + * 5a1bc1296205e6fdbe3979728efe3920798885c1c4590b5f90f43222d239ca6a" + * * On Windows, trusted CA certificates can be loaded from the system * certificate store by setting this to cert_store://name, e.g., * ca_cert="cert_store://CA" or ca_cert="cert_store://ROOT". diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index 7b4474fc0..b7fcdf2dc 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -999,6 +999,7 @@ endif SHA256OBJS = # none by default ifdef NEED_SHA256 +CFLAGS += -DCONFIG_SHA256 SHA256OBJS += ../src/crypto/sha256.o ifdef CONFIG_INTERNAL_SHA256 SHA256OBJS += ../src/crypto/sha256-internal.o diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf index d552014df..4866ae41f 100644 --- a/wpa_supplicant/wpa_supplicant.conf +++ b/wpa_supplicant/wpa_supplicant.conf @@ -390,6 +390,16 @@ fast_reauth=1 # a trusted CA certificate should always be configured when using # EAP-TLS/TTLS/PEAP. Full path should be used since working directory may # change when wpa_supplicant is run in the background. +# +# Alternatively, this can be used to only perform matching of the server +# certificate (SHA-256 hash of the DER encoded X.509 certificate). In +# this case, the possible CA certificates in the server certificate chain +# are ignored and only the server certificate is verified. This is +# configured with the following format: +# hash:://server/sha256/cert_hash_in_hex +# For example: "hash://server/sha256/ +# 5a1bc1296205e6fdbe3979728efe3920798885c1c4590b5f90f43222d239ca6a" +# # On Windows, trusted CA certificates can be loaded from the system # certificate store by setting this to cert_store://, e.g., # ca_cert="cert_store://CA" or ca_cert="cert_store://ROOT".