Updated through tag hostap_2_5 from git://w1.fi/hostap.git
[mech_eap.git] / libeap / src / crypto / tls_gnutls.c
index c3a7358..f994379 100644 (file)
@@ -1,15 +1,9 @@
 /*
  * SSL/TLS interface functions for GnuTLS
- * Copyright (c) 2004-2009, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2004-2011, Jouni Malinen <j@w1.fi>
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * Alternatively, this software may be distributed under the terms of BSD
- * license.
- *
- * See README and COPYING for more details.
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
  */
 
 #include "includes.h"
 #ifdef PKCS12_FUNCS
 #include <gnutls/pkcs12.h>
 #endif /* PKCS12_FUNCS */
-
-#ifdef CONFIG_GNUTLS_EXTRA
-#if LIBGNUTLS_VERSION_NUMBER >= 0x010302
-#define GNUTLS_IA
-#include <gnutls/extra.h>
-#if LIBGNUTLS_VERSION_NUMBER == 0x010302
-/* This function is not included in the current gnutls/extra.h even though it
- * should be, so define it here as a workaround for the time being. */
-int gnutls_ia_verify_endphase(gnutls_session_t session, char *checksum);
-#endif /* LIBGNUTLS_VERSION_NUMBER == 0x010302 */
-#endif /* LIBGNUTLS_VERSION_NUMBER >= 0x010302 */
-#endif /* CONFIG_GNUTLS_EXTRA */
+#if GNUTLS_VERSION_NUMBER >= 0x030103
+#include <gnutls/ocsp.h>
+#endif /* 3.1.3 */
 
 #include "common.h"
+#include "crypto/crypto.h"
 #include "tls.h"
 
 
-#ifndef TLS_RANDOM_SIZE
-#define TLS_RANDOM_SIZE 32
-#endif
-#ifndef TLS_MASTER_SIZE
-#define TLS_MASTER_SIZE 48
-#endif
-
-
-#if LIBGNUTLS_VERSION_NUMBER < 0x010302
-/* GnuTLS 1.3.2 added functions for using master secret. Older versions require
- * use of internal structures to get the master_secret and
- * {server,client}_random.
- */
-#define GNUTLS_INTERNAL_STRUCTURE_HACK
-#endif /* LIBGNUTLS_VERSION_NUMBER < 0x010302 */
-
-
-#ifdef GNUTLS_INTERNAL_STRUCTURE_HACK
-/*
- * It looks like gnutls does not provide access to client/server_random and
- * master_key. This is somewhat unfortunate since these are needed for key
- * derivation in EAP-{TLS,TTLS,PEAP,FAST}. Workaround for now is a horrible
- * hack that copies the gnutls_session_int definition from gnutls_int.h so that
- * we can get the needed information.
- */
-
-typedef u8 uint8;
-typedef unsigned char opaque;
-typedef struct {
-    uint8 suite[2];
-} cipher_suite_st;
-
-typedef struct {
-       gnutls_connection_end_t entity;
-       gnutls_kx_algorithm_t kx_algorithm;
-       gnutls_cipher_algorithm_t read_bulk_cipher_algorithm;
-       gnutls_mac_algorithm_t read_mac_algorithm;
-       gnutls_compression_method_t read_compression_algorithm;
-       gnutls_cipher_algorithm_t write_bulk_cipher_algorithm;
-       gnutls_mac_algorithm_t write_mac_algorithm;
-       gnutls_compression_method_t write_compression_algorithm;
-       cipher_suite_st current_cipher_suite;
-       opaque master_secret[TLS_MASTER_SIZE];
-       opaque client_random[TLS_RANDOM_SIZE];
-       opaque server_random[TLS_RANDOM_SIZE];
-       /* followed by stuff we are not interested in */
-} security_parameters_st;
-
-struct gnutls_session_int {
-       security_parameters_st security_parameters;
-       /* followed by things we are not interested in */
-};
-#endif /* LIBGNUTLS_VERSION_NUMBER < 0x010302 */
-
 static int tls_gnutls_ref_count = 0;
 
 struct tls_global {
@@ -100,17 +32,23 @@ struct tls_global {
 
        int params_set;
        gnutls_certificate_credentials_t xcred;
+
+       void (*event_cb)(void *ctx, enum tls_event ev,
+                        union tls_event_data *data);
+       void *cb_ctx;
+       int cert_in_cb;
 };
 
 struct tls_connection {
-       gnutls_session session;
-       char *subject_match, *altsubject_match;
+       struct tls_global *global;
+       gnutls_session_t session;
        int read_alerts, write_alerts, failed;
 
        u8 *pre_shared_secret;
        size_t pre_shared_secret_len;
        int established;
        int verify_peer;
+       unsigned int disable_time_checks:1;
 
        struct wpabuf *push_buf;
        struct wpabuf *pull_buf;
@@ -119,21 +57,13 @@ struct tls_connection {
        int params_set;
        gnutls_certificate_credentials_t xcred;
 
-       int tls_ia;
-       int final_phase_finished;
-
-#ifdef GNUTLS_IA
-       gnutls_ia_server_credentials_t iacred_srv;
-       gnutls_ia_client_credentials_t iacred_cli;
+       char *suffix_match;
+       char *domain_match;
+       unsigned int flags;
+};
 
-       /* Session keys generated in the current phase for inner secret
-        * permutation before generating/verifying PhaseFinished. */
-       u8 *session_keys;
-       size_t session_keys_len;
 
-       u8 inner_secret[TLS_MASTER_SIZE];
-#endif /* GNUTLS_IA */
-};
+static int tls_connection_verify_peer(gnutls_session_t session);
 
 
 static void tls_log_func(int level, const char *msg)
@@ -162,23 +92,15 @@ static void tls_log_func(int level, const char *msg)
 }
 
 
-extern int wpa_debug_show_keys;
-
 void * tls_init(const struct tls_config *conf)
 {
        struct tls_global *global;
 
-#ifdef GNUTLS_INTERNAL_STRUCTURE_HACK
-       /* Because of the horrible hack to get master_secret and client/server
-        * random, we need to make sure that the gnutls version is something
-        * that is expected to have same structure definition for the session
-        * data.. */
-       const char *ver;
-       const char *ok_ver[] = { "1.2.3", "1.2.4", "1.2.5", "1.2.6", "1.2.9",
-                                "1.3.2",
-                                NULL };
-       int i;
-#endif /* GNUTLS_INTERNAL_STRUCTURE_HACK */
+       if (tls_gnutls_ref_count == 0) {
+               wpa_printf(MSG_DEBUG,
+                          "GnuTLS: Library version %s (runtime) - %s (build)",
+                          gnutls_check_version(NULL), GNUTLS_VERSION);
+       }
 
        global = os_zalloc(sizeof(*global));
        if (global == NULL)
@@ -190,28 +112,16 @@ void * tls_init(const struct tls_config *conf)
        }
        tls_gnutls_ref_count++;
 
-#ifdef GNUTLS_INTERNAL_STRUCTURE_HACK
-       ver = gnutls_check_version(NULL);
-       if (ver == NULL) {
-               tls_deinit(global);
-               return NULL;
-       }
-       wpa_printf(MSG_DEBUG, "%s - gnutls version %s", __func__, ver);
-       for (i = 0; ok_ver[i]; i++) {
-               if (strcmp(ok_ver[i], ver) == 0)
-                       break;
-       }
-       if (ok_ver[i] == NULL) {
-               wpa_printf(MSG_INFO, "Untested gnutls version %s - this needs "
-                          "to be tested and enabled in tls_gnutls.c", ver);
-               tls_deinit(global);
-               return NULL;
-       }
-#endif /* GNUTLS_INTERNAL_STRUCTURE_HACK */
-
        gnutls_global_set_log_function(tls_log_func);
        if (wpa_debug_show_keys)
                gnutls_global_set_log_level(11);
+
+       if (conf) {
+               global->event_cb = conf->event_cb;
+               global->cb_ctx = conf->cb_ctx;
+               global->cert_in_cb = conf->cert_in_cb;
+       }
+
        return global;
 }
 
@@ -238,7 +148,7 @@ int tls_get_errors(void *ssl_ctx)
 }
 
 
-static ssize_t tls_pull_func(gnutls_transport_ptr ptr, void *buf,
+static ssize_t tls_pull_func(gnutls_transport_ptr_t ptr, void *buf,
                             size_t len)
 {
        struct tls_connection *conn = (struct tls_connection *) ptr;
@@ -267,7 +177,7 @@ static ssize_t tls_pull_func(gnutls_transport_ptr ptr, void *buf,
 }
 
 
-static ssize_t tls_push_func(gnutls_transport_ptr ptr, const void *buf,
+static ssize_t tls_push_func(gnutls_transport_ptr_t ptr, const void *buf,
                             size_t len)
 {
        struct tls_connection *conn = (struct tls_connection *) ptr;
@@ -285,8 +195,7 @@ static ssize_t tls_push_func(gnutls_transport_ptr ptr, const void *buf,
 static int tls_gnutls_init_session(struct tls_global *global,
                                   struct tls_connection *conn)
 {
-       const int cert_types[2] = { GNUTLS_CRT_X509, 0 };
-       const int protos[2] = { GNUTLS_TLS1, 0 };
+       const char *err;
        int ret;
 
        ret = gnutls_init(&conn->session,
@@ -301,17 +210,18 @@ static int tls_gnutls_init_session(struct tls_global *global,
        if (ret < 0)
                goto fail;
 
-       ret = gnutls_certificate_type_set_priority(conn->session, cert_types);
-       if (ret < 0)
-               goto fail;
-
-       ret = gnutls_protocol_set_priority(conn->session, protos);
-       if (ret < 0)
+       ret = gnutls_priority_set_direct(conn->session, "NORMAL:-VERS-SSL3.0",
+                                        &err);
+       if (ret < 0) {
+               wpa_printf(MSG_ERROR, "GnuTLS: Priority string failure at "
+                          "'%s'", err);
                goto fail;
+       }
 
        gnutls_transport_set_pull_function(conn->session, tls_pull_func);
        gnutls_transport_set_push_function(conn->session, tls_push_func);
-       gnutls_transport_set_ptr(conn->session, (gnutls_transport_ptr) conn);
+       gnutls_transport_set_ptr(conn->session, (gnutls_transport_ptr_t) conn);
+       gnutls_session_set_ptr(conn->session, conn);
 
        return 0;
 
@@ -332,6 +242,7 @@ struct tls_connection * tls_connection_init(void *ssl_ctx)
        conn = os_zalloc(sizeof(*conn));
        if (conn == NULL)
                return NULL;
+       conn->global = global;
 
        if (tls_gnutls_init_session(global, conn)) {
                os_free(conn);
@@ -364,24 +275,13 @@ void tls_connection_deinit(void *ssl_ctx, struct tls_connection *conn)
        if (conn == NULL)
                return;
 
-#ifdef GNUTLS_IA
-       if (conn->iacred_srv)
-               gnutls_ia_free_server_credentials(conn->iacred_srv);
-       if (conn->iacred_cli)
-               gnutls_ia_free_client_credentials(conn->iacred_cli);
-       if (conn->session_keys) {
-               os_memset(conn->session_keys, 0, conn->session_keys_len);
-               os_free(conn->session_keys);
-       }
-#endif /* GNUTLS_IA */
-
        gnutls_certificate_free_credentials(conn->xcred);
        gnutls_deinit(conn->session);
        os_free(conn->pre_shared_secret);
-       os_free(conn->subject_match);
-       os_free(conn->altsubject_match);
        wpabuf_free(conn->push_buf);
        wpabuf_free(conn->pull_buf);
+       os_free(conn->suffix_match);
+       os_free(conn->domain_match);
        os_free(conn);
 }
 
@@ -407,14 +307,6 @@ int tls_connection_shutdown(void *ssl_ctx, struct tls_connection *conn)
        wpabuf_free(conn->push_buf);
        conn->push_buf = NULL;
        conn->established = 0;
-       conn->final_phase_finished = 0;
-#ifdef GNUTLS_IA
-       if (conn->session_keys) {
-               os_memset(conn->session_keys, 0, conn->session_keys_len);
-               os_free(conn->session_keys);
-       }
-       conn->session_keys_len = 0;
-#endif /* GNUTLS_IA */
 
        gnutls_deinit(conn->session);
        if (tls_gnutls_init_session(global, conn)) {
@@ -447,104 +339,6 @@ int tls_connection_shutdown(void *ssl_ctx, struct tls_connection *conn)
 }
 
 
-#if 0
-static int tls_match_altsubject(X509 *cert, const char *match)
-{
-       GENERAL_NAME *gen;
-       char *field, *tmp;
-       void *ext;
-       int i, found = 0;
-       size_t len;
-
-       ext = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
-
-       for (i = 0; ext && i < sk_GENERAL_NAME_num(ext); i++) {
-               gen = sk_GENERAL_NAME_value(ext, i);
-               switch (gen->type) {
-               case GEN_EMAIL:
-                       field = "EMAIL";
-                       break;
-               case GEN_DNS:
-                       field = "DNS";
-                       break;
-               case GEN_URI:
-                       field = "URI";
-                       break;
-               default:
-                       field = NULL;
-                       wpa_printf(MSG_DEBUG, "TLS: altSubjectName: "
-                                  "unsupported type=%d", gen->type);
-                       break;
-               }
-
-               if (!field)
-                       continue;
-
-               wpa_printf(MSG_DEBUG, "TLS: altSubjectName: %s:%s",
-                          field, gen->d.ia5->data);
-               len = os_strlen(field) + 1 +
-                       strlen((char *) gen->d.ia5->data) + 1;
-               tmp = os_malloc(len);
-               if (tmp == NULL)
-                       continue;
-               snprintf(tmp, len, "%s:%s", field, gen->d.ia5->data);
-               if (strstr(tmp, match))
-                       found++;
-               os_free(tmp);
-       }
-
-       return found;
-}
-#endif
-
-
-#if 0
-static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
-{
-       char buf[256];
-       X509 *err_cert;
-       int err, depth;
-       SSL *ssl;
-       struct tls_connection *conn;
-       char *match, *altmatch;
-
-       err_cert = X509_STORE_CTX_get_current_cert(x509_ctx);
-       err = X509_STORE_CTX_get_error(x509_ctx);
-       depth = X509_STORE_CTX_get_error_depth(x509_ctx);
-       ssl = X509_STORE_CTX_get_ex_data(x509_ctx,
-                                        SSL_get_ex_data_X509_STORE_CTX_idx());
-       X509_NAME_oneline(X509_get_subject_name(err_cert), buf, sizeof(buf));
-
-       conn = SSL_get_app_data(ssl);
-       match = conn ? conn->subject_match : NULL;
-       altmatch = conn ? conn->altsubject_match : NULL;
-
-       if (!preverify_ok) {
-               wpa_printf(MSG_WARNING, "TLS: Certificate verification failed,"
-                          " error %d (%s) depth %d for '%s'", err,
-                          X509_verify_cert_error_string(err), depth, buf);
-       } else {
-               wpa_printf(MSG_DEBUG, "TLS: tls_verify_cb - "
-                          "preverify_ok=%d err=%d (%s) depth=%d buf='%s'",
-                          preverify_ok, err,
-                          X509_verify_cert_error_string(err), depth, buf);
-               if (depth == 0 && match && strstr(buf, match) == NULL) {
-                       wpa_printf(MSG_WARNING, "TLS: Subject '%s' did not "
-                                  "match with '%s'", buf, match);
-                       preverify_ok = 0;
-               } else if (depth == 0 && altmatch &&
-                          !tls_match_altsubject(err_cert, altmatch)) {
-                       wpa_printf(MSG_WARNING, "TLS: altSubjectName match "
-                                  "'%s' not found", altmatch);
-                       preverify_ok = 0;
-               }
-       }
-
-       return preverify_ok;
-}
-#endif
-
-
 int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
                              const struct tls_connection_params *params)
 {
@@ -553,44 +347,101 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
        if (conn == NULL || params == NULL)
                return -1;
 
-       os_free(conn->subject_match);
-       conn->subject_match = NULL;
        if (params->subject_match) {
-               conn->subject_match = os_strdup(params->subject_match);
-               if (conn->subject_match == NULL)
-                       return -1;
+               wpa_printf(MSG_INFO, "GnuTLS: subject_match not supported");
+               return -1;
        }
 
-       os_free(conn->altsubject_match);
-       conn->altsubject_match = NULL;
        if (params->altsubject_match) {
-               conn->altsubject_match = os_strdup(params->altsubject_match);
-               if (conn->altsubject_match == NULL)
+               wpa_printf(MSG_INFO, "GnuTLS: altsubject_match not supported");
+               return -1;
+       }
+
+       os_free(conn->suffix_match);
+       conn->suffix_match = NULL;
+       if (params->suffix_match) {
+               conn->suffix_match = os_strdup(params->suffix_match);
+               if (conn->suffix_match == NULL)
                        return -1;
        }
 
+#if GNUTLS_VERSION_NUMBER >= 0x030300
+       os_free(conn->domain_match);
+       conn->domain_match = NULL;
+       if (params->domain_match) {
+               conn->domain_match = os_strdup(params->domain_match);
+               if (conn->domain_match == NULL)
+                       return -1;
+       }
+#else /* < 3.3.0 */
+       if (params->domain_match) {
+               wpa_printf(MSG_INFO, "GnuTLS: domain_match not supported");
+               return -1;
+       }
+#endif /* >= 3.3.0 */
+
+       conn->flags = params->flags;
+
+       if (params->openssl_ciphers) {
+               wpa_printf(MSG_INFO, "GnuTLS: openssl_ciphers not supported");
+               return -1;
+       }
+
        /* TODO: gnutls_certificate_set_verify_flags(xcred, flags); 
         * to force peer validation(?) */
 
        if (params->ca_cert) {
-               conn->verify_peer = 1;
+               wpa_printf(MSG_DEBUG, "GnuTLS: Try to parse %s in DER format",
+                          params->ca_cert);
                ret = gnutls_certificate_set_x509_trust_file(
-                       conn->xcred, params->ca_cert, GNUTLS_X509_FMT_PEM);
+                       conn->xcred, params->ca_cert, GNUTLS_X509_FMT_DER);
                if (ret < 0) {
-                       wpa_printf(MSG_DEBUG, "Failed to read CA cert '%s' "
-                                  "in PEM format: %s", params->ca_cert,
+                       wpa_printf(MSG_DEBUG,
+                                  "GnuTLS: Failed to read CA cert '%s' in DER format (%s) - try in PEM format",
+                                  params->ca_cert,
                                   gnutls_strerror(ret));
                        ret = gnutls_certificate_set_x509_trust_file(
                                conn->xcred, params->ca_cert,
-                               GNUTLS_X509_FMT_DER);
+                               GNUTLS_X509_FMT_PEM);
                        if (ret < 0) {
-                               wpa_printf(MSG_DEBUG, "Failed to read CA cert "
-                                          "'%s' in DER format: %s",
+                               wpa_printf(MSG_DEBUG,
+                                          "Failed to read CA cert '%s' in PEM format: %s",
                                           params->ca_cert,
                                           gnutls_strerror(ret));
                                return -1;
                        }
                }
+       } else if (params->ca_cert_blob) {
+               gnutls_datum_t ca;
+
+               ca.data = (unsigned char *) params->ca_cert_blob;
+               ca.size = params->ca_cert_blob_len;
+
+               ret = gnutls_certificate_set_x509_trust_mem(
+                       conn->xcred, &ca, GNUTLS_X509_FMT_DER);
+               if (ret < 0) {
+                       wpa_printf(MSG_DEBUG,
+                                  "Failed to parse CA cert in DER format: %s",
+                                  gnutls_strerror(ret));
+                       ret = gnutls_certificate_set_x509_trust_mem(
+                               conn->xcred, &ca, GNUTLS_X509_FMT_PEM);
+                       if (ret < 0) {
+                               wpa_printf(MSG_DEBUG,
+                                          "Failed to parse CA cert in PEM format: %s",
+                                          gnutls_strerror(ret));
+                               return -1;
+                       }
+               }
+       } else if (params->ca_path) {
+               wpa_printf(MSG_INFO, "GnuTLS: ca_path not supported");
+               return -1;
+       }
+
+       conn->disable_time_checks = 0;
+       if (params->ca_cert || params->ca_cert_blob) {
+               conn->verify_peer = 1;
+               gnutls_certificate_set_verify_function(
+                       conn->xcred, tls_connection_verify_peer);
 
                if (params->flags & TLS_CONN_ALLOW_SIGN_RSA_MD5) {
                        gnutls_certificate_set_verify_flags(
@@ -598,6 +449,7 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
                }
 
                if (params->flags & TLS_CONN_DISABLE_TIME_CHECKS) {
+                       conn->disable_time_checks = 1;
                        gnutls_certificate_set_verify_flags(
                                conn->xcred,
                                GNUTLS_VERIFY_DISABLE_TIME_CHECKS);
@@ -605,19 +457,32 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
        }
 
        if (params->client_cert && params->private_key) {
-               /* TODO: private_key_passwd? */
+#if GNUTLS_VERSION_NUMBER >= 0x03010b
+               ret = gnutls_certificate_set_x509_key_file2(
+                       conn->xcred, params->client_cert, params->private_key,
+                       GNUTLS_X509_FMT_DER, params->private_key_passwd, 0);
+#else
+               /* private_key_passwd not (easily) supported here */
                ret = gnutls_certificate_set_x509_key_file(
                        conn->xcred, params->client_cert, params->private_key,
-                       GNUTLS_X509_FMT_PEM);
+                       GNUTLS_X509_FMT_DER);
+#endif
                if (ret < 0) {
                        wpa_printf(MSG_DEBUG, "Failed to read client cert/key "
-                                  "in PEM format: %s", gnutls_strerror(ret));
+                                  "in DER format: %s", gnutls_strerror(ret));
+#if GNUTLS_VERSION_NUMBER >= 0x03010b
+                       ret = gnutls_certificate_set_x509_key_file2(
+                               conn->xcred, params->client_cert,
+                               params->private_key, GNUTLS_X509_FMT_PEM,
+                               params->private_key_passwd, 0);
+#else
                        ret = gnutls_certificate_set_x509_key_file(
                                conn->xcred, params->client_cert,
-                               params->private_key, GNUTLS_X509_FMT_DER);
+                               params->private_key, GNUTLS_X509_FMT_PEM);
+#endif
                        if (ret < 0) {
                                wpa_printf(MSG_DEBUG, "Failed to read client "
-                                          "cert/key in DER format: %s",
+                                          "cert/key in PEM format: %s",
                                           gnutls_strerror(ret));
                                return ret;
                        }
@@ -626,7 +491,6 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
                int pkcs12_ok = 0;
 #ifdef PKCS12_FUNCS
                /* Try to load in PKCS#12 format */
-#if LIBGNUTLS_VERSION_NUMBER >= 0x010302
                ret = gnutls_certificate_set_x509_simple_pkcs12_file(
                        conn->xcred, params->private_key, GNUTLS_X509_FMT_DER,
                        params->private_key_passwd);
@@ -636,7 +500,6 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
                        return -1;
                } else
                        pkcs12_ok = 1;
-#endif /* LIBGNUTLS_VERSION_NUMBER >= 0x010302 */
 #endif /* PKCS12_FUNCS */
 
                if (!pkcs12_ok) {
@@ -644,9 +507,82 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
                                   "included");
                        return -1;
                }
+       } else if (params->client_cert_blob && params->private_key_blob) {
+               gnutls_datum_t cert, key;
+
+               cert.data = (unsigned char *) params->client_cert_blob;
+               cert.size = params->client_cert_blob_len;
+               key.data = (unsigned char *) params->private_key_blob;
+               key.size = params->private_key_blob_len;
+
+#if GNUTLS_VERSION_NUMBER >= 0x03010b
+               ret = gnutls_certificate_set_x509_key_mem2(
+                       conn->xcred, &cert, &key, GNUTLS_X509_FMT_DER,
+                       params->private_key_passwd, 0);
+#else
+               /* private_key_passwd not (easily) supported here */
+               ret = gnutls_certificate_set_x509_key_mem(
+                       conn->xcred, &cert, &key, GNUTLS_X509_FMT_DER);
+#endif
+               if (ret < 0) {
+                       wpa_printf(MSG_DEBUG, "Failed to read client cert/key "
+                                  "in DER format: %s", gnutls_strerror(ret));
+#if GNUTLS_VERSION_NUMBER >= 0x03010b
+                       ret = gnutls_certificate_set_x509_key_mem2(
+                               conn->xcred, &cert, &key, GNUTLS_X509_FMT_PEM,
+                               params->private_key_passwd, 0);
+#else
+                       /* private_key_passwd not (easily) supported here */
+                       ret = gnutls_certificate_set_x509_key_mem(
+                               conn->xcred, &cert, &key, GNUTLS_X509_FMT_PEM);
+#endif
+                       if (ret < 0) {
+                               wpa_printf(MSG_DEBUG, "Failed to read client "
+                                          "cert/key in PEM format: %s",
+                                          gnutls_strerror(ret));
+                               return ret;
+                       }
+               }
+       } else if (params->private_key_blob) {
+#ifdef PKCS12_FUNCS
+               gnutls_datum_t key;
+
+               key.data = (unsigned char *) params->private_key_blob;
+               key.size = params->private_key_blob_len;
+
+               /* Try to load in PKCS#12 format */
+               ret = gnutls_certificate_set_x509_simple_pkcs12_mem(
+                       conn->xcred, &key, GNUTLS_X509_FMT_DER,
+                       params->private_key_passwd);
+               if (ret != 0) {
+                       wpa_printf(MSG_DEBUG, "Failed to load private_key in "
+                                  "PKCS#12 format: %s", gnutls_strerror(ret));
+                       return -1;
+               }
+#else /* PKCS12_FUNCS */
+               wpa_printf(MSG_DEBUG, "GnuTLS: PKCS#12 support not included");
+               return -1;
+#endif /* PKCS12_FUNCS */
        }
 
-       conn->tls_ia = params->tls_ia;
+#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,
@@ -656,28 +592,6 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
                           gnutls_strerror(ret));
        }
 
-#ifdef GNUTLS_IA
-       if (conn->iacred_cli)
-               gnutls_ia_free_client_credentials(conn->iacred_cli);
-
-       ret = gnutls_ia_allocate_client_credentials(&conn->iacred_cli);
-       if (ret) {
-               wpa_printf(MSG_DEBUG, "Failed to allocate IA credentials: %s",
-                          gnutls_strerror(ret));
-               return -1;
-       }
-
-       ret = gnutls_credentials_set(conn->session, GNUTLS_CRD_IA,
-                                    conn->iacred_cli);
-       if (ret) {
-               wpa_printf(MSG_DEBUG, "Failed to configure IA credentials: %s",
-                          gnutls_strerror(ret));
-               gnutls_ia_free_client_credentials(conn->iacred_cli);
-               conn->iacred_cli = NULL;
-               return -1;
-       }
-#endif /* GNUTLS_IE */
-
        return ret;
 }
 
@@ -706,17 +620,17 @@ int tls_global_set_params(void *tls_ctx,
 
        if (params->ca_cert) {
                ret = gnutls_certificate_set_x509_trust_file(
-                       global->xcred, params->ca_cert, GNUTLS_X509_FMT_PEM);
+                       global->xcred, params->ca_cert, GNUTLS_X509_FMT_DER);
                if (ret < 0) {
                        wpa_printf(MSG_DEBUG, "Failed to read CA cert '%s' "
-                                  "in PEM format: %s", params->ca_cert,
+                                  "in DER format: %s", params->ca_cert,
                                   gnutls_strerror(ret));
                        ret = gnutls_certificate_set_x509_trust_file(
                                global->xcred, params->ca_cert,
-                               GNUTLS_X509_FMT_DER);
+                               GNUTLS_X509_FMT_PEM);
                        if (ret < 0) {
                                wpa_printf(MSG_DEBUG, "Failed to read CA cert "
-                                          "'%s' in DER format: %s",
+                                          "'%s' in PEM format: %s",
                                           params->ca_cert,
                                           gnutls_strerror(ret));
                                goto fail;
@@ -740,16 +654,16 @@ int tls_global_set_params(void *tls_ctx,
                /* TODO: private_key_passwd? */
                ret = gnutls_certificate_set_x509_key_file(
                        global->xcred, params->client_cert,
-                       params->private_key, GNUTLS_X509_FMT_PEM);
+                       params->private_key, GNUTLS_X509_FMT_DER);
                if (ret < 0) {
                        wpa_printf(MSG_DEBUG, "Failed to read client cert/key "
-                                  "in PEM format: %s", gnutls_strerror(ret));
+                                  "in DER format: %s", gnutls_strerror(ret));
                        ret = gnutls_certificate_set_x509_key_file(
                                global->xcred, params->client_cert,
-                               params->private_key, GNUTLS_X509_FMT_DER);
+                               params->private_key, GNUTLS_X509_FMT_PEM);
                        if (ret < 0) {
                                wpa_printf(MSG_DEBUG, "Failed to read client "
-                                          "cert/key in DER format: %s",
+                                          "cert/key in PEM format: %s",
                                           gnutls_strerror(ret));
                                goto fail;
                        }
@@ -758,7 +672,6 @@ int tls_global_set_params(void *tls_ctx,
                int pkcs12_ok = 0;
 #ifdef PKCS12_FUNCS
                /* Try to load in PKCS#12 format */
-#if LIBGNUTLS_VERSION_NUMBER >= 0x010302
                ret = gnutls_certificate_set_x509_simple_pkcs12_file(
                        global->xcred, params->private_key,
                        GNUTLS_X509_FMT_DER, params->private_key_passwd);
@@ -768,7 +681,6 @@ int tls_global_set_params(void *tls_ctx,
                        goto fail;
                } else
                        pkcs12_ok = 1;
-#endif /* LIBGNUTLS_VERSION_NUMBER >= 0x010302 */
 #endif /* PKCS12_FUNCS */
 
                if (!pkcs12_ok) {
@@ -796,7 +708,8 @@ int tls_global_set_verify(void *ssl_ctx, int check_crl)
 
 
 int tls_connection_set_verify(void *ssl_ctx, struct tls_connection *conn,
-                             int verify_peer)
+                             int verify_peer, unsigned int flags,
+                             const u8 *session_ctx, size_t session_ctx_len)
 {
        if (conn == NULL || conn->session == NULL)
                return -1;
@@ -810,75 +723,269 @@ int tls_connection_set_verify(void *ssl_ctx, struct tls_connection *conn,
 }
 
 
-int tls_connection_get_keys(void *ssl_ctx, struct tls_connection *conn,
-                           struct tls_keys *keys)
+int tls_connection_get_random(void *ssl_ctx, struct tls_connection *conn,
+                           struct tls_random *keys)
 {
-#ifdef GNUTLS_INTERNAL_STRUCTURE_HACK
-       security_parameters_st *sec;
-#endif /* GNUTLS_INTERNAL_STRUCTURE_HACK */
+#if GNUTLS_VERSION_NUMBER >= 0x030012
+       gnutls_datum_t client, server;
 
        if (conn == NULL || conn->session == NULL || keys == NULL)
                return -1;
 
        os_memset(keys, 0, sizeof(*keys));
-
-#ifdef GNUTLS_INTERNAL_STRUCTURE_HACK
-       sec = &conn->session->security_parameters;
-       keys->master_key = sec->master_secret;
-       keys->master_key_len = TLS_MASTER_SIZE;
-       keys->client_random = sec->client_random;
-       keys->server_random = sec->server_random;
-#else /* GNUTLS_INTERNAL_STRUCTURE_HACK */
-       keys->client_random =
-               (u8 *) gnutls_session_get_client_random(conn->session);
-       keys->server_random =
-               (u8 *) gnutls_session_get_server_random(conn->session);
-       /* No access to master_secret */
-#endif /* GNUTLS_INTERNAL_STRUCTURE_HACK */
-
-#ifdef GNUTLS_IA
-       gnutls_ia_extract_inner_secret(conn->session,
-                                      (char *) conn->inner_secret);
-       keys->inner_secret = conn->inner_secret;
-       keys->inner_secret_len = TLS_MASTER_SIZE;
-#endif /* GNUTLS_IA */
-
-       keys->client_random_len = TLS_RANDOM_SIZE;
-       keys->server_random_len = TLS_RANDOM_SIZE;
+       gnutls_session_get_random(conn->session, &client, &server);
+       keys->client_random = client.data;
+       keys->server_random = server.data;
+       keys->client_random_len = client.size;
+       keys->server_random_len = client.size;
 
        return 0;
+#else /* 3.0.18 */
+       return -1;
+#endif /* 3.0.18 */
 }
 
 
 int tls_connection_prf(void *tls_ctx, struct tls_connection *conn,
                       const char *label, int server_random_first,
-                      u8 *out, size_t out_len)
+                      int skip_keyblock, u8 *out, size_t out_len)
 {
-#if LIBGNUTLS_VERSION_NUMBER >= 0x010302
-       if (conn == NULL || conn->session == NULL)
+       if (conn == NULL || conn->session == NULL || skip_keyblock)
                return -1;
 
        return gnutls_prf(conn->session, os_strlen(label), label,
                          server_random_first, 0, NULL, out_len, (char *) out);
-#else /* LIBGNUTLS_VERSION_NUMBER >= 0x010302 */
+}
+
+
+static void gnutls_tls_fail_event(struct tls_connection *conn,
+                                 const gnutls_datum_t *cert, int depth,
+                                 const char *subject, const char *err_str,
+                                 enum tls_fail_reason reason)
+{
+       union tls_event_data ev;
+       struct tls_global *global = conn->global;
+       struct wpabuf *cert_buf = NULL;
+
+       if (global->event_cb == NULL)
+               return;
+
+       os_memset(&ev, 0, sizeof(ev));
+       ev.cert_fail.depth = depth;
+       ev.cert_fail.subject = subject ? subject : "";
+       ev.cert_fail.reason = reason;
+       ev.cert_fail.reason_txt = err_str;
+       if (cert) {
+               cert_buf = wpabuf_alloc_copy(cert->data, cert->size);
+               ev.cert_fail.cert = cert_buf;
+       }
+       global->event_cb(global->cb_ctx, TLS_CERT_CHAIN_FAILURE, &ev);
+       wpabuf_free(cert_buf);
+}
+
+
+#if GNUTLS_VERSION_NUMBER < 0x030300
+static int server_eku_purpose(gnutls_x509_crt_t cert)
+{
+       unsigned int i;
+
+       for (i = 0; ; i++) {
+               char oid[128];
+               size_t oid_size = sizeof(oid);
+               int res;
+
+               res = gnutls_x509_crt_get_key_purpose_oid(cert, i, oid,
+                                                         &oid_size, NULL);
+               if (res == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+                       if (i == 0) {
+                               /* No EKU - assume any use allowed */
+                               return 1;
+                       }
+                       break;
+               }
+
+               if (res < 0) {
+                       wpa_printf(MSG_INFO, "GnuTLS: Failed to get EKU");
+                       return 0;
+               }
+
+               wpa_printf(MSG_DEBUG, "GnuTLS: Certificate purpose: %s", oid);
+               if (os_strcmp(oid, GNUTLS_KP_TLS_WWW_SERVER) == 0 ||
+                   os_strcmp(oid, GNUTLS_KP_ANY) == 0)
+                       return 1;
+       }
+
+       return 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;
-#endif /* LIBGNUTLS_VERSION_NUMBER >= 0x010302 */
+#else /* GnuTLS 3.1.3 or newer */
+       return 0;
+#endif /* GnuTLS 3.1.3 or newer */
 }
 
 
-static int tls_connection_verify_peer(struct tls_connection *conn,
-                                     gnutls_alert_description_t *err)
+static int tls_connection_verify_peer(gnutls_session_t session)
 {
+       struct tls_connection *conn;
        unsigned int status, num_certs, i;
        struct os_time now;
        const gnutls_datum_t *certs;
        gnutls_x509_crt_t cert;
+       gnutls_alert_description_t err;
+       int res;
+
+       conn = gnutls_session_get_ptr(session);
+       if (!conn->verify_peer) {
+               wpa_printf(MSG_DEBUG,
+                          "GnuTLS: No peer certificate verification enabled");
+               return 0;
+       }
 
-       if (gnutls_certificate_verify_peers2(conn->session, &status) < 0) {
+       wpa_printf(MSG_DEBUG, "GnuTSL: Verifying peer certificate");
+
+#if GNUTLS_VERSION_NUMBER >= 0x030300
+       {
+               gnutls_typed_vdata_st data[1];
+               unsigned int elements = 0;
+
+               os_memset(data, 0, sizeof(data));
+               if (!conn->global->server) {
+                       data[elements].type = GNUTLS_DT_KEY_PURPOSE_OID;
+                       data[elements].data = (void *) GNUTLS_KP_TLS_WWW_SERVER;
+                       elements++;
+               }
+               res = gnutls_certificate_verify_peers(session, data, 1,
+                                                     &status);
+       }
+#else /* < 3.3.0 */
+       res = gnutls_certificate_verify_peers2(session, &status);
+#endif
+       if (res < 0) {
                wpa_printf(MSG_INFO, "TLS: Failed to verify peer "
                           "certificate chain");
-               *err = GNUTLS_A_INTERNAL_ERROR;
-               return -1;
+               err = GNUTLS_A_INTERNAL_ERROR;
+               goto out;
+       }
+
+#if GNUTLS_VERSION_NUMBER >= 0x030104
+       {
+               gnutls_datum_t info;
+               int ret, type;
+
+               type = gnutls_certificate_type_get(session);
+               ret = gnutls_certificate_verification_status_print(status, type,
+                                                                  &info, 0);
+               if (ret < 0) {
+                       wpa_printf(MSG_DEBUG,
+                                  "GnuTLS: Failed to print verification status");
+                       err = GNUTLS_A_INTERNAL_ERROR;
+                       goto out;
+               }
+               wpa_printf(MSG_DEBUG, "GnuTLS: %s", info.data);
+               gnutls_free(info.data);
+       }
+#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)) {
@@ -886,51 +993,74 @@ static int tls_connection_verify_peer(struct tls_connection *conn,
                if (status & GNUTLS_CERT_INSECURE_ALGORITHM) {
                        wpa_printf(MSG_INFO, "TLS: Certificate uses insecure "
                                   "algorithm");
-                       *err = GNUTLS_A_INSUFFICIENT_SECURITY;
+                       gnutls_tls_fail_event(conn, NULL, 0, NULL,
+                                             "certificate uses insecure algorithm",
+                                             TLS_FAIL_BAD_CERTIFICATE);
+                       err = GNUTLS_A_INSUFFICIENT_SECURITY;
+                       goto out;
                }
                if (status & GNUTLS_CERT_NOT_ACTIVATED) {
                        wpa_printf(MSG_INFO, "TLS: Certificate not yet "
                                   "activated");
-                       *err = GNUTLS_A_CERTIFICATE_EXPIRED;
+                       gnutls_tls_fail_event(conn, NULL, 0, NULL,
+                                             "certificate not yet valid",
+                                             TLS_FAIL_NOT_YET_VALID);
+                       err = GNUTLS_A_CERTIFICATE_EXPIRED;
+                       goto out;
                }
                if (status & GNUTLS_CERT_EXPIRED) {
                        wpa_printf(MSG_INFO, "TLS: Certificate expired");
-                       *err = GNUTLS_A_CERTIFICATE_EXPIRED;
+                       gnutls_tls_fail_event(conn, NULL, 0, NULL,
+                                             "certificate has expired",
+                                             TLS_FAIL_EXPIRED);
+                       err = GNUTLS_A_CERTIFICATE_EXPIRED;
+                       goto out;
                }
-               return -1;
+               gnutls_tls_fail_event(conn, NULL, 0, NULL,
+                                     "untrusted certificate",
+                                     TLS_FAIL_UNTRUSTED);
+               err = GNUTLS_A_INTERNAL_ERROR;
+               goto out;
        }
 
        if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
                wpa_printf(MSG_INFO, "TLS: Peer certificate does not have a "
                           "known issuer");
-               *err = GNUTLS_A_UNKNOWN_CA;
-               return -1;
+               gnutls_tls_fail_event(conn, NULL, 0, NULL, "signed not found",
+                                     TLS_FAIL_UNTRUSTED);
+               err = GNUTLS_A_UNKNOWN_CA;
+               goto out;
        }
 
        if (status & GNUTLS_CERT_REVOKED) {
                wpa_printf(MSG_INFO, "TLS: Peer certificate has been revoked");
-               *err = GNUTLS_A_CERTIFICATE_REVOKED;
-               return -1;
+               gnutls_tls_fail_event(conn, NULL, 0, NULL,
+                                     "certificate revoked",
+                                     TLS_FAIL_REVOKED);
+               err = GNUTLS_A_CERTIFICATE_REVOKED;
+               goto out;
        }
 
-       os_get_time(&now);
-
-       certs = gnutls_certificate_get_peers(conn->session, &num_certs);
-       if (certs == NULL) {
-               wpa_printf(MSG_INFO, "TLS: No peer certificate chain "
-                          "received");
-               *err = GNUTLS_A_UNKNOWN_CA;
-               return -1;
+       if (status != 0) {
+               wpa_printf(MSG_INFO, "TLS: Unknown verification status: %d",
+                          status);
+               err = GNUTLS_A_INTERNAL_ERROR;
+               goto out;
        }
 
+       if (check_ocsp(conn, session, &err))
+               goto out;
+
+       os_get_time(&now);
+
        for (i = 0; i < num_certs; i++) {
                char *buf;
                size_t len;
                if (gnutls_x509_crt_init(&cert) < 0) {
                        wpa_printf(MSG_INFO, "TLS: Certificate initialization "
                                   "failed");
-                       *err = GNUTLS_A_BAD_CERTIFICATE;
-                       return -1;
+                       err = GNUTLS_A_BAD_CERTIFICATE;
+                       goto out;
                }
 
                if (gnutls_x509_crt_import(cert, &certs[i],
@@ -938,8 +1068,8 @@ static int tls_connection_verify_peer(struct tls_connection *conn,
                        wpa_printf(MSG_INFO, "TLS: Could not parse peer "
                                   "certificate %d/%d", i + 1, num_certs);
                        gnutls_x509_crt_deinit(cert);
-                       *err = GNUTLS_A_BAD_CERTIFICATE;
-                       return -1;
+                       err = GNUTLS_A_BAD_CERTIFICATE;
+                       goto out;
                }
 
                gnutls_x509_crt_get_dn(cert, NULL, &len);
@@ -952,26 +1082,128 @@ static int tls_connection_verify_peer(struct tls_connection *conn,
                wpa_printf(MSG_DEBUG, "TLS: Peer cert chain %d/%d: %s",
                           i + 1, num_certs, buf);
 
-               if (i == 0) {
-                       /* TODO: validate subject_match and altsubject_match */
+               if (conn->global->event_cb) {
+                       struct wpabuf *cert_buf = NULL;
+                       union tls_event_data ev;
+#ifdef CONFIG_SHA256
+                       u8 hash[32];
+                       const u8 *_addr[1];
+                       size_t _len[1];
+#endif /* CONFIG_SHA256 */
+
+                       os_memset(&ev, 0, sizeof(ev));
+                       if (conn->global->cert_in_cb) {
+                               cert_buf = wpabuf_alloc_copy(certs[i].data,
+                                                            certs[i].size);
+                               ev.peer_cert.cert = cert_buf;
+                       }
+#ifdef CONFIG_SHA256
+                       _addr[0] = certs[i].data;
+                       _len[0] = certs[i].size;
+                       if (sha256_vector(1, _addr, _len, hash) == 0) {
+                               ev.peer_cert.hash = hash;
+                               ev.peer_cert.hash_len = sizeof(hash);
+                       }
+#endif /* CONFIG_SHA256 */
+                       ev.peer_cert.depth = i;
+                       ev.peer_cert.subject = buf;
+                       conn->global->event_cb(conn->global->cb_ctx,
+                                              TLS_PEER_CERTIFICATE, &ev);
+                       wpabuf_free(cert_buf);
                }
 
-               os_free(buf);
+               if (i == 0) {
+                       if (conn->suffix_match &&
+                           !gnutls_x509_crt_check_hostname(
+                                   cert, conn->suffix_match)) {
+                               wpa_printf(MSG_WARNING,
+                                          "TLS: Domain suffix match '%s' not found",
+                                          conn->suffix_match);
+                               gnutls_tls_fail_event(
+                                       conn, &certs[i], i, buf,
+                                       "Domain suffix mismatch",
+                                       TLS_FAIL_DOMAIN_SUFFIX_MISMATCH);
+                               err = GNUTLS_A_BAD_CERTIFICATE;
+                               gnutls_x509_crt_deinit(cert);
+                               os_free(buf);
+                               goto out;
+                       }
 
-               if (gnutls_x509_crt_get_expiration_time(cert) < now.sec ||
-                   gnutls_x509_crt_get_activation_time(cert) > now.sec) {
+#if GNUTLS_VERSION_NUMBER >= 0x030300
+                       if (conn->domain_match &&
+                           !gnutls_x509_crt_check_hostname2(
+                                   cert, conn->domain_match,
+                                   GNUTLS_VERIFY_DO_NOT_ALLOW_WILDCARDS)) {
+                               wpa_printf(MSG_WARNING,
+                                          "TLS: Domain match '%s' not found",
+                                          conn->domain_match);
+                               gnutls_tls_fail_event(
+                                       conn, &certs[i], i, buf,
+                                       "Domain mismatch",
+                                       TLS_FAIL_DOMAIN_MISMATCH);
+                               err = GNUTLS_A_BAD_CERTIFICATE;
+                               gnutls_x509_crt_deinit(cert);
+                               os_free(buf);
+                               goto out;
+                       }
+#endif /* >= 3.3.0 */
+
+                       /* TODO: validate altsubject_match.
+                        * For now, any such configuration is rejected in
+                        * tls_connection_set_params() */
+
+#if GNUTLS_VERSION_NUMBER < 0x030300
+                       /*
+                        * gnutls_certificate_verify_peers() not available, so
+                        * need to check EKU separately.
+                        */
+                       if (!conn->global->server &&
+                           !server_eku_purpose(cert)) {
+                               wpa_printf(MSG_WARNING,
+                                          "GnuTLS: No server EKU");
+                               gnutls_tls_fail_event(
+                                       conn, &certs[i], i, buf,
+                                       "No server EKU",
+                                       TLS_FAIL_BAD_CERTIFICATE);
+                               err = GNUTLS_A_BAD_CERTIFICATE;
+                               gnutls_x509_crt_deinit(cert);
+                               os_free(buf);
+                               goto out;
+                       }
+#endif /* < 3.3.0 */
+               }
+
+               if (!conn->disable_time_checks &&
+                   (gnutls_x509_crt_get_expiration_time(cert) < now.sec ||
+                    gnutls_x509_crt_get_activation_time(cert) > now.sec)) {
                        wpa_printf(MSG_INFO, "TLS: Peer certificate %d/%d is "
                                   "not valid at this time",
                                   i + 1, num_certs);
+                       gnutls_tls_fail_event(
+                               conn, &certs[i], i, buf,
+                               "Certificate is not valid at this time",
+                               TLS_FAIL_EXPIRED);
                        gnutls_x509_crt_deinit(cert);
-                       *err = GNUTLS_A_CERTIFICATE_EXPIRED;
-                       return -1;
+                       os_free(buf);
+                       err = GNUTLS_A_CERTIFICATE_EXPIRED;
+                       goto out;
                }
 
+               os_free(buf);
+
                gnutls_x509_crt_deinit(cert);
        }
 
+       if (conn->global->event_cb != NULL)
+               conn->global->event_cb(conn->global->cb_ctx,
+                                      TLS_CERT_CHAIN_SUCCESS, NULL);
+
        return 0;
+
+out:
+       conn->failed++;
+       gnutls_alert_send(session, GNUTLS_AL_FATAL, err);
+       return GNUTLS_E_CERTIFICATE_ERROR;
 }
 
 
@@ -988,7 +1220,7 @@ static struct wpabuf * gnutls_get_appl_data(struct tls_connection *conn)
                                 wpabuf_size(ad));
        wpa_printf(MSG_DEBUG, "GnuTLS: gnutls_record_recv: %d", res);
        if (res < 0) {
-               wpa_printf(MSG_DEBUG, "%s - gnutls_ia_recv failed: %d "
+               wpa_printf(MSG_DEBUG, "%s - gnutls_record_recv failed: %d "
                           "(%s)", __func__, (int) res,
                           gnutls_strerror(res));
                wpabuf_free(ad);
@@ -1029,6 +1261,8 @@ struct wpabuf * tls_connection_handshake(void *tls_ctx,
 
        ret = gnutls_handshake(conn->session);
        if (ret < 0) {
+               gnutls_alert_description_t alert;
+
                switch (ret) {
                case GNUTLS_E_AGAIN:
                        if (global->server && conn->established &&
@@ -1039,10 +1273,20 @@ struct wpabuf * tls_connection_handshake(void *tls_ctx,
                        }
                        break;
                case GNUTLS_E_FATAL_ALERT_RECEIVED:
+                       alert = gnutls_alert_get(conn->session);
                        wpa_printf(MSG_DEBUG, "%s - received fatal '%s' alert",
-                                  __func__, gnutls_alert_get_name(
-                                          gnutls_alert_get(conn->session)));
+                                  __func__, gnutls_alert_get_name(alert));
                        conn->read_alerts++;
+                       if (conn->global->event_cb != NULL) {
+                               union tls_event_data ev;
+
+                               os_memset(&ev, 0, sizeof(ev));
+                               ev.alert.is_local = 0;
+                               ev.alert.type = gnutls_alert_get_name(alert);
+                               ev.alert.description = ev.alert.type;
+                               conn->global->event_cb(conn->global->cb_ctx,
+                                                      TLS_ALERT, &ev);
+                       }
                        /* continue */
                default:
                        wpa_printf(MSG_DEBUG, "%s - gnutls_handshake failed "
@@ -1051,31 +1295,21 @@ struct wpabuf * tls_connection_handshake(void *tls_ctx,
                }
        } else {
                size_t size;
-               gnutls_alert_description_t err;
 
-               if (conn->verify_peer &&
-                   tls_connection_verify_peer(conn, &err)) {
-                       wpa_printf(MSG_INFO, "TLS: Peer certificate chain "
-                                  "failed validation");
-                       conn->failed++;
-                       gnutls_alert_send(conn->session, GNUTLS_AL_FATAL, err);
-                       goto out;
-               }
+               wpa_printf(MSG_DEBUG, "TLS: Handshake completed successfully");
 
-#ifdef CONFIG_GNUTLS_EXTRA
-               if (conn->tls_ia && !gnutls_ia_handshake_p(conn->session)) {
-                       wpa_printf(MSG_INFO, "TLS: No TLS/IA negotiation");
-                       conn->failed++;
-                       return NULL;
-               }
-#endif /* CONFIG_GNUTLS_EXTRA */
+#if GNUTLS_VERSION_NUMBER >= 0x03010a
+               {
+                       char *desc;
 
-               if (conn->tls_ia)
-                       wpa_printf(MSG_DEBUG, "TLS: Start TLS/IA handshake");
-               else {
-                       wpa_printf(MSG_DEBUG, "TLS: Handshake completed "
-                                  "successfully");
+                       desc = gnutls_session_get_desc(conn->session);
+                       if (desc) {
+                               wpa_printf(MSG_DEBUG, "GnuTLS: %s", desc);
+                               gnutls_free(desc);
+                       }
                }
+#endif /* GnuTLS 3.1.10 or newer */
+
                conn->established = 1;
                if (conn->push_buf == NULL) {
                        /* Need to return something to get final TLS ACK. */
@@ -1099,7 +1333,6 @@ struct wpabuf * tls_connection_handshake(void *tls_ctx,
                        *appl_data = gnutls_get_appl_data(conn);
        }
 
-out:
        out_data = conn->push_buf;
        conn->push_buf = NULL;
        return out_data;
@@ -1122,12 +1355,6 @@ struct wpabuf * tls_connection_encrypt(void *tls_ctx,
        ssize_t res;
        struct wpabuf *buf;
 
-#ifdef GNUTLS_IA
-       if (conn->tls_ia)
-               res = gnutls_ia_send(conn->session, wpabuf_head(in_data),
-                                    wpabuf_len(in_data));
-       else
-#endif /* GNUTLS_IA */
        res = gnutls_record_send(conn->session, wpabuf_head(in_data),
                                 wpabuf_len(in_data));
        if (res < 0) {
@@ -1170,65 +1397,6 @@ struct wpabuf * tls_connection_decrypt(void *tls_ctx,
        if (out == NULL)
                return NULL;
 
-#ifdef GNUTLS_IA
-       if (conn->tls_ia) {
-               res = gnutls_ia_recv(conn->session, wpabuf_mhead(out),
-                                    wpabuf_size(out));
-               if (res == GNUTLS_E_WARNING_IA_IPHF_RECEIVED ||
-                   res == GNUTLS_E_WARNING_IA_FPHF_RECEIVED) {
-                       int final = res == GNUTLS_E_WARNING_IA_FPHF_RECEIVED;
-                       wpa_printf(MSG_DEBUG, "%s: Received %sPhaseFinished",
-                                  __func__, final ? "Final" : "Intermediate");
-
-                       res = gnutls_ia_permute_inner_secret(
-                               conn->session, conn->session_keys_len,
-                               (char *) conn->session_keys);
-                       if (conn->session_keys) {
-                               os_memset(conn->session_keys, 0,
-                                         conn->session_keys_len);
-                               os_free(conn->session_keys);
-                       }
-                       conn->session_keys = NULL;
-                       conn->session_keys_len = 0;
-                       if (res) {
-                               wpa_printf(MSG_DEBUG, "%s: Failed to permute "
-                                          "inner secret: %s",
-                                          __func__, gnutls_strerror(res));
-                               wpabuf_free(out);
-                               return NULL;
-                       }
-
-                       res = gnutls_ia_verify_endphase(conn->session,
-                                                       wpabuf_head(out));
-                       if (res == 0) {
-                               wpa_printf(MSG_DEBUG, "%s: Correct endphase "
-                                          "checksum", __func__);
-                       } else {
-                               wpa_printf(MSG_INFO, "%s: Endphase "
-                                          "verification failed: %s",
-                                          __func__, gnutls_strerror(res));
-                               wpabuf_free(out);
-                               return NULL;
-                       }
-
-                       if (final)
-                               conn->final_phase_finished = 1;
-
-                       return out;
-               }
-
-               if (res < 0) {
-                       wpa_printf(MSG_DEBUG, "%s - gnutls_ia_recv failed: %d "
-                                  "(%s)", __func__, (int) res,
-                                  gnutls_strerror(res));
-                       wpabuf_free(out);
-                       return NULL;
-               }
-               wpabuf_put(out, res);
-               return out;
-       }
-#endif /* GNUTLS_IA */
-
        res = gnutls_record_recv(conn->session, wpabuf_mhead(out),
                                 wpabuf_size(out));
        if (res < 0) {
@@ -1259,6 +1427,14 @@ int tls_connection_set_cipher_list(void *tls_ctx, struct tls_connection *conn,
 }
 
 
+int tls_get_version(void *ssl_ctx, struct tls_connection *conn,
+                   char *buf, size_t buflen)
+{
+       /* TODO */
+       return -1;
+}
+
+
 int tls_get_cipher(void *ssl_ctx, struct tls_connection *conn,
                   char *buf, size_t buflen)
 {
@@ -1309,149 +1485,39 @@ int tls_connection_get_write_alerts(void *ssl_ctx, struct tls_connection *conn)
 }
 
 
-int tls_connection_get_keyblock_size(void *tls_ctx,
-                                    struct tls_connection *conn)
+int tls_connection_set_session_ticket_cb(void *tls_ctx,
+                                        struct tls_connection *conn,
+                                        tls_session_ticket_cb cb, void *ctx)
 {
-       /* TODO */
        return -1;
 }
 
 
-unsigned int tls_capabilities(void *tls_ctx)
+int tls_get_library_version(char *buf, size_t buf_len)
 {
-       unsigned int capa = 0;
-
-#ifdef GNUTLS_IA
-       capa |= TLS_CAPABILITY_IA;
-#endif /* GNUTLS_IA */
-
-       return capa;
-}
-
-
-int tls_connection_set_ia(void *tls_ctx, struct tls_connection *conn,
-                         int tls_ia)
-{
-#ifdef GNUTLS_IA
-       int ret;
-
-       if (conn == NULL)
-               return -1;
-
-       conn->tls_ia = tls_ia;
-       if (!tls_ia)
-               return 0;
-
-       ret = gnutls_ia_allocate_server_credentials(&conn->iacred_srv);
-       if (ret) {
-               wpa_printf(MSG_DEBUG, "Failed to allocate IA credentials: %s",
-                          gnutls_strerror(ret));
-               return -1;
-       }
-
-       ret = gnutls_credentials_set(conn->session, GNUTLS_CRD_IA,
-                                    conn->iacred_srv);
-       if (ret) {
-               wpa_printf(MSG_DEBUG, "Failed to configure IA credentials: %s",
-                          gnutls_strerror(ret));
-               gnutls_ia_free_server_credentials(conn->iacred_srv);
-               conn->iacred_srv = NULL;
-               return -1;
-       }
-
-       return 0;
-#else /* GNUTLS_IA */
-       return -1;
-#endif /* GNUTLS_IA */
+       return os_snprintf(buf, buf_len, "GnuTLS build=%s run=%s",
+                          GNUTLS_VERSION, gnutls_check_version(NULL));
 }
 
 
-struct wpabuf * tls_connection_ia_send_phase_finished(
-       void *tls_ctx, struct tls_connection *conn, int final)
+void tls_connection_set_success_data(struct tls_connection *conn,
+                                    struct wpabuf *data)
 {
-#ifdef GNUTLS_IA
-       int ret;
-       struct wpabuf *buf;
-
-       if (conn == NULL || conn->session == NULL || !conn->tls_ia)
-               return NULL;
-
-       ret = gnutls_ia_permute_inner_secret(conn->session,
-                                            conn->session_keys_len,
-                                            (char *) conn->session_keys);
-       if (conn->session_keys) {
-               os_memset(conn->session_keys, 0, conn->session_keys_len);
-               os_free(conn->session_keys);
-       }
-       conn->session_keys = NULL;
-       conn->session_keys_len = 0;
-       if (ret) {
-               wpa_printf(MSG_DEBUG, "%s: Failed to permute inner secret: %s",
-                          __func__, gnutls_strerror(ret));
-               return NULL;
-       }
-
-       ret = gnutls_ia_endphase_send(conn->session, final);
-       if (ret) {
-               wpa_printf(MSG_DEBUG, "%s: Failed to send endphase: %s",
-                          __func__, gnutls_strerror(ret));
-               return NULL;
-       }
-
-       buf = conn->push_buf;
-       conn->push_buf = NULL;
-       return buf;
-#else /* GNUTLS_IA */
-       return NULL;
-#endif /* GNUTLS_IA */
 }
 
 
-int tls_connection_ia_final_phase_finished(void *tls_ctx,
-                                          struct tls_connection *conn)
+void tls_connection_set_success_data_resumed(struct tls_connection *conn)
 {
-       if (conn == NULL)
-               return -1;
-
-       return conn->final_phase_finished;
 }
 
 
-int tls_connection_ia_permute_inner_secret(void *tls_ctx,
-                                          struct tls_connection *conn,
-                                          const u8 *key, size_t key_len)
+const struct wpabuf *
+tls_connection_get_success_data(struct tls_connection *conn)
 {
-#ifdef GNUTLS_IA
-       if (conn == NULL || !conn->tls_ia)
-               return -1;
-
-       if (conn->session_keys) {
-               os_memset(conn->session_keys, 0, conn->session_keys_len);
-               os_free(conn->session_keys);
-       }
-       conn->session_keys_len = 0;
-
-       if (key) {
-               conn->session_keys = os_malloc(key_len);
-               if (conn->session_keys == NULL)
-                       return -1;
-               os_memcpy(conn->session_keys, key, key_len);
-               conn->session_keys_len = key_len;
-       } else {
-               conn->session_keys = NULL;
-               conn->session_keys_len = 0;
-       }
-
-       return 0;
-#else /* GNUTLS_IA */
-       return -1;
-#endif /* GNUTLS_IA */
+       return NULL;
 }
 
 
-int tls_connection_set_session_ticket_cb(void *tls_ctx,
-                                        struct tls_connection *conn,
-                                        tls_session_ticket_cb cb, void *ctx)
+void tls_connection_remove_session(struct tls_connection *conn)
 {
-       return -1;
 }