From 080585c01a1e0ffc42577dd10d475f3ab01d0280 Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Sun, 30 Jun 2013 00:54:24 +0300 Subject: [PATCH] Add support for OCSP stapling to validate server certificate When using OpenSSL with TLS-based EAP methods, wpa_supplicant can now be configured to use OCSP stapling (TLS certificate status request) with ocsp=1 network block parameter. ocsp=2 can be used to require valid OCSP response before connection is allowed to continue. hostapd as EAP server can be configured to return cached OCSP response using the new ocsp_stapling_response parameter and an external mechanism for updating the response data (e.g., "openssl ocsp ..." command). This allows wpa_supplicant to verify that the server certificate has not been revoked as part of the EAP-TLS/PEAP/TTLS/FAST handshake before actual data connection has been established (i.e., when a CRL could not be fetched even if a distribution point were specified). Signed-hostap: Jouni Malinen --- hostapd/config_file.c | 3 + hostapd/hostapd.conf | 14 +++ src/ap/ap_config.c | 1 + src/ap/ap_config.h | 1 + src/ap/authsrv.c | 2 + src/crypto/tls.h | 7 +- src/crypto/tls_openssl.c | 216 ++++++++++++++++++++++++++++++++++++- src/eap_peer/eap_config.h | 11 +- src/eap_peer/eap_tls_common.c | 6 +- wpa_supplicant/config.c | 1 + wpa_supplicant/wpa_supplicant.conf | 5 + 11 files changed, 263 insertions(+), 4 deletions(-) diff --git a/hostapd/config_file.c b/hostapd/config_file.c index cd9c7ca..9cc45e9 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -1833,6 +1833,9 @@ static int hostapd_config_fill(struct hostapd_config *conf, bss->private_key_passwd = os_strdup(pos); } else if (os_strcmp(buf, "check_crl") == 0) { bss->check_crl = atoi(pos); + } else if (os_strcmp(buf, "ocsp_stapling_response") == 0) { + os_free(bss->ocsp_stapling_response); + bss->ocsp_stapling_response = os_strdup(pos); } else if (os_strcmp(buf, "dh_file") == 0) { os_free(bss->dh_file); bss->dh_file = os_strdup(pos); diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 1eaa366..de10c4e 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -677,6 +677,20 @@ eap_server=0 # 2 = check all CRLs in the certificate path #check_crl=1 +# Cached OCSP stapling response (DER encoded) +# If set, this file is sent as a certificate status response by the EAP server +# if the EAP peer requests certificate status in the ClientHello message. +# This cache file can be updated, e.g., by running following command +# periodically to get an update from the OCSP responder: +# openssl ocsp \ +# -no_nonce \ +# -CAfile /etc/hostapd.ca.pem \ +# -issuer /etc/hostapd.ca.pem \ +# -cert /etc/hostapd.server.pem \ +# -url http://ocsp.example.com:8888/ \ +# -respout /tmp/ocsp-cache.der +#ocsp_stapling_response=/tmp/ocsp-cache.der + # dh_file: File path to DH/DSA parameters file (in PEM format) # This is an optional configuration file for setting parameters for an # ephemeral DH key exchange. In most cases, the default RSA authentication does diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index 051c453..c7748da 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -440,6 +440,7 @@ static void hostapd_config_free_bss(struct hostapd_bss_config *conf) os_free(conf->server_cert); os_free(conf->private_key); os_free(conf->private_key_passwd); + os_free(conf->ocsp_stapling_response); os_free(conf->dh_file); os_free(conf->pac_opaque_encr_key); os_free(conf->eap_fast_a_id); diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index d1ae9c2..1124920 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -295,6 +295,7 @@ struct hostapd_bss_config { char *private_key; char *private_key_passwd; int check_crl; + char *ocsp_stapling_response; char *dh_file; u8 *pac_opaque_encr_key; u8 *eap_fast_a_id; diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c index d66d97e..597b8dd 100644 --- a/src/ap/authsrv.c +++ b/src/ap/authsrv.c @@ -148,6 +148,8 @@ int authsrv_init(struct hostapd_data *hapd) params.private_key = hapd->conf->private_key; params.private_key_passwd = hapd->conf->private_key_passwd; params.dh_file = hapd->conf->dh_file; + params.ocsp_stapling_response = + hapd->conf->ocsp_stapling_response; if (tls_global_set_params(hapd->ssl_ctx, ¶ms)) { wpa_printf(MSG_ERROR, "Failed to set TLS parameters"); diff --git a/src/crypto/tls.h b/src/crypto/tls.h index b61e439..2fdaa02 100644 --- a/src/crypto/tls.h +++ b/src/crypto/tls.h @@ -1,6 +1,6 @@ /* * SSL/TLS interface definition - * Copyright (c) 2004-2010, Jouni Malinen + * Copyright (c) 2004-2013, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -82,6 +82,8 @@ struct tls_config { #define TLS_CONN_ALLOW_SIGN_RSA_MD5 BIT(0) #define TLS_CONN_DISABLE_TIME_CHECKS BIT(1) #define TLS_CONN_DISABLE_SESSION_TICKET BIT(2) +#define TLS_CONN_REQUEST_OCSP BIT(3) +#define TLS_CONN_REQUIRE_OCSP BIT(4) /** * struct tls_connection_params - Parameters for TLS connection @@ -117,6 +119,8 @@ struct tls_config { * @cert_id: the certificate's id when using engine * @ca_cert_id: the CA certificate's id when using engine * @flags: Parameter options (TLS_CONN_*) + * @ocsp_stapling_response: DER encoded file with cached OCSP stapling response + * or %NULL if OCSP is not enabled * * TLS connection parameters to be configured with tls_connection_set_params() * and tls_global_set_params(). @@ -153,6 +157,7 @@ struct tls_connection_params { const char *ca_cert_id; unsigned int flags; + const char *ocsp_stapling_response; }; diff --git a/src/crypto/tls_openssl.c b/src/crypto/tls_openssl.c index 8600f5f..034c72f 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-2011, Jouni Malinen + * Copyright (c) 2004-2013, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -51,6 +51,13 @@ #endif #endif +#ifdef SSL_set_tlsext_status_type +#ifndef OPENSSL_NO_TLSEXT +#define HAVE_OCSP +#include +#endif /* OPENSSL_NO_TLSEXT */ +#endif /* SSL_set_tlsext_status_type */ + static int tls_openssl_ref_count = 0; struct tls_context { @@ -58,6 +65,7 @@ struct tls_context { union tls_event_data *data); void *cb_ctx; int cert_in_cb; + char *ocsp_stapling_response; }; static struct tls_context *tls_global = NULL; @@ -88,6 +96,9 @@ struct tls_connection { u8 srv_cert_hash[32]; unsigned int flags; + + X509 *peer_cert; + X509 *peer_issuer; }; @@ -827,6 +838,8 @@ void tls_deinit(void *ssl_ctx) ERR_remove_state(0); ERR_free_strings(); EVP_cleanup(); + os_free(tls_global->ocsp_stapling_response); + tls_global->ocsp_stapling_response = NULL; os_free(tls_global); tls_global = NULL; } @@ -1241,6 +1254,12 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) conn = SSL_get_app_data(ssl); if (conn == NULL) return 0; + + if (depth == 0) + conn->peer_cert = err_cert; + else if (depth == 1) + conn->peer_issuer = err_cert; + context = conn->context; match = conn->subject_match; altmatch = conn->altsubject_match; @@ -2755,11 +2774,187 @@ int tls_connection_get_write_alerts(void *ssl_ctx, struct tls_connection *conn) } +#ifdef HAVE_OCSP + +static void ocsp_debug_print_resp(OCSP_RESPONSE *rsp) +{ +#ifndef CONFIG_NO_STDOUT_DEBUG + extern int wpa_debug_level; + BIO *out; + size_t rlen; + char *txt; + int res; + + if (wpa_debug_level > MSG_DEBUG) + return; + + out = BIO_new(BIO_s_mem()); + if (!out) + return; + + OCSP_RESPONSE_print(out, rsp, 0); + rlen = BIO_ctrl_pending(out); + txt = os_malloc(rlen + 1); + if (!txt) { + BIO_free(out); + return; + } + + res = BIO_read(out, txt, rlen); + if (res > 0) { + txt[res] = '\0'; + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP Response\n%s", txt); + } + os_free(txt); + BIO_free(out); +#endif /* CONFIG_NO_STDOUT_DEBUG */ +} + + +static int ocsp_resp_cb(SSL *s, void *arg) +{ + struct tls_connection *conn = arg; + const unsigned char *p; + int len, status, reason; + OCSP_RESPONSE *rsp; + OCSP_BASICRESP *basic; + OCSP_CERTID *id; + ASN1_GENERALIZEDTIME *produced_at, *this_update, *next_update; + + len = SSL_get_tlsext_status_ocsp_resp(s, &p); + if (!p) { + wpa_printf(MSG_DEBUG, "OpenSSL: No OCSP response received"); + return (conn->flags & TLS_CONN_REQUIRE_OCSP) ? 0 : 1; + } + + wpa_hexdump(MSG_DEBUG, "OpenSSL: OCSP response", p, len); + + rsp = d2i_OCSP_RESPONSE(NULL, &p, len); + if (!rsp) { + wpa_printf(MSG_INFO, "OpenSSL: Failed to parse OCSP response"); + return 0; + } + + ocsp_debug_print_resp(rsp); + + status = OCSP_response_status(rsp); + if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + wpa_printf(MSG_INFO, "OpenSSL: OCSP responder error %d (%s)", + status, OCSP_response_status_str(status)); + return 0; + } + + basic = OCSP_response_get1_basic(rsp); + if (!basic) { + wpa_printf(MSG_INFO, "OpenSSL: Could not find BasicOCSPResponse"); + return 0; + } + + status = OCSP_basic_verify(basic, NULL, SSL_CTX_get_cert_store(s->ctx), + 0); + if (status <= 0) { + tls_show_errors(MSG_INFO, __func__, + "OpenSSL: OCSP response failed verification"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + return 0; + } + + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP response verification succeeded"); + + if (!conn->peer_cert || !conn->peer_issuer) { + wpa_printf(MSG_DEBUG, "OpenSSL: Peer certificate or issue certificate not available for OCSP status check"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + return 0; + } + + id = OCSP_cert_to_id(NULL, conn->peer_cert, conn->peer_issuer); + if (!id) { + wpa_printf(MSG_DEBUG, "OpenSSL: Could not create OCSP certificate identifier"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + return 0; + } + + if (!OCSP_resp_find_status(basic, id, &status, &reason, &produced_at, + &this_update, &next_update)) { + wpa_printf(MSG_INFO, "OpenSSL: Could not find current server certificate from OCSP response%s", + (conn->flags & TLS_CONN_REQUIRE_OCSP) ? "" : + " (OCSP not required)"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + return (conn->flags & TLS_CONN_REQUIRE_OCSP) ? 0 : 1; + } + + if (!OCSP_check_validity(this_update, next_update, 5 * 60, -1)) { + tls_show_errors(MSG_INFO, __func__, + "OpenSSL: OCSP status times invalid"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + return 0; + } + + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status for server certificate: %s", + OCSP_cert_status_str(status)); + + if (status == V_OCSP_CERTSTATUS_GOOD) + return 1; + if (status == V_OCSP_CERTSTATUS_REVOKED) + return 0; + if (conn->flags & TLS_CONN_REQUIRE_OCSP) { + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status unknown, but OCSP required"); + return 0; + } + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status unknown, but OCSP was not required, so allow connection to continue"); + return 1; +} + + +static int ocsp_status_cb(SSL *s, void *arg) +{ + char *tmp; + char *resp; + size_t len; + + if (tls_global->ocsp_stapling_response == NULL) { + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status callback - no response configured"); + return SSL_TLSEXT_ERR_OK; + } + + resp = os_readfile(tls_global->ocsp_stapling_response, &len); + if (resp == NULL) { + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status callback - could not read response file"); + /* TODO: Build OCSPResponse with responseStatus = internalError + */ + return SSL_TLSEXT_ERR_OK; + } + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status callback - send cached response"); + tmp = OPENSSL_malloc(len); + if (tmp == NULL) { + os_free(resp); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + os_memcpy(tmp, resp, len); + os_free(resp); + SSL_set_tlsext_status_ocsp_resp(s, tmp, len); + + return SSL_TLSEXT_ERR_OK; +} + +#endif /* HAVE_OCSP */ + + int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn, const struct tls_connection_params *params) { int ret; unsigned long err; + SSL_CTX *ssl_ctx = tls_ctx; if (conn == NULL) return -1; @@ -2827,6 +3022,14 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn, SSL_clear_options(conn->ssl, SSL_OP_NO_TICKET); #endif /* SSL_OP_NO_TICKET */ +#ifdef HAVE_OCSP + if (params->flags & TLS_CONN_REQUEST_OCSP) { + SSL_set_tlsext_status_type(conn->ssl, TLSEXT_STATUSTYPE_ocsp); + SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_resp_cb); + SSL_CTX_set_tlsext_status_arg(ssl_ctx, conn); + } +#endif /* HAVE_OCSP */ + conn->flags = params->flags; tls_get_errors(tls_ctx); @@ -2869,6 +3072,17 @@ int tls_global_set_params(void *tls_ctx, SSL_CTX_clear_options(ssl_ctx, SSL_OP_NO_TICKET); #endif /* SSL_OP_NO_TICKET */ +#ifdef HAVE_OCSP + SSL_CTX_set_tlsext_status_cb(ssl_ctx, ocsp_status_cb); + SSL_CTX_set_tlsext_status_arg(ssl_ctx, ssl_ctx); + os_free(tls_global->ocsp_stapling_response); + if (params->ocsp_stapling_response) + tls_global->ocsp_stapling_response = + os_strdup(params->ocsp_stapling_response); + else + tls_global->ocsp_stapling_response = NULL; +#endif /* HAVE_OCSP */ + return 0; } diff --git a/src/eap_peer/eap_config.h b/src/eap_peer/eap_config.h index ed90919..42f525b 100644 --- a/src/eap_peer/eap_config.h +++ b/src/eap_peer/eap_config.h @@ -1,6 +1,6 @@ /* * EAP peer configuration data - * Copyright (c) 2003-2008, Jouni Malinen + * Copyright (c) 2003-2013, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -634,6 +634,15 @@ struct eap_peer_config { * password field is the name of that external entry */ u32 flags; + + /** + * ocsp - Whether to use/require OCSP to check server certificate + * + * 0 = do not use OCSP stapling (TLS certificate status extension) + * 1 = try to use OCSP stapling, but not require response + * 2 = require valid OCSP stapling response + */ + int ocsp; }; diff --git a/src/eap_peer/eap_tls_common.c b/src/eap_peer/eap_tls_common.c index a777bb0..be8c301 100644 --- a/src/eap_peer/eap_tls_common.c +++ b/src/eap_peer/eap_tls_common.c @@ -1,6 +1,6 @@ /* * EAP peer: EAP-TLS/PEAP/TTLS/FAST common functions - * Copyright (c) 2004-2012, Jouni Malinen + * Copyright (c) 2004-2013, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -164,6 +164,10 @@ static int eap_tls_init_connection(struct eap_sm *sm, { int res; + if (config->ocsp) + params->flags |= TLS_CONN_REQUEST_OCSP; + if (config->ocsp == 2) + params->flags |= TLS_CONN_REQUIRE_OCSP; data->conn = tls_connection_init(data->ssl_ctx); if (data->conn == NULL) { wpa_printf(MSG_INFO, "SSL: Failed to initialize new TLS " diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index 7a860b6..a35be51 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -1522,6 +1522,7 @@ static const struct parse_data ssid_fields[] = { { INT(eap_workaround) }, { STRe(pac_file) }, { INTe(fragment_size) }, + { INTe(ocsp) }, #endif /* IEEE8021X_EAPOL */ { INT_RANGE(mode, 0, 4) }, { INT_RANGE(proactive_key_caching, 0, 1) }, diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf index 8e21a3a..d73d371 100644 --- a/wpa_supplicant/wpa_supplicant.conf +++ b/wpa_supplicant/wpa_supplicant.conf @@ -815,6 +815,11 @@ fast_reauth=1 # interface used for EAPOL. The default value is suitable for most # cases. # +# ocsp: Whether to use/require OCSP to check server certificate +# 0 = do not use OCSP stapling (TLS certificate status extension) +# 1 = try to use OCSP stapling, but not require response +# 2 = require valid OCSP stapling response +# # EAP-FAST variables: # pac_file: File path for the PAC entries. wpa_supplicant will need to be able # to create this file and write updates to it when PAC is being -- 2.1.4