GnuTLS: Add support for OCSP stapling as a client

This allows ocsp=2 to be used with wpa_supplicant when built with GnuTLS
to request TLS status extension (OCSP stapling) to be used to validate
server certificate validity.

Signed-off-by: Jouni Malinen <j@w1.fi>
This commit is contained in:
Jouni Malinen 2015-01-11 19:07:13 +02:00
parent 279a0afffb
commit 0eb2ed067f

View file

@ -12,6 +12,9 @@
#ifdef PKCS12_FUNCS #ifdef PKCS12_FUNCS
#include <gnutls/pkcs12.h> #include <gnutls/pkcs12.h>
#endif /* PKCS12_FUNCS */ #endif /* PKCS12_FUNCS */
#if GNUTLS_VERSION_NUMBER >= 0x030103
#include <gnutls/ocsp.h>
#endif /* 3.1.3 */
#include "common.h" #include "common.h"
#include "tls.h" #include "tls.h"
@ -54,6 +57,7 @@ struct tls_connection {
gnutls_certificate_credentials_t xcred; gnutls_certificate_credentials_t xcred;
char *suffix_match; char *suffix_match;
unsigned int flags;
}; };
@ -358,6 +362,8 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
return -1; return -1;
} }
conn->flags = params->flags;
if (params->openssl_ciphers) { if (params->openssl_ciphers) {
wpa_printf(MSG_INFO, "GnuTLS: openssl_ciphers not supported"); wpa_printf(MSG_INFO, "GnuTLS: openssl_ciphers not supported");
return -1; return -1;
@ -541,6 +547,24 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
#endif /* PKCS12_FUNCS */ #endif /* PKCS12_FUNCS */
} }
#if GNUTLS_VERSION_NUMBER >= 0x030103
if (params->flags & (TLS_CONN_REQUEST_OCSP | TLS_CONN_REQUIRE_OCSP)) {
ret = gnutls_ocsp_status_request_enable_client(conn->session,
NULL, 0, NULL);
if (ret != GNUTLS_E_SUCCESS) {
wpa_printf(MSG_INFO,
"GnuTLS: Failed to enable OCSP client");
return -1;
}
}
#else /* 3.1.3 */
if (params->flags & TLS_CONN_REQUIRE_OCSP) {
wpa_printf(MSG_INFO,
"GnuTLS: OCSP not supported by this version of GnuTLS");
return -1;
}
#endif /* 3.1.3 */
conn->params_set = 1; conn->params_set = 1;
ret = gnutls_credentials_set(conn->session, GNUTLS_CRD_CERTIFICATE, ret = gnutls_credentials_set(conn->session, GNUTLS_CRD_CERTIFICATE,
@ -777,6 +801,105 @@ static int server_eku_purpose(gnutls_x509_crt_t cert)
#endif /* < 3.3.0 */ #endif /* < 3.3.0 */
static int check_ocsp(struct tls_connection *conn, gnutls_session_t session,
gnutls_alert_description_t *err)
{
#if GNUTLS_VERSION_NUMBER >= 0x030103
gnutls_datum_t response, buf;
gnutls_ocsp_resp_t resp;
unsigned int cert_status;
int res;
if (!(conn->flags & (TLS_CONN_REQUEST_OCSP | TLS_CONN_REQUIRE_OCSP)))
return 0;
if (!gnutls_ocsp_status_request_is_checked(session, 0)) {
if (conn->flags & TLS_CONN_REQUIRE_OCSP) {
wpa_printf(MSG_INFO,
"GnuTLS: No valid OCSP response received");
goto ocsp_error;
}
wpa_printf(MSG_DEBUG,
"GnuTLS: Valid OCSP response was not received - continue since OCSP was not required");
return 0;
}
/*
* GnuTLS has already verified the OCSP response in
* check_ocsp_response() and rejected handshake if the certificate was
* found to be revoked. However, if the response indicates that the
* status is unknown, handshake continues and reaches here. We need to
* re-import the OCSP response to check for unknown certificate status,
* but we do not need to repeat gnutls_ocsp_resp_check_crt() and
* gnutls_ocsp_resp_verify_direct() calls.
*/
res = gnutls_ocsp_status_request_get(session, &response);
if (res != GNUTLS_E_SUCCESS) {
wpa_printf(MSG_INFO,
"GnuTLS: OCSP response was received, but it was not valid");
goto ocsp_error;
}
if (gnutls_ocsp_resp_init(&resp) != GNUTLS_E_SUCCESS)
goto ocsp_error;
res = gnutls_ocsp_resp_import(resp, &response);
if (res != GNUTLS_E_SUCCESS) {
wpa_printf(MSG_INFO,
"GnuTLS: Could not parse received OCSP response: %s",
gnutls_strerror(res));
gnutls_ocsp_resp_deinit(resp);
goto ocsp_error;
}
res = gnutls_ocsp_resp_print(resp, GNUTLS_OCSP_PRINT_FULL, &buf);
if (res == GNUTLS_E_SUCCESS) {
wpa_printf(MSG_DEBUG, "GnuTLS: %s", buf.data);
gnutls_free(buf.data);
}
res = gnutls_ocsp_resp_get_single(resp, 0, NULL, NULL, NULL,
NULL, &cert_status, NULL,
NULL, NULL, NULL);
gnutls_ocsp_resp_deinit(resp);
if (res != GNUTLS_E_SUCCESS) {
wpa_printf(MSG_INFO,
"GnuTLS: Failed to extract OCSP information: %s",
gnutls_strerror(res));
goto ocsp_error;
}
if (cert_status == GNUTLS_OCSP_CERT_GOOD) {
wpa_printf(MSG_DEBUG, "GnuTLS: OCSP cert status: good");
} else if (cert_status == GNUTLS_OCSP_CERT_REVOKED) {
wpa_printf(MSG_DEBUG,
"GnuTLS: OCSP cert status: revoked");
goto ocsp_error;
} else {
wpa_printf(MSG_DEBUG,
"GnuTLS: OCSP cert status: unknown");
if (conn->flags & TLS_CONN_REQUIRE_OCSP)
goto ocsp_error;
wpa_printf(MSG_DEBUG,
"GnuTLS: OCSP was not required, so allow connection to continue");
}
return 0;
ocsp_error:
gnutls_tls_fail_event(conn, NULL, 0, NULL,
"bad certificate status response",
TLS_FAIL_REVOKED);
*err = GNUTLS_A_CERTIFICATE_REVOKED;
return -1;
#else /* GnuTLS 3.1.3 or newer */
return 0;
#endif /* GnuTLS 3.1.3 or newer */
}
static int tls_connection_verify_peer(gnutls_session_t session) static int tls_connection_verify_peer(gnutls_session_t session)
{ {
struct tls_connection *conn; struct tls_connection *conn;
@ -839,6 +962,13 @@ static int tls_connection_verify_peer(gnutls_session_t session)
} }
#endif /* GnuTLS 3.1.4 or newer */ #endif /* GnuTLS 3.1.4 or newer */
certs = gnutls_certificate_get_peers(session, &num_certs);
if (certs == NULL || num_certs == 0) {
wpa_printf(MSG_INFO, "TLS: No peer certificate chain received");
err = GNUTLS_A_UNKNOWN_CA;
goto out;
}
if (conn->verify_peer && (status & GNUTLS_CERT_INVALID)) { if (conn->verify_peer && (status & GNUTLS_CERT_INVALID)) {
wpa_printf(MSG_INFO, "TLS: Peer certificate not trusted"); wpa_printf(MSG_INFO, "TLS: Peer certificate not trusted");
if (status & GNUTLS_CERT_INSECURE_ALGORITHM) { if (status & GNUTLS_CERT_INSECURE_ALGORITHM) {
@ -899,15 +1029,10 @@ static int tls_connection_verify_peer(gnutls_session_t session)
goto out; goto out;
} }
os_get_time(&now); if (check_ocsp(conn, session, &err))
certs = gnutls_certificate_get_peers(session, &num_certs);
if (certs == NULL) {
wpa_printf(MSG_INFO, "TLS: No peer certificate chain "
"received");
err = GNUTLS_A_UNKNOWN_CA;
goto out; goto out;
}
os_get_time(&now);
for (i = 0; i < num_certs; i++) { for (i = 0; i < num_certs; i++) {
char *buf; char *buf;