From patchwork Sat Dec 21 15:37:30 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gert Doering X-Patchwork-Id: 4009 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:998b:b0:5e7:b9eb:58e8 with SMTP id d11csp3045324mav; Sat, 21 Dec 2024 07:41:52 -0800 (PST) X-Forwarded-Encrypted: i=2; AJvYcCVOpU4s0iKHx1i6LGuLegRk6K3IrGTq7bJ3g46BDmitEOmcdfych9XPTGk8cee3Gfn6S3FfKPMf4To=@openvpn.net X-Received: by 2002:a05:6871:a4c4:b0:29e:4277:d398 with SMTP id 586e51a60fabf-2a7fb4aceb1mr3594921fac.31.1734795472900; Sat, 21 Dec 2024 07:37:52 -0800 (PST) X-Google-Smtp-Source: AGHT+IEyOBAWy4SQNmVTEDyfRL3bNzqmEgOVOf0vHIBKFRl55csWpDeK+aI7NfCU+2RlhXzdc9i/ X-Received: by 2002:a05:6871:a4c4:b0:29e:4277:d398 with SMTP id 586e51a60fabf-2a7fb4aceb1mr3594876fac.31.1734795471116; Sat, 21 Dec 2024 07:37:51 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1734795471; cv=none; d=google.com; s=arc-20240605; b=aMJ65SBDM1DDewECqpEvB1JEvX0xCcJ8YxOQkOKKGW4qG2TzUPedo6tYAvw/RRlzBI HlHefBedpQfpCWgjUnbHkN/rITBleGTffPcI8MRM4QB4CnlfxmqJbfNoOFLgqqaPBiD0 80ubSX0ikOiDzTH0PqLdo956OM55/b5pZ/tv/LeRPzxhFaRqqfdSUcEXSMTajivtjD9A ztvjnkZEHNwxljfh4ds4wgeqF6GvoVFkKvFTvGBg6RY8707ZhS8yP1JQEKWnMWNQrjCL Fo0/OriqJh00x43a0BEfmYN2JdYJGxDrYViwhu1eFr1sYOS7NcV6CaqDJ0HPG9snLCYK WU1Q== 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:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature; bh=MzPtYapPAcP427kzr6lxHA78OHSaUDs46vmmVs9uz+s=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=JDhEqaDjtta9Uq3Z9D4p/oir4s5cnVEjiSbhoeWI5tHqu04CH0gzIBksL5zIol4BAQ opbAcDAtTYScMBsEGvO6pirXzXNwQ3R2StjvAXRa3eUlVPbSgXuGvBBWtGqtN4YOOZUO sF0syTum3be5BgfKyZprGcaIc+aLavwuAp0ynfqgK1rGA6C8YkJ3rt7sM7vazTCGLPqE 3vLWOcxXcYe9awYf1sQYPVKGdyliDP5z9kM1mRkWPfdbBIlAuyhFIMCwxTa5q7JltdjW pZjsELnxDbckGQUKGtxSnUCEGjm9uPP3oIHDLRHHl2MGbT1eI4rcZdbx4ACKgkx+2xrk G1jQ==; 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=VhyfqcJm; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=mTdAtDMo; 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 586e51a60fabf-2a7d77c60f8si4025191fac.166.2024.12.21.07.37.50 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Sat, 21 Dec 2024 07:37:51 -0800 (PST) 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=VhyfqcJm; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=mTdAtDMo; 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-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1tP1Y2-0007SX-7N; Sat, 21 Dec 2024 15:37:46 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1tP1Y0-0007SP-O9 for openvpn-devel@lists.sourceforge.net; Sat, 21 Dec 2024 15:37:45 +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=ue7/rDBcDiAjdg7ix8x+3jT/Y/uuKO5u1ThLyx9KlF4=; b=VhyfqcJmrmfaCNges2UtGay0w8 V4det470aM9293TXYJ+bYNIpqsJxGdtxn9q1w3kg6DV2guRUut2oKXjYwwP8PUGX/ln+rNAkN7kar ZoCZA4GARSwwxhmSGnxCyrZ3Exw4nsDpMYG2Cs2y4olyT2jMSEjYLhyRWEk05ynsWVXw=; 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=ue7/rDBcDiAjdg7ix8x+3jT/Y/uuKO5u1ThLyx9KlF4=; b=mTdAtDMo35EP8SQBS/ilPKSyy+ 5GOaJCm2tGWPowgvNIsuHX+JqR17iMyL7nFAgE8YbnnEf3dIEj1JNeJfGZOw5MxSs9lT3nuFBcR6n Frfwh1ZEV8gmRbjfx7nLpmTjXy/PZY98PUbaSvnuUiOJJWvklK/I0VDoLTbNjxOy9vf0=; Received: from dhcp-174.greenie.muc.de ([193.149.48.174] helo=blue.greenie.muc.de) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1tP1Xz-00045B-GQ for openvpn-devel@lists.sourceforge.net; Sat, 21 Dec 2024 15:37:45 +0000 Received: from blue.greenie.muc.de (localhost [127.0.0.1]) by blue.greenie.muc.de (8.17.1.9/8.17.1.9) with ESMTP id 4BLFbVso001765 for ; Sat, 21 Dec 2024 16:37:31 +0100 Received: (from gert@localhost) by blue.greenie.muc.de (8.17.1.9/8.17.1.9/Submit) id 4BLFbVqa001764 for openvpn-devel@lists.sourceforge.net; Sat, 21 Dec 2024 16:37:31 +0100 From: Gert Doering To: openvpn-devel@lists.sourceforge.net Date: Sat, 21 Dec 2024 16:37:30 +0100 Message-ID: <20241221153731.1755-1-gert@greenie.muc.de> X-Mailer: git-send-email 2.45.2 In-Reply-To: References: 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 implements the limitation of AEAD key usage[1] with a confidentiality margin of 2^-57, the same as TLS 1.3. In this implementation, unlike TLS 1.3 that counts the number of records, we count the [...] Content analysis details: (0.0 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 RCVD_IN_VALIDITY_CERTIFIED_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.174 listed in sa-accredit.habeas.com] 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.174 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: 1tP1Xz-00045B-GQ Subject: [Openvpn-devel] [PATCH v14] Trigger renegotiation of data key if getting close to the AEAD usage limit 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?1819064896101053210?= X-GMAIL-MSGID: =?utf-8?q?1819064896101053210?= From: Arne Schwabe This implements the limitation of AEAD key usage[1] with a confidentiality margin of 2^-57, the same as TLS 1.3. In this implementation, unlike TLS 1.3 that counts the number of records, we count the actual number of packets and plaintext blocks. TLS 1.3 can reasonable assume that for large data transfers, full records are used and therefore the maximum record size of 2**14 (2*10 blocks) is used to calculate the number of records before a new key needs to be used. For a VPN like OpenVPN, the same calculation would either require using a pessimistic assumption of using a MTU size of 65k which limits us to 2^24 packets, which equals only 24 GB with more common MTU/MSS of 1400 or requiring a dynamic calculation which includes the actual MTU that we allow to send. For 1500 the calculation yields 2*29.4 which is a quite significant higher number of packets (923 GB at 1400 MSS/MTU). To avoid this dynamic calculation and also avoid needing to know the MSS/MTU size in the crypto layer, this implementation foregoes the simplification of counting just packets but will count blocks and packets instead and determines the limit from that. This also has the side effect that connections with a lot of small packets (like TCP ACKs) mixed with large packets will be able to keep using the same key much longer until requiring a renegotiation. This patch will set the limit where to trigger the renegotiation at 7/8 of the recommended maximum value. [1] https://www.ietf.org/archive/id/draft-irtf-cfrg-aead-limits-08.html Testing instructions: The easiest way to test if this patch works as intended is to manually change the return value of cipher_get_aead_limits to some silly low value like 2048. After a bit of VPN traffic, a soft reset should occur that indicates being over the TLS: soft reset sec=41/3600 bytes=59720/-1 pkts=78/0 aead_limit_send=1883/1792 aead_limit_recv=1937/1792 Here the send limit is over the limit (1792 = 2048 * 8/7). Change-Id: I057f007577f10c6ac917ee4620ee3d2559187dc7 Signed-off-by: Arne Schwabe Acked-by: Gert Doering --- This change was reviewed on Gerrit and approved by at least one developer. I request to merge it to master. Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/796 This mail reflects revision 14 of this Change. Acked-by according to Gerrit (reflected above): Gert Doering diff --git a/Changes.rst b/Changes.rst index a6cc3be..34542a5 100644 --- a/Changes.rst +++ b/Changes.rst @@ -22,6 +22,14 @@ For more details see [lwipovpn on Gihtub](https://github.com/OpenVPN/lwipovpn). +Enforcement of AES-GCM usage limit + OpenVPN will now enforce the usage limits on AES-GCM with the same + confidentiality margin as TLS 1.3 does. This mean that renegotiation will + be triggered after roughly 2^28 to 2^31 packets depending of the packet + size. More details about usage limit of AES-GCM can be found here: + + https://datatracker.ietf.org/doc/draft-irtf-cfrg-aead-limits/ + Deprecated features ------------------- ``secret`` support has been removed by default. diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c index d548c8d..faf69fc 100644 --- a/src/openvpn/crypto.c +++ b/src/openvpn/crypto.c @@ -138,6 +138,11 @@ ASSERT(cipher_ctx_update(ctx->cipher, BEND(&work), &outlen, BPTR(buf), BLEN(buf))); ASSERT(buf_inc_len(&work, outlen)); + /* update number of plaintext blocks encrypted. Use the (x + (n-1))/n trick + * to round up the result to the number of blocks used */ + const int blocksize = AEAD_LIMIT_BLOCKSIZE; + opt->key_ctx_bi.encrypt.plaintext_blocks += (outlen + (blocksize - 1))/blocksize; + /* Flush the encryption buffer */ ASSERT(cipher_ctx_final(ctx->cipher, BEND(&work), &outlen)); ASSERT(buf_inc_len(&work, outlen)); @@ -325,6 +330,37 @@ } } +uint64_t +cipher_get_aead_limits(const char *ciphername) +{ + if (!cipher_kt_mode_aead(ciphername)) + { + return 0; + } + + if (cipher_kt_name(ciphername) == cipher_kt_name("CHACHA20-POLY1305")) + { + return 0; + } + + /* Assume all other ciphers require the limit */ + + /* We focus here on the equation + * + * q + s <= p^(1/2) * 2^(129/2) - 1 + * + * as is the one that is limiting us. + * + * With p = 2^-57 this becomes + * + * q + s <= (2^36 - 1) + * + */ + uint64_t rs = (1ull << 36) - 1; + + return rs; +} + bool crypto_check_replay(struct crypto_options *opt, const struct packet_id_net *pin, const char *error_prefix, @@ -487,6 +523,12 @@ goto error_exit; } + + /* update number of plaintext blocks decrypted. Use the (x + (n-1))/n trick + * to round up the result to the number of blocks used. */ + const int blocksize = AEAD_LIMIT_BLOCKSIZE; + opt->key_ctx_bi.decrypt.plaintext_blocks += (outlen + (blocksize - 1))/blocksize; + *buf = work; gc_free(&gc); diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h index 04d7bb2..4579b65 100644 --- a/src/openvpn/crypto.h +++ b/src/openvpn/crypto.h @@ -177,6 +177,10 @@ uint8_t implicit_iv[OPENVPN_MAX_IV_LENGTH]; /**< The implicit part of the IV */ size_t implicit_iv_len; /**< The length of implicit_iv */ + /** Counter for the number of plaintext block encrypted using this cipher + * with the current key in number of 128 bit blocks (only used for + * AEAD ciphers) */ + uint64_t plaintext_blocks; }; #define KEY_DIRECTION_BIDIRECTIONAL 0 /* same keys for both directions */ @@ -607,6 +611,25 @@ } /** + * Check if the cipher is an AEAD cipher and needs to be limited to a certain + * number of number of blocks + packets. Return 0 if ciphername is not an AEAD + * cipher or no limit (e.g. Chacha20-Poly1305) is needed. (Or the limit is + * larger than 2^64) + * + * For reference see the OpenVPN RFC draft and + * https://www.ietf.org/archive/id/draft-irtf-cfrg-aead-limits-08.html + */ +uint64_t +cipher_get_aead_limits(const char *ciphername); + +/** + * Blocksize used for the AEAD limit caluclation + * + * Since cipher_ctx_block_size() is not reliable and will return 1 in many + * cases use a hardcoded blocksize instead */ +#define AEAD_LIMIT_BLOCKSIZE 16 + +/** * Checks if the current TLS library supports the TLS 1.0 PRF with MD5+SHA1 * that OpenVPN uses when TLS Keying Material Export is not available. * @@ -614,4 +637,20 @@ */ bool check_tls_prf_working(void); +/** + * Checks if the usage limit for an AEAD cipher is reached + * + * This method abstracts the calculation to make the calling function easier + * to read. + */ +static inline bool +aead_usage_limit_reached(const uint64_t limit, const struct key_ctx *key_ctx, + int64_t higest_pid) +{ + /* This is the q + s <= p^(1/2) * 2^(129/2) - 1 calculation where + * q is the number of protected messages (highest_pid) + * s Total plaintext length in all messages (in blocks) */ + return (limit > 0 && key_ctx->plaintext_blocks + (uint64_t) higest_pid > limit); +} + #endif /* CRYPTO_H */ diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index fcfb344..c77c4ed 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -131,6 +131,26 @@ } } +static uint64_t +tls_get_limit_aead(const char *ciphername) +{ + uint64_t limit = cipher_get_aead_limits(ciphername); + + if (limit == 0) + { + return 0; + } + + /* set limit to 7/8 of the limit so the renegotiation can succeed before + * we go over the limit */ + limit = limit/8 * 7; + + msg(D_SHOW_KEYS, "Note: AEAD cipher %s will trigger a renegotiation" + " at a sum of %" PRIi64 " blocks and packets.", + ciphername, limit); + return limit; +} + void tls_init_control_channel_frame_parameters(struct frame *frame, int tls_mtu) { @@ -1579,6 +1599,8 @@ tls_limit_reneg_bytes(session->opt->key_type.cipher, &session->opt->renegotiate_bytes); + session->opt->aead_usage_limit = tls_get_limit_aead(session->opt->key_type.cipher); + /* set the state of the keys for the session to generated */ ks->state = S_GENERATED_KEYS; @@ -2999,6 +3021,27 @@ return true; } + /* Check the AEAD usage limit of cleartext blocks + packets. + * + * Contrary to when epoch data mode is active, where only the sender side + * checks the limit, here we check both receive and send limit since + * we assume that only one side is aware of the limit. + * + * Since if both sides were aware, then both sides will probably also + * switch to use epoch data channel instead, so this code is not + * in effect then. + */ + const struct key_ctx_bi *key_ctx_bi = &ks->crypto_options.key_ctx_bi; + const uint64_t usage_limit = session->opt->aead_usage_limit; + + if (aead_usage_limit_reached(usage_limit, &key_ctx_bi->encrypt, + ks->crypto_options.packet_id.send.id) + || aead_usage_limit_reached(usage_limit, &key_ctx_bi->decrypt, + ks->crypto_options.packet_id.rec.id)) + { + return true; + } + return false; } /* @@ -3031,10 +3074,17 @@ && should_trigger_renegotiation(session, ks)) { msg(D_TLS_DEBUG_LOW, "TLS: soft reset sec=%d/%d bytes=" counter_format - "/%" PRIi64 " pkts=" counter_format "/%" PRIi64, + "/%" PRIi64 " pkts=" counter_format "/%" PRIi64 + " aead_limit_send=%" PRIu64 "/%" PRIu64 + " aead_limit_recv=%" PRIu64 "/%" PRIu64, (int) (now - ks->established), session->opt->renegotiate_seconds, ks->n_bytes, session->opt->renegotiate_bytes, - ks->n_packets, session->opt->renegotiate_packets); + ks->n_packets, session->opt->renegotiate_packets, + ks->crypto_options.key_ctx_bi.encrypt.plaintext_blocks + ks->n_packets, + session->opt->aead_usage_limit, + ks->crypto_options.key_ctx_bi.decrypt.plaintext_blocks + ks->n_packets, + session->opt->aead_usage_limit + ); key_state_soft_reset(session); } diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 5840e2d..ccbc053 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -333,6 +333,9 @@ interval_t packet_timeout; int64_t renegotiate_bytes; int64_t renegotiate_packets; + /** limit for AEAD cipher, this is the sum of packets + blocks + * that are allowed to be used */ + uint64_t aead_usage_limit; interval_t renegotiate_seconds; /* cert verification parms */ diff --git a/tests/unit_tests/openvpn/test_crypto.c b/tests/unit_tests/openvpn/test_crypto.c index fdc8fbd..ec8e661 100644 --- a/tests/unit_tests/openvpn/test_crypto.c +++ b/tests/unit_tests/openvpn/test_crypto.c @@ -448,6 +448,29 @@ gc_free(&gc); } +void +crypto_test_aead_limits(void **state) +{ + /* if ChaCha20-Poly1305 is not supported by the crypto library or in the + * current mode (FIPS), this will still return -1 */ + assert_int_equal(cipher_get_aead_limits("CHACHA20-POLY1305"), 0); + + int64_t aeslimit = cipher_get_aead_limits("AES-128-GCM"); + + assert_int_equal(aeslimit, (1ull << 36) - 1); + + /* Check if this matches our exception for 1600 size packets assuming + * AEAD_LIMIT_BLOCKSIZE (128 bits/ 16 bytes). Gives us 100 blocks + * + 1 for the packet */ + int64_t L = 101; + /* 2 ^ 29.34, using the result here to avoid linking to libm */ + assert_int_equal(aeslimit / L, 680390858); + + /* and for 9000, 2^26.86 */ + L = 563; + assert_int_equal(aeslimit / L, 122059461); +} + int main(void) { @@ -458,7 +481,8 @@ cmocka_unit_test(crypto_test_tls_prf), cmocka_unit_test(crypto_test_hmac), cmocka_unit_test(test_occ_mtu_calculation), - cmocka_unit_test(test_mssfix_mtu_calculation) + cmocka_unit_test(test_mssfix_mtu_calculation), + cmocka_unit_test(crypto_test_aead_limits) }; #if defined(ENABLE_CRYPTO_OPENSSL)