From patchwork Fri Dec 8 01:07:46 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steffan Karger X-Patchwork-Id: 149 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director6.mail.ord1d.rsapps.net ([172.27.255.52]) by backend31.mail.ord1d.rsapps.net (Dovecot) with LMTP id /WWCAQWBKlrLagAAgoeIoA for ; Fri, 08 Dec 2017 07:09:41 -0500 Received: from proxy18.mail.iad3a.rsapps.net ([172.27.255.52]) by director6.mail.ord1d.rsapps.net (Dovecot) with LMTP id K7y4JgWBKlr2JAAAhgvE6Q ; Fri, 08 Dec 2017 07:09:41 -0500 Received: from smtp27.gate.iad3a ([172.27.255.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy18.mail.iad3a.rsapps.net (Dovecot) with LMTP id s+hrBQWBKlqAQQAAon3hFg ; Fri, 08 Dec 2017 07:09:41 -0500 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.34.181.88] Authentication-Results: smtp27.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.34.181.88"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=fox-it.com X-Classification-ID: 944e437c-dc10-11e7-a07f-782bcb33f754-1-1 Received: from [216.34.181.88] ([216.34.181.88:2500] helo=lists.sourceforge.net) by smtp27.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.1.56364 r(Core:4.2.1.14)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 3A/79-23589-ED08A2A5; Fri, 08 Dec 2017 07:09:02 -0500 Received: from localhost ([127.0.0.1] helo=sfs-ml-2.v29.ch3.sourceforge.com) by sfs-ml-2.v29.ch3.sourceforge.com with esmtp (Exim 4.89) (envelope-from ) id 1eNHS5-0004wY-7s; Fri, 08 Dec 2017 12:08:25 +0000 Received: from sfi-mx-2.v28.ch3.sourceforge.com ([172.29.28.192] helo=mx.sourceforge.net) by sfs-ml-2.v29.ch3.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.89) (envelope-from ) id 1eNHS3-0004w8-FM for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:23 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Type:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:CC:To:From:Sender:Reply-To:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=OFRNtpLfDR2YGd7IVVJGTqzpsfqpA05QWLKsXfftVFE=; b=e0ebf2vBo/GT4veVT7GkCvHCmG YrstGt7CWsL3GnNQ/daNvd/TkCKe4Vkk24vxT7XafL31XuYyRvZaqTCd2FConhABXrk8j/GvDraFZ NeotJ3ULxsI5UDw3nHt233JGRSiPl3y7X1XeKk37W9A+57/J3+sOroEc+drdc3S5jqfo=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject: CC:To:From:Sender:Reply-To:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=OFRNtpLfDR2YGd7IVVJGTqzpsfqpA05QWLKsXfftVFE=; b=nEHCPWEUm2UgL74J9Ns0ASFh+n 3elrVQveMhaXHV/rgnTkVRLclz50ddg/iufVWfRdme6p+5Hc/VzpIn5idLYyzSM+iDOupl0+27/Tu s5VjR9rcYUIL86k2Mvo3vFuCWLz+e9PUQNVk5tTqxfEKlbQqEk+uwTCPIbvhRbjlui4s=; Received: from ns2.fox-it.com ([178.250.144.131]) by sfi-mx-2.v28.ch3.sourceforge.com with esmtps (TLSv1:ECDHE-RSA-AES256-SHA:256) (Exim 4.89) id 1eNHS1-0004Kz-3Q for openvpn-devel@lists.sourceforge.net; Fri, 08 Dec 2017 12:08:23 +0000 Received: from FOXDFT52.FOX.local (unknown [10.0.0.129]) by ns2.fox-it.com (Postfix) with ESMTPS id BFF1C1C5244 for ; Fri, 8 Dec 2017 13:08:07 +0100 (CET) Received: from steffan-fox.fox.local (172.16.5.166) by FOXDFT52.FOX.local (10.0.0.129) with Microsoft SMTP Server (TLS) id 15.0.1293.2; Fri, 8 Dec 2017 13:08:07 +0100 From: Steffan Karger To: Date: Fri, 8 Dec 2017 13:07:46 +0100 Message-ID: <1512734870-17133-7-git-send-email-steffan.karger@fox-it.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> References: <1512734870-17133-1-git-send-email-steffan.karger@fox-it.com> MIME-Version: 1.0 X-ClientProxiedBy: FOXDFT52.FOX.local (10.0.0.129) To FOXDFT52.FOX.local (10.0.0.129) X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 T_RP_MATCHES_RCVD Envelope sender domain matches handover relay domain -0.0 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1eNHS1-0004Kz-3Q Subject: [Openvpn-devel] [PATCH 06/10] tls-crypt-v2: implement tls-crypt-v2 handshake X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox This makes clients send-and-use, and servers receive-unwrap-and-use tls-crypt-v2 client keys, which completes the on-the-wire work. Signed-off-by: Steffan Karger --- src/openvpn/init.c | 39 +++++++++++++++++- src/openvpn/openvpn.h | 2 + src/openvpn/options.c | 5 +++ src/openvpn/ssl.c | 79 ++++++++++++++++++++++++++++-------- src/openvpn/ssl_common.h | 6 +++ src/openvpn/tls_crypt.c | 50 +++++++++++++++++++++++ src/openvpn/tls_crypt.h | 14 +++++++ tests/unit_tests/openvpn/Makefile.am | 2 +- 8 files changed, 179 insertions(+), 18 deletions(-) diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 3e694b9..a4603b3 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -2432,6 +2432,9 @@ key_schedule_free(struct key_schedule *ks, bool free_ssl_ctx) { tls_ctx_free(&ks->ssl_ctx); free_key_ctx_bi(&ks->tls_wrap_key); + free_key_ctx(&ks->tls_crypt_v2_server_key); + buf_clear(&ks->tls_crypt_v2_wkc); + free_buf(&ks->tls_crypt_v2_wkc); } CLEAR(*ks); } @@ -2600,6 +2603,24 @@ do_init_crypto_tls_c1(struct context *c) options->tls_crypt_inline, options->tls_server); } + /* tls-crypt with client-specific keys (--tls-crypt-v2) */ + if (options->tls_crypt_v2_file) + { + if (options->tls_server) + { + tls_crypt_v2_init_server_key(&c->c1.ks.tls_crypt_v2_server_key, + true, options->tls_crypt_v2_file, + options->tls_crypt_v2_inline); + } + else + { + tls_crypt_v2_init_client_key(&c->c1.ks.tls_wrap_key, + &c->c1.ks.tls_crypt_v2_wkc, + options->tls_crypt_v2_file, + options->tls_crypt_v2_inline); + } + } + c->c1.ciphername = options->ciphername; c->c1.authname = options->authname; c->c1.keysize = options->keysize; @@ -2819,13 +2840,28 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) } /* TLS handshake encryption (--tls-crypt) */ - if (options->tls_crypt_file) + if (options->tls_crypt_file + || (options->tls_crypt_v2_file && options->tls_client)) { to.tls_wrap.mode = TLS_WRAP_CRYPT; to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key; to.tls_wrap.opt.pid_persist = &c->c1.pid_persist; to.tls_wrap.opt.flags |= CO_PACKET_ID_LONG_FORM; tls_crypt_adjust_frame_parameters(&to.frame); + + if (options->tls_crypt_v2_file) + { + to.tls_wrap.tls_crypt_v2_wkc = &c->c1.ks.tls_crypt_v2_wkc; + } + } + + if (options->tls_crypt_v2_file) + { + to.tls_crypt_v2 = true; + if (options->tls_server) + { + to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key; + } } /* If we are running over TCP, allow for @@ -4349,6 +4385,7 @@ inherit_context_child(struct context *dest, dest->c1.ks.ssl_ctx = src->c1.ks.ssl_ctx; dest->c1.ks.tls_wrap_key = src->c1.ks.tls_wrap_key; dest->c1.ks.tls_auth_key_type = src->c1.ks.tls_auth_key_type; + dest->c1.ks.tls_crypt_v2_server_key = src->c1.ks.tls_crypt_v2_server_key; /* inherit pre-NCP ciphers */ dest->c1.ciphername = src->c1.ciphername; dest->c1.authname = src->c1.authname; diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h index db16842..38b4822 100644 --- a/src/openvpn/openvpn.h +++ b/src/openvpn/openvpn.h @@ -66,6 +66,8 @@ struct key_schedule /* optional TLS control channel wrapping */ struct key_type tls_auth_key_type; struct key_ctx_bi tls_wrap_key; + struct key_ctx tls_crypt_v2_server_key; + struct buffer tls_crypt_v2_wkc; /**< Wrapped client key */ }; /* diff --git a/src/openvpn/options.c b/src/openvpn/options.c index aa73fd9..89bff1e 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -2704,6 +2704,11 @@ options_postprocess_verify_ce(const struct options *options, const struct connec { msg(M_USAGE, "--tls-auth and --tls-crypt are mutually exclusive"); } + if (options->client && options->tls_crypt_v2_file + && (options->tls_auth_file || options->tls_crypt_file)) + { + msg(M_USAGE, "--tls-crypt-v2, --tls-auth and --tls-crypt are mutually exclusive in client mode"); + } } else { diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index d45cfe4..6090826 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1048,6 +1048,23 @@ tls_session_user_pass_enabled(struct tls_session *session) /** @addtogroup control_processor * @{ */ +/** Free the elements of a tls_wrap_ctx structure */ +static void tls_wrap_free(struct tls_wrap_ctx *tls_wrap) +{ + if (packet_id_initialized(&tls_wrap->opt.packet_id)) + { + packet_id_free(&tls_wrap->opt.packet_id); + } + + if (tls_wrap->cleanup_key_ctx) + { + free_key_ctx_bi(&tls_wrap->opt.key_ctx_bi); + } + + free_buf(&tls_wrap->tls_crypt_v2_metadata); + free_buf(&tls_wrap->work); +} + /** @name Functions for initialization and cleanup of tls_session structures * @{ */ @@ -1140,16 +1157,9 @@ tls_session_init(struct tls_multi *multi, struct tls_session *session) static void tls_session_free(struct tls_session *session, bool clear) { - int i; - - if (packet_id_initialized(&session->tls_wrap.opt.packet_id)) - { - packet_id_free(&session->tls_wrap.opt.packet_id); - } + tls_wrap_free(&session->tls_wrap); - free_buf(&session->tls_wrap.work); - - for (i = 0; i < KS_SIZE; ++i) + for (size_t i = 0; i < KS_SIZE; ++i) { key_state_free(&session->key[i], false); } @@ -1475,6 +1485,8 @@ write_control_auth(struct tls_session *session, ASSERT(reliable_ack_write (ks->rec_ack, buf, &ks->session_id_remote, max_ack, prepend_ack)); + msg(D_TLS_DEBUG, "%s(): %s", __func__, packet_opcode_name(opcode)); + if (session->tls_wrap.mode == TLS_WRAP_AUTH || session->tls_wrap.mode == TLS_WRAP_NONE) { @@ -1492,17 +1504,26 @@ write_control_auth(struct tls_session *session, ASSERT(buf_init(&session->tls_wrap.work, buf->offset)); ASSERT(buf_write(&session->tls_wrap.work, &header, sizeof(header))); ASSERT(session_id_write(&session->session_id, &session->tls_wrap.work)); - if (tls_crypt_wrap(buf, &session->tls_wrap.work, &session->tls_wrap.opt)) - { - /* Don't change the original data in buf, it's used by the reliability - * layer to resend on failure. */ - *buf = session->tls_wrap.work; - } - else + if (!tls_crypt_wrap(buf, &session->tls_wrap.work, &session->tls_wrap.opt)) { buf->len = 0; return; } + + if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3) + { + if (!buf_copy(&session->tls_wrap.work, + session->tls_wrap.tls_crypt_v2_wkc)) + { + msg(D_TLS_ERRORS, "Could not append tls-crypt-v2 client key"); + buf->len = 0; + return; + } + } + + /* Don't change the original data in buf, it's used by the reliability + * layer to resend on failure. */ + *buf = session->tls_wrap.work; } *to_link_addr = &ks->remote_addr; } @@ -1518,6 +1539,16 @@ read_control_auth(struct buffer *buf, struct gc_arena gc = gc_new(); bool ret = false; + const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT; + if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3 + && !tls_crypt_v2_extract_client_key(buf, ctx)) + { + msg (D_TLS_ERRORS, + "TLS Error: can not extract tls-crypt-v2 client key from %s", + print_link_socket_actual(from, &gc)); + goto cleanup; + } + if (ctx->mode == TLS_WRAP_AUTH) { struct buffer null = clear_buf(); @@ -1557,6 +1588,18 @@ read_control_auth(struct buffer *buf, ASSERT(buf_copy(buf, &tmp)); buf_clear(&tmp); } + else if (ctx->tls_crypt_v2_server_key.cipher) + { + /* If tls-crypt-v2 is enabled, require *some* wrapping */ + msg(D_TLS_ERRORS, "TLS Error: could not determine wrapping from %s", + print_link_socket_actual(from, &gc)); + /* TODO Do we want to support using tls-crypt-v2 and no control channel + * wrapping at all simultaneously? That would allow server admins to + * upgrade clients one-by-one without running a second instance, but we + * should not enable it by default because it breaks DoS-protection. + * So, add something like --tls-crypt-v2-allow-insecure-fallback ? */ + goto cleanup; + } if (ctx->mode == TLS_WRAP_NONE || ctx->mode == TLS_WRAP_AUTH) { @@ -3854,6 +3897,10 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas, /* HMAC test, if --tls-auth was specified */ status = read_control_auth(&newbuf, &tls_wrap_tmp, from); free_buf(&newbuf); + if (tls_wrap_tmp.cleanup_key_ctx) + { + free_key_ctx_bi(&tls_wrap_tmp.opt.key_ctx_bi); + } if (!status) { goto error; diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 7e8f7d9..d6e0d1b 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -213,6 +213,12 @@ struct tls_wrap_ctx } mode; /**< Control channel wrapping mode */ struct crypto_options opt; /**< Crypto state */ struct buffer work; /**< Work buffer (only for --tls-crypt) */ + struct key_ctx tls_crypt_v2_server_key; /**< Decrypts client keys */ + const struct buffer *tls_crypt_v2_wkc; /**< Wrapped client key, + sent to server */ + struct buffer tls_crypt_v2_metadata; /**< Received from client */ + bool cleanup_key_ctx; /**< opt.key_ctx_bi is owned by + this context */ }; /* diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index 82d7037..0c94d73 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -33,6 +33,7 @@ #include "crypto.h" #include "platform.h" #include "session_id.h" +#include "ssl.h" #include "tls_crypt.h" @@ -519,6 +520,55 @@ error_exit: return ret; } +bool +tls_crypt_v2_extract_client_key(struct buffer *buf, + struct tls_wrap_ctx *ctx) +{ + static const int hard_reset_length = + TLS_CRYPT_OFF_CT + sizeof(uint8_t) + sizeof(packet_id_type); + + if (!ctx->tls_crypt_v2_server_key.cipher) + { + msg (D_TLS_ERRORS, + "Client want tls-crypt-v2, but no server key present."); + return false; + } + + msg (D_HANDSHAKE, "Control Channel: using tls-crypt-v2 key"); + + struct buffer wrapped_client_key = *buf; + if (!buf_advance(&wrapped_client_key, hard_reset_length)) + { + msg (D_TLS_ERRORS, "Can not locate tls-crypt-v2 client key"); + return false; + } + + struct key2 client_key = { 0 }; + ctx->tls_crypt_v2_metadata = alloc_buf(TLS_CRYPT_V2_MAX_METADATA_LEN); + if (!tls_crypt_v2_unwrap_client_key(&client_key, + &ctx->tls_crypt_v2_metadata, + wrapped_client_key, + &ctx->tls_crypt_v2_server_key)) + { + msg (D_TLS_ERRORS, "Can not unwrap tls-crypt-v2 client key"); + secure_memzero(&client_key, sizeof(client_key)); + return false; + } + + /* Load the decrypted key */ + ctx->mode = TLS_WRAP_CRYPT; + ctx->cleanup_key_ctx = true; + ctx->opt.flags |= CO_PACKET_ID_LONG_FORM; + memset(&ctx->opt.key_ctx_bi, 0, sizeof(ctx->opt.key_ctx_bi)); + tls_crypt_v2_load_client_key(&ctx->opt.key_ctx_bi, &client_key, true); + secure_memzero(&client_key, sizeof(client_key)); + + /* Remove client key from buffer so tls-crypt code can unwrap message */ + ASSERT(buf_inc_len(buf, -(BLEN(&wrapped_client_key)))); + + return true; +} + void tls_crypt_v2_write_server_key_file(const char *filename) { diff --git a/src/openvpn/tls_crypt.h b/src/openvpn/tls_crypt.h index f4d3ca7..e720729 100644 --- a/src/openvpn/tls_crypt.h +++ b/src/openvpn/tls_crypt.h @@ -83,6 +83,7 @@ #include "buffer.h" #include "crypto.h" #include "session_id.h" +#include "ssl_common.h" #define TLS_CRYPT_TAG_SIZE (256/8) #define TLS_CRYPT_PID_SIZE (sizeof(packet_id_type) + sizeof(net_time_t)) @@ -182,6 +183,19 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key, const char *key_inline); /** + * Extract a tls-crypt-v2 client key from a P_CONTROL_HARD_RESET_CLIENT_V3 + * message, and load the key into the supplied tls wrap context. + * + * @param buf Buffer containing a received P_CONTROL_HARD_RESET_CLIENT_V3 + * message. + * @param ctx tls-wrap context to be initialized with the client key. + * + * @returns true if a key was successfully extracted. + */ +bool tls_crypt_v2_extract_client_key(struct buffer *buf, + struct tls_wrap_ctx *ctx); + +/** * Generate a tls-crypt-v2 server key, and write to file. * * @param filename Filename of the server key file to create. diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index 937393d..c457ef9 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -56,7 +56,7 @@ packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \ tls_crypt_testdriver_CFLAGS = @TEST_CFLAGS@ \ -I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \ - $(OPTIONAL_CRYPTO_CFLAGS) + $(OPTIONAL_CRYPTO_CFLAGS) $(OPTIONAL_PKCS11_HELPER_CFLAGS) tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ $(OPTIONAL_CRYPTO_LIBS) tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \