2008-02-28 02:34:43 +01:00
|
|
|
/*
|
|
|
|
* WPA Supplicant / SSL/TLS interface functions for openssl
|
|
|
|
* Copyright (c) 2004-2007, 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "includes.h"
|
|
|
|
#include <gnutls/gnutls.h>
|
|
|
|
#include <gnutls/x509.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 */
|
|
|
|
|
|
|
|
#include "common.h"
|
|
|
|
#include "tls.h"
|
|
|
|
|
|
|
|
|
|
|
|
#define TLS_RANDOM_SIZE 32
|
|
|
|
#define 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[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 {
|
|
|
|
/* 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 session;
|
|
|
|
char *subject_match, *altsubject_match;
|
|
|
|
int read_alerts, write_alerts, failed;
|
|
|
|
|
|
|
|
u8 *pre_shared_secret;
|
|
|
|
size_t pre_shared_secret_len;
|
|
|
|
int established;
|
|
|
|
int verify_peer;
|
|
|
|
|
|
|
|
u8 *push_buf, *pull_buf, *pull_buf_offset;
|
|
|
|
size_t push_buf_len, pull_buf_len;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
/* 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 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 */
|
|
|
|
|
|
|
|
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 ptr, void *buf,
|
|
|
|
size_t len)
|
|
|
|
{
|
|
|
|
struct tls_connection *conn = (struct tls_connection *) ptr;
|
|
|
|
u8 *end;
|
|
|
|
if (conn->pull_buf == NULL) {
|
|
|
|
errno = EWOULDBLOCK;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
end = conn->pull_buf + conn->pull_buf_len;
|
|
|
|
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__);
|
|
|
|
os_free(conn->pull_buf);
|
|
|
|
conn->pull_buf = conn->pull_buf_offset = NULL;
|
|
|
|
conn->pull_buf_len = 0;
|
|
|
|
} else {
|
2009-02-05 17:18:31 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "%s - %lu bytes remaining in pull_buf",
|
|
|
|
__func__,
|
|
|
|
(unsigned long) (end - conn->pull_buf_offset));
|
2008-02-28 02:34:43 +01:00
|
|
|
}
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static ssize_t tls_push_func(gnutls_transport_ptr ptr, const void *buf,
|
|
|
|
size_t len)
|
|
|
|
{
|
|
|
|
struct tls_connection *conn = (struct tls_connection *) ptr;
|
|
|
|
u8 *nbuf;
|
|
|
|
|
|
|
|
nbuf = os_realloc(conn->push_buf, conn->push_buf_len + len);
|
|
|
|
if (nbuf == NULL) {
|
|
|
|
errno = ENOMEM;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
os_memcpy(nbuf + conn->push_buf_len, buf, len);
|
|
|
|
conn->push_buf = nbuf;
|
|
|
|
conn->push_buf_len += len;
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 };
|
|
|
|
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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
#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);
|
|
|
|
os_free(conn->push_buf);
|
|
|
|
os_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);
|
|
|
|
os_free(conn->push_buf);
|
|
|
|
conn->push_buf = NULL;
|
|
|
|
conn->push_buf_len = 0;
|
|
|
|
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)) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#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)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
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->client_cert && params->private_key) {
|
|
|
|
/* TODO: private_key_passwd? */
|
|
|
|
ret = gnutls_certificate_set_x509_key_file(
|
|
|
|
conn->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(
|
|
|
|
conn->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));
|
|
|
|
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->tls_ia = params->tls_ia;
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
|
|
|
#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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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->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));
|
|
|
|
|
|
|
|
#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;
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
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");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (conn->verify_peer && (status & GNUTLS_CERT_INVALID)) {
|
|
|
|
wpa_printf(MSG_INFO, "TLS: Peer certificate not trusted");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
|
|
|
|
wpa_printf(MSG_INFO, "TLS: Peer certificate does not have a "
|
|
|
|
"known issuer");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status & GNUTLS_CERT_REVOKED) {
|
|
|
|
wpa_printf(MSG_INFO, "TLS: Peer certificate has been 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");
|
|
|
|
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");
|
|
|
|
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);
|
|
|
|
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 subject_match and altsubject_match */
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
gnutls_x509_crt_deinit(cert);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
u8 * tls_connection_handshake(void *ssl_ctx, struct tls_connection *conn,
|
|
|
|
const u8 *in_data, size_t in_len,
|
|
|
|
size_t *out_len, u8 **appl_data,
|
|
|
|
size_t *appl_data_len)
|
|
|
|
{
|
|
|
|
struct tls_global *global = ssl_ctx;
|
|
|
|
u8 *out_data;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (appl_data)
|
|
|
|
*appl_data = NULL;
|
|
|
|
|
|
|
|
if (in_data && in_len) {
|
|
|
|
if (conn->pull_buf) {
|
2009-02-05 17:18:31 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "%s - %lu bytes remaining in "
|
|
|
|
"pull_buf", __func__,
|
|
|
|
(unsigned long) conn->pull_buf_len);
|
2008-02-28 02:34:43 +01:00
|
|
|
os_free(conn->pull_buf);
|
|
|
|
}
|
|
|
|
conn->pull_buf = os_malloc(in_len);
|
|
|
|
if (conn->pull_buf == NULL)
|
|
|
|
return NULL;
|
|
|
|
os_memcpy(conn->pull_buf, in_data, in_len);
|
|
|
|
conn->pull_buf_offset = conn->pull_buf;
|
|
|
|
conn->pull_buf_len = in_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 = os_malloc(1);
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
|
|
|
|
if (conn->verify_peer && tls_connection_verify_peer(conn)) {
|
|
|
|
wpa_printf(MSG_INFO, "TLS: Peer certificate chain "
|
|
|
|
"failed validation");
|
|
|
|
conn->failed++;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (conn->tls_ia && !gnutls_ia_handshake_p(conn->session)) {
|
|
|
|
wpa_printf(MSG_INFO, "TLS: No TLS/IA negotiation");
|
|
|
|
conn->failed++;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (conn->tls_ia)
|
|
|
|
wpa_printf(MSG_DEBUG, "TLS: Start TLS/IA handshake");
|
|
|
|
else {
|
|
|
|
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 = os_malloc(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out_data = conn->push_buf;
|
|
|
|
*out_len = conn->push_buf_len;
|
|
|
|
conn->push_buf = NULL;
|
|
|
|
conn->push_buf_len = 0;
|
|
|
|
return out_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
u8 * tls_connection_server_handshake(void *ssl_ctx,
|
|
|
|
struct tls_connection *conn,
|
|
|
|
const u8 *in_data, size_t in_len,
|
|
|
|
size_t *out_len)
|
|
|
|
{
|
|
|
|
return tls_connection_handshake(ssl_ctx, conn, in_data, in_len,
|
|
|
|
out_len, NULL, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int tls_connection_encrypt(void *ssl_ctx, struct tls_connection *conn,
|
|
|
|
const u8 *in_data, size_t in_len,
|
|
|
|
u8 *out_data, size_t out_len)
|
|
|
|
{
|
|
|
|
ssize_t res;
|
|
|
|
|
|
|
|
#ifdef GNUTLS_IA
|
|
|
|
if (conn->tls_ia)
|
|
|
|
res = gnutls_ia_send(conn->session, (char *) in_data, in_len);
|
|
|
|
else
|
|
|
|
#endif /* GNUTLS_IA */
|
|
|
|
res = gnutls_record_send(conn->session, in_data, in_len);
|
|
|
|
if (res < 0) {
|
|
|
|
wpa_printf(MSG_INFO, "%s: Encryption failed: %s",
|
|
|
|
__func__, gnutls_strerror(res));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (conn->push_buf == NULL)
|
|
|
|
return -1;
|
|
|
|
if (conn->push_buf_len < out_len)
|
|
|
|
out_len = conn->push_buf_len;
|
2009-02-09 21:37:55 +01:00
|
|
|
else if (conn->push_buf_len > out_len) {
|
|
|
|
wpa_printf(MSG_INFO, "GnuTLS: Not enough buffer space for "
|
|
|
|
"encrypted message (in_len=%lu push_buf_len=%lu "
|
|
|
|
"out_len=%lu",
|
|
|
|
(unsigned long) in_len,
|
|
|
|
(unsigned long) conn->push_buf_len,
|
|
|
|
(unsigned long) out_len);
|
|
|
|
}
|
2008-02-28 02:34:43 +01:00
|
|
|
os_memcpy(out_data, conn->push_buf, out_len);
|
|
|
|
os_free(conn->push_buf);
|
|
|
|
conn->push_buf = NULL;
|
|
|
|
conn->push_buf_len = 0;
|
|
|
|
return out_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int tls_connection_decrypt(void *ssl_ctx, struct tls_connection *conn,
|
|
|
|
const u8 *in_data, size_t in_len,
|
|
|
|
u8 *out_data, size_t out_len)
|
|
|
|
{
|
|
|
|
ssize_t res;
|
|
|
|
|
|
|
|
if (conn->pull_buf) {
|
2009-02-05 17:18:31 +01:00
|
|
|
wpa_printf(MSG_DEBUG, "%s - %lu bytes remaining in "
|
|
|
|
"pull_buf", __func__,
|
|
|
|
(unsigned long) conn->pull_buf_len);
|
2008-02-28 02:34:43 +01:00
|
|
|
os_free(conn->pull_buf);
|
|
|
|
}
|
|
|
|
conn->pull_buf = os_malloc(in_len);
|
|
|
|
if (conn->pull_buf == NULL)
|
|
|
|
return -1;
|
|
|
|
os_memcpy(conn->pull_buf, in_data, in_len);
|
|
|
|
conn->pull_buf_offset = conn->pull_buf;
|
|
|
|
conn->pull_buf_len = in_len;
|
|
|
|
|
|
|
|
#ifdef GNUTLS_IA
|
|
|
|
if (conn->tls_ia) {
|
|
|
|
res = gnutls_ia_recv(conn->session, (char *) out_data,
|
|
|
|
out_len);
|
|
|
|
if (out_len >= 12 &&
|
|
|
|
(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));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
res = gnutls_ia_verify_endphase(conn->session,
|
|
|
|
(char *) out_data);
|
|
|
|
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));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (final)
|
|
|
|
conn->final_phase_finished = 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (res < 0) {
|
|
|
|
wpa_printf(MSG_DEBUG, "%s - gnutls_ia_recv failed: %d "
|
2009-02-05 17:18:31 +01:00
|
|
|
"(%s)", __func__, (int) res,
|
2008-02-28 02:34:43 +01:00
|
|
|
gnutls_strerror(res));
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
#endif /* GNUTLS_IA */
|
|
|
|
|
|
|
|
res = gnutls_record_recv(conn->session, out_data, out_len);
|
|
|
|
if (res < 0) {
|
|
|
|
wpa_printf(MSG_DEBUG, "%s - gnutls_record_recv failed: %d "
|
2009-02-05 17:18:31 +01:00
|
|
|
"(%s)", __func__, (int) res, gnutls_strerror(res));
|
2008-02-28 02:34:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
/* TODO: set SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS */
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
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 */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int tls_connection_ia_send_phase_finished(void *tls_ctx,
|
|
|
|
struct tls_connection *conn,
|
|
|
|
int final,
|
|
|
|
u8 *out_data, size_t out_len)
|
|
|
|
{
|
|
|
|
#ifdef GNUTLS_IA
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (conn == NULL || conn->session == NULL || !conn->tls_ia)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
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 -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (conn->push_buf == NULL)
|
|
|
|
return -1;
|
|
|
|
if (conn->push_buf_len < out_len)
|
|
|
|
out_len = conn->push_buf_len;
|
|
|
|
os_memcpy(out_data, conn->push_buf, out_len);
|
|
|
|
os_free(conn->push_buf);
|
|
|
|
conn->push_buf = NULL;
|
|
|
|
conn->push_buf_len = 0;
|
|
|
|
return out_len;
|
|
|
|
#else /* GNUTLS_IA */
|
|
|
|
return -1;
|
|
|
|
#endif /* GNUTLS_IA */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int tls_connection_ia_final_phase_finished(void *tls_ctx,
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
#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 */
|
|
|
|
}
|