From 789e867f6e51b2d3e7f6ebe723f43764d5d8b595 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Eivind=20N=C3=A6ss?= Date: Thu, 24 Jun 2021 16:06:11 -0700 Subject: [PATCH] Improve the PEAP contribution by Rustam Kovhaev MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit These changes adds to his contribution by * Adding options to perform CA/CRL checking and certificate validation consistent with what is already been done for EAP-TLS * Certificate validation is now in line with what is already been done for EAP-TLS. Users can now set "remotename" and "tls-verify-method" to control these. * Validation of certificate purpose and extended key usage is controlled by the option "tls-verify-key-usage". * Fixing up MPPE key generation to use the new API for handling MPPE keys * Man page is updated where appropriate for the new options. * Added unit-tests for the PEAP code in case of crypto or parameters would change in the future. * Added the peap feature to configure scripts. Users can now control the feature by specifying --enable-peap/--disable-peap. To acheive feature parity with the EAP-TLS change, the EAP-TLS common code was refactored into tls.c/.h such that it could be re-used in both instances. Using PEAP/MSCHAPv2 is now supported in PPPD with this change. Signed-off-by: Eivind Næss --- configure.ac | 17 +- pppd/Makefile.am | 60 +++-- pppd/auth.c | 53 +++-- pppd/eap-tls.c | 446 ++++------------------------------- pppd/eap-tls.h | 7 +- pppd/eap.c | 28 ++- pppd/eap.h | 3 + pppd/mppe.c | 3 + pppd/peap.c | 587 +++++++++++++++++++++++++++++++++++------------ pppd/peap.h | 49 +++- pppd/pppd.8 | 33 ++- pppd/pppd.h | 14 +- pppd/tls.c | 447 ++++++++++++++++++++++++++++++++++++ pppd/tls.h | 88 +++++++ 14 files changed, 1206 insertions(+), 629 deletions(-) create mode 100644 pppd/tls.c create mode 100644 pppd/tls.h diff --git a/configure.ac b/configure.ac index d4db617..283b666 100644 --- a/configure.ac +++ b/configure.ac @@ -189,7 +189,15 @@ AC_ARG_ENABLE([eaptls], AS_HELP_STRING([--disable-eaptls], [Disable EAP-TLS authentication support])) AS_IF([test "x$enable_eaptls" != "xno"], AC_DEFINE([USE_EAPTLS], 1, ["Have EAP-TLS authentication support"])) -AM_CONDITIONAL(WITH_EAPTLS, test "${enable_eaptls}" != "no") +AM_CONDITIONAL(WITH_EAPTLS, test "x${enable_eaptls}" != "xno") + +# +# Disable PEAP support +AC_ARG_ENABLE([peap], + AS_HELP_STRING([--disable-peap], [Disable PEAP authentication support])) +AS_IF([test "x${enable_peap}" != "xno"], + AC_DEFINE([USE_PEAP], 1, ["Have PEAP authentication support"])) +AM_CONDITIONAL([WITH_PEAP], test "x${enable_peap}" != "xno") # # Disable OpenSSL engine support @@ -232,14 +240,14 @@ AM_CONDITIONAL(WITH_OPENSSL, test "${with_openssl}" != "no") # # Check if OpenSSL has compiled in support for various ciphers -AS_IF([test "${with_openssl}" != "no" ], [ +AS_IF([test "x${with_openssl}" != "xno" ], [ AX_CHECK_OPENSSL_DEFINE([OPENSSL_NO_MD4], [md4]) AX_CHECK_OPENSSL_DEFINE([OPENSSL_NO_MD5], [md5]) AX_CHECK_OPENSSL_DEFINE([OPENSSL_NO_DES], [des]) AX_CHECK_OPENSSL_DEFINE([OPENSSL_NO_SHA], [sha]) ], [ - AS_IF([test "x$enable_eaptls" != "xno"], - [AC_MSG_ERROR([OpenSSL not found, and if this is your intention then run configure --disable-eaptls])]) + AS_IF([test "x${enable_eaptls}" != "xno" || test "x${enable_peap}" != "xno"], + [AC_MSG_ERROR([OpenSSL not found, and if this is your intention then run configure --disable-eaptls and --disable-peap])]) ]) AM_CONDITIONAL([OPENSSL_HAVE_MD4], test "x${ac_cv_openssl_md4}" = "xyes") @@ -414,4 +422,5 @@ Features enabled CBCP.................: ${enable_cbcp:-no} IPXCP................: ${enable_ipxcp:-no} EAP-TLS..............: ${enable_eaptls:-yes} + PEAP.................: ${enable_peap:-yes} " diff --git a/pppd/Makefile.am b/pppd/Makefile.am index 2244507..1397088 100644 --- a/pppd/Makefile.am +++ b/pppd/Makefile.am @@ -1,13 +1,14 @@ sbin_PROGRAMS = pppd dist_man8_MANS = pppd.8 -check_PROGRAMS = \ - utest_chap +check_PROGRAMS = utest_chap_SOURCES = chap_ms.c pppcrypt.c utils.c utest_chap_CPPFLAGS = -DUNIT_TEST utest_chap_LDFLAGS = -TESTS = $(check_PROGRAMS) +utest_peap_SOURCES = peap.c utils.c mppe.c +utest_peap_CPPFLAGS = -DUNIT_TEST -I${top_srcdir}/include +utest_peap_LDFLAGS = if WITH_SRP sbin_PROGRAMS += srp-entry @@ -37,14 +38,16 @@ pppd_include_HEADERS = \ md4.h \ md5.h \ mppe.h \ - pppdconf.h \ patchlevel.h \ pathnames.h \ + peap.h \ pppcrypt.h \ pppd.h \ + pppdconf.h \ session.h \ sha1.h \ spinlock.h \ + tls.h \ tdb.h \ upap.h @@ -84,6 +87,7 @@ endif if WITH_CHAPMS pppd_SOURCES += chap_ms.c pppd_SOURCES += pppcrypt.c +check_PROGRAMS += utest_chap else if WITH_SRP pppd_SOURCES += pppcrypt.c @@ -133,35 +137,42 @@ pppd_LIBS += -lpam -ldl endif if WITH_EAPTLS -pppd_SOURCES += eap-tls.c +pppd_SOURCES += eap-tls.c tls.c +else +if WITH_PEAP +pppd_SOURCES += tls.c +endif endif -if !WITH_OPENSSL -pppd_SOURCES += md5.c md4.c sha1.c -utest_chap_SOURCES += md5.c md4.c sha1.c -else -pppd_CPPFLAGS += $(OPENSSL_INCLUDES) -pppd_LDFLAGS += $(OPENSSL_LDFLAGS) +if WITH_PEAP +pppd_SOURCES += peap.c +check_PROGRAMS += utest_peap +endif -utest_chap_CPPFLAGS += $(OPENSSL_INCLUDES) -utest_chap_LDFLAGS += $(OPENSSL_LDFLAGS) -utest_chap_LDADD = $(OPENSSL_LIBS) +noinst_LTLIBRARIES = libppp_crypt.la +libppp_crypt_la_SOURCES= -pppd_LIBS += $(OPENSSL_LIBS) +if !WITH_OPENSSL +libppp_crypt_la_SOURCES += md4.c md5.c sha1.c +else +libppp_crypt_la_CPPFLAGS=$(OPENSSL_INCLUDES) +libppp_crypt_la_LDFLAGS=$(OPENSSL_LDFLAGS) +libppp_crypt_la_LIBADD=$(OPENSSL_LIBS) if !OPENSSL_HAVE_SHA -pppd_SOURCES += sha1.c -utest_chap_SOURCES += sha1.c -endif -if !OPENSSL_HAVE_MD4 -pppd_SOURCES += md4.c -utest_chap_SOURCES += md4.c +libppp_crypt_la_SOURCES += sha1.c endif if !OPENSSL_HAVE_MD5 -pppd_SOURCES += md5.c -utest_chap_SOURCES += md5.c +libppp_crypt_la_SOURCES += md5.c +endif +if !OPENSSL_HAVE_MD4 +libppp_crypt_la_SOURCES += md4.c endif endif +utest_peap_LDADD = libppp_crypt.la +utest_chap_LDADD = libppp_crypt.la +pppd_LIBS += libppp_crypt.la + if WITH_SYSTEMD pppd_LIBS += -lsystemd endif @@ -181,3 +192,6 @@ pppd_LDADD = $(pppd_LIBS) EXTRA_DIST = \ ppp.pam + +TESTS = $(check_PROGRAMS) + diff --git a/pppd/auth.c b/pppd/auth.c index 7500352..01ece57 100644 --- a/pppd/auth.c +++ b/pppd/auth.c @@ -258,20 +258,23 @@ bool explicit_remote = 0; /* User specified explicit remote name */ bool explicit_user = 0; /* Set if "user" option supplied */ bool explicit_passwd = 0; /* Set if "password" option supplied */ char remote_name[MAXNAMELEN]; /* Peer's name for authentication */ -#ifdef USE_EAPTLS -char *cacert_file = NULL; /* CA certificate file (pem format) */ -char *ca_path = NULL; /* directory with CA certificates */ -char *cert_file = NULL; /* client certificate file (pem format) */ -char *privkey_file = NULL; /* client private key file (pem format) */ -char *pkcs12_file = NULL; /* client private key envelope file (pkcs12 format) */ -char *crl_dir = NULL; /* directory containing CRL files */ -char *crl_file = NULL; /* Certificate Revocation List (CRL) file (pem format) */ -char *max_tls_version = NULL; /* Maximum TLS protocol version (default=1.2) */ -char *tls_verify_method = NULL; -bool tls_verify_key_usage = 0; -bool need_peer_eap = 0; /* Require peer to authenticate us */ + +#if defined(USE_EAPTLS) || defined(USE_PEAP) +char *cacert_file = NULL; /* CA certificate file (pem format) */ +char *ca_path = NULL; /* Directory with CA certificates */ +char *crl_dir = NULL; /* Directory containing CRL files */ +char *crl_file = NULL; /* Certificate Revocation List (CRL) file (pem format) */ +char *max_tls_version = NULL; /* Maximum TLS protocol version (default=1.2) */ +char *tls_verify_method = NULL; /* Verify certificate method */ +bool tls_verify_key_usage = 0; /* Verify peer certificate key usage */ +#endif + +#if defined(USE_EAPTLS) +char *cert_file = NULL; /* Client certificate file (pem format) */ +char *privkey_file = NULL; /* Client private key file (pem format) */ +char *pkcs12_file = NULL; /* Client private key envelope file (pkcs12 format) */ +bool need_peer_eap = 0; /* Require peer to authenticate us */ #endif -bool tls_verify_cert = 0; /* Do not verify server's SSL certificate */ static char *uafname; /* name of most recent +ua file */ @@ -446,26 +449,26 @@ option_t auth_options[] = { "Set telephone number(s) which are allowed to connect", OPT_PRIV | OPT_A2LIST }, - { "tls-verify-certificate", o_bool, &tls_verify_cert, - "Enable server's SSL certificate validation", 1 }, - -#ifdef USE_EAPTLS - { "ca", o_string, &cacert_file, "EAP-TLS CA certificate in PEM format" }, - { "capath", o_string, &ca_path, "EAP-TLS CA certificate directory" }, - { "cert", o_string, &cert_file, "EAP-TLS client certificate in PEM format" }, - { "key", o_string, &privkey_file, "EAP-TLS client private key in PEM format" }, - { "crl-dir", o_string, &crl_dir, "Use CRLs in directory" }, - { "crl", o_string, &crl_file, "Use specific CRL file" }, - { "pkcs12", o_string, &pkcs12_file, "EAP-TLS client credentials in PKCS12 format" }, +#if defined(USE_EAPTLS) || defined(USE_PEAP) + { "ca", o_string, &cacert_file, "CA certificate in PEM format" }, + { "capath", o_string, &ca_path, "TLS CA certificate directory" }, + { "crl-dir", o_string, &crl_dir, "Use CRLs in directory" }, + { "crl", o_string, &crl_file, "Use specific CRL file" }, { "max-tls-version", o_string, &max_tls_version, "Maximum TLS version (1.0/1.1/1.2 (default)/1.3)" }, { "tls-verify-key-usage", o_bool, &tls_verify_key_usage, "Verify certificate type and extended key usage" }, { "tls-verify-method", o_string, &tls_verify_method, "Verify peer by method (none|subject|name|suffix)" }, +#endif + +#if defined(USE_EAPTLS) + { "cert", o_string, &cert_file, "client certificate in PEM format" }, + { "key", o_string, &privkey_file, "client private key in PEM format" }, + { "pkcs12", o_string, &pkcs12_file, "EAP-TLS client credentials in PKCS12 format" }, { "need-peer-eap", o_bool, &need_peer_eap, "Require the peer to authenticate us", 1 }, -#endif /* USE_EAPTLS */ +#endif { NULL } }; diff --git a/pppd/eap-tls.c b/pppd/eap-tls.c index b15d801..b9bab84 100644 --- a/pppd/eap-tls.c +++ b/pppd/eap-tls.c @@ -44,6 +44,7 @@ #ifndef OPENSSL_NO_ENGINE #include #endif +#include #include #include #include @@ -51,6 +52,7 @@ #include #include "pppd.h" +#include "tls.h" #include "eap.h" #include "eap-tls.h" #include "fsm.h" @@ -59,6 +61,10 @@ #include "mppe.h" #include "pathnames.h" +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#define SSL3_RT_HEADER 0x100 +#endif + typedef struct pw_cb_data { const void *password; @@ -75,55 +81,10 @@ 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); - -/* - * 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 */ - #ifdef MPPE #define EAPTLS_MPPE_KEY_LEN 32 @@ -178,20 +139,6 @@ void eaptls_gen_mppe_keys(struct eaptls_session *ets, int client) #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) @@ -230,7 +177,7 @@ CONF *eaptls_ssl_load_config( void ) if (CONF_modules_load( config, NULL, 0 ) <= 0 ) { warn( "EAP-TLS: Error loading OpenSSL modules" ); - log_ssl_errors(); + tls_log_sslerr(); config = NULL; } @@ -257,7 +204,7 @@ ENGINE *eaptls_ssl_load_engine( char *engine_name ) || !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0)) { warn( "EAP-TLS: Error loading dynamic engine '%s'", engine_name ); - log_ssl_errors(); + tls_log_sslerr(); ENGINE_free(e); e = NULL; } @@ -274,7 +221,7 @@ ENGINE *eaptls_ssl_load_engine( char *engine_name ) if(!ENGINE_set_default(e, ENGINE_METHOD_ALL)) { warn( "EAP-TLS: Cannot use that engine" ); - log_ssl_errors(); + tls_log_sslerr(); ENGINE_free(e); e = NULL; } @@ -307,7 +254,7 @@ static int eaptls_UI_reader(UI *ui, UI_STRING *uis) { * 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 *pkcs12) + char *certfile, char *privkeyfile, char *pkcs12) { #ifndef OPENSSL_NO_ENGINE char *cert_engine_name = NULL; @@ -316,8 +263,6 @@ SSL_CTX *eaptls_init_ssl(int init_server, char *cacertfile, char *capath, #endif SSL_CTX *ctx; SSL *ssl; - X509_STORE *certstore; - X509_LOOKUP *lookup; X509 *tmp; X509 *cert = NULL; PKCS12 *p12 = NULL; @@ -326,13 +271,6 @@ SSL_CTX *eaptls_init_ssl(int init_server, char *cacertfile, char *capath, BIO *input; int ret; int reason; -#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 @@ -358,8 +296,7 @@ SSL_CTX *eaptls_init_ssl(int init_server, char *cacertfile, char *capath, } } - SSL_library_init(); - SSL_load_error_strings(); + tls_init(); #ifndef OPENSSL_NO_ENGINE /* load the openssl config file only once and load it before triggering @@ -369,8 +306,7 @@ SSL_CTX *eaptls_init_ssl(int init_server, char *cacertfile, char *capath, ssl_config = eaptls_ssl_load_config(); #endif - ctx = SSL_CTX_new(TLS_method()); - + ctx = SSL_CTX_new(tls_method()); if (!ctx) { error("EAP-TLS: Cannot initialize SSL CTX context"); goto fail; @@ -451,14 +387,7 @@ SSL_CTX *eaptls_init_ssl(int init_server, char *cacertfile, char *capath, 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); + if (tls_set_ca(ctx, capath, cacertfile) != 0) { goto fail; } @@ -492,7 +421,7 @@ SSL_CTX *eaptls_init_ssl(int init_server, char *cacertfile, char *capath, else { warn("EAP-TLS: Cannot load key with URI: '%s'", certfile ); - log_ssl_errors(); + tls_log_sslerr(); } } else @@ -645,21 +574,8 @@ SSL_CTX *eaptls_init_ssl(int init_server, char *cacertfile, char *capath, 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"); - */ - + /* Configure the default options */ + tls_set_opts(ctx); /* 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 @@ -671,94 +587,17 @@ SSL_CTX *eaptls_init_ssl(int init_server, char *cacertfile, char *capath, 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); + /* Configure the maximum SSL version */ + tls_set_version(ctx, max_tls_version); + /* Configure the callback */ + if (tls_set_verify(ctx, 5)) { + goto fail; } - /* - * 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); + /* Configure CRL check (if any) */ + if (tls_set_crl(ctx, crl_dir, crl_file)) { + goto fail; } return ctx; @@ -774,7 +613,7 @@ fail: if (chain) sk_X509_pop_free(chain, X509_free); - log_ssl_errors(); + tls_log_sslerr(); SSL_CTX_free(ctx); return NULL; } @@ -821,15 +660,12 @@ int eaptls_init_ssl_server(eap_state * esp) if (!esp->es_server.ea_session) fatal("Allocation error"); ets = esp->es_server.ea_session; - ets->client = 0; 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, @@ -841,13 +677,17 @@ int eaptls_init_ssl_server(eap_state * esp) ets->mtu = eaptls_get_mtu(esp->es_unit); - ets->ctx = eaptls_init_ssl(1, cacertfile, capath, servcertfile, clicertfile, pkfile, pkcs12); + ets->ctx = eaptls_init_ssl(1, cacertfile, capath, servcertfile, pkfile, pkcs12); if (!ets->ctx) goto fail; if (!(ets->ssl = SSL_new(ets->ctx))) goto fail; + if (tls_set_verify_info(ets->ssl, esp->es_server.ea_peer, + clicertfile, 0, &ets->info)) + goto fail; + /* * Set auto-retry to avoid timeouts on BIO_read */ @@ -863,12 +703,6 @@ int eaptls_init_ssl_server(eap_state * esp) 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; @@ -877,16 +711,6 @@ int eaptls_init_ssl_server(eap_state * esp) 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: @@ -914,38 +738,30 @@ int eaptls_init_ssl_client(eap_state * esp) if (!esp->es_client.ea_session) fatal("Allocation error"); ets = esp->es_client.ea_session; - ets->client = 1; - - /* - * 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, + esp->es_client.ea_peer, clicertfile, servcertfile, cacertfile, capath, pkfile, pkcs12, 0)) { error( "EAP-TLS: Cannot get secret/password for client \"%s\", server \"%s\"", - esp->es_client.ea_name, ets->peer ); + esp->es_client.ea_name, esp->es_client.ea_peer); return 0; } dbglog( "calling eaptls_init_ssl" ); - ets->ctx = eaptls_init_ssl(0, cacertfile, capath, clicertfile, servcertfile, pkfile, pkcs12); + ets->ctx = eaptls_init_ssl(0, cacertfile, capath, clicertfile, pkfile, pkcs12); if (!ets->ctx) goto fail; ets->ssl = SSL_new(ets->ctx); - if (!ets->ssl) goto fail; + if (tls_set_verify_info(ets->ssl, esp->es_client.ea_peer, + servcertfile, 0, &ets->info)) + goto fail; + /* * Initialize the BIOs we use to read/write to ssl engine */ @@ -956,13 +772,6 @@ int eaptls_init_ssl_client(eap_state * esp) 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; @@ -971,17 +780,6 @@ int eaptls_init_ssl_client(eap_state * esp) 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: @@ -999,6 +797,9 @@ void eaptls_free_session(struct eaptls_session *ets) if (ets->ctx) SSL_CTX_free(ets->ctx); + if (ets->info) + tls_free_verify_info(&ets->info); + free(ets); } @@ -1108,7 +909,7 @@ int eaptls_receive(struct eaptls_session *ets, u_char * inp, int len) } if (BIO_write(ets->into_ssl, ets->data, ets->datalen) == -1) - log_ssl_errors(); + tls_log_sslerr(); SSL_read(ets->ssl, dummy, 65536); @@ -1223,171 +1024,6 @@ void eaptls_retransmit(struct eaptls_session *ets, u_char ** outp) 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; - char *ptr1 = NULL, *ptr2 = NULL; - - 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) - { - /* Verify certificate based on certificate type and extended key usage */ - if (tls_verify_key_usage) { - int purpose = ets->client ? X509_PURPOSE_SSL_SERVER : X509_PURPOSE_SSL_CLIENT ; - if (X509_check_purpose(peer_cert, purpose, 0) == 0) { - error("Certificate verification error: nsCertType mismatch"); - return 0; - } - -#if OPENSSL_VERSION_NUMBER >= 0x10100000L - int flags = ets->client ? XKU_SSL_SERVER : XKU_SSL_CLIENT; - if (!(X509_get_extended_key_usage(peer_cert) & flags)) { - error("Certificate verification error: invalid extended key usage"); - return 0; - } -#endif - info("Certificate key usage: OK"); - } - - /* - * If acting as client and the name of the server wasn't specified - * explicitely, we can't verify the server authenticity - */ - if (!tls_verify_method) - tls_verify_method = TLS_VERIFY_NONE; - - if (!ets->peer[0] || !strcmp(TLS_VERIFY_NONE, tls_verify_method)) { - warn("Certificate verication disabled or no peer name was specified"); - return ok; - } - - /* 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); - - /* Verify based on subject name */ - ptr1 = ets->peer; - if (!strcmp(TLS_VERIFY_SUBJECT, tls_verify_method)) { - ptr2 = subject; - } - - /* Verify based on common name (default) */ - if (strlen(tls_verify_method) == 0 || - !strcmp(TLS_VERIFY_NAME, tls_verify_method)) { - ptr2 = cn_str; - } - - /* Match the suffix of common name */ - if (!strcmp(TLS_VERIFY_SUFFIX, tls_verify_method)) { - int len = strlen(ptr1); - int off = strlen(cn_str) - len; - ptr2 = cn_str; - if (off > 0) { - ptr2 = cn_str + off; - } - } - - if (strcmp(ptr1, ptr2)) { - error("Certificate verification error: CN (%s) != %s", ptr1, ptr2); - return 0; - } - - info("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 diff --git a/pppd/eap-tls.h b/pppd/eap-tls.h index d0c80b6..9c56687 100644 --- a/pppd/eap-tls.h +++ b/pppd/eap-tls.h @@ -46,6 +46,8 @@ #define EAP_TLS_MAX_LEN 65536 /* max eap tls packet size */ +struct tls_info; + struct eaptls_session { u_char *data; /* buffered data */ @@ -58,7 +60,6 @@ struct eaptls_session SSL *ssl; /* ssl connection */ BIO *from_ssl; BIO *into_ssl; - char peer[MAXWORDLEN]; /* peer name */ char peercertfile[MAXWORDLEN]; bool alert_sent; u_char alert_sent_desc; @@ -67,12 +68,12 @@ struct eaptls_session char rtx[EAP_TLS_MAX_LEN]; /* retransmission buffer */ int rtx_len; int mtu; /* unit mtu */ - bool client; + struct tls_info *info; }; SSL_CTX *eaptls_init_ssl(int init_server, char *cacertfile, char *capath, - char *certfile, char *peer_certfile, char *privkeyfile, char *pkcs12); + char *certfile, char *privkeyfile, char *pkcs12); int eaptls_init_ssl_server(eap_state * esp); int eaptls_init_ssl_client(eap_state * esp); void eaptls_free_session(struct eaptls_session *ets); diff --git a/pppd/eap.c b/pppd/eap.c index b758711..54c3d42 100644 --- a/pppd/eap.c +++ b/pppd/eap.c @@ -2221,7 +2221,24 @@ eap_request(eap_state *esp, u_char *inp, int id, int len) #endif /* CHAPMS */ #ifdef USE_PEAP case EAPT_PEAP: - peap_process(esp, id, inp, len, rhostname); + + /* Initialize the PEAP context (if not already initialized) */ + if (!esp->ea_peap) { + rhostname[0] = '\0'; + if (explicit_remote || (remote_name[0] != '\0')) { + strlcpy(rhostname, remote_name, sizeof (rhostname)); + } + if (peap_init(&esp->ea_peap, rhostname)) { + eap_send_nak(esp, id, EAPT_TLS); + break; + } + } + + /* Process the PEAP packet */ + if (peap_process(esp, id, inp, len)) { + eap_send_nak(esp, id, EAPT_TLS); + } + break; #endif /* USE_PEAP */ @@ -2777,6 +2794,10 @@ eap_success(eap_state *esp, u_char *inp, int id, int len) PRINTMSG(inp, len); } +#ifdef USE_PEAP + peap_finish(&esp->ea_peap); +#endif + esp->es_client.ea_state = eapOpen; auth_withpeer_success(esp->es_unit, PPP_EAP, 0); } @@ -2811,6 +2832,11 @@ eap_failure(eap_state *esp, u_char *inp, int id, int len) esp->es_client.ea_state = eapBadAuth; error("EAP: peer reports authentication failure"); + +#ifdef USE_PEAP + peap_finish(&esp->ea_peap); +#endif + auth_withpeer_fail(esp->es_unit, PPP_EAP); } diff --git a/pppd/eap.h b/pppd/eap.h index 956b2ea..5d582bc 100644 --- a/pppd/eap.h +++ b/pppd/eap.h @@ -169,6 +169,9 @@ typedef struct eap_state { int es_unit; /* Interface unit number */ struct eap_auth es_client; /* Client (authenticatee) data */ struct eap_auth es_server; /* Server (authenticator) data */ +#ifdef USE_PEAP + struct peap_state *ea_peap; /* Client PEAP (authenticator) data */ +#endif int es_savedtime; /* Saved timeout */ int es_rechallenge; /* EAP rechallenge interval */ int es_lwrechallenge; /* SRP lightweight rechallenge inter */ diff --git a/pppd/mppe.c b/pppd/mppe.c index d2ba0eb..f1b7abf 100644 --- a/pppd/mppe.c +++ b/pppd/mppe.c @@ -220,6 +220,8 @@ mppe_set_chapv2(u_char PasswordHashHash[MD4_SIGNATURE_SIZE], mppe_set_keys(SendKey, RecvKey, SHA1_SIGNATURE_SIZE); } +#ifndef UNIT_TEST + /* * Set MPPE options from plugins. */ @@ -251,3 +253,4 @@ mppe_set_enc_types(int policy, int types) } } +#endif diff --git a/pppd/peap.c b/pppd/peap.c index e8d1a19..fb9af3e 100644 --- a/pppd/peap.c +++ b/pppd/peap.c @@ -1,14 +1,36 @@ /* - * Copyright (c) 2011 + * Copyright (c) 2011 Rustam Kovhaev. All rights reserved. + * Copyright (c) 2021 Eivind Næss. All rights reserved. * - * Authors: + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * Rustam Kovhaev + * 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. + * + * NOTES: * * PEAP has 2 phases, * 1 - Outer EAP, where TLS session gets established - * 2 - Inner EAP, where inside TLS session with EAP MSCHAPV2 auth, or any - * other auth + * 2 - Inner EAP, where inside TLS session with EAP MSCHAPV2 auth, or any other auth * * And so protocols encapsulation looks like this: * Outer EAP -> TLS -> Inner EAP -> MSCHAPV2 @@ -21,6 +43,8 @@ * b) Inner EAP fragmentation * c) Any other auth other than MSCHAPV2 * + * For details on the PEAP protocol, look to Microsoft: + * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-peap */ #include @@ -32,18 +56,27 @@ #include #include #include +#include + #include "pppd.h" #include "eap.h" +#include "tls.h" #include "chap-new.h" #include "chap_ms.h" +#include "mppe.h" #include "peap.h" +#ifdef UNIT_TEST +#define novm(x) +#endif + struct peap_state { SSL_CTX *ctx; SSL *ssl; BIO *in_bio; BIO *out_bio; + int phase; int written, read; u_char *in_buf; u_char *out_buf; @@ -51,21 +84,11 @@ struct peap_state { u_char ipmk[PEAP_TLV_IPMK_LEN]; u_char tk[PEAP_TLV_TK_LEN]; u_char nonce[PEAP_TLV_NONCE_LEN]; -}; - -static struct peap_state *psm; -static int peap_phase; -extern bool tls_verify_cert; -static bool init; - -static void ssl_init() -{ -#if OPENSSL_VERSION_NUMBER < 0x10100000L - SSL_library_init(); - SSL_load_error_strings(); + struct tls_info *info; +#ifdef CHAPMS + struct chap_digest_type *chap; #endif - init = 1; -} +}; /* * K = Key, S = Seed, LEN = output length @@ -85,11 +108,11 @@ static void peap_prfplus(u_char *seed, size_t seed_len, u_char *key, size_t key_ size_t max_iter, i, j, k; u_int len; - max_iter = (pfr_len + SHA_HASH_LEN - 1) / SHA_HASH_LEN; - buf = malloc(seed_len + max_iter * SHA_HASH_LEN); + max_iter = (pfr_len + SHA_DIGEST_LENGTH - 1) / SHA_DIGEST_LENGTH; + buf = malloc(seed_len + max_iter * SHA_DIGEST_LENGTH); if (!buf) novm("pfr buffer"); - hash = malloc(pfr_len + SHA_HASH_LEN); + hash = malloc(pfr_len + SHA_DIGEST_LENGTH); if (!hash) novm("hash buffer"); @@ -98,7 +121,7 @@ static void peap_prfplus(u_char *seed, size_t seed_len, u_char *key, size_t key_ k = 0; if (i > 0) - j = SHA_HASH_LEN; + j = SHA_DIGEST_LENGTH; for (k = 0; k < seed_len; k++) buf[j + k] = seed[k]; pos = j + k; @@ -108,17 +131,17 @@ static void peap_prfplus(u_char *seed, size_t seed_len, u_char *key, size_t key_ pos++; buf[pos] = 0x00; pos++; - if (!HMAC(EVP_sha1(), key, key_len, buf, pos, (hash + i * SHA_HASH_LEN), &len)) + if (!HMAC(EVP_sha1(), key, key_len, buf, pos, (hash + i * SHA_DIGEST_LENGTH), &len)) fatal("HMAC() failed"); - for (j = 0; j < SHA_HASH_LEN; j++) - buf[j] = hash[i * SHA_HASH_LEN + j]; + for (j = 0; j < SHA_DIGEST_LENGTH; j++) + buf[j] = hash[i * SHA_DIGEST_LENGTH + j]; } BCOPY(hash, out_buf, pfr_len); free(hash); free(buf); } -static void generate_cmk(u_char *tempkey, u_char *nonce, u_char *tlv_response_out, int client) +static void generate_cmk(u_char *ipmk, u_char *tempkey, u_char *nonce, u_char *tlv_response_out, int client) { const char *label = PEAP_TLV_IPMK_SEED_LABEL; u_char data_tlv[PEAP_TLV_DATA_LEN] = {0}; @@ -129,8 +152,6 @@ static void generate_cmk(u_char *tempkey, u_char *nonce, u_char *tlv_response_ou u_char compound_mac[PEAP_TLV_COMP_MAC_LEN] = {0}; u_int len; - if (debug) - info("PEAP CB: generate compound mac"); /* format outgoing CB TLV response packet */ data_tlv[1] = PEAP_TLV_TYPE; data_tlv[3] = PEAP_TLV_LENGTH_FIELD; @@ -141,15 +162,17 @@ static void generate_cmk(u_char *tempkey, u_char *nonce, u_char *tlv_response_ou BCOPY(nonce, (data_tlv + PEAP_TLV_HEADERLEN), PEAP_TLV_NONCE_LEN); data_tlv[60] = EAPT_PEAP; - BCOPY(mppe_send_key, isk, MPPE_MAX_KEY_LEN); - BCOPY(mppe_recv_key, isk + MPPE_MAX_KEY_LEN, MPPE_MAX_KEY_LEN); +#ifdef MPPE + mppe_get_send_key(isk, MPPE_MAX_KEY_LEN); + mppe_get_recv_key(isk + MPPE_MAX_KEY_LEN, MPPE_MAX_KEY_LEN); +#endif BCOPY(label, ipmkseed, strlen(label)); BCOPY(isk, ipmkseed + strlen(label), PEAP_TLV_ISK_LEN); peap_prfplus(ipmkseed, PEAP_TLV_IPMKSEED_LEN, tempkey, PEAP_TLV_TEMPKEY_LEN, buf, PEAP_TLV_CMK_LEN + PEAP_TLV_IPMK_LEN); - BCOPY(buf, psm->ipmk, PEAP_TLV_IPMK_LEN); + BCOPY(buf, ipmk, PEAP_TLV_IPMK_LEN); BCOPY(buf + PEAP_TLV_IPMK_LEN, cmk, PEAP_TLV_CMK_LEN); if (!HMAC(EVP_sha1(), cmk, PEAP_TLV_CMK_LEN, data_tlv, PEAP_TLV_DATA_LEN, compound_mac, &len)) fatal("HMAC() failed"); @@ -158,28 +181,30 @@ static void generate_cmk(u_char *tempkey, u_char *nonce, u_char *tlv_response_ou BCOPY(data_tlv, tlv_response_out, PEAP_TLV_DATA_LEN - 1); } -static void verify_compound_mac(u_char *in_buf) +static void verify_compound_mac(struct peap_state *psm, u_char *in_buf) { u_char nonce[PEAP_TLV_NONCE_LEN] = {0}; u_char out_buf[PEAP_TLV_LEN] = {0}; BCOPY(in_buf, nonce, PEAP_TLV_NONCE_LEN); - generate_cmk(psm->tk, nonce, out_buf, 0); + generate_cmk(psm->ipmk, psm->tk, nonce, out_buf, 0); if (memcmp((in_buf + PEAP_TLV_NONCE_LEN), (out_buf + PEAP_TLV_HEADERLEN + PEAP_TLV_NONCE_LEN), PEAP_TLV_CMK_LEN)) fatal("server's CMK does not match client's CMK, potential MiTM"); } -static void generate_mppe_keys(void) +#ifdef MPPE +#define PEAP_MPPE_KEY_LEN 32 + +static void generate_mppe_keys(u_char *ipmk, int client) { const char *label = PEAP_TLV_CSK_SEED_LABEL; u_char csk[PEAP_TLV_CSK_LEN] = {0}; size_t len; - if (debug) - info("PEAP CB: generate mppe keys"); + dbglog("PEAP CB: generate mppe keys"); len = strlen(label); len++; /* CSK requires NULL byte in seed */ - peap_prfplus((u_char *)label, len, psm->ipmk, PEAP_TLV_IPMK_LEN, csk, PEAP_TLV_CSK_LEN); + peap_prfplus((u_char *)label, len, ipmk, PEAP_TLV_IPMK_LEN, csk, PEAP_TLV_CSK_LEN); /* * The first 64 bytes of the CSK are split into two MPPE keys, as follows. @@ -190,19 +215,16 @@ static void generate_mppe_keys(void) * | MS-MPPE-Send-Key | MS-MPPE-Recv-Key | * +-----------------------+------------------------+ */ - BCOPY(csk, mppe_send_key, MPPE_MAX_KEY_LEN); - BCOPY(csk + 32, mppe_recv_key, MPPE_MAX_KEY_LEN); + if (client) { + mppe_set_keys(csk, csk + PEAP_MPPE_KEY_LEN, PEAP_MPPE_KEY_LEN); + } else { + mppe_set_keys(csk + PEAP_MPPE_KEY_LEN, csk, PEAP_MPPE_KEY_LEN); + } } -static void dump(u_char *buf, int len) -{ - int i = 0; +#endif - dbglog("len: %d bytes", len); - for (i = 0; i < len; i++) - printf("%02x ", buf[i]); - printf("\n"); -} +#ifndef UNIT_TEST static void peap_ack(eap_state *esp, u_char id) { @@ -221,6 +243,7 @@ static void peap_ack(eap_state *esp, u_char id) static void peap_response(eap_state *esp, u_char id, u_char *buf, int len) { + struct peap_state *psm = esp->ea_peap; u_char *outp; int peap_len; @@ -230,7 +253,7 @@ static void peap_response(eap_state *esp, u_char id, u_char *buf, int len) PUTCHAR(id, outp); esp->es_client.ea_id = id; - if (peap_phase == PEAP_PHASE_1) + if (psm->phase == PEAP_PHASE_1) peap_len = PEAP_HEADERLEN + PEAP_FRAGMENT_LENGTH_FIELD + len; else peap_len = PEAP_HEADERLEN + len; @@ -238,7 +261,7 @@ static void peap_response(eap_state *esp, u_char id, u_char *buf, int len) PUTSHORT(peap_len, outp); PUTCHAR(EAPT_PEAP, outp); - if (peap_phase == PEAP_PHASE_1) { + if (psm->phase == PEAP_PHASE_1) { PUTCHAR(PEAP_L_FLAG_SET, outp); PUTLONG(len, outp); } else @@ -248,24 +271,20 @@ static void peap_response(eap_state *esp, u_char id, u_char *buf, int len) output(esp->es_unit, outpacket_buf, PPP_HDRLEN + peap_len); } -void do_inner_eap(u_char *in_buf, int in_len, eap_state *esp, int id, - char *rhostname, u_char *out_buf, int *out_len) +void peap_do_inner_eap(u_char *in_buf, int in_len, eap_state *esp, int id, + u_char *out_buf, int *out_len) { - if (debug) - dump(in_buf, in_len); - int used; - u_char *outp; + struct peap_state *psm = esp->ea_peap; + int used = 0; + int typenum; + int secret_len; + char secret[MAXSECRETLEN + 1]; + char rhostname[MAXWORDLEN]; + u_char *outp = out_buf; - used = 0; - outp = out_buf; + dbglog("PEAP: EAP (in): %.*B", in_len, in_buf); - if (*in_buf == EAPT_IDENTITY && in_len == 1) { - PUTCHAR(EAPT_IDENTITY, outp); - used++; - BCOPY(esp->es_client.ea_name, outp, - esp->es_client.ea_namelen); - used += esp->es_client.ea_namelen; - } else if (*(in_buf + EAP_HEADERLEN) == PEAP_CAPABILITIES_TYPE && + if (*(in_buf + EAP_HEADERLEN) == PEAP_CAPABILITIES_TYPE && in_len == (EAP_HEADERLEN + PEAP_CAPABILITIES_LEN)) { /* use original packet as template for response */ BCOPY(in_buf, outp, EAP_HEADERLEN + PEAP_CAPABILITIES_LEN); @@ -274,57 +293,15 @@ void do_inner_eap(u_char *in_buf, int in_len, eap_state *esp, int id, /* change last byte to 0 to disable fragmentation */ *(outp + PEAP_CAPABILITIES_LEN + 1) = 0x00; used = EAP_HEADERLEN + PEAP_CAPABILITIES_LEN; - } else if (*in_buf == EAPT_TLS && in_len == 2) { - /* send NAK to EAP_TLS request */ - PUTCHAR(EAPT_NAK, outp); - used++; - PUTCHAR(EAPT_MSCHAPV2, outp); - used++; - } else if (*in_buf == EAPT_MSCHAPV2 && *(in_buf + 1) == CHAP_CHALLENGE) { - /* MSCHAPV2 auth */ - int secret_len; - char secret[MAXSECRETLEN + 1]; - char *user; - u_char user_len; - u_char response[MS_CHAP2_RESPONSE_LEN]; - u_char auth_response[MS_AUTH_RESPONSE_LENGTH + 1]; - u_char chap_id; - u_char rchallenge[MS_CHAP2_PEER_CHAL_LEN]; - - user = esp->es_client.ea_name; - user_len = esp->es_client.ea_namelen; - chap_id = *(in_buf + 2); - BCOPY((in_buf + 6), rchallenge, MS_CHAP2_PEER_CHAL_LEN); - if (!get_secret(esp->es_unit, esp->es_client.ea_name, - rhostname, secret, &secret_len, 0)) - fatal("Can't read password file"); - /* MSCHAPV2 response */ - ChapMS2(rchallenge, NULL, esp->es_client.ea_name, - secret, secret_len, response, auth_response, MS_CHAP2_AUTHENTICATEE); - PUTCHAR(EAPT_MSCHAPV2, outp); - PUTCHAR(CHAP_RESPONSE, outp); - PUTCHAR(chap_id, outp); - PUTCHAR(0, outp); - PUTCHAR(5 + user_len + MS_CHAP2_RESPONSE_LEN, outp); - PUTCHAR(MS_CHAP2_RESPONSE_LEN, outp) - BCOPY(response, outp, MS_CHAP2_RESPONSE_LEN); - outp = outp + MS_CHAP2_RESPONSE_LEN; - BCOPY(user, outp, user_len); - used = 5 + user_len + MS_CHAP2_RESPONSE_LEN + 1; - } else if (*in_buf == EAPT_MSCHAPV2 && *(in_buf + 1) == CHAP_SUCCESS) { - PUTCHAR(EAPT_MSCHAPV2, outp); - used++; - PUTCHAR(CHAP_SUCCESS, outp); - used++; - auth_peer_success(esp->es_unit, PPP_CHAP, CHAP_MICROSOFT_V2, - esp->es_server.ea_peer, esp->es_server.ea_peerlen); - } else if (*(in_buf + EAP_HEADERLEN + PEAP_TLV_HEADERLEN) == PEAP_TLV_TYPE && + goto done; + } + if (*(in_buf + EAP_HEADERLEN + PEAP_TLV_HEADERLEN) == PEAP_TLV_TYPE && in_len == PEAP_TLV_LEN) { /* PEAP TLV message, do cryptobinding */ SSL_export_keying_material(psm->ssl, psm->tk, PEAP_TLV_TK_LEN, PEAP_TLV_TK_SEED_LABEL, strlen(PEAP_TLV_TK_SEED_LABEL), NULL, 0, 0); /* verify server's CMK */ - verify_compound_mac(in_buf + EAP_HEADERLEN + PEAP_TLV_RESULT_LEN + PEAP_TLV_HEADERLEN); + verify_compound_mac(psm, in_buf + EAP_HEADERLEN + PEAP_TLV_RESULT_LEN + PEAP_TLV_HEADERLEN); /* generate client's CMK with new nonce */ PUTCHAR(EAP_RESPONSE, outp); PUTCHAR(id, outp); @@ -332,26 +309,169 @@ void do_inner_eap(u_char *in_buf, int in_len, eap_state *esp, int id, BCOPY(in_buf + EAP_HEADERLEN, outp, PEAP_TLV_RESULT_LEN); outp = outp + PEAP_TLV_RESULT_LEN; RAND_bytes(psm->nonce, PEAP_TLV_NONCE_LEN); - generate_cmk(psm->tk, psm->nonce, outp, 1); + generate_cmk(psm->ipmk, psm->tk, psm->nonce, outp, 1); +#ifdef MPPE /* set mppe keys */ - generate_mppe_keys(); + generate_mppe_keys(psm->ipmk, 1); +#endif used = PEAP_TLV_LEN; - } else { + goto done; + } + + GETCHAR(typenum, in_buf); + in_len--; + + switch (typenum) { + case EAPT_IDENTITY: + /* Respond with our identity to the peer */ + PUTCHAR(EAPT_IDENTITY, outp); + BCOPY(esp->es_client.ea_name, outp, + esp->es_client.ea_namelen); + used += (esp->es_client.ea_namelen + 1); + break; + + case EAPT_TLS: + /* Send NAK to EAP_TLS request */ + PUTCHAR(EAPT_NAK, outp); + PUTCHAR(EAPT_MSCHAPV2, outp); + used += 2; + break; + +#if CHAPMS + case EAPT_MSCHAPV2: { + + // Must have at least 4 more bytes to process CHAP header + if (in_len < 4) { + error("PEAP: received invalid MSCHAPv2 packet, too short"); + break; + } + + u_char opcode; + GETCHAR(opcode, in_buf); + + u_char chap_id; + GETCHAR(chap_id, in_buf); + + short mssize; + GETSHORT(mssize, in_buf); + + // Validate the CHAP packet (including header) + if (in_len != mssize) { + error("PEAP: received invalid MSCHAPv2 packet, invalid length"); + break; + } + in_len -= 4; + + switch (opcode) { + case CHAP_CHALLENGE: { + + u_char *challenge = in_buf; // VLEN + VALUE + u_char vsize; + + GETCHAR(vsize, in_buf); + in_len -= 1; + + if (vsize != MS_CHAP2_PEER_CHAL_LEN || in_len < MS_CHAP2_PEER_CHAL_LEN) { + error("PEAP: received invalid MSCHAPv2 packet, invalid value-length: %d", vsize); + goto done; + } + + INCPTR(MS_CHAP2_PEER_CHAL_LEN, in_buf); + in_len -= MS_CHAP2_PEER_CHAL_LEN; + + // Copy the provided remote host name + rhostname[0] = '\0'; + if (in_len > 0) { + if (in_len >= sizeof(rhostname)) { + dbglog("PEAP: trimming really long peer name down"); + in_len = sizeof(rhostname) - 1; + } + BCOPY(in_buf, rhostname, in_len); + rhostname[in_len] = '\0'; + } + + // In case the remote doesn't give us his name, or user explictly specified remotename is config + if (explicit_remote || (remote_name[0] != '\0' && in_len == 0)) + strlcpy(rhostname, remote_name, sizeof(rhostname)); + + // Get the scrert for authenticating ourselves with the specified host + if (get_secret(esp->es_unit, esp->es_client.ea_name, + rhostname, secret, &secret_len, 0)) { + + u_char response[MS_CHAP2_RESPONSE_LEN+1]; + u_char user_len = esp->es_client.ea_namelen; + char *user = esp->es_client.ea_name; + + psm->chap->make_response(response, chap_id, user, + challenge, secret, secret_len, NULL); + + PUTCHAR(EAPT_MSCHAPV2, outp); + PUTCHAR(CHAP_RESPONSE, outp); + PUTCHAR(chap_id, outp); + PUTCHAR(0, outp); + PUTCHAR(5 + user_len + MS_CHAP2_RESPONSE_LEN, outp); + BCOPY(response, outp, MS_CHAP2_RESPONSE_LEN+1); // VLEN + VALUE + INCPTR(MS_CHAP2_RESPONSE_LEN+1, outp); + BCOPY(user, outp, user_len); + used = 5 + user_len + MS_CHAP2_RESPONSE_LEN + 1; + + } else { + dbglog("PEAP: no CHAP secret for auth to %q", rhostname); + PUTCHAR(EAPT_NAK, outp); + ++used; + } + break; + } + case CHAP_SUCCESS: { + + u_char status = CHAP_FAILURE; + if (psm->chap->check_success(chap_id, in_buf, in_len)) { + info("Chap authentication succeeded! %.*v", in_len, in_buf); + status = CHAP_SUCCESS; + } + + PUTCHAR(EAPT_MSCHAPV2, outp); + PUTCHAR(status, outp); + used += 2; + break; + } + case CHAP_FAILURE: { + + psm->chap->handle_failure(in_buf, in_len); + PUTCHAR(EAPT_MSCHAPV2, outp); + PUTCHAR(status, outp); + used += 2; + break; + } + default: + break; + } + break; + } // EAPT_MSCHAPv2 +#endif + default: + /* send compressed EAP NAK for any unknown packet */ PUTCHAR(EAPT_NAK, outp); ++used; } - if (debug) - dump(psm->out_buf, used); +done: + + dbglog("PEAP: EAP (out): %.*B", used, psm->out_buf); *out_len = used; } -void allocate_buffers(void) +int peap_init(struct peap_state **ctx, const char *rhostname) { const SSL_METHOD *method; - psm = malloc(sizeof(*psm)); + if (!ctx) + return -1; + + tls_init(); + + struct peap_state *psm = malloc(sizeof(*psm)); if (!psm) novm("peap psm struct"); psm->in_buf = malloc(TLS_RECORD_MAX_SIZE); @@ -360,20 +480,31 @@ void allocate_buffers(void) psm->out_buf = malloc(TLS_RECORD_MAX_SIZE); if (!psm->out_buf) novm("peap tls buffer"); - method = TLS_method(); + method = tls_method(); if (!method) novm("TLS_method() failed"); psm->ctx = SSL_CTX_new(method); if (!psm->ctx) novm("SSL_CTX_new() failed"); - if (!tls_verify_cert) - SSL_CTX_set_verify(psm->ctx, SSL_VERIFY_NONE, NULL); - else - SSL_CTX_set_verify(psm->ctx, SSL_VERIFY_PEER, NULL); - info("PEAP: SSL certificate validation is %s", tls_verify_cert ? "enabled" : "disabled"); + /* Configure the default options */ + tls_set_opts(psm->ctx); + + /* Configure the max TLS version */ + tls_set_version(psm->ctx, max_tls_version); - SSL_CTX_set_options(psm->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION); + /* Configure the peer certificate callback */ + tls_set_verify(psm->ctx, 5); + + /* Configure CA locations */ + if (tls_set_ca(psm->ctx, ca_path, cacert_file)) { + fatal("Could not set CA verify locations"); + } + + /* Configure CRL check (if any) */ + if (tls_set_crl(psm->ctx, crl_dir, crl_file)) { + fatal("Could not set CRL verify locations"); + } psm->out_bio = BIO_new(BIO_s_mem()); psm->in_bio = BIO_new(BIO_s_mem()); @@ -382,27 +513,49 @@ void allocate_buffers(void) psm->ssl = SSL_new(psm->ctx); SSL_set_bio(psm->ssl, psm->in_bio, psm->out_bio); SSL_set_connect_state(psm->ssl); - peap_phase = PEAP_PHASE_1; + psm->phase = PEAP_PHASE_1; + tls_set_verify_info(psm->ssl, explicit_remote ? rhostname : NULL, NULL, 1, &psm->info); + psm->chap = chap_find_digest(CHAP_MICROSOFT_V2); + *ctx = psm; + return 0; +} + +void peap_finish(struct peap_state **psm) { + + if (psm && *psm) { + struct peap_state *tmp = *psm; + + if (tmp->ssl) + SSL_free(tmp->ssl); + + if (tmp->ctx) + SSL_CTX_free(tmp->ctx); + + if (tmp->info) + tls_free_verify_info(&tmp->info); + + // NOTE: BIO and memory is freed as a part of SSL_free() + + free(*psm); + *psm = NULL; + } } -void peap_process(eap_state *esp, u_char id, u_char *inp, int len, char *rhostname) +int peap_process(eap_state *esp, u_char id, u_char *inp, int len) { int ret; int out_len; - if (!init) - ssl_init(); + struct peap_state *psm = esp->ea_peap; if (esp->es_client.ea_id == id) { info("PEAP: retransmits are not supported.."); - return; + return -1; } switch (*inp) { case PEAP_S_FLAG_SET: - allocate_buffers(); - if (debug) - info("PEAP: S bit is set, starting PEAP phase 1"); + dbglog("PEAP: S bit is set, starting PEAP phase 1"); ret = SSL_do_handshake(psm->ssl); if (ret != 1) { ret = SSL_get_error(psm->ssl, ret); @@ -415,16 +568,14 @@ void peap_process(eap_state *esp, u_char id, u_char *inp, int len, char *rhostna break; case PEAP_LM_FLAG_SET: - if (debug) - info("PEAP TLS: LM bits are set, need to get more TLS fragments"); + dbglog("PEAP TLS: LM bits are set, need to get more TLS fragments"); inp = inp + PEAP_FRAGMENT_LENGTH_FIELD + PEAP_FLAGS_FIELD; psm->written = BIO_write(psm->in_bio, inp, len - PEAP_FRAGMENT_LENGTH_FIELD - PEAP_FLAGS_FIELD); peap_ack(esp, id); break; case PEAP_M_FLAG_SET: - if (debug) - info("PEAP TLS: M bit is set, need to get more TLS fragments"); + dbglog("PEAP TLS: M bit is set, need to get more TLS fragments"); inp = inp + PEAP_FLAGS_FIELD; psm->written = BIO_write(psm->in_bio, inp, len - PEAP_FLAGS_FIELD); peap_ack(esp, id); @@ -433,20 +584,17 @@ void peap_process(eap_state *esp, u_char id, u_char *inp, int len, char *rhostna case PEAP_L_FLAG_SET: case PEAP_NO_FLAGS: if (*inp == PEAP_L_FLAG_SET) { - if (debug) - info("PEAP TLS: L bit is set"); + dbglog("PEAP TLS: L bit is set"); inp = inp + PEAP_FRAGMENT_LENGTH_FIELD + PEAP_FLAGS_FIELD; psm->written = BIO_write(psm->in_bio, inp, len - PEAP_FRAGMENT_LENGTH_FIELD - PEAP_FLAGS_FIELD); } else { - if (debug) - info("PEAP TLS: all bits are off"); + dbglog("PEAP TLS: all bits are off"); inp = inp + PEAP_FLAGS_FIELD; psm->written = BIO_write(psm->in_bio, inp, len - PEAP_FLAGS_FIELD); } - if (peap_phase == PEAP_PHASE_1) { - if (debug) - info("PEAP TLS: continue handshake"); + if (psm->phase == PEAP_PHASE_1) { + dbglog("PEAP TLS: continue handshake"); ret = SSL_do_handshake(psm->ssl); if (ret != 1) { ret = SSL_get_error(psm->ssl, ret); @@ -454,7 +602,7 @@ void peap_process(eap_state *esp, u_char id, u_char *inp, int len, char *rhostna fatal("SSL_do_handshake(): %s", ERR_error_string(ret, NULL)); } if (SSL_is_init_finished(psm->ssl)) - peap_phase = PEAP_PHASE_2; + psm->phase = PEAP_PHASE_2; if (BIO_ctrl_pending(psm->out_bio) == 0) { peap_ack(esp, id); break; @@ -468,12 +616,153 @@ void peap_process(eap_state *esp, u_char id, u_char *inp, int len, char *rhostna psm->read = SSL_read(psm->ssl, psm->in_buf, TLS_RECORD_MAX_SIZE); out_len = TLS_RECORD_MAX_SIZE; - do_inner_eap(psm->in_buf, psm->read, esp, id, rhostname, + peap_do_inner_eap(psm->in_buf, psm->read, esp, id, psm->out_buf, &out_len); - psm->written = SSL_write(psm->ssl, psm->out_buf, out_len); - psm->read = BIO_read(psm->out_bio, psm->out_buf, + if (out_len > 0) { + psm->written = SSL_write(psm->ssl, psm->out_buf, out_len); + psm->read = BIO_read(psm->out_bio, psm->out_buf, TLS_RECORD_MAX_SIZE); - peap_response(esp, id, psm->out_buf, psm->read); + peap_response(esp, id, psm->out_buf, psm->read); + } break; } + return 0; +} + +#else + +u_char outpacket_buf[255]; +int debug = 1; +int error_count = 0; +int unsuccess = 0; + +/** + * Using the example in MS-PEAP, section 4.4.1. + * see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-peap/5308642b-90c9-4cc4-beec-fb367325c0f9 + */ +int test_cmk(u_char *ipmk) { + u_char nonce[PEAP_TLV_NONCE_LEN] = { + 0x6C, 0x6B, 0xA3, 0x87, 0x84, 0x23, 0x74, 0x57, + 0xCC, 0xC9, 0x0B, 0x1A, 0x90, 0x8C, 0xBD, 0xF4, + 0x71, 0x1B, 0x69, 0x99, 0x4D, 0x0C, 0xFE, 0x8D, + 0x3D, 0xB4, 0x4E, 0xCB, 0xCD, 0xAD, 0x37, 0xE9 + }; + + u_char tmpkey[PEAP_TLV_TEMPKEY_LEN] = { + 0x73, 0x8B, 0xB5, 0xF4, 0x62, 0xD5, 0x8E, 0x7E, + 0xD8, 0x44, 0xE1, 0xF0, 0x0D, 0x0E, 0xBE, 0x50, + 0xC5, 0x0A, 0x20, 0x50, 0xDE, 0x11, 0x99, 0x77, + 0x10, 0xD6, 0x5F, 0x45, 0xFB, 0x5F, 0xBA, 0xB7, + 0xE3, 0x18, 0x1E, 0x92, 0x4F, 0x42, 0x97, 0x38, + // 0xDE, 0x40, 0xC8, 0x46, 0xCD, 0xF5, 0x0B, 0xCB, + // 0xF9, 0xCE, 0xDB, 0x1E, 0x85, 0x1D, 0x22, 0x52, + // 0x45, 0x3B, 0xDF, 0x63 + }; + + u_char expected[60] = { + 0x00, 0x0C, 0x00, 0x38, 0x00, 0x00, 0x00, 0x01, + 0x6C, 0x6B, 0xA3, 0x87, 0x84, 0x23, 0x74, 0x57, + 0xCC, 0xC9, 0x0B, 0x1A, 0x90, 0x8C, 0xBD, 0xF4, + 0x71, 0x1B, 0x69, 0x99, 0x4D, 0x0C, 0xFE, 0x8D, + 0x3D, 0xB4, 0x4E, 0xCB, 0xCD, 0xAD, 0x37, 0xE9, + 0x42, 0xE0, 0x86, 0x07, 0x1D, 0x1C, 0x8B, 0x8C, + 0x8E, 0x45, 0x8F, 0x70, 0x21, 0xF0, 0x6A, 0x6E, + 0xAB, 0x16, 0xB6, 0x46 + }; + + u_char inner_mppe_keys[32] = { + 0x67, 0x3E, 0x96, 0x14, 0x01, 0xBE, 0xFB, 0xA5, + 0x60, 0x71, 0x7B, 0x3B, 0x5D, 0xDD, 0x40, 0x38, + 0x65, 0x67, 0xF9, 0xF4, 0x16, 0xFD, 0x3E, 0x9D, + 0xFC, 0x71, 0x16, 0x3B, 0xDF, 0xF2, 0xFA, 0x95 + }; + + u_char response[60] = {}; + + // Set the inner MPPE keys (e.g. from CHAPv2) + mppe_set_keys(inner_mppe_keys, inner_mppe_keys + 16, 16); + + // Generate and compare the response + generate_cmk(ipmk, tmpkey, nonce, response, 1); + if (memcmp(expected, response, sizeof(response)) != 0) { + dbglog("Failed CMK key generation\n"); + dbglog("%.*B", sizeof(response), response); + dbglog("%.*B", sizeof(expected), expected); + return -1; + } + + return 0; +} + +int test_mppe(u_char *ipmk) { + u_char outer_mppe_send_key[MPPE_MAX_KEY_SIZE] = { + 0x6A, 0x02, 0xD7, 0x82, 0x20, 0x1B, 0xC7, 0x13, + 0x8B, 0xF8, 0xEF, 0xF7, 0x33, 0xB4, 0x96, 0x97, + 0x0D, 0x7C, 0xAB, 0x30, 0x0A, 0xC9, 0x57, 0x72, + 0x78, 0xE1, 0xDD, 0xD5, 0xAE, 0xF7, 0x66, 0x97 + }; + + u_char outer_mppe_recv_key[MPPE_MAX_KEY_SIZE] = { + 0x17, 0x52, 0xD4, 0xE5, 0x84, 0xA1, 0xC8, 0x95, + 0x03, 0x9B, 0x4D, 0x05, 0xE3, 0xBC, 0x9A, 0x84, + 0x84, 0xDD, 0xC2, 0xAA, 0x6E, 0x2C, 0xE1, 0x62, + 0x76, 0x5C, 0x40, 0x68, 0xBF, 0xF6, 0x5A, 0x45 + }; + + u_char result[MPPE_MAX_KEY_SIZE]; + int len; + + mppe_clear_keys(); + + generate_mppe_keys(ipmk, 1); + + len = mppe_get_recv_key(result, sizeof(result)); + if (len != sizeof(result)) { + dbglog("Invalid length of resulting MPPE recv key"); + return -1; + } + + if (memcmp(result, outer_mppe_recv_key, len) != 0) { + dbglog("Invalid result for outer mppe recv key"); + return -1; + } + + len = mppe_get_send_key(result, sizeof(result)); + if (len != sizeof(result)) { + dbglog("Invalid length of resulting MPPE send key"); + return -1; + } + + if (memcmp(result, outer_mppe_send_key, len) != 0) { + dbglog("Invalid result for outer mppe send key"); + return -1; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + u_char ipmk[PEAP_TLV_IPMK_LEN] = { + 0x3A, 0x91, 0x1C, 0x25, 0x54, 0x73, 0xE8, 0x3E, + 0x9A, 0x0C, 0xC3, 0x33, 0xAE, 0x1F, 0x8A, 0x35, + 0xCD, 0xC7, 0x41, 0x63, 0xE7, 0xF6, 0x0F, 0x6C, + 0x65, 0xEF, 0x71, 0xC2, 0x64, 0x42, 0xAA, 0xAC, + 0xA2, 0xB6, 0xF1, 0xEB, 0x4F, 0x25, 0xEC, 0xA3, + }; + int ret = -1; + + ret = test_cmk(ipmk); + if (ret != 0) { + return -1; + } + + ret = test_mppe(ipmk); + if (ret != 0) { + return -1; + } + + return 0; } + +#endif diff --git a/pppd/peap.h b/pppd/peap.h index fd002d2..49e28e8 100644 --- a/pppd/peap.h +++ b/pppd/peap.h @@ -1,16 +1,35 @@ /* - * Copyright (c) 2011 + * Copyright (c) 2011 Rustam Kovhaev. All rights reserved. + * Copyright (c) 2021 Eivind Næss. All rights reserved. * - * Authors: + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: * - * Rustam Kovhaev + * 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. */ #ifndef PPP_PEAP_H #define PPP_PEAP_H -#define EAPT_MSCHAPV2 26 - #define PEAP_PHASE_1 1 #define PEAP_PHASE_2 2 @@ -22,8 +41,6 @@ #define PEAP_CAPABILITIES_TYPE 254 #define PEAP_CAPABILITIES_LEN 12 -#define SHA_HASH_LEN 20 - #define PEAP_TLV_TYPE 12 #define PEAP_TLV_LENGTH_FIELD 56 #define PEAP_TLV_SUBTYPE_REQUEST 0 @@ -60,7 +77,21 @@ #define EAP_TLS_KEY_LEN 0x40 #define TLS_RECORD_MAX_SIZE 0x4000 -void peap_process(eap_state *esp, u_char id, u_char *inp, - int len, char *rhostname); +struct peap_state; + +/** + * Initialize the PEAP structure + */ +int peap_init(struct peap_state** psm, const char *remote_name); + +/** + * Process a PEAP packet + */ +int peap_process(eap_state *esp, u_char id, u_char *inp, int len); + +/** + * Clean up the PEAP structure + */ +void peap_finish(struct peap_state **psm); #endif /* PPP_PEAP_H */ diff --git a/pppd/pppd.8 b/pppd/pppd.8 index cd4b9eb..b007406 100644 --- a/pppd/pppd.8 +++ b/pppd/pppd.8 @@ -260,10 +260,16 @@ compression in the corresponding direction. Use \fInobsdcomp\fR or \fIbsdcomp 0\fR to disable BSD-Compress compression entirely. .TP .B ca \fIca-file -(EAP-TLS) Use the file \fIca-file\fR as the X.509 Certificate Authority +(EAP-TLS, or PEAP) Use the file \fIca-file\fR as the X.509 Certificate Authority (CA) file (in PEM format), needed for setting up an EAP-TLS connection. This option is used on the client-side in conjunction with the \fBcert\fR -and \fBkey\fR options. +and \fBkey\fR options. Either \fIca\fR, or \fIcapath\fR options are required +for PEAP. EAP-TLS may also use the entry in eaptls-client or eaptls-server +for a CA certificate associated with a particular peer. +.TP +.B capath \fIpath +(EAP-TLS, or PEAP) Specify a location that contains public CA certificates. +Either \fIca\fR, or \fIcapath\fR options are required for PEAP. .TP .B cdtrcts Use a non-standard hardware flow control (i.e. DTR/CTS) to control @@ -320,15 +326,15 @@ negotiation by sending its first LCP packet. The default value is or \fBpty\fR option is used. .TP .B crl \fIfilename -(EAP-TLS) Use the file \fIfilename\fR as the Certificate Revocation List +(EAP-TLS, or PEAP) Use the file \fIfilename\fR as the Certificate Revocation List to check for the validity of the peer's certificate. This option is not -mandatory for setting up an EAP-TLS connection. Also see the \fBcrl-dir\fR +mandatory for setting up a TLS connection. Also see the \fBcrl-dir\fR option. .TP .B crl-dir \fIdirectory -(EAP-TLS) Use the directory \fIdirectory\fR to scan for CRL files in +(EAP-TLS, or PEAP) Use the directory \fIdirectory\fR to scan for CRL files in has format ($hash.r0) to check for the validity of the peer's certificate. -This option is not mandatory for setting up an EAP-TLS connection. +This option is not mandatory for setting up a TLS connection. Also see the \fBcrl\fR option. .TP .B debug @@ -724,6 +730,11 @@ network control protocol comes up). Terminate after \fIn\fR consecutive failed connection attempts. A value of 0 means no limit. The default value is 10. .TP +.B max-tls-version \fIstring +(EAP-TLS, or PEAP) Configures the max allowed TLS version used during +negotiation with a peer. The default value for this is \fI1.2\fR. Values +allowed for this option is \fI1.0.\fR, \fI1.1\fR, \fI1.2\fR, \fI1.3\fR. +.TP .B modem Use the modem control lines. This option is the default. With this option, pppd will wait for the CD (Carrier Detect) signal from the @@ -1173,6 +1184,16 @@ The device used by pppd with this option must have sync support. Currently supports Microgate SyncLink adapters under Linux and FreeBSD 2.2.8 and later. .TP +.B tls-verify-method \fIstring +(EAP-TLS, or PEAP) Match the value specified for \fIremotename\fR to that that +of the X509 certificates subject name, common name, or suffix of the common +name. Respective values allowed for this option is: \fInone\fR, \fIsubject\fR, +\fIname\fR, or \fIsuffix\fR. The default value for this option is \fIname\fR. +.TP +.B tls-verify-key-usage +(EAP-TLS, or PEAP) Enables examination of peer certificate's purpose, and +extended key usage attributes. +.TP .B unit \fInum Sets the ppp unit number (for a ppp0 or ppp1 etc interface name) for outbound connections. If the unit is already in use a dynamically allocated number will diff --git a/pppd/pppd.h b/pppd/pppd.h index ba62ca5..ab8f674 100644 --- a/pppd/pppd.h +++ b/pppd/pppd.h @@ -336,19 +336,25 @@ extern bool dump_options; /* print out option values */ extern bool dryrun; /* check everything, print options, exit */ extern int child_wait; /* # seconds to wait for children at end */ -#ifdef USE_EAPTLS +#if defined(USE_EAPTLS) || defined(USE_PEAP) #define TLS_VERIFY_NONE "none" #define TLS_VERIFY_NAME "name" #define TLS_VERIFY_SUBJECT "subject" #define TLS_VERIFY_SUFFIX "suffix" -extern char *crl_dir; -extern char *crl_file; -extern char *pkcs12_file; +extern char *crl_dir; +extern char *crl_file; +extern char *ca_path; +extern char *cacert_file; + extern char *max_tls_version; extern bool tls_verify_key_usage; extern char *tls_verify_method; +#endif /* USE_EAPTLS || USE_PEAP */ + +#ifdef USE_EAPTLS +extern char *pkcs12_file; #endif /* USE_EAPTLS */ #ifdef MAXOCTETS diff --git a/pppd/tls.c b/pppd/tls.c new file mode 100644 index 0000000..28c2e57 --- /dev/null +++ b/pppd/tls.c @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2021 Eivind Næss. 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 "pppd.h" +#include "tls.h" + +/** + * Structure used in verifying the peer certificate + */ +struct tls_info +{ + char *peer_name; + X509 *peer_cert; + bool client; +}; + + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + +/* + * OpenSSL 1.1+ introduced a generic TLS_method() + * For older releases we substitute the appropriate method + */ +#define TLS_method SSLv23_method + +#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 */ + + +/* + * 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. + */ +static int tls_verify_callback(int ok, X509_STORE_CTX *ctx) +{ + char subject[256]; + char cn_str[256]; + X509 *peer_cert; + int err, depth; + SSL *ssl; + struct tls_info *inf; + char *ptr1 = NULL, *ptr2 = NULL; + + 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()); + + inf = (struct tls_info*) SSL_get_ex_data(ssl, 0); + if (inf == NULL) { + error("Error: SSL_get_ex_data returned NULL"); + return 0; + } + + tls_log_sslerr(); + + if (!depth) + { + /* Verify certificate based on certificate type and extended key usage */ + if (tls_verify_key_usage) { + int purpose = inf->client ? X509_PURPOSE_SSL_SERVER : X509_PURPOSE_SSL_CLIENT ; + if (X509_check_purpose(peer_cert, purpose, 0) == 0) { + error("Certificate verification error: nsCertType mismatch"); + return 0; + } + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + int flags = inf->client ? XKU_SSL_SERVER : XKU_SSL_CLIENT; + if (!(X509_get_extended_key_usage(peer_cert) & flags)) { + error("Certificate verification error: invalid extended key usage"); + return 0; + } +#endif + info("Certificate key usage: OK"); + } + + /* + * If acting as client and the name of the server wasn't specified + * explicitely, we can't verify the server authenticity + */ + if (!tls_verify_method) + tls_verify_method = TLS_VERIFY_NONE; + + if (!inf->peer_name || !strcmp(TLS_VERIFY_NONE, tls_verify_method)) { + warn("Certificate verication disabled or no peer name was specified"); + return ok; + } + + /* 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); + + /* Verify based on subject name */ + ptr1 = inf->peer_name; + if (!strcmp(TLS_VERIFY_SUBJECT, tls_verify_method)) { + ptr2 = subject; + } + + /* Verify based on common name (default) */ + if (strlen(tls_verify_method) == 0 || + !strcmp(TLS_VERIFY_NAME, tls_verify_method)) { + ptr2 = cn_str; + } + + /* Match the suffix of common name */ + if (!strcmp(TLS_VERIFY_SUFFIX, tls_verify_method)) { + int len = strlen(ptr1); + int off = strlen(cn_str) - len; + ptr2 = cn_str; + if (off > 0) { + ptr2 = cn_str + off; + } + } + + if (strcmp(ptr1, ptr2)) { + error("Certificate verification error: CN (%s) != %s", ptr1, ptr2); + return 0; + } + + if (inf->peer_cert) { + if (X509_cmp(inf->peer_cert, peer_cert) != 0) { + error("Peer certificate doesn't match stored certificate"); + return 0; + } + } + + info("Certificate CN: %s, peer name %s", cn_str, inf->peer_name); + } + + return ok; +} + +int tls_init() +{ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + SSL_library_init(); + SSL_load_error_strings(); +#endif + return 0; +} + +int tls_set_verify(SSL_CTX *ctx, int depth) +{ + SSL_CTX_set_verify_depth(ctx, depth); + SSL_CTX_set_verify(ctx, + SSL_VERIFY_PEER | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + &tls_verify_callback); + return 0; +} + +int tls_set_verify_info(SSL *ssl, const char *peer_name, const char *peer_cert, + bool client, struct tls_info **out) +{ + if (out != NULL) { + struct tls_info *tmp = calloc(sizeof(struct tls_info), 1); + if (!tmp) { + fatal("Allocation error"); + } + + tmp->client = client; + if (peer_name) { + tmp->peer_name = strdup(peer_name); + } + + if (peer_cert && strlen(peer_cert) > 0) { + FILE *fp = fopen(peer_cert, "r"); + if (fp) { + tmp->peer_cert = PEM_read_X509(fp, NULL, NULL, NULL); + fclose(fp); + } + + if (!tmp->peer_cert) { + error("EAP-TLS: Error loading client certificate from file %s", + peer_cert); + tls_free_verify_info(&tmp); + return -1; + } + } + + SSL_set_ex_data(ssl, 0, tmp); + *out = tmp; + return 0; + } + + return -1; +} + +void tls_free_verify_info(struct tls_info **in) { + if (in && *in) { + struct tls_info *tmp = *in; + if (tmp->peer_name) { + free(tmp->peer_name); + } + if (tmp->peer_cert) { + X509_free(tmp->peer_cert); + } + free(tmp); + *in = NULL; + } +} + +const SSL_METHOD* tls_method() { + return TLS_method(); +} + +int tls_set_version(SSL_CTX *ctx, const char *max_version) +{ +#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 + + /* As EAP-TLS+TLSv1.3 is highly experimental we offer the user a chance to override */ + if (max_version) { + if (strncmp(max_version, "1.0", 3) == 0) { + tls_version = TLS1_VERSION; + } + else if (strncmp(max_version, "1.1", 3) == 0) { + tls_version = TLS1_1_VERSION; + } + else if (strncmp(max_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_version, "1.3", 3) == 0) { +#ifdef TLS1_3_VERSION + tls_version = TLS1_3_VERSION; +#else + warn("TLSv1.3 not available."); +#endif + } + } + + dbglog("Setting max protocol version to 0x%X", tls_version); + if (!SSL_CTX_set_max_proto_version(ctx, tls_version)) { + error("Could not set max protocol version"); + return -1; + } + + return 0; +} + +int tls_set_opts(SSL_CTX *ctx) { + + /* 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 + | SSL_OP_NO_COMPRESSION + ); + + /* 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"); + */ + return 0; +} + +int tls_set_crl(SSL_CTX *ctx, const char *crl_dir, const char *crl_file) +{ + X509_STORE *certstore = NULL; + X509_LOOKUP *lookup = NULL; + FILE *fp = NULL; + int status = -1; + + if (crl_dir) { + if (!(certstore = SSL_CTX_get_cert_store(ctx))) { + error("Failed to get certificate store"); + goto done; + } + + if (!(lookup = + X509_STORE_add_lookup(certstore, X509_LOOKUP_hash_dir()))) { + error("Store lookup for CRL failed"); + goto done; + } + + X509_LOOKUP_add_dir(lookup, crl_dir, X509_FILETYPE_PEM); + X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK); + } + + if (crl_file) { + X509_CRL *crl = NULL; + + fp = fopen(crl_file, "r"); + if (!fp) { + error("Cannot open CRL file '%s'", crl_file); + goto done; + } + + crl = PEM_read_X509_CRL(fp, NULL, NULL, NULL); + if (!crl) { + error("Cannot read CRL file '%s'", crl_file); + goto done; + } + + if (!(certstore = SSL_CTX_get_cert_store(ctx))) { + error("Failed to get certificate store"); + goto done; + } + if (!X509_STORE_add_crl(certstore, crl)) { + error("Cannot add CRL to certificate store"); + goto done; + } + X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK); + } + + status = 0; + +done: + + if (fp != NULL) { + fclose(fp); + } + + return status; +} + +int tls_set_ca(SSL_CTX *ctx, const char *ca_dir, const char *ca_file) +{ + if (ca_file && strlen(ca_file) == 0) { + ca_file = NULL; + } + + if (ca_dir && strlen(ca_dir) == 0) { + ca_dir = NULL; + } + + if (!SSL_CTX_load_verify_locations(ctx, ca_file, ca_dir)) { + + error("Cannot load verify locations"); + if (ca_file) { + dbglog("CA certificate file = [%s]", ca_file); + } + + if (ca_dir) { + dbglog("CA certificate path = [%s]", ca_dir); + } + + return -1; + } + + return 0; +} + +void tls_log_sslerr( 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(); + } +} + diff --git a/pppd/tls.h b/pppd/tls.h new file mode 100644 index 0000000..39fdef7 --- /dev/null +++ b/pppd/tls.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021 Eivind Næss. 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. + */ + +#ifndef TLS_H +#define TLS_H + +/** + * Structure used in verifying the peer certificate + */ +struct tls_info; + +/** + * Initialize the SSL library + */ +int tls_init(); + +/** + * Get the SSL_METHOD + */ +const SSL_METHOD* tls_method(); + +/** + * Configure the SSL options + */ +int tls_set_opts(SSL_CTX *ctx); + +/** + * Configure the SSL context's max TLS version + */ +int tls_set_version(SSL_CTX *ctx, const char *max_version); + +/** + * Configure the SSL context's verify callback + */ +int tls_set_verify(SSL_CTX *ctx, int depth); + +/** + * Configure the SSL verify information + */ +int tls_set_verify_info(SSL *ssl, const char *peer_name, const char *peer_cert_file, + bool client, struct tls_info **out); + +/** + * Free the tls_info structure and it's members + */ +void tls_free_verify_info(struct tls_info **in); + +/** + * Configure the SSL context's CRL details + */ +int tls_set_crl(SSL_CTX *ctx, const char *crl_dir, const char *crl_file); + +/** + * Configure the SSL context's CA verify locations + */ +int tls_set_ca(SSL_CTX *ctx, const char *ca_dir, const char *ca_file); + +/** + * Log all errors from ssl library + */ +void tls_log_sslerr( void ); + +#endif /* TLS_H */ -- 2.39.2