From b2a4275ba78b07a4e47dcefb8c9bf2fd8456184a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Eivind=20N=C3=A6ss?= Date: Sun, 14 Mar 2021 16:17:41 -0700 Subject: [PATCH] pppd: EAP-TLS: Verify Subject or CommonName by suffix (#261) This feature matches closely what OpenVPN and the network-manager-openvpn plugin do for certificate verification. It allows the end user to configure the certificate to be matched by its common name (entire string), its subject name, or the suffix of a subject name. The latter is especially useful if you are trying to match against a random server in a RADIUS pool. Lastly, it also allows you to turn off the certificate matching altogether. tls-verify-method can have the following parameter values: - none - subject - The entire subject, e.g. /CN=some.server.org - name - The entire common name, e.g. some.server.org - suffix - The latter part of a name, e.g. servers.org Secondly, it also introduces a new parameter 'tls-verify-key-usage' which permits checking of the 'server' or 'client' side attributes of nsCertType and X509 extended key attributes. For example, in client mode, it will verify that received certificate has the 'server' side attributes enabled. Signed-off-by: Eivind Naess --- README.eap-tls | 5 +++- pppd/auth.c | 6 +++++ pppd/eap-tls.c | 71 ++++++++++++++++++++++++++++++++++++++------------ pppd/eap-tls.h | 1 + pppd/pppd.h | 8 ++++++ 5 files changed, 73 insertions(+), 18 deletions(-) diff --git a/README.eap-tls b/README.eap-tls index ab3794e..bc1066e 100644 --- a/README.eap-tls +++ b/README.eap-tls @@ -126,7 +126,7 @@ EAP-TLS authentication support for PPP ca Use the CA public certificate found in in PEM format - ca-path + capath Use the directory as the CA public certificate directory cert Use the client public certificate found in in PEM format @@ -147,6 +147,9 @@ EAP-TLS authentication support for PPP max-tls-version <1.0|1.1|1.2 (default)|1.3> Specify the maximum TLS protocol version to negotiate with peers. Defaults to TLSv1.2 as the TLSv1.3 code is experimental. + verify-tls-peer + Compare the remotename against the subject, certificate name, or + match by suffix. Default is 'name'. Note: password-encrypted certificates can be used as of v0.94 of this diff --git a/pppd/auth.c b/pppd/auth.c index b1271c4..6ccdbf8 100644 --- a/pppd/auth.c +++ b/pppd/auth.c @@ -258,6 +258,8 @@ char *privkey_file = NULL; /* client private key file (pem 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 */ #endif @@ -445,6 +447,10 @@ option_t auth_options[] = { { "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)" }, { "need-peer-eap", o_bool, &need_peer_eap, "Require the peer to authenticate us", 1 }, #endif /* USE_EAPTLS */ diff --git a/pppd/eap-tls.c b/pppd/eap-tls.c index 5740f30..4759764 100644 --- a/pppd/eap-tls.c +++ b/pppd/eap-tls.c @@ -64,7 +64,7 @@ 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 *); +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); @@ -733,6 +733,7 @@ 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)"); @@ -824,6 +825,7 @@ 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 @@ -1146,6 +1148,7 @@ int ssl_verify_callback(int ok, X509_STORE_CTX * ctx) 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); @@ -1181,34 +1184,68 @@ int ssl_verify_callback(int ok, X509_STORE_CTX * ctx) if (!depth) { - /* This is the peer certificate */ - - X509_NAME_oneline(X509_get_subject_name(peer_cert), - subject, 256); + /* 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; + } - X509_NAME_get_text_by_NID(X509_get_subject_name(peer_cert), - NID_commonName, cn_str, 256); +#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 (!ets->peer[0]) { - warn("Peer name not specified: no check"); + if (!ets->peer[0] || !strcmp(tls_verify_method, TLS_VERIFY_NONE)) { + warn("Certificate verication disabled or no peer name was specified"); return ok; } - /* - * Check the CN - */ - if (strcmp(cn_str, ets->peer)) { - error - ("Certificate verification error: CN (%s) != peer_name (%s)", - cn_str, ets->peer); + /* 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; } - warn("Certificate CN: %s , peer name %s", cn_str, ets->peer); + info("Certificate CN: %s, peer name %s", cn_str, ets->peer); /* * If a peer certificate file was specified, here we check it diff --git a/pppd/eap-tls.h b/pppd/eap-tls.h index cdbc9e4..c74a831 100644 --- a/pppd/eap-tls.h +++ b/pppd/eap-tls.h @@ -65,6 +65,7 @@ struct eaptls_session char rtx[EAP_TLS_MAX_LEN]; /* retransmission buffer */ int rtx_len; int mtu; /* unit mtu */ + bool client; }; diff --git a/pppd/pppd.h b/pppd/pppd.h index 612902f..05e8e37 100644 --- a/pppd/pppd.h +++ b/pppd/pppd.h @@ -336,9 +336,17 @@ extern bool dryrun; /* check everything, print options, exit */ extern int child_wait; /* # seconds to wait for children at end */ #ifdef USE_EAPTLS + +#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 *max_tls_version; +extern bool tls_verify_key_usage; +extern char *tls_verify_method; #endif /* USE_EAPTLS */ #ifdef MAXOCTETS -- 2.39.2