/* * 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 #include #include #include #include #include #include #include #include #include #include #include #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; }