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>
This commit is contained in:
parent
72950ed240
commit
080585c01a
11 changed files with 263 additions and 4 deletions
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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 <openssl/ocsp.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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 "
|
||||
|
|
|
@ -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) },
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue