]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
GnuTLS: Add support for OCSP stapling as a client
authorJouni Malinen <j@w1.fi>
Sun, 11 Jan 2015 17:07:13 +0000 (19:07 +0200)
committerJouni Malinen <j@w1.fi>
Sun, 11 Jan 2015 22:19:21 +0000 (00:19 +0200)
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>
src/crypto/tls_gnutls.c

index ca5226a680a0e3ea55aceeef2ea5c788e2570a19..4329616181717d13bae51dd3312816507317c15a 100644 (file)
@@ -12,6 +12,9 @@
 #ifdef PKCS12_FUNCS
 #include <gnutls/pkcs12.h>
 #endif /* PKCS12_FUNCS */
+#if GNUTLS_VERSION_NUMBER >= 0x030103
+#include <gnutls/ocsp.h>
+#endif /* 3.1.3 */
 
 #include "common.h"
 #include "tls.h"
@@ -54,6 +57,7 @@ struct tls_connection {
        gnutls_certificate_credentials_t xcred;
 
        char *suffix_match;
+       unsigned int flags;
 };
 
 
@@ -358,6 +362,8 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
                        return -1;
        }
 
+       conn->flags = params->flags;
+
        if (params->openssl_ciphers) {
                wpa_printf(MSG_INFO, "GnuTLS: openssl_ciphers not supported");
                return -1;
@@ -541,6 +547,24 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
 #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;
 
        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 */
 
 
+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)
 {
        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 */
 
+       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)) {
                wpa_printf(MSG_INFO, "TLS: Peer certificate not trusted");
                if (status & GNUTLS_CERT_INSECURE_ALGORITHM) {
@@ -899,15 +1029,10 @@ static int tls_connection_verify_peer(gnutls_session_t session)
                goto out;
        }
 
-       os_get_time(&now);
-
-       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;
+       if (check_ocsp(conn, session, &err))
                goto out;
-       }
+
+       os_get_time(&now);
 
        for (i = 0; i < num_certs; i++) {
                char *buf;