From patchwork Fri Dec 8 01:07:41 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 145 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director2.mail.ord1d.rsapps.net ([172.28.255.1]) by backend31.mail.ord1d.rsapps.net (Dovecot) with LMTP id 4/gIAPGAKlr/DwAAgoeIoA for ; Fri, 08 Dec 2017 07:09:21 -0500 Received: from proxy2.mail.ord1c.rsapps.net ([172.28.255.1]) by director2.mail.ord1d.rsapps.net (Dovecot) with LMTP id ODIEAPGAKlpzewAAgYhSiA ; Fri, 08 Dec 2017 07:09:21 -0500 Received: from smtp50.gate.ord1a ([172.28.255.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy2.mail.ord1c.rsapps.net (Dovecot) with LMTP id uSu9LPCAKlpnVwAA311kuQ ; Fri, 08 Dec 2017 07:09:20 -0500 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.34.181.88] Authentication-Results: smtp50.gate.ord1a.rsapps.net; iprev=pass policy.iprev="216.34.181.88"; 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; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Classification-ID: 95cf4a34-dc10-11e7-9176-842b2b5a4a0a-1-1 Received: from [216.34.181.88] ([216.34.181.88:57594] helo=lists.sourceforge.net) by smtp50.gate.ord1a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 1D/55-29852-0E08A2A5; Fri, 08 Dec 2017 07:09:05 -0500 Received: from localhost ([127.0.0.1] helo=sfs-ml-1.v29.ch3.sourceforge.com) by sfs-ml-1.v29.ch3.sourceforge.com with esmtp (Exim 4.89) (envelope-from ) id 1eNHRx-0000TZ-B9; Fri, 08 Dec 2017 12:08:17 +0000 Received: from sfi-mx-4.v28.ch3.sourceforge.com ([172.29.28.194] helo=mx.sourceforge.net) by sfs-ml-1.v29.ch3.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.89) (envelope-from ) id 1eNHRv-0000TM-Sf for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:15 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Type:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:CC:To:From:Sender:Reply-To:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=j1AVJBaEXBDtNc24ZuRdnaHej2p/Z8X7409IRajmbDU=; b=TRE//VUrwhyLkWWOaMPW867Gl7 l4oQOZWSqAz5zAC0S8yHdt4GuC9GDGo6FnO+K49mJW8h75QEyviAX0NUsn9Mdbvsoer84FD85CjcM wyT74YBQ2lYkYK8MBfM+B7yTI9ny4UGmPCXx6W/V2NOJDIgywGmKrLnpbYSCLmNN09aU=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject: CC:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=j1AVJBaEXBDtNc24ZuRdnaHej2p/Z8X7409IRajmbDU=; b=kGzlbMnOcRjJ1zV6AfOLvBfp4n y8LODPRq8TUWEOmEdKc4vhdlDkxJnMQ/JPtWdtQzfpRbO1Ul/+wcaD1uROKrl2q3ZbmXkDagVB8Vy F+6T+1l0aCBwwSZhtLL/bRm6sxLnKKoTInEEZQgfQTC/GCF0/mYJWSykiLI3bLbA6tzE=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-4.v28.ch3.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.89) id 1eNHRt-0000Vp-9R for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:15 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id D817C1C5237 for ; Fri, 8 Dec 2017 13:08:06 +0100 (CET) Received: from steffan-fox.fox.local (172.16.5.166) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Fri, 8 Dec 2017 13:08:06 +0100 From: Steffan Karger To: Date: Fri, 8 Dec 2017 13:07:41 +0100 Message-ID: <1512734870-17133-2-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> MIME-Version: 1.0 X-ClientProxiedBy: FOXDFT52.FOX.local (10.0.0.129) To FOXDFT52.FOX.local (10.0.0.129) X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -0.0 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1eNHRt-0000Vp-9R Subject: [Openvpn-devel] [PATCH 01/10] Add crypto_pem_{encode,decode}() 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: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox Needed for tls-crypt-v2, but isolated enough to be reviewed as a separate patch. The encode API allocates memory, because it fits our typical gc-oriented code pattern and the caller does not have to do multiple calls or calculations to determine the required destination buffer size. The decode API does not allocate memory, because the required destination buffer is always smaller than the input buffer (so is easy to manage by the caller) and does not force the caller to use the heap. Signed-off-by: Steffan Karger --- src/openvpn/crypto_backend.h | 30 +++++++++++ src/openvpn/crypto_mbedtls.c | 74 +++++++++++++++++++++++++++ src/openvpn/crypto_openssl.c | 82 ++++++++++++++++++++++++++++++ tests/unit_tests/openvpn/Makefile.am | 16 +++++- tests/unit_tests/openvpn/test_crypto.c | 92 ++++++++++++++++++++++++++++++++++ 5 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/openvpn/test_crypto.c diff --git a/src/openvpn/crypto_backend.h b/src/openvpn/crypto_backend.h index 567fd9b..83e14c8 100644 --- a/src/openvpn/crypto_backend.h +++ b/src/openvpn/crypto_backend.h @@ -36,6 +36,7 @@ #include "crypto_mbedtls.h" #endif #include "basic.h" +#include "buffer.h" /* TLS uses a tag of 128 bytes, let's do the same for OpenVPN */ #define OPENVPN_AEAD_TAG_LENGTH 16 @@ -105,6 +106,35 @@ void show_available_digests(void); void show_available_engines(void); +/** + * Encode binary data as PEM + * + * @param name The name to use in the PEM header/footer + * @param dst Destination buffer for PEM-encoded data. Must be a valid + * pointer to an uninitialized buffer structure. Iff this + * function returns true, the buffer will contain memory + * allocated through the supplied gc. + * @param src Source buffer + * @param gc The garbage collector to use when allocating memory for + * dst. + * + * @return true iff PEM encode succeeded. + */ +bool crypto_pem_encode(const char *name, struct buffer *dst, + const struct buffer *src, struct gc_arena *gc); + +/** + * Decode a PEM buffer to binary data + * + * @param name The name expected in the PEM header/footer + * @param dst Destination buffer for decoded data + * @param src Source buffer (PEM data) + * + * @return true iff PEM decode succeeded. + */ +bool crypto_pem_decode(const char *name, struct buffer *dst, + const struct buffer *src); + /* * * Random number functions, used in cases where we want diff --git a/src/openvpn/crypto_mbedtls.c b/src/openvpn/crypto_mbedtls.c index 8fa03da..1e86854 100644 --- a/src/openvpn/crypto_mbedtls.c +++ b/src/openvpn/crypto_mbedtls.c @@ -44,11 +44,13 @@ #include "otime.h" #include "misc.h" +#include #include #include #include #include #include +#include #include @@ -229,6 +231,78 @@ show_available_engines(void) "available\n"); } +bool +crypto_pem_encode(const char *name, struct buffer *dst, + const struct buffer *src, struct gc_arena *gc) +{ + /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */ + char header[1000+1] = { 0 }; + char footer[1000+1] = { 0 }; + + if (!openvpn_snprintf(header, sizeof(header), "-----BEGIN %s-----\n", name)) + { + return false; + } + if (!openvpn_snprintf(footer, sizeof(footer), "-----END %s-----\n", name)) + { + return false; + } + + size_t out_len = 0; + if (MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL != + mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src), + NULL, 0, &out_len)) + { + return false; + } + + *dst = alloc_buf_gc(out_len, gc); + if (!mbed_ok(mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src), + BPTR(dst), BCAP(dst), &out_len)) + || !buf_inc_len(dst, out_len)) + { + return false; + } + + return true; +} + +bool +crypto_pem_decode(const char *name, struct buffer *dst, + const struct buffer *src) +{ + /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */ + char header[1000+1] = { 0 }; + char footer[1000+1] = { 0 }; + + if (*(BLAST(src)) != '\0') + { + msg(M_WARN, "PEM decode error: source buffer not null-terminated"); + return false; + } + if (!openvpn_snprintf(header, sizeof(header), "-----BEGIN %s-----", name)) + { + return false; + } + if (!openvpn_snprintf(footer, sizeof(footer), "-----END %s-----", name)) + { + return false; + } + + size_t use_len = 0; + mbedtls_pem_context ctx = { 0 }; + bool ret = mbed_ok(mbedtls_pem_read_buffer(&ctx, header, footer, BPTR(src), + NULL, 0, &use_len)); + if (ret && !buf_write(dst, ctx.buf, ctx.buflen)) + { + ret = false; + msg(M_WARN, "PEM decode error: destination buffer too small"); + } + + mbedtls_pem_free(&ctx); + return ret; +} + /* * * Random number functions, used in cases where we want diff --git a/src/openvpn/crypto_openssl.c b/src/openvpn/crypto_openssl.c index 20a519e..49d3aeb 100644 --- a/src/openvpn/crypto_openssl.c +++ b/src/openvpn/crypto_openssl.c @@ -387,6 +387,88 @@ show_available_engines(void) #endif } + +bool +crypto_pem_encode(const char *name, struct buffer *dst, + const struct buffer *src, struct gc_arena *gc) +{ + bool ret = false; + BIO *bio = BIO_new(BIO_s_mem()); + if (!PEM_write_bio(bio, name, "", BPTR(src), BLEN(src))) + { + ret = false; + goto cleanup; + } + + BUF_MEM *bptr; + BIO_get_mem_ptr(bio, &bptr); + + *dst = alloc_buf_gc(bptr->length, gc); + ASSERT(buf_write(dst, bptr->data, bptr->length)); + + ret = true; +cleanup: + if (!BIO_free(bio)) + { + ret = false;; + } + + return ret; +} + +bool +crypto_pem_decode(const char *name, struct buffer *dst, + const struct buffer *src) +{ + bool ret = false; + BIO *bio; + + if (!(bio = BIO_new_mem_buf((char *)BPTR(src), BLEN(src)))) + { + crypto_msg(M_FATAL, "Cannot open memory BIO for PEM decode"); + } + + char *name_read = NULL; + char *header_read = NULL; + uint8_t *data_read = NULL; + long data_read_len = 0; + if (!PEM_read_bio(bio, &name_read, &header_read, &data_read, + &data_read_len)) + { + dmsg(D_CRYPT_ERRORS, "%s: PEM decode failed", __func__); + goto cleanup; + } + + if (strcmp(name, name_read)) + { + dmsg(D_CRYPT_ERRORS, + "%s: unexpected PEM name (got '%s', expected '%s')", + __func__, name_read, name); + goto cleanup; + } + + uint8_t *dst_data = buf_write_alloc(dst, data_read_len); + if (!dst_data) + { + dmsg(D_CRYPT_ERRORS, "%s: dst too small (%i, needs %li)", __func__, + BCAP(dst), data_read_len); + goto cleanup; + } + memcpy(dst_data, data_read, data_read_len); + + ret = true; +cleanup: + OPENSSL_free(name_read); + OPENSSL_free(header_read); + OPENSSL_free(data_read); + if (!BIO_free(bio)) + { + ret = false;; + } + + return ret; +} + /* * * Random number functions, used in cases where we want diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index 23d758b..d100c21 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -6,7 +6,7 @@ if HAVE_LD_WRAP_SUPPORT check_PROGRAMS += argv_testdriver buffer_testdriver endif -check_PROGRAMS += packet_id_testdriver tls_crypt_testdriver +check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver TESTS = $(check_PROGRAMS) @@ -29,6 +29,20 @@ buffer_testdriver_SOURCES = test_buffer.c mock_msg.c \ $(openvpn_srcdir)/buffer.c \ $(openvpn_srcdir)/platform.c +crypto_testdriver_CFLAGS = @TEST_CFLAGS@ \ + -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \ + $(OPTIONAL_CRYPTO_CFLAGS) +crypto_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ + $(OPTIONAL_CRYPTO_LIBS) +crypto_testdriver_SOURCES = test_crypto.c mock_msg.c \ + $(openvpn_srcdir)/buffer.c \ + $(openvpn_srcdir)/crypto.c \ + $(openvpn_srcdir)/crypto_mbedtls.c \ + $(openvpn_srcdir)/crypto_openssl.c \ + $(openvpn_srcdir)/otime.c \ + $(openvpn_srcdir)/packet_id.c \ + $(openvpn_srcdir)/platform.c + packet_id_testdriver_CFLAGS = @TEST_CFLAGS@ \ -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \ $(OPTIONAL_CRYPTO_CFLAGS) diff --git a/tests/unit_tests/openvpn/test_crypto.c b/tests/unit_tests/openvpn/test_crypto.c new file mode 100644 index 0000000..62d5b3f --- /dev/null +++ b/tests/unit_tests/openvpn/test_crypto.c @@ -0,0 +1,92 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2016-2017 Fox Crypto B.V. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#include "syshead.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "crypto.h" + +#include "mock_msg.h" + +int script_security = 0; /* Avoid including misc.c */ + +static const char testtext[] = "Dummy text to test PEM encoding"; + +/** + * Check that packet replays are accepted when CO_IGNORE_PACKET_ID is set. This + * is used for the first control channel packet that arrives, because we don't + * know the packet ID yet. + */ +static void +crypto_pem_encode_decode_loopback(void **state) { + struct gc_arena gc = gc_new(); + struct buffer src_buf; + buf_set_read(&src_buf, (void *)testtext, sizeof(testtext)); + + uint8_t dec[sizeof(testtext)]; + struct buffer dec_buf; + buf_set_write(&dec_buf, dec, sizeof(dec)); + + struct buffer pem_buf; + + assert_true(crypto_pem_encode("TESTKEYNAME", &pem_buf, &src_buf, &gc)); + + /* Wrong key name */ + assert_false(crypto_pem_decode("WRONGNAME", &dec_buf, &pem_buf)); + + assert_true(crypto_pem_decode("TESTKEYNAME", &dec_buf, &pem_buf)); + + gc_free(&gc); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(crypto_pem_encode_decode_loopback), + }; + +#if defined(ENABLE_CRYPTO_OPENSSL) + OpenSSL_add_all_algorithms(); +#endif + + int ret = cmocka_run_group_tests_name("crypto tests", tests, NULL, NULL); + +#if defined(ENABLE_CRYPTO_OPENSSL) + EVP_cleanup(); +#endif + + return ret; +} From patchwork Fri Dec 8 01:07:42 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 141 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director5.mail.ord1d.rsapps.net ([172.27.255.56]) by backend31.mail.ord1d.rsapps.net (Dovecot) with LMTP id NUxvAOGAKlpmJwAAgoeIoA for ; Fri, 08 Dec 2017 07:09:05 -0500 Received: from proxy17.mail.iad3a.rsapps.net ([172.27.255.56]) by director5.mail.ord1d.rsapps.net (Dovecot) with LMTP id 88KvBuGAKloYfQAAsdCWiw ; Fri, 08 Dec 2017 07:09:05 -0500 Received: from smtp30.gate.iad3a ([172.27.255.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy17.mail.iad3a.rsapps.net (Dovecot) with LMTP id eLUFBOGAKlr3dwAAR4KW9A ; Fri, 08 Dec 2017 07:09:05 -0500 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.34.181.88] Authentication-Results: smtp30.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.34.181.88"; 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; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Classification-ID: 913bf030-dc10-11e7-86a5-bc305bf63980-1-1 Received: from [216.34.181.88] ([216.34.181.88:2436] helo=lists.sourceforge.net) by smtp30.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 1D/07-14491-9D08A2A5; Fri, 08 Dec 2017 07:08:57 -0500 Received: from localhost ([127.0.0.1] helo=sfs-ml-2.v29.ch3.sourceforge.com) by sfs-ml-2.v29.ch3.sourceforge.com with esmtp (Exim 4.89) (envelope-from ) id 1eNHRw-0004vX-37; Fri, 08 Dec 2017 12:08:16 +0000 Received: from sfi-mx-3.v28.ch3.sourceforge.com ([172.29.28.193] helo=mx.sourceforge.net) by sfs-ml-2.v29.ch3.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.89) (envelope-from ) id 1eNHRv-0004vO-4M for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:15 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Type:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:CC:To:From:Sender:Reply-To:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=VRzAXNfw9oBPdk4fbFNjUw8BebCJ2l/erx/ZrzeMEsk=; b=EpawYAN9lhIjpSOrjq1ENdsKPk yvfua8IbfSJuiQllU2p4f07s0g6c8H4DCaojHqOwT6XzEHXN83mXHZwj44mRfG0XuhYe2VdFhIu6g 5ksW+Dw24OREAsDtBz81+lxevUW7B8rQfLBCmVEu8roWiJyc8v0pW3aDfc84Geftb9OE=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject: CC:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=VRzAXNfw9oBPdk4fbFNjUw8BebCJ2l/erx/ZrzeMEsk=; b=TCBz8Co2GEkBvm4XA0obBpUoEd yYJHy4eNya68Dmoq9PXeE/63PZI68hpgcexbybKTOrJ9800jw21CexKjOwbu9NyWzMwqb0+EdbTGj fsHEYnYWWmdp0uQnOhNQeJYUxtCDBOyer8AvjHwYSVrRv8abbhEUDghlcp7Lq+QGeSQ4=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-3.v28.ch3.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.89) id 1eNHRt-0007SJ-67 for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:15 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id E2CCF1C523D for ; Fri, 8 Dec 2017 13:08:06 +0100 (CET) Received: from steffan-fox.fox.local (172.16.5.166) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Fri, 8 Dec 2017 13:08:06 +0100 From: Steffan Karger To: Date: Fri, 8 Dec 2017 13:07:42 +0100 Message-ID: <1512734870-17133-3-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> MIME-Version: 1.0 X-ClientProxiedBy: FOXDFT52.FOX.local (10.0.0.129) To FOXDFT52.FOX.local (10.0.0.129) X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -0.0 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1eNHRt-0007SJ-67 Subject: [Openvpn-devel] [PATCH 02/10] tls-crypt-v2: add specification to doc/ 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: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox This is a preliminary description of tls-crypt-v2. It should give a good impression about the reasoning and design behind tls-crypt-v2, but might need some polishing and updating. Signed-off-by: Steffan Karger --- doc/tls-crypt-v2.txt | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 doc/tls-crypt-v2.txt diff --git a/doc/tls-crypt-v2.txt b/doc/tls-crypt-v2.txt new file mode 100644 index 0000000..578b2f9 --- /dev/null +++ b/doc/tls-crypt-v2.txt @@ -0,0 +1,164 @@ +Client-specific tls-crypt keys (--tls-crypt-v2) +=============================================== + +This document describes the ``--tls-crypt-v2`` option, which enables OpenVPN +to use client-specific ``--tls-crypt`` keys. + +Rationale +--------- + +``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared +among all clients and servers in an OpenVPN deployment. If any client or +server is compromised, the attacker will have access to this shared key, and it +will no longer provide any security. To reduce the risk of loosing pre-shared +keys, ``tls-crypt-v2`` adds the ability to supply each client with a unique +tls-crypt key. This allows large organisations and VPN providers to profit +from the same DoS and TLS stack protection that small deployments can already +achieve using ``tls-auth`` or ``tls-crypt``. + +Also, for ``tls-crypt``, even if all these peers succeed in keeping the key +secret, the key lifetime is limited to roughly 8000 years, divided by the +number of clients (see the ``--tls-crypt`` section of the man page). Using +client-specific keys, we lift this lifetime requirement to roughly 8000 years +for each client key (which "Should Be Enough For Everybody (tm)"). + + +Introduction +------------ + +``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce +client-specific tls-crypt keys without introducing a lot of server-side state. +The client-specific key is encrypted using a server key. The server key is the +same for all servers in a group. When a client connects, it first sends the +encrypted key to the server, such that the server can decrypt the key and all +messages can thereafter be encrypted using the client-specific key. + +A wrapped (encrypted and authenticated) client-specific key can also contain +metadata. The metadata is wrapped together with the key, and can be used to +allow servers to identify clients and/or key validity. This allows the server +to abort the connection immediately after receiving the first packet, rather +than performing an entire TLS handshake. Aborting the connection this early +greatly improves the DoS resilience and reduces attack service against +malicious clients that have the ``tls-crypt`` or ``tls-auth`` key. This is +particularly relevant for large deployments (think lost key or disgruntled +employee) and VPN providers (clients are not trusted). + +To allow for a smooth transition, ``tls-crypt-v2`` is designed such that a +server can enable both ``tls-crypt-v2`` and either ``tls-crypt`` or +``tls-auth``. This is achieved by introducing a P_CONTROL_HARD_RESET_CLIENT_V3 +opcode, that indicates that the client wants to use ``tls-crypt-v2`` for the +current connection. + +For an exact specification and more details, read the Implementation section. + + +Implementation +-------------- + +When setting up a tls-crypt-v2 group (similar to generating a tls-crypt or +tls-auth key previously): + +1. Generate a tls-crypt-v2 server key using OpenVPN's ``--genkey``. This key + contains 4 512-bit keys, of which we use: + + * the first 256 bits of key 1 as AES-256-CTR encryption key ``Ke`` + * the first 256 bits of key 2 as HMAC-SHA-256 authentication key ``Ka`` + +2. Add the tls-crypt-v2 server key to all server configs + (``tls-crypt-v2 /path/to/server.key``) + + +When provisioning a client, create a client-specific tls-crypt key: + +1. Generate 2048 bits client-specific key ``Kc`` +2. Optionally generate metadata +3. Create a wrapped client key ``WKc``, using the same nonce-misuse-resistant + SIV conruction we use for tls-crypt: + + ``T = HMAC-SHA256(Ka, Kc || metadata)`` + + ``IV = 128 most significant bits of T`` + + ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata)`` + +4. Create a tls-crypt-v2 client key: PEM-encode ``Kc || WKc`` and store in a + file, using the header ``-----BEGIN OpenVPN tls-crypt-v2 client key-----`` + and the footer ``-----END OpenVPN tls-crypt-v2 client key-----``. (The PEM + format is simple, and following PEM allows us to use the crypto lib function + for en/decoding.) +5. Add the tls-crypt-v2 client key to the client config + (``tls-crypt-v2 /path/to/client-specific.key``) + + +When setting up the openvpn connection: + +1. The client reads the tls-crypt-v2 key from its config, and: + + 1. loads ``Kc`` as its tls-crypt key, + 2. stores ``WKc`` in memory for sending to the server. + +2. To start the connection, the client creates a P_CONTROL_HARD_RESET_CLIENT_V3 + message without payload, wraps it with tls-crypt using ``Kc`` as the key, + and appends ``WKc``. (``WKc`` must not be encrypted, to prevent a + chicken-and-egg problem.) + +3. The server receives the P_CONTROL_HARD_RESET_CLIENT_V3 message, and + + a. unwraps ``WKc`` and strips ``WKc`` from the message. + b. uses unwrapped ``Kc`` to verify the remaining + P_CONTROL_HARD_RESET_CLIENT_V3 message's authentication. + + The message is dropped and no error response is sent when either a or b + fails (DoS protection). + +4. Server optionally checks metadata using a --tls-crypt-v2-verify script + + Metadata could for example contain the users certificate serial, such that + the incoming connection can be verified against a CRL, or a notAfter + timestamp that limits the key's validity period. + + This allows early abort of connection, *before* we expose any of the + notoriously dangerous TLS, X.509 and ASN.1 parsers and thereby reduces the + attack surface of the server. + + The metadata is checked *after* the OpenVPN three-way handshake has + completed, to prevent DoS attacks. (That is, once the client has proved to + the server that it possesses Kc, by authenticating a packet that contains the + session ID picked by the server.) + + RFC: should the server send a 'key rejected' message if the key is e.g. + revoked or expired? That allows for better client-side error reporting, but + also reduces the DoS resilience. + +6. Client and server use ``Kc`` for (un)wrapping any following control channel + messages. + + +Considerations +-------------- + +To allow for a smooth transition, the server implementation allows +``tls-crypt`` or ``tls-auth`` to be used simultaneously with ``tls-crypt-v2``. +This specification does not allow simultaneously using ``tls-crypt-v2`` and +connections without any control channel wrapping, because that would break DoS +resilience. RFC: should we add an option along the lines of +--tls-crypt-v2-allow-insecure-fallback to allow admins to enable this anyway? +It might help with transitioning. + +``tls-crypt-v2`` uses fixed crypto algorithms, because: + + * The crypto is used before we can do any negotiation, so the algorithms have + to be predefined. + * The crypto primitives are chosen conservatively, making problems with these + primitives unlikely. + * Making anything configurable adds complexity, both in implementation and + usage. We should not add anymore complexity than is absolutely necessary. + +Potential ``tls-crypt-v2`` risks: + + * Slightly more work on first connection (``WKc`` unwrap + hard reset unwrap) + than with ``tls-crypt`` (hard reset unwrap) or ``tls-auth`` (hard reset auth). + * Flexible metadata allow mistakes + (So we should make it easy to do it right. Provide tooling to create client + keys based on cert serial + CA fingerprint, provide script that uses CRL (if + available) to drop revoked keys.) From patchwork Fri Dec 8 01:07:43 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 140 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director2.mail.ord1d.rsapps.net ([172.27.255.59]) by backend31.mail.ord1d.rsapps.net (Dovecot) with LMTP id VA66A9yAKlrlCAAAgoeIoA for ; Fri, 08 Dec 2017 07:09:00 -0500 Received: from proxy7.mail.iad3a.rsapps.net ([172.27.255.59]) by director2.mail.ord1d.rsapps.net (Dovecot) with LMTP id c+zeHtyAKlqDewAAgYhSiA ; Fri, 08 Dec 2017 07:09:00 -0500 Received: from smtp45.gate.iad3a ([172.27.255.59]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy7.mail.iad3a.rsapps.net (Dovecot) with LMTP id JlDEAtyAKloIPwAAnPvY+A ; Fri, 08 Dec 2017 07:09:00 -0500 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.34.181.88] Authentication-Results: smtp45.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.34.181.88"; 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; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Classification-ID: 92942e8e-dc10-11e7-aeb8-782bcb788684-1-1 Received: from [216.34.181.88] ([216.34.181.88:57424] helo=lists.sourceforge.net) by smtp45.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 3B/A0-26862-BD08A2A5; Fri, 08 Dec 2017 07:08:59 -0500 Received: from localhost ([127.0.0.1] helo=sfs-ml-4.v29.ch3.sourceforge.com) by sfs-ml-4.v29.ch3.sourceforge.com with esmtp (Exim 4.89) (envelope-from ) id 1eNHRy-0007BO-4p; Fri, 08 Dec 2017 12:08:18 +0000 Received: from sfi-mx-2.v28.ch3.sourceforge.com ([172.29.28.192] helo=mx.sourceforge.net) by sfs-ml-4.v29.ch3.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.89) (envelope-from ) id 1eNHRw-0007B4-KX for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:16 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Type:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:CC:To:From:Sender:Reply-To:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=1mkhwIkT2WHipW9c6Q7WQJOU0q/R0wYTh6h2jCxccA8=; b=IjPdcfYJ4JEVmPiYq/xt0JCfnV +TxWU2sIZBlNvc7rpFp3vaNR9BVPayO0GkpbY7C/7DKGndWwhFqfA975mu6FrWjFi8+vc9+xpKcpn L9/P8b+ejYHsRYmMK39qipAY9Hj09TcvdKrRCTs8jJrHqJcIX/JCQTAfMEpdmgF4UB2g=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject: CC:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=1mkhwIkT2WHipW9c6Q7WQJOU0q/R0wYTh6h2jCxccA8=; b=NfTOxaQMr0vD/4UMy1nfKLli3C spMBXMEuJCGjqOGvC3YcqAfSkanXOlZTR7cEXiS1uqYgS9u4+YHqgXBCQBT3vTEiJ3eePkuCKX5e0 3Q/K90boZlcQBMj1QwUHHcf9/qM2F7oHzZcel0afinwGcbMKeNco6p3E2lKtrpn5r2a8=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-2.v28.ch3.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.89) id 1eNHRt-0004KZ-Dm for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:16 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id 4836B1C5241 for ; Fri, 8 Dec 2017 13:08:07 +0100 (CET) Received: from steffan-fox.fox.local (172.16.5.166) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Fri, 8 Dec 2017 13:08:06 +0100 From: Steffan Karger To: Date: Fri, 8 Dec 2017 13:07:43 +0100 Message-ID: <1512734870-17133-4-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> MIME-Version: 1.0 X-ClientProxiedBy: FOXDFT52.FOX.local (10.0.0.129) To FOXDFT52.FOX.local (10.0.0.129) X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -0.0 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1eNHRt-0004KZ-Dm Subject: [Openvpn-devel] [PATCH 03/10] tls-crypt-v2: generate client keys 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: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox As a first step towards a full tls-crypt-v2 implementation, add functionality to generate tls-crypt-v2 client keys. Signed-off-by: Steffan Karger --- doc/openvpn.8 | 51 +++++++ src/openvpn/buffer.c | 63 ++++++++ src/openvpn/buffer.h | 6 + src/openvpn/init.c | 35 ++++- src/openvpn/integer.h | 10 ++ src/openvpn/options.c | 37 +++++ src/openvpn/options.h | 9 ++ src/openvpn/tls_crypt.c | 284 +++++++++++++++++++++++++++++++++++ src/openvpn/tls_crypt.h | 81 +++++++++- tests/t_lpback.sh | 40 ++++- tests/unit_tests/openvpn/Makefile.am | 1 + 11 files changed, 601 insertions(+), 16 deletions(-) diff --git a/doc/openvpn.8 b/doc/openvpn.8 index 2b5cab1..0719087 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -5199,6 +5199,57 @@ degrading to the same security as using That is, the control channel still benefits from the extra protection against active man\-in\-the\-middle\-attacks and DoS attacks, but may no longer offer extra privacy and post\-quantum security on top of what TLS itself offers. + +For large setups or setups where clients are not trusted, consider using +.B \-\-tls\-crypt\-v2 +instead. That uses per\-client unique keys, and thereby improves the bounds to +\fR'rotate a client key at least once per 8000 years'. +.\"********************************************************* +.TP +.B \-\-tls\-crypt\-v2 keyfile + +Use client\-specific tls\-crypt keys. + +For clients, +.B keyfile +is a client\-specific tls\-crypt key. Such a key can be generated using the +.B \-\-tls\-crypt\-v2\-genkey +option. + +For servers, +.B keyfile +is used to unwrap client\-specific keys supplied by the client during connection +setup. This key must be the same as the key used to generate the +client\-specific key (see +.B \-\-tls\-crypt\-v2\-genkey\fR). + +On servers, this option can be used together with the +.B \-\-tls\-auth +or +.B \-\-tls\-crypt +option. In that case, the server will detect whether the client is using +client\-specific keys, and automatically select the right mode. +.\"********************************************************* +.TP +.B \-\-tls\-crypt\-v2\-genkey client|server keyfile [metadata] + +If the first parameter equals "server", generate a \-\-tls\-crypt\-v2 server +key and store the key in +.B keyfile\fR. + + +If the first parameter equals "client", generate a \-\-tls\-crypt\-v2 client +key, and store the key in +.B keyfile\fR. + +If supplied, include the supplied +.B metadata +in the wrapped client key. This metadata must be supplied in base64\-encoded +form. The metadata must be at most 735 bytes long (980 bytes in base64). + +.B TODO +Metadata handling is not yet implemented. This text will be updated by the +commit that introduces metadata handling. .\"********************************************************* .TP .B \-\-askpass [file] diff --git a/src/openvpn/buffer.c b/src/openvpn/buffer.c index ecd3b17..09dc2fd 100644 --- a/src/openvpn/buffer.c +++ b/src/openvpn/buffer.c @@ -315,6 +315,69 @@ convert_to_one_line(struct buffer *buf) } } +bool +buffer_write_file(const char *filename, const struct buffer *buf) +{ + bool ret = false; + int fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY, + S_IRUSR | S_IWUSR); + if (fd == -1) + { + msg(M_ERRNO, "Cannot open file '%s' for write", filename); + goto cleanup; + } + + const int size = write(fd, BPTR(buf), BLEN(buf)); + if (size != BLEN(buf)) + { + msg(M_ERRNO, "Write error on file '%s'", filename); + goto cleanup; + } + + ret = true; +cleanup: + if (close(fd)) + { + msg(M_ERRNO, "Close error on file %s", filename); + ret = false; + } + return ret; +} + +struct buffer +buffer_read_from_file(const char *filename, struct gc_arena *gc) +{ + struct buffer ret = { 0 }; + + platform_stat_t file_stat = {0}; + if (platform_stat(filename, &file_stat) < 0) + { + return ret; + } + + FILE *fp = platform_fopen(filename, "r"); + if (!fp) + { + return ret; + } + + const size_t size = file_stat.st_size; + ret = alloc_buf_gc(size + 1, gc); /* Space for trailing \0 */ + ssize_t read_size = fread(BPTR(&ret), 1, size, fp); + if (read_size < 0) + { + free_buf(&ret); + goto cleanup; + } + ASSERT(buf_inc_len(&ret, read_size)); + buf_null_terminate(&ret); + +cleanup: + fclose(fp); + return ret; +} + + /* NOTE: requires that string be null terminated */ void buf_write_string_file(const struct buffer *buf, const char *filename, int fd) diff --git a/src/openvpn/buffer.h b/src/openvpn/buffer.h index 84ad3ef..d15af07 100644 --- a/src/openvpn/buffer.h +++ b/src/openvpn/buffer.h @@ -469,6 +469,12 @@ const char *skip_leading_whitespace(const char *str); void string_null_terminate(char *str, int len, int capacity); +/** Write buffer contents to file */ +bool buffer_write_file(const char *filename, const struct buffer *buf); + +/** Read file contents. Returns invalid buffer on error. */ +struct buffer buffer_read_from_file(const char *filename, struct gc_arena *gc); + /* * Write string in buf to file descriptor fd. * NOTE: requires that string be null terminated. diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 30beadb..3e694b9 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -1027,6 +1027,11 @@ print_openssl_info(const struct options *options) bool do_genkey(const struct options *options) { + /* should we disable paging? */ + if (options->mlock && (options->genkey || options->tls_crypt_v2_genkey_file)) + { + platform_mlockall(true); + } if (options->genkey) { int nbits_written; @@ -1034,11 +1039,6 @@ do_genkey(const struct options *options) notnull(options->shared_secret_file, "shared secret output file (--secret)"); - if (options->mlock) /* should we disable paging? */ - { - platform_mlockall(true); - } - nbits_written = write_key_file(2, options->shared_secret_file); msg(D_GENKEY | M_NOPREFIX, @@ -1046,6 +1046,31 @@ do_genkey(const struct options *options) options->shared_secret_file); return true; } + if (options->tls_crypt_v2_genkey_type) + { + if(!strcmp(options->tls_crypt_v2_genkey_type, "server")) + { + tls_crypt_v2_write_server_key_file(options->tls_crypt_v2_genkey_file); + return true; + } + else if (options->tls_crypt_v2_genkey_type + && !strcmp(options->tls_crypt_v2_genkey_type, "client")) + { + if (!options->tls_crypt_v2_file) + { + msg(M_USAGE, "--tls-crypt-v2-gen-client-key requires a server key to be set via --tls-crypt-v2"); + } + + tls_crypt_v2_write_client_key_file(options->tls_crypt_v2_genkey_file, + options->tls_crypt_v2_metadata, options->tls_crypt_v2_file, + options->tls_crypt_v2_inline); + return true; + } + else + { + msg(M_USAGE, "--tls-crypt-v2-genkey type should be \"client\" or \"server\""); + } + } return false; } diff --git a/src/openvpn/integer.h b/src/openvpn/integer.h index 9bb00a3..02edc63 100644 --- a/src/openvpn/integer.h +++ b/src/openvpn/integer.h @@ -26,6 +26,16 @@ #include "error.h" +#ifndef htonll +#define htonll(x) ((1==htonl(1)) ? (x) : \ + ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32)) +#endif + +#ifndef ntohll +#define ntohll(x) ((1==ntohl(1)) ? (x) : \ + ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32)) +#endif + /* * min/max functions */ diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 7be5f38..aa73fd9 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -621,6 +621,13 @@ static const char usage_message[] = " attacks on the TLS stack and DoS attacks.\n" " key (required) provides the pre-shared key file.\n" " see --secret option for more info.\n" + "--tls-crypt-v2 key : For clients: use key as a client-specific tls-crypt key.\n" + " For servers: use key to decrypt client-specific keys. For\n" + " key generation (--tls-crypt-v2-genkey): use key to\n" + " encrypt generated client-specific key. (See --tls-crypt.)\n" + "--tls-crypt-v2-genkey client|server keyfile [base64 metadata]: Generate a\n" + " fresh tls-crypt-v2 client or server key, and store to\n" + " keyfile. If supplied, include metadata in wrapped key.\n" "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n" "--auth-nocache : Don't cache --askpass or --auth-user-pass passwords.\n" "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n" @@ -1775,6 +1782,10 @@ show_settings(const struct options *o) SHOW_STR(tls_auth_file); SHOW_STR(tls_crypt_file); + SHOW_STR(tls_crypt_v2_file); + SHOW_STR(tls_crypt_v2_genkey_type); + SHOW_STR(tls_crypt_v2_genkey_file); + SHOW_STR(tls_crypt_v2_metadata); #ifdef ENABLE_PKCS11 { @@ -2727,6 +2738,7 @@ options_postprocess_verify_ce(const struct options *options, const struct connec MUST_BE_UNDEF(transition_window); MUST_BE_UNDEF(tls_auth_file); MUST_BE_UNDEF(tls_crypt_file); + MUST_BE_UNDEF(tls_crypt_v2_file); MUST_BE_UNDEF(single_session); MUST_BE_UNDEF(push_peer_info); MUST_BE_UNDEF(tls_exit); @@ -3274,6 +3286,12 @@ options_postprocess_filechecks(struct options *options) errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE, options->tls_crypt_file, R_OK, "--tls-crypt"); errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE, + options->tls_crypt_v2_file, R_OK, + "--tls-crypt-v2"); + errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE, + options->tls_crypt_v2_genkey_file, R_OK, + "--tls-crypt-v2-genkey"); + errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE, options->shared_secret_file, R_OK, "--secret"); errs |= check_file_access(CHKACC_DIRPATH|CHKACC_FILEXSTWR, options->packet_id_file, R_OK|W_OK, "--replay-persist"); @@ -8010,6 +8028,25 @@ add_option(struct options *options, } options->tls_crypt_file = p[1]; } + else if (streq(p[0], "tls-crypt-v2") && p[1] && !p[3]) + { + VERIFY_PERMISSION(OPT_P_GENERAL); + if (streq(p[1], INLINE_FILE_TAG) && p[2]) + { + options->tls_crypt_v2_inline = p[2]; + } + options->tls_crypt_v2_file = p[1]; + } + else if (streq(p[0], "tls-crypt-v2-genkey") && p[2] && !p[4]) + { + VERIFY_PERMISSION(OPT_P_GENERAL); + options->tls_crypt_v2_genkey_type = p[1]; + options->tls_crypt_v2_genkey_file = p[2]; + if (p[3]) + { + options->tls_crypt_v2_metadata = p[3]; + } + } else if (streq(p[0], "key-method") && p[1] && !p[2]) { int key_method; diff --git a/src/openvpn/options.h b/src/openvpn/options.h index f70760c..7d995c3 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -566,6 +566,15 @@ struct options const char *tls_crypt_file; const char *tls_crypt_inline; + /* Client-specific secret or server key used for TLS control channel + * authenticated encryption v2 */ + const char *tls_crypt_v2_file; + const char *tls_crypt_v2_inline; + + const char *tls_crypt_v2_genkey_type; + const char *tls_crypt_v2_genkey_file; + const char *tls_crypt_v2_metadata; + /* Allow only one session */ bool single_session; diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index d9c67c3..fb4ac84 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -29,11 +29,21 @@ #include "syshead.h" +#include "base64.h" #include "crypto.h" +#include "platform.h" #include "session_id.h" #include "tls_crypt.h" +const char *tls_crypt_v2_cli_pem_name = "OpenVPN tls-crypt-v2 client key"; +const char *tls_crypt_v2_srv_pem_name = "OpenVPN tls-crypt-v2 server key"; + +/** Metadata contains user-specified data */ +static const uint8_t TLS_CRYPT_METADATA_TYPE_USER = 0x00; +/** Metadata contains a 64-bit unix timestamp in network byte order */ +static const uint8_t TLS_CRYPT_METADATA_TYPE_TIMESTAMP = 0x01; + static struct key_type tls_crypt_kt(void) { @@ -264,3 +274,277 @@ error_exit: gc_free(&gc); return false; } + +static inline bool +tls_crypt_v2_read_keyfile(struct buffer *key, const char *pem_name, + const char *key_file, const char *key_inline) +{ + bool ret = false; + struct buffer key_pem = { 0 }; + struct gc_arena gc = gc_new(); + + if (strcmp(key_file, INLINE_FILE_TAG)) + { + key_pem = buffer_read_from_file(key_file, &gc); + if (!buf_valid(&key_pem)) + { + msg(M_WARN, "ERROR: failed to read tls-crypt-v2 key file (%s)", + key_file); + goto cleanup; + } + } + else + { + buf_set_read(&key_pem, (const void *)key_inline, strlen(key_inline)); + } + + if (!crypto_pem_decode(pem_name, key, &key_pem)) + { + msg(M_WARN, "ERROR: tls-crypt-v2 pem decode failed"); + goto cleanup; + } + + ret = true; +cleanup: + if (strcmp(key_file, INLINE_FILE_TAG)) + { + buf_clear(&key_pem); + } + gc_free(&gc); + return ret; +} + +static inline void +tls_crypt_v2_load_client_key(struct key_ctx_bi *key, const struct key2 *key2, + bool tls_server) +{ + const int key_direction = tls_server ? + KEY_DIRECTION_NORMAL : KEY_DIRECTION_INVERSE; + struct key_type kt = tls_crypt_kt(); + if (!kt.cipher || !kt.digest) + { + msg (M_FATAL, "ERROR: --tls-crypt not supported"); + } + init_key_ctx_bi(key, key2, key_direction, &kt, + "Control Channel Encryption"); +} + +void +tls_crypt_v2_init_client_key(struct key_ctx_bi *key, struct buffer *wkc_buf, + const char *key_file, const char *key_inline) +{ + struct buffer client_key = alloc_buf(TLS_CRYPT_V2_CLIENT_KEY_LEN + + TLS_CRYPT_V2_MAX_WKC_LEN); + + if (!tls_crypt_v2_read_keyfile(&client_key, tls_crypt_v2_cli_pem_name, + key_file, key_inline)) + { + msg(M_FATAL, "ERROR: invalid tls-crypt-v2 client key format"); + } + + struct key2 key2; + if (!buf_read(&client_key, &key2.keys, sizeof(key2.keys))) + { + msg (M_FATAL, "ERROR: not enough data in tls-crypt-v2 client key"); + } + + tls_crypt_v2_load_client_key(key, &key2, false); + secure_memzero(&key2, sizeof(key2)); + + *wkc_buf = client_key; +} + +void +tls_crypt_v2_init_server_key(struct key_ctx *key_ctx, bool encrypt, + const char *key_file, const char *key_inline) +{ + struct key srv_key; + struct buffer srv_key_buf; + + buf_set_write(&srv_key_buf, (void *) &srv_key, sizeof(srv_key)); + if (!tls_crypt_v2_read_keyfile(&srv_key_buf, tls_crypt_v2_srv_pem_name, + key_file, key_inline)) + { + msg(M_FATAL, "ERROR: invalid tls-crypt-v2 server key format"); + } + + struct key_type kt = tls_crypt_kt(); + if (!kt.cipher || !kt.digest) + { + msg (M_FATAL, "ERROR: --tls-crypt not supported"); + } + init_key_ctx(key_ctx, &srv_key, &kt, encrypt, "tls-crypt-v2 server key"); + secure_memzero(&srv_key, sizeof(srv_key)); +} + +static bool +tls_crypt_v2_wrap_client_key(struct buffer *wkc, + const struct key2 *src_key, + const struct buffer *src_metadata, + struct key_ctx *server_key, struct gc_arena *gc) +{ + cipher_ctx_t *cipher_ctx = server_key->cipher; + struct buffer work = alloc_buf_gc(TLS_CRYPT_V2_MAX_WKC_LEN + + cipher_ctx_block_size(cipher_ctx), gc); + + /* Calculate auth tag and synthetic IV */ + uint8_t *tag = buf_write_alloc(&work, TLS_CRYPT_TAG_SIZE); + if (!tag) + { + msg (M_WARN, "ERROR: could not write tag"); + return false; + } + hmac_ctx_t *hmac_ctx = server_key->hmac; + hmac_ctx_reset(hmac_ctx); + hmac_ctx_update(hmac_ctx, (void*)src_key->keys, sizeof(src_key->keys)); + hmac_ctx_update(hmac_ctx, BPTR(src_metadata), BLEN(src_metadata)); + hmac_ctx_final(hmac_ctx, tag); + + dmsg(D_CRYPTO_DEBUG, "TLS-CRYPT WRAP TAG: %s", + format_hex(tag, TLS_CRYPT_TAG_SIZE, 0, gc)); + + /* Use the 128 most significant bits of the tag as IV */ + ASSERT(cipher_ctx_reset(cipher_ctx, tag)); + + /* Overflow check (OpenSSL requires an extra block in the dst buffer) */ + if (buf_forward_capacity(&work) < (sizeof(src_key->keys) + + BLEN(src_metadata) + + cipher_ctx_block_size(cipher_ctx))) + { + msg (M_WARN, "ERROR: could not crypt: insufficient space in dst"); + return false; + } + + /* Encrypt */ + int outlen = 0; + ASSERT(cipher_ctx_update(cipher_ctx, BEND(&work), &outlen, + (void*)src_key->keys, sizeof(src_key->keys))); + ASSERT(buf_inc_len(&work, outlen)); + ASSERT(cipher_ctx_update(cipher_ctx, BEND(&work), &outlen, + BPTR(src_metadata), BLEN(src_metadata))); + ASSERT(buf_inc_len(&work, outlen)); + ASSERT(cipher_ctx_final(cipher_ctx, BEND(&work), &outlen)); + ASSERT(buf_inc_len(&work, outlen)); + + return buf_copy(wkc, &work); +} + +void +tls_crypt_v2_write_server_key_file(const char *filename) +{ + struct gc_arena gc = gc_new(); + struct key server_key = { 0 }; + struct buffer server_key_buf = clear_buf(); + struct buffer server_key_pem = clear_buf(); + + if (!rand_bytes((void *)&server_key, sizeof(server_key))) + { + msg(M_NONFATAL, "ERROR: could not generate random key"); + goto cleanup; + } + buf_set_read(&server_key_buf, (void *) &server_key, sizeof(server_key)); + if (!crypto_pem_encode(tls_crypt_v2_srv_pem_name, &server_key_pem, + &server_key_buf, &gc)) + { + msg(M_WARN, "ERROR: could not PEM-encode client key"); + goto cleanup; + } + + if (!buffer_write_file(filename, &server_key_pem)) + { + msg(M_ERR, "ERROR: could not write server key file"); + goto cleanup; + } + +cleanup: + secure_memzero(&server_key, sizeof(server_key)); + buf_clear(&server_key_pem); + gc_free(&gc); + return; +} + +void +tls_crypt_v2_write_client_key_file(const char *filename, const char *b64_metadata, + const char *server_key_file, + const char *server_key_inline) +{ + struct gc_arena gc = gc_new(); + struct key_ctx server_key = { 0 }; + struct buffer client_key_pem = { 0 }; + struct buffer dst = alloc_buf_gc(TLS_CRYPT_V2_CLIENT_KEY_LEN + + TLS_CRYPT_V2_MAX_WKC_LEN, &gc); + + struct key2 client_key = { 2 }; + if (!rand_bytes((void*)client_key.keys, sizeof(client_key.keys))) + { + msg(M_FATAL, "ERROR: could not generate random key"); + goto cleanup; + } + ASSERT(buf_write(&dst, client_key.keys, sizeof(client_key.keys))); + + struct buffer metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN, &gc); + if (b64_metadata) + { + if (TLS_CRYPT_V2_MAX_B64_METADATA_LEN < strlen(b64_metadata)) + { + msg(M_FATAL, + "ERROR: metadata too long (%d bytes, max %u bytes)", + (int) strlen(b64_metadata), TLS_CRYPT_V2_MAX_B64_METADATA_LEN); + } + ASSERT(buf_write(&metadata, &TLS_CRYPT_METADATA_TYPE_USER, 1)); + int decoded_len = openvpn_base64_decode(b64_metadata, BPTR(&metadata), + BCAP(&metadata)); + if (decoded_len < 0) + { + msg(M_FATAL, "ERROR: failed to base64 decode provided metadata"); + goto cleanup; + } + ASSERT(buf_inc_len(&metadata, decoded_len)); + } + else + { + int64_t timestamp = htonll(now); + ASSERT(buf_write(&metadata, &TLS_CRYPT_METADATA_TYPE_TIMESTAMP, 1)); + ASSERT(buf_write(&metadata, ×tamp, sizeof(timestamp))); + } + + tls_crypt_v2_init_server_key(&server_key, true, server_key_file, + server_key_inline); + if (!tls_crypt_v2_wrap_client_key(&dst, &client_key, &metadata, &server_key, + &gc)) + { + msg (M_FATAL, "ERROR: could not wrap generated client key"); + goto cleanup; + } + + /* PEM-encode Kc || WKc */ + if (!crypto_pem_encode(tls_crypt_v2_cli_pem_name, &client_key_pem, &dst, + &gc)) + { + msg(M_FATAL, "ERROR: could not PEM-encode client key"); + goto cleanup; + } + + if (!buffer_write_file(filename, &client_key_pem)) + { + msg(M_FATAL, "ERROR: could not write client key file"); + goto cleanup; + } + + /* Sanity check: load client key (as "client") */ + struct key_ctx_bi test_client_key; + struct buffer test_wrapped_client_key; + msg (D_GENKEY, "Testing client-side key loading..."); + tls_crypt_v2_init_client_key(&test_client_key, &test_wrapped_client_key, + filename, NULL); + free_key_ctx_bi(&test_client_key); + free_buf(&test_wrapped_client_key); + +cleanup: + secure_memzero(&client_key, sizeof(client_key)); + free_key_ctx(&server_key); + buf_clear(&client_key_pem); + buf_clear(&dst); + + gc_free(&gc); +} diff --git a/src/openvpn/tls_crypt.h b/src/openvpn/tls_crypt.h index e8080df..f4d3ca7 100644 --- a/src/openvpn/tls_crypt.h +++ b/src/openvpn/tls_crypt.h @@ -22,15 +22,13 @@ */ /** - * @defgroup tls_crypt Control channel encryption (--tls-crypt) + * @defgroup tls_crypt Control channel encryption (--tls-crypt, --tls-crypt-v2) * @ingroup control_tls * @{ * - * @par * Control channel encryption uses a pre-shared static key (like the --tls-auth * key) to encrypt control channel packets. * - * @par * Encrypting control channel packets has three main advantages: * - It provides more privacy by hiding the certificate used for the TLS * connection. @@ -38,11 +36,20 @@ * - It provides "poor-man's" post-quantum security, against attackers who * will never know the pre-shared key (i.e. no forward secrecy). * - * @par Specification + * --tls-crypt uses a tls-auth-style group key, where all servers and clients + * share the same group key. --tls-crypt-v2 adds support for client-specific + * keys, where all servers share the same client-key encryption key, and each + * clients receives a unique client key, both in plaintext and in encrypted + * form. When connecting to a server, the client sends the encrypted key to + * the server in the first packet (P_CONTROL_HARD_RESET_CLIENT_V3). The server + * then decrypts that key, and both parties can use the same client-specific + * key for tls-crypt packets. See doc/tls-crypt-v2.txt for more details. + * + * @par On-the-wire tls-crypt packet specification + * @parblock * Control channel encryption is based on the SIV construction [0], to achieve * nonce misuse-resistant authenticated encryption: * - * @par * \code{.unparsed} * msg = control channel plaintext * header = opcode (1 byte) || session_id (8 bytes) || packet_id (8 bytes) @@ -57,18 +64,17 @@ * output = Header || Tag || Ciph * \endcode * - * @par * This boils down to the following on-the-wire packet format: * - * @par * \code{.unparsed} * - opcode - || - session_id - || - packet_id - || auth_tag || * payload * * \endcode * - * @par * Where * - XXX - means authenticated, and * * XXX * means authenticated and encrypted. + * + * @endparblock */ #ifndef TLSCRYPT_H @@ -86,6 +92,15 @@ #define TLS_CRYPT_OFF_TAG (TLS_CRYPT_OFF_PID + TLS_CRYPT_PID_SIZE) #define TLS_CRYPT_OFF_CT (TLS_CRYPT_OFF_TAG + TLS_CRYPT_TAG_SIZE) +#define TLS_CRYPT_V2_MAX_WKC_LEN (1024) +#define TLS_CRYPT_V2_CLIENT_KEY_LEN (2048/8) +#define TLS_CRYPT_V2_SERVER_KEY_LEN (sizeof(struct key)) +#define TLS_CRYPT_V2_TAG_SIZE (TLS_CRYPT_TAG_SIZE) +#define TLS_CRYPT_V2_MAX_METADATA_LEN (TLS_CRYPT_V2_MAX_WKC_LEN \ + - (TLS_CRYPT_V2_CLIENT_KEY_LEN + TLS_CRYPT_V2_TAG_SIZE)) +#define TLS_CRYPT_V2_MAX_B64_METADATA_LEN \ + ((((TLS_CRYPT_V2_MAX_METADATA_LEN - 1) * 8) + 5) / 6) + /** * Initialize a key_ctx_bi structure for use with --tls-crypt. * @@ -138,6 +153,56 @@ bool tls_crypt_wrap(const struct buffer *src, struct buffer *dst, bool tls_crypt_unwrap(const struct buffer *src, struct buffer *dst, struct crypto_options *opt); +/** + * Initialize a tls-crypt-v2 server key (used to encrypt/decrypt client keys). + * + * @param key Key structure to be initialized. Must be non-NULL. + * @parem encrypt If true, initialize the key structure for encryption, + * otherwise for decryption. + * @param key_file File path of the key file to load, or INLINE tag. + * @param key_inline Inline key file contents (or NULL if not inline). + */ +void tls_crypt_v2_init_server_key(struct key_ctx *key_ctx, bool encrypt, + const char *key_file, const char *key_inline); + +/** + * Initialize a tls-crypt-v2 client key. + * + * @param key Key structure to be initialized with the client + * key. + * @param wrapped_key_buf Returns buffer containing the wrapped key that will + * be sent to the server when connecting. Caller must + * free this buffer when no longer neede. + * @param key_file File path of the key file to load, or INLINE tag. + * @param key_inline Inline key file contents (or NULL if not inline). + */ +void tls_crypt_v2_init_client_key(struct key_ctx_bi *key, + struct buffer *wrapped_key_buf, + const char *key_file, + const char *key_inline); + +/** + * Generate a tls-crypt-v2 server key, and write to file. + * + * @param filename Filename of the server key file to create. + */ +void tls_crypt_v2_write_server_key_file(const char *filename); + +/** + * Generate a tls-crypt-v2 client key, and write to file. + * + * @param filename Filename of the client key file to create. + * @param b64_metadata Base64 metadata to be included in the client key. + * @param server_key_file File path of the server key to use for wrapping the + * client key, or INLINE tag. + * @param server_key_inline Inline server key file contents (or NULL if not + * inline). + */ +void tls_crypt_v2_write_client_key_file(const char *filename, + const char *b64_metadata, + const char *key_file, + const char *key_inline); + /** @} */ #endif /* TLSCRYPT_H */ diff --git a/tests/t_lpback.sh b/tests/t_lpback.sh index 2052c62..107e7c1 100755 --- a/tests/t_lpback.sh +++ b/tests/t_lpback.sh @@ -21,8 +21,8 @@ set -eu top_builddir="${top_builddir:-..}" -trap "rm -f key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15 -trap "rm -f key.$$ log.$$ ; exit 1" 0 3 +trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15 +trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; exit 1" 0 3 # Get list of supported ciphers from openvpn --show-ciphers output CIPHERS=$(${top_builddir}/src/openvpn/openvpn --show-ciphers | \ @@ -55,6 +55,40 @@ do fi done -rm key.$$ log.$$ +echo -n "Testing tls-crypt-v2 server key generation..." +"${top_builddir}/src/openvpn/openvpn" \ + --tls-crypt-v2-genkey server tc-server-key.$$ >log.$$ 2>&1 +if [ $? != 0 ] ; then + echo "FAILED" + cat log.$$ + e=1 +else + echo "OK" +fi + +echo -n "Testing tls-crypt-v2 key generation (no metadata)..." +"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \ + --tls-crypt-v2-genkey client tc-client-key.$$ >log.$$ 2>&1 +if [ $? != 0 ] ; then + echo "FAILED" + cat log.$$ + e=1 +else + echo "OK" +fi + +echo -n "Testing tls-crypt-v2 key generation (max length metadata)..." +"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \ + --tls-crypt-v2-genkey client tc-client-key.$$ \ + $(head -c735 /dev/zero | base64 -w0) >log.$$ 2>&1 +if [ $? != 0 ] ; then + echo "FAILED" + cat log.$$ + e=1 +else + echo "OK" +fi + +rm key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ trap 0 exit $e diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index d100c21..937393d 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -60,6 +60,7 @@ tls_crypt_testdriver_CFLAGS = @TEST_CFLAGS@ \ tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ $(OPTIONAL_CRYPTO_LIBS) tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \ + $(openvpn_srcdir)/base64.c \ $(openvpn_srcdir)/buffer.c \ $(openvpn_srcdir)/crypto.c \ $(openvpn_srcdir)/crypto_mbedtls.c \ From patchwork Fri Dec 8 01:07:44 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 143 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director4.mail.ord1d.rsapps.net ([172.27.255.58]) by backend31.mail.ord1d.rsapps.net (Dovecot) with LMTP id LLuXCuqAKlr/DwAAgoeIoA for ; Fri, 08 Dec 2017 07:09:14 -0500 Received: from proxy19.mail.iad3a.rsapps.net ([172.27.255.58]) by director4.mail.ord1d.rsapps.net (Dovecot) with LMTP id E40eDeqAKlqmNgAAHDmxtw ; Fri, 08 Dec 2017 07:09:14 -0500 Received: from smtp50.gate.iad3a ([172.27.255.58]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy19.mail.iad3a.rsapps.net (Dovecot) with LMTP id 0B1hC+qAKlqKLgAAXy6Yeg ; Fri, 08 Dec 2017 07:09:14 -0500 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.34.181.88] Authentication-Results: smtp50.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.34.181.88"; 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; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Classification-ID: 9a322e8e-dc10-11e7-9655-525400c2fb51-1-1 Received: from [216.34.181.88] ([216.34.181.88:37318] helo=lists.sourceforge.net) by smtp50.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 55/52-16690-8E08A2A5; Fri, 08 Dec 2017 07:09:12 -0500 Received: from localhost ([127.0.0.1] helo=sfs-ml-1.v29.ch3.sourceforge.com) by sfs-ml-1.v29.ch3.sourceforge.com with esmtp (Exim 4.89) (envelope-from ) id 1eNHRx-0000Tj-EB; Fri, 08 Dec 2017 12:08:17 +0000 Received: from sfi-mx-3.v28.ch3.sourceforge.com ([172.29.28.193] helo=mx.sourceforge.net) by sfs-ml-1.v29.ch3.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.89) (envelope-from ) id 1eNHRw-0000TS-47 for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:16 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Type:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:CC:To:From:Sender:Reply-To:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=KXWSUo6wMUNThV+aFHksexyQb2ryuReLSA42KLkw7QQ=; b=RD8OD1GYIJb3a1+t4AYFO21IMY nVXsvVwnTVG7BuOOxS4UlJX6eSLTyaRsiF3h1npghBzADYSh50j0jLRM1Y4GvG1jQ8ids+0vkWRLo k1nUUwHMcbQYRXAkLID2XxJDBPAN16M/tBd2boRybrTt4m7QO9NjLBiO2eiFpWc+daFg=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject: CC:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=KXWSUo6wMUNThV+aFHksexyQb2ryuReLSA42KLkw7QQ=; b=KuKWozNbxuzzd/TxvFdDdJL4tY cWy0iQ6gO4NrYGDfffaAPMz1ZjC9HQGtOOz4QGaYfmMINNeeDhpndLZxyRzpfJoQfxXLHEftpo3Hy YHg1Waf+a+pwd7wSkLCJTQDlGIA08EINz0zWXhKMdpDGLLs7bjqfif7iZSeWwqBsZJ9E=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-3.v28.ch3.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.89) id 1eNHRt-0007SL-Bt for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:16 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id 538991C5242 for ; Fri, 8 Dec 2017 13:08:07 +0100 (CET) Received: from steffan-fox.fox.local (172.16.5.166) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Fri, 8 Dec 2017 13:08:06 +0100 From: Steffan Karger To: Date: Fri, 8 Dec 2017 13:07:44 +0100 Message-ID: <1512734870-17133-5-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> MIME-Version: 1.0 X-ClientProxiedBy: FOXDFT52.FOX.local (10.0.0.129) To FOXDFT52.FOX.local (10.0.0.129) X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -0.0 SPF_PASS SPF: sender matches SPF record -0.0 AWL AWL: Adjusted score from AWL reputation of From: address X-Headers-End: 1eNHRt-0007SL-Bt Subject: [Openvpn-devel] [PATCH 04/10] tls-crypt-v2: add unwrap_client_key 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: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox Add helper functions to unwrap tls-crypt-v2 client keys. Signed-off-by: Steffan Karger --- src/openvpn/buffer.h | 7 + src/openvpn/tls_crypt.c | 102 ++++++++++++ tests/unit_tests/openvpn/test_tls_crypt.c | 253 +++++++++++++++++++++++++++--- 3 files changed, 342 insertions(+), 20 deletions(-) diff --git a/src/openvpn/buffer.h b/src/openvpn/buffer.h index d15af07..c900c32 100644 --- a/src/openvpn/buffer.h +++ b/src/openvpn/buffer.h @@ -839,6 +839,13 @@ buf_read_u32(struct buffer *buf, bool *good) } } +/** Return true if buffer contents are equal */ +static inline bool +buf_equal(const struct buffer *a, const struct buffer *b) +{ + return BLEN(a) == BLEN(b) && 0 == memcmp(BPTR(a), BPTR(b), BLEN(a)); +} + /** * Compare src buffer contents with match. * *NOT* constant time. Do not use when comparing HMACs. diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index fb4ac84..82d7037 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -429,6 +429,96 @@ tls_crypt_v2_wrap_client_key(struct buffer *wkc, return buf_copy(wkc, &work); } +static bool +tls_crypt_v2_unwrap_client_key(struct key2 *client_key, struct buffer *metadata, + struct buffer wrapped_client_key, + struct key_ctx *server_key) +{ + const char *error_prefix = __func__; + bool ret = false; + struct gc_arena gc = gc_new(); + /* The crypto API requires one extra cipher block of buffer head room when + * decrypting, which nicely matches the tag size of WKc. So + * TLS_CRYPT_V2_MAX_WKC_LEN is always large enough for the plaintext. */ + uint8_t plaintext_buf_data[TLS_CRYPT_V2_MAX_WKC_LEN] = { 0 }; + struct buffer plaintext = { 0 }; + + dmsg(D_TLS_DEBUG_MED, "%s: unwrapping client key (len=%d): %s", __func__, + BLEN(&wrapped_client_key), format_hex(BPTR(&wrapped_client_key), + BLEN(&wrapped_client_key), + 0, &gc)); + + if (TLS_CRYPT_V2_MAX_WKC_LEN < BLEN(&wrapped_client_key)) + { + CRYPT_ERROR("wrapped client key too big"); + } + + /* Decrypt client key and metadata */ + const uint8_t *tag = BPTR(&wrapped_client_key); + if (!buf_advance(&wrapped_client_key, TLS_CRYPT_TAG_SIZE)) + { + CRYPT_ERROR("failed to read tag"); + } + + if (!cipher_ctx_reset(server_key->cipher, tag)) + { + CRYPT_ERROR("failed to initialize IV"); + } + buf_set_write(&plaintext, plaintext_buf_data, sizeof(plaintext_buf_data)); + int outlen = 0; + if (!cipher_ctx_update(server_key->cipher, BPTR(&plaintext), &outlen, + BPTR(&wrapped_client_key), + BLEN(&wrapped_client_key))) + { + CRYPT_ERROR("could not decrypt client key"); + } + ASSERT(buf_inc_len(&plaintext, outlen)); + + if (!cipher_ctx_final(server_key->cipher, BEND(&plaintext), &outlen)) + { + CRYPT_ERROR("cipher final failed"); + } + ASSERT(buf_inc_len(&plaintext, outlen)); + + /* Check authentication */ + uint8_t tag_check[TLS_CRYPT_TAG_SIZE] = { 0 }; + hmac_ctx_reset(server_key->hmac); + hmac_ctx_update(server_key->hmac, BPTR(&plaintext), + BLEN(&plaintext)); + hmac_ctx_final(server_key->hmac, tag_check); + + if (memcmp_constant_time(tag, tag_check, sizeof(tag_check))) + { + dmsg(D_CRYPTO_DEBUG, "tag : %s", + format_hex(tag, sizeof(tag_check), 0, &gc)); + dmsg(D_CRYPTO_DEBUG, "tag_check: %s", + format_hex(tag_check, sizeof(tag_check), 0, &gc)); + CRYPT_ERROR("client key authentication error"); + } + + if (buf_len(&plaintext) < sizeof(client_key->keys)) + { + CRYPT_ERROR("failed to read client key"); + } + memcpy(&client_key->keys, BPTR(&plaintext), sizeof(client_key->keys)); + ASSERT(buf_advance(&plaintext, sizeof(client_key->keys))); + + if(!buf_copy(metadata, &plaintext)) + { + CRYPT_ERROR("metadata too large for supplied buffer"); + } + + ret = true; +error_exit: + if (!ret) + { + secure_memzero(client_key, sizeof(*client_key)); + } + buf_clear(&plaintext); + gc_free(&gc); + return ret; +} + void tls_crypt_v2_write_server_key_file(const char *filename) { @@ -538,6 +628,18 @@ tls_crypt_v2_write_client_key_file(const char *filename, const char *b64_metadat tls_crypt_v2_init_client_key(&test_client_key, &test_wrapped_client_key, filename, NULL); free_key_ctx_bi(&test_client_key); + + /* Sanity check: unwrap and load client key (as "server") */ + struct buffer test_metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN, + &gc); + struct key2 test_client_key2 = { 0 }; + free_key_ctx(&server_key); + tls_crypt_v2_init_server_key(&server_key, false, server_key_file, + server_key_inline); + msg (D_GENKEY, "Testing server-side key loading..."); + ASSERT(tls_crypt_v2_unwrap_client_key(&test_client_key2, &test_metadata, + test_wrapped_client_key, &server_key)); + secure_memzero(&test_client_key2, sizeof(test_client_key2)); free_buf(&test_wrapped_client_key); cleanup: diff --git a/tests/unit_tests/openvpn/test_tls_crypt.c b/tests/unit_tests/openvpn/test_tls_crypt.c index cf40e4b..724abda 100644 --- a/tests/unit_tests/openvpn/test_tls_crypt.c +++ b/tests/unit_tests/openvpn/test_tls_crypt.c @@ -45,7 +45,7 @@ const char plaintext_short[1]; -struct test_context { +struct test_tls_crypt_context { struct crypto_options co; struct key_type kt; struct buffer source; @@ -54,8 +54,8 @@ struct test_context { }; static int -setup(void **state) { - struct test_context *ctx = calloc(1, sizeof(*ctx)); +test_tls_crypt_setup(void **state) { + struct test_tls_crypt_context *ctx = calloc(1, sizeof(*ctx)); *state = ctx; struct key key = { 0 }; @@ -84,8 +84,8 @@ setup(void **state) { } static int -teardown(void **state) { - struct test_context *ctx = (struct test_context *) *state; +test_tls_crypt_teardown(void **state) { + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; free_buf(&ctx->source); free_buf(&ctx->ciphertext); @@ -98,7 +98,7 @@ teardown(void **state) { return 0; } -static void skip_if_tls_crypt_not_supported(struct test_context *ctx) +static void skip_if_tls_crypt_not_supported(struct test_tls_crypt_context *ctx) { if (!ctx->kt.cipher || !ctx->kt.digest) { @@ -111,7 +111,7 @@ static void skip_if_tls_crypt_not_supported(struct test_context *ctx) */ static void tls_crypt_loopback(void **state) { - struct test_context *ctx = (struct test_context *) *state; + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; skip_if_tls_crypt_not_supported(ctx); @@ -128,7 +128,7 @@ tls_crypt_loopback(void **state) { */ static void tls_crypt_loopback_zero_len(void **state) { - struct test_context *ctx = (struct test_context *) *state; + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; skip_if_tls_crypt_not_supported(ctx); @@ -147,7 +147,7 @@ tls_crypt_loopback_zero_len(void **state) { */ static void tls_crypt_loopback_max_len(void **state) { - struct test_context *ctx = (struct test_context *) *state; + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; skip_if_tls_crypt_not_supported(ctx); @@ -168,7 +168,7 @@ tls_crypt_loopback_max_len(void **state) { */ static void tls_crypt_fail_msg_too_long(void **state) { - struct test_context *ctx = (struct test_context *) *state; + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; skip_if_tls_crypt_not_supported(ctx); @@ -184,7 +184,7 @@ tls_crypt_fail_msg_too_long(void **state) { */ static void tls_crypt_fail_invalid_key(void **state) { - struct test_context *ctx = (struct test_context *) *state; + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; skip_if_tls_crypt_not_supported(ctx); @@ -203,7 +203,7 @@ tls_crypt_fail_invalid_key(void **state) { */ static void tls_crypt_fail_replay(void **state) { - struct test_context *ctx = (struct test_context *) *state; + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; skip_if_tls_crypt_not_supported(ctx); @@ -222,7 +222,7 @@ tls_crypt_fail_replay(void **state) { */ static void tls_crypt_ignore_replay(void **state) { - struct test_context *ctx = (struct test_context *) *state; + struct test_tls_crypt_context *ctx = (struct test_tls_crypt_context *) *state; skip_if_tls_crypt_not_supported(ctx); @@ -236,22 +236,235 @@ tls_crypt_ignore_replay(void **state) { assert_true(tls_crypt_unwrap(&ctx->ciphertext, &ctx->unwrapped, &ctx->co)); } +struct test_tls_crypt_v2_context { + struct gc_arena gc; + struct key2 server_key2; + struct key_ctx_bi server_keys; + struct key2 client_key2; + struct key_ctx_bi client_key; + struct buffer metadata; + struct buffer unwrapped_metadata; + struct buffer wkc; +}; + +static int +test_tls_crypt_v2_setup(void **state) { + struct test_tls_crypt_v2_context *ctx = calloc(1, sizeof(*ctx)); + *state = ctx; + + ctx->gc = gc_new(); + + /* Sligthly longer buffers to be able to test too-long data */ + ctx->metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN+16, &ctx->gc); + ctx->unwrapped_metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN+16, + &ctx->gc); + ctx->wkc = alloc_buf_gc(TLS_CRYPT_V2_MAX_WKC_LEN+16, &ctx->gc); + + /* Generate server key */ + rand_bytes((void *)ctx->server_key2.keys, sizeof(ctx->server_key2.keys)); + ctx->server_key2.n = 2; + struct key_type kt = tls_crypt_kt(); + init_key_ctx_bi(&ctx->server_keys, &ctx->server_key2, + KEY_DIRECTION_BIDIRECTIONAL, &kt, + "tls-crypt-v2 server key"); + + /* Generate client key */ + rand_bytes((void *)ctx->client_key2.keys, sizeof(ctx->client_key2.keys)); + ctx->client_key2.n = 2; + + return 0; +} + +static int +test_tls_crypt_v2_teardown(void **state) { + struct test_tls_crypt_v2_context *ctx = + (struct test_tls_crypt_v2_context *) *state; + + free_key_ctx_bi(&ctx->server_keys); + free_key_ctx_bi(&ctx->client_key); + + gc_free(&ctx->gc); + + free(ctx); + + return 0; +} + +/** + * Check wrapping and unwrapping a tls-crypt-v2 client key without metadata. + */ +static void +tls_crypt_v2_wrap_unwrap_no_metadata(void **state) { + struct test_tls_crypt_v2_context *ctx = + (struct test_tls_crypt_v2_context *) *state; + + struct buffer wrapped_client_key = alloc_buf_gc(TLS_CRYPT_V2_MAX_WKC_LEN, + &ctx->gc); + assert_true(tls_crypt_v2_wrap_client_key(&wrapped_client_key, + &ctx->client_key2, + &ctx->metadata, + &ctx->server_keys.encrypt, + &ctx->gc)); + + struct buffer unwrap_metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN, + &ctx->gc); + struct key2 unwrapped_client_key2 = { 0 }; + assert_true(tls_crypt_v2_unwrap_client_key(&unwrapped_client_key2, + &unwrap_metadata, + wrapped_client_key, + &ctx->server_keys.decrypt)); + + assert_true(0 == memcmp(ctx->client_key2.keys, unwrapped_client_key2.keys, + sizeof(ctx->client_key2.keys))); +} + +/** + * Check wrapping and unwrapping a tls-crypt-v2 client key with maximum length + * metadata. + */ +static void +tls_crypt_v2_wrap_unwrap_max_metadata(void **state) { + struct test_tls_crypt_v2_context *ctx = + (struct test_tls_crypt_v2_context *) *state; + + uint8_t* metadata = + buf_write_alloc(&ctx->metadata, TLS_CRYPT_V2_MAX_METADATA_LEN); + assert_true(rand_bytes(metadata, TLS_CRYPT_V2_MAX_METADATA_LEN)); + assert_true(tls_crypt_v2_wrap_client_key(&ctx->wkc, &ctx->client_key2, + &ctx->metadata, + &ctx->server_keys.encrypt, + &ctx->gc)); + + struct buffer unwrap_metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN, + &ctx->gc); + struct key2 unwrapped_client_key2 = { 0 }; + assert_true(tls_crypt_v2_unwrap_client_key(&unwrapped_client_key2, + &unwrap_metadata, ctx->wkc, + &ctx->server_keys.decrypt)); + + assert_true(0 == memcmp(ctx->client_key2.keys, unwrapped_client_key2.keys, + sizeof(ctx->client_key2.keys))); + assert_true(buf_equal(&ctx->metadata, &unwrap_metadata)); +} + +/** + * Check that wrapping a tls-crypt-v2 client key with too long metadata fails + * as expected. + */ +static void +tls_crypt_v2_wrap_too_long_metadata(void **state) { + struct test_tls_crypt_v2_context *ctx = + (struct test_tls_crypt_v2_context *) *state; + + assert_true(buf_inc_len(&ctx->metadata, TLS_CRYPT_V2_MAX_METADATA_LEN+1)); + assert_false(tls_crypt_v2_wrap_client_key(&ctx->wkc, &ctx->client_key2, + &ctx->metadata, + &ctx->server_keys.encrypt, + &ctx->gc)); +} + +/** + * Check that unwrapping a tls-crypt-v2 client key with the wrong server key + * fails as expected. + */ +static void +tls_crypt_v2_wrap_unwrap_wrong_key(void **state) { + struct test_tls_crypt_v2_context *ctx = + (struct test_tls_crypt_v2_context *) *state; + + assert_true(tls_crypt_v2_wrap_client_key(&ctx->wkc, &ctx->client_key2, + &ctx->metadata, + &ctx->server_keys.encrypt, + &ctx->gc)); + + /* Change server key */ + struct key_type kt = tls_crypt_kt(); + free_key_ctx_bi(&ctx->server_keys); + memset(&ctx->server_key2.keys, 0, sizeof(ctx->server_key2.keys)); + init_key_ctx_bi(&ctx->server_keys, &ctx->server_key2, + KEY_DIRECTION_BIDIRECTIONAL, &kt, + "wrong tls-crypt-v2 server key"); + + + struct key2 unwrapped_client_key2 = { 0 }; + assert_false(tls_crypt_v2_unwrap_client_key(&unwrapped_client_key2, + &ctx->unwrapped_metadata, + ctx->wkc, + &ctx->server_keys.decrypt)); + + const struct key2 zero = { 0 }; + assert_true(0 == memcmp(&unwrapped_client_key2, &zero, sizeof(zero))); + assert_true(0 == BLEN(&ctx->unwrapped_metadata)); +} + +/** + * Check that unwrapping a tls-crypt-v2 client key to a too small metadata + * buffer fails as expected. + */ +static void +tls_crypt_v2_wrap_unwrap_dst_too_small(void **state) { + struct test_tls_crypt_v2_context *ctx = + (struct test_tls_crypt_v2_context *) *state; + + uint8_t* metadata = + buf_write_alloc(&ctx->metadata, TLS_CRYPT_V2_MAX_METADATA_LEN); + assert_true(rand_bytes(metadata, TLS_CRYPT_V2_MAX_METADATA_LEN)); + assert_true(tls_crypt_v2_wrap_client_key(&ctx->wkc, &ctx->client_key2, + &ctx->metadata, + &ctx->server_keys.encrypt, + &ctx->gc)); + + struct key2 unwrapped_client_key2 = { 0 }; + struct buffer unwrapped_metadata = + alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN-1, &ctx->gc); + assert_false(tls_crypt_v2_unwrap_client_key(&unwrapped_client_key2, + &unwrapped_metadata, ctx->wkc, + &ctx->server_keys.decrypt)); + + const struct key2 zero = { 0 }; + assert_true(0 == memcmp(&unwrapped_client_key2, &zero, sizeof(zero))); + assert_true(0 == BLEN(&ctx->unwrapped_metadata)); +} + int main(void) { const struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(tls_crypt_loopback, setup, teardown), + cmocka_unit_test_setup_teardown(tls_crypt_loopback, + test_tls_crypt_setup, + test_tls_crypt_teardown), cmocka_unit_test_setup_teardown(tls_crypt_loopback_zero_len, - setup, teardown), + test_tls_crypt_setup, + test_tls_crypt_teardown), cmocka_unit_test_setup_teardown(tls_crypt_loopback_max_len, - setup, teardown), + test_tls_crypt_setup, + test_tls_crypt_teardown), cmocka_unit_test_setup_teardown(tls_crypt_fail_msg_too_long, - setup, teardown), + test_tls_crypt_setup, + test_tls_crypt_teardown), cmocka_unit_test_setup_teardown(tls_crypt_fail_invalid_key, - setup, teardown), + test_tls_crypt_setup, + test_tls_crypt_teardown), cmocka_unit_test_setup_teardown(tls_crypt_fail_replay, - setup, teardown), + test_tls_crypt_setup, + test_tls_crypt_teardown), cmocka_unit_test_setup_teardown(tls_crypt_ignore_replay, - setup, teardown), + test_tls_crypt_setup, + test_tls_crypt_teardown), + cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_unwrap_no_metadata, + test_tls_crypt_v2_setup, + test_tls_crypt_v2_teardown), + cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_unwrap_max_metadata, + test_tls_crypt_v2_setup, + test_tls_crypt_v2_teardown), + cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_too_long_metadata, + test_tls_crypt_v2_setup, + test_tls_crypt_v2_teardown), + cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_unwrap_wrong_key, + test_tls_crypt_v2_setup, + test_tls_crypt_v2_teardown), + cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_unwrap_dst_too_small, + test_tls_crypt_v2_setup, + test_tls_crypt_v2_teardown), }; #if defined(ENABLE_CRYPTO_OPENSSL) From patchwork Fri Dec 8 01:07:45 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 148 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director3.mail.ord1d.rsapps.net ([172.27.255.50]) by backend31.mail.ord1d.rsapps.net (Dovecot) with LMTP id y1onKfaAKlrGPgAAgoeIoA for ; Fri, 08 Dec 2017 07:09:26 -0500 Received: from proxy7.mail.iad3a.rsapps.net ([172.27.255.50]) by director3.mail.ord1d.rsapps.net (Dovecot) with LMTP id q5XSIfaAKlpgKgAAkXNnRw ; Fri, 08 Dec 2017 07:09:26 -0500 Received: from smtp32.gate.iad3a ([172.27.255.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy7.mail.iad3a.rsapps.net (Dovecot) with LMTP id y1pqCfaAKlqwPwAAnPvY+A ; Fri, 08 Dec 2017 07:09:26 -0500 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.34.181.88] Authentication-Results: smtp32.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.34.181.88"; 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; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Classification-ID: 97825254-dc10-11e7-8dd2-bc305bf65d38-1-1 Received: from [216.34.181.88] ([216.34.181.88:2556] helo=lists.sourceforge.net) by smtp32.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id B1/19-21079-4E08A2A5; Fri, 08 Dec 2017 07:09:08 -0500 Received: from localhost ([127.0.0.1] helo=sfs-ml-2.v29.ch3.sourceforge.com) by sfs-ml-2.v29.ch3.sourceforge.com with esmtp (Exim 4.89) (envelope-from ) id 1eNHS4-0004wF-5q; Fri, 08 Dec 2017 12:08:24 +0000 Received: from sfi-mx-2.v28.ch3.sourceforge.com ([172.29.28.192] helo=mx.sourceforge.net) by sfs-ml-2.v29.ch3.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.89) (envelope-from ) id 1eNHS2-0004w0-Kc for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:22 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Type:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:CC:To:From:Sender:Reply-To:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=A9gP0yzLeJFnC1BUCybWe5FCBOLgRNCtVy/IoYtvP1o=; b=SSt9dHVjNPknng0cKPnZuwU3kZ SSSRos1p54blNgrjSJm+6pSnBpDxgKntx2arib6au/Jc2O42983fWCiiUv7/o+5CA2YZg0adGLrqb kxapHyWMKl87gQ+epfXhWGQJDpIPYVbpw1yuiIQr/21tOSGThTgbiLSabSiBxNwNGC8M=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject: CC:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=A9gP0yzLeJFnC1BUCybWe5FCBOLgRNCtVy/IoYtvP1o=; b=WSuKVvtdPK13Bm9u9wWaGydoYK o3NNB4N7ga3KSJ7nKix2owvV4/MTW6U4qF5Rx+mFJoc+VoMu99k5N4ZEgYivQKkyjNYeYWuN8326j 2KRFogC5dQchnND1aSuYFJb/2kuL3z3KzVTy0l3or96KuytcPesz2UkwCowZELCjdeWk=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-2.v28.ch3.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.89) id 1eNHS1-0004L0-3h for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:22 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id 5E3EB1C5243 for ; Fri, 8 Dec 2017 13:08:07 +0100 (CET) Received: from steffan-fox.fox.local (172.16.5.166) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Fri, 8 Dec 2017 13:08:07 +0100 From: Steffan Karger To: Date: Fri, 8 Dec 2017 13:07:45 +0100 Message-ID: <1512734870-17133-6-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> MIME-Version: 1.0 X-ClientProxiedBy: FOXDFT52.FOX.local (10.0.0.129) To FOXDFT52.FOX.local (10.0.0.129) X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -0.0 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1eNHS1-0004L0-3h Subject: [Openvpn-devel] [PATCH 05/10] tls-crypt-v2: add P_CONTROL_HARD_RESET_CLIENT_V3 opcode 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: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox Not used yet, but prepare for sending and receiving tls-crypt-v2 handshake messages. Signed-off-by: Steffan Karger --- src/openvpn/ps.c | 3 ++- src/openvpn/ssl.c | 23 ++++++++++++++++++----- src/openvpn/ssl.h | 5 ++++- src/openvpn/ssl_common.h | 2 ++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/openvpn/ps.c b/src/openvpn/ps.c index 5136a20..6c37753 100644 --- a/src/openvpn/ps.c +++ b/src/openvpn/ps.c @@ -985,7 +985,8 @@ is_openvpn_protocol(const struct buffer *buf) { return p[0] == 0 && p[1] >= 14 - && p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<= 2) { diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 7b42845..d45cfe4 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -785,6 +785,9 @@ packet_opcode_name(int op) case P_CONTROL_HARD_RESET_SERVER_V2: return "P_CONTROL_HARD_RESET_SERVER_V2"; + case P_CONTROL_HARD_RESET_CLIENT_V3: + return "P_CONTROL_HARD_RESET_CLIENT_V3"; + case P_CONTROL_SOFT_RESET_V1: return "P_CONTROL_SOFT_RESET_V1"; @@ -857,7 +860,8 @@ is_hard_reset(int op, int key_method) if (!key_method || key_method >= 2) { - if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2) + if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2 + || op == P_CONTROL_HARD_RESET_CLIENT_V3) { return true; } @@ -1088,8 +1092,15 @@ tls_session_init(struct tls_multi *multi, struct tls_session *session) } else /* session->opt->key_method >= 2 */ { - session->initial_opcode = session->opt->server ? - P_CONTROL_HARD_RESET_SERVER_V2 : P_CONTROL_HARD_RESET_CLIENT_V2; + if (session->opt->server) + { + session->initial_opcode = P_CONTROL_HARD_RESET_SERVER_V2; + } + else + { + session->initial_opcode = session->opt->tls_crypt_v2 ? + P_CONTROL_HARD_RESET_CLIENT_V3 : P_CONTROL_HARD_RESET_CLIENT_V2; + } } /* Initialize control channel authentication parameters */ @@ -3417,7 +3428,8 @@ tls_pre_decrypt(struct tls_multi *multi, { /* verify client -> server or server -> client connection */ if (((op == P_CONTROL_HARD_RESET_CLIENT_V1 - || op == P_CONTROL_HARD_RESET_CLIENT_V2) && !multi->opt.server) + || op == P_CONTROL_HARD_RESET_CLIENT_V2 + || op == P_CONTROL_HARD_RESET_CLIENT_V3) && !multi->opt.server) || ((op == P_CONTROL_HARD_RESET_SERVER_V1 || op == P_CONTROL_HARD_RESET_SERVER_V2) && multi->opt.server)) { @@ -3802,7 +3814,8 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas, /* this packet is from an as-yet untrusted source, so * scrutinize carefully */ - if (op != P_CONTROL_HARD_RESET_CLIENT_V2) + if (op != P_CONTROL_HARD_RESET_CLIENT_V2 + && op != P_CONTROL_HARD_RESET_CLIENT_V3) { /* * This can occur due to bogus data or DoS packets. diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h index dd1ab0f..1a138ab 100644 --- a/src/openvpn/ssl.h +++ b/src/openvpn/ssl.h @@ -63,9 +63,12 @@ #define P_CONTROL_HARD_RESET_CLIENT_V2 7 /* initial key from client, forget previous state */ #define P_CONTROL_HARD_RESET_SERVER_V2 8 /* initial key from server, forget previous state */ +/* indicates key_method >= 2 and client-specific tls-crypt key */ +#define P_CONTROL_HARD_RESET_CLIENT_V3 10 /* initial key from client, forget previous state */ + /* define the range of legal opcodes */ #define P_FIRST_OPCODE 1 -#define P_LAST_OPCODE 9 +#define P_LAST_OPCODE 10 /* * Set the max number of acknowledgments that can "hitch a ride" on an outgoing diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 60ed5f8..7e8f7d9 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -286,6 +286,8 @@ struct tls_options const char *config_authname; bool ncp_enabled; + bool tls_crypt_v2; + /** TLS handshake wrapping state */ struct tls_wrap_ctx tls_wrap; From patchwork Fri Dec 8 01:07:46 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 149 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director6.mail.ord1d.rsapps.net ([172.27.255.52]) by backend31.mail.ord1d.rsapps.net (Dovecot) with LMTP id /WWCAQWBKlrLagAAgoeIoA for ; Fri, 08 Dec 2017 07:09:41 -0500 Received: from proxy18.mail.iad3a.rsapps.net ([172.27.255.52]) by director6.mail.ord1d.rsapps.net (Dovecot) with LMTP id K7y4JgWBKlr2JAAAhgvE6Q ; Fri, 08 Dec 2017 07:09:41 -0500 Received: from smtp27.gate.iad3a ([172.27.255.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy18.mail.iad3a.rsapps.net (Dovecot) with LMTP id s+hrBQWBKlqAQQAAon3hFg ; Fri, 08 Dec 2017 07:09:41 -0500 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.34.181.88] Authentication-Results: smtp27.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.34.181.88"; 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; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Classification-ID: 944e437c-dc10-11e7-a07f-782bcb33f754-1-1 Received: from [216.34.181.88] ([216.34.181.88:2500] helo=lists.sourceforge.net) by smtp27.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 3A/79-23589-ED08A2A5; Fri, 08 Dec 2017 07:09:02 -0500 Received: from localhost ([127.0.0.1] helo=sfs-ml-2.v29.ch3.sourceforge.com) by sfs-ml-2.v29.ch3.sourceforge.com with esmtp (Exim 4.89) (envelope-from ) id 1eNHS5-0004wY-7s; Fri, 08 Dec 2017 12:08:25 +0000 Received: from sfi-mx-2.v28.ch3.sourceforge.com ([172.29.28.192] helo=mx.sourceforge.net) by sfs-ml-2.v29.ch3.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.89) (envelope-from ) id 1eNHS3-0004w8-FM for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:23 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Type:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:CC:To:From:Sender:Reply-To:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=OFRNtpLfDR2YGd7IVVJGTqzpsfqpA05QWLKsXfftVFE=; b=e0ebf2vBo/GT4veVT7GkCvHCmG YrstGt7CWsL3GnNQ/daNvd/TkCKe4Vkk24vxT7XafL31XuYyRvZaqTCd2FConhABXrk8j/GvDraFZ NeotJ3ULxsI5UDw3nHt233JGRSiPl3y7X1XeKk37W9A+57/J3+sOroEc+drdc3S5jqfo=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject: CC:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=OFRNtpLfDR2YGd7IVVJGTqzpsfqpA05QWLKsXfftVFE=; b=nEHCPWEUm2UgL74J9Ns0ASFh+n 3elrVQveMhaXHV/rgnTkVRLclz50ddg/iufVWfRdme6p+5Hc/VzpIn5idLYyzSM+iDOupl0+27/Tu s5VjR9rcYUIL86k2Mvo3vFuCWLz+e9PUQNVk5tTqxfEKlbQqEk+uwTCPIbvhRbjlui4s=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-2.v28.ch3.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.89) id 1eNHS1-0004Kz-3Q for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:23 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id BFF1C1C5244 for ; Fri, 8 Dec 2017 13:08:07 +0100 (CET) Received: from steffan-fox.fox.local (172.16.5.166) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Fri, 8 Dec 2017 13:08:07 +0100 From: Steffan Karger To: Date: Fri, 8 Dec 2017 13:07:46 +0100 Message-ID: <1512734870-17133-7-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> MIME-Version: 1.0 X-ClientProxiedBy: FOXDFT52.FOX.local (10.0.0.129) To FOXDFT52.FOX.local (10.0.0.129) X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -0.0 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1eNHS1-0004Kz-3Q Subject: [Openvpn-devel] [PATCH 06/10] tls-crypt-v2: implement tls-crypt-v2 handshake 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: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox This makes clients send-and-use, and servers receive-unwrap-and-use tls-crypt-v2 client keys, which completes the on-the-wire work. Signed-off-by: Steffan Karger --- src/openvpn/init.c | 39 +++++++++++++++++- src/openvpn/openvpn.h | 2 + src/openvpn/options.c | 5 +++ src/openvpn/ssl.c | 79 ++++++++++++++++++++++++++++-------- src/openvpn/ssl_common.h | 6 +++ src/openvpn/tls_crypt.c | 50 +++++++++++++++++++++++ src/openvpn/tls_crypt.h | 14 +++++++ tests/unit_tests/openvpn/Makefile.am | 2 +- 8 files changed, 179 insertions(+), 18 deletions(-) diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 3e694b9..a4603b3 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -2432,6 +2432,9 @@ key_schedule_free(struct key_schedule *ks, bool free_ssl_ctx) { tls_ctx_free(&ks->ssl_ctx); free_key_ctx_bi(&ks->tls_wrap_key); + free_key_ctx(&ks->tls_crypt_v2_server_key); + buf_clear(&ks->tls_crypt_v2_wkc); + free_buf(&ks->tls_crypt_v2_wkc); } CLEAR(*ks); } @@ -2600,6 +2603,24 @@ do_init_crypto_tls_c1(struct context *c) options->tls_crypt_inline, options->tls_server); } + /* tls-crypt with client-specific keys (--tls-crypt-v2) */ + if (options->tls_crypt_v2_file) + { + if (options->tls_server) + { + tls_crypt_v2_init_server_key(&c->c1.ks.tls_crypt_v2_server_key, + true, options->tls_crypt_v2_file, + options->tls_crypt_v2_inline); + } + else + { + tls_crypt_v2_init_client_key(&c->c1.ks.tls_wrap_key, + &c->c1.ks.tls_crypt_v2_wkc, + options->tls_crypt_v2_file, + options->tls_crypt_v2_inline); + } + } + c->c1.ciphername = options->ciphername; c->c1.authname = options->authname; c->c1.keysize = options->keysize; @@ -2819,13 +2840,28 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) } /* TLS handshake encryption (--tls-crypt) */ - if (options->tls_crypt_file) + if (options->tls_crypt_file + || (options->tls_crypt_v2_file && options->tls_client)) { to.tls_wrap.mode = TLS_WRAP_CRYPT; to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key; to.tls_wrap.opt.pid_persist = &c->c1.pid_persist; to.tls_wrap.opt.flags |= CO_PACKET_ID_LONG_FORM; tls_crypt_adjust_frame_parameters(&to.frame); + + if (options->tls_crypt_v2_file) + { + to.tls_wrap.tls_crypt_v2_wkc = &c->c1.ks.tls_crypt_v2_wkc; + } + } + + if (options->tls_crypt_v2_file) + { + to.tls_crypt_v2 = true; + if (options->tls_server) + { + to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key; + } } /* If we are running over TCP, allow for @@ -4349,6 +4385,7 @@ inherit_context_child(struct context *dest, dest->c1.ks.ssl_ctx = src->c1.ks.ssl_ctx; dest->c1.ks.tls_wrap_key = src->c1.ks.tls_wrap_key; dest->c1.ks.tls_auth_key_type = src->c1.ks.tls_auth_key_type; + dest->c1.ks.tls_crypt_v2_server_key = src->c1.ks.tls_crypt_v2_server_key; /* inherit pre-NCP ciphers */ dest->c1.ciphername = src->c1.ciphername; dest->c1.authname = src->c1.authname; diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h index db16842..38b4822 100644 --- a/src/openvpn/openvpn.h +++ b/src/openvpn/openvpn.h @@ -66,6 +66,8 @@ struct key_schedule /* optional TLS control channel wrapping */ struct key_type tls_auth_key_type; struct key_ctx_bi tls_wrap_key; + struct key_ctx tls_crypt_v2_server_key; + struct buffer tls_crypt_v2_wkc; /**< Wrapped client key */ }; /* diff --git a/src/openvpn/options.c b/src/openvpn/options.c index aa73fd9..89bff1e 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -2704,6 +2704,11 @@ options_postprocess_verify_ce(const struct options *options, const struct connec { msg(M_USAGE, "--tls-auth and --tls-crypt are mutually exclusive"); } + if (options->client && options->tls_crypt_v2_file + && (options->tls_auth_file || options->tls_crypt_file)) + { + msg(M_USAGE, "--tls-crypt-v2, --tls-auth and --tls-crypt are mutually exclusive in client mode"); + } } else { diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index d45cfe4..6090826 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1048,6 +1048,23 @@ tls_session_user_pass_enabled(struct tls_session *session) /** @addtogroup control_processor * @{ */ +/** Free the elements of a tls_wrap_ctx structure */ +static void tls_wrap_free(struct tls_wrap_ctx *tls_wrap) +{ + if (packet_id_initialized(&tls_wrap->opt.packet_id)) + { + packet_id_free(&tls_wrap->opt.packet_id); + } + + if (tls_wrap->cleanup_key_ctx) + { + free_key_ctx_bi(&tls_wrap->opt.key_ctx_bi); + } + + free_buf(&tls_wrap->tls_crypt_v2_metadata); + free_buf(&tls_wrap->work); +} + /** @name Functions for initialization and cleanup of tls_session structures * @{ */ @@ -1140,16 +1157,9 @@ tls_session_init(struct tls_multi *multi, struct tls_session *session) static void tls_session_free(struct tls_session *session, bool clear) { - int i; - - if (packet_id_initialized(&session->tls_wrap.opt.packet_id)) - { - packet_id_free(&session->tls_wrap.opt.packet_id); - } + tls_wrap_free(&session->tls_wrap); - free_buf(&session->tls_wrap.work); - - for (i = 0; i < KS_SIZE; ++i) + for (size_t i = 0; i < KS_SIZE; ++i) { key_state_free(&session->key[i], false); } @@ -1475,6 +1485,8 @@ write_control_auth(struct tls_session *session, ASSERT(reliable_ack_write (ks->rec_ack, buf, &ks->session_id_remote, max_ack, prepend_ack)); + msg(D_TLS_DEBUG, "%s(): %s", __func__, packet_opcode_name(opcode)); + if (session->tls_wrap.mode == TLS_WRAP_AUTH || session->tls_wrap.mode == TLS_WRAP_NONE) { @@ -1492,17 +1504,26 @@ write_control_auth(struct tls_session *session, ASSERT(buf_init(&session->tls_wrap.work, buf->offset)); ASSERT(buf_write(&session->tls_wrap.work, &header, sizeof(header))); ASSERT(session_id_write(&session->session_id, &session->tls_wrap.work)); - if (tls_crypt_wrap(buf, &session->tls_wrap.work, &session->tls_wrap.opt)) - { - /* Don't change the original data in buf, it's used by the reliability - * layer to resend on failure. */ - *buf = session->tls_wrap.work; - } - else + if (!tls_crypt_wrap(buf, &session->tls_wrap.work, &session->tls_wrap.opt)) { buf->len = 0; return; } + + if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3) + { + if (!buf_copy(&session->tls_wrap.work, + session->tls_wrap.tls_crypt_v2_wkc)) + { + msg(D_TLS_ERRORS, "Could not append tls-crypt-v2 client key"); + buf->len = 0; + return; + } + } + + /* Don't change the original data in buf, it's used by the reliability + * layer to resend on failure. */ + *buf = session->tls_wrap.work; } *to_link_addr = &ks->remote_addr; } @@ -1518,6 +1539,16 @@ read_control_auth(struct buffer *buf, struct gc_arena gc = gc_new(); bool ret = false; + const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT; + if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3 + && !tls_crypt_v2_extract_client_key(buf, ctx)) + { + msg (D_TLS_ERRORS, + "TLS Error: can not extract tls-crypt-v2 client key from %s", + print_link_socket_actual(from, &gc)); + goto cleanup; + } + if (ctx->mode == TLS_WRAP_AUTH) { struct buffer null = clear_buf(); @@ -1557,6 +1588,18 @@ read_control_auth(struct buffer *buf, ASSERT(buf_copy(buf, &tmp)); buf_clear(&tmp); } + else if (ctx->tls_crypt_v2_server_key.cipher) + { + /* If tls-crypt-v2 is enabled, require *some* wrapping */ + msg(D_TLS_ERRORS, "TLS Error: could not determine wrapping from %s", + print_link_socket_actual(from, &gc)); + /* TODO Do we want to support using tls-crypt-v2 and no control channel + * wrapping at all simultaneously? That would allow server admins to + * upgrade clients one-by-one without running a second instance, but we + * should not enable it by default because it breaks DoS-protection. + * So, add something like --tls-crypt-v2-allow-insecure-fallback ? */ + goto cleanup; + } if (ctx->mode == TLS_WRAP_NONE || ctx->mode == TLS_WRAP_AUTH) { @@ -3854,6 +3897,10 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas, /* HMAC test, if --tls-auth was specified */ status = read_control_auth(&newbuf, &tls_wrap_tmp, from); free_buf(&newbuf); + if (tls_wrap_tmp.cleanup_key_ctx) + { + free_key_ctx_bi(&tls_wrap_tmp.opt.key_ctx_bi); + } if (!status) { goto error; diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 7e8f7d9..d6e0d1b 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -213,6 +213,12 @@ struct tls_wrap_ctx } mode; /**< Control channel wrapping mode */ struct crypto_options opt; /**< Crypto state */ struct buffer work; /**< Work buffer (only for --tls-crypt) */ + struct key_ctx tls_crypt_v2_server_key; /**< Decrypts client keys */ + const struct buffer *tls_crypt_v2_wkc; /**< Wrapped client key, + sent to server */ + struct buffer tls_crypt_v2_metadata; /**< Received from client */ + bool cleanup_key_ctx; /**< opt.key_ctx_bi is owned by + this context */ }; /* diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index 82d7037..0c94d73 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -33,6 +33,7 @@ #include "crypto.h" #include "platform.h" #include "session_id.h" +#include "ssl.h" #include "tls_crypt.h" @@ -519,6 +520,55 @@ error_exit: return ret; } +bool +tls_crypt_v2_extract_client_key(struct buffer *buf, + struct tls_wrap_ctx *ctx) +{ + static const int hard_reset_length = + TLS_CRYPT_OFF_CT + sizeof(uint8_t) + sizeof(packet_id_type); + + if (!ctx->tls_crypt_v2_server_key.cipher) + { + msg (D_TLS_ERRORS, + "Client want tls-crypt-v2, but no server key present."); + return false; + } + + msg (D_HANDSHAKE, "Control Channel: using tls-crypt-v2 key"); + + struct buffer wrapped_client_key = *buf; + if (!buf_advance(&wrapped_client_key, hard_reset_length)) + { + msg (D_TLS_ERRORS, "Can not locate tls-crypt-v2 client key"); + return false; + } + + struct key2 client_key = { 0 }; + ctx->tls_crypt_v2_metadata = alloc_buf(TLS_CRYPT_V2_MAX_METADATA_LEN); + if (!tls_crypt_v2_unwrap_client_key(&client_key, + &ctx->tls_crypt_v2_metadata, + wrapped_client_key, + &ctx->tls_crypt_v2_server_key)) + { + msg (D_TLS_ERRORS, "Can not unwrap tls-crypt-v2 client key"); + secure_memzero(&client_key, sizeof(client_key)); + return false; + } + + /* Load the decrypted key */ + ctx->mode = TLS_WRAP_CRYPT; + ctx->cleanup_key_ctx = true; + ctx->opt.flags |= CO_PACKET_ID_LONG_FORM; + memset(&ctx->opt.key_ctx_bi, 0, sizeof(ctx->opt.key_ctx_bi)); + tls_crypt_v2_load_client_key(&ctx->opt.key_ctx_bi, &client_key, true); + secure_memzero(&client_key, sizeof(client_key)); + + /* Remove client key from buffer so tls-crypt code can unwrap message */ + ASSERT(buf_inc_len(buf, -(BLEN(&wrapped_client_key)))); + + return true; +} + void tls_crypt_v2_write_server_key_file(const char *filename) { diff --git a/src/openvpn/tls_crypt.h b/src/openvpn/tls_crypt.h index f4d3ca7..e720729 100644 --- a/src/openvpn/tls_crypt.h +++ b/src/openvpn/tls_crypt.h @@ -83,6 +83,7 @@ #include "buffer.h" #include "crypto.h" #include "session_id.h" +#include "ssl_common.h" #define TLS_CRYPT_TAG_SIZE (256/8) #define TLS_CRYPT_PID_SIZE (sizeof(packet_id_type) + sizeof(net_time_t)) @@ -182,6 +183,19 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key, const char *key_inline); /** + * Extract a tls-crypt-v2 client key from a P_CONTROL_HARD_RESET_CLIENT_V3 + * message, and load the key into the supplied tls wrap context. + * + * @param buf Buffer containing a received P_CONTROL_HARD_RESET_CLIENT_V3 + * message. + * @param ctx tls-wrap context to be initialized with the client key. + * + * @returns true if a key was successfully extracted. + */ +bool tls_crypt_v2_extract_client_key(struct buffer *buf, + struct tls_wrap_ctx *ctx); + +/** * Generate a tls-crypt-v2 server key, and write to file. * * @param filename Filename of the server key file to create. diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index 937393d..c457ef9 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -56,7 +56,7 @@ packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \ tls_crypt_testdriver_CFLAGS = @TEST_CFLAGS@ \ -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \ - $(OPTIONAL_CRYPTO_CFLAGS) + $(OPTIONAL_CRYPTO_CFLAGS) $(OPTIONAL_PKCS11_HELPER_CFLAGS) tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ $(OPTIONAL_CRYPTO_LIBS) tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \ From patchwork Fri Dec 8 01:07:47 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 147 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director5.mail.ord1d.rsapps.net ([172.27.255.51]) by backend31.mail.ord1d.rsapps.net (Dovecot) with LMTP id A18+NfKAKlpmJwAAgoeIoA for ; Fri, 08 Dec 2017 07:09:22 -0500 Received: from proxy15.mail.iad3a.rsapps.net ([172.27.255.51]) by director5.mail.ord1d.rsapps.net (Dovecot) with LMTP id 1WLyAPKAKlpHfgAAsdCWiw ; Fri, 08 Dec 2017 07:09:22 -0500 Received: from smtp21.gate.iad3a ([172.27.255.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy15.mail.iad3a.rsapps.net (Dovecot) with LMTP id SuuGCfKAKlqaWwAAHi9b9g ; Fri, 08 Dec 2017 07:09:22 -0500 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.34.181.88] Authentication-Results: smtp21.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.34.181.88"; 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; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Classification-ID: 9f0431aa-dc10-11e7-bfd2-bc305bf59800-1-1 Received: from [216.34.181.88] ([216.34.181.88:7418] helo=lists.sourceforge.net) by smtp21.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 74/D4-19545-0F08A2A5; Fri, 08 Dec 2017 07:09:20 -0500 Received: from localhost ([127.0.0.1] helo=sfs-ml-3.v29.ch3.sourceforge.com) by sfs-ml-3.v29.ch3.sourceforge.com with esmtp (Exim 4.89) (envelope-from ) id 1eNHS5-0001Vm-J2; Fri, 08 Dec 2017 12:08:25 +0000 Received: from sfi-mx-3.v28.ch3.sourceforge.com ([172.29.28.193] helo=mx.sourceforge.net) by sfs-ml-3.v29.ch3.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.89) (envelope-from ) id 1eNHS3-0001V0-Sn for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:23 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Type:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:CC:To:From:Sender:Reply-To:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=jJY7zeGx3dieheJ3LEAsvtGtaVsljlYVCiNvjRNLaMU=; b=TA4Q2lBHOqKdnfX8U5yvXcqtg9 IVPPZgjL3It30PrBPdp0rxTbXoOzJSGCChb5S9Y3AhaXUYDGNyvGQ4+jj3My3CB9nC3qy7Xcj1EpI 3HVkZ3GcH5aW8s2HaP8yqyi9UirNW5ycv/4UgTvFK7/McnKkleQNtGA4MxzNBNtDjx8g=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject: CC:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=jJY7zeGx3dieheJ3LEAsvtGtaVsljlYVCiNvjRNLaMU=; b=aNUvg1irdWDN25jUAkv1X8nv/h xfexucbmJ+ou8/WrVVnn3JRvtf3f+aEMyFkekWARfu1vxXgA8gcZmV1/qBppJKYq24Ts7RAc7eaVt nAlDYTDzf72ZpP6nlCdqmZeE3cxoYpmfAUU9JXyLmLoz3ztcTlAqLgMGnAt9VVFdsCYc=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-3.v28.ch3.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.89) id 1eNHS1-0007So-A2 for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:23 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id CB4481C5245; Fri, 8 Dec 2017 13:08:07 +0100 (CET) Received: from steffan-fox.fox.local (172.16.5.166) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Fri, 8 Dec 2017 13:08:07 +0100 From: Steffan Karger To: Date: Fri, 8 Dec 2017 13:07:47 +0100 Message-ID: <1512734870-17133-8-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> MIME-Version: 1.0 X-ClientProxiedBy: FOXDFT52.FOX.local (10.0.0.129) To FOXDFT52.FOX.local (10.0.0.129) X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -0.0 SPF_PASS SPF: sender matches SPF record -0.0 AWL AWL: Adjusted score from AWL reputation of From: address X-Headers-End: 1eNHS1-0007So-A2 Subject: [Openvpn-devel] [PATCH 07/10] Move env helper functions into their own module/file 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: , Cc: Antonio Quartulli Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox To avoid a dependency on misc.c - which is a dependency mess - in the tls-crypt unit tests, split the env_set functionality out into it's own file. Signed-off-by: Antonio Quartulli Signed-off-by: Steffan Karger Acked-by: Gert Doering Acked-by: Gert Doering --- src/openvpn/Makefile.am | 1 + src/openvpn/argv.c | 1 + src/openvpn/env_set.c | 459 +++++++++++++++++++++++++++++++++++ src/openvpn/env_set.h | 121 +++++++++ src/openvpn/misc.c | 441 --------------------------------- src/openvpn/misc.h | 73 +----- tests/unit_tests/openvpn/Makefile.am | 1 + 7 files changed, 584 insertions(+), 513 deletions(-) create mode 100644 src/openvpn/env_set.c create mode 100644 src/openvpn/env_set.h diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index fcc22d6..441700a 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -51,6 +51,7 @@ openvpn_SOURCES = \ crypto_openssl.c crypto_openssl.h \ crypto_mbedtls.c crypto_mbedtls.h \ dhcp.c dhcp.h \ + env_set.c env_set.h \ errlevel.h \ error.c error.h \ event.c event.h \ diff --git a/src/openvpn/argv.c b/src/openvpn/argv.c index 95bdfea..7a41169 100644 --- a/src/openvpn/argv.c +++ b/src/openvpn/argv.c @@ -37,6 +37,7 @@ #include "argv.h" #include "integer.h" +#include "env_set.h" #include "options.h" static void diff --git a/src/openvpn/env_set.c b/src/openvpn/env_set.c new file mode 100644 index 0000000..2a22e0b --- /dev/null +++ b/src/openvpn/env_set.c @@ -0,0 +1,459 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2017 OpenVPN Technologies, Inc. + * Copyright (C) 2014-2015 David Sommerseth + * Copyright (C) 2016-2017 David Sommerseth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#include "syshead.h" + +#include "misc.h" + +#include "env_set.h" + +/* + * Set environmental variable (int or string). + * + * On Posix, we use putenv for portability, + * and put up with its painful semantics + * that require all the support code below. + */ + +/* General-purpose environmental variable set functions */ + +static char * +construct_name_value(const char *name, const char *value, struct gc_arena *gc) +{ + struct buffer out; + + ASSERT(name); + if (!value) + { + value = ""; + } + out = alloc_buf_gc(strlen(name) + strlen(value) + 2, gc); + buf_printf(&out, "%s=%s", name, value); + return BSTR(&out); +} + +static bool +env_string_equal(const char *s1, const char *s2) +{ + int c1, c2; + ASSERT(s1); + ASSERT(s2); + + while (true) + { + c1 = *s1++; + c2 = *s2++; + if (c1 == '=') + { + c1 = 0; + } + if (c2 == '=') + { + c2 = 0; + } + if (!c1 && !c2) + { + return true; + } + if (c1 != c2) + { + break; + } + } + return false; +} + +static bool +remove_env_item(const char *str, const bool do_free, struct env_item **list) +{ + struct env_item *current, *prev; + + ASSERT(str); + ASSERT(list); + + for (current = *list, prev = NULL; current != NULL; current = current->next) + { + if (env_string_equal(current->string, str)) + { + if (prev) + { + prev->next = current->next; + } + else + { + *list = current->next; + } + if (do_free) + { + secure_memzero(current->string, strlen(current->string)); + free(current->string); + free(current); + } + return true; + } + prev = current; + } + return false; +} + +static void +add_env_item(char *str, const bool do_alloc, struct env_item **list, struct gc_arena *gc) +{ + struct env_item *item; + + ASSERT(str); + ASSERT(list); + + ALLOC_OBJ_GC(item, struct env_item, gc); + item->string = do_alloc ? string_alloc(str, gc) : str; + item->next = *list; + *list = item; +} + +/* struct env_set functions */ + +static bool +env_set_del_nolock(struct env_set *es, const char *str) +{ + return remove_env_item(str, es->gc == NULL, &es->list); +} + +static void +env_set_add_nolock(struct env_set *es, const char *str) +{ + remove_env_item(str, es->gc == NULL, &es->list); + add_env_item((char *)str, true, &es->list, es->gc); +} + +struct env_set * +env_set_create(struct gc_arena *gc) +{ + struct env_set *es; + ALLOC_OBJ_CLEAR_GC(es, struct env_set, gc); + es->list = NULL; + es->gc = gc; + return es; +} + +void +env_set_destroy(struct env_set *es) +{ + if (es && es->gc == NULL) + { + struct env_item *e = es->list; + while (e) + { + struct env_item *next = e->next; + free(e->string); + free(e); + e = next; + } + free(es); + } +} + +bool +env_set_del(struct env_set *es, const char *str) +{ + bool ret; + ASSERT(es); + ASSERT(str); + ret = env_set_del_nolock(es, str); + return ret; +} + +void +env_set_add(struct env_set *es, const char *str) +{ + ASSERT(es); + ASSERT(str); + env_set_add_nolock(es, str); +} + +const char * +env_set_get(const struct env_set *es, const char *name) +{ + const struct env_item *item = es->list; + while (item && !env_string_equal(item->string, name)) + { + item = item->next; + } + return item ? item->string : NULL; +} + +void +env_set_print(int msglevel, const struct env_set *es) +{ + if (check_debug_level(msglevel)) + { + const struct env_item *e; + int i; + + if (es) + { + e = es->list; + i = 0; + + while (e) + { + if (env_safe_to_print(e->string)) + { + msg(msglevel, "ENV [%d] '%s'", i, e->string); + } + ++i; + e = e->next; + } + } + } +} + +void +env_set_inherit(struct env_set *es, const struct env_set *src) +{ + const struct env_item *e; + + ASSERT(es); + + if (src) + { + e = src->list; + while (e) + { + env_set_add_nolock(es, e->string); + e = e->next; + } + } +} + + +/* add/modify/delete environmental strings */ + +void +setenv_counter(struct env_set *es, const char *name, counter_type value) +{ + char buf[64]; + openvpn_snprintf(buf, sizeof(buf), counter_format, value); + setenv_str(es, name, buf); +} + +void +setenv_int(struct env_set *es, const char *name, int value) +{ + char buf[64]; + openvpn_snprintf(buf, sizeof(buf), "%d", value); + setenv_str(es, name, buf); +} + +void +setenv_long_long(struct env_set *es, const char *name, long long value) +{ + char buf[64]; + openvpn_snprintf(buf, sizeof(buf), "%lld", value); + setenv_str(es, name, buf); +} + +void +setenv_str(struct env_set *es, const char *name, const char *value) +{ + setenv_str_ex(es, name, value, CC_NAME, 0, 0, CC_PRINT, 0, 0); +} + +void +setenv_str_safe(struct env_set *es, const char *name, const char *value) +{ + uint8_t b[64]; + struct buffer buf; + buf_set_write(&buf, b, sizeof(b)); + if (buf_printf(&buf, "OPENVPN_%s", name)) + { + setenv_str(es, BSTR(&buf), value); + } + else + { + msg(M_WARN, "setenv_str_safe: name overflow"); + } +} + +void +setenv_str_incr(struct env_set *es, const char *name, const char *value) +{ + unsigned int counter = 1; + const size_t tmpname_len = strlen(name) + 5; /* 3 digits counter max */ + char *tmpname = gc_malloc(tmpname_len, true, NULL); + strcpy(tmpname, name); + while (NULL != env_set_get(es, tmpname) && counter < 1000) + { + ASSERT(openvpn_snprintf(tmpname, tmpname_len, "%s_%u", name, counter)); + counter++; + } + if (counter < 1000) + { + setenv_str(es, tmpname, value); + } + else + { + msg(D_TLS_DEBUG_MED, "Too many same-name env variables, ignoring: %s", name); + } + free(tmpname); +} + +void +setenv_del(struct env_set *es, const char *name) +{ + ASSERT(name); + setenv_str(es, name, NULL); +} + +void +setenv_str_ex(struct env_set *es, + const char *name, + const char *value, + const unsigned int name_include, + const unsigned int name_exclude, + const char name_replace, + const unsigned int value_include, + const unsigned int value_exclude, + const char value_replace) +{ + struct gc_arena gc = gc_new(); + const char *name_tmp; + const char *val_tmp = NULL; + + ASSERT(name && strlen(name) > 1); + + name_tmp = string_mod_const(name, name_include, name_exclude, name_replace, &gc); + + if (value) + { + val_tmp = string_mod_const(value, value_include, value_exclude, value_replace, &gc); + } + + ASSERT(es); + + if (val_tmp) + { + const char *str = construct_name_value(name_tmp, val_tmp, &gc); + env_set_add(es, str); +#if DEBUG_VERBOSE_SETENV + msg(M_INFO, "SETENV_ES '%s'", str); +#endif + } + else + { + env_set_del(es, name_tmp); + } + + gc_free(&gc); +} + +/* + * Setenv functions that append an integer index to the name + */ +static const char * +setenv_format_indexed_name(const char *name, const int i, struct gc_arena *gc) +{ + struct buffer out = alloc_buf_gc(strlen(name) + 16, gc); + if (i >= 0) + { + buf_printf(&out, "%s_%d", name, i); + } + else + { + buf_printf(&out, "%s", name); + } + return BSTR(&out); +} + +void +setenv_int_i(struct env_set *es, const char *name, const int value, const int i) +{ + struct gc_arena gc = gc_new(); + const char *name_str = setenv_format_indexed_name(name, i, &gc); + setenv_int(es, name_str, value); + gc_free(&gc); +} + +void +setenv_str_i(struct env_set *es, const char *name, const char *value, const int i) +{ + struct gc_arena gc = gc_new(); + const char *name_str = setenv_format_indexed_name(name, i, &gc); + setenv_str(es, name_str, value); + gc_free(&gc); +} + +bool +env_allowed(const char *str) +{ + return (script_security >= SSEC_PW_ENV || !is_password_env_var(str)); +} + +/* Make arrays of strings */ + +const char ** +make_env_array(const struct env_set *es, + const bool check_allowed, + struct gc_arena *gc) +{ + char **ret = NULL; + struct env_item *e = NULL; + int i = 0, n = 0; + + /* figure length of es */ + if (es) + { + for (e = es->list; e != NULL; e = e->next) + { + ++n; + } + } + + /* alloc return array */ + ALLOC_ARRAY_CLEAR_GC(ret, char *, n+1, gc); + + /* fill return array */ + if (es) + { + i = 0; + for (e = es->list; e != NULL; e = e->next) + { + if (!check_allowed || env_allowed(e->string)) + { + ASSERT(i < n); + ret[i++] = e->string; + } + } + } + + ret[i] = NULL; + return (const char **)ret; +} diff --git a/src/openvpn/env_set.h b/src/openvpn/env_set.h new file mode 100644 index 0000000..5dc3348 --- /dev/null +++ b/src/openvpn/env_set.h @@ -0,0 +1,121 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2017 OpenVPN Technologies, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef ENV_SET_H +#define ENV_SET_H + +#include "argv.h" +#include "basic.h" +#include "buffer.h" +#include "common.h" + +/* + * Handle environmental variable lists + */ + +struct env_item { + char *string; + struct env_item *next; +}; + +struct env_set { + struct gc_arena *gc; + struct env_item *list; +}; + +/* set/delete environmental variable */ +void setenv_str_ex(struct env_set *es, + const char *name, + const char *value, + const unsigned int name_include, + const unsigned int name_exclude, + const char name_replace, + const unsigned int value_include, + const unsigned int value_exclude, + const char value_replace); + +void setenv_counter(struct env_set *es, const char *name, counter_type value); + +void setenv_int(struct env_set *es, const char *name, int value); + +void setenv_long_long(struct env_set *es, const char *name, long long value); + +void setenv_str(struct env_set *es, const char *name, const char *value); + +void setenv_str_safe(struct env_set *es, const char *name, const char *value); + +void setenv_del(struct env_set *es, const char *name); + +/** + * Store the supplied name value pair in the env_set. If the variable with the + * supplied name already exists, append _N to the name, starting at N=1. + */ +void setenv_str_incr(struct env_set *es, const char *name, const char *value); + +void setenv_int_i(struct env_set *es, const char *name, const int value, const int i); + +void setenv_str_i(struct env_set *es, const char *name, const char *value, const int i); + +/* struct env_set functions */ + +struct env_set *env_set_create(struct gc_arena *gc); + +void env_set_destroy(struct env_set *es); + +bool env_set_del(struct env_set *es, const char *str); + +void env_set_add(struct env_set *es, const char *str); + +const char *env_set_get(const struct env_set *es, const char *name); + +void env_set_print(int msglevel, const struct env_set *es); + +void env_set_inherit(struct env_set *es, const struct env_set *src); + +/* returns true if environmental variable name starts with 'password' */ +static inline bool is_password_env_var(const char *str) +{ + return (strncmp(str, "password", 8) == 0); +} + +/* returns true if environmental variable safe to print to log */ +static inline bool env_safe_to_print(const char *str) +{ +#ifndef UNSAFE_DEBUG + if (is_password_env_var(str)) + { + return false; + } +#endif + return true; +} + +/* returns true if environmental variable may be passed to an external program */ +bool env_allowed(const char *str); + +const char **make_env_array(const struct env_set *es, + const bool check_allowed, + struct gc_arena *gc); + +#endif /* ifndef ENV_SET_H */ diff --git a/src/openvpn/misc.c b/src/openvpn/misc.c index 76b592f..ad26f3d 100644 --- a/src/openvpn/misc.c +++ b/src/openvpn/misc.c @@ -314,382 +314,6 @@ openvpn_popen(const struct argv *a, const struct env_set *es) } - -/* - * Set environmental variable (int or string). - * - * On Posix, we use putenv for portability, - * and put up with its painful semantics - * that require all the support code below. - */ - -/* General-purpose environmental variable set functions */ - -static char * -construct_name_value(const char *name, const char *value, struct gc_arena *gc) -{ - struct buffer out; - - ASSERT(name); - if (!value) - { - value = ""; - } - out = alloc_buf_gc(strlen(name) + strlen(value) + 2, gc); - buf_printf(&out, "%s=%s", name, value); - return BSTR(&out); -} - -static bool -env_string_equal(const char *s1, const char *s2) -{ - int c1, c2; - ASSERT(s1); - ASSERT(s2); - - while (true) - { - c1 = *s1++; - c2 = *s2++; - if (c1 == '=') - { - c1 = 0; - } - if (c2 == '=') - { - c2 = 0; - } - if (!c1 && !c2) - { - return true; - } - if (c1 != c2) - { - break; - } - } - return false; -} - -static bool -remove_env_item(const char *str, const bool do_free, struct env_item **list) -{ - struct env_item *current, *prev; - - ASSERT(str); - ASSERT(list); - - for (current = *list, prev = NULL; current != NULL; current = current->next) - { - if (env_string_equal(current->string, str)) - { - if (prev) - { - prev->next = current->next; - } - else - { - *list = current->next; - } - if (do_free) - { - secure_memzero(current->string, strlen(current->string)); - free(current->string); - free(current); - } - return true; - } - prev = current; - } - return false; -} - -static void -add_env_item(char *str, const bool do_alloc, struct env_item **list, struct gc_arena *gc) -{ - struct env_item *item; - - ASSERT(str); - ASSERT(list); - - ALLOC_OBJ_GC(item, struct env_item, gc); - item->string = do_alloc ? string_alloc(str, gc) : str; - item->next = *list; - *list = item; -} - -/* struct env_set functions */ - -static bool -env_set_del_nolock(struct env_set *es, const char *str) -{ - return remove_env_item(str, es->gc == NULL, &es->list); -} - -static void -env_set_add_nolock(struct env_set *es, const char *str) -{ - remove_env_item(str, es->gc == NULL, &es->list); - add_env_item((char *)str, true, &es->list, es->gc); -} - -struct env_set * -env_set_create(struct gc_arena *gc) -{ - struct env_set *es; - ALLOC_OBJ_CLEAR_GC(es, struct env_set, gc); - es->list = NULL; - es->gc = gc; - return es; -} - -void -env_set_destroy(struct env_set *es) -{ - if (es && es->gc == NULL) - { - struct env_item *e = es->list; - while (e) - { - struct env_item *next = e->next; - free(e->string); - free(e); - e = next; - } - free(es); - } -} - -bool -env_set_del(struct env_set *es, const char *str) -{ - bool ret; - ASSERT(es); - ASSERT(str); - ret = env_set_del_nolock(es, str); - return ret; -} - -void -env_set_add(struct env_set *es, const char *str) -{ - ASSERT(es); - ASSERT(str); - env_set_add_nolock(es, str); -} - -const char * -env_set_get(const struct env_set *es, const char *name) -{ - const struct env_item *item = es->list; - while (item && !env_string_equal(item->string, name)) - { - item = item->next; - } - return item ? item->string : NULL; -} - -void -env_set_print(int msglevel, const struct env_set *es) -{ - if (check_debug_level(msglevel)) - { - const struct env_item *e; - int i; - - if (es) - { - e = es->list; - i = 0; - - while (e) - { - if (env_safe_to_print(e->string)) - { - msg(msglevel, "ENV [%d] '%s'", i, e->string); - } - ++i; - e = e->next; - } - } - } -} - -void -env_set_inherit(struct env_set *es, const struct env_set *src) -{ - const struct env_item *e; - - ASSERT(es); - - if (src) - { - e = src->list; - while (e) - { - env_set_add_nolock(es, e->string); - e = e->next; - } - } -} - - -/* add/modify/delete environmental strings */ - -void -setenv_counter(struct env_set *es, const char *name, counter_type value) -{ - char buf[64]; - openvpn_snprintf(buf, sizeof(buf), counter_format, value); - setenv_str(es, name, buf); -} - -void -setenv_int(struct env_set *es, const char *name, int value) -{ - char buf[64]; - openvpn_snprintf(buf, sizeof(buf), "%d", value); - setenv_str(es, name, buf); -} - -void -setenv_long_long(struct env_set *es, const char *name, long long value) -{ - char buf[64]; - openvpn_snprintf(buf, sizeof(buf), "%lld", value); - setenv_str(es, name, buf); -} - -void -setenv_str(struct env_set *es, const char *name, const char *value) -{ - setenv_str_ex(es, name, value, CC_NAME, 0, 0, CC_PRINT, 0, 0); -} - -void -setenv_str_safe(struct env_set *es, const char *name, const char *value) -{ - uint8_t b[64]; - struct buffer buf; - buf_set_write(&buf, b, sizeof(b)); - if (buf_printf(&buf, "OPENVPN_%s", name)) - { - setenv_str(es, BSTR(&buf), value); - } - else - { - msg(M_WARN, "setenv_str_safe: name overflow"); - } -} - -void -setenv_str_incr(struct env_set *es, const char *name, const char *value) -{ - unsigned int counter = 1; - const size_t tmpname_len = strlen(name) + 5; /* 3 digits counter max */ - char *tmpname = gc_malloc(tmpname_len, true, NULL); - strcpy(tmpname, name); - while (NULL != env_set_get(es, tmpname) && counter < 1000) - { - ASSERT(openvpn_snprintf(tmpname, tmpname_len, "%s_%u", name, counter)); - counter++; - } - if (counter < 1000) - { - setenv_str(es, tmpname, value); - } - else - { - msg(D_TLS_DEBUG_MED, "Too many same-name env variables, ignoring: %s", name); - } - free(tmpname); -} - -void -setenv_del(struct env_set *es, const char *name) -{ - ASSERT(name); - setenv_str(es, name, NULL); -} - -void -setenv_str_ex(struct env_set *es, - const char *name, - const char *value, - const unsigned int name_include, - const unsigned int name_exclude, - const char name_replace, - const unsigned int value_include, - const unsigned int value_exclude, - const char value_replace) -{ - struct gc_arena gc = gc_new(); - const char *name_tmp; - const char *val_tmp = NULL; - - ASSERT(name && strlen(name) > 1); - - name_tmp = string_mod_const(name, name_include, name_exclude, name_replace, &gc); - - if (value) - { - val_tmp = string_mod_const(value, value_include, value_exclude, value_replace, &gc); - } - - ASSERT(es); - - if (val_tmp) - { - const char *str = construct_name_value(name_tmp, val_tmp, &gc); - env_set_add(es, str); -#if DEBUG_VERBOSE_SETENV - msg(M_INFO, "SETENV_ES '%s'", str); -#endif - } - else - { - env_set_del(es, name_tmp); - } - - gc_free(&gc); -} - -/* - * Setenv functions that append an integer index to the name - */ -static const char * -setenv_format_indexed_name(const char *name, const int i, struct gc_arena *gc) -{ - struct buffer out = alloc_buf_gc(strlen(name) + 16, gc); - if (i >= 0) - { - buf_printf(&out, "%s_%d", name, i); - } - else - { - buf_printf(&out, "%s", name); - } - return BSTR(&out); -} - -void -setenv_int_i(struct env_set *es, const char *name, const int value, const int i) -{ - struct gc_arena gc = gc_new(); - const char *name_str = setenv_format_indexed_name(name, i, &gc); - setenv_int(es, name_str, value); - gc_free(&gc); -} - -void -setenv_str_i(struct env_set *es, const char *name, const char *value, const int i) -{ - struct gc_arena gc = gc_new(); - const char *name_str = setenv_format_indexed_name(name, i, &gc); - setenv_str(es, name_str, value); - gc_free(&gc); -} - /* return true if filename can be opened for read */ bool test_file(const char *filename) @@ -1288,71 +912,6 @@ safe_print(const char *str, struct gc_arena *gc) return string_mod_const(str, CC_PRINT, CC_CRLF, '.', gc); } -static bool -is_password_env_var(const char *str) -{ - return (strncmp(str, "password", 8) == 0); -} - -bool -env_allowed(const char *str) -{ - return (script_security >= SSEC_PW_ENV || !is_password_env_var(str)); -} - -bool -env_safe_to_print(const char *str) -{ -#ifndef UNSAFE_DEBUG - if (is_password_env_var(str)) - { - return false; - } -#endif - return true; -} - -/* Make arrays of strings */ - -const char ** -make_env_array(const struct env_set *es, - const bool check_allowed, - struct gc_arena *gc) -{ - char **ret = NULL; - struct env_item *e = NULL; - int i = 0, n = 0; - - /* figure length of es */ - if (es) - { - for (e = es->list; e != NULL; e = e->next) - { - ++n; - } - } - - /* alloc return array */ - ALLOC_ARRAY_CLEAR_GC(ret, char *, n+1, gc); - - /* fill return array */ - if (es) - { - i = 0; - for (e = es->list; e != NULL; e = e->next) - { - if (!check_allowed || env_allowed(e->string)) - { - ASSERT(i < n); - ret[i++] = e->string; - } - } - } - - ret[i] = NULL; - return (const char **)ret; -} - const char ** make_arg_array(const char *first, const char *parms, struct gc_arena *gc) { diff --git a/src/openvpn/misc.h b/src/openvpn/misc.h index ec20ee7..3725fcf 100644 --- a/src/openvpn/misc.h +++ b/src/openvpn/misc.h @@ -27,6 +27,7 @@ #include "argv.h" #include "basic.h" #include "common.h" +#include "env_set.h" #include "integer.h" #include "buffer.h" #include "platform.h" @@ -37,20 +38,6 @@ /* forward declarations */ struct plugin_list; -/* - * Handle environmental variable lists - */ - -struct env_item { - char *string; - struct env_item *next; -}; - -struct env_set { - struct gc_arena *gc; - struct env_item *list; -}; - /* system flags */ #define S_SCRIPT (1<<0) #define S_FATAL (1<<1) @@ -83,61 +70,8 @@ void set_std_files_to_null(bool stdin_only); extern int inetd_socket_descriptor; void save_inetd_socket_descriptor(void); -/* set/delete environmental variable */ -void setenv_str_ex(struct env_set *es, - const char *name, - const char *value, - const unsigned int name_include, - const unsigned int name_exclude, - const char name_replace, - const unsigned int value_include, - const unsigned int value_exclude, - const char value_replace); - -void setenv_counter(struct env_set *es, const char *name, counter_type value); - -void setenv_int(struct env_set *es, const char *name, int value); - -void setenv_long_long(struct env_set *es, const char *name, long long value); - -void setenv_str(struct env_set *es, const char *name, const char *value); - -void setenv_str_safe(struct env_set *es, const char *name, const char *value); - -void setenv_del(struct env_set *es, const char *name); - -/** - * Store the supplied name value pair in the env_set. If the variable with the - * supplied name already exists, append _N to the name, starting at N=1. - */ -void setenv_str_incr(struct env_set *es, const char *name, const char *value); - -void setenv_int_i(struct env_set *es, const char *name, const int value, const int i); - -void setenv_str_i(struct env_set *es, const char *name, const char *value, const int i); - -/* struct env_set functions */ - -struct env_set *env_set_create(struct gc_arena *gc); - -void env_set_destroy(struct env_set *es); - -bool env_set_del(struct env_set *es, const char *str); - -void env_set_add(struct env_set *es, const char *str); - -const char *env_set_get(const struct env_set *es, const char *name); - -void env_set_print(int msglevel, const struct env_set *es); - -void env_set_inherit(struct env_set *es, const struct env_set *src); - /* Make arrays of strings */ -const char **make_env_array(const struct env_set *es, - const bool check_allowed, - struct gc_arena *gc); - const char **make_arg_array(const char *first, const char *parms, struct gc_arena *gc); const char **make_extended_arg_array(char **p, struct gc_arena *gc); @@ -259,11 +193,6 @@ void set_auth_token(struct user_pass *up, const char *token); */ const char *safe_print(const char *str, struct gc_arena *gc); -/* returns true if environmental variable safe to print to log */ -bool env_safe_to_print(const char *str); - -/* returns true if environmental variable may be passed to an external program */ -bool env_allowed(const char *str); void configure_path(void); diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index c457ef9..7b41363 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -39,6 +39,7 @@ crypto_testdriver_SOURCES = test_crypto.c mock_msg.c \ $(openvpn_srcdir)/crypto.c \ $(openvpn_srcdir)/crypto_mbedtls.c \ $(openvpn_srcdir)/crypto_openssl.c \ + $(openvpn_srcdir)/env_set.c \ $(openvpn_srcdir)/otime.c \ $(openvpn_srcdir)/packet_id.c \ $(openvpn_srcdir)/platform.c From patchwork Fri Dec 8 01:07:48 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 146 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director1.mail.ord1d.rsapps.net ([172.28.255.1]) by backend31.mail.ord1d.rsapps.net (Dovecot) with LMTP id 8yXdG/CAKlpzCgAAgoeIoA for ; Fri, 08 Dec 2017 07:09:21 -0500 Received: from director4.mail.ord1c.rsapps.net ([172.28.255.1]) by director1.mail.ord1d.rsapps.net (Dovecot) with LMTP id ed6aB/CAKlqiLAAANGzteQ ; Fri, 08 Dec 2017 07:09:21 -0500 Received: from smtp30.gate.ord1a ([172.28.255.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by director4.mail.ord1c.rsapps.net (Dovecot) with LMTP id 0A/qOvCAKlptMwAAsEL7Xg ; Fri, 08 Dec 2017 07:09:21 -0500 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.34.181.88] Authentication-Results: smtp30.gate.ord1a.rsapps.net; iprev=pass policy.iprev="216.34.181.88"; 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; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Classification-ID: 95cead7c-dc10-11e7-9c07-0024e83017e2-1-1 Received: from [216.34.181.88] ([216.34.181.88:62579] helo=lists.sourceforge.net) by smtp30.gate.ord1a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id D3/A4-17060-0E08A2A5; Fri, 08 Dec 2017 07:09:05 -0500 Received: from localhost ([127.0.0.1] helo=sfs-ml-3.v29.ch3.sourceforge.com) by sfs-ml-3.v29.ch3.sourceforge.com with esmtp (Exim 4.89) (envelope-from ) id 1eNHS4-0001V7-EU; Fri, 08 Dec 2017 12:08:24 +0000 Received: from sfi-mx-2.v28.ch3.sourceforge.com ([172.29.28.192] helo=mx.sourceforge.net) by sfs-ml-3.v29.ch3.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.89) (envelope-from ) id 1eNHS3-0001Ut-1u for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:23 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Type:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:CC:To:From:Sender:Reply-To:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=tVILnZOPqsFY1/D/DdHSYgx9bzXeVLjUHJ37xf9pREA=; b=VqO014Cl166MJY7HpsGqq83d2x cD6t7+IjXQtNGxblDD7STJnn8zd1+GWWYAb9pX0IPWxELWoHJi2OuOXE6FixbKkLCR2n76m3voupU aymgoFirXrGC/94wvC+a4eV3kW71WbrETmdvQdoA7Ek+AjtKbr8cJp3/uBUrgzI5McN0=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject: CC:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=tVILnZOPqsFY1/D/DdHSYgx9bzXeVLjUHJ37xf9pREA=; b=LaSMwGFIcIihGOkD1jKpbjqQD7 ODZLwyB0GETayI1kntMyYtho58vjWVxiD6vIhDGsCH8qZSfP7dwKICrA+/FF0CpUzCygebFgzH1UR SNQISb/lXE6uXhiJDhZV5Gr2K4ywXPHuc3jUj6nP6lYzD9gSa/qPYPimeQahbTOEKT9Y=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-2.v28.ch3.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.89) id 1eNHS1-0004L1-89 for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:23 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id 25F971C5246 for ; Fri, 8 Dec 2017 13:08:08 +0100 (CET) Received: from steffan-fox.fox.local (172.16.5.166) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Fri, 8 Dec 2017 13:08:07 +0100 From: Steffan Karger To: Date: Fri, 8 Dec 2017 13:07:48 +0100 Message-ID: <1512734870-17133-9-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> MIME-Version: 1.0 X-ClientProxiedBy: FOXDFT52.FOX.local (10.0.0.129) To FOXDFT52.FOX.local (10.0.0.129) X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -0.0 SPF_PASS SPF: sender matches SPF record -0.0 AWL AWL: Adjusted score from AWL reputation of From: address X-Headers-End: 1eNHS1-0004L1-89 Subject: [Openvpn-devel] [PATCH 08/10] Move file-related functions from misc.c to platform.c 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: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox To avoid having to include misc.c - which is a dependency mess - in the tls-crypt unit tests, move file-handing related functions to platform.c (which is where other file-related functions already reside). Signed-off-by: Steffan Karger --- src/openvpn/init.c | 2 +- src/openvpn/misc.c | 148 ----------------------------- src/openvpn/misc.h | 12 --- src/openvpn/multi.c | 25 ++--- src/openvpn/pf.c | 4 +- src/openvpn/platform.c | 148 +++++++++++++++++++++++++++++ src/openvpn/platform.h | 18 ++++ src/openvpn/plugin.c | 6 +- src/openvpn/ssl_verify.c | 12 ++- tests/unit_tests/openvpn/Makefile.am | 3 + tests/unit_tests/openvpn/mock_get_random.c | 36 +++++++ 11 files changed, 232 insertions(+), 182 deletions(-) create mode 100644 tests/unit_tests/openvpn/mock_get_random.c diff --git a/src/openvpn/init.c b/src/openvpn/init.c index a4603b3..4055f28 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -807,7 +807,7 @@ init_static(void) #ifdef STATUS_PRINTF_TEST { struct gc_arena gc = gc_new(); - const char *tmp_file = create_temp_file("/tmp", "foo", &gc); + const char *tmp_file = platform_create_temp_file("/tmp", "foo", &gc); struct status_output *so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE); status_printf(so, "%s", "foo"); status_printf(so, "%s", "bar"); diff --git a/src/openvpn/misc.c b/src/openvpn/misc.c index ad26f3d..b04a9d7 100644 --- a/src/openvpn/misc.c +++ b/src/openvpn/misc.c @@ -313,87 +313,6 @@ openvpn_popen(const struct argv *a, const struct env_set *es) return ret; } - -/* return true if filename can be opened for read */ -bool -test_file(const char *filename) -{ - bool ret = false; - if (filename) - { - FILE *fp = platform_fopen(filename, "r"); - if (fp) - { - fclose(fp); - ret = true; - } - else - { - if (openvpn_errno() == EACCES) - { - msg( M_WARN | M_ERRNO, "Could not access file '%s'", filename); - } - } - } - - dmsg(D_TEST_FILE, "TEST FILE '%s' [%d]", - filename ? filename : "UNDEF", - ret); - - return ret; -} - -/* create a temporary filename in directory */ -const char * -create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc) -{ - int fd; - const char *retfname = NULL; - unsigned int attempts = 0; - char fname[256] = { 0 }; - const char *fname_fmt = PACKAGE "_%.*s_%08lx%08lx.tmp"; - const int max_prefix_len = sizeof(fname) - (sizeof(PACKAGE) + 7 + (2 * 8)); - - while (attempts < 6) - { - ++attempts; - - if (!openvpn_snprintf(fname, sizeof(fname), fname_fmt, max_prefix_len, - prefix, (unsigned long) get_random(), - (unsigned long) get_random())) - { - msg(M_WARN, "ERROR: temporary filename too long"); - return NULL; - } - - retfname = gen_path(directory, fname, gc); - if (!retfname) - { - msg(M_WARN, "Failed to create temporary filename and path"); - return NULL; - } - - /* Atomically create the file. Errors out if the file already - * exists. */ - fd = platform_open(retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); - if (fd != -1) - { - close(fd); - return retfname; - } - else if (fd == -1 && errno != EEXIST) - { - /* Something else went wrong, no need to retry. */ - msg(M_WARN | M_ERRNO, "Could not create temporary file '%s'", - retfname); - return NULL; - } - } - - msg(M_WARN, "Failed to create temporary file after %i attempts", attempts); - return NULL; -} - /* * Prepend a random string to hostname to prevent DNS caching. * For example, foo.bar.gov would be modified to .foo.bar.gov. @@ -416,73 +335,6 @@ hostname_randomize(const char *hostname, struct gc_arena *gc) } /* - * Put a directory and filename together. - */ -const char * -gen_path(const char *directory, const char *filename, struct gc_arena *gc) -{ -#ifdef _WIN32 - const int CC_PATH_RESERVED = CC_LESS_THAN|CC_GREATER_THAN|CC_COLON - |CC_DOUBLE_QUOTE|CC_SLASH|CC_BACKSLASH|CC_PIPE|CC_QUESTION_MARK|CC_ASTERISK; -#else - const int CC_PATH_RESERVED = CC_SLASH; -#endif - - if (!gc) - { - return NULL; /* Would leak memory otherwise */ - } - - const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', gc); - - if (safe_filename - && strcmp(safe_filename, ".") - && strcmp(safe_filename, "..") -#ifdef _WIN32 - && win_safe_filename(safe_filename) -#endif - ) - { - const size_t outsize = strlen(safe_filename) + (directory ? strlen(directory) : 0) + 16; - struct buffer out = alloc_buf_gc(outsize, gc); - char dirsep[2]; - - dirsep[0] = OS_SPECIFIC_DIRSEP; - dirsep[1] = '\0'; - - if (directory) - { - buf_printf(&out, "%s%s", directory, dirsep); - } - buf_printf(&out, "%s", safe_filename); - - return BSTR(&out); - } - else - { - return NULL; - } -} - -bool -absolute_pathname(const char *pathname) -{ - if (pathname) - { - const int c = pathname[0]; -#ifdef _WIN32 - return c == '\\' || (isalpha(c) && pathname[1] == ':' && pathname[2] == '\\'); -#else - return c == '/'; -#endif - } - else - { - return false; - } -} - -/* * Get and store a username/password */ diff --git a/src/openvpn/misc.h b/src/openvpn/misc.h index 3725fcf..82bff9a 100644 --- a/src/openvpn/misc.h +++ b/src/openvpn/misc.h @@ -79,18 +79,6 @@ const char **make_extended_arg_array(char **p, struct gc_arena *gc); /* an analogue to the random() function, but use OpenSSL functions if available */ long int get_random(void); -/* return true if filename can be opened for read */ -bool test_file(const char *filename); - -/* create a temporary file in directory, returns the filename of the created file */ -const char *create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc); - -/* put a directory and filename together */ -const char *gen_path(const char *directory, const char *filename, struct gc_arena *gc); - -/* return true if pathname is absolute */ -bool absolute_pathname(const char *pathname); - /* prepend a random prefix to hostname */ const char *hostname_randomize(const char *hostname, struct gc_arena *gc); diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 25b2d09..acc6912 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -1639,7 +1639,7 @@ multi_client_connect_post(struct multi_context *m, unsigned int *option_types_found) { /* Did script generate a dynamic config file? */ - if (test_file(dc_file)) + if (platform_test_file(dc_file)) { options_server_import(&mi->context.options, dc_file, @@ -1826,12 +1826,13 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi) { const char *ccd_file; - ccd_file = gen_path(mi->context.options.client_config_dir, - tls_common_name(mi->context.c2.tls_multi, false), - &gc); + ccd_file = platform_gen_path(mi->context.options.client_config_dir, + tls_common_name(mi->context.c2.tls_multi, + false), + &gc); /* try common-name file */ - if (test_file(ccd_file)) + if (platform_test_file(ccd_file)) { options_server_import(&mi->context.options, ccd_file, @@ -1842,11 +1843,11 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi) } else /* try default file */ { - ccd_file = gen_path(mi->context.options.client_config_dir, - CCD_DEFAULT, - &gc); + ccd_file = platform_gen_path(mi->context.options.client_config_dir, + CCD_DEFAULT, + &gc); - if (test_file(ccd_file)) + if (platform_test_file(ccd_file)) { options_server_import(&mi->context.options, ccd_file, @@ -1876,7 +1877,8 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi) if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT)) { struct argv argv = argv_new(); - const char *dc_file = create_temp_file(mi->context.options.tmp_dir, "cc", &gc); + const char *dc_file = platform_create_temp_file(mi->context.options.tmp_dir, + "cc", &gc); if (!dc_file) { @@ -1938,7 +1940,8 @@ script_depr_failed: setenv_str(mi->context.c2.es, "script_type", "client-connect"); - dc_file = create_temp_file(mi->context.options.tmp_dir, "cc", &gc); + dc_file = platform_create_temp_file(mi->context.options.tmp_dir, + "cc", &gc); if (!dc_file) { cc_succeeded = false; diff --git a/src/openvpn/pf.c b/src/openvpn/pf.c index 6e4107c..fd9f215 100644 --- a/src/openvpn/pf.c +++ b/src/openvpn/pf.c @@ -621,8 +621,8 @@ pf_init_context(struct context *c) #ifdef PLUGIN_PF if (plugin_defined(c->plugins, OPENVPN_PLUGIN_ENABLE_PF)) { - c->c2.pf.filename = create_temp_file(c->options.tmp_dir, "pf", - &c->c2.gc); + c->c2.pf.filename = platform_create_temp_file(c->options.tmp_dir, "pf", + &c->c2.gc); if (c->c2.pf.filename) { setenv_str(c->c2.es, "pf_file", c->c2.pf.filename); diff --git a/src/openvpn/platform.c b/src/openvpn/platform.c index e942ba9..5281267 100644 --- a/src/openvpn/platform.c +++ b/src/openvpn/platform.c @@ -31,6 +31,7 @@ #include "buffer.h" #include "error.h" +#include "misc.h" #include "win32.h" #include "memdbg.h" @@ -335,3 +336,150 @@ platform_stat(const char *path, platform_stat_t *buf) #endif } +/* create a temporary filename in directory */ +const char * +platform_create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc) +{ + int fd; + const char *retfname = NULL; + unsigned int attempts = 0; + char fname[256] = { 0 }; + const char *fname_fmt = PACKAGE "_%.*s_%08lx%08lx.tmp"; + const int max_prefix_len = sizeof(fname) - (sizeof(PACKAGE) + 7 + (2 * 8)); + + while (attempts < 6) + { + ++attempts; + + if (!openvpn_snprintf(fname, sizeof(fname), fname_fmt, max_prefix_len, + prefix, (unsigned long) get_random(), + (unsigned long) get_random())) + { + msg(M_WARN, "ERROR: temporary filename too long"); + return NULL; + } + + retfname = platform_gen_path(directory, fname, gc); + if (!retfname) + { + msg(M_WARN, "Failed to create temporary filename and path"); + return NULL; + } + + /* Atomically create the file. Errors out if the file already + * exists. */ + fd = platform_open(retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); + if (fd != -1) + { + close(fd); + return retfname; + } + else if (fd == -1 && errno != EEXIST) + { + /* Something else went wrong, no need to retry. */ + msg(M_WARN | M_ERRNO, "Could not create temporary file '%s'", + retfname); + return NULL; + } + } + + msg(M_WARN, "Failed to create temporary file after %i attempts", attempts); + return NULL; +} + +/* + * Put a directory and filename together. + */ +const char * +platform_gen_path(const char *directory, const char *filename, + struct gc_arena *gc) +{ +#ifdef _WIN32 + const int CC_PATH_RESERVED = CC_LESS_THAN|CC_GREATER_THAN|CC_COLON + |CC_DOUBLE_QUOTE|CC_SLASH|CC_BACKSLASH|CC_PIPE|CC_QUESTION_MARK|CC_ASTERISK; +#else + const int CC_PATH_RESERVED = CC_SLASH; +#endif + + if (!gc) + { + return NULL; /* Would leak memory otherwise */ + } + + const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', gc); + + if (safe_filename + && strcmp(safe_filename, ".") + && strcmp(safe_filename, "..") +#ifdef _WIN32 + && win_safe_filename(safe_filename) +#endif + ) + { + const size_t outsize = strlen(safe_filename) + (directory ? strlen(directory) : 0) + 16; + struct buffer out = alloc_buf_gc(outsize, gc); + char dirsep[2]; + + dirsep[0] = OS_SPECIFIC_DIRSEP; + dirsep[1] = '\0'; + + if (directory) + { + buf_printf(&out, "%s%s", directory, dirsep); + } + buf_printf(&out, "%s", safe_filename); + + return BSTR(&out); + } + else + { + return NULL; + } +} + +bool +platform_absolute_pathname(const char *pathname) +{ + if (pathname) + { + const int c = pathname[0]; +#ifdef _WIN32 + return c == '\\' || (isalpha(c) && pathname[1] == ':' && pathname[2] == '\\'); +#else + return c == '/'; +#endif + } + else + { + return false; + } +} + +/* return true if filename can be opened for read */ +bool +platform_test_file(const char *filename) +{ + bool ret = false; + if (filename) + { + FILE *fp = platform_fopen(filename, "r"); + if (fp) + { + fclose(fp); + ret = true; + } + else + { + if (openvpn_errno() == EACCES) + { + msg( M_WARN | M_ERRNO, "Could not access file '%s'", filename); + } + } + } + + dmsg(D_TEST_FILE, "TEST FILE '%s' [%d]", + filename ? filename : "UNDEF", + ret); + + return ret; +} diff --git a/src/openvpn/platform.h b/src/openvpn/platform.h index cd2bbc9..e116fd5 100644 --- a/src/openvpn/platform.h +++ b/src/openvpn/platform.h @@ -49,6 +49,7 @@ #endif #include "basic.h" +#include "buffer.h" /* Get/Set UID of process */ @@ -143,4 +144,21 @@ typedef struct stat platform_stat_t; #endif int platform_stat(const char *path, platform_stat_t *buf); +/** + * Create a temporary file in directory, returns the filename of the created + * file. + */ +const char *platform_create_temp_file(const char *directory, const char *prefix, + struct gc_arena *gc); + +/** Put a directory and filename together. */ +const char *platform_gen_path(const char *directory, const char *filename, + struct gc_arena *gc); + +/** Return true if pathname is absolute. */ +bool platform_absolute_pathname(const char *pathname); + +/** Return true if filename can be opened for read. */ +bool platform_test_file(const char *filename); + #endif /* ifndef PLATFORM_H */ diff --git a/src/openvpn/plugin.c b/src/openvpn/plugin.c index 7387f8b..9dfa744 100644 --- a/src/openvpn/plugin.c +++ b/src/openvpn/plugin.c @@ -249,7 +249,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o) * was parsed. * */ - if (!absolute_pathname(p->so_pathname) + if (!platform_absolute_pathname(p->so_pathname) && p->so_pathname[0] != '.') { char full[PATH_MAX]; @@ -259,7 +259,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o) } else { - rel = !absolute_pathname(p->so_pathname); + rel = !platform_absolute_pathname(p->so_pathname); p->handle = dlopen(p->so_pathname, RTLD_NOW); } if (!p->handle) @@ -271,7 +271,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o) #else /* ifndef _WIN32 */ - rel = !absolute_pathname(p->so_pathname); + rel = !platform_absolute_pathname(p->so_pathname); p->module = LoadLibraryW(wide_string(p->so_pathname, &gc)); if (!p->module) { diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c index ebb1da2..3568bad 100644 --- a/src/openvpn/ssl_verify.c +++ b/src/openvpn/ssl_verify.c @@ -547,7 +547,7 @@ verify_cert_export_cert(openvpn_x509_cert_t *peercert, const char *tmp_dir, stru /* create tmp file to store peer cert */ if (!tmp_dir - || !(peercert_filename = create_temp_file(tmp_dir, "pcf", gc))) + || !(peercert_filename = platform_create_temp_file(tmp_dir, "pcf", gc))) { msg (M_WARN, "Failed to create peer cert file"); return NULL; @@ -887,7 +887,7 @@ key_state_gen_auth_control_file(struct key_state *ks, const struct tls_options * struct gc_arena gc = gc_new(); key_state_rm_auth_control_file(ks); - const char *acf = create_temp_file(opt->tmp_dir, "acf", &gc); + const char *acf = platform_create_temp_file(opt->tmp_dir, "acf", &gc); if (acf) { ks->auth_control_file = string_alloc(acf, NULL); @@ -1100,7 +1100,8 @@ verify_user_pass_script(struct tls_session *session, const struct user_pass *up) { struct status_output *so; - tmp_file = create_temp_file(session->opt->tmp_dir, "up", &gc); + tmp_file = platform_create_temp_file(session->opt->tmp_dir, "up", + &gc); if (tmp_file) { so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE); @@ -1510,8 +1511,9 @@ verify_final_auth_checks(struct tls_multi *multi, struct tls_session *session) struct gc_arena gc = gc_new(); const char *cn = session->common_name; - const char *path = gen_path(session->opt->client_config_dir_exclusive, cn, &gc); - if (!cn || !strcmp(cn, CCD_DEFAULT) || !test_file(path)) + const char *path = platform_gen_path(session->opt->client_config_dir_exclusive, + cn, &gc); + if (!cn || !strcmp(cn, CCD_DEFAULT) || !platform_test_file(path)) { ks->authenticated = false; wipe_auth_token(multi); diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index 7b41363..b69da84 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -19,6 +19,7 @@ argv_testdriver_CFLAGS = @TEST_CFLAGS@ -I$(openvpn_srcdir) -I$(compat_srcdir) \ argv_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(openvpn_srcdir) -Wl,--wrap=parse_line \ $(OPTIONAL_CRYPTO_LIBS) argv_testdriver_SOURCES = test_argv.c mock_msg.c \ + mock_get_random.c \ $(openvpn_srcdir)/platform.c \ $(openvpn_srcdir)/buffer.c \ $(openvpn_srcdir)/argv.c @@ -26,6 +27,7 @@ argv_testdriver_SOURCES = test_argv.c mock_msg.c \ buffer_testdriver_CFLAGS = @TEST_CFLAGS@ -I$(openvpn_srcdir) -I$(compat_srcdir) buffer_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(openvpn_srcdir) -Wl,--wrap=parse_line buffer_testdriver_SOURCES = test_buffer.c mock_msg.c \ + mock_get_random.c \ $(openvpn_srcdir)/buffer.c \ $(openvpn_srcdir)/platform.c @@ -50,6 +52,7 @@ packet_id_testdriver_CFLAGS = @TEST_CFLAGS@ \ packet_id_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ $(OPTIONAL_CRYPTO_LIBS) packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \ + mock_get_random.c \ $(openvpn_srcdir)/buffer.c \ $(openvpn_srcdir)/otime.c \ $(openvpn_srcdir)/packet_id.c \ diff --git a/tests/unit_tests/openvpn/mock_get_random.c b/tests/unit_tests/openvpn/mock_get_random.c new file mode 100644 index 0000000..da92a9b --- /dev/null +++ b/tests/unit_tests/openvpn/mock_get_random.c @@ -0,0 +1,36 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2017 Fox Crypto B.V. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include + +unsigned long +get_random(void) +{ + /* rand() is not very random, but it's C99 and this is just for testing */ + return rand(); +} From patchwork Fri Dec 8 01:07:49 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 142 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director5.mail.ord1d.rsapps.net ([172.27.255.59]) by backend31.mail.ord1d.rsapps.net (Dovecot) with LMTP id E8QMC+eAKlqaUAAAgoeIoA for ; Fri, 08 Dec 2017 07:09:11 -0500 Received: from proxy2.mail.iad3a.rsapps.net ([172.27.255.59]) by director5.mail.ord1d.rsapps.net (Dovecot) with LMTP id 09jaA+eAKlo8fgAAsdCWiw ; Fri, 08 Dec 2017 07:09:11 -0500 Received: from smtp1.gate.iad3a ([172.27.255.59]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy2.mail.iad3a.rsapps.net (Dovecot) with LMTP id CKZpAeeAKlqgQwAABcWvHw ; Fri, 08 Dec 2017 07:09:11 -0500 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.34.181.88] Authentication-Results: smtp1.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.34.181.88"; 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; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Classification-ID: 96558dc4-dc10-11e7-a697-52540091dea5-1-1 Received: from [216.34.181.88] ([216.34.181.88:29618] helo=lists.sourceforge.net) by smtp1.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 66/7C-04923-2E08A2A5; Fri, 08 Dec 2017 07:09:06 -0500 Received: from localhost ([127.0.0.1] helo=sfs-ml-3.v29.ch3.sourceforge.com) by sfs-ml-3.v29.ch3.sourceforge.com with esmtp (Exim 4.89) (envelope-from ) id 1eNHS5-0001Vx-MM; Fri, 08 Dec 2017 12:08:25 +0000 Received: from sfi-mx-4.v28.ch3.sourceforge.com ([172.29.28.194] helo=mx.sourceforge.net) by sfs-ml-3.v29.ch3.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.89) (envelope-from ) id 1eNHS4-0001Va-LO for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:24 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Type:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:CC:To:From:Sender:Reply-To:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=KOZOQma0XTZ9kytnnk6O6Y0bn+bugZLWuHv0wwoXVTA=; b=GodoZ2lSt56VPKD5VJIH3ygV8/ Kkq5ag6pg0j4HO7tjDpyjx/bS/HLLYE65SpVVF/nntric5IlVeYy3EyYF1sOlie9DpubzjkcIEJNR pKZP2/aiuPyeUjbIpkkP+se9rsvte5iWUAxndeTqhYc277dzzXj57JjljV8iauPl1PSY=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject: CC:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=KOZOQma0XTZ9kytnnk6O6Y0bn+bugZLWuHv0wwoXVTA=; b=iKz+D/IuKQo6NYuOV5Uu5/7ZwQ rrb2xNM8T+4HzXiU1do5iLkpyEzoRWHqb/qiUIxqEd5mBbmT/Nd3Hjnr6/trnfBy1HSdsg+BoVN4A oPorGgyqiPKrPOzN156/P3mKUCS+4XaDUbScAUzsaE/GM8PPHUip3Vv+xfxsikIB5h5I=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-4.v28.ch3.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.89) id 1eNHS1-0000WL-Os for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:24 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id 300EA1C5247; Fri, 8 Dec 2017 13:08:08 +0100 (CET) Received: from steffan-fox.fox.local (172.16.5.166) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Fri, 8 Dec 2017 13:08:07 +0100 From: Steffan Karger To: Date: Fri, 8 Dec 2017 13:07:49 +0100 Message-ID: <1512734870-17133-10-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> MIME-Version: 1.0 X-ClientProxiedBy: FOXDFT52.FOX.local (10.0.0.129) To FOXDFT52.FOX.local (10.0.0.129) X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -0.0 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1eNHS1-0000WL-Os Subject: [Openvpn-devel] [PATCH 09/10] Move execve/run_script helper functions to run_command.c 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: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox From: Steffan Karger To avoid having to include misc.c - which is a dependency mess - in the tls-crypt unit tests, move the command execution helper functions to a new run_command.c module. While at it, abstract away the script_security global variable. Signed-off-by: Steffan Karger --- src/openvpn/Makefile.am | 1 + src/openvpn/env_set.c | 6 +- src/openvpn/init.c | 5 +- src/openvpn/lladdr.c | 1 + src/openvpn/misc.c | 218 --------------------------- src/openvpn/misc.h | 32 ---- src/openvpn/multi.c | 2 +- src/openvpn/options.c | 3 +- src/openvpn/route.c | 2 +- src/openvpn/run_command.c | 267 +++++++++++++++++++++++++++++++++ src/openvpn/run_command.h | 63 ++++++++ src/openvpn/socket.c | 1 + src/openvpn/ssl_verify.c | 4 +- src/openvpn/tls_crypt.c | 1 + src/openvpn/tun.c | 2 +- src/openvpn/win32.c | 4 +- tests/unit_tests/openvpn/Makefile.am | 3 +- tests/unit_tests/openvpn/test_crypto.c | 2 - 18 files changed, 351 insertions(+), 266 deletions(-) create mode 100644 src/openvpn/run_command.c create mode 100644 src/openvpn/run_command.h diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index 441700a..86bb40a 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -102,6 +102,7 @@ openvpn_SOURCES = \ pushlist.h \ reliable.c reliable.h \ route.c route.h \ + run_command.c run_command.h \ schedule.c schedule.h \ session_id.c session_id.h \ shaper.c shaper.h \ diff --git a/src/openvpn/env_set.c b/src/openvpn/env_set.c index 2a22e0b..4a9de1a 100644 --- a/src/openvpn/env_set.c +++ b/src/openvpn/env_set.c @@ -32,10 +32,10 @@ #include "syshead.h" -#include "misc.h" - #include "env_set.h" +#include "run_command.h" + /* * Set environmental variable (int or string). * @@ -414,7 +414,7 @@ setenv_str_i(struct env_set *es, const char *name, const char *value, const int bool env_allowed(const char *str) { - return (script_security >= SSEC_PW_ENV || !is_password_env_var(str)); + return (script_security() >= SSEC_PW_ENV || !is_password_env_var(str)); } /* Make arrays of strings */ diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 4055f28..93143b1 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -35,6 +35,7 @@ #include "win32.h" #include "init.h" +#include "run_command.h" #include "sig.h" #include "occ.h" #include "list.h" @@ -3153,11 +3154,11 @@ do_option_warnings(struct context *c) /* If a script is used, print appropiate warnings */ if (o->user_script_used) { - if (script_security >= SSEC_SCRIPTS) + if (script_security() >= SSEC_SCRIPTS) { msg(M_WARN, "NOTE: the current --script-security setting may allow this configuration to call user-defined scripts"); } - else if (script_security >= SSEC_PW_ENV) + else if (script_security() >= SSEC_PW_ENV) { msg(M_WARN, "WARNING: the current --script-security setting may allow passwords to be passed to scripts via environmental variables"); } diff --git a/src/openvpn/lladdr.c b/src/openvpn/lladdr.c index ff71e48..f24596b 100644 --- a/src/openvpn/lladdr.c +++ b/src/openvpn/lladdr.c @@ -11,6 +11,7 @@ #include "syshead.h" #include "error.h" #include "misc.h" +#include "run_command.h" int set_lladdr(const char *ifname, const char *lladdr, diff --git a/src/openvpn/misc.c b/src/openvpn/misc.c index b04a9d7..b2c5626 100644 --- a/src/openvpn/misc.c +++ b/src/openvpn/misc.c @@ -51,9 +51,6 @@ const char *iproute_path = IPROUTE_PATH; /* GLOBAL */ #endif -/* contains an SSEC_x value defined in misc.h */ -int script_security = SSEC_BUILT_IN; /* GLOBAL */ - /* * Set standard file descriptors to /dev/null */ @@ -99,221 +96,6 @@ save_inetd_socket_descriptor(void) } /* - * Print an error message based on the status code returned by system(). - */ -const char * -system_error_message(int stat, struct gc_arena *gc) -{ - struct buffer out = alloc_buf_gc(256, gc); -#ifdef _WIN32 - if (stat == -1) - { - buf_printf(&out, "external program did not execute -- "); - } - buf_printf(&out, "returned error code %d", stat); -#else /* ifdef _WIN32 */ - if (stat == -1) - { - buf_printf(&out, "external program fork failed"); - } - else if (!WIFEXITED(stat)) - { - buf_printf(&out, "external program did not exit normally"); - } - else - { - const int cmd_ret = WEXITSTATUS(stat); - if (!cmd_ret) - { - buf_printf(&out, "external program exited normally"); - } - else if (cmd_ret == 127) - { - buf_printf(&out, "could not execute external program"); - } - else - { - buf_printf(&out, "external program exited with error status: %d", cmd_ret); - } - } -#endif /* ifdef _WIN32 */ - return (const char *)out.data; -} - -/* - * Wrapper around openvpn_execve - */ -bool -openvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message) -{ - struct gc_arena gc = gc_new(); - const int stat = openvpn_execve(a, es, flags); - int ret = false; - - if (platform_system_ok(stat)) - { - ret = true; - } - else - { - if (error_message) - { - msg(((flags & S_FATAL) ? M_FATAL : M_WARN), "%s: %s", - error_message, - system_error_message(stat, &gc)); - } - } - gc_free(&gc); - return ret; -} - -bool -openvpn_execve_allowed(const unsigned int flags) -{ - if (flags & S_SCRIPT) - { - return script_security >= SSEC_SCRIPTS; - } - else - { - return script_security >= SSEC_BUILT_IN; - } -} - - -#ifndef _WIN32 -/* - * Run execve() inside a fork(). Designed to replicate the semantics of system() but - * in a safer way that doesn't require the invocation of a shell or the risks - * assocated with formatting and parsing a command line. - */ -int -openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags) -{ - struct gc_arena gc = gc_new(); - int ret = -1; - static bool warn_shown = false; - - if (a && a->argv[0]) - { -#if defined(ENABLE_FEATURE_EXECVE) - if (openvpn_execve_allowed(flags)) - { - const char *cmd = a->argv[0]; - char *const *argv = a->argv; - char *const *envp = (char *const *)make_env_array(es, true, &gc); - pid_t pid; - - pid = fork(); - if (pid == (pid_t)0) /* child side */ - { - execve(cmd, argv, envp); - exit(127); - } - else if (pid < (pid_t)0) /* fork failed */ - { - msg(M_ERR, "openvpn_execve: unable to fork"); - } - else /* parent side */ - { - if (waitpid(pid, &ret, 0) != pid) - { - ret = -1; - } - } - } - else if (!warn_shown && (script_security < SSEC_SCRIPTS)) - { - msg(M_WARN, SCRIPT_SECURITY_WARNING); - warn_shown = true; - } -#else /* if defined(ENABLE_FEATURE_EXECVE) */ - msg(M_WARN, "openvpn_execve: execve function not available"); -#endif /* if defined(ENABLE_FEATURE_EXECVE) */ - } - else - { - msg(M_FATAL, "openvpn_execve: called with empty argv"); - } - - gc_free(&gc); - return ret; -} -#endif /* ifndef _WIN32 */ - -/* - * Run execve() inside a fork(), duping stdout. Designed to replicate the semantics of popen() but - * in a safer way that doesn't require the invocation of a shell or the risks - * assocated with formatting and parsing a command line. - */ -int -openvpn_popen(const struct argv *a, const struct env_set *es) -{ - struct gc_arena gc = gc_new(); - int ret = -1; - static bool warn_shown = false; - - if (a && a->argv[0]) - { -#if defined(ENABLE_FEATURE_EXECVE) - if (script_security >= SSEC_BUILT_IN) - { - const char *cmd = a->argv[0]; - char *const *argv = a->argv; - char *const *envp = (char *const *)make_env_array(es, true, &gc); - pid_t pid; - int pipe_stdout[2]; - - if (pipe(pipe_stdout) == 0) - { - pid = fork(); - if (pid == (pid_t)0) /* child side */ - { - close(pipe_stdout[0]); /* Close read end */ - dup2(pipe_stdout[1],1); - execve(cmd, argv, envp); - exit(127); - } - else if (pid > (pid_t)0) /* parent side */ - { - int status = 0; - - close(pipe_stdout[1]); /* Close write end */ - waitpid(pid, &status, 0); - ret = pipe_stdout[0]; - } - else /* fork failed */ - { - close(pipe_stdout[0]); - close(pipe_stdout[1]); - msg(M_ERR, "openvpn_popen: unable to fork %s", cmd); - } - } - else - { - msg(M_WARN, "openvpn_popen: unable to create stdout pipe for %s", cmd); - ret = -1; - } - } - else if (!warn_shown && (script_security < SSEC_SCRIPTS)) - { - msg(M_WARN, SCRIPT_SECURITY_WARNING); - warn_shown = true; - } -#else /* if defined(ENABLE_FEATURE_EXECVE) */ - msg(M_WARN, "openvpn_popen: execve function not available"); -#endif /* if defined(ENABLE_FEATURE_EXECVE) */ - } - else - { - msg(M_FATAL, "openvpn_popen: called with empty argv"); - } - - gc_free(&gc); - return ret; -} - -/* * Prepend a random string to hostname to prevent DNS caching. * For example, foo.bar.gov would be modified to .foo.bar.gov. * Of course, this requires explicit support in the DNS server (wildcard). diff --git a/src/openvpn/misc.h b/src/openvpn/misc.h index 82bff9a..491906b 100644 --- a/src/openvpn/misc.h +++ b/src/openvpn/misc.h @@ -38,30 +38,6 @@ /* forward declarations */ struct plugin_list; -/* system flags */ -#define S_SCRIPT (1<<0) -#define S_FATAL (1<<1) - -const char *system_error_message(int, struct gc_arena *gc); - -/* wrapper around the execve() call */ -int openvpn_popen(const struct argv *a, const struct env_set *es); - -int openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags); - -bool openvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message); - -bool openvpn_execve_allowed(const unsigned int flags); - -static inline bool -openvpn_run_script(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *hook) -{ - char msg[256]; - - openvpn_snprintf(msg, sizeof(msg), "WARNING: Failed running command (%s)", hook); - return openvpn_execve_check(a, es, flags | S_SCRIPT, msg); -} - /* Set standard file descriptors to /dev/null */ void set_std_files_to_null(bool stdin_only); @@ -198,14 +174,6 @@ void get_user_pass_auto_userid(struct user_pass *up, const char *tag); extern const char *iproute_path; #endif -/* Script security */ -#define SSEC_NONE 0 /* strictly no calling of external programs */ -#define SSEC_BUILT_IN 1 /* only call built-in programs such as ifconfig, route, netsh, etc.*/ -#define SSEC_SCRIPTS 2 /* allow calling of built-in programs and user-defined scripts */ -#define SSEC_PW_ENV 3 /* allow calling of built-in programs and user-defined scripts that may receive a password as an environmental variable */ -extern int script_security; /* GLOBAL */ - - #define COMPAT_FLAG_QUERY 0 /** compat_flags operator: Query for a flag */ #define COMPAT_FLAG_SET (1<<0) /** compat_flags operator: Set a compat flag */ #define COMPAT_NAMES (1<<1) /** compat flag: --compat-names set */ diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index acc6912..2818b9a 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -38,7 +38,7 @@ #include "multi.h" #include "push.h" -#include "misc.h" +#include "run_command.h" #include "otime.h" #include "gremlin.h" #include "mstats.h" diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 89bff1e..2d577f8 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -41,6 +41,7 @@ #include "buffer.h" #include "error.h" #include "common.h" +#include "run_command.h" #include "shaper.h" #include "crypto.h" #include "ssl.h" @@ -6386,7 +6387,7 @@ add_option(struct options *options, else if (streq(p[0], "script-security") && p[1] && !p[2]) { VERIFY_PERMISSION(OPT_P_GENERAL); - script_security = atoi(p[1]); + script_security_set(atoi(p[1])); } else if (streq(p[0], "mssfix") && !p[2]) { diff --git a/src/openvpn/route.c b/src/openvpn/route.c index 8c71e6e..955e15f 100644 --- a/src/openvpn/route.c +++ b/src/openvpn/route.c @@ -36,7 +36,7 @@ #include "common.h" #include "error.h" #include "route.h" -#include "misc.h" +#include "run_command.h" #include "socket.h" #include "manage.h" #include "win32.h" diff --git a/src/openvpn/run_command.c b/src/openvpn/run_command.c new file mode 100644 index 0000000..4e19867 --- /dev/null +++ b/src/openvpn/run_command.c @@ -0,0 +1,267 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2017 OpenVPN Technologies, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#elif defined(_MSC_VER) +#include "config-msvc.h" +#endif + +#include "syshead.h" + +#include "buffer.h" +#include "error.h" +#include "platform.h" +#include "win32.h" + +#include "memdbg.h" + +#include "run_command.h" + +/* contains an SSEC_x value defined in platform.h */ +static int script_security_level = SSEC_BUILT_IN; /* GLOBAL */ + +int script_security(void) +{ + return script_security_level; +} + +void script_security_set(int level) +{ + script_security_level = level; +} + +/* + * Print an error message based on the status code returned by system(). + */ +static const char * +system_error_message(int stat, struct gc_arena *gc) +{ + struct buffer out = alloc_buf_gc(256, gc); +#ifdef _WIN32 + if (stat == -1) + { + buf_printf(&out, "external program did not execute -- "); + } + buf_printf(&out, "returned error code %d", stat); +#else /* ifdef _WIN32 */ + if (stat == -1) + { + buf_printf(&out, "external program fork failed"); + } + else if (!WIFEXITED(stat)) + { + buf_printf(&out, "external program did not exit normally"); + } + else + { + const int cmd_ret = WEXITSTATUS(stat); + if (!cmd_ret) + { + buf_printf(&out, "external program exited normally"); + } + else if (cmd_ret == 127) + { + buf_printf(&out, "could not execute external program"); + } + else + { + buf_printf(&out, "external program exited with error status: %d", cmd_ret); + } + } +#endif /* ifdef _WIN32 */ + return (const char *)out.data; +} + +bool +openvpn_execve_allowed(const unsigned int flags) +{ + if (flags & S_SCRIPT) + { + return script_security() >= SSEC_SCRIPTS; + } + else + { + return script_security() >= SSEC_BUILT_IN; + } +} + + +#ifndef _WIN32 +/* + * Run execve() inside a fork(). Designed to replicate the semantics of system() but + * in a safer way that doesn't require the invocation of a shell or the risks + * assocated with formatting and parsing a command line. + */ +int +openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags) +{ + struct gc_arena gc = gc_new(); + int ret = -1; + static bool warn_shown = false; + + if (a && a->argv[0]) + { +#if defined(ENABLE_FEATURE_EXECVE) + if (openvpn_execve_allowed(flags)) + { + const char *cmd = a->argv[0]; + char *const *argv = a->argv; + char *const *envp = (char *const *)make_env_array(es, true, &gc); + pid_t pid; + + pid = fork(); + if (pid == (pid_t)0) /* child side */ + { + execve(cmd, argv, envp); + exit(127); + } + else if (pid < (pid_t)0) /* fork failed */ + { + msg(M_ERR, "openvpn_execve: unable to fork"); + } + else /* parent side */ + { + if (waitpid(pid, &ret, 0) != pid) + { + ret = -1; + } + } + } + else if (!warn_shown && (script_security() < SSEC_SCRIPTS)) + { + msg(M_WARN, SCRIPT_SECURITY_WARNING); + warn_shown = true; + } +#else /* if defined(ENABLE_FEATURE_EXECVE) */ + msg(M_WARN, "openvpn_execve: execve function not available"); +#endif /* if defined(ENABLE_FEATURE_EXECVE) */ + } + else + { + msg(M_FATAL, "openvpn_execve: called with empty argv"); + } + + gc_free(&gc); + return ret; +} +#endif /* ifndef _WIN32 */ + +/* + * Wrapper around openvpn_execve + */ +bool +openvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message) +{ + struct gc_arena gc = gc_new(); + const int stat = openvpn_execve(a, es, flags); + int ret = false; + + if (platform_system_ok(stat)) + { + ret = true; + } + else + { + if (error_message) + { + msg(((flags & S_FATAL) ? M_FATAL : M_WARN), "%s: %s", + error_message, + system_error_message(stat, &gc)); + } + } + gc_free(&gc); + return ret; +} + +/* + * Run execve() inside a fork(), duping stdout. Designed to replicate the semantics of popen() but + * in a safer way that doesn't require the invocation of a shell or the risks + * assocated with formatting and parsing a command line. + */ +int +openvpn_popen(const struct argv *a, const struct env_set *es) +{ + struct gc_arena gc = gc_new(); + int ret = -1; + static bool warn_shown = false; + + if (a && a->argv[0]) + { +#if defined(ENABLE_FEATURE_EXECVE) + if (script_security() >= SSEC_BUILT_IN) + { + const char *cmd = a->argv[0]; + char *const *argv = a->argv; + char *const *envp = (char *const *)make_env_array(es, true, &gc); + pid_t pid; + int pipe_stdout[2]; + + if (pipe(pipe_stdout) == 0) + { + pid = fork(); + if (pid == (pid_t)0) /* child side */ + { + close(pipe_stdout[0]); /* Close read end */ + dup2(pipe_stdout[1],1); + execve(cmd, argv, envp); + exit(127); + } + else if (pid > (pid_t)0) /* parent side */ + { + int status = 0; + + close(pipe_stdout[1]); /* Close write end */ + waitpid(pid, &status, 0); + ret = pipe_stdout[0]; + } + else /* fork failed */ + { + close(pipe_stdout[0]); + close(pipe_stdout[1]); + msg(M_ERR, "openvpn_popen: unable to fork %s", cmd); + } + } + else + { + msg(M_WARN, "openvpn_popen: unable to create stdout pipe for %s", cmd); + ret = -1; + } + } + else if (!warn_shown && (script_security() < SSEC_SCRIPTS)) + { + msg(M_WARN, SCRIPT_SECURITY_WARNING); + warn_shown = true; + } +#else /* if defined(ENABLE_FEATURE_EXECVE) */ + msg(M_WARN, "openvpn_popen: execve function not available"); +#endif /* if defined(ENABLE_FEATURE_EXECVE) */ + } + else + { + msg(M_FATAL, "openvpn_popen: called with empty argv"); + } + + gc_free(&gc); + return ret; +} diff --git a/src/openvpn/run_command.h b/src/openvpn/run_command.h new file mode 100644 index 0000000..4501a5c --- /dev/null +++ b/src/openvpn/run_command.h @@ -0,0 +1,63 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2017 OpenVPN Technologies, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef RUN_COMMAND_H +#define RUN_COMMAND_H + +#include "basic.h" +#include "env_set.h" + +/* Script security */ +#define SSEC_NONE 0 /* strictly no calling of external programs */ +#define SSEC_BUILT_IN 1 /* only call built-in programs such as ifconfig, route, netsh, etc.*/ +#define SSEC_SCRIPTS 2 /* allow calling of built-in programs and user-defined scripts */ +#define SSEC_PW_ENV 3 /* allow calling of built-in programs and user-defined scripts that may receive a password as an environmental variable */ + +int script_security(void); + +void script_security_set(int level); + +/* openvpn_execve flags */ +#define S_SCRIPT (1<<0) +#define S_FATAL (1<<1) + +/* wrapper around the execve() call */ +int openvpn_popen(const struct argv *a, const struct env_set *es); + +bool openvpn_execve_allowed(const unsigned int flags); + +bool openvpn_execve_check(const struct argv *a, const struct env_set *es, + const unsigned int flags, const char *error_message); + +static inline bool +openvpn_run_script(const struct argv *a, const struct env_set *es, + const unsigned int flags, const char *hook) +{ + char msg[256]; + + openvpn_snprintf(msg, sizeof(msg), + "WARNING: Failed running command (%s)", hook); + return openvpn_execve_check(a, es, flags | S_SCRIPT, msg); +} + +#endif /* ifndef RUN_COMMAND_H */ diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c index 0fc91f2..e7126da 100644 --- a/src/openvpn/socket.c +++ b/src/openvpn/socket.c @@ -35,6 +35,7 @@ #include "gremlin.h" #include "plugin.h" #include "ps.h" +#include "run_command.h" #include "manage.h" #include "misc.h" #include "manage.h" diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c index 3568bad..6e7b0b0 100644 --- a/src/openvpn/ssl_verify.c +++ b/src/openvpn/ssl_verify.c @@ -34,10 +34,10 @@ #include "syshead.h" -#include "misc.h" +#include "base64.h" #include "manage.h" #include "otime.h" -#include "base64.h" +#include "run_command.h" #include "ssl_verify.h" #include "ssl_verify_backend.h" diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index 0c94d73..6fbe3e5 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -32,6 +32,7 @@ #include "base64.h" #include "crypto.h" #include "platform.h" +#include "run_command.h" #include "session_id.h" #include "ssl.h" diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c index 25831ce..6103163 100644 --- a/src/openvpn/tun.c +++ b/src/openvpn/tun.c @@ -40,7 +40,7 @@ #include "tun.h" #include "fdmisc.h" #include "common.h" -#include "misc.h" +#include "run_command.h" #include "socket.h" #include "manage.h" #include "route.h" diff --git a/src/openvpn/win32.c b/src/openvpn/win32.c index 95fea5d..1e5d871 100644 --- a/src/openvpn/win32.c +++ b/src/openvpn/win32.c @@ -39,9 +39,9 @@ #include "buffer.h" #include "error.h" #include "mtu.h" +#include "run_command.h" #include "sig.h" #include "win32.h" -#include "misc.h" #include "openvpn-msg.h" #include "memdbg.h" @@ -1137,7 +1137,7 @@ openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned in free(env); gc_free(&gc); } - else if (!exec_warn && (script_security < SSEC_SCRIPTS)) + else if (!exec_warn && (script_security() < SSEC_SCRIPTS)) { msg(M_WARN, SCRIPT_SECURITY_WARNING); exec_warn = true; diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index b69da84..a5d7f01 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -44,7 +44,8 @@ crypto_testdriver_SOURCES = test_crypto.c mock_msg.c \ $(openvpn_srcdir)/env_set.c \ $(openvpn_srcdir)/otime.c \ $(openvpn_srcdir)/packet_id.c \ - $(openvpn_srcdir)/platform.c + $(openvpn_srcdir)/platform.c \ + $(openvpn_srcdir)/run_command.c packet_id_testdriver_CFLAGS = @TEST_CFLAGS@ \ -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \ diff --git a/tests/unit_tests/openvpn/test_crypto.c b/tests/unit_tests/openvpn/test_crypto.c index 62d5b3f..b4ef668 100644 --- a/tests/unit_tests/openvpn/test_crypto.c +++ b/tests/unit_tests/openvpn/test_crypto.c @@ -41,8 +41,6 @@ #include "mock_msg.h" -int script_security = 0; /* Avoid including misc.c */ - static const char testtext[] = "Dummy text to test PEM encoding"; /** From patchwork Fri Dec 8 01:07:50 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 144 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director6.mail.ord1d.rsapps.net ([172.27.255.58]) by backend31.mail.ord1d.rsapps.net (Dovecot) with LMTP id i6SiHPCAKlqaUAAAgoeIoA for ; Fri, 08 Dec 2017 07:09:20 -0500 Received: from proxy21.mail.iad3a.rsapps.net ([172.27.255.58]) by director6.mail.ord1d.rsapps.net (Dovecot) with LMTP id G7ZdFfCAKloPIQAAhgvE6Q ; Fri, 08 Dec 2017 07:09:20 -0500 Received: from smtp1.gate.iad3a ([172.27.255.58]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy21.mail.iad3a.rsapps.net (Dovecot) with LMTP id cYBQD/CAKlpaZAAASBQwCQ ; Fri, 08 Dec 2017 07:09:20 -0500 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.34.181.88] Authentication-Results: smtp1.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.34.181.88"; 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; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Classification-ID: 9e2cfd16-dc10-11e7-a697-52540091dea5-1-1 Received: from [216.34.181.88] ([216.34.181.88:26913] helo=lists.sourceforge.net) by smtp1.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id D5/9C-04923-FE08A2A5; Fri, 08 Dec 2017 07:09:19 -0500 Received: from localhost ([127.0.0.1] helo=sfs-ml-4.v29.ch3.sourceforge.com) by sfs-ml-4.v29.ch3.sourceforge.com with esmtp (Exim 4.89) (envelope-from ) id 1eNHS4-0007CS-9L; Fri, 08 Dec 2017 12:08:24 +0000 Received: from sfi-mx-2.v28.ch3.sourceforge.com ([172.29.28.192] helo=mx.sourceforge.net) by sfs-ml-4.v29.ch3.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.89) (envelope-from ) id 1eNHS3-0007CJ-Nr for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:23 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Type:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:CC:To:From:Sender:Reply-To:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=cUQCwVr30Zb9Rl35G+V1nkOJIRTHkGjOgG2a7yds57M=; b=l7udM856aAuDNZJrYqZL0Um5cX 8lMPVG+E1vY+rykoRZV++rMLRD1yvJDzuqYRIhVmaHumH/5lbArH/SBANnhQoo9bXvmT+/Je3W7yH WkM8bgl3Zt6NfwvMD/x+YqSyjquNek4R+xaLtz2vyd+EndBpIN0wvvEdpG/9N/TBanaU=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject: CC:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=cUQCwVr30Zb9Rl35G+V1nkOJIRTHkGjOgG2a7yds57M=; b=kN+Ny3/HQJeWxiFB+dhbA+BkHB gHfQljrhXCxLNDzaIUgfm5F2wbiq3DHKUBSitSsyeMRnvhs9DqIVTp4infh3/wOUymN47LxUnB+25 WhwkYvbRiUgDIcF/kec7q8kKieYp/5vQJpqhAiPVno/wA+oEGQjPtc/YndyOLfeVwKwM=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-2.v28.ch3.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.89) id 1eNHS1-0004L7-N2 for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:23 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id 808021C5248; Fri, 8 Dec 2017 13:08:08 +0100 (CET) Received: from steffan-fox.fox.local (172.16.5.166) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Fri, 8 Dec 2017 13:08:08 +0100 From: Steffan Karger To: Date: Fri, 8 Dec 2017 13:07:50 +0100 Message-ID: <1512734870-17133-11-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> MIME-Version: 1.0 X-ClientProxiedBy: FOXDFT52.FOX.local (10.0.0.129) To FOXDFT52.FOX.local (10.0.0.129) X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -0.0 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1eNHS1-0004L7-N2 Subject: [Openvpn-devel] [PATCH 10/10] tls-crypt-v2: Add script hook to verify metadata 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: , Cc: Antonio Quartulli Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox To allow rejecting incoming connections very early in the handshake, add a --tls-crypt-v2-verify option that allows administators to run an external command to verify the metadata from the client key. See doc/tls-crypt-v2.txt for more details. Because of the extra dependencies, this requires adding a mock parse_line() to the tls-crypt unit tests. Signed-off-by: Antonio Quartulli Signed-off-by: Steffan Karger --- Changes.rst | 12 ++++++ doc/openvpn.8 | 35 ++++++++++++++-- src/openvpn/init.c | 1 + src/openvpn/options.c | 7 ++++ src/openvpn/options.h | 2 + src/openvpn/ssl.c | 16 ++++--- src/openvpn/ssl_common.h | 1 + src/openvpn/tls_crypt.c | 69 ++++++++++++++++++++++++++++++- src/openvpn/tls_crypt.h | 3 +- tests/unit_tests/openvpn/Makefile.am | 12 ++++-- tests/unit_tests/openvpn/test_tls_crypt.c | 16 +++++++ 11 files changed, 160 insertions(+), 14 deletions(-) diff --git a/Changes.rst b/Changes.rst index a6090cf..e77b3d7 100644 --- a/Changes.rst +++ b/Changes.rst @@ -1,3 +1,15 @@ +Overview of changes in 2.5 +========================== + +New features +------------ +Client-specific tls-crypt keys (``--tls-crypt-v2``) + ``tls-crypt-v2`` adds the ability to supply each client with a unique + tls-crypt key. This allows large organisations and VPN providers to profit + from the same DoS and TLS stack protection that small deployments can + already achieve using ``tls-auth`` or ``tls-crypt``. + + Overview of changes in 2.4 ========================== diff --git a/doc/openvpn.8 b/doc/openvpn.8 index 0719087..2cbdfcd 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -5247,9 +5247,38 @@ If supplied, include the supplied in the wrapped client key. This metadata must be supplied in base64\-encoded form. The metadata must be at most 735 bytes long (980 bytes in base64). -.B TODO -Metadata handling is not yet implemented. This text will be updated by the -commit that introduces metadata handling. +If no metadata is supplied, OpenVPN will use a 64-bit unix timestamp +representing the current time in UTC, encoded in network order, as metadata for +the generated key. + +Servers can use +.B \-\-tls\-crypt\-v2\-verify +to specify a metadata verification command. +.\"********************************************************* +.TP +.B \-\-tls\-crypt\-v2\-verify cmd + +Run command +.B cmd +to verify the metadata of the client-specific tls-crypt-v2 key of a connecting +client. This allows server administrators to reject client connections, before +exposing the TLS stack (including the notoriously dangerous X.509 and ASN.1 +stacks) to the connecting client. + +OpenVPN supplies the following env vars to the command: +.RS +.IP \[bu] 2 +script_type is set to "tls-crypt-v2-verify" +.IP \[bu] +metadata_type is set to "0" is the metadata was user supplied, or "1" if it's a +64-bit unix timestamp representing the key creation time. +.IP \[bu] +metadata_file contains the filename of a temporary file that contains the client +metadata. +.RE + +.IP +The command can reject the connection by exitingwith a non-zero exit code. .\"********************************************************* .TP .B \-\-askpass [file] diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 93143b1..c80df81 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -2862,6 +2862,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) if (options->tls_server) { to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key; + to.tls_crypt_v2_verify_script = c->options.tls_crypt_v2_verify_script; } } diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 2d577f8..3d0e03b 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -629,6 +629,8 @@ static const char usage_message[] = "--tls-crypt-v2-genkey client|server keyfile [base64 metadata]: Generate a\n" " fresh tls-crypt-v2 client or server key, and store to\n" " keyfile. If supplied, include metadata in wrapped key.\n" + "--tls-crypt-v2-verify cmd : Run command cmd to verify the metadata of the\n" + " client-supplied tls-crypt-v2 client key\n" "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n" "--auth-nocache : Don't cache --askpass or --auth-user-pass passwords.\n" "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n" @@ -8053,6 +8055,11 @@ add_option(struct options *options, options->tls_crypt_v2_metadata = p[3]; } } + else if (streq(p[0], "tls-crypt-v2-verify") && p[1] && !p[2]) + { + VERIFY_PERMISSION(OPT_P_GENERAL); + options->tls_crypt_v2_verify_script = p[1]; + } else if (streq(p[0], "key-method") && p[1] && !p[2]) { int key_method; diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 7d995c3..068e051 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -575,6 +575,8 @@ struct options const char *tls_crypt_v2_genkey_file; const char *tls_crypt_v2_metadata; + const char *tls_crypt_v2_verify_script; + /* Allow only one session */ bool single_session; diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 6090826..854b923 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1534,14 +1534,15 @@ write_control_auth(struct tls_session *session, static bool read_control_auth(struct buffer *buf, struct tls_wrap_ctx *ctx, - const struct link_socket_actual *from) + const struct link_socket_actual *from, + const struct tls_options *opt) { struct gc_arena gc = gc_new(); bool ret = false; const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT; if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3 - && !tls_crypt_v2_extract_client_key(buf, ctx)) + && !tls_crypt_v2_extract_client_key(buf, ctx, opt)) { msg (D_TLS_ERRORS, "TLS Error: can not extract tls-crypt-v2 client key from %s", @@ -3619,7 +3620,8 @@ tls_pre_decrypt(struct tls_multi *multi, goto error; } - if (!read_control_auth(buf, &session->tls_wrap, from)) + if (!read_control_auth(buf, &session->tls_wrap, from, + session->opt)) { goto error; } @@ -3672,7 +3674,8 @@ tls_pre_decrypt(struct tls_multi *multi, if (op == P_CONTROL_SOFT_RESET_V1 && DECRYPT_KEY_ENABLED(multi, ks)) { - if (!read_control_auth(buf, &session->tls_wrap, from)) + if (!read_control_auth(buf, &session->tls_wrap, from, + session->opt)) { goto error; } @@ -3693,7 +3696,8 @@ tls_pre_decrypt(struct tls_multi *multi, do_burst = true; } - if (!read_control_auth(buf, &session->tls_wrap, from)) + if (!read_control_auth(buf, &session->tls_wrap, from, + session->opt)) { goto error; } @@ -3895,7 +3899,7 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas, bool status; /* HMAC test, if --tls-auth was specified */ - status = read_control_auth(&newbuf, &tls_wrap_tmp, from); + status = read_control_auth(&newbuf, &tls_wrap_tmp, from, NULL); free_buf(&newbuf); if (tls_wrap_tmp.cleanup_key_ctx) { diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index d6e0d1b..d52f337 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -293,6 +293,7 @@ struct tls_options bool ncp_enabled; bool tls_crypt_v2; + const char *tls_crypt_v2_verify_script; /** TLS handshake wrapping state */ struct tls_wrap_ctx tls_wrap; diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index 6fbe3e5..2d53f44 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -29,8 +29,10 @@ #include "syshead.h" +#include "argv.h" #include "base64.h" #include "crypto.h" +#include "misc.h" #include "platform.h" #include "run_command.h" #include "session_id.h" @@ -521,9 +523,69 @@ error_exit: return ret; } +static bool +tls_crypt_v2_verify_metadata(const struct tls_wrap_ctx *ctx, + const struct tls_options *opt) +{ + bool ret = false; + struct gc_arena gc = gc_new(); + const char *tmp_file = NULL; + struct buffer metadata = ctx->tls_crypt_v2_metadata; + int metadata_type = buf_read_u8(&metadata); + if (metadata_type < 0) + { + msg(M_WARN, "ERROR: no metadata type"); + goto cleanup; + } + + tmp_file = platform_create_temp_file(opt->tmp_dir, "tls_crypt_v2_metadata_", + &gc); + if (!tmp_file || !buffer_write_file(tmp_file, &metadata)) + { + msg(M_WARN, "ERROR: could not write metadata to file"); + goto cleanup; + } + + char metadata_type_str[4] = { 0 }; /* Max value: 255 */ + openvpn_snprintf(metadata_type_str, sizeof(metadata_type_str), + "%i", metadata_type); + struct env_set *es = env_set_create(NULL); + setenv_str(es, "script_type", "tls-crypt-v2-verify"); + setenv_str(es, "metadata_type", metadata_type_str); + setenv_str(es, "metadata_file", tmp_file); + + struct argv argv = argv_new(); + argv_parse_cmd(&argv, opt->tls_crypt_v2_verify_script); + argv_msg_prefix(D_TLS_DEBUG, &argv, "Executing tls-crypt-v2-verify"); + + ret = openvpn_run_script(&argv, es, 0, "--tls-crypt-v2-verify"); + + argv_reset(&argv); + env_set_destroy(es); + + if (!platform_unlink(tmp_file)) + { + msg(M_WARN, "WARNING: failed to remove temp file '%s", tmp_file); + } + + if (ret) + { + msg(D_HANDSHAKE, "TLS CRYPT V2 VERIFY SCRIPT OK"); + } + else + { + msg(D_HANDSHAKE, "TLS CRYPT V2 VERIFY SCRIPT ERROR"); + } + +cleanup: + gc_free(&gc); + return ret; +} + bool tls_crypt_v2_extract_client_key(struct buffer *buf, - struct tls_wrap_ctx *ctx) + struct tls_wrap_ctx *ctx, + const struct tls_options *opt) { static const int hard_reset_length = TLS_CRYPT_OFF_CT + sizeof(uint8_t) + sizeof(packet_id_type); @@ -567,6 +629,11 @@ tls_crypt_v2_extract_client_key(struct buffer *buf, /* Remove client key from buffer so tls-crypt code can unwrap message */ ASSERT(buf_inc_len(buf, -(BLEN(&wrapped_client_key)))); + if (opt && opt->tls_crypt_v2_verify_script) + { + return tls_crypt_v2_verify_metadata(ctx, opt); + } + return true; } diff --git a/src/openvpn/tls_crypt.h b/src/openvpn/tls_crypt.h index e720729..d9174da 100644 --- a/src/openvpn/tls_crypt.h +++ b/src/openvpn/tls_crypt.h @@ -193,7 +193,8 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key, * @returns true if a key was successfully extracted. */ bool tls_crypt_v2_extract_client_key(struct buffer *buf, - struct tls_wrap_ctx *ctx); + struct tls_wrap_ctx *ctx, + const struct tls_options *opt); /** * Generate a tls-crypt-v2 server key, and write to file. diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index a5d7f01..7346230 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -6,7 +6,10 @@ if HAVE_LD_WRAP_SUPPORT check_PROGRAMS += argv_testdriver buffer_testdriver endif -check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver +check_PROGRAMS += crypto_testdriver packet_id_testdriver +if HAVE_LD_WRAP_SUPPORT +check_PROGRAMS += tls_crypt_testdriver +endif TESTS = $(check_PROGRAMS) @@ -62,14 +65,17 @@ packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \ tls_crypt_testdriver_CFLAGS = @TEST_CFLAGS@ \ -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \ $(OPTIONAL_CRYPTO_CFLAGS) $(OPTIONAL_PKCS11_HELPER_CFLAGS) -tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ +tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ -Wl,--wrap=parse_line \ $(OPTIONAL_CRYPTO_LIBS) tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \ + $(openvpn_srcdir)/argv.c \ $(openvpn_srcdir)/base64.c \ $(openvpn_srcdir)/buffer.c \ $(openvpn_srcdir)/crypto.c \ $(openvpn_srcdir)/crypto_mbedtls.c \ $(openvpn_srcdir)/crypto_openssl.c \ + $(openvpn_srcdir)/env_set.c \ $(openvpn_srcdir)/otime.c \ $(openvpn_srcdir)/packet_id.c \ - $(openvpn_srcdir)/platform.c + $(openvpn_srcdir)/platform.c \ + $(openvpn_srcdir)/run_command.c diff --git a/tests/unit_tests/openvpn/test_tls_crypt.c b/tests/unit_tests/openvpn/test_tls_crypt.c index 724abda..ca23ecb 100644 --- a/tests/unit_tests/openvpn/test_tls_crypt.c +++ b/tests/unit_tests/openvpn/test_tls_crypt.c @@ -43,8 +43,24 @@ #define TESTBUF_SIZE 128 +/* Defines for use in the tests and the mock parse_line() */ +#define PATH1 "/s p a c e" +#define PATH2 "/foo bar/baz" +#define PARAM1 "param1" +#define PARAM2 "param two" + const char plaintext_short[1]; +int +__wrap_parse_line(const char *line, char **p, const int n, const char *file, + const int line_num, int msglevel, struct gc_arena *gc) +{ + p[0] = PATH1 PATH2; + p[1] = PARAM1; + p[2] = PARAM2; + return 3; +} + struct test_tls_crypt_context { struct crypto_options co; struct key_type kt;