From patchwork Wed Jul 25 06:08:47 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 428 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director11.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id dDNwDfygWFs1WQAAIUCqbw for ; Wed, 25 Jul 2018 12:10:36 -0400 Received: from proxy17.mail.iad3b.rsapps.net ([172.31.255.6]) by director11.mail.ord1d.rsapps.net (Dovecot) with LMTP id hxOjA/ygWFvHJQAAvGGmqA ; Wed, 25 Jul 2018 12:10:36 -0400 Received: from smtp9.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy17.mail.iad3b.rsapps.net with LMTP id YDrhEPygWFtyHgAA5ccGVQ ; Wed, 25 Jul 2018 12:10:36 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp9.gate.iad3b.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Suspicious-Flag: YES X-Classification-ID: 4322369c-9025-11e8-a6fa-525400f4d366-1-1 Received: from [216.105.38.7] ([216.105.38.7:63997] helo=lists.sourceforge.net) by smtp9.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 96/C4-04181-BF0A85B5; Wed, 25 Jul 2018 12:10:35 -0400 Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1fiMM2-0007Hk-Sl; Wed, 25 Jul 2018 16:09:34 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1fiMM1-0007H6-KN for openvpn-devel@lists.sourceforge.net; Wed, 25 Jul 2018 16:09:33 +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=JU+F2wz511Fk75XhyXcagEu8dgtaRZwuWQzjWqKjYnY=; b=AgzxRpiA3YCZh8LRNW9MSMJ9Qo qaluWjwpCXx3RbuLJ1+owiZgyorYn0GDIB/IA8E+ulUaA5a88aMli5nOs5/w1oADWswv7F+kr/VNH HyK3qhSl1YfnUxyH1kYgUVzU4csngWbehcLzT9D6vni7yMmDc1lqlccnwQhhN/WxxGHA=; 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=JU+F2wz511Fk75XhyXcagEu8dgtaRZwuWQzjWqKjYnY=; b=XPjBFgtEc3ZBym50vk78R3VWkd yJLeMaaMbw9dXsdS/FflO4KUL/Cjc+fikejQzkdLhYN7STqUYLPySzS0wEa0XbY8OdGal/JUykRFB jbIEALx3p67v1PUpSIpdpS4IrLMlrDiDJ95WSeACJLPWG2fMCge7J9UR/6iZEBXHCW14=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-4.v28.lw.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.90_1) id 1fiMLz-00BS9A-I7 for openvpn-devel@lists.sourceforge.net; Wed, 25 Jul 2018 16:09:33 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id 906691AF667 for ; Wed, 25 Jul 2018 18:09:22 +0200 (CEST) Received: from steffan-fox.fox.local (10.0.3.178) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Wed, 25 Jul 2018 18:09:22 +0200 From: Steffan Karger To: Date: Wed, 25 Jul 2018 18:08:47 +0200 Message-ID: <1532534933-3858-1-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 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1fiMLz-00BS9A-I7 Subject: [Openvpn-devel] [PATCH v3 1/7] Introduce buffer_write_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: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox Rewrite buf_write_string_file to buffer_write_file, which is simpler to use and can deal with not-null-terminated strings. Mostly implemented so this can be easily reused for tls-crypt-v2 (client) key files. Signed-off-by: Steffan Karger --- v3: split change out of "generate client key", reuse in write_key_file() src/openvpn/buffer.c | 31 ++++++++++++++++++++++++------- src/openvpn/buffer.h | 7 ++----- src/openvpn/crypto.c | 30 +++++------------------------- 3 files changed, 31 insertions(+), 37 deletions(-) diff --git a/src/openvpn/buffer.c b/src/openvpn/buffer.c index 0972139..20e2b9c 100644 --- a/src/openvpn/buffer.c +++ b/src/openvpn/buffer.c @@ -343,16 +343,33 @@ convert_to_one_line(struct buffer *buf) } } -/* NOTE: requires that string be null terminated */ -void -buf_write_string_file(const struct buffer *buf, const char *filename, int fd) +bool +buffer_write_file(const char *filename, const struct buffer *buf) { - const int len = strlen((char *) BPTR(buf)); - const int size = write(fd, BPTR(buf), len); - if (size != len) + 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_ERR, "Write error on file '%s'", filename); + 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; } /* diff --git a/src/openvpn/buffer.h b/src/openvpn/buffer.h index 6b6025e..180dd56 100644 --- a/src/openvpn/buffer.h +++ b/src/openvpn/buffer.h @@ -469,11 +469,8 @@ const char *skip_leading_whitespace(const char *str); void string_null_terminate(char *str, int len, int capacity); -/* - * Write string in buf to file descriptor fd. - * NOTE: requires that string be null terminated. - */ -void buf_write_string_file(const struct buffer *buf, const char *filename, int fd); +/** Write buffer contents to file */ +bool buffer_write_file(const char *filename, const struct buffer *buf); /* * write a string to the end of a buffer that was diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c index 5381ef2..35bd243 100644 --- a/src/openvpn/crypto.c +++ b/src/openvpn/crypto.c @@ -1427,27 +1427,19 @@ write_key_file(const int nkeys, const char *filename) { struct gc_arena gc = gc_new(); - int fd, i; - int nbits = 0; + int nbits = nkeys * sizeof(struct key) * 8; /* must be large enough to hold full key file */ struct buffer out = alloc_buf_gc(2048, &gc); - struct buffer nbits_head_text = alloc_buf_gc(128, &gc); /* how to format the ascii file representation of key */ const int bytes_per_line = 16; - /* open key file */ - fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR); - - if (fd == -1) - { - msg(M_ERR, "Cannot open shared secret file '%s' for write", filename); - } - + /* write header */ + buf_printf(&out, "#\n# %d bit OpenVPN static key\n#\n", nbits); buf_printf(&out, "%s\n", static_key_head); - for (i = 0; i < nkeys; ++i) + for (int i = 0; i < nkeys; ++i) { struct key key; char *fmt; @@ -1463,9 +1455,6 @@ write_key_file(const int nkeys, const char *filename) "\n", &gc); - /* increment random bits counter */ - nbits += sizeof(key) * 8; - /* write to holding buffer */ buf_printf(&out, "%s\n", fmt); @@ -1476,17 +1465,8 @@ write_key_file(const int nkeys, const char *filename) buf_printf(&out, "%s\n", static_key_foot); - /* write number of bits */ - buf_printf(&nbits_head_text, "#\n# %d bit OpenVPN static key\n#\n", nbits); - buf_write_string_file(&nbits_head_text, filename, fd); - /* write key file, now formatted in out, to file */ - buf_write_string_file(&out, filename, fd); - - if (close(fd)) - { - msg(M_ERR, "Close error on shared secret file %s", filename); - } + buffer_write_file(filename, &out); /* zero memory which held file content (memory will be freed by GC) */ buf_clear(&out); From patchwork Wed Jul 25 06:08:48 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 423 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director12.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id JeMQBuugWFu1EwAAIUCqbw for ; Wed, 25 Jul 2018 12:10:19 -0400 Received: from proxy1.mail.iad3b.rsapps.net ([172.31.255.6]) by director12.mail.ord1d.rsapps.net (Dovecot) with LMTP id rctlCOugWFu8GgAAIasKDg ; Wed, 25 Jul 2018 12:10:19 -0400 Received: from smtp32.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy1.mail.iad3b.rsapps.net with LMTP id eAUWDeugWFtKEwAALM5PBw ; Wed, 25 Jul 2018 12:10:19 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp32.gate.iad3b.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Suspicious-Flag: YES X-Classification-ID: 390c2014-9025-11e8-86f9-5254006a2e70-1-1 Received: from [216.105.38.7] ([216.105.38.7:59382] helo=lists.sourceforge.net) by smtp32.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 43/DA-01785-AE0A85B5; Wed, 25 Jul 2018 12:10:19 -0400 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1fiMM2-00043u-J6; Wed, 25 Jul 2018 16:09:34 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1fiMM1-00043j-5t for openvpn-devel@lists.sourceforge.net; Wed, 25 Jul 2018 16:09:33 +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=kpqXoJC952/CrYvcCXh8NzSTjCUOu6pA/Vml5ujZeqE=; b=U5kHxi8F6XoxIRYX0rLSu5u0iy UATDdUFXgZkFQshDp3qXyh7C05sUfcnKR0tU8IHxSVa982w+/4PG3aq+eifBYLaaNhI7WUFUwheBv OEv/L0S0xH1/WzlkzGsF8wxV5g0WwTSzgIyKDQIZF3t1oXjDSUYuTolp/UOvEsEBwc3I=; 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=kpqXoJC952/CrYvcCXh8NzSTjCUOu6pA/Vml5ujZeqE=; b=RjI3RE5yyr8T/d60dpbpJpF2wk 83Lhb91WeK2WXid5/sQrch3Jz6uEkDyYeLpL/STfBfRn5w45Qdydo00MiSeYwvjzyqwBS5RyMWbtA 2sE+baxqFoseXXOjbNQrBlUzeNl9/kmVw2nvRBqSDzz7BbaFURWHWgBFKH351XNac9YA=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-3.v28.lw.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.90_1) id 1fiMM0-008Ivh-PK for openvpn-devel@lists.sourceforge.net; Wed, 25 Jul 2018 16:09:33 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id D86EE1AF66A for ; Wed, 25 Jul 2018 18:09:22 +0200 (CEST) Received: from steffan-fox.fox.local (10.0.3.178) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Wed, 25 Jul 2018 18:09:22 +0200 From: Steffan Karger To: Date: Wed, 25 Jul 2018 18:08:48 +0200 Message-ID: <1532534933-3858-2-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1532534933-3858-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> <1532534933-3858-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. X-Headers-End: 1fiMM0-008Ivh-PK Subject: [Openvpn-devel] [PATCH v3 2/7] 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 --- v3: Include length in WKc doc/tls-crypt-v2.txt | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 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..cc6453c --- /dev/null +++ b/doc/tls-crypt-v2.txt @@ -0,0 +1,170 @@ +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: + + ``len = len(Kc || metadata)`` (16 bit, network byte order) + + ``T = HMAC-SHA256(Ka, len || Kc || metadata)`` + + ``IV = 128 most significant bits of T`` + + ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata || len)`` + +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, 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 + + 1. reads the WKc length field from the end of the message, and extracts WKc + from the message + 2. unwraps ``WKc`` + 3. uses unwrapped ``Kc`` to verify the remaining + P_CONTROL_HARD_RESET_CLIENT_V3 message's (encryption and) authentication. + + The message is dropped and no error response is sent when either 3.1, 3.2 or + 3.3 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. + +WKc includes a length field, so we leave the option for future extension of the +P_CONTROL_HEAD_RESET_CLIENT_V3 message open. (E.g. add payload to the reset to +indicate low-level protocol features.) + +``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 Wed Jul 25 06:08:49 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 424 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director9.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id 7D8QKuygWFvsNAAAIUCqbw for ; Wed, 25 Jul 2018 12:10:20 -0400 Received: from proxy19.mail.iad3b.rsapps.net ([172.31.255.6]) by director9.mail.ord1d.rsapps.net (Dovecot) with LMTP id Rz/+EeygWFvQHwAAalYnBA ; Wed, 25 Jul 2018 12:10:20 -0400 Received: from smtp6.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy19.mail.iad3b.rsapps.net with LMTP id SNQsKuygWFsLagAAIG4riQ ; Wed, 25 Jul 2018 12:10:20 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp6.gate.iad3b.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Suspicious-Flag: YES X-Classification-ID: 39cd398e-9025-11e8-b1a3-5254000d607e-1-1 Received: from [216.105.38.7] ([216.105.38.7:18218] helo=lists.sourceforge.net) by smtp6.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 97/0A-32606-BE0A85B5; Wed, 25 Jul 2018 12:10:20 -0400 Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1fiMM1-0007H7-L2; Wed, 25 Jul 2018 16:09:33 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1fiMM0-0007Gi-9L for openvpn-devel@lists.sourceforge.net; Wed, 25 Jul 2018 16:09:32 +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=GuDS+k+YgO+4cNLTsUpoVb7gbZjAiQ18/nXfv8W5H3s=; b=bcq1MU58HJ3vsN+5jWgxzBa7W/ EPh/cl93KeJCvnTxhqAjIdOzyqMLw2SYeOgc30bokXhCDauVDwXSUSQRRLL8CaI8PRY0wtpbWhvlD DZ46ASWC0pPf+3QnOw5l/8Tpbfat2jsfg3/w06WoDO1DIjVrOG1zEWqUBBOcwb5ekWa8=; 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=GuDS+k+YgO+4cNLTsUpoVb7gbZjAiQ18/nXfv8W5H3s=; b=in7IyfmDc6VOa/98gKW9/csEKP r8NMrtf8fPxjZa/RdxFSp3GbFi63cRlfEfuJISbm6WkSekWpIYttPCZw1lESdfJn0s1qxxHUtkOD7 c8JwY4d0rfNv40CecSoBJRJj/KU2m/AQ7fy6wcYkHcCzxh+DNokUf+CR+EI074fDXf10=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.90_1) id 1fiMLx-001ASG-L6 for openvpn-devel@lists.sourceforge.net; Wed, 25 Jul 2018 16:09:32 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id 219E81AF6EB for ; Wed, 25 Jul 2018 18:09:23 +0200 (CEST) Received: from steffan-fox.fox.local (10.0.3.178) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Wed, 25 Jul 2018 18:09:22 +0200 From: Steffan Karger To: Date: Wed, 25 Jul 2018 18:08:49 +0200 Message-ID: <1532534933-3858-3-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1532534933-3858-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> <1532534933-3858-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 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1fiMLx-001ASG-L6 Subject: [Openvpn-devel] [PATCH v3 3/7] 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 --- v3: Include length in WKc doc/openvpn.8 | 51 +++++++++ src/openvpn/init.c | 35 +++++- src/openvpn/integer.h | 10 ++ src/openvpn/options.c | 66 ++++++++++- src/openvpn/options.h | 14 +++ src/openvpn/tls_crypt.c | 288 ++++++++++++++++++++++++++++++++++++++++++++++++ src/openvpn/tls_crypt.h | 81 ++++++++++++-- tests/t_lpback.sh | 40 ++++++- 8 files changed, 565 insertions(+), 20 deletions(-) diff --git a/doc/openvpn.8 b/doc/openvpn.8 index f01b48b..597c0c4 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -5248,6 +5248,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/init.c b/src/openvpn/init.c index f432106..ef7b422 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -1028,6 +1028,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; @@ -1035,11 +1040,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, @@ -1047,6 +1047,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 a7e19d3..b1ae0ed 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 61fa983..acab042 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -622,6 +622,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" @@ -1512,6 +1519,7 @@ show_connection_entry(const struct connection_entry *o) SHOW_PARM(key_direction, keydirection2ascii(o->key_direction, false, true), "%s"); SHOW_STR(tls_crypt_file); + SHOW_STR(tls_crypt_v2_file); } @@ -1792,6 +1800,10 @@ show_settings(const struct options *o) SHOW_BOOL(push_peer_info); SHOW_BOOL(tls_exit); + SHOW_STR(tls_crypt_v2_genkey_type); + SHOW_STR(tls_crypt_v2_genkey_file); + SHOW_STR(tls_crypt_v2_metadata); + #ifdef ENABLE_PKCS11 { int i; @@ -2730,6 +2742,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->tls_client && ((ce->tls_auth_file && ce->tls_crypt_v2_file) + || (ce->tls_crypt_file && ce->tls_crypt_v2_file))) + { + msg(M_USAGE, "--tls-auth, --tls-crypt and --tls-crypt-v2 are mutually exclusive in client mode"); + } } else { @@ -2764,6 +2781,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); @@ -2873,12 +2891,12 @@ options_postprocess_mutate_ce(struct options *o, struct connection_entry *ce) } /* - * Set per-connection block tls-auth/crypt fields if undefined. + * Set per-connection block tls-auth/crypt/crypto-v2 fields if undefined. * - * At the end only one of the two will be really set because the parser - * logic prevents configurations where both are set. + * At the end only one of these will be really set because the parser + * logic prevents configurations where more are set. */ - if (!ce->tls_auth_file && !ce->tls_crypt_file) + if (!ce->tls_auth_file && !ce->tls_crypt_file && !ce->tls_crypt_v2_file) { ce->tls_auth_file = o->tls_auth_file; ce->tls_auth_file_inline = o->tls_auth_file_inline; @@ -2886,6 +2904,9 @@ options_postprocess_mutate_ce(struct options *o, struct connection_entry *ce) ce->tls_crypt_file = o->tls_crypt_file; ce->tls_crypt_inline = o->tls_crypt_inline; + + ce->tls_crypt_v2_file = o->tls_crypt_v2_file; + ce->tls_crypt_v2_inline = o->tls_crypt_v2_inline; } /* pre-cache tls-auth/crypt key file if persist-key was specified and keys @@ -3342,9 +3363,15 @@ options_postprocess_filechecks(struct options *options) errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE, ce->tls_crypt_file, R_OK, "--tls-crypt"); + errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE, + ce->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, @@ -8118,6 +8145,37 @@ add_option(struct options *options, } } + else if (streq(p[0], "tls-crypt-v2") && p[1] && !p[3]) + { + VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_CONNECTION); + if (permission_mask & 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 (permission_mask & OPT_P_CONNECTION) + { + if (streq(p[1], INLINE_FILE_TAG) && p[2]) + { + options->ce.tls_crypt_v2_inline = p[2]; + } + options->ce.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 acbd108..3d2c770 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -139,6 +139,11 @@ struct connection_entry /* Shared secret used for TLS control channel authenticated encryption */ 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; }; struct remote_entry @@ -576,6 +581,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 36ead84..103a4fc 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,281 @@ 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; + } + uint16_t net_len = htons(sizeof(src_key->keys) + BLEN(src_metadata)); + hmac_ctx_t *hmac_ctx = server_key->hmac; + hmac_ctx_reset(hmac_ctx); + hmac_ctx_update(hmac_ctx, (void*)&net_len, sizeof(net_len)); + 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) + + sizeof(net_len) + + 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)); + ASSERT(buf_write(&work, &net_len, sizeof(net_len))); + + 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 067758c..6513836 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 (unsigned) (TLS_CRYPT_V2_MAX_WKC_LEN \ + - (TLS_CRYPT_V2_CLIENT_KEY_LEN + TLS_CRYPT_V2_TAG_SIZE + sizeof(uint16_t))) +#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..fb43211 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 -c732 /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 From patchwork Wed Jul 25 06:08:50 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 427 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director10.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id Y2oDCvGgWFvsNAAAIUCqbw for ; Wed, 25 Jul 2018 12:10:25 -0400 Received: from proxy19.mail.iad3b.rsapps.net ([172.31.255.6]) by director10.mail.ord1d.rsapps.net (Dovecot) with LMTP id Ax7jA/GgWFuyIwAApN4f7A ; Wed, 25 Jul 2018 12:10:25 -0400 Received: from smtp12.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy19.mail.iad3b.rsapps.net with LMTP id UDzMAfGgWFv5aQAAIG4riQ ; Wed, 25 Jul 2018 12:10:25 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp12.gate.iad3b.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Suspicious-Flag: YES X-Classification-ID: 3c537c54-9025-11e8-9ca9-525400ae1f9d-1-1 Received: from [216.105.38.7] ([216.105.38.7:37598] helo=lists.sourceforge.net) by smtp12.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id E2/90-26788-0F0A85B5; Wed, 25 Jul 2018 12:10:24 -0400 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1fiMM4-00044C-Lz; Wed, 25 Jul 2018 16:09:36 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1fiMM2-000446-UQ for openvpn-devel@lists.sourceforge.net; Wed, 25 Jul 2018 16:09:34 +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=J/SAqK9Af838Yl6d4jMj8j1mbY37If+Ak2nUo1tEpmk=; b=C1jjYsLtHXRghapJRKap+pDVkM 1OaZRYAlkt11k2bvbQTYWZU4SkEqXQ0rYnT0/oabzGZmH1RTZIeB1AHLiyVSiu06DYdYft8cj1huj m/v/rBZA/TguZPyhDNauVGhscOy/N6xOEG9jD+I7etjpmla/Cl6QyaLPW/9FAjQUgMmI=; 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=J/SAqK9Af838Yl6d4jMj8j1mbY37If+Ak2nUo1tEpmk=; b=mcbVbKmISk0HLzTzwsm5O33IZT ujb+ScdvdMDKhCP72pnx/eJiY7rv556qFJh9q8QWJyMLsYobH/l6GvaV2n5Em1MdSg+DCsWTgwK3B PN5ohIc2g9NGRBG9pSQ/vY9ZGZw9Q4LwvQ5NgSC+6KAxZYdkUsXE/hG+irkvQZ5qN94k=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-3.v28.lw.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.90_1) id 1fiMM0-008Ivi-QZ for openvpn-devel@lists.sourceforge.net; Wed, 25 Jul 2018 16:09:34 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id 2B5AE1AF6F4 for ; Wed, 25 Jul 2018 18:09:23 +0200 (CEST) Received: from steffan-fox.fox.local (10.0.3.178) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Wed, 25 Jul 2018 18:09:22 +0200 From: Steffan Karger To: Date: Wed, 25 Jul 2018 18:08:50 +0200 Message-ID: <1532534933-3858-4-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1532534933-3858-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> <1532534933-3858-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 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1fiMM0-008Ivi-QZ Subject: [Openvpn-devel] [PATCH v3 4/7] 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 --- v3: Include length in WKc src/openvpn/buffer.h | 7 + src/openvpn/tls_crypt.c | 120 ++++++++++++++ tests/unit_tests/openvpn/test_tls_crypt.c | 253 +++++++++++++++++++++++++++--- 3 files changed, 360 insertions(+), 20 deletions(-) diff --git a/src/openvpn/buffer.h b/src/openvpn/buffer.h index 180dd56..0c730ae 100644 --- a/src/openvpn/buffer.h +++ b/src/openvpn/buffer.h @@ -830,6 +830,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 103a4fc..6437eb1 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -433,6 +433,114 @@ 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 */ + uint16_t net_len = 0; + 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 (BLEN(&wrapped_client_key) < sizeof(net_len)) + { + CRYPT_ERROR("failed to read length"); + } + memcpy(&net_len, BEND(&wrapped_client_key) - sizeof(net_len), + sizeof(net_len)); + buf_inc_len(&wrapped_client_key, -(int)sizeof(net_len)); + + if (ntohs(net_len) != BLEN(&wrapped_client_key)) + { + dmsg(D_TLS_DEBUG_LOW, "%s: net_len=%u, BLEN=%i", __func__, + ntohs(net_len), BLEN(&wrapped_client_key)); + CRYPT_ERROR("invalid length"); + } + + 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, (void*)&net_len, sizeof(net_len)); + 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) { @@ -542,6 +650,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 f3228ad..f53255c 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 Wed Jul 25 06:08:51 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 422 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director7.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id C2wHDOugWFs1WQAAIUCqbw for ; Wed, 25 Jul 2018 12:10:19 -0400 Received: from proxy1.mail.iad3b.rsapps.net ([172.31.255.6]) by director7.mail.ord1d.rsapps.net (Dovecot) with LMTP id UxK1BuugWFutIwAAovjBpQ ; Wed, 25 Jul 2018 12:10:19 -0400 Received: from smtp24.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy1.mail.iad3b.rsapps.net with LMTP id 6AcLBeugWFtwEwAALM5PBw ; Wed, 25 Jul 2018 12:10:19 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp24.gate.iad3b.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Suspicious-Flag: YES X-Classification-ID: 39014c3e-9025-11e8-9c5c-525400892b35-1-1 Received: from [216.105.38.7] ([216.105.38.7:35967] helo=lists.sourceforge.net) by smtp24.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id FD/4F-16590-AE0A85B5; Wed, 25 Jul 2018 12:10:18 -0400 Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1fiMM0-0007Gp-I2; Wed, 25 Jul 2018 16:09:32 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1fiMLz-0007GY-Em for openvpn-devel@lists.sourceforge.net; Wed, 25 Jul 2018 16:09:31 +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=cQ5R/U3cMVtlUXU6uJPNCI7QtH8nUk8tqOxkhnjW8e0=; b=NXIFnU695HJ+lMEkRK8QZMShlm Ld2WNsYh1crUOxgWUMFGWO0h/7U0SSHlvthcPwKL0OB5Fk87uVaDGYbhdoo8m/jnKGVJ0lGbhRPM7 efI5cDOoAnwTd4pN7Kr5yztzTXTunkNQqMA8lp4wQff7KE3ysGlt8aiK1NOQnOdMPKps=; 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=cQ5R/U3cMVtlUXU6uJPNCI7QtH8nUk8tqOxkhnjW8e0=; b=SVquvd77eJ+rv9Mr3SjFMliTSY htTSla/WPoeNXxvHkEJfhJQtonBxDysr+A7YfsEwXrJPw+jfS6QsZvpYHJt+j+GGxgVdCFI7o+z3V MTcv7QCv/opurwvwJWWaAoamzopZVe9iM6ONf5Mo1wAl+2OkhJJnnf0K3TprPbqNOYqk=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.90_1) id 1fiMLx-000HLI-OY for openvpn-devel@lists.sourceforge.net; Wed, 25 Jul 2018 16:09:31 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id 35FB71AF714 for ; Wed, 25 Jul 2018 18:09:23 +0200 (CEST) Received: from steffan-fox.fox.local (10.0.3.178) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Wed, 25 Jul 2018 18:09:22 +0200 From: Steffan Karger To: Date: Wed, 25 Jul 2018 18:08:51 +0200 Message-ID: <1532534933-3858-5-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1532534933-3858-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> <1532534933-3858-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 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1fiMLx-000HLI-OY Subject: [Openvpn-devel] [PATCH v3 5/7] 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 --- v3: rebase on curent master / v3 patch set 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 25ab374..adec072 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 dcb5445..757754f 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -792,6 +792,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"; @@ -864,7 +867,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; } @@ -1095,8 +1099,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 */ @@ -3427,7 +3438,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)) { @@ -3812,7 +3824,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 72227d9..852debd 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 08ef6ff..e3c852a 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 Wed Jul 25 06:08:52 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 425 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director12.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id yUM4A+2gWFvUGAAAIUCqbw for ; Wed, 25 Jul 2018 12:10:21 -0400 Received: from proxy17.mail.iad3b.rsapps.net ([172.31.255.6]) by director12.mail.ord1d.rsapps.net (Dovecot) with LMTP id ayMwJu2gWFvKGwAAIasKDg ; Wed, 25 Jul 2018 12:10:21 -0400 Received: from smtp21.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy17.mail.iad3b.rsapps.net with LMTP id WPKWI+2gWFuWHgAA5ccGVQ ; Wed, 25 Jul 2018 12:10:21 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp21.gate.iad3b.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Suspicious-Flag: YES X-Classification-ID: 3a947800-9025-11e8-8063-525400cdc90a-1-1 Received: from [216.105.38.7] ([216.105.38.7:57351] helo=lists.sourceforge.net) by smtp21.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id D4/0B-25602-DE0A85B5; Wed, 25 Jul 2018 12:10:21 -0400 Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1fiMM9-0007JB-V6; Wed, 25 Jul 2018 16:09:41 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1fiMM7-0007Ig-Pt for openvpn-devel@lists.sourceforge.net; Wed, 25 Jul 2018 16:09:39 +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=Q5aW2udV3VaQoRPxayj5tg48JRDpvFbwTaQ0iDd62CY=; b=NT6pJrcpicLlQoXasyLi4EEK1t qBFxnMueI9mCukkweCd8x5GeoIbzX89R1gWaaUdjWx+nKjIbZRaSYu1U64N5XE4oJnRAaXQvW6d65 PqQ9iwhpgD2m14bcEmj/2HjXhQyAT/ME05P0K1ZQa+f6qm/IbuXsGSWhDZDR3cVBi7vw=; 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=Q5aW2udV3VaQoRPxayj5tg48JRDpvFbwTaQ0iDd62CY=; b=V4gpmThisNWUstbLBXvTDY4KZl 4pQMYSyBCleWKy31nrnsGgoM12T24rzXoEMZO5O2RiEBDjW2ebMWZ/Dq5FVeYSv52XQz4PTzdHcnL o+p9b1nENS7Lk7mymzWCJzBD6Mbesmg2dqweOYf+GPa4x96mBfYRyvw+NvolfFnJjIKM=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-4.v28.lw.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.90_1) id 1fiMM5-00BS9c-QW for openvpn-devel@lists.sourceforge.net; Wed, 25 Jul 2018 16:09:39 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id 810461AF720 for ; Wed, 25 Jul 2018 18:09:23 +0200 (CEST) Received: from steffan-fox.fox.local (10.0.3.178) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Wed, 25 Jul 2018 18:09:23 +0200 From: Steffan Karger To: Date: Wed, 25 Jul 2018 18:08:52 +0200 Message-ID: <1532534933-3858-6-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1532534933-3858-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> <1532534933-3858-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 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1fiMM5-00BS9c-QW Subject: [Openvpn-devel] [PATCH v3 6/7] 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 --- v3: include length in WKc, rebase on curent master / v3 patch set src/openvpn/init.c | 41 +++++++++++++++- src/openvpn/openvpn.h | 2 + src/openvpn/options.c | 6 +-- src/openvpn/ssl.c | 79 ++++++++++++++++++++++++------- src/openvpn/ssl_common.h | 6 +++ src/openvpn/tls_crypt.c | 57 ++++++++++++++++++++++ src/openvpn/tls_crypt.h | 14 ++++++ tests/unit_tests/openvpn/Makefile.am | 2 +- tests/unit_tests/openvpn/test_tls_crypt.c | 6 +++ 9 files changed, 192 insertions(+), 21 deletions(-) diff --git a/src/openvpn/init.c b/src/openvpn/init.c index ef7b422..01667c3 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -2435,6 +2435,11 @@ key_schedule_free(struct key_schedule *ks, bool free_ssl_ctx) if (tls_ctx_initialised(&ks->ssl_ctx) && free_ssl_ctx) { tls_ctx_free(&ks->ssl_ctx); + /* XXX TODO merge conflict here - is this still correct? */ + 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); } @@ -2618,6 +2623,24 @@ do_init_crypto_tls_c1(struct context *c) /* initialize tls-auth/crypt key */ do_init_tls_wrap_key(c); + /* 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; @@ -2843,13 +2866,28 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) } /* TLS handshake encryption (--tls-crypt) */ - if (options->ce.tls_crypt_file) + if (options->ce.tls_crypt_file + || (options->ce.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 @@ -4386,6 +4424,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 c5c767c..96cff87 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 acab042..8ce5cf4 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -2742,10 +2742,10 @@ options_postprocess_verify_ce(const struct options *options, const struct connec { msg(M_USAGE, "--tls-auth and --tls-crypt are mutually exclusive"); } - if (options->tls_client && ((ce->tls_auth_file && ce->tls_crypt_v2_file) - || (ce->tls_crypt_file && ce->tls_crypt_v2_file))) + if (options->client && options->ce.tls_crypt_v2_file + && (options->ce.tls_auth_file || options->ce.tls_crypt_file)) { - msg(M_USAGE, "--tls-auth, --tls-crypt and --tls-crypt-v2 are mutually exclusive in client mode"); + 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 757754f..dcae279 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1055,6 +1055,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 * @{ */ @@ -1147,16 +1164,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); } @@ -1482,6 +1492,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) { @@ -1499,17 +1511,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; } @@ -1525,6 +1546,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(); @@ -1564,6 +1595,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) { @@ -3864,6 +3907,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 e3c852a..d744881 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 6437eb1..87e4fd4 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" @@ -541,6 +542,62 @@ error_exit: return ret; } +bool +tls_crypt_v2_extract_client_key(struct buffer *buf, + struct tls_wrap_ctx *ctx) +{ + if (!ctx->tls_crypt_v2_server_key.cipher) + { + msg(D_TLS_ERRORS, + "Client wants 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; + uint16_t net_len = 0; + + if (BLEN(&wrapped_client_key) < sizeof(net_len)) + { + msg(D_TLS_ERRORS, "failed to read length"); + } + memcpy(&net_len, BEND(&wrapped_client_key) - sizeof(net_len), + sizeof(net_len)); + + size_t wkc_len = ntohs(net_len) + TLS_CRYPT_V2_TAG_SIZE + sizeof(uint16_t); + if (!buf_advance(&wrapped_client_key, BLEN(&wrapped_client_key) - wkc_len)) + { + 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 6513836..edee590 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 0f7f86b..6e7fa20 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -58,7 +58,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 \ diff --git a/tests/unit_tests/openvpn/test_tls_crypt.c b/tests/unit_tests/openvpn/test_tls_crypt.c index f53255c..22c7486 100644 --- a/tests/unit_tests/openvpn/test_tls_crypt.c +++ b/tests/unit_tests/openvpn/test_tls_crypt.c @@ -345,6 +345,12 @@ tls_crypt_v2_wrap_unwrap_max_metadata(void **state) { 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)); + + struct tls_wrap_ctx wrap_ctx = { + .mode = TLS_WRAP_CRYPT, + .tls_crypt_v2_server_key = ctx->server_keys.encrypt, + }; + assert_true(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx)); } /** From patchwork Wed Jul 25 06:08:53 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 426 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director10.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net (Dovecot) with LMTP id wqXcEe2gWFvbJQAAIUCqbw for ; Wed, 25 Jul 2018 12:10:21 -0400 Received: from proxy10.mail.iad3b.rsapps.net ([172.31.255.6]) by director10.mail.ord1d.rsapps.net (Dovecot) with LMTP id SaEYBO2gWFvUJgAApN4f7A ; Wed, 25 Jul 2018 12:10:21 -0400 Received: from smtp34.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy10.mail.iad3b.rsapps.net with LMTP id aJqiKO2gWFvcIQAA/F5p9A ; Wed, 25 Jul 2018 12:10:21 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp34.gate.iad3b.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Suspicious-Flag: YES X-Classification-ID: 3a342a36-9025-11e8-aad3-5254005e8ddb-1-1 Received: from [216.105.38.7] ([216.105.38.7:24635] helo=lists.sourceforge.net) by smtp34.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id F6/58-02781-CE0A85B5; Wed, 25 Jul 2018 12:10:20 -0400 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1fiMM7-00044y-QN; Wed, 25 Jul 2018 16:09:39 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1fiMM6-00044d-84 for openvpn-devel@lists.sourceforge.net; Wed, 25 Jul 2018 16:09:38 +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=Elehueja+8HqPZDSIyEuVu3+RwnCyISqVcs5u7oGHno=; b=YsZ3zy0VffKG+/BBghqhlIEy+z Viak87S1PIGNeWSX1Saa4Q25eECEdYTipoqv9FiacEpo23sml7vv83oLjXa7IOt3tiepQ0m9HWQrh Ny8C6xpUGnvbjh+iYihioB8yic+a7jDY/I2qGx2kcICWXcaAu3cOG4oMrpas2ILo0FqI=; 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=Elehueja+8HqPZDSIyEuVu3+RwnCyISqVcs5u7oGHno=; b=In1W8oKQ6sj7pxNO37jJbEUUaE vAGACuychQ7hUxacREuBt9ZDEns5ynxfTuMPz6hl8V3J4s3zsgJXyC2F2Y5/kUHv9aWjsuShG4EKu S/hMND5ktBNrK6tgx2z+LgXDUdKrnVu3iHvEL9GPEQ3Te4/Cspo5Y4lSmx9foK25Rxr4=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.90_1) id 1fiMM5-001ASe-Qy for openvpn-devel@lists.sourceforge.net; Wed, 25 Jul 2018 16:09:38 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id 8BB911AF729; Wed, 25 Jul 2018 18:09:23 +0200 (CEST) Received: from steffan-fox.fox.local (10.0.3.178) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Wed, 25 Jul 2018 18:09:23 +0200 From: Steffan Karger To: Date: Wed, 25 Jul 2018 18:08:53 +0200 Message-ID: <1532534933-3858-7-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1532534933-3858-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> <1532534933-3858-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. X-Headers-End: 1fiMM5-001ASe-Qy Subject: [Openvpn-devel] [PATCH v3 7/7] 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 --- v3: rebase on curent master / v3 patch set 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 | 18 +++++++- 11 files changed, 161 insertions(+), 15 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 597c0c4..cb61a53 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -5296,9 +5296,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 01667c3..a349d42 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -2887,6 +2887,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 8ce5cf4..bd324fd 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" @@ -8176,6 +8178,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 3d2c770..d5ceb18 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -590,6 +590,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 dcae279..4cf7619 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1541,14 +1541,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", @@ -3629,7 +3630,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; } @@ -3682,7 +3684,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; } @@ -3703,7 +3706,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; } @@ -3905,7 +3909,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 d744881..923cd95 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 87e4fd4..8c6d739 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -29,9 +29,11 @@ #include "syshead.h" +#include "argv.h" #include "base64.h" #include "crypto.h" #include "platform.h" +#include "run_command.h" #include "session_id.h" #include "ssl.h" @@ -542,9 +544,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) { if (!ctx->tls_crypt_v2_server_key.cipher) { @@ -595,6 +657,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 edee590..3dc2661 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 6e7fa20..e5ffba9 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) @@ -59,14 +62,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 22c7486..897adce 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; @@ -350,7 +366,7 @@ tls_crypt_v2_wrap_unwrap_max_metadata(void **state) { .mode = TLS_WRAP_CRYPT, .tls_crypt_v2_server_key = ctx->server_keys.encrypt, }; - assert_true(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx)); + assert_true(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx, NULL)); } /**