From patchwork Tue Apr 17 06:50:00 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Jason A. Donenfeld" X-Patchwork-Id: 304 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director10.mail.ord1d.rsapps.net ([172.30.191.6]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id 8GJwA/kl1lpEYwAAIUCqbw for ; Tue, 17 Apr 2018 12:51:05 -0400 Received: from proxy20.mail.ord1d.rsapps.net ([172.30.191.6]) by director10.mail.ord1d.rsapps.net (Dovecot) with LMTP id GVByGfkl1lpnCwAApN4f7A ; Tue, 17 Apr 2018 12:51:05 -0400 Received: from smtp26.gate.ord1d ([172.30.191.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy20.mail.ord1d.rsapps.net with LMTP id UBMtLvkl1loXBgAAsk8m8w ; Tue, 17 Apr 2018 12:51:05 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp26.gate.ord1d.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dkim=fail (signature verification failed) header.d=zx2c4.com; dmarc=fail (p=none; dis=none) header.from=zx2c4.com X-Suspicious-Flag: YES X-Classification-ID: 842daed2-425f-11e8-afa9-525400c5b129-1-1 Received: from [216.105.38.7] ([216.105.38.7:39577] helo=lists.sourceforge.net) by smtp26.gate.ord1d.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 49/6C-23050-8F526DA5; Tue, 17 Apr 2018 12:51:04 -0400 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1f8To8-0006DN-Cz; Tue, 17 Apr 2018 16:50:16 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1f8To7-0006DF-4N for openvpn-devel@lists.sourceforge.net; Tue, 17 Apr 2018 16:50:15 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To: MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=08QCWteMv3bbkBsK+G4OzN1R3v7Amuf8bHX5sqTAvzg=; b=SoOzRzNIZsEGVk8Xn3mkQleWOI VdGKBKf81o2S0nqtuppX5n5L8+xnqGSA+pNMYUZMuNKtXdnqQoAsomSZYVZO0yoraoTrFVMZP0QWM UhLdV3wXdqAFbf466YQj5L2jXhmRRuSSdCW0qKj8GPIBMOktTsWpMKvvEZi6xGplrYDA=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:MIME-Version: Content-Type:Content-Transfer-Encoding:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=08QCWteMv3bbkBsK+G4OzN1R3v7Amuf8bHX5sqTAvzg=; b=mLGSNFGCd72JaMEt3tIufQQdkf ViR+/nHbYmd6HyuntHECGv+fxGoiMFzEACCuMrCZ1KKnk1a3HIFaDXjZdgFmXOQrvhcvs6HR4ZHjn kyQLZSbh26waXBKNhb2cF8jGC/euY2ZVoe8s5fwLWwDJ2bsTFjlpxrke3bezQ86zxKFA=; Received: from frisell.zx2c4.com ([192.95.5.64]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) id 1f8To5-00HThB-8H for openvpn-devel@lists.sourceforge.net; Tue, 17 Apr 2018 16:50:14 +0000 Received: by frisell.zx2c4.com (ZX2C4 Mail Server) with ESMTP id b1b44d18; Tue, 17 Apr 2018 16:26:35 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=zx2c4.com; h=from:to:cc :subject:date:message-id; s=mail; bh=HYsVbNCq6uFXhheVmoH7tFTf9TM =; b=ZIDEM6dsxlBJu03QotemwXV2FUppc7Q5Tm+B/eaf7TTVkDn6eqIOxtE5wwj DmWWAPc0+p+43wMC0Zf/s/5I8DOsNPgI00QNXWhywxEj5lr6Df7MuvJcSY1sN47x 7019cknQJ/+rhww9DDRXw9MUF2vL8p9WrwnMn0bnzzovZR/zhvc3qPgH5rHEmw+T qulp7pq9Y3C4+lRSnFHctREO0HsF5flnarDzN/n88Y6zncOUQsLzZGx1yqjvJb/o DxezQsKQkUY49q+lNN0c6rFPRYVeuooAgLjxn4rA6Z3CjpomLD44Vlv2NbqV3eU5 VVBKVXTxUV8Mi0eH7Jo6+mpTnCg== Received: by frisell.zx2c4.com (ZX2C4 Mail Server) with ESMTPSA id 4df4d685 (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256:NO); Tue, 17 Apr 2018 16:26:34 +0000 (UTC) From: "Jason A. Donenfeld" To: openvpn-devel@lists.sourceforge.net Date: Tue, 17 Apr 2018 18:50:00 +0200 Message-Id: <20180417165000.3872-1-Jason@zx2c4.com> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, no trust [192.95.5.64 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature X-Headers-End: 1f8To5-00HThB-8H Subject: [Openvpn-devel] [PATCH] Support fingerprint authentication X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox OpenVPN traditionally works around CAs. However many TLS-based protocols also allow an alternative simpler mode in which rather than verify certificates against CAs, the certificate itself is hashed and compared against a pre-known set of acceptable hashes. This is usually referred to as "fingerprint verification". It's popular across SMTP servers, IRC servers, XMPP servers, and even in the context of HTTP with pinning. So, I'd like to propose an extremely simple and non-invasive way of supporting this in OpenVPN, by re-using several features that already basically support it. Namely, what I propose is: * Allow specifying 'none' to the --ca parameter, to specify that certificates should not be checked against a CA. Note that 'none' is already used in other similar options as a special placeholder. * When '--ca none' is in use, --verify-hash checks all depths instead of just level 1. With these very simple changes, fingerprint authentication is easily achieved via the --tls-verify script on the server and via --verify-hash on the client. I've included some instructions on how to use all of this. Server side: ============ Make self-signed cert: $ openssl req -x509 -newkey ec:<(openssl ecparam -name secp384r1) -keyout key.pem -out cert.pem -nodes -sha256 -days 3650 -subj '/CN=server' Record our fingerprint in an environment variable for the client to use later: $ server_fingerprint="$(openssl x509 -in cert.pem -noout -sha256 -fingerprint | sed 's/.*=//;s/\(.*\)/\L\1/')" Start openvpn with tls verify script: $ sudo openvpn --server 10.66.0.0 255.255.255.0 --dev tun --dh none --ca none --cert cert.pem --key key.pem --tls-verify $(readlink -f tls-verify.sh) --script-security 2 TLS Verify Script: ================== #!/bin/sh [ -n "$tls_digest_sha256_0" -a -e "/tmp/allowed-openvpn-fingerprints/$tls_digest_sha256_0" ] Client side: ============ Make self-signed cert: $ openssl req -x509 -newkey ec:<(openssl ecparam -name secp384r1) -keyout key.pem -out cert.pem -nodes -sha256 -days 3650 -subj '/CN=client' "Tell" the server about our fingerprint: $ mkdir -p /tmp/allowed-openvpn-fingerprints; touch "/tmp/allowed-openvpn-fingerprints/$(openssl x509 -in cert.pem -noout -sha256 -fingerprint | sed 's/.*=//;s/\(.*\)/\L\1/')" Start openvpn with server fingerprint verification: $ sudo openvpn --client --remote 127.0.0.1 --dev tun --ca none --cert cert.pem --key key.pem --verify-hash "$server_fingerprint" SHA256 --nobind Signed-off-by: Jason A. Donenfeld --- Sorry for the double post. Somebody suggested I submit this as an actual git patch instead of as just part of the goofy email I sent earlier. So here's that email turned into a proper patch that you can apply. Feel free to hack the patch to pieces or discard it and start over. src/openvpn/init.c | 1 + src/openvpn/options.c | 9 ++++++++- src/openvpn/options.h | 1 + src/openvpn/ssl.c | 2 +- src/openvpn/ssl_common.h | 1 + src/openvpn/ssl_verify.c | 2 +- src/openvpn/ssl_verify_mbedtls.c | 2 +- src/openvpn/ssl_verify_openssl.c | 2 +- 8 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 36c1a4c4..4da994d8 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -2722,6 +2722,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) to.remote_cert_eku = options->remote_cert_eku; to.verify_hash = options->verify_hash; to.verify_hash_algo = options->verify_hash_algo; + to.ca_file_none = options->ca_file_none; #ifdef ENABLE_X509ALTUSERNAME to.x509_username_field = (char *) options->x509_username_field; #else diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 426057ab..dad706d9 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -3259,7 +3259,10 @@ options_postprocess_filechecks(struct options *options) /* ** SSL/TLS/crypto related files ** */ errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->dh_file, R_OK, "--dh"); - errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->ca_file, R_OK, "--ca"); + if (!options->ca_file_none) + { + errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->ca_file, R_OK, "--ca"); + } errs |= check_file_access_chroot(options->chroot_dir, CHKACC_FILE, options->ca_path, R_OK, "--capath"); errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->cert_file, R_OK, "--cert"); errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE, options->extra_certs_file, R_OK, @@ -7638,6 +7641,10 @@ add_option(struct options *options, { options->ca_file_inline = p[2]; } + else if (streq(p[1], "none")) + { + options->ca_file_none = true; + } } #ifndef ENABLE_CRYPTO_MBEDTLS else if (streq(p[0], "capath") && p[1] && !p[2]) diff --git a/src/openvpn/options.h b/src/openvpn/options.h index f7d0145a..6ccdca49 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -489,6 +489,7 @@ struct options /* TLS (control channel) parms */ bool tls_server; bool tls_client; + bool ca_file_none; const char *ca_file; const char *ca_path; const char *dh_file; diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 669f941b..0ef0f31f 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -693,7 +693,7 @@ init_ssl(const struct options *options, struct tls_root_ctx *new_ctx) } } - if (options->ca_file || options->ca_path) + if ((!options->ca_file_none && options->ca_file) || options->ca_path) { tls_ctx_load_ca(new_ctx, options->ca_file, options->ca_file_inline, options->ca_path, options->tls_server); diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 08ef6ffa..e9ac5271 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -270,6 +270,7 @@ struct tls_options uint8_t *verify_hash; hash_algo_type verify_hash_algo; char *x509_username_field; + bool ca_file_none; /* allow openvpn config info to be * passed over control channel */ diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c index 25395b27..61873e6f 100644 --- a/src/openvpn/ssl_verify.c +++ b/src/openvpn/ssl_verify.c @@ -720,7 +720,7 @@ verify_cert(struct tls_session *session, openvpn_x509_cert_t *cert, int cert_dep } /* verify level 1 cert, i.e. the CA that signed our leaf cert */ - if (cert_depth == 1 && opt->verify_hash) + if ((opt->ca_file_none || cert_depth == 1) && opt->verify_hash) { struct buffer ca_hash = {0}; diff --git a/src/openvpn/ssl_verify_mbedtls.c b/src/openvpn/ssl_verify_mbedtls.c index fd31bbbd..147fb983 100644 --- a/src/openvpn/ssl_verify_mbedtls.c +++ b/src/openvpn/ssl_verify_mbedtls.c @@ -63,7 +63,7 @@ verify_callback(void *session_obj, mbedtls_x509_crt *cert, int cert_depth, cert_hash_remember(session, cert_depth, &cert_fingerprint); /* did peer present cert which was signed by our root cert? */ - if (*flags != 0) + if (*flags != 0 && !session->opt->ca_file_none) { int ret = 0; char errstr[512] = { 0 }; diff --git a/src/openvpn/ssl_verify_openssl.c b/src/openvpn/ssl_verify_openssl.c index 9b984751..10d351f4 100644 --- a/src/openvpn/ssl_verify_openssl.c +++ b/src/openvpn/ssl_verify_openssl.c @@ -66,7 +66,7 @@ verify_callback(int preverify_ok, X509_STORE_CTX *ctx) cert_hash_remember(session, X509_STORE_CTX_get_error_depth(ctx), &cert_hash); /* did peer present cert which was signed by our root cert? */ - if (!preverify_ok) + if (!preverify_ok && !session->opt->ca_file_none) { /* get the X509 name */ char *subject = x509_get_subject(current_cert, &gc);