Browse Source

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 <j@w1.fi>
Jouni Malinen 11 years ago
parent
commit
080585c01a

+ 3 - 0
hostapd/config_file.c

@@ -1833,6 +1833,9 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 			bss->private_key_passwd = os_strdup(pos);
 			bss->private_key_passwd = os_strdup(pos);
 		} else if (os_strcmp(buf, "check_crl") == 0) {
 		} else if (os_strcmp(buf, "check_crl") == 0) {
 			bss->check_crl = atoi(pos);
 			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) {
 		} else if (os_strcmp(buf, "dh_file") == 0) {
 			os_free(bss->dh_file);
 			os_free(bss->dh_file);
 			bss->dh_file = os_strdup(pos);
 			bss->dh_file = os_strdup(pos);

+ 14 - 0
hostapd/hostapd.conf

@@ -677,6 +677,20 @@ eap_server=0
 # 2 = check all CRLs in the certificate path
 # 2 = check all CRLs in the certificate path
 #check_crl=1
 #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)
 # dh_file: File path to DH/DSA parameters file (in PEM format)
 # This is an optional configuration file for setting parameters for an
 # This is an optional configuration file for setting parameters for an
 # ephemeral DH key exchange. In most cases, the default RSA authentication does
 # ephemeral DH key exchange. In most cases, the default RSA authentication does

+ 1 - 0
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->server_cert);
 	os_free(conf->private_key);
 	os_free(conf->private_key);
 	os_free(conf->private_key_passwd);
 	os_free(conf->private_key_passwd);
+	os_free(conf->ocsp_stapling_response);
 	os_free(conf->dh_file);
 	os_free(conf->dh_file);
 	os_free(conf->pac_opaque_encr_key);
 	os_free(conf->pac_opaque_encr_key);
 	os_free(conf->eap_fast_a_id);
 	os_free(conf->eap_fast_a_id);

+ 1 - 0
src/ap/ap_config.h

@@ -295,6 +295,7 @@ struct hostapd_bss_config {
 	char *private_key;
 	char *private_key;
 	char *private_key_passwd;
 	char *private_key_passwd;
 	int check_crl;
 	int check_crl;
+	char *ocsp_stapling_response;
 	char *dh_file;
 	char *dh_file;
 	u8 *pac_opaque_encr_key;
 	u8 *pac_opaque_encr_key;
 	u8 *eap_fast_a_id;
 	u8 *eap_fast_a_id;

+ 2 - 0
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 = hapd->conf->private_key;
 		params.private_key_passwd = hapd->conf->private_key_passwd;
 		params.private_key_passwd = hapd->conf->private_key_passwd;
 		params.dh_file = hapd->conf->dh_file;
 		params.dh_file = hapd->conf->dh_file;
+		params.ocsp_stapling_response =
+			hapd->conf->ocsp_stapling_response;
 
 
 		if (tls_global_set_params(hapd->ssl_ctx, &params)) {
 		if (tls_global_set_params(hapd->ssl_ctx, &params)) {
 			wpa_printf(MSG_ERROR, "Failed to set TLS parameters");
 			wpa_printf(MSG_ERROR, "Failed to set TLS parameters");

+ 6 - 1
src/crypto/tls.h

@@ -1,6 +1,6 @@
 /*
 /*
  * SSL/TLS interface definition
  * SSL/TLS interface definition
- * Copyright (c) 2004-2010, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2004-2013, Jouni Malinen <j@w1.fi>
  *
  *
  * This software may be distributed under the terms of the BSD license.
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
  * See README for more details.
@@ -82,6 +82,8 @@ struct tls_config {
 #define TLS_CONN_ALLOW_SIGN_RSA_MD5 BIT(0)
 #define TLS_CONN_ALLOW_SIGN_RSA_MD5 BIT(0)
 #define TLS_CONN_DISABLE_TIME_CHECKS BIT(1)
 #define TLS_CONN_DISABLE_TIME_CHECKS BIT(1)
 #define TLS_CONN_DISABLE_SESSION_TICKET BIT(2)
 #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
  * struct tls_connection_params - Parameters for TLS connection
@@ -117,6 +119,8 @@ struct tls_config {
  * @cert_id: the certificate's id when using engine
  * @cert_id: the certificate's id when using engine
  * @ca_cert_id: the CA certificate's id when using engine
  * @ca_cert_id: the CA certificate's id when using engine
  * @flags: Parameter options (TLS_CONN_*)
  * @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()
  * TLS connection parameters to be configured with tls_connection_set_params()
  * and tls_global_set_params().
  * and tls_global_set_params().
@@ -153,6 +157,7 @@ struct tls_connection_params {
 	const char *ca_cert_id;
 	const char *ca_cert_id;
 
 
 	unsigned int flags;
 	unsigned int flags;
+	const char *ocsp_stapling_response;
 };
 };
 
 
 
 

+ 215 - 1
src/crypto/tls_openssl.c

@@ -1,6 +1,6 @@
 /*
 /*
  * SSL/TLS interface functions for OpenSSL
  * SSL/TLS interface functions for OpenSSL
- * Copyright (c) 2004-2011, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2004-2013, Jouni Malinen <j@w1.fi>
  *
  *
  * This software may be distributed under the terms of the BSD license.
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
  * See README for more details.
@@ -51,6 +51,13 @@
 #endif
 #endif
 #endif
 #endif
 
 
+#ifdef SSL_set_tlsext_status_type
+#ifndef OPENSSL_NO_TLSEXT
+#define HAVE_OCSP
+#include <openssl/ocsp.h>
+#endif /* OPENSSL_NO_TLSEXT */
+#endif /* SSL_set_tlsext_status_type */
+
 static int tls_openssl_ref_count = 0;
 static int tls_openssl_ref_count = 0;
 
 
 struct tls_context {
 struct tls_context {
@@ -58,6 +65,7 @@ struct tls_context {
 			 union tls_event_data *data);
 			 union tls_event_data *data);
 	void *cb_ctx;
 	void *cb_ctx;
 	int cert_in_cb;
 	int cert_in_cb;
+	char *ocsp_stapling_response;
 };
 };
 
 
 static struct tls_context *tls_global = NULL;
 static struct tls_context *tls_global = NULL;
@@ -88,6 +96,9 @@ struct tls_connection {
 	u8 srv_cert_hash[32];
 	u8 srv_cert_hash[32];
 
 
 	unsigned int flags;
 	unsigned int flags;
+
+	X509 *peer_cert;
+	X509 *peer_issuer;
 };
 };
 
 
 
 
@@ -827,6 +838,8 @@ void tls_deinit(void *ssl_ctx)
 		ERR_remove_state(0);
 		ERR_remove_state(0);
 		ERR_free_strings();
 		ERR_free_strings();
 		EVP_cleanup();
 		EVP_cleanup();
+		os_free(tls_global->ocsp_stapling_response);
+		tls_global->ocsp_stapling_response = NULL;
 		os_free(tls_global);
 		os_free(tls_global);
 		tls_global = NULL;
 		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);
 	conn = SSL_get_app_data(ssl);
 	if (conn == NULL)
 	if (conn == NULL)
 		return 0;
 		return 0;
+
+	if (depth == 0)
+		conn->peer_cert = err_cert;
+	else if (depth == 1)
+		conn->peer_issuer = err_cert;
+
 	context = conn->context;
 	context = conn->context;
 	match = conn->subject_match;
 	match = conn->subject_match;
 	altmatch = conn->altsubject_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,
 int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
 			      const struct tls_connection_params *params)
 			      const struct tls_connection_params *params)
 {
 {
 	int ret;
 	int ret;
 	unsigned long err;
 	unsigned long err;
+	SSL_CTX *ssl_ctx = tls_ctx;
 
 
 	if (conn == NULL)
 	if (conn == NULL)
 		return -1;
 		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);
 		SSL_clear_options(conn->ssl, SSL_OP_NO_TICKET);
 #endif /*  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;
 	conn->flags = params->flags;
 
 
 	tls_get_errors(tls_ctx);
 	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);
 		SSL_CTX_clear_options(ssl_ctx, SSL_OP_NO_TICKET);
 #endif /*  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;
 	return 0;
 }
 }
 
 

+ 10 - 1
src/eap_peer/eap_config.h

@@ -1,6 +1,6 @@
 /*
 /*
  * EAP peer configuration data
  * EAP peer configuration data
- * Copyright (c) 2003-2008, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2003-2013, Jouni Malinen <j@w1.fi>
  *
  *
  * This software may be distributed under the terms of the BSD license.
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
  * See README for more details.
@@ -634,6 +634,15 @@ struct eap_peer_config {
 	 *         password field is the name of that external entry
 	 *         password field is the name of that external entry
 	 */
 	 */
 	u32 flags;
 	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;
 };
 };
 
 
 
 

+ 5 - 1
src/eap_peer/eap_tls_common.c

@@ -1,6 +1,6 @@
 /*
 /*
  * EAP peer: EAP-TLS/PEAP/TTLS/FAST common functions
  * EAP peer: EAP-TLS/PEAP/TTLS/FAST common functions
- * Copyright (c) 2004-2012, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2004-2013, Jouni Malinen <j@w1.fi>
  *
  *
  * This software may be distributed under the terms of the BSD license.
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
  * See README for more details.
@@ -164,6 +164,10 @@ static int eap_tls_init_connection(struct eap_sm *sm,
 {
 {
 	int res;
 	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);
 	data->conn = tls_connection_init(data->ssl_ctx);
 	if (data->conn == NULL) {
 	if (data->conn == NULL) {
 		wpa_printf(MSG_INFO, "SSL: Failed to initialize new TLS "
 		wpa_printf(MSG_INFO, "SSL: Failed to initialize new TLS "

+ 1 - 0
wpa_supplicant/config.c

@@ -1522,6 +1522,7 @@ static const struct parse_data ssid_fields[] = {
 	{ INT(eap_workaround) },
 	{ INT(eap_workaround) },
 	{ STRe(pac_file) },
 	{ STRe(pac_file) },
 	{ INTe(fragment_size) },
 	{ INTe(fragment_size) },
+	{ INTe(ocsp) },
 #endif /* IEEE8021X_EAPOL */
 #endif /* IEEE8021X_EAPOL */
 	{ INT_RANGE(mode, 0, 4) },
 	{ INT_RANGE(mode, 0, 4) },
 	{ INT_RANGE(proactive_key_caching, 0, 1) },
 	{ INT_RANGE(proactive_key_caching, 0, 1) },

+ 5 - 0
wpa_supplicant/wpa_supplicant.conf

@@ -815,6 +815,11 @@ fast_reauth=1
 #	interface used for EAPOL. The default value is suitable for most
 #	interface used for EAPOL. The default value is suitable for most
 #	cases.
 #	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:
 # EAP-FAST variables:
 # pac_file: File path for the PAC entries. wpa_supplicant will need to be able
 # 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
 #	to create this file and write updates to it when PAC is being