]> git.ozlabs.org Git - ppp.git/blobdiff - pppd/eap-tls.c
Add support for EAP-TLS (including experimental TLS v1.3 support).
[ppp.git] / pppd / eap-tls.c
diff --git a/pppd/eap-tls.c b/pppd/eap-tls.c
new file mode 100644 (file)
index 0000000..3d8fdc7
--- /dev/null
@@ -0,0 +1,1427 @@
+/* * 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;
+        UI_METHOD* transfer_pin = NULL;
+
+        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);
+        }
+        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();
+        }
+
+        if (transfer_pin) UI_destroy_method(transfer_pin);
+    }
+    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: allocation error\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);
+        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;
+}
+