hostap/src/crypto/tls_gnutls.c
Jouni Malinen 224104ddf6 TLS: Reject openssl_ciphers parameter in non-OpenSSL cases
This TLS configuration parameter is explicitly for OpenSSL. Instead of
ignoring it silently, reject any configuration trying to use it in
builds that use other options for TLS implementation.

Signed-off-by: Jouni Malinen <j@w1.fi>
2015-01-11 01:35:54 +02:00

1108 lines
27 KiB
C

/*
* SSL/TLS interface functions for GnuTLS
* Copyright (c) 2004-2011, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "includes.h"
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#ifdef PKCS12_FUNCS
#include <gnutls/pkcs12.h>
#endif /* PKCS12_FUNCS */
#include "common.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 {
/* Data for session resumption */
void *session_data;
size_t session_data_size;
int server;
int params_set;
gnutls_certificate_credentials_t xcred;
};
struct tls_connection {
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;
struct wpabuf *push_buf;
struct wpabuf *pull_buf;
const u8 *pull_buf_offset;
int params_set;
gnutls_certificate_credentials_t xcred;
};
static void tls_log_func(int level, const char *msg)
{
char *s, *pos;
if (level == 6 || level == 7) {
/* These levels seem to be mostly I/O debug and msg dumps */
return;
}
s = os_strdup(msg);
if (s == NULL)
return;
pos = s;
while (*pos != '\0') {
if (*pos == '\n') {
*pos = '\0';
break;
}
pos++;
}
wpa_printf(level > 3 ? MSG_MSGDUMP : MSG_DEBUG,
"gnutls<%d> %s", level, s);
os_free(s);
}
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 */
global = os_zalloc(sizeof(*global));
if (global == NULL)
return NULL;
if (tls_gnutls_ref_count == 0 && gnutls_global_init() < 0) {
os_free(global);
return NULL;
}
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);
return global;
}
void tls_deinit(void *ssl_ctx)
{
struct tls_global *global = ssl_ctx;
if (global) {
if (global->params_set)
gnutls_certificate_free_credentials(global->xcred);
os_free(global->session_data);
os_free(global);
}
tls_gnutls_ref_count--;
if (tls_gnutls_ref_count == 0)
gnutls_global_deinit();
}
int tls_get_errors(void *ssl_ctx)
{
return 0;
}
static ssize_t tls_pull_func(gnutls_transport_ptr_t ptr, void *buf,
size_t len)
{
struct tls_connection *conn = (struct tls_connection *) ptr;
const u8 *end;
if (conn->pull_buf == NULL) {
errno = EWOULDBLOCK;
return -1;
}
end = wpabuf_head_u8(conn->pull_buf) + wpabuf_len(conn->pull_buf);
if ((size_t) (end - conn->pull_buf_offset) < len)
len = end - conn->pull_buf_offset;
os_memcpy(buf, conn->pull_buf_offset, len);
conn->pull_buf_offset += len;
if (conn->pull_buf_offset == end) {
wpa_printf(MSG_DEBUG, "%s - pull_buf consumed", __func__);
wpabuf_free(conn->pull_buf);
conn->pull_buf = NULL;
conn->pull_buf_offset = NULL;
} else {
wpa_printf(MSG_DEBUG, "%s - %lu bytes remaining in pull_buf",
__func__,
(unsigned long) (end - conn->pull_buf_offset));
}
return len;
}
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;
if (wpabuf_resize(&conn->push_buf, len) < 0) {
errno = ENOMEM;
return -1;
}
wpabuf_put_data(conn->push_buf, buf, len);
return len;
}
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,
global->server ? GNUTLS_SERVER : GNUTLS_CLIENT);
if (ret < 0) {
wpa_printf(MSG_INFO, "TLS: Failed to initialize new TLS "
"connection: %s", gnutls_strerror(ret));
return -1;
}
ret = gnutls_set_default_priority(conn->session);
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) {
wpa_printf(MSG_ERROR, "GnuTLS: Priority string failure at "
"'%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_t) conn);
return 0;
fail:
wpa_printf(MSG_INFO, "TLS: Failed to setup new TLS connection: %s",
gnutls_strerror(ret));
gnutls_deinit(conn->session);
return -1;
}
struct tls_connection * tls_connection_init(void *ssl_ctx)
{
struct tls_global *global = ssl_ctx;
struct tls_connection *conn;
int ret;
conn = os_zalloc(sizeof(*conn));
if (conn == NULL)
return NULL;
if (tls_gnutls_init_session(global, conn)) {
os_free(conn);
return NULL;
}
if (global->params_set) {
ret = gnutls_credentials_set(conn->session,
GNUTLS_CRD_CERTIFICATE,
global->xcred);
if (ret < 0) {
wpa_printf(MSG_INFO, "Failed to configure "
"credentials: %s", gnutls_strerror(ret));
os_free(conn);
return NULL;
}
}
if (gnutls_certificate_allocate_credentials(&conn->xcred)) {
os_free(conn);
return NULL;
}
return conn;
}
void tls_connection_deinit(void *ssl_ctx, struct tls_connection *conn)
{
if (conn == NULL)
return;
gnutls_certificate_free_credentials(conn->xcred);
gnutls_deinit(conn->session);
os_free(conn->pre_shared_secret);
wpabuf_free(conn->push_buf);
wpabuf_free(conn->pull_buf);
os_free(conn);
}
int tls_connection_established(void *ssl_ctx, struct tls_connection *conn)
{
return conn ? conn->established : 0;
}
int tls_connection_shutdown(void *ssl_ctx, struct tls_connection *conn)
{
struct tls_global *global = ssl_ctx;
int ret;
if (conn == NULL)
return -1;
/* Shutdown previous TLS connection without notifying the peer
* because the connection was already terminated in practice
* and "close notify" shutdown alert would confuse AS. */
gnutls_bye(conn->session, GNUTLS_SHUT_RDWR);
wpabuf_free(conn->push_buf);
conn->push_buf = NULL;
conn->established = 0;
gnutls_deinit(conn->session);
if (tls_gnutls_init_session(global, conn)) {
wpa_printf(MSG_INFO, "GnuTLS: Failed to preparare new session "
"for session resumption use");
return -1;
}
ret = gnutls_credentials_set(conn->session, GNUTLS_CRD_CERTIFICATE,
conn->params_set ? conn->xcred :
global->xcred);
if (ret < 0) {
wpa_printf(MSG_INFO, "GnuTLS: Failed to configure credentials "
"for session resumption: %s", gnutls_strerror(ret));
return -1;
}
if (global->session_data) {
ret = gnutls_session_set_data(conn->session,
global->session_data,
global->session_data_size);
if (ret < 0) {
wpa_printf(MSG_INFO, "GnuTLS: Failed to set session "
"data: %s", gnutls_strerror(ret));
return -1;
}
}
return 0;
}
int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn,
const struct tls_connection_params *params)
{
int ret;
if (conn == NULL || params == NULL)
return -1;
if (params->subject_match) {
wpa_printf(MSG_INFO, "GnuTLS: subject_match not supported");
return -1;
}
if (params->altsubject_match) {
wpa_printf(MSG_INFO, "GnuTLS: altsubject_match not supported");
return -1;
}
if (params->suffix_match) {
wpa_printf(MSG_INFO, "GnuTLS: suffix_match not supported");
return -1;
}
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;
ret = gnutls_certificate_set_x509_trust_file(
conn->xcred, params->ca_cert, GNUTLS_X509_FMT_PEM);
if (ret < 0) {
wpa_printf(MSG_DEBUG, "Failed to read CA cert '%s' "
"in PEM format: %s", params->ca_cert,
gnutls_strerror(ret));
ret = gnutls_certificate_set_x509_trust_file(
conn->xcred, params->ca_cert,
GNUTLS_X509_FMT_DER);
if (ret < 0) {
wpa_printf(MSG_DEBUG, "Failed to read CA cert "
"'%s' in DER format: %s",
params->ca_cert,
gnutls_strerror(ret));
return -1;
}
}
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) {
gnutls_certificate_set_verify_flags(
conn->xcred,
GNUTLS_VERIFY_DISABLE_TIME_CHECKS);
}
#endif /* LIBGNUTLS_VERSION_NUMBER >= 0x020800 */
}
if (params->client_cert && params->private_key) {
#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
/* 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);
#endif
if (ret < 0) {
wpa_printf(MSG_DEBUG, "Failed to read client cert/key "
"in PEM 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_DER,
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);
#endif
if (ret < 0) {
wpa_printf(MSG_DEBUG, "Failed to read client "
"cert/key in DER format: %s",
gnutls_strerror(ret));
return ret;
}
}
} else if (params->private_key) {
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);
if (ret != 0) {
wpa_printf(MSG_DEBUG, "Failed to load private_key in "
"PKCS#12 format: %s", gnutls_strerror(ret));
return -1;
} else
pkcs12_ok = 1;
#endif /* LIBGNUTLS_VERSION_NUMBER >= 0x010302 */
#endif /* PKCS12_FUNCS */
if (!pkcs12_ok) {
wpa_printf(MSG_DEBUG, "GnuTLS: PKCS#12 support not "
"included");
return -1;
}
}
conn->params_set = 1;
ret = gnutls_credentials_set(conn->session, GNUTLS_CRD_CERTIFICATE,
conn->xcred);
if (ret < 0) {
wpa_printf(MSG_INFO, "Failed to configure credentials: %s",
gnutls_strerror(ret));
}
return ret;
}
int tls_global_set_params(void *tls_ctx,
const struct tls_connection_params *params)
{
struct tls_global *global = tls_ctx;
int ret;
/* Currently, global parameters are only set when running in server
* mode. */
global->server = 1;
if (global->params_set) {
gnutls_certificate_free_credentials(global->xcred);
global->params_set = 0;
}
ret = gnutls_certificate_allocate_credentials(&global->xcred);
if (ret) {
wpa_printf(MSG_DEBUG, "Failed to allocate global credentials "
"%s", gnutls_strerror(ret));
return -1;
}
if (params->ca_cert) {
ret = gnutls_certificate_set_x509_trust_file(
global->xcred, params->ca_cert, GNUTLS_X509_FMT_PEM);
if (ret < 0) {
wpa_printf(MSG_DEBUG, "Failed to read CA cert '%s' "
"in PEM format: %s", params->ca_cert,
gnutls_strerror(ret));
ret = gnutls_certificate_set_x509_trust_file(
global->xcred, params->ca_cert,
GNUTLS_X509_FMT_DER);
if (ret < 0) {
wpa_printf(MSG_DEBUG, "Failed to read CA cert "
"'%s' in DER format: %s",
params->ca_cert,
gnutls_strerror(ret));
goto fail;
}
}
if (params->flags & TLS_CONN_ALLOW_SIGN_RSA_MD5) {
gnutls_certificate_set_verify_flags(
global->xcred,
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);
if (ret < 0) {
wpa_printf(MSG_DEBUG, "Failed to read client cert/key "
"in PEM format: %s", gnutls_strerror(ret));
ret = gnutls_certificate_set_x509_key_file(
global->xcred, params->client_cert,
params->private_key, GNUTLS_X509_FMT_DER);
if (ret < 0) {
wpa_printf(MSG_DEBUG, "Failed to read client "
"cert/key in DER format: %s",
gnutls_strerror(ret));
goto fail;
}
}
} else if (params->private_key) {
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);
if (ret != 0) {
wpa_printf(MSG_DEBUG, "Failed to load private_key in "
"PKCS#12 format: %s", gnutls_strerror(ret));
goto fail;
} else
pkcs12_ok = 1;
#endif /* LIBGNUTLS_VERSION_NUMBER >= 0x010302 */
#endif /* PKCS12_FUNCS */
if (!pkcs12_ok) {
wpa_printf(MSG_DEBUG, "GnuTLS: PKCS#12 support not "
"included");
goto fail;
}
}
global->params_set = 1;
return 0;
fail:
gnutls_certificate_free_credentials(global->xcred);
return -1;
}
int tls_global_set_verify(void *ssl_ctx, int check_crl)
{
/* TODO */
return 0;
}
int tls_connection_set_verify(void *ssl_ctx, struct tls_connection *conn,
int verify_peer)
{
if (conn == NULL || conn->session == NULL)
return -1;
conn->verify_peer = verify_peer;
gnutls_certificate_server_set_request(conn->session,
verify_peer ? GNUTLS_CERT_REQUIRE
: GNUTLS_CERT_REQUEST);
return 0;
}
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 (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 */
return 0;
}
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 */
return -1;
#endif /* LIBGNUTLS_VERSION_NUMBER >= 0x010302 */
}
static int tls_connection_verify_peer(struct tls_connection *conn,
gnutls_alert_description_t *err)
{
unsigned int status, num_certs, i;
struct os_time now;
const gnutls_datum_t *certs;
gnutls_x509_crt_t cert;
if (gnutls_certificate_verify_peers2(conn->session, &status) < 0) {
wpa_printf(MSG_INFO, "TLS: Failed to verify peer "
"certificate chain");
*err = GNUTLS_A_INTERNAL_ERROR;
return -1;
}
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;
}
#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;
}
if (status & GNUTLS_CERT_EXPIRED) {
wpa_printf(MSG_INFO, "TLS: Certificate expired");
*err = GNUTLS_A_CERTIFICATE_EXPIRED;
}
#endif /* LIBGNUTLS_VERSION_NUMBER >= 0x020800 */
return -1;
}
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;
}
if (status & GNUTLS_CERT_REVOKED) {
wpa_printf(MSG_INFO, "TLS: Peer certificate has been revoked");
*err = GNUTLS_A_CERTIFICATE_REVOKED;
return -1;
}
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;
}
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;
}
if (gnutls_x509_crt_import(cert, &certs[i],
GNUTLS_X509_FMT_DER) < 0) {
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;
}
gnutls_x509_crt_get_dn(cert, NULL, &len);
len++;
buf = os_malloc(len + 1);
if (buf) {
buf[0] = buf[len] = '\0';
gnutls_x509_crt_get_dn(cert, buf, &len);
}
wpa_printf(MSG_DEBUG, "TLS: Peer cert chain %d/%d: %s",
i + 1, num_certs, buf);
if (i == 0) {
/* TODO: validate altsubject_match and suffix_match.
* For now, any such configuration is rejected in
* tls_connection_set_params() */
}
os_free(buf);
if (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_x509_crt_deinit(cert);
*err = GNUTLS_A_CERTIFICATE_EXPIRED;
return -1;
}
gnutls_x509_crt_deinit(cert);
}
return 0;
}
static struct wpabuf * gnutls_get_appl_data(struct tls_connection *conn)
{
int res;
struct wpabuf *ad;
wpa_printf(MSG_DEBUG, "GnuTLS: Check for possible Application Data");
ad = wpabuf_alloc((wpabuf_len(conn->pull_buf) + 500) * 3);
if (ad == NULL)
return NULL;
res = gnutls_record_recv(conn->session, wpabuf_mhead(ad),
wpabuf_size(ad));
wpa_printf(MSG_DEBUG, "GnuTLS: gnutls_record_recv: %d", res);
if (res < 0) {
wpa_printf(MSG_DEBUG, "%s - gnutls_record_recv failed: %d "
"(%s)", __func__, (int) res,
gnutls_strerror(res));
wpabuf_free(ad);
return NULL;
}
wpabuf_put(ad, res);
wpa_printf(MSG_DEBUG, "GnuTLS: Received %d bytes of Application Data",
res);
return ad;
}
struct wpabuf * tls_connection_handshake(void *tls_ctx,
struct tls_connection *conn,
const struct wpabuf *in_data,
struct wpabuf **appl_data)
{
struct tls_global *global = tls_ctx;
struct wpabuf *out_data;
int ret;
if (appl_data)
*appl_data = NULL;
if (in_data && wpabuf_len(in_data) > 0) {
if (conn->pull_buf) {
wpa_printf(MSG_DEBUG, "%s - %lu bytes remaining in "
"pull_buf", __func__,
(unsigned long) wpabuf_len(conn->pull_buf));
wpabuf_free(conn->pull_buf);
}
conn->pull_buf = wpabuf_dup(in_data);
if (conn->pull_buf == NULL)
return NULL;
conn->pull_buf_offset = wpabuf_head(conn->pull_buf);
}
ret = gnutls_handshake(conn->session);
if (ret < 0) {
switch (ret) {
case GNUTLS_E_AGAIN:
if (global->server && conn->established &&
conn->push_buf == NULL) {
/* Need to return something to trigger
* completion of EAP-TLS. */
conn->push_buf = wpabuf_alloc(0);
}
break;
case GNUTLS_E_FATAL_ALERT_RECEIVED:
wpa_printf(MSG_DEBUG, "%s - received fatal '%s' alert",
__func__, gnutls_alert_get_name(
gnutls_alert_get(conn->session)));
conn->read_alerts++;
/* continue */
default:
wpa_printf(MSG_DEBUG, "%s - gnutls_handshake failed "
"-> %s", __func__, gnutls_strerror(ret));
conn->failed++;
}
} 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");
conn->established = 1;
if (conn->push_buf == NULL) {
/* Need to return something to get final TLS ACK. */
conn->push_buf = wpabuf_alloc(0);
}
gnutls_session_get_data(conn->session, NULL, &size);
if (global->session_data == NULL ||
global->session_data_size < size) {
os_free(global->session_data);
global->session_data = os_malloc(size);
}
if (global->session_data) {
global->session_data_size = size;
gnutls_session_get_data(conn->session,
global->session_data,
&global->session_data_size);
}
if (conn->pull_buf && appl_data)
*appl_data = gnutls_get_appl_data(conn);
}
out:
out_data = conn->push_buf;
conn->push_buf = NULL;
return out_data;
}
struct wpabuf * tls_connection_server_handshake(void *tls_ctx,
struct tls_connection *conn,
const struct wpabuf *in_data,
struct wpabuf **appl_data)
{
return tls_connection_handshake(tls_ctx, conn, in_data, appl_data);
}
struct wpabuf * tls_connection_encrypt(void *tls_ctx,
struct tls_connection *conn,
const struct wpabuf *in_data)
{
ssize_t res;
struct wpabuf *buf;
res = gnutls_record_send(conn->session, wpabuf_head(in_data),
wpabuf_len(in_data));
if (res < 0) {
wpa_printf(MSG_INFO, "%s: Encryption failed: %s",
__func__, gnutls_strerror(res));
return NULL;
}
buf = conn->push_buf;
conn->push_buf = NULL;
return buf;
}
struct wpabuf * tls_connection_decrypt(void *tls_ctx,
struct tls_connection *conn,
const struct wpabuf *in_data)
{
ssize_t res;
struct wpabuf *out;
if (conn->pull_buf) {
wpa_printf(MSG_DEBUG, "%s - %lu bytes remaining in "
"pull_buf", __func__,
(unsigned long) wpabuf_len(conn->pull_buf));
wpabuf_free(conn->pull_buf);
}
conn->pull_buf = wpabuf_dup(in_data);
if (conn->pull_buf == NULL)
return NULL;
conn->pull_buf_offset = wpabuf_head(conn->pull_buf);
/*
* Even though we try to disable TLS compression, it is possible that
* this cannot be done with all TLS libraries. Add extra buffer space
* to handle the possibility of the decrypted data being longer than
* input data.
*/
out = wpabuf_alloc((wpabuf_len(in_data) + 500) * 3);
if (out == NULL)
return NULL;
res = gnutls_record_recv(conn->session, wpabuf_mhead(out),
wpabuf_size(out));
if (res < 0) {
wpa_printf(MSG_DEBUG, "%s - gnutls_record_recv failed: %d "
"(%s)", __func__, (int) res, gnutls_strerror(res));
wpabuf_free(out);
return NULL;
}
wpabuf_put(out, res);
return out;
}
int tls_connection_resumed(void *ssl_ctx, struct tls_connection *conn)
{
if (conn == NULL)
return 0;
return gnutls_session_is_resumed(conn->session);
}
int tls_connection_set_cipher_list(void *tls_ctx, struct tls_connection *conn,
u8 *ciphers)
{
/* TODO */
return -1;
}
int tls_get_cipher(void *ssl_ctx, struct tls_connection *conn,
char *buf, size_t buflen)
{
/* TODO */
buf[0] = '\0';
return 0;
}
int tls_connection_enable_workaround(void *ssl_ctx,
struct tls_connection *conn)
{
gnutls_record_disable_padding(conn->session);
return 0;
}
int tls_connection_client_hello_ext(void *ssl_ctx, struct tls_connection *conn,
int ext_type, const u8 *data,
size_t data_len)
{
/* TODO */
return -1;
}
int tls_connection_get_failed(void *ssl_ctx, struct tls_connection *conn)
{
if (conn == NULL)
return -1;
return conn->failed;
}
int tls_connection_get_read_alerts(void *ssl_ctx, struct tls_connection *conn)
{
if (conn == NULL)
return -1;
return conn->read_alerts;
}
int tls_connection_get_write_alerts(void *ssl_ctx, struct tls_connection *conn)
{
if (conn == NULL)
return -1;
return conn->write_alerts;
}
int tls_connection_get_keyblock_size(void *tls_ctx,
struct tls_connection *conn)
{
/* TODO */
return -1;
}
unsigned int tls_capabilities(void *tls_ctx)
{
return 0;
}
int tls_connection_set_session_ticket_cb(void *tls_ctx,
struct tls_connection *conn,
tls_session_ticket_cb cb, void *ctx)
{
return -1;
}