blob: 5740f308a89ae6c2e645222474a2ba6497cde9e7 [file] [log] [blame]
/* * eap-tls.c - EAP-TLS implementation for PPP
*
* Copyright (c) Beniamino Galvani 2005 All rights reserved.
* Jan Just Keijser 2006-2019 All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name(s) of the authors of this software must not be used to
* endorse or promote products derived from this software without
* prior written permission.
*
* THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <openssl/conf.h>
#include <openssl/engine.h>
#include <openssl/hmac.h>
#include <openssl/err.h>
#include <openssl/ui.h>
#include <openssl/x509v3.h>
#include "pppd.h"
#include "eap.h"
#include "eap-tls.h"
#include "fsm.h"
#include "lcp.h"
#include "pathnames.h"
typedef struct pw_cb_data
{
const void *password;
const char *prompt_info;
} PW_CB_DATA;
/* The openssl configuration file and engines can be loaded only once */
static CONF *ssl_config = NULL;
static ENGINE *cert_engine = NULL;
static ENGINE *pkey_engine = NULL;
/* TLSv1.3 do we have a session ticket ? */
static int have_session_ticket = 0;
int ssl_verify_callback(int, X509_STORE_CTX *);
void ssl_msg_callback(int write_p, int version, int ct, const void *buf,
size_t len, SSL * ssl, void *arg);
int ssl_new_session_cb(SSL *s, SSL_SESSION *sess);
X509 *get_X509_from_file(char *filename);
int ssl_cmp_certs(char *filename, X509 * a);
#ifdef MPPE
#define EAPTLS_MPPE_KEY_LEN 32
/*
* OpenSSL 1.1+ introduced a generic TLS_method()
* For older releases we substitute the appropriate method
*/
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#define TLS_method SSLv23_method
#define SSL3_RT_HEADER 0x100
#ifndef SSL_CTX_set_max_proto_version
/** Mimics SSL_CTX_set_max_proto_version for OpenSSL < 1.1 */
static inline int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, long tls_ver_max)
{
long sslopt = 0;
if (tls_ver_max < TLS1_VERSION)
{
sslopt |= SSL_OP_NO_TLSv1;
}
#ifdef SSL_OP_NO_TLSv1_1
if (tls_ver_max < TLS1_1_VERSION)
{
sslopt |= SSL_OP_NO_TLSv1_1;
}
#endif
#ifdef SSL_OP_NO_TLSv1_2
if (tls_ver_max < TLS1_2_VERSION)
{
sslopt |= SSL_OP_NO_TLSv1_2;
}
#endif
SSL_CTX_set_options(ctx, sslopt);
return 1;
}
#endif /* SSL_CTX_set_max_proto_version */
#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
/*
* Generate keys according to RFC 2716 and add to reply
*/
void eaptls_gen_mppe_keys(struct eaptls_session *ets, int client)
{
unsigned char out[4*EAPTLS_MPPE_KEY_LEN];
const char *prf_label;
size_t prf_size;
unsigned char eap_tls13_context[] = { EAPT_TLS };
unsigned char *context = NULL;
size_t context_len = 0;
unsigned char *p;
dbglog("EAP-TLS generating MPPE keys");
if (ets->tls_v13)
{
prf_label = "EXPORTER_EAP_TLS_Key_Material";
context = eap_tls13_context;
context_len = 1;
}
else
{
prf_label = "client EAP encryption";
}
dbglog("EAP-TLS PRF label = %s", prf_label);
prf_size = strlen(prf_label);
if (SSL_export_keying_material(ets->ssl, out, sizeof(out), prf_label, prf_size,
context, context_len, 0) != 1)
{
warn( "EAP-TLS: Failed generating keying material" );
return;
}
/*
* We now have the master send and receive keys.
* From these, generate the session send and receive keys.
* (see RFC3079 / draft-ietf-pppext-mppe-keys-03.txt for details)
*/
if (client)
{
p = out;
BCOPY( p, mppe_send_key, sizeof(mppe_send_key) );
p += EAPTLS_MPPE_KEY_LEN;
BCOPY( p, mppe_recv_key, sizeof(mppe_recv_key) );
}
else
{
p = out;
BCOPY( p, mppe_recv_key, sizeof(mppe_recv_key) );
p += EAPTLS_MPPE_KEY_LEN;
BCOPY( p, mppe_send_key, sizeof(mppe_send_key) );
}
mppe_keys_set = 1;
}
#endif /* MPPE */
void log_ssl_errors( void )
{
unsigned long ssl_err = ERR_get_error();
if (ssl_err != 0)
dbglog("EAP-TLS SSL error stack:");
while (ssl_err != 0) {
dbglog( ERR_error_string( ssl_err, NULL ) );
ssl_err = ERR_get_error();
}
}
int password_callback (char *buf, int size, int rwflag, void *u)
{
if (buf)
{
strlcpy (buf, passwd, size);
return strlen (buf);
}
return 0;
}
CONF *eaptls_ssl_load_config( void )
{
CONF *config;
int ret_code;
long error_line = 33;
config = NCONF_new( NULL );
dbglog( "Loading OpenSSL config file" );
ret_code = NCONF_load( config, _PATH_OPENSSLCONFFILE, &error_line );
if (ret_code == 0)
{
warn( "EAP-TLS: Error in OpenSSL config file %s at line %d", _PATH_OPENSSLCONFFILE, error_line );
NCONF_free( config );
config = NULL;
ERR_clear_error();
}
dbglog( "Loading OpenSSL built-ins" );
ENGINE_load_builtin_engines();
OPENSSL_load_builtin_modules();
dbglog( "Loading OpenSSL configured modules" );
if (CONF_modules_load( config, NULL, 0 ) <= 0 )
{
warn( "EAP-TLS: Error loading OpenSSL modules" );
log_ssl_errors();
config = NULL;
}
return config;
}
ENGINE *eaptls_ssl_load_engine( char *engine_name )
{
ENGINE *e = NULL;
dbglog( "Enabling OpenSSL auto engines" );
ENGINE_register_all_complete();
dbglog( "Loading OpenSSL '%s' engine support", engine_name );
e = ENGINE_by_id( engine_name );
if (!e)
{
dbglog( "EAP-TLS: Cannot load '%s' engine support, trying 'dynamic'", engine_name );
e = ENGINE_by_id( "dynamic" );
if (e)
{
if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", engine_name, 0)
|| !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0))
{
warn( "EAP-TLS: Error loading dynamic engine '%s'", engine_name );
log_ssl_errors();
ENGINE_free(e);
e = NULL;
}
}
else
{
warn( "EAP-TLS: Cannot load dynamic engine support" );
}
}
if (e)
{
dbglog( "Initialising engine" );
if(!ENGINE_set_default(e, ENGINE_METHOD_ALL))
{
warn( "EAP-TLS: Cannot use that engine" );
log_ssl_errors();
ENGINE_free(e);
e = NULL;
}
}
return e;
}
/*
* Initialize the SSL stacks and tests if certificates, key and crl
* for client or server use can be loaded.
*/
SSL_CTX *eaptls_init_ssl(int init_server, char *cacertfile, char *capath,
char *certfile, char *peer_certfile, char *privkeyfile)
{
char *cert_engine_name = NULL;
char *cert_identifier = NULL;
char *pkey_engine_name = NULL;
char *pkey_identifier = NULL;
SSL_CTX *ctx;
SSL *ssl;
X509_STORE *certstore;
X509_LOOKUP *lookup;
X509 *tmp;
int ret;
#if defined(TLS1_2_VERSION)
long tls_version = TLS1_2_VERSION;
#elif defined(TLS1_1_VERSION)
long tls_version = TLS1_1_VERSION;
#else
long tls_version = TLS1_VERSION;
#endif
/*
* Without these can't continue
*/
if (!(cacertfile[0] || capath[0]))
{
error("EAP-TLS: CA certificate file or path missing");
return NULL;
}
if (!certfile[0])
{
error("EAP-TLS: Certificate missing");
return NULL;
}
if (!privkeyfile[0])
{
error("EAP-TLS: Private key missing");
return NULL;
}
SSL_library_init();
SSL_load_error_strings();
/* load the openssl config file only once and load it before triggering
the loading of a global openssl config file via SSL_CTX_new()
*/
if (!ssl_config)
ssl_config = eaptls_ssl_load_config();
ctx = SSL_CTX_new(TLS_method());
if (!ctx) {
error("EAP-TLS: Cannot initialize SSL CTX context");
goto fail;
}
/* if the certificate filename is of the form engine:id. e.g.
pkcs11:12345
then we try to load and use this engine.
If the certificate filename starts with a / or . then we
ALWAYS assume it is a file and not an engine/pkcs11 identifier
*/
if ( index( certfile, '/' ) == NULL && index( certfile, '.') == NULL )
{
cert_identifier = index( certfile, ':' );
if (cert_identifier)
{
cert_engine_name = certfile;
*cert_identifier = '\0';
cert_identifier++;
dbglog( "Found certificate engine '%s'", cert_engine_name );
dbglog( "Found certificate identifier '%s'", cert_identifier );
}
}
/* if the privatekey filename is of the form engine:id. e.g.
pkcs11:12345
then we try to load and use this engine.
If the privatekey filename starts with a / or . then we
ALWAYS assume it is a file and not an engine/pkcs11 identifier
*/
if ( index( privkeyfile, '/' ) == NULL && index( privkeyfile, '.') == NULL )
{
pkey_identifier = index( privkeyfile, ':' );
if (pkey_identifier)
{
pkey_engine_name = privkeyfile;
*pkey_identifier = '\0';
pkey_identifier++;
dbglog( "Found privatekey engine '%s'", pkey_engine_name );
dbglog( "Found privatekey identifier '%s'", pkey_identifier );
}
}
if (cert_identifier && pkey_identifier)
{
if (strlen( cert_identifier ) == 0)
{
if (strlen( pkey_identifier ) == 0)
error( "EAP-TLS: both the certificate and privatekey identifiers are missing!" );
else
{
dbglog( "Substituting privatekey identifier for certificate identifier" );
cert_identifier = pkey_identifier;
}
}
else
{
if (strlen( pkey_identifier ) == 0)
{
dbglog( "Substituting certificate identifier for privatekey identifier" );
pkey_identifier = cert_identifier;
}
}
}
if (ssl_config && cert_engine_name)
cert_engine = eaptls_ssl_load_engine( cert_engine_name );
if (ssl_config && pkey_engine_name)
{
/* don't load the same engine twice */
if ( cert_engine && strcmp( cert_engine_name, pkey_engine_name) == 0 )
pkey_engine = cert_engine;
else
pkey_engine = eaptls_ssl_load_engine( pkey_engine_name );
}
SSL_CTX_set_default_passwd_cb (ctx, password_callback);
if (strlen(cacertfile) == 0) cacertfile = NULL;
if (strlen(capath) == 0) capath = NULL;
if (!SSL_CTX_load_verify_locations(ctx, cacertfile, capath))
{
error("EAP-TLS: Cannot load verify locations");
if (cacertfile) dbglog("CA certificate file = [%s]", cacertfile);
if (capath) dbglog("CA certificate path = [%s]", capath);
goto fail;
}
if (init_server)
SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(cacertfile));
if (cert_engine)
{
struct
{
const char *s_slot_cert_id;
X509 *cert;
} cert_info;
cert_info.s_slot_cert_id = cert_identifier;
cert_info.cert = NULL;
if (!ENGINE_ctrl_cmd( cert_engine, "LOAD_CERT_CTRL", 0, &cert_info, NULL, 0 ) )
{
error( "EAP-TLS: Error loading certificate with id '%s' from engine", cert_identifier );
goto fail;
}
if (cert_info.cert)
{
dbglog( "Got the certificate, adding it to SSL context" );
dbglog( "subject = %s", X509_NAME_oneline( X509_get_subject_name( cert_info.cert ), NULL, 0 ) );
if (SSL_CTX_use_certificate(ctx, cert_info.cert) <= 0)
{
error("EAP-TLS: Cannot use PKCS11 certificate %s", cert_identifier);
goto fail;
}
}
else
{
warn("EAP-TLS: Cannot load PKCS11 key %s", cert_identifier);
log_ssl_errors();
}
}
else
{
if (!SSL_CTX_use_certificate_chain_file(ctx, certfile))
{
error( "EAP-TLS: Cannot use public certificate %s", certfile );
goto fail;
}
}
/*
* Check the Before and After dates of the certificate
*/
ssl = SSL_new(ctx);
tmp = SSL_get_certificate(ssl);
ret = X509_cmp_time(X509_get_notBefore(tmp), NULL);
if (ret == 0)
{
warn( "EAP-TLS: Failed to read certificate notBefore field.");
}
if (ret > 0)
{
warn( "EAP-TLS: Your certificate is not yet valid!");
}
ret = X509_cmp_time(X509_get_notAfter(tmp), NULL);
if (ret == 0)
{
warn( "EAP-TLS: Failed to read certificate notAfter field.");
}
if (ret < 0)
{
warn( "EAP-TLS: Your certificate has expired!");
}
SSL_free(ssl);
if (pkey_engine)
{
EVP_PKEY *pkey = NULL;
PW_CB_DATA cb_data;
cb_data.password = passwd;
cb_data.prompt_info = pkey_identifier;
if (passwd[0] != 0)
{
UI_METHOD* transfer_pin = UI_create_method("transfer_pin");
int writer (UI *ui, UI_STRING *uis)
{
PW_CB_DATA* cb_data = (PW_CB_DATA*)UI_get0_user_data(ui);
UI_set_result(ui, uis, cb_data->password);
return 1;
};
int stub (UI* ui) {return 1;};
int stub_reader (UI *ui, UI_STRING *uis) {return 1;};
UI_method_set_writer(transfer_pin, writer);
UI_method_set_opener(transfer_pin, stub);
UI_method_set_closer(transfer_pin, stub);
UI_method_set_flusher(transfer_pin, stub);
UI_method_set_reader(transfer_pin, stub_reader);
dbglog( "Using our private key '%s' in engine", pkey_identifier );
pkey = ENGINE_load_private_key(pkey_engine, pkey_identifier, transfer_pin, &cb_data);
if (transfer_pin) UI_destroy_method(transfer_pin);
}
else {
dbglog( "Loading private key '%s' from engine", pkey_identifier );
pkey = ENGINE_load_private_key(pkey_engine, pkey_identifier, NULL, NULL);
}
if (pkey)
{
dbglog( "Got the private key, adding it to SSL context" );
if (SSL_CTX_use_PrivateKey(ctx, pkey) <= 0)
{
error("EAP-TLS: Cannot use PKCS11 key %s", pkey_identifier);
goto fail;
}
}
else
{
warn("EAP-TLS: Cannot load PKCS11 key %s", pkey_identifier);
log_ssl_errors();
}
}
else
{
if (!SSL_CTX_use_PrivateKey_file(ctx, privkeyfile, SSL_FILETYPE_PEM))
{
error("EAP-TLS: Cannot use private key %s", privkeyfile);
goto fail;
}
}
if (SSL_CTX_check_private_key(ctx) != 1) {
error("EAP-TLS: Private key %s fails security check", privkeyfile);
goto fail;
}
/* Explicitly set the NO_TICKETS flag to support Win7/Win8 clients */
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3
#ifdef SSL_OP_NO_TICKET
| SSL_OP_NO_TICKET
#endif
);
/* OpenSSL 1.1.1+ does not include RC4 ciphers by default.
* This causes totally obsolete WinXP clients to fail. If you really
* need ppp+EAP-TLS+openssl 1.1.1+WinXP then enable RC4 cipers and
* make sure that you use an OpenSSL that supports them
SSL_CTX_set_cipher_list(ctx, "RC4");
*/
/* Set up a SSL Session cache with a callback. This is needed for TLSv1.3+.
* During the initial handshake the server signals to the client early on
* that the handshake is finished, even before the client has sent its
* credentials to the server. The actual connection (and moment that the
* client sends its credentials) only starts after the arrival of the first
* session ticket. The 'ssl_new_session_cb' catches this ticket.
*/
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE);
SSL_CTX_sess_set_new_cb(ctx, ssl_new_session_cb);
/* As EAP-TLS+TLSv1.3 is highly experimental we offer the user a chance to override */
if (max_tls_version)
{
if (strncmp(max_tls_version, "1.0", 3) == 0)
tls_version = TLS1_VERSION;
else if (strncmp(max_tls_version, "1.1", 3) == 0)
tls_version = TLS1_1_VERSION;
else if (strncmp(max_tls_version, "1.2", 3) == 0)
#ifdef TLS1_2_VERSION
tls_version = TLS1_2_VERSION;
#else
{
warn("TLSv1.2 not available. Defaulting to TLSv1.1");
tls_version = TLS_1_1_VERSION;
}
#endif
else if (strncmp(max_tls_version, "1.3", 3) == 0)
#ifdef TLS1_3_VERSION
tls_version = TLS1_3_VERSION;
#else
warn("TLSv1.3 not available.");
#endif
}
dbglog("EAP-TLS: Setting max protocol version to 0x%X", tls_version);
SSL_CTX_set_max_proto_version(ctx, tls_version);
SSL_CTX_set_verify_depth(ctx, 5);
SSL_CTX_set_verify(ctx,
SSL_VERIFY_PEER |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
&ssl_verify_callback);
if (crl_dir) {
if (!(certstore = SSL_CTX_get_cert_store(ctx))) {
error("EAP-TLS: Failed to get certificate store");
goto fail;
}
if (!(lookup =
X509_STORE_add_lookup(certstore, X509_LOOKUP_hash_dir()))) {
error("EAP-TLS: Store lookup for CRL failed");
goto fail;
}
X509_LOOKUP_add_dir(lookup, crl_dir, X509_FILETYPE_PEM);
X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK);
}
if (crl_file) {
FILE *fp = NULL;
X509_CRL *crl = NULL;
fp = fopen(crl_file, "r");
if (!fp) {
error("EAP-TLS: Cannot open CRL file '%s'", crl_file);
goto fail;
}
crl = PEM_read_X509_CRL(fp, NULL, NULL, NULL);
if (!crl) {
error("EAP-TLS: Cannot read CRL file '%s'", crl_file);
goto fail;
}
if (!(certstore = SSL_CTX_get_cert_store(ctx))) {
error("EAP-TLS: Failed to get certificate store");
goto fail;
}
if (!X509_STORE_add_crl(certstore, crl)) {
error("EAP-TLS: Cannot add CRL to certificate store");
goto fail;
}
X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK);
}
/*
* If a peer certificate file was specified, it must be valid, else fail
*/
if (peer_certfile[0]) {
if (!(tmp = get_X509_from_file(peer_certfile))) {
error("EAP-TLS: Error loading client certificate from file %s",
peer_certfile);
goto fail;
}
X509_free(tmp);
}
return ctx;
fail:
log_ssl_errors();
SSL_CTX_free(ctx);
return NULL;
}
/*
* Determine the maximum packet size by looking at the LCP handshake
*/
int eaptls_get_mtu(int unit)
{
int mtu, mru;
lcp_options *wo = &lcp_wantoptions[unit];
lcp_options *go = &lcp_gotoptions[unit];
lcp_options *ho = &lcp_hisoptions[unit];
lcp_options *ao = &lcp_allowoptions[unit];
mtu = ho->neg_mru? ho->mru: PPP_MRU;
mru = go->neg_mru? MAX(wo->mru, go->mru): PPP_MRU;
mtu = MIN(MIN(mtu, mru), ao->mru)- PPP_HDRLEN - 10;
dbglog("MTU = %d", mtu);
return mtu;
}
/*
* Init the ssl handshake (server mode)
*/
int eaptls_init_ssl_server(eap_state * esp)
{
struct eaptls_session *ets;
char servcertfile[MAXWORDLEN];
char clicertfile[MAXWORDLEN];
char cacertfile[MAXWORDLEN];
char capath[MAXWORDLEN];
char pkfile[MAXWORDLEN];
/*
* Allocate new eaptls session
*/
esp->es_server.ea_session = malloc(sizeof(struct eaptls_session));
if (!esp->es_server.ea_session)
fatal("Allocation error");
ets = esp->es_server.ea_session;
if (!esp->es_server.ea_peer) {
error("EAP-TLS: Error: client name not set (BUG)");
return 0;
}
strlcpy(ets->peer, esp->es_server.ea_peer, MAXWORDLEN-1);
dbglog( "getting eaptls secret" );
if (!get_eaptls_secret(esp->es_unit, esp->es_server.ea_peer,
esp->es_server.ea_name, clicertfile,
servcertfile, cacertfile, capath, pkfile, 1)) {
error( "EAP-TLS: Cannot get secret/password for client \"%s\", server \"%s\"",
esp->es_server.ea_peer, esp->es_server.ea_name );
return 0;
}
ets->mtu = eaptls_get_mtu(esp->es_unit);
ets->ctx = eaptls_init_ssl(1, cacertfile, capath, servcertfile, clicertfile, pkfile);
if (!ets->ctx)
goto fail;
if (!(ets->ssl = SSL_new(ets->ctx)))
goto fail;
/*
* Set auto-retry to avoid timeouts on BIO_read
*/
SSL_set_mode(ets->ssl, SSL_MODE_AUTO_RETRY);
/*
* Initialize the BIOs we use to read/write to ssl engine
*/
ets->into_ssl = BIO_new(BIO_s_mem());
ets->from_ssl = BIO_new(BIO_s_mem());
SSL_set_bio(ets->ssl, ets->into_ssl, ets->from_ssl);
SSL_set_msg_callback(ets->ssl, ssl_msg_callback);
SSL_set_msg_callback_arg(ets->ssl, ets);
/*
* Attach the session struct to the connection, so we can later
* retrieve it when doing certificate verification
*/
SSL_set_ex_data(ets->ssl, 0, ets);
SSL_set_accept_state(ets->ssl);
ets->tls_v13 = 0;
ets->data = NULL;
ets->datalen = 0;
ets->alert_sent = 0;
ets->alert_recv = 0;
/*
* If we specified the client certificate file, store it in ets->peercertfile,
* so we can check it later in ssl_verify_callback()
*/
if (clicertfile[0])
strlcpy(&ets->peercertfile[0], clicertfile, MAXWORDLEN);
else
ets->peercertfile[0] = 0;
return 1;
fail:
SSL_CTX_free(ets->ctx);
return 0;
}
/*
* Init the ssl handshake (client mode)
*/
int eaptls_init_ssl_client(eap_state * esp)
{
struct eaptls_session *ets;
char servcertfile[MAXWORDLEN];
char clicertfile[MAXWORDLEN];
char cacertfile[MAXWORDLEN];
char capath[MAXWORDLEN];
char pkfile[MAXWORDLEN];
/*
* Allocate new eaptls session
*/
esp->es_client.ea_session = malloc(sizeof(struct eaptls_session));
if (!esp->es_client.ea_session)
fatal("Allocation error");
ets = esp->es_client.ea_session;
/*
* If available, copy server name in ets; it will be used in cert
* verify
*/
if (esp->es_client.ea_peer)
strlcpy(ets->peer, esp->es_client.ea_peer, MAXWORDLEN-1);
else
ets->peer[0] = 0;
ets->mtu = eaptls_get_mtu(esp->es_unit);
dbglog( "calling get_eaptls_secret" );
if (!get_eaptls_secret(esp->es_unit, esp->es_client.ea_name,
ets->peer, clicertfile,
servcertfile, cacertfile, capath, pkfile, 0)) {
error( "EAP-TLS: Cannot get secret/password for client \"%s\", server \"%s\"",
esp->es_client.ea_name, ets->peer );
return 0;
}
dbglog( "calling eaptls_init_ssl" );
ets->ctx = eaptls_init_ssl(0, cacertfile, capath, clicertfile, servcertfile, pkfile);
if (!ets->ctx)
goto fail;
ets->ssl = SSL_new(ets->ctx);
if (!ets->ssl)
goto fail;
/*
* Initialize the BIOs we use to read/write to ssl engine
*/
dbglog( "Initializing SSL BIOs" );
ets->into_ssl = BIO_new(BIO_s_mem());
ets->from_ssl = BIO_new(BIO_s_mem());
SSL_set_bio(ets->ssl, ets->into_ssl, ets->from_ssl);
SSL_set_msg_callback(ets->ssl, ssl_msg_callback);
SSL_set_msg_callback_arg(ets->ssl, ets);
/*
* Attach the session struct to the connection, so we can later
* retrieve it when doing certificate verification
*/
SSL_set_ex_data(ets->ssl, 0, ets);
SSL_set_connect_state(ets->ssl);
ets->tls_v13 = 0;
ets->data = NULL;
ets->datalen = 0;
ets->alert_sent = 0;
ets->alert_recv = 0;
/*
* If we specified the server certificate file, store it in
* ets->peercertfile, so we can check it later in
* ssl_verify_callback()
*/
if (servcertfile[0])
strlcpy(ets->peercertfile, servcertfile, MAXWORDLEN);
else
ets->peercertfile[0] = 0;
return 1;
fail:
dbglog( "eaptls_init_ssl_client: fail" );
SSL_CTX_free(ets->ctx);
return 0;
}
void eaptls_free_session(struct eaptls_session *ets)
{
if (ets->ssl)
SSL_free(ets->ssl);
if (ets->ctx)
SSL_CTX_free(ets->ctx);
free(ets);
}
int eaptls_is_init_finished(struct eaptls_session *ets)
{
if (ets->ssl && SSL_is_init_finished(ets->ssl))
{
if (ets->tls_v13)
return have_session_ticket;
else
return 1;
}
return 0;
}
/*
* Handle a received packet, reassembling fragmented messages and
* passing them to the ssl engine
*/
int eaptls_receive(struct eaptls_session *ets, u_char * inp, int len)
{
u_char flags;
u_int tlslen = 0;
u_char dummy[65536];
if (len < 1) {
warn("EAP-TLS: received no or invalid data");
return 1;
}
GETCHAR(flags, inp);
len--;
if (flags & EAP_TLS_FLAGS_LI && len > 4) {
/*
* LenghtIncluded flag set -> this is the first packet of a message
*/
/*
* the first 4 octets are the length of the EAP-TLS message
*/
GETLONG(tlslen, inp);
len -= 4;
if (!ets->data) {
if (tlslen > EAP_TLS_MAX_LEN) {
error("EAP-TLS: TLS message length > %d, truncated", EAP_TLS_MAX_LEN);
tlslen = EAP_TLS_MAX_LEN;
}
/*
* Allocate memory for the whole message
*/
ets->data = malloc(tlslen);
if (!ets->data)
fatal("EAP-TLS: allocation error\n");
ets->datalen = 0;
ets->tlslen = tlslen;
}
else
warn("EAP-TLS: non-first LI packet? that's odd...");
}
else if (!ets->data) {
/*
* A non fragmented message without LI flag
*/
ets->data = malloc(len);
if (!ets->data)
fatal("EAP-TLS: memory allocation error in eaptls_receive\n");
ets->datalen = 0;
ets->tlslen = len;
}
if (flags & EAP_TLS_FLAGS_MF)
ets->frag = 1;
else
ets->frag = 0;
if (len < 0) {
warn("EAP-TLS: received malformed data");
return 1;
}
if (len + ets->datalen > ets->tlslen) {
warn("EAP-TLS: received data > TLS message length");
return 1;
}
BCOPY(inp, ets->data + ets->datalen, len);
ets->datalen += len;
if (!ets->frag) {
/*
* If we have the whole message, pass it to ssl
*/
if (ets->datalen != ets->tlslen) {
warn("EAP-TLS: received data != TLS message length");
return 1;
}
if (BIO_write(ets->into_ssl, ets->data, ets->datalen) == -1)
log_ssl_errors();
SSL_read(ets->ssl, dummy, 65536);
free(ets->data);
ets->data = NULL;
ets->datalen = 0;
}
return 0;
}
/*
* Return an eap-tls packet in outp.
* A TLS message read from the ssl engine is buffered in ets->data.
* At each call we control if there is buffered data and send a
* packet of mtu bytes.
*/
int eaptls_send(struct eaptls_session *ets, u_char ** outp)
{
bool first = 0;
int size;
u_char fromtls[65536];
int res;
u_char *start;
start = *outp;
if (!ets->data)
{
if(!ets->alert_sent)
{
res = SSL_read(ets->ssl, fromtls, 65536);
}
/*
* Read from ssl
*/
if ((res = BIO_read(ets->from_ssl, fromtls, 65536)) == -1)
{
warn("EAP-TLS send: No data from BIO_read");
return 1;
}
ets->datalen = res;
ets->data = malloc(ets->datalen);
if (!ets->data)
fatal("EAP-TLS: memory allocation error in eaptls_send\n");
BCOPY(fromtls, ets->data, ets->datalen);
ets->offset = 0;
first = 1;
}
size = ets->datalen - ets->offset;
if (size > ets->mtu) {
size = ets->mtu;
ets->frag = 1;
} else
ets->frag = 0;
PUTCHAR(EAPT_TLS, *outp);
/*
* Set right flags and length if necessary
*/
if (ets->frag && first) {
PUTCHAR(EAP_TLS_FLAGS_LI | EAP_TLS_FLAGS_MF, *outp);
PUTLONG(ets->datalen, *outp);
} else if (ets->frag) {
PUTCHAR(EAP_TLS_FLAGS_MF, *outp);
} else
PUTCHAR(0, *outp);
/*
* Copy the data in outp
*/
BCOPY(ets->data + ets->offset, *outp, size);
INCPTR(size, *outp);
/*
* Copy the packet in retransmission buffer
*/
BCOPY(start, &ets->rtx[0], *outp - start);
ets->rtx_len = *outp - start;
ets->offset += size;
if (ets->offset >= ets->datalen) {
/*
* The whole message has been sent
*/
free(ets->data);
ets->data = NULL;
ets->datalen = 0;
ets->offset = 0;
}
return 0;
}
/*
* Get the sent packet from the retransmission buffer
*/
void eaptls_retransmit(struct eaptls_session *ets, u_char ** outp)
{
BCOPY(ets->rtx, *outp, ets->rtx_len);
INCPTR(ets->rtx_len, *outp);
}
/*
* Verify a certificate.
* Most of the work (signatures and issuer attributes checking)
* is done by ssl; we check the CN in the peer certificate
* against the peer name.
*/
int ssl_verify_callback(int ok, X509_STORE_CTX * ctx)
{
char subject[256];
char cn_str[256];
X509 *peer_cert;
int err, depth;
SSL *ssl;
struct eaptls_session *ets;
peer_cert = X509_STORE_CTX_get_current_cert(ctx);
err = X509_STORE_CTX_get_error(ctx);
depth = X509_STORE_CTX_get_error_depth(ctx);
dbglog("certificate verify depth: %d", depth);
if (auth_required && !ok) {
X509_NAME_oneline(X509_get_subject_name(peer_cert),
subject, 256);
X509_NAME_get_text_by_NID(X509_get_subject_name(peer_cert),
NID_commonName, cn_str, 256);
dbglog("Certificate verification error:\n depth: %d CN: %s"
"\n err: %d (%s)\n", depth, cn_str, err,
X509_verify_cert_error_string(err));
return 0;
}
ssl = X509_STORE_CTX_get_ex_data(ctx,
SSL_get_ex_data_X509_STORE_CTX_idx());
ets = (struct eaptls_session *)SSL_get_ex_data(ssl, 0);
if (ets == NULL) {
error("Error: SSL_get_ex_data returned NULL");
return 0;
}
log_ssl_errors();
if (!depth)
{
/* This is the peer certificate */
X509_NAME_oneline(X509_get_subject_name(peer_cert),
subject, 256);
X509_NAME_get_text_by_NID(X509_get_subject_name(peer_cert),
NID_commonName, cn_str, 256);
/*
* If acting as client and the name of the server wasn't specified
* explicitely, we can't verify the server authenticity
*/
if (!ets->peer[0]) {
warn("Peer name not specified: no check");
return ok;
}
/*
* Check the CN
*/
if (strcmp(cn_str, ets->peer)) {
error
("Certificate verification error: CN (%s) != peer_name (%s)",
cn_str, ets->peer);
return 0;
}
warn("Certificate CN: %s , peer name %s", cn_str, ets->peer);
/*
* If a peer certificate file was specified, here we check it
*/
if (ets->peercertfile[0]) {
if (ssl_cmp_certs(&ets->peercertfile[0], peer_cert)
!= 0) {
error
("Peer certificate doesn't match stored certificate");
return 0;
}
}
}
return ok;
}
/*
* Compare a certificate with the one stored in a file
*/
int ssl_cmp_certs(char *filename, X509 * a)
{
X509 *b;
int ret;
if (!(b = get_X509_from_file(filename)))
return 1;
ret = X509_cmp(a, b);
X509_free(b);
return ret;
}
X509 *get_X509_from_file(char *filename)
{
FILE *fp;
X509 *ret;
if (!(fp = fopen(filename, "r")))
return NULL;
ret = PEM_read_X509(fp, NULL, NULL, NULL);
fclose(fp);
return ret;
}
/*
* Every sent & received message this callback function is invoked,
* so we know when alert messages have arrived or are sent and
* we can print debug information about TLS handshake.
*/
void
ssl_msg_callback(int write_p, int version, int content_type,
const void *buf, size_t len, SSL * ssl, void *arg)
{
char string[256];
struct eaptls_session *ets = (struct eaptls_session *)arg;
unsigned char code;
const unsigned char*msg = buf;
int hvers = msg[1] << 8 | msg[2];
if(write_p)
strcpy(string, " -> ");
else
strcpy(string, " <- ");
switch(content_type) {
case SSL3_RT_HEADER:
strcat(string, "SSL/TLS Header: ");
switch(hvers) {
case SSL3_VERSION:
strcat(string, "SSL 3.0");
break;
case TLS1_VERSION:
strcat(string, "TLS 1.0");
break;
case TLS1_1_VERSION:
strcat(string, "TLS 1.1");
break;
case TLS1_2_VERSION:
strcat(string, "TLS 1.2");
break;
default:
sprintf(string, "SSL/TLS Header: Unknown version (%d)", hvers);
}
break;
case SSL3_RT_ALERT:
strcat(string, "Alert: ");
code = msg[1];
if (write_p) {
ets->alert_sent = 1;
ets->alert_sent_desc = code;
} else {
ets->alert_recv = 1;
ets->alert_recv_desc = code;
}
strcat(string, SSL_alert_desc_string_long(code));
break;
case SSL3_RT_CHANGE_CIPHER_SPEC:
strcat(string, "ChangeCipherSpec");
break;
#ifdef SSL3_RT_INNER_CONTENT_TYPE
case SSL3_RT_INNER_CONTENT_TYPE:
strcat(string, "InnerContentType (TLS1.3)");
break;
#endif
case SSL3_RT_HANDSHAKE:
strcat(string, "Handshake: ");
code = msg[0];
switch(code) {
case SSL3_MT_HELLO_REQUEST:
strcat(string,"Hello Request");
break;
case SSL3_MT_CLIENT_HELLO:
strcat(string,"Client Hello");
break;
case SSL3_MT_SERVER_HELLO:
strcat(string,"Server Hello");
break;
#ifdef SSL3_MT_NEWSESSION_TICKET
case SSL3_MT_NEWSESSION_TICKET:
strcat(string,"New Session Ticket");
break;
#endif
#ifdef SSL3_MT_END_OF_EARLY_DATA
case SSL3_MT_END_OF_EARLY_DATA:
strcat(string,"End of Early Data");
break;
#endif
#ifdef SSL3_MT_ENCRYPTED_EXTENSIONS
case SSL3_MT_ENCRYPTED_EXTENSIONS:
strcat(string,"Encryped Extensions");
break;
#endif
case SSL3_MT_CERTIFICATE:
strcat(string,"Certificate");
break;
case SSL3_MT_SERVER_KEY_EXCHANGE:
strcat(string,"Server Key Exchange");
break;
case SSL3_MT_CERTIFICATE_REQUEST:
strcat(string,"Certificate Request");
break;
case SSL3_MT_SERVER_DONE:
strcat(string,"Server Hello Done");
break;
case SSL3_MT_CERTIFICATE_VERIFY:
strcat(string,"Certificate Verify");
break;
case SSL3_MT_CLIENT_KEY_EXCHANGE:
strcat(string,"Client Key Exchange");
break;
case SSL3_MT_FINISHED:
strcat(string,"Finished: ");
hvers = SSL_version(ssl);
switch(hvers){
case SSL3_VERSION:
strcat(string, "SSL 3.0");
break;
case TLS1_VERSION:
strcat(string, "TLS 1.0");
break;
case TLS1_1_VERSION:
strcat(string, "TLS 1.1");
break;
case TLS1_2_VERSION:
strcat(string, "TLS 1.2");
break;
#ifdef TLS1_3_VERSION
case TLS1_3_VERSION:
strcat(string, "TLS 1.3 (experimental)");
ets->tls_v13 = 1;
break;
#endif
default:
strcat(string, "Unknown version");
}
break;
default:
sprintf( string, "Handshake: Unknown SSL3 code received: %d", code );
}
break;
default:
sprintf( string, "SSL message contains unknown content type: %d", content_type );
}
/* Alert messages must always be displayed */
if(content_type == SSL3_RT_ALERT)
error("%s", string);
else
dbglog("%s", string);
}
int
ssl_new_session_cb(SSL *s, SSL_SESSION *sess)
{
dbglog("EAP-TLS: Post-Handshake New Session Ticket arrived:");
have_session_ticket = 1;
/* always return success */
return 1;
}