From patchwork Wed Apr 2 14:23:38 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gert Doering X-Patchwork-Id: 4205 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:6c6:b0:60a:d70a:d3c7 with SMTP id j6csp3551103maw; Wed, 2 Apr 2025 08:03:45 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCXvu4X1XWFembarrBuFxujiX1RBGgjZf5PO+P1Azqqap/+GmjVZEUC3vLUYl7XLSnDG4fePfApdElI=@openvpn.net X-Google-Smtp-Source: AGHT+IEnaWCN01Rju5ZXKAdg+E2gjBKWiTiBBQADNrYxX9lY6ns1u6KhiiteItYPyMGslwMcAPAM X-Received: by 2002:a05:6808:1890:b0:3fe:b0ad:f92a with SMTP id 5614622812f47-4003618ae18mr1509932b6e.6.1743606225611; Wed, 02 Apr 2025 08:03:45 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1743606225; cv=none; d=google.com; s=arc-20240605; b=T2KfblPL1S8WEADQpNPGmNobj3hRRMd16atbTQyRjfxWDXLCP2kGGdjlT7BBjzq6RV yTExLzBp0DhA0yFkLgm6ow+coEsx9ru0vrcQnv1DlqLgOLosB1FEa6EQTd7Lu5nrQtXd RJ6gS2vPV2tJBFJKKDJ3kQwryAfWJcANVuTTHuQTY3a+otSNnlNe9yqKFiNhLxV2m+tt /j3QW6IexDlmOk/TZWz2UaI0VbzIrucSTLF7DravNBTdjRwWP6XYA9E2bi3hTTwItf6k BAh5aDLBAQ5OuIz1rKPMx5L6uYXQoe4U6j8X7+IFNZ3ys/9PjImyhYe/xBJ2zl6pUJ+c SR9A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:message-id:date:to:from:dkim-signature:dkim-signature; bh=xi3J4DP7X9WjMcMg+g2wCTHGoug/qU6Fc5hdMYffAfE=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=bN+DWn4RVMXN+y9vjTmN2QP7OLs/HwKs5sGxhat8Jko+iqJunLJtv9kbHuQFcpmOEF Ahuc/EICpQWRI0OSGhusq5ubEwkANXaSoNecUMLFH1GPr07eZXaXjwy0OaQusQ/3Mumf wWLVwdrmjfazWOYp0zKncI42ti2vxVbFAF/1++Qa50ZhhfwhlQYvseksuRrZFLKJThHg iT04pB9HnqWNAE54R4o3KBfdH5FWtfGzcm0kYxaXJiBb1hpdMricj3c+/DYeZxZRDzcK QZLT8mZyJkHyjAdlZ0d/T0Xbf06t+YgdNFDBun8uINoWHKCvFVpbCjvv/PtVGFbcEy/u qJ5A==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=liclVoMA; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=gsT15oYA; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=muc.de Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 006d021491bc7-602867479cesi8036754eaf.32.2025.04.02.08.03.44 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 02 Apr 2025 08:03:45 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=liclVoMA; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=gsT15oYA; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=muc.de Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1tzzcx-0003vr-Cq; Wed, 02 Apr 2025 15:03:40 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1tzzcv-0003vk-Vx for openvpn-devel@lists.sourceforge.net; Wed, 02 Apr 2025 15:03:38 +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: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:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=DtiZGDuzQ1SbCE4OhVTDEu+C7gSJqkLeYuzJUy1epLA=; b=liclVoMAY+p6zG0bQ+4J0rEr9u dMWaAd/XbYIs9POSvu+0TNZDkxv0+5m4S8DJ95DgQK5RsyTmhpWrLxRUY6+v8sOTWCs3Jz/1wGPIO p5fbaLjmvIYrI1ABaxytvfQsKv0U+R8QbQZJ5Np4BnnZwzNxF8t2fXS3k2Q26xSHdDxA=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version: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:In-Reply-To: References:List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post: List-Owner:List-Archive; bh=DtiZGDuzQ1SbCE4OhVTDEu+C7gSJqkLeYuzJUy1epLA=; b=g sT15oYAFo9qAZ7MaC3i2c7n+GyaL0sgOb2vvjdb/s3ai9p+/HO84BsaLCVbKF/rtTwERGK1RNupaV j4l2Bd0qMiHs+BHD6fDegKNhB2awmIPDNqwmzEvAhc3MqooRwwwEuZVUqsXhXEUDJiYBe6U73jCxB skxaZtBeRjbBong0=; Received: from chekov.greenie.muc.de ([193.149.48.178]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1tzzcf-0003vn-CD for openvpn-devel@lists.sourceforge.net; Wed, 02 Apr 2025 15:03:38 +0000 Received: from chekov.greenie.muc.de (localhost [IPv6:0:0:0:0:0:0:0:1]) by chekov.greenie.muc.de (8.18.1/8.18.1) with ESMTPS id 532ENcAm060888 (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NO) for ; Wed, 2 Apr 2025 16:23:38 +0200 (CEST) (envelope-from gert@chekov.greenie.muc.de) Received: (from gert@localhost) by chekov.greenie.muc.de (8.18.1/8.18.1/Submit) id 532ENcDT060887 for openvpn-devel@lists.sourceforge.net; Wed, 2 Apr 2025 16:23:38 +0200 (CEST) (envelope-from gert) From: Gert Doering To: openvpn-devel@lists.sourceforge.net Date: Wed, 2 Apr 2025 16:23:38 +0200 Message-ID: <20250402142338.60879-1-gert@greenie.muc.de> X-Mailer: git-send-email 2.47.0 MIME-Version: 1.0 X-Spam-Score: 0.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: From: Arne Schwabe This fixes an internal server error condition that can be triggered by a malicous authenticated client, a very unlucky corruption of packets in transit or by an attacker that is able to inject a speci [...] Content analysis details: (0.0 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 RCVD_IN_VALIDITY_SAFE_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. [193.149.48.178 listed in sa-trusted.bondedsender.org] 0.0 RCVD_IN_VALIDITY_RPBL_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. [193.149.48.178 listed in bl.score.senderscore.com] -0.0 SPF_PASS SPF: sender matches SPF record -0.0 SPF_HELO_PASS SPF: HELO matches SPF record X-Headers-End: 1tzzcf-0003vn-CD Subject: [Openvpn-devel] [PATCH] Allow tls-crypt-v2 to be setup only on initial packet of a session 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 X-GMAIL-THRID: =?utf-8?q?1828303641804573333?= X-GMAIL-MSGID: =?utf-8?q?1828303641804573333?= From: Arne Schwabe This fixes an internal server error condition that can be triggered by a malicous authenticated client, a very unlucky corruption of packets in transit or by an attacker that is able to inject a specially created packet at the right time and is able to observe the traffic to construct the packet. The error condition results in an ASSERT statement being triggered, NOTE: due to the security sensitive nature, this patch was prepared under embargo on the security@openvpn.net mailing list, and thus has no publically available "mailing list discussion before merge" URL. CVE: 2025-2704 Change-Id: I07c1352204d308e5bde5f0b85e561a5dd0bc63c8 Signed-off-by: Arne Schwabe Acked-by: Gert Doering Message-Id: <385d88f0-d7c9-4330-82ff-9f5931183afd@rfc2549.org> Signed-off-by: Gert Doering --- src/openvpn/ssl.c | 26 +++++++++++++++++++---- src/openvpn/ssl_common.h | 15 +++++++------ src/openvpn/ssl_pkt.c | 7 +++--- src/openvpn/ssl_pkt.h | 12 +++++++++-- src/openvpn/tls_crypt.c | 24 ++++++++++++++++++++- src/openvpn/tls_crypt.h | 7 +++++- tests/unit_tests/openvpn/test_tls_crypt.c | 2 +- 7 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 99b5c077..23f64232 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -697,6 +697,9 @@ state_name(int state) case S_INITIAL: return "S_INITIAL"; + case S_PRE_START_SKIP: + return "S_PRE_START_SKIP"; + case S_PRE_START: return "S_PRE_START"; @@ -2506,7 +2509,7 @@ session_move_pre_start(const struct tls_session *session, } INCR_GENERATED; - ks->state = S_PRE_START; + ks->state = skip_initial_send ? S_PRE_START_SKIP : S_PRE_START; struct gc_arena gc = gc_new(); dmsg(D_TLS_DEBUG, "TLS: Initial Handshake, sid=%s", @@ -3825,7 +3828,7 @@ tls_pre_decrypt(struct tls_multi *multi, } if (!read_control_auth(buf, tls_session_get_tls_wrap(session, key_id), from, - session->opt)) + session->opt, true)) { goto error; } @@ -3895,7 +3898,7 @@ tls_pre_decrypt(struct tls_multi *multi, if (op == P_CONTROL_SOFT_RESET_V1 && ks->state >= S_GENERATED_KEYS) { if (!read_control_auth(buf, tls_session_get_tls_wrap(session, key_id), - from, session->opt)) + from, session->opt, false)) { goto error; } @@ -3908,6 +3911,15 @@ tls_pre_decrypt(struct tls_multi *multi, } else { + bool initial_packet = false; + if (ks->state == S_PRE_START_SKIP) + { + /* When we are coming from the session_skip_to_pre_start + * method, we allow this initial packet to setup the + * tls-crypt-v2 peer specific key */ + initial_packet = true; + ks->state = S_PRE_START; + } /* * Remote responding to our key renegotiation request? */ @@ -3917,8 +3929,14 @@ tls_pre_decrypt(struct tls_multi *multi, } if (!read_control_auth(buf, tls_session_get_tls_wrap(session, key_id), - from, session->opt)) + from, session->opt, initial_packet)) { + /* if an initial packet in read_control_auth, we rather + * error out than anything else */ + if (initial_packet) + { + multi->n_hard_errors++; + } goto error; } diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 68a6ce63..90e16f9e 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -84,22 +84,25 @@ #define S_INITIAL 1 /**< Initial \c key_state state after * initialization by \c key_state_init() * before start of three-way handshake. */ -#define S_PRE_START 2 /**< Waiting for the remote OpenVPN peer +#define S_PRE_START_SKIP 2 /**< Waiting for the remote OpenVPN peer * to acknowledge during the initial * three-way handshake. */ -#define S_START 3 /**< Three-way handshake is complete, +#define S_PRE_START 3 /**< Waiting for the remote OpenVPN peer + * to acknowledge during the initial + * three-way handshake. */ +#define S_START 4 /**< Three-way handshake is complete, * start of key exchange. */ -#define S_SENT_KEY 4 /**< Local OpenVPN process has sent its +#define S_SENT_KEY 5 /**< Local OpenVPN process has sent its * part of the key material. */ -#define S_GOT_KEY 5 /**< Local OpenVPN process has received +#define S_GOT_KEY 6 /**< Local OpenVPN process has received * the remote's part of the key * material. */ -#define S_ACTIVE 6 /**< Operational \c key_state state +#define S_ACTIVE 7 /**< Operational \c key_state state * immediately after negotiation has * completed while still within the * handshake window. Deferred auth and * client connect can still be pending. */ -#define S_GENERATED_KEYS 7 /**< The data channel keys have been generated +#define S_GENERATED_KEYS 8 /**< The data channel keys have been generated * The TLS session is fully authenticated * when reaching this state. */ diff --git a/src/openvpn/ssl_pkt.c b/src/openvpn/ssl_pkt.c index 689cd7f9..41299f46 100644 --- a/src/openvpn/ssl_pkt.c +++ b/src/openvpn/ssl_pkt.c @@ -200,7 +200,8 @@ bool read_control_auth(struct buffer *buf, struct tls_wrap_ctx *ctx, const struct link_socket_actual *from, - const struct tls_options *opt) + const struct tls_options *opt, + bool initial_packet) { struct gc_arena gc = gc_new(); bool ret = false; @@ -208,7 +209,7 @@ read_control_auth(struct buffer *buf, const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT; if ((opcode == P_CONTROL_HARD_RESET_CLIENT_V3 || opcode == P_CONTROL_WKC_V1) - && !tls_crypt_v2_extract_client_key(buf, ctx, opt)) + && !tls_crypt_v2_extract_client_key(buf, ctx, opt, initial_packet)) { msg(D_TLS_ERRORS, "TLS Error: can not extract tls-crypt-v2 client key from %s", @@ -373,7 +374,7 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas, * into newbuf or just setting newbuf to point to the start of control * message */ bool status = read_control_auth(&state->newbuf, &state->tls_wrap_tmp, - from, NULL); + from, NULL, true); if (!status) { diff --git a/src/openvpn/ssl_pkt.h b/src/openvpn/ssl_pkt.h index b2c4b372..e7823916 100644 --- a/src/openvpn/ssl_pkt.h +++ b/src/openvpn/ssl_pkt.h @@ -208,14 +208,22 @@ write_control_auth(struct tls_session *session, bool prepend_ack); -/* + +/** * Read a control channel authentication record. + * @param buf buffer that holds the incoming packet + * @param ctx control channel security context + * @param from incoming link socket address + * @param opt tls options struct for the session + * @param initial_packet whether this is the initial packet for the connection + * @return if the packet was successfully processed */ bool read_control_auth(struct buffer *buf, struct tls_wrap_ctx *ctx, const struct link_socket_actual *from, - const struct tls_options *opt); + const struct tls_options *opt, + bool initial_packet); /** diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index 9e9807d3..67e19382 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -614,7 +614,8 @@ cleanup: bool tls_crypt_v2_extract_client_key(struct buffer *buf, struct tls_wrap_ctx *ctx, - const struct tls_options *opt) + const struct tls_options *opt, + bool initial_packet) { if (!ctx->tls_crypt_v2_server_key.cipher) { @@ -643,6 +644,27 @@ tls_crypt_v2_extract_client_key(struct buffer *buf, return false; } + if (!initial_packet) + { + /* This might be a harmless resend of the packet but it is better to + * just ignore the WKC part than trying to setup tls-crypt keys again. + * + * A CONTROL_WKC_V1 packets has a normal packet part and an appended + * wrapped control key. These are authenticated individually. We already + * set up tls-crypt with the wrapped key, so we are ignoring this part + * of the message but we return the normal packet part as the normal + * part of the message might have been corrupted earlier and discarded + * and this is resend. So return the normal part of the packet, + * basically transforming the CONTROL_WKC_V1 into a normal CONTROL_V1 + * packet*/ + msg(D_TLS_ERRORS, "control channel security already setup ignoring " + "wrapped key part of packet."); + + /* Remove client key from buffer so tls-crypt code can unwrap message */ + ASSERT(buf_inc_len(buf, -(BLEN(&wrapped_client_key)))); + return true; + } + ctx->tls_crypt_v2_metadata = alloc_buf(TLS_CRYPT_V2_MAX_METADATA_LEN); if (!tls_crypt_v2_unwrap_client_key(&ctx->original_wrap_keydata, &ctx->tls_crypt_v2_metadata, diff --git a/src/openvpn/tls_crypt.h b/src/openvpn/tls_crypt.h index e98aae78..1a604ce8 100644 --- a/src/openvpn/tls_crypt.h +++ b/src/openvpn/tls_crypt.h @@ -205,11 +205,16 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key, * message. * @param ctx tls-wrap context to be initialized with the client key. * + * @param initial_packet whether this is the initial packet of the + * connection. Only in these scenarios unwrapping + * of a tls-crypt-v2 key is allowed + * * @returns true if a key was successfully extracted. */ bool tls_crypt_v2_extract_client_key(struct buffer *buf, struct tls_wrap_ctx *ctx, - const struct tls_options *opt); + const struct tls_options *opt, + bool initial_packet); /** * Generate a tls-crypt-v2 server key, and write to file. diff --git a/tests/unit_tests/openvpn/test_tls_crypt.c b/tests/unit_tests/openvpn/test_tls_crypt.c index ee252f43..cc415c89 100644 --- a/tests/unit_tests/openvpn/test_tls_crypt.c +++ b/tests/unit_tests/openvpn/test_tls_crypt.c @@ -535,7 +535,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, NULL)); + assert_true(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx, NULL, true)); tls_wrap_free(&wrap_ctx); }