From patchwork Fri Apr 22 04:29:47 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 2397 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director10.mail.ord1d.rsapps.net ([172.30.191.6]) by backend41.mail.ord1d.rsapps.net with LMTP id yOZjKRy+YmLxUQAAqwncew (envelope-from ) for ; Fri, 22 Apr 2022 10:39:24 -0400 Received: from proxy18.mail.ord1d.rsapps.net ([172.30.191.6]) by director10.mail.ord1d.rsapps.net with LMTP id 2H67Bh2+YmIYeQAApN4f7A (envelope-from ) for ; Fri, 22 Apr 2022 10:39:25 -0400 Received: from smtp15.gate.ord1d ([172.30.191.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy18.mail.ord1d.rsapps.net with LMTPS id gAF4Bh2+YmKAUAAATCaURg (envelope-from ) for ; Fri, 22 Apr 2022 10:39: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: smtp15.gate.ord1d.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: 00eaa97a-c24a-11ec-9d9d-5254007ab6c8-1-1 Received: from [216.105.38.7] ([216.105.38.7:39196] helo=lists.sourceforge.net) by smtp15.gate.ord1d.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id CC/AB-16970-C1EB2626; Fri, 22 Apr 2022 10:39: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.94.2) (envelope-from ) id 1nhuQN-00015q-94; Fri, 22 Apr 2022 14:38:17 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1nhuQL-00015k-Dl for openvpn-devel@lists.sourceforge.net; Fri, 22 Apr 2022 14:38:16 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: 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=o4lpi3V54M9ZJdG4+TlrfrR30T8EorBF+yLl3QPWfOI=; b=f5masiz2VhZg3u8jk52BakRW3P oliT8O5GwlXusCu20M/LEFdat+GJumvmlEnz/xinaXm4k2DWE31zL05ShOU1vLtiWOZIe0E6hP02a q+RizR0bN7VuEcF/6MBVD7AURHdpnQWauOKj9xVqOoK7IumGbM10tjn9Y8Tk75N0/dcU=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-Id: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: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=o4lpi3V54M9ZJdG4+TlrfrR30T8EorBF+yLl3QPWfOI=; b=d3BhblSrQdwdPSGYlpISOHp2ZD edXjOmIbondi6jF2k1d3ZWJlp2F9o86Z4jTQY1EpKJib/svVL1GhD04DOgmU9pnlMlI6xnPFgzoRW AztR7WB0FTgOwxecztwdHv8LbCc+9mk0aN/6K222bHK80nWnrj4S464SCQ7zUibxSsGQ=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.94.2) id 1nhuQH-00062l-Gi for openvpn-devel@lists.sourceforge.net; Fri, 22 Apr 2022 14:38:15 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.95 (FreeBSD)) (envelope-from ) id 1nhuIE-00096u-6h for openvpn-devel@lists.sourceforge.net; Fri, 22 Apr 2022 16:29:54 +0200 Received: (nullmailer pid 3805447 invoked by uid 10006); Fri, 22 Apr 2022 14:29:54 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Fri, 22 Apr 2022 16:29:47 +0200 Message-Id: <20220422142953.3805364-13-arne@rfc2549.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220422142953.3805364-1-arne@rfc2549.org> References: <20220422134038.3801239-1-arne@rfc2549.org> <20220422142953.3805364-1-arne@rfc2549.org> MIME-Version: 1.0 X-Spam-Report: Spam detection software, running on the system "util-spamd-2.v13.lw.sourceforge.com", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Tls-crypt v2 is more complicated to implement a proper stateless handshake. To allow state handshake this commit does - introduce a new packet CONTROL_WKC_V1 that repeats the wrapped client key. - introduce a way to negotiate the support for this packet in the three way handshake Content analysis details: (0.2 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record 0.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.0 SPF_NONE SPF: sender does not publish an SPF Record X-Headers-End: 1nhuQH-00062l-Gi Subject: [Openvpn-devel] [PATCH 22/28] Implement HMAC based session id for tls-crypt v2 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 Tls-crypt v2 is more complicated to implement a proper stateless handshake. To allow state handshake this commit does - introduce a new packet CONTROL_WKC_V1 that repeats the wrapped client key. - introduce a way to negotiate the support for this packet in the three way handshake Details about the protocol changes are in tls-crypt-v2.txt. Optional arguments to the tls-crypt-v2 option have been added to explicitly allow or disallow client that do not support the stateless handshake. --- Changes.rst | 8 ++ doc/man-sections/tls-options.rst | 14 ++++ doc/tls-crypt-v2.txt | 41 +++++++++ src/openvpn/crypto.h | 8 ++ src/openvpn/init.c | 4 + src/openvpn/mudp.c | 89 +++++++++++++++----- src/openvpn/options.c | 13 +++ src/openvpn/options.h | 3 + src/openvpn/reliable.h | 2 - src/openvpn/ssl.c | 125 +++++++++++++++++++++++++--- src/openvpn/ssl_pkt.c | 28 +++++-- src/openvpn/ssl_pkt.h | 36 +++++++- tests/unit_tests/openvpn/test_pkt.c | 8 +- 13 files changed, 333 insertions(+), 46 deletions(-) diff --git a/Changes.rst b/Changes.rst index ceb0b2680..a7e8b4b81 100644 --- a/Changes.rst +++ b/Changes.rst @@ -69,6 +69,14 @@ Improved ``--mssfix`` and ``--fragment`` calculation account and the resulting size is specified as the total size of the VPN packets including IP and UDP headers. +Cookie based handshake for UDP server + Instead of allocating a connection for each client on the initial packet + OpenVPN will now send back a response that contains an HMAC based cookie + that the client will need to respond to. This eliminates the amplification + attack and resource exhaustion attacks. For tls-crypt-v2 clients, this + requires OpenVPN 2.6 clients or later and the tls-crypt-v2 option allows + controlling if older clients are accepted. + Deprecated features ------------------- ``inetd`` has been removed diff --git a/doc/man-sections/tls-options.rst b/doc/man-sections/tls-options.rst index ac5756034..5ad58140d 100644 --- a/doc/man-sections/tls-options.rst +++ b/doc/man-sections/tls-options.rst @@ -486,6 +486,13 @@ certificates and keys: https://github.com/OpenVPN/easy-rsa 8000 years'. --tls-crypt-v2 keyfile + + Valid syntax: + :: + + tls-crypt-v2 keyfile [force-cookie] + tls-crypt-v2 keyfile [allow-noncookie] + Use client-specific tls-crypt keys. For clients, ``keyfile`` is a client-specific tls-crypt key. Such a key @@ -501,6 +508,13 @@ certificates and keys: https://github.com/OpenVPN/easy-rsa client is using client-specific keys, and automatically select the right mode. + The optional parameters :code:`force-cookie` allows only tls-crypt-v2 + clients that support a cookie based stateless three way handshake that + avoids replay attacks and state exhaustion on the server side (OpenVPN + 2.6 and later). The option :code:`allow-noncookie` explicilty allows + older tls-crypt-v2 clients. The default is (currently) + :code:`allow-noncookie`. + --tls-crypt-v2-verify cmd Run command ``cmd`` to verify the metadata of the client-specific tls-crypt-v2 key of a connecting client. This allows server diff --git a/doc/tls-crypt-v2.txt b/doc/tls-crypt-v2.txt index f6a6a1395..224b24ab4 100644 --- a/doc/tls-crypt-v2.txt +++ b/doc/tls-crypt-v2.txt @@ -157,6 +157,47 @@ When setting up the openvpn connection: messages. +HMAC Cookie support +------------------- +To avoid exhaustion attack and keeping state for connections that fail to +complete thethree way handshake, the OpenVPN server will use its own session +id as challenge that the client must repeat in the third packet of the +handshake. This introduces a problem. If the server does not keep the wrapped +client key from the initial packet, the server cannot decode the third packet. +Therefore, tls-crypt-v2 in 2.6 allows resending the wrapped key in the third +packet of the handshake with the P_CONTROL_WKC_V1 message. The modified +handshake is as follows (the rest of the handshake is unmodified): + +1. The client creates the P_CONTROL_HARD_RESET_CLIENT_V3 message as before + but to indicate that it supports resending the wrapped key by setting the + packet id of the replay id to 0x0f010000 where the first byte indicates the + early negotiation support and the next bytes the flags. + +2. The server responds with a P_CONTROL_HARD_RESET_V2 message. Instead of having + an empty payload like normally, the payload consists of TLV (type (uint16), + length (uint16), value) packets. TLV was chosen + to allow extensibility in the future. Currently only the following TLV is + defined: + + flags - type 0x01, length 2. + + Bit 1 indicates that the client needs to resend the WKC in the third packet. + +3. Instead of normal P_ACK_V1 or P_CONTROL_V1 packet, the client will send a + P_CONTROL_WKC_V1 packet. The P_CONTROL_WKC_V1 is identical to a normal + P_CONTROL_V1 packet but with the WKc appended. + + Normally the first message of the client is either P_ACK_V1, directly + followed by a P_CONTROL_V1 message that contains the TLS Client Hello or + just a P_CONTROL_V1 message. Instead of a P_ACK_V1 message the client should + send a P_CONTROL_WKC_V1 message with an empty payload. This message must + also include an ACK for the P_CONTROL_HARD_RESET_V2 message. + + When directly sending the TLS Client Hello message in the P_CONTROL_WKC_V1 + message, the client must ensure that the resulting P_CONTROL_WKC_V1 message + with the appended Wkc does not extend the control message length. + + Considerations -------------- diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h index 806632edf..98e2c7664 100644 --- a/src/openvpn/crypto.h +++ b/src/openvpn/crypto.h @@ -256,6 +256,14 @@ struct crypto_options /**< Bit-flag indicating that data channel key derivation * is done using TLS keying material export [RFC5705] */ +#define CO_RESEND_WKC (1<<4) + /**< Bit-flag indicating that the client is expected to + * resend the wrapped client key with the 2nd packet (packet-id 1) + * like with the HARD_RESET_CLIENT_V3 packet */ +#define CO_FORCE_TLSCRYPTV2_COOKIE (1<<5) + /**< Bit-flag indicating that we do not allow clients that do + * not support resending the wrapped client key (WKc) with the + * third packet of the three-way handshake */ unsigned int flags; /**< Bit-flags determining behavior of * security operation functions. */ }; diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 97a5fd01b..a88f7d80d 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -2961,6 +2961,10 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) { 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; + if (options->ce.tls_crypt_v2_force_cookie) + { + to.tls_wrap.opt.flags |= CO_FORCE_TLSCRYPTV2_COOKIE; + } } } diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c index 75619bd54..9e8643d35 100644 --- a/src/openvpn/mudp.c +++ b/src/openvpn/mudp.c @@ -40,6 +40,30 @@ #include #endif +static void +send_hmac_reset_packet(struct multi_context *m, + struct tls_pre_decrypt_state *state, + struct tls_auth_standalone *tas, + struct session_id *sid, + bool tlscryptv2) +{ + reset_packet_id_send(&state->tls_wrap_tmp.opt.packet_id.send); + state->tls_wrap_tmp.opt.packet_id.rec.initialized = true; + uint8_t header = 0 | (P_CONTROL_HARD_RESET_SERVER_V2 << P_OPCODE_SHIFT); + struct buffer buf = tls_reset_standalone(&state->tls_wrap_tmp, tas, sid, + &state->peer_session_id, header, + tlscryptv2); + + struct context *c = &m->top; + + buf_reset_len(&c->c2.buffers->aux_buf); + buf_copy(&c->c2.buffers->aux_buf, &buf); + m->hmac_reply = c->c2.buffers->aux_buf; + m->hmac_reply_dest = &m->top.c2.from; + msg(D_MULTI_DEBUG, "Reset packet from client, sending HMAC based reset challenge"); +} + + /* Return if this packet should create a new session */ static bool do_pre_decrypt_check(struct multi_context *m, @@ -62,37 +86,62 @@ do_pre_decrypt_check(struct multi_context *m, struct openvpn_sockaddr *from = &m->top.c2.from.dest; int handwindow = m->top.options.handshake_window; - if (verdict == VERDICT_VALID_RESET_V3) { - /* For tls-crypt-v2 we need to keep the state of the first packet to - * store the unwrapped key */ - return true; + /* Extract the packet id to check if it has the special format that + * indicates early negotiation support */ + struct packet_id_net pin; + struct buffer tmp = m->top.c2.buf; + ASSERT(buf_advance(&tmp, 1 + SID_SIZE)); + ASSERT(packet_id_read(&pin, &tmp, true)); + + /* The most significant byte ist 0x0f if early negotiation is supported */ + bool early_neg_support = (pin.id & EARLY_NEG_MASK) == EARLY_NEG_START; + + if (early_neg_support && (pin.id & EARLY_NEG_RESENDWKC)) + { + /* Calculate the session ID HMAC for our reply and create reset packet */ + struct session_id sid = calculate_session_id_hmac(state->peer_session_id, + from, hmac, handwindow, 0); + send_hmac_reset_packet(m, state, tas, &sid, true); + + return false; + } + else + { + /* For tls-crypt-v2 we need to keep the state of the first packet + * to store the unwrapped key if the client doesn't support resending + * the wrapped key */ + if (tas->tls_wrap.opt.flags & CO_FORCE_TLSCRYPTV2_COOKIE) + { + struct gc_arena gc = gc_new(); + const char *peer = print_link_socket_actual(&m->top.c2.from, &gc); + msg(D_MULTI_DEBUG, "tls-crypt-v2 force-cookie is enabled," + "ignoring connection attempt from old client" + " (%s)", peer); + gc_free(&gc); + return false; + } + else + { + return true; + } + } } else if (verdict == VERDICT_VALID_RESET_V2) { /* Calculate the session ID HMAC for our reply and create reset packet */ struct session_id sid = calculate_session_id_hmac(state->peer_session_id, from, hmac, handwindow, 0); - reset_packet_id_send(&tas->tls_wrap.opt.packet_id.send); - tas->tls_wrap.opt.packet_id.rec.initialized = true; - uint8_t header = 0 | (P_CONTROL_HARD_RESET_SERVER_V2 << P_OPCODE_SHIFT); - struct buffer buf = tls_reset_standalone(tas, &sid, - &state->peer_session_id, header); - - struct context *c = &m->top; + send_hmac_reset_packet(m, state, tas, &sid, false); - buf_reset_len(&c->c2.buffers->aux_buf); - buf_copy(&c->c2.buffers->aux_buf, &buf); - m->hmac_reply = c->c2.buffers->aux_buf; - m->hmac_reply_dest = &m->top.c2.from; - msg(D_MULTI_DEBUG, "Reset packet from client, sending HMAC based reset challenge"); /* We have a reply do not create a new session */ return false; } - else if (verdict == VERDICT_VALID_CONTROL_V1 || verdict == VERDICT_VALID_ACK_V1) + else if (verdict == VERDICT_VALID_CONTROL_V1 || verdict == VERDICT_VALID_ACK_V1 + || verdict == VERDICT_VALID_WKC_V1) { /* ACK_V1 contains the peer id (our id) while CONTROL_V1 can but does not * need to contain the peer id */ @@ -103,14 +152,12 @@ do_pre_decrypt_check(struct multi_context *m, const char *peer = print_link_socket_actual(&m->top.c2.from, &gc); if (!ret) { - msg(D_MULTI_MEDIUM, "Packet with invalid or missing SID from %s", peer); - } else { - msg(D_MULTI_DEBUG, "Reset packet from client (%s), " - "sending HMAC based reset challenge", peer); + msg(D_MULTI_DEBUG, "Valid packet with HMAC challenge from peer (%s), " + "accepting new connection.", peer); } gc_free(&gc); diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 7f5c903d1..9ff384d09 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -8896,6 +8896,19 @@ add_option(struct options *options, options->ce.tls_crypt_v2_file = p[1]; options->ce.tls_crypt_v2_file_inline = is_inline; } + + if (p[2] && streq(p[2], "force-cookie")) + { + options->ce.tls_crypt_v2_force_cookie = true; + } + else if (p[2] && streq(p[2], "allow-noncookie")) + { + options->ce.tls_crypt_v2_force_cookie = false; + } + else if (p[2]) + { + msg(msglevel, "Unsupported tls-crypt-v2 argument: %s", p[2]); + } } else if (streq(p[0], "tls-crypt-v2-verify") && p[1] && !p[2]) { diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 055789b3b..c2937dc37 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -162,6 +162,9 @@ struct connection_entry * authenticated encryption v2 */ const char *tls_crypt_v2_file; bool tls_crypt_v2_file_inline; + + /* Allow only client that support resending the wrapped client key */ + bool tls_crypt_v2_force_cookie; }; struct remote_entry diff --git a/src/openvpn/reliable.h b/src/openvpn/reliable.h index 0bc8ab913..8152e788c 100644 --- a/src/openvpn/reliable.h +++ b/src/openvpn/reliable.h @@ -361,8 +361,6 @@ struct reliable_entry *reliable_get_entry_sequenced(struct reliable *rel); * * @param rel The reliable structure associated with the given buffer. * @param buf The buffer of the reliable entry which is to be removed. - * @param inc_pid If true, the reliable structure's packet ID counter - * will be incremented. */ void reliable_mark_deleted(struct reliable *rel, struct buffer *buf); diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 8ea7c06fa..5e1a23ccd 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1099,6 +1099,14 @@ tls_session_init(struct tls_multi *multi, struct tls_session *session) session->opt->replay_time, "TLS_WRAP", session->key_id); + /* If we are using tls-crypt-v2 we manipulate the packet id to be (ab)used + * to indicate early protocol negotiation */ + if (session->opt->tls_crypt_v2) + { + session->tls_wrap.opt.packet_id.send.time = now; + session->tls_wrap.opt.packet_id.send.id = EARLY_NEG_START | EARLY_NEG_RESENDWKC; + } + /* load most recent packet-id to replay protect on --tls-auth */ packet_id_persist_load_obj(session->tls_wrap.opt.pid_persist, &session->tls_wrap.opt.packet_id); @@ -2525,6 +2533,53 @@ session_skip_to_pre_start(struct tls_session *session, return session_move_pre_start(session, ks, true); } +/** + * Parses the TLVs (type, length, value) in the early negotiation + */ +static bool +parse_early_negotiation_tlvs(struct buffer *buf, struct key_state *ks) +{ + while (buf->len > 0) + { + if (buf_len(buf) < 4) + { + goto error; + } + /* read type */ + uint16_t type = buf_read_u16(buf); + uint16_t len = buf_read_u16(buf); + if (buf_len(buf) < len) + { + goto error; + } + + if (type == EARLY_NEG_TLV_FLAG) + { + if (len != sizeof(uint16_t)) + { + goto error; + } + uint16_t flags = buf_read_u16(buf); + + if (flags & EARLY_NEG_FLAG_RESEND_WKC) + { + ks->crypto_options.flags |= CO_RESEND_WKC; + } + } + else + { + /* Skip types we do not parse */ + buf_advance(buf, len); + } + } + reliable_mark_deleted(ks->rec_reliable, buf); + + return true; +error: + msg(D_TLS_ERRORS, "TLS Error: Early negotiation malformed packet"); + return false; +} + /** * Read incoming ciphertext and passes it to the buffer of the SSL library. * Returns false if an error is encountered that should abort the session. @@ -2557,6 +2612,13 @@ read_incoming_tls_ciphertext(struct buffer *buf, struct key_state *ks, return true; } +static bool +control_packet_needs_wkc(const struct key_state *ks) +{ + return (ks->crypto_options.flags & CO_RESEND_WKC) + && (ks->send_reliable->packet_id == 1); +} + static bool tls_process_state(struct tls_multi *multi, @@ -2626,9 +2688,21 @@ tls_process_state(struct tls_multi *multi, struct reliable_entry *entry = reliable_get_entry_sequenced(ks->rec_reliable); if (entry) { - if (!read_incoming_tls_ciphertext(&entry->buf, ks, &state_change)) + /* The first packet from the peer (the reset packet) is special and + * contains early protocol negotiation */ + if (entry->packet_id == 0 && is_hard_reset_method2(entry->opcode)) { - goto error; + if (!parse_early_negotiation_tlvs(&entry->buf, ks)) + { + goto error; + } + } + else + { + if (!read_incoming_tls_ciphertext(&entry->buf, ks, &state_change)) + { + goto error; + } } } @@ -2721,7 +2795,12 @@ tls_process_state(struct tls_multi *multi, } if (status == 1) { - reliable_mark_active_outgoing(ks->send_reliable, buf, P_CONTROL_V1); + int opcode = P_CONTROL_V1; + if (control_packet_needs_wkc(ks)) + { + opcode = P_CONTROL_WKC_V1; + } + reliable_mark_active_outgoing(ks->send_reliable, buf, opcode); INCR_GENERATED; state_change = true; dmsg(D_TLS_DEBUG, "Outgoing Ciphertext -> Reliable"); @@ -2811,15 +2890,38 @@ tls_process(struct tls_multi *multi, update_time(); + /* We often send acks back to back to a following control packet. This + * normally does not create a problem (apart from an extra packet. However, + * with the P_CONTROL_WKC_V1 we need to ensure that the packet gets resend + * if not received by remote, so instead we use an empty control packet in + * this special case */ + + /* Send 1 or more ACKs (each received control packet gets one ACK) */ if (!to_link->len && !reliable_ack_empty(ks->rec_ack)) { - struct buffer buf = ks->ack_write_buf; - ASSERT(buf_init(&buf, multi->opt.frame.buf.headroom)); - write_control_auth(session, ks, &buf, to_link_addr, P_ACK_V1, - RELIABLE_ACK_SIZE, false); - *to_link = buf; - dmsg(D_TLS_DEBUG, "Dedicated ACK -> TCP/UDP"); + if (control_packet_needs_wkc(ks)) + { + struct buffer *buf = reliable_get_buf_output_sequenced(ks->send_reliable); + if (!buf) + { + return false; + } + + /* We do not write anything to the buffer, this way this will be + * an empty control packet that gets the ack piggybacked and + * also appended the wrapped client key since it has a WCK opcode */ + reliable_mark_active_outgoing(ks->send_reliable, buf, P_CONTROL_WKC_V1); + } + else + { + struct buffer buf = ks->ack_write_buf; + ASSERT(buf_init(&buf, multi->opt.frame.buf.headroom)); + write_control_auth(session, ks, &buf, to_link_addr, P_ACK_V1, + RELIABLE_ACK_SIZE, false); + *to_link = buf; + dmsg(D_TLS_DEBUG, "Dedicated ACK -> TCP/UDP"); + } } /* When should we wake up again? */ @@ -3464,7 +3566,8 @@ tls_pre_decrypt(struct tls_multi *multi, } /* - * We have an authenticated control channel packet (if --tls-auth was set). + * We have an authenticated control channel packet (if --tls-auth/tls-crypt + * or tls-crypt-v2 was set). * Now pass to our reliability layer which deals with * packet acknowledgements, retransmits, sequencing, etc. */ @@ -3893,7 +3996,7 @@ protocol_dump(struct buffer *buffer, unsigned int flags, struct gc_arena *gc) if (op == P_ACK_V1) { - goto done; + goto print_data; } /* diff --git a/src/openvpn/ssl_pkt.c b/src/openvpn/ssl_pkt.c index 56baa2895..96a040347 100644 --- a/src/openvpn/ssl_pkt.c +++ b/src/openvpn/ssl_pkt.c @@ -148,7 +148,8 @@ tls_wrap_control(struct tls_wrap_ctx *ctx, uint8_t header, struct buffer *buf, return; } - if ((header >> P_OPCODE_SHIFT) == P_CONTROL_HARD_RESET_CLIENT_V3) + if ((header >> P_OPCODE_SHIFT) == P_CONTROL_HARD_RESET_CLIENT_V3 + || (header >> P_OPCODE_SHIFT) == P_CONTROL_WKC_V1) { if (!buf_copy(&ctx->work, ctx->tls_crypt_v2_wkc)) @@ -197,7 +198,8 @@ read_control_auth(struct buffer *buf, bool ret = false; const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT; - if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3 + if ((opcode == P_CONTROL_HARD_RESET_CLIENT_V3 + || opcode == P_CONTROL_WKC_V1) && !tls_crypt_v2_extract_client_key(buf, ctx, opt)) { msg(D_TLS_ERRORS, @@ -321,6 +323,7 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas, if (op != P_CONTROL_HARD_RESET_CLIENT_V2 && op != P_CONTROL_HARD_RESET_CLIENT_V3 && op != P_CONTROL_V1 + && op != P_CONTROL_WKC_V1 && op != P_ACK_V1) { /* @@ -397,6 +400,10 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas, { return VERDICT_VALID_RESET_V3; } + else if (op == P_CONTROL_WKC_V1) + { + return VERDICT_VALID_WKC_V1; + } else { return VERDICT_VALID_RESET_V2; @@ -410,10 +417,12 @@ error: struct buffer -tls_reset_standalone(struct tls_auth_standalone *tas, +tls_reset_standalone(struct tls_wrap_ctx *ctx, + struct tls_auth_standalone *tas, struct session_id *own_sid, struct session_id *remote_sid, - uint8_t header) + uint8_t header, + bool tlscryptv2) { struct buffer buf = alloc_buf(tas->frame.buf.payload_size); ASSERT(buf_init(&buf, tas->frame.buf.headroom)); @@ -434,8 +443,17 @@ tls_reset_standalone(struct tls_auth_standalone *tas, ASSERT(buf_write(&buf, &net_pid, sizeof(net_pid))); + /* Add indication for tls-crypt-v2 to resend the packet with the with + * reply */ + if (tlscryptv2) + { + buf_write_u16(&buf, EARLY_NEG_TLV_FLAG); /* TYPE: flags */ + buf_write_u16(&buf, sizeof(uint16_t)); + buf_write_u16(&buf, EARLY_NEG_FLAG_RESEND_WKC); + } + /* Add tls-auth/tls-crypt wrapping, this might replace buf */ - tls_wrap_control(&tas->tls_wrap, header, &buf, own_sid); + tls_wrap_control(ctx, header, &buf, own_sid); return buf; } diff --git a/src/openvpn/ssl_pkt.h b/src/openvpn/ssl_pkt.h index 75cdc1c58..48b94e952 100644 --- a/src/openvpn/ssl_pkt.h +++ b/src/openvpn/ssl_pkt.h @@ -54,11 +54,15 @@ /* 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 */ +/* Variant of P_CONTROL_V1 but with appended wrapped key + * like P_CONTROL_HARD_RESET_CLIENT_V3 */ +#define P_CONTROL_WKC_V1 11 + /* define the range of legal opcodes * Since we do no longer support key-method 1 we consider * the v1 op codes invalid */ #define P_FIRST_OPCODE 3 -#define P_LAST_OPCODE 10 +#define P_LAST_OPCODE 11 /* * Define number of buffers for send and receive in the reliability layer. @@ -86,6 +90,8 @@ enum first_packet_verdict { /** This packet is a valid ACK control packet from the peer, * i.e. it has a valid session id hmac in it */ VERDICT_VALID_ACK_V1, + /** The packet is a valid control packet with appended wrapped client key */ + VERDICT_VALID_WKC_V1, /** the packet failed on of the various checks */ VERDICT_INVALID }; @@ -218,10 +224,12 @@ read_control_auth(struct buffer *buf, * The returned buf need to be free with \c free_buf */ struct buffer -tls_reset_standalone(struct tls_auth_standalone *tas, +tls_reset_standalone(struct tls_wrap_ctx *ctx, + struct tls_auth_standalone *tas, struct session_id *own_sid, struct session_id *remote_sid, - uint8_t header); + uint8_t header, + bool b); static inline const char * packet_opcode_name(int op) @@ -249,6 +257,9 @@ packet_opcode_name(int op) case P_CONTROL_V1: return "P_CONTROL_V1"; + case P_CONTROL_WKC_V1: + return "P_CONTROL_WKC_V1"; + case P_ACK_V1: return "P_ACK_V1"; @@ -262,4 +273,23 @@ packet_opcode_name(int op) return "P_???"; } } + +/* initial packet id (instead of 0) that indicates that the peer supports + * early protocol negotiation. This will make the packet id turn a bit faster + * but the network time part of the packet id could take care of that. And + * this is also a rather theoretical scenario as it still needs more than + * 2^31 control channel packets to happen */ +#define EARLY_NEG_MASK 0xff000000 +#define EARLY_NEG_START 0x0a000000 + +#define EARLY_NEG_RESENDWKC 0x00010000 + + +/* Early negotiation that part of the server response in the RESET_V2 packet. + * Since clients that announce early negotiation support will treat the payload + * of reset packets special and parse it as TLV messages. + * as TLV (type, length, value) */ +#define EARLY_NEG_TLV_FLAG 0x01 +#define EARLY_NEG_FLAG_RESEND_WKC 0x01 #endif /* ifndef SSL_PKT_H */ + diff --git a/tests/unit_tests/openvpn/test_pkt.c b/tests/unit_tests/openvpn/test_pkt.c index c4e23521d..184b88383 100644 --- a/tests/unit_tests/openvpn/test_pkt.c +++ b/tests/unit_tests/openvpn/test_pkt.c @@ -531,7 +531,7 @@ test_generate_reset_packet_plain(void **ut_state) uint8_t header = 0 | (P_CONTROL_HARD_RESET_CLIENT_V2 << P_OPCODE_SHIFT); - struct buffer buf = tls_reset_standalone(&tas, &client_id, &server_id, header); + struct buffer buf = tls_reset_standalone(&tas, &client_id, &server_id, header, 0, 0); verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf); @@ -539,7 +539,7 @@ test_generate_reset_packet_plain(void **ut_state) /* Assure repeated generation of reset is deterministic/stateless*/ assert_memory_equal(state.peer_session_id.id, client_id.id, SID_SIZE); - struct buffer buf2 = tls_reset_standalone(&tas, &client_id, &server_id, header); + struct buffer buf2 = tls_reset_standalone(&tas, &client_id, &server_id, header, 0, 0); assert_int_equal(BLEN(&buf), BLEN(&buf2)); assert_memory_equal(BPTR(&buf), BPTR(&buf2), BLEN(&buf)); free_buf(&buf2); @@ -566,7 +566,7 @@ test_generate_reset_packet_tls_auth(void **ut_state) now = 0x22446688; reset_packet_id_send(&tas_client.tls_wrap.opt.packet_id.send); - struct buffer buf = tls_reset_standalone(&tas_client, &client_id, &server_id, header); + struct buffer buf = tls_reset_standalone(&tas_client, &client_id, &server_id, header, 0, 0); enum first_packet_verdict verdict = tls_pre_decrypt_lite(&tas_server, &state, &from, &buf); assert_int_equal(verdict, VERDICT_VALID_RESET_V2); @@ -575,7 +575,7 @@ test_generate_reset_packet_tls_auth(void **ut_state) /* Assure repeated generation of reset is deterministic/stateless*/ reset_packet_id_send(&tas_client.tls_wrap.opt.packet_id.send); - struct buffer buf2 = tls_reset_standalone(&tas_client, &client_id, &server_id, header); + struct buffer buf2 = tls_reset_standalone(&tas_client, &client_id, &server_id, header, 0, 0); assert_int_equal(BLEN(&buf), BLEN(&buf2)); assert_memory_equal(BPTR(&buf), BPTR(&buf2), BLEN(&buf)); free_buf(&buf2);