diff options
Diffstat (limited to 'src/crypto/tls_gnutls.c')
-rw-r--r-- | src/crypto/tls_gnutls.c | 939 |
1 files changed, 626 insertions, 313 deletions
diff --git a/src/crypto/tls_gnutls.c b/src/crypto/tls_gnutls.c index a5d72f407a8d2..65db6fcc25653 100644 --- a/src/crypto/tls_gnutls.c +++ b/src/crypto/tls_gnutls.c @@ -12,61 +12,15 @@ #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 "crypto/crypto.h" #include "tls.h" -#define WPA_TLS_RANDOM_SIZE 32 -#define WPA_TLS_MASTER_SIZE 48 - - -#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[WPA_TLS_MASTER_SIZE]; - opaque client_random[WPA_TLS_RANDOM_SIZE]; - opaque server_random[WPA_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 { @@ -78,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; @@ -96,9 +56,16 @@ struct tls_connection { int params_set; gnutls_certificate_credentials_t xcred; + + char *suffix_match; + char *domain_match; + unsigned int flags; }; +static int tls_connection_verify_peer(gnutls_session_t session); + + static void tls_log_func(int level, const char *msg) { char *s, *pos; @@ -125,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) @@ -153,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; } @@ -201,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; @@ -230,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; @@ -248,12 +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) { -#if LIBGNUTLS_VERSION_NUMBER >= 0x020200 const char *err; -#else /* LIBGNUTLS_VERSION_NUMBER >= 0x020200 */ - const int cert_types[2] = { GNUTLS_CRT_X509, 0 }; - const int protos[2] = { GNUTLS_TLS1, 0 }; -#endif /* LIBGNUTLS_VERSION_NUMBER < 0x020200 */ int ret; ret = gnutls_init(&conn->session, @@ -268,7 +210,6 @@ static int tls_gnutls_init_session(struct tls_global *global, if (ret < 0) goto fail; -#if LIBGNUTLS_VERSION_NUMBER >= 0x020200 ret = gnutls_priority_set_direct(conn->session, "NORMAL:-VERS-SSL3.0", &err); if (ret < 0) { @@ -276,19 +217,11 @@ static int tls_gnutls_init_session(struct tls_global *global, "'%s'", err); goto fail; } -#else /* LIBGNUTLS_VERSION_NUMBER >= 0x020200 */ - 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) - goto fail; -#endif /* LIBGNUTLS_VERSION_NUMBER < 0x020200 */ 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; @@ -309,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); @@ -344,10 +278,10 @@ void tls_connection_deinit(void *ssl_ctx, struct tls_connection *conn) 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); } @@ -405,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) { @@ -511,73 +347,142 @@ 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( conn->xcred, GNUTLS_VERIFY_ALLOW_SIGN_RSA_MD5); } -#if LIBGNUTLS_VERSION_NUMBER >= 0x020800 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); } -#endif /* LIBGNUTLS_VERSION_NUMBER >= 0x020800 */ } 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; } @@ -586,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); @@ -596,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) { @@ -604,8 +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 */ } +#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, @@ -643,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; @@ -666,29 +643,27 @@ int tls_global_set_params(void *tls_ctx, GNUTLS_VERIFY_ALLOW_SIGN_RSA_MD5); } -#if LIBGNUTLS_VERSION_NUMBER >= 0x020800 if (params->flags & TLS_CONN_DISABLE_TIME_CHECKS) { gnutls_certificate_set_verify_flags( global->xcred, GNUTLS_VERIFY_DISABLE_TIME_CHECKS); } -#endif /* LIBGNUTLS_VERSION_NUMBER >= 0x020800 */ } if (params->client_cert && params->private_key) { /* 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; } @@ -697,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); @@ -707,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) { @@ -752,37 +725,23 @@ 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) { -#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)); - -#if LIBGNUTLS_VERSION_NUMBER < 0x020c00 -#ifdef GNUTLS_INTERNAL_STRUCTURE_HACK - sec = &conn->session->security_parameters; - keys->master_key = sec->master_secret; - keys->master_key_len = WPA_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 */ -#endif /* LIBGNUTLS_VERSION_NUMBER < 0x020c00 */ - -#if LIBGNUTLS_VERSION_NUMBER < 0x020c00 - keys->client_random_len = WPA_TLS_RANDOM_SIZE; - keys->server_random_len = WPA_TLS_RANDOM_SIZE; -#endif /* LIBGNUTLS_VERSION_NUMBER < 0x020c00 */ + 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 */ } @@ -790,86 +749,317 @@ int tls_connection_prf(void *tls_ctx, struct tls_connection *conn, const char *label, int server_random_first, u8 *out, size_t out_len) { -#if LIBGNUTLS_VERSION_NUMBER >= 0x010302 if (conn == NULL || conn->session == NULL) 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; + } + + wpa_printf(MSG_DEBUG, "GnuTSL: Verifying peer certificate"); + +#if GNUTLS_VERSION_NUMBER >= 0x030300 + { + gnutls_typed_vdata_st data[1]; + unsigned int elements = 0; - if (gnutls_certificate_verify_peers2(conn->session, &status) < 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)) { wpa_printf(MSG_INFO, "TLS: Peer certificate not trusted"); - *err = GNUTLS_A_INTERNAL_ERROR; 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 LIBGNUTLS_VERSION_NUMBER >= 0x020800 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; } -#endif /* LIBGNUTLS_VERSION_NUMBER >= 0x020800 */ - 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], @@ -877,8 +1067,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); @@ -891,26 +1081,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; } @@ -968,6 +1260,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 && @@ -978,10 +1272,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 " @@ -990,18 +1294,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"); + +#if GNUTLS_VERSION_NUMBER >= 0x03010a + { + char *desc; + + 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 */ - wpa_printf(MSG_DEBUG, "TLS: Handshake completed successfully"); conn->established = 1; if (conn->push_buf == NULL) { /* Need to return something to get final TLS ACK. */ @@ -1025,7 +1332,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; @@ -1190,3 +1496,10 @@ int tls_connection_set_session_ticket_cb(void *tls_ctx, { return -1; } + + +int tls_get_library_version(char *buf, size_t buf_len) +{ + return os_snprintf(buf, buf_len, "GnuTLS build=%s run=%s", + GNUTLS_VERSION, gnutls_check_version(NULL)); +} |