From patchwork Mon Nov 11 02:00:01 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "plaisthos (Code Review)" X-Patchwork-Id: 3936 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:41ba:b0:5d9:9f4c:3bc7 with SMTP id a26csp2278282mad; Sun, 10 Nov 2024 18:00:36 -0800 (PST) X-Forwarded-Encrypted: i=2; AJvYcCVINbmOAiUC9z9PVmtEBzH+rdkQ4pB/7D3YUtKOPeVutQoynbYn3CcOF1IeIUpBjMv7IIhcWsZx1Og=@openvpn.net X-Google-Smtp-Source: AGHT+IH7qz/J2edZP+L8GMtqWEYotfL+e1FiIYUPFtRzA2lGEESrYYQtuGJdUxCuGhAfp5h6ymRl X-Received: by 2002:a05:6e02:7:b0:3a6:af24:b8c4 with SMTP id e9e14a558f8ab-3a6f1a4e8b1mr117024095ab.20.1731290435606; Sun, 10 Nov 2024 18:00:35 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1731290435; cv=none; d=google.com; s=arc-20240605; b=bmhF9mbDbVI1qEDo8fmTRUagVgJVfbHY2RIVcJc+uruwJr+1UFLKSisVUu95E3sB15 ZnLD3a5yDBPDLaebUJncRKQL2NLNpFlOlAonOtGVU58rnbI5C8WcpFGKra6DGxEstRwq MehWqpJzTj2KMvwG/tM9aFzKEfyBf3MVxq7fF3zy+DigYAagfFlY2KGfDM2HwYMk0yJb 0ZIbh7FatQO/9KJO/+j9FiehKi5ZxuenegrqgIkjsgu7dhbGf3+MVkLrK0BXQDnZFjmS aWKh5otmpn4+5BMk/dDCz5B6txgkX0r0F5cV8xuzviYCmDoGK41WMCF/VZ1eFJ1v04tv qbUw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=errors-to:cc:reply-to:list-subscribe:list-help:list-post :list-archive:list-unsubscribe:list-id:precedence:subject:user-agent :mime-version:message-id:references:auto-submitted:to:date:from :dkim-signature:dkim-signature:dkim-signature; bh=g5ptlPhdzu74mHiBeANnFgCpKw6lTrZ8i/1RfWrpM7c=; fh=lm0MLPW7DntlrDqRECIiC9JlE1uPxhepE0URYHIf+eE=; b=WiMl3LvWtoZ7f/Xy+oarAv9AzTZlrcR7UPJ9StoQJfdYamA3d5f6NNkrEjEWWOr0mG hmJ+JQn809j4uHSI/CojztsTcjAKSx4bFbggYHPWE2NptzFAescEQO1BIEV7mz5Kv4Q7 3txM5hrSKhzx2kUsbbNRaqSDf6aH1OOnMkoZ0/UJSkddizLyoi8o6SokhuqgtuQjU6Un bsEHgVECYmTn1McMhwMITFzqUh1QrRskPJkAF+OpZ036tPSTpQMqTNWkoemr7Ldb5kyG RkVBqYFruD98P5OpwvvIntuhn8s7GJL8h+QmL/cpt38TTy5al/1aQ1+5UBFjQG9vl3Og 24Tg==; 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=VmJa3Tcb; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="HA38a/3N"; dkim=neutral (body hash did not verify) header.i=@openvpn.net header.s=google header.b=fUWcIbey; 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=openvpn.net; dara=fail header.i=@openvpn.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id e9e14a558f8ab-3a6f98250besi5027465ab.83.2024.11.10.18.00.35 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Sun, 10 Nov 2024 18:00:35 -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=VmJa3Tcb; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b="HA38a/3N"; dkim=neutral (body hash did not verify) header.i=@openvpn.net header.s=google header.b=fUWcIbey; 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=openvpn.net; dara=fail header.i=@openvpn.net 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 1tAJj6-0007Jt-M4; Mon, 11 Nov 2024 02:00:25 +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 1tAJj5-0007Jj-Dq for openvpn-devel@lists.sourceforge.net; Mon, 11 Nov 2024 02:00:24 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Type:Content-Transfer-Encoding:MIME-Version :Message-ID:Reply-To:References:Subject:List-Unsubscribe:List-Id:Cc:To:Date: From:Sender:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:List-Help: List-Subscribe:List-Post:List-Owner:List-Archive; bh=nt6ZMVO9Udcb6NsYMN6bsXsFdXNPrG3u7N6iEPZBGJw=; b=VmJa3TcbGR7CQysdkJ62mQcSbe dJHS9O7HRhKj+Hi6bkF0x1Yor8FUQj/I96Bqtei0Y/QyvCr7cD0QmGSxv/LPk2oOn+ivOhQNc1v1z HeKqKJ6sj4NirrHDlTw689eIhcix8igqU+dGMkfCeqY4M3Rbzb589+67s1RzERxFw5eY=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Type:Content-Transfer-Encoding:MIME-Version:Message-ID:Reply-To: References:Subject:List-Unsubscribe:List-Id:Cc:To:Date:From:Sender:Content-ID :Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To: Resent-Cc:Resent-Message-ID:In-Reply-To:List-Help:List-Subscribe:List-Post: List-Owner:List-Archive; bh=nt6ZMVO9Udcb6NsYMN6bsXsFdXNPrG3u7N6iEPZBGJw=; b=H A38a/3NXTX452ua5oTEz2T9vaD6a8lxR7kR1pTOuVEdySwz9CkBaaLZNKlwlGE2Rq56nhbgw5ojr2 SHrm5Zih0LBwTwp3CDI4/tqJnJAUr1jMTt6pLuNHvXCG76lwgpdafEZrD7NfbQdxQVBYLwCGDLgBe MoG/K2c7Ctprr3QY=; Received: from mail-wm1-f52.google.com ([209.85.128.52]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.95) id 1tAJj2-0001as-0S for openvpn-devel@lists.sourceforge.net; Mon, 11 Nov 2024 02:00:24 +0000 Received: by mail-wm1-f52.google.com with SMTP id 5b1f17b1804b1-43161c0068bso33825205e9.1 for ; Sun, 10 Nov 2024 18:00:18 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1731290407; x=1731895207; darn=lists.sourceforge.net; h=user-agent:content-disposition:content-transfer-encoding :mime-version:message-id:reply-to:references:subject :list-unsubscribe:list-id:auto-submitted:cc:to:date:from:from:to:cc :subject:date:message-id:reply-to; bh=nt6ZMVO9Udcb6NsYMN6bsXsFdXNPrG3u7N6iEPZBGJw=; b=fUWcIbey2Cmf5BhEsZK0YqjNoKnC+3jlSTb4VCQVLg4nUViAEEJ0leWLkVHGfR/rkW 6ij/DaQ5KKBlpdYODfCa0dtLUaQKBKpHL7gEZFVQJwjs2PQwUMKC3PDc0RaMrfbus21e ek2BMjLdZu6N4NYSikqPiGUQ/AARWjTxOxxwuREWQlLG3pTExywVWRNvNHZtZ5watfG9 V85EPGOe//YVk8BeJmpxNWNN3U6XzkHReNtfMluuN+jAekioIN53to8qG1hMoJohEaIT OnvmpFsFHMkE+CNrhkjHNokz+pKgHO/I5T/7KMlZYHjtTSCoLgeGU8sYtsLwnUTteROY iI1A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1731290407; x=1731895207; h=user-agent:content-disposition:content-transfer-encoding :mime-version:message-id:reply-to:references:subject :list-unsubscribe:list-id:auto-submitted:cc:to:date:from :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=nt6ZMVO9Udcb6NsYMN6bsXsFdXNPrG3u7N6iEPZBGJw=; b=fV5IDXH/0/4QoiIqlx8F1IT/vAcc7f6iT5WWAcv4cvFbfM8GkRz7mNR+JXKqWBE5Uc E1raewOFgpEU9vs8c/v6wPxe2DNJjMt6qr63Yvm91Ssuulv5RtAOjNFB2QXUycSl6zix HpShcq7UK00PcAlfWSOJY+iGLwowTftKBxWh4TEOchqmPWUNj3UoGT+uV7DOTxEUFVhR Wnlr0aQ++UUGrQF5AAys0kSsOe7b+/1lVdO0Mv98XwKsy5orhUqJ4ga1c9d2RCDKeeRJ kBwBr6WI4t5g3In1A88DWIsOd/LLE+/XyT2F/arh/DvQ4lwEj+g+O+l+4v4eiJR8ndUY 38vw== X-Gm-Message-State: AOJu0YxpbzjBA0HOUemrBSVrpoOvep1f74LcI4YWrHcaXIGKsvraPwvM g1XCPDo98VIRi5x43wZwnaMSGVARBWBI3prRVLkrraFEVAtQNbBXRiZx/GESvYPZaZOfoHUnQwV D X-Received: by 2002:a5d:5850:0:b0:381:cde6:4ced with SMTP id ffacd0b85a97d-381f1880662mr8565889f8f.45.1731290405045; Sun, 10 Nov 2024 18:00:05 -0800 (PST) Received: from gerrit.openvpn.in (ec2-18-159-0-78.eu-central-1.compute.amazonaws.com. [18.159.0.78]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-381eda03696sm11706419f8f.87.2024.11.10.18.00.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 10 Nov 2024 18:00:03 -0800 (PST) From: "plaisthos (Code Review)" X-Google-Original-From: "plaisthos (Code Review)" X-Gerrit-PatchSet: 1 Date: Mon, 11 Nov 2024 02:00:01 +0000 To: flichtenheld Auto-Submitted: auto-generated X-Gerrit-MessageType: newchange X-Gerrit-Change-Id: Id7d6a576ca8c9560cb2dfae82fc62175820e9b80 X-Gerrit-Change-Number: 804 X-Gerrit-Project: openvpn X-Gerrit-ChangeURL: X-Gerrit-Commit: 7f70f68a80fc43f62cfd26652928cadba69b8e5f References: Message-ID: <4134baab4dccf956709c8577b8a4d87858aff017-HTML@gerrit.openvpn.net> MIME-Version: 1.0 User-Agent: Gerrit/3.8.2 X-Spam-Score: -0.9 (/) 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: Attention is currently required from: flichtenheld. Hello flichtenheld, I'd like you to do a code review. Please visit Content analysis details: (-0.9 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at https://www.dnswl.org/, no trust [209.85.128.52 listed in list.dnswl.org] -0.7 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [209.85.128.52 listed in wl.mailspike.net] 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.0 SPF_PASS SPF: sender matches SPF record 0.0 WEIRD_PORT URI: Uses non-standard port number for HTTP 0.0 HTML_MESSAGE BODY: HTML included in message -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.0 T_KAM_HTML_FONT_INVALID Test for Invalidly Named or Formatted Colors in HTML X-Headers-End: 1tAJj2-0001as-0S Subject: [Openvpn-devel] [L] Change in openvpn[master]: Implement methods to generate and manage OpenVPN Epoch 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: , Reply-To: arne-openvpn@rfc2549.org, openvpn-devel@lists.sourceforge.net, frank@lichtenheld.com Cc: openvpn-devel Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: =?utf-8?q?1815389599719043360?= X-GMAIL-MSGID: =?utf-8?q?1815389599719043360?= X-getmail-filter-classifier: gerrit message type newchange Attention is currently required from: flichtenheld. Hello flichtenheld, I'd like you to do a code review. Please visit http://gerrit.openvpn.net/c/openvpn/+/804?usp=email to review the following change. Change subject: Implement methods to generate and manage OpenVPN Epoch keys ...................................................................... Implement methods to generate and manage OpenVPN Epoch keys This implements functions that allow these keys to be generated and managed. It does not yet implement using them for the data channel. Change-Id: Id7d6a576ca8c9560cb2dfae82fc62175820e9b80 Signed-off-by: Arne Schwabe --- M src/openvpn/crypto.c M src/openvpn/crypto.h M src/openvpn/crypto_epoch.c M src/openvpn/crypto_epoch.h M src/openvpn/packet_id.c M src/openvpn/packet_id.h M src/openvpn/ssl_common.h M tests/unit_tests/openvpn/test_crypto.c 8 files changed, 776 insertions(+), 22 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/04/804/1 diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c index f4453d8..dc23ffc 100644 --- a/src/openvpn/crypto.c +++ b/src/openvpn/crypto.c @@ -909,14 +909,27 @@ if (cipher_ctx_mode_aead(ctx->cipher)) { size_t impl_iv_len = 0; + size_t impl_iv_offset = 0; ASSERT(cipher_ctx_iv_length(ctx->cipher) >= OPENVPN_AEAD_MIN_IV_LEN); - impl_iv_len = cipher_ctx_iv_length(ctx->cipher) - sizeof(packet_id_type); + + /* Epoch keys use XOR of full IV length with the packet id to generate + * IVs. Old data format uses concatenation instead (XOR with 0 for the + * first 4 bytes (sizeof(packet_id_type) */ + if (key->epoch) + { + impl_iv_len = cipher_ctx_iv_length(ctx->cipher); + impl_iv_offset = 0; + } + else + { + impl_iv_len = cipher_ctx_iv_length(ctx->cipher) - sizeof(packet_id_type); + impl_iv_offset = sizeof(packet_id_type); + } ASSERT(impl_iv_len <= OPENVPN_MAX_IV_LENGTH); ASSERT(impl_iv_len <= MAX_HMAC_KEY_LENGTH); ASSERT(key->hmac_size >= impl_iv_len); CLEAR(ctx->implicit_iv); - /* The first bytes of the IV are filled with the packet id */ - memcpy(ctx->implicit_iv + sizeof(packet_id_type), key->hmac, impl_iv_len); + memcpy(ctx->implicit_iv + impl_iv_offset, key->hmac, impl_iv_len); } } @@ -965,6 +978,7 @@ hmac_ctx_size(ctx->hmac)); } + ctx->epoch = key->epoch; gc_free(&gc); } @@ -977,6 +991,7 @@ snprintf(log_prefix, sizeof(log_prefix), "Outgoing %s", name); init_key_ctx(ctx, key_params, kt, OPENVPN_OP_ENCRYPT, log_prefix); key_ctx_update_implicit_iv(ctx, key_params); + ctx->epoch = key_params->epoch; } void @@ -988,6 +1003,7 @@ snprintf(log_prefix, sizeof(log_prefix), "Incoming %s", name); init_key_ctx(ctx, key_params, kt, OPENVPN_OP_DECRYPT, log_prefix); key_ctx_update_implicit_iv(ctx, key_params); + ctx->epoch = key_params->epoch; } void @@ -1025,6 +1041,7 @@ } CLEAR(ctx->implicit_iv); ctx->plaintext_blocks = 0; + ctx->epoch = 0; } void diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h index ff24f87..abba30c 100644 --- a/src/openvpn/crypto.h +++ b/src/openvpn/crypto.h @@ -170,6 +170,9 @@ /** Number of bytes set in the HMac key material */ int hmac_size; + + /** the epoch of the key is if it was generated as epoch data key material */ + uint16_t epoch; }; /** @@ -180,6 +183,11 @@ void key_parameters_from_key(struct key_parameters *key_params, const struct key *key); +struct epoch_key { + uint8_t epoch_key[SHA256_DIGEST_LENGTH]; + uint16_t epoch; +}; + /** * Container for one set of cipher and/or HMAC contexts. * @ingroup control_processor @@ -199,6 +207,10 @@ /** Counter for the number of plaintext encrypted using this cipher * in number of 128 bit blocks (only used for AEAD ciphers) */ uint64_t plaintext_blocks; + /** OpenVPN data channel epoch, this variable holds the + * epoch number that is key belongs to. Note that epoch 0 is not used + * and epoch is always non-zero for epoch key contexts */ + uint16_t epoch; }; #define KEY_DIRECTION_BIDIRECTIONAL 0 /* same keys for both directions */ @@ -268,6 +280,39 @@ /**< OpenSSL cipher and HMAC contexts for * both sending and receiving * directions. */ + + /** last epoch_key used for generation of the current send data keys. + * As invariant, the epoch of epoch_key_send is always kept >= the epoch of + * epoch_key_recv */ + struct epoch_key epoch_key_send; + + /** epoch_key used for the highest receive epoch keys */ + struct epoch_key epoch_key_recv; + + /** the key_type that is used to generate the epoch keys */ + struct key_type epoch_key_type; + + /** This limit for AEAD cipher, this is the sum of packets + blocks + * that are allowed to be used. Will switch to a new epoch if this + * limit is reached*/ + uint64_t aead_usage_limit; + + /** Keeps the future epoch data keys for decryption. The current one + * that is expected to be used is stored in key_ctx_bi. + * + * We keep both decrypt and encrypt key here in the future keys + * as we want to be able to switch also sending key if the peer + * switching to a newer key epoch + * */ + struct key_ctx *epoch_data_keys_future; + + /** number of keys stored in \c epoch_data_keys_future */ + uint16_t epoch_data_keys_future_count; + + /** The old key bevor the sender switch to a new epoch data key */ + struct key_ctx epoch_retiring_data_receive_key; + struct packet_id_rec epoch_retiring_key_pid_recv; + struct packet_id packet_id; /**< Current packet ID state for both * sending and receiving directions. * @@ -276,7 +321,7 @@ * * The packet id also used as the IV * for AEAD/OFB/CFG ciphers. - * */ + */ struct packet_id_persist *pid_persist; /**< Persistent packet ID state for * keeping state between successive @@ -464,13 +509,15 @@ * * @param opt Crypto options for this packet, contains replay state. * @param pin Packet ID read from packet. + * @param epoch Epoch read from packet or 0 when epoch is not used. * @param error_prefix Prefix to use when printing error messages. * @param gc Garbage collector to use. * * @return true if packet ID is validated to be not a replay, false otherwise. */ bool crypto_check_replay(struct crypto_options *opt, - const struct packet_id_net *pin, const char *error_prefix, + const struct packet_id_net *pin, + const char *error_prefix, struct gc_arena *gc); diff --git a/src/openvpn/crypto_epoch.c b/src/openvpn/crypto_epoch.c index 7a5b460..1f89804 100644 --- a/src/openvpn/crypto_epoch.c +++ b/src/openvpn/crypto_epoch.c @@ -21,7 +21,7 @@ * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ + */ #ifdef HAVE_CONFIG_H @@ -29,7 +29,13 @@ #endif #include +#include +#include + #include "crypto_backend.h" +#include "packet_id.h" +#include "crypto.h" +#include "crypto_epoch.h" #include "buffer.h" #include "integer.h" @@ -103,4 +109,333 @@ gc_free(&gc); return true; -} \ No newline at end of file +} + +/** + * Iterates the epoch key to make it E_n+1, ie increase the epoch by one + * and derive the new key material accordingly + * @param epoch_key epoch key to iterate + */ +static void +epoch_key_iterate(struct epoch_key *epoch_key) +{ + struct epoch_key new_epoch_key = { 0 }; + new_epoch_key.epoch = epoch_key->epoch + 1; + const uint8_t epoch_update_label[] = "datakey upd"; + + /* E_N+1 = OVPN-Expand-Label(E_N, "datakey upd", "", 32) */ + ovpn_expand_label(epoch_key->epoch_key, sizeof(epoch_key->epoch_key), + epoch_update_label, 11, + NULL, 0, + new_epoch_key.epoch_key, sizeof(new_epoch_key.epoch_key)); + *epoch_key = new_epoch_key; +} + +void +epoch_data_key_derive(struct key_parameters *key, + const struct epoch_key *epoch_key, + const struct key_type *kt) +{ + key->hmac_size = cipher_kt_iv_size(kt->cipher); + key->cipher_size = cipher_kt_key_size(kt->cipher); + + /* Generate data key from epoch key: + * K_i = OVPN-Expand-Label(E_i, "data_key", "", key_size) + * implicit_iv = OVPN-Expand-Label(E_i, "data_iv", "", implicit_iv_len) + */ + + const uint8_t epoch_data_key_label[] = "data_key"; + ovpn_expand_label(epoch_key->epoch_key, sizeof(epoch_key->epoch_key), + epoch_data_key_label, 8, + NULL, 0, + (uint8_t *)(&key->cipher), key->cipher_size); + + const uint8_t epoch_data_iv_label[] = "data_iv"; + ovpn_expand_label(epoch_key->epoch_key, sizeof(epoch_key->epoch_key), + epoch_data_iv_label, 7, + NULL, 0, + (uint8_t *)(&key->hmac), key->hmac_size); + key->epoch = epoch_key->epoch; +} + +static void +epoch_init_send_key_ctx(struct crypto_options *co) +{ + /* Ensure that we are NEVER regenerating the same key that has already + * been generated. Since we also reset the packet ID counter this would be + * catastrophic as we would do IV reuse which breaks ciphers like AES-GCM */ + ASSERT(co->key_ctx_bi.encrypt.epoch != co->epoch_key_send.epoch); + char name[32] = { 0 }; + snprintf(name, sizeof(name), "Epoch Data key %" PRIu16, co->epoch_key_send.epoch); + + struct key_parameters send_key = { 0 }; + + epoch_data_key_derive(&send_key, &co->epoch_key_send, &co->epoch_key_type); + + init_key_bi_ctx_send(&co->key_ctx_bi.encrypt, &send_key, + &co->epoch_key_type, name); + reset_packet_id_send(&co->packet_id.send); + CLEAR(send_key); +} + + +static void +epoch_init_recv_key(struct key_ctx *ctx, struct crypto_options *co) +{ + struct key_parameters recv_key = { 0 }; + epoch_data_key_derive(&recv_key, &co->epoch_key_recv, &co->epoch_key_type); + + char name[32]; + + snprintf(name, sizeof(name), "Epoch Data key %" PRIu16, co->epoch_key_recv.epoch); + + init_key_bi_ctx_recv(ctx, &recv_key, &co->epoch_key_type, name); + CLEAR(recv_key); +} + +void +epoch_generate_future_receive_keys(struct crypto_options *co) +{ + /* We want the number of receive keys starting with the currently used + * keys. */ + ASSERT(co->key_ctx_bi.initialized); + uint16_t current_epoch_recv = co->key_ctx_bi.decrypt.epoch; + + /* Either we have not generated any future keys yet or the last + * index is the same as our current epoch key */ + struct key_ctx *highest_future_key = &co->epoch_data_keys_future[co->epoch_data_keys_future_count - 1]; + + ASSERT(co->epoch_key_recv.epoch == 1 + || highest_future_key->epoch == co->epoch_key_recv.epoch); + + /* free the keys that are not used anymore */ + for (uint16_t i = 0; i < co->epoch_data_keys_future_count; i++) + { + /* Keys in future keys are always epoch > 1 if initialised */ + if (co->epoch_data_keys_future[i].epoch > 0 + && co->epoch_data_keys_future[i].epoch < current_epoch_recv) + { + /* Key is old, free it */ + free_key_ctx(&co->epoch_data_keys_future[i]); + } + } + + /* Calculate the number of keys that need to be generated, + * if no keys have been generated assume only the first key is defined */ + uint16_t current_highest_key = highest_future_key->epoch ? highest_future_key->epoch : 1; + uint16_t desired_highest_key = current_epoch_recv + co->epoch_data_keys_future_count; + uint16_t num_keys_generate = desired_highest_key - current_highest_key; + + + /* Move the old keys out of the way so the order of keys stays strictly + * monotonic and consecutive. */ + /* first check that the destination we are going to overwrite is freed */ + for (uint16_t i = 0; i < num_keys_generate; i++) + { + ASSERT(co->epoch_data_keys_future[i].epoch == 0); + } + memmove(co->epoch_data_keys_future, + co->epoch_data_keys_future + num_keys_generate, + (co->epoch_data_keys_future_count - num_keys_generate) * sizeof(struct key_ctx)); + + /* Clear and regenerate the array elements at the end */ + for (uint16_t i = co->epoch_data_keys_future_count - num_keys_generate; i < co->epoch_data_keys_future_count; i++) + { + CLEAR(co->epoch_data_keys_future[i]); + epoch_key_iterate(&co->epoch_key_recv); + + epoch_init_recv_key(&co->epoch_data_keys_future[i], co); + } + + /* Assert that all keys are initialised */ + for (uint16_t i = 0; i < co->epoch_data_keys_future_count; i++) + { + ASSERT(co->epoch_data_keys_future[i].epoch > 0); + } +} + +void +epoch_iterate_send_key(struct crypto_options *co) +{ + ASSERT(co->epoch_key_send.epoch < UINT16_MAX); + epoch_key_iterate(&co->epoch_key_send); + free_key_ctx(&co->key_ctx_bi.encrypt); + epoch_init_send_key_ctx(co); +} + +void +epoch_replace_update_recv_key(struct crypto_options *co, + uint16_t new_epoch) +{ + /* Find the key of the new epoch in future keys */ + uint16_t fki; + for (fki = 0; fki < co->epoch_data_keys_future_count; fki++) + { + if (co->epoch_data_keys_future[fki].epoch == new_epoch) + { + break; + } + } + /* we should only ever be called when we successfully decrypted/authenticated + * a packet from a peer, ie the epoch recv key *MUST* be in that + * array */ + ASSERT(fki < co->epoch_data_keys_future_count); + ASSERT(co->epoch_data_keys_future[fki].epoch == new_epoch); + + struct key_ctx *new_ctx = &co->epoch_data_keys_future[fki]; + + /* Check if the new recv key epoch is higher than the send key epoch. If + * yes we will replace the send key as well */ + if (co->key_ctx_bi.encrypt.epoch < new_epoch) + { + free_key_ctx(&co->key_ctx_bi.encrypt); + + /* Update the epoch_key for send to match the current key being used. + * This is a bit of extra work but since we are a maximum of 16 + * keys behind, a maximum 16 HMAC invocations are a small price to + * pay for not keeping all the old epoch keys around in future_keys + * array */ + while (co->epoch_key_send.epoch < new_epoch) + { + epoch_key_iterate(&co->epoch_key_send); + } + epoch_init_send_key_ctx(co); + } + + /* Replace receive key */ + free_key_ctx(&co->epoch_retiring_data_receive_key); + co->epoch_retiring_data_receive_key = co->key_ctx_bi.decrypt; + packet_id_move_recv(&co->epoch_retiring_key_pid_recv, &co->packet_id.rec); + + co->key_ctx_bi.decrypt = *new_ctx; + + /* Zero the old location instead to of free_key_ctx since we moved the keys + * and do not want to free the pointers in the old place */ + memset(new_ctx, 0, sizeof(struct key_ctx)); + + /* Generate new future keys */ + epoch_generate_future_receive_keys(co); +} + +void +free_epoch_key_ctx(struct crypto_options *co) +{ + for (uint16_t i = 0; i < co->epoch_data_keys_future_count; i++) + { + free_key_ctx(&co->epoch_data_keys_future[i]); + } + + free(co->epoch_data_keys_future); + free_key_ctx(&co->epoch_retiring_data_receive_key); + free(co->epoch_retiring_key_pid_recv.seq_list); + CLEAR(co->epoch_key_recv); + CLEAR(co->epoch_key_send); +} + +void +epoch_init_key_ctx(struct crypto_options *co, const struct key_type *key_type, + const struct epoch_key *e1_send, + const struct epoch_key *e1_recv, + uint16_t future_key_count) +{ + ASSERT(e1_send->epoch == 1 && e1_recv->epoch == 1); + co->epoch_key_recv = *e1_recv; + co->epoch_key_send = *e1_send; + + co->epoch_key_type = *key_type; + co->aead_usage_limit = cipher_get_aead_limits(key_type->cipher); + + epoch_init_send_key_ctx(co); + epoch_init_recv_key(&co->key_ctx_bi.decrypt, co); + co->key_ctx_bi.initialized = true; + + co->epoch_data_keys_future_count = future_key_count; + ALLOC_ARRAY_CLEAR(co->epoch_data_keys_future, struct key_ctx, co->epoch_data_keys_future_count); + epoch_generate_future_receive_keys(co); +} + +const struct key_ctx * +epoch_lookup_decrypt_key(struct crypto_options *opt, uint16_t epoch) +{ + /* Current decrypt key is the most likely one */ + if (opt->key_ctx_bi.decrypt.epoch == epoch) + { + return &opt->key_ctx_bi.decrypt; + } + else if (opt->epoch_retiring_data_receive_key.epoch + && opt->epoch_retiring_data_receive_key.epoch == epoch) + { + return &opt->epoch_retiring_data_receive_key; + } + else if (epoch > opt->key_ctx_bi.decrypt.epoch + && epoch <= opt->key_ctx_bi.decrypt.epoch + opt->epoch_data_keys_future_count) + { + /* Key in the range of future keys */ + int index = epoch - (opt->key_ctx_bi.decrypt.epoch + 1); + + /* If we have reached the edge of the valid keys we do not return + * the key anymore since regenerating the new keys would move us + * over the window of valid keys and would need all kind of + * special casing, so we stop returning the key in this case */ + if (epoch > (UINT16_MAX - opt->epoch_data_keys_future_count - 1)) + { + return NULL; + } + else + { + return &opt->epoch_data_keys_future[index]; + } + } + else + { + return NULL; + } +} + + +void +epoch_check_send_iterate(struct crypto_options *opt) +{ + if (opt->epoch_key_send.epoch == UINT16_MAX) + { + /* limit of epoch keys reached, cannot move to a newer key anymore */ + return; + } + if (opt->aead_usage_limit) + { + if (aead_usage_limit_reached(opt->aead_usage_limit, &opt->key_ctx_bi.encrypt, + opt->packet_id.send.id)) + { + /* Send key limit reached */ + epoch_iterate_send_key(opt); + } + /* draft 8 of the aead usage limit still had but draft 9 complete + * dropped this statement: + * + * In particular, is two-party communication, one participant cannot + * regard apparent overuse of a key by other participants as + * being in error, when it could be that the other participant has + * better information about bounds. + * + * OpenVPN 2.x implements a compromise of not regarding this as an + * error but still accepting packets of the usage limit but tries to + * push the peer to a new epoch key by increasing our own key epoch + * */ + else if (opt->key_ctx_bi.encrypt.epoch == opt->key_ctx_bi.decrypt.epoch + && aead_usage_limit_reached(opt->aead_usage_limit, + &opt->key_ctx_bi.decrypt, + opt->packet_id.rec.id)) + { + /* Receive key limit reached. Increase our own send key to signal + * that we want to use a new epoch. Peer should then also move its + * key but is not required to do this */ + epoch_iterate_send_key(opt); + } + } + + if (opt->packet_id.send.id == PACKET_ID_EPOCH_MAX) + { + epoch_iterate_send_key(opt); + } + +} diff --git a/src/openvpn/crypto_epoch.h b/src/openvpn/crypto_epoch.h index dad2473..95d8e9e 100644 --- a/src/openvpn/crypto_epoch.h +++ b/src/openvpn/crypto_epoch.h @@ -66,4 +66,81 @@ const uint8_t *context, size_t context_len, uint8_t *out, uint16_t out_len); -#endif +/** + * Generate a data channel key pair from the epoch key + * @param epoch_key Epoch key to be used + * @param key Destination for the generated data key + * @parm kt Cipher information to generate the data channel key for + */ +void +epoch_data_key_derive(struct key_parameters *key, + const struct epoch_key *epoch_key, + const struct key_type *kt); + +/** + * Generates and fills the epoch_data_keys_future with next valid + * future keys in crypto_options + * + * This assume that the normal key_ctx_bi and epoch keys are already setup + */ +void +epoch_generate_future_receive_keys(struct crypto_options *co); + + +/** This is called when the peer using a new send key that is not the default + * key. This function ensures the following: + * - recv key matches the epoch index provided + * - send key epoch is equal or higher than recv_key epoch + * + * @param new_epoch the new epoch to use a the receive key + */ +void +epoch_replace_update_recv_key(struct crypto_options *co, + uint16_t new_epoch); + +/** + * Updates the send key and send_epoch_keyt in cryptio_options->key_ctx_bi to + * use the next epoch */ +void +epoch_iterate_send_key(struct crypto_options *co); + +/** + * Frees the extra data structures used by epoch keys in \c crypto_options + */ +void +free_epoch_key_ctx(struct crypto_options *co); + +/** + * Initialises data channel keys and internal structures for epoch data keys + * using the provided E0 epoch key + * + * @param e1_send The E1 send epoch key derived by TLS-EKM + * @param e1_recv The E1 receive epoch key derived by TLS-EKM + */ +void +epoch_init_key_ctx(struct crypto_options *co, const struct key_type *key_type, + const struct epoch_key *e1_send, const struct epoch_key *e1_recv, + uint16_t future_key_count); + +/** + * Using an epoch, this function will try to retrieve a decryption + * key context that matches that epoch from the \c opt argument + * @param opt crypto_options to use to find the decrypt key + * @param epoch epoch of the key to lookup + * @return the key context with + */ +const struct key_ctx * +epoch_lookup_decrypt_key(struct crypto_options *opt, uint16_t epoch); + +/** + * Checks if we need to iterate the send epoch key. This needs to be in one + * of the following condition + * - max epoch counter reached + * - send key aead usage limit reached (for AES-GCM and similar ciphers) + * - recv key usage limit reached + */ +void +epoch_check_send_iterate(struct crypto_options *opt); + + +#endif /* ifndef CRYPTO_EPOCH_H */ diff --git a/src/openvpn/packet_id.c b/src/openvpn/packet_id.c index 8cde108..177391a 100644 --- a/src/openvpn/packet_id.c +++ b/src/openvpn/packet_id.c @@ -78,6 +78,21 @@ } void +packet_id_init_recv(struct packet_id_rec *rec, int seq_backtrack, int time_backtrack, const char *name, int unit) +{ + rec->name = name; + rec->unit = unit; + if (seq_backtrack) + { + ASSERT(MIN_SEQ_BACKTRACK <= seq_backtrack && seq_backtrack <= MAX_SEQ_BACKTRACK); + ASSERT(MIN_TIME_BACKTRACK <= time_backtrack && time_backtrack <= MAX_TIME_BACKTRACK); + CIRC_LIST_ALLOC(rec->seq_list, struct seq_list, seq_backtrack); + rec->seq_backtrack = seq_backtrack; + rec->time_backtrack = time_backtrack; + } + rec->initialized = true; +} +void packet_id_init(struct packet_id *p, int seq_backtrack, int time_backtrack, const char *name, int unit) { dmsg(D_PID_DEBUG, "PID packet_id_init seq_backtrack=%d time_backtrack=%d", @@ -87,17 +102,25 @@ ASSERT(p); CLEAR(*p); - p->rec.name = name; - p->rec.unit = unit; - if (seq_backtrack) - { - ASSERT(MIN_SEQ_BACKTRACK <= seq_backtrack && seq_backtrack <= MAX_SEQ_BACKTRACK); - ASSERT(MIN_TIME_BACKTRACK <= time_backtrack && time_backtrack <= MAX_TIME_BACKTRACK); - CIRC_LIST_ALLOC(p->rec.seq_list, struct seq_list, seq_backtrack); - p->rec.seq_backtrack = seq_backtrack; - p->rec.time_backtrack = time_backtrack; - } - p->rec.initialized = true; + packet_id_init_recv(&p->rec, seq_backtrack, time_backtrack, name, unit); +} + +void +packet_id_move_recv(struct packet_id_rec *dest, struct packet_id_rec *src) +{ + ASSERT(src); + ASSERT(dest); + /* clear free any old data in rec list */ + free(dest->seq_list); + CLEAR(*dest); + + /* Copy data to dest */ + *dest = *src; + + /* Reinitalise the source */ + CLEAR(*src); + packet_id_init_recv(src, dest->seq_backtrack, dest->time_backtrack, + dest->name, dest->unit); } void diff --git a/src/openvpn/packet_id.h b/src/openvpn/packet_id.h index 79224d2..7620ce5 100644 --- a/src/openvpn/packet_id.h +++ b/src/openvpn/packet_id.h @@ -203,6 +203,15 @@ void packet_id_free(struct packet_id *p); +/** + * Move the packet id recv structure from src to dest. src will will + * be reinitialised with + * @param dest + * @param src + */ +void +packet_id_move_recv(struct packet_id_rec *dest, struct packet_id_rec *src); + /* should we accept an incoming packet id ? */ bool packet_id_test(struct packet_id_rec *p, const struct packet_id_net *pin); diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index ce1cd9e..511127d 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -333,8 +333,8 @@ interval_t packet_timeout; int64_t renegotiate_bytes; int64_t renegotiate_packets; - /** This limit for AEAD cipher, this is the sum of packets + blocks - * that are allowed to be used */ + /** This limit for AEAD cipher when not running in epoch data key mode, + * this is the sum of packets + blocks that are allowed to be used */ int64_t aead_usage_limit; interval_t renegotiate_seconds; diff --git a/tests/unit_tests/openvpn/test_crypto.c b/tests/unit_tests/openvpn/test_crypto.c index c8d2a10..54a6caa 100644 --- a/tests/unit_tests/openvpn/test_crypto.c +++ b/tests/unit_tests/openvpn/test_crypto.c @@ -575,7 +575,7 @@ 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31, 0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5}; - const uint8_t *label = (const uint8_t *)("unit test"); + const uint8_t *label = (const uint8_t *) ("unit test"); uint8_t out[16]; ovpn_expand_label(secret, sizeof(secret), label, 9, NULL, 0, out, sizeof(out)); @@ -586,9 +586,238 @@ assert_memory_equal(out, out_expected, 16); } +struct epoch_test_state +{ + struct key_type kt; + struct gc_arena gc; + struct crypto_options co; +}; + +static int +crypto_test_epoch_setup(void **state) +{ + int *num_future_keys = (int *)*state; + struct epoch_test_state *data = calloc(1, sizeof(struct epoch_test_state)); + + data->gc = gc_new(); + + init_key_type(&data->kt, "AES-128-GCM", "none", true, false); + + /* have an epoch key that is uses 0x23 for the key for all bytes */ + struct epoch_key epoch1send = { .epoch = 1, .epoch_key = {0x23} }; + struct epoch_key epoch1recv = { .epoch = 1, .epoch_key = {0x27} }; + + epoch_init_key_ctx(&data->co, &data->kt, &epoch1send, + &epoch1recv, *num_future_keys); + + *state = data; + return 0; +} + +static int +crypto_test_epoch_teardown(void **state) +{ + struct epoch_test_state *data = *state; + free_epoch_key_ctx(&data->co); + free_key_ctx_bi(&data->co.key_ctx_bi); + gc_free(&data->gc); + free(*state); + return 0; +} + +void +crypto_test_epoch_key_generation(void **state) +{ + struct epoch_test_state *data = *state; + struct crypto_options *co = &data->co; + + /* check the keys look like expect */ + assert_int_equal(co->epoch_data_keys_future[0].epoch, 2); + assert_int_equal(co->epoch_data_keys_future[15].epoch, 17); + assert_int_equal(co->epoch_key_send.epoch, 1); + assert_int_equal(co->epoch_key_recv.epoch, 17); + + /* Now replace the recv key with the 6th future key (epoch = 8) */ + free_key_ctx(&co->key_ctx_bi.decrypt); + assert_int_equal(co->epoch_data_keys_future[6].epoch, 8); + co->key_ctx_bi.decrypt = co->epoch_data_keys_future[6]; + CLEAR(co->epoch_data_keys_future[6]); + + epoch_generate_future_receive_keys(co); + assert_int_equal(co->epoch_data_keys_future[0].epoch, 9); + assert_int_equal(co->epoch_data_keys_future[15].epoch, 24); +} + + +void +crypto_test_epoch_key_rotation(void **state) +{ + struct epoch_test_state *data = *state; + struct crypto_options *co = &data->co; + + /* should replace send + key recv */ + epoch_replace_update_recv_key(co, 9); + + assert_int_equal(co->key_ctx_bi.decrypt.epoch, 9); + assert_int_equal(co->key_ctx_bi.encrypt.epoch, 9); + assert_int_equal(co->epoch_key_send.epoch, 9); + assert_int_equal(co->epoch_retiring_data_receive_key.epoch, 1); + + /* Iterate the data send key four times to get it to 13 */ + for (int i = 0; i < 4; i++) + { + epoch_iterate_send_key(co); + } + assert_int_equal(co->key_ctx_bi.encrypt.epoch, 13); + + epoch_replace_update_recv_key(co, 10); + assert_int_equal(co->key_ctx_bi.decrypt.epoch, 10); + assert_int_equal(co->key_ctx_bi.encrypt.epoch, 13); + assert_int_equal(co->epoch_key_send.epoch, 13); + assert_int_equal(co->epoch_retiring_data_receive_key.epoch, 9); + + epoch_replace_update_recv_key(co, 12); + assert_int_equal(co->key_ctx_bi.decrypt.epoch, 12); + assert_int_equal(co->key_ctx_bi.encrypt.epoch, 13); + assert_int_equal(co->epoch_key_send.epoch, 13); + assert_int_equal(co->epoch_retiring_data_receive_key.epoch, 10); + + epoch_iterate_send_key(co); + assert_int_equal(co->key_ctx_bi.encrypt.epoch, 14); +} + +void +crypto_test_epoch_key_receive_lookup(void **state) +{ + struct epoch_test_state *data = *state; + struct crypto_options *co = &data->co; + + /* lookup some wacky things that should fail */ + assert_null(epoch_lookup_decrypt_key(co, 2000)); + assert_null(epoch_lookup_decrypt_key(co, -1)); + assert_null(epoch_lookup_decrypt_key(co, 0xefff)); + + /* Lookup the edges of the current window */ + assert_null(epoch_lookup_decrypt_key(co, 0)); + assert_int_equal(epoch_lookup_decrypt_key(co, 1)->epoch, 1); + assert_int_equal(epoch_lookup_decrypt_key(co, 2)->epoch, 2); + assert_int_equal(epoch_lookup_decrypt_key(co, 13)->epoch, 13); + assert_int_equal(epoch_lookup_decrypt_key(co, 14)->epoch, 14); + assert_null(epoch_lookup_decrypt_key(co, 15)); + + /* Should move 1 to retiring key but leave 1-5 undefined, 7 as + * active and 8-20 as future keys*/ + epoch_replace_update_recv_key(co, 7); + + assert_null(epoch_lookup_decrypt_key(co, 0)); + assert_int_equal(epoch_lookup_decrypt_key(co, 1)->epoch, 1); + assert_null(epoch_lookup_decrypt_key(co, 2)); + assert_null(epoch_lookup_decrypt_key(co, 3)); + assert_null(epoch_lookup_decrypt_key(co, 4)); + assert_null(epoch_lookup_decrypt_key(co, 5)); + assert_null(epoch_lookup_decrypt_key(co, 6)); + assert_int_equal(epoch_lookup_decrypt_key(co, 7)->epoch, 7); + assert_int_equal(epoch_lookup_decrypt_key(co, 8)->epoch, 8); + assert_int_equal(epoch_lookup_decrypt_key(co, 20)->epoch, 20); + assert_null(epoch_lookup_decrypt_key(co, 21)); + assert_null(epoch_lookup_decrypt_key(co, 22)); + + + /* Should move 7 to retiring key and have 8 as active key and + * 9-21 as future keys */ + epoch_replace_update_recv_key(co, 8); + assert_null(epoch_lookup_decrypt_key(co, 0)); + assert_null(epoch_lookup_decrypt_key(co, 1)); + assert_null(epoch_lookup_decrypt_key(co, 2)); + assert_null(epoch_lookup_decrypt_key(co, 3)); + assert_null(epoch_lookup_decrypt_key(co, 4)); + assert_null(epoch_lookup_decrypt_key(co, 5)); + assert_null(epoch_lookup_decrypt_key(co, 6)); + assert_int_equal(epoch_lookup_decrypt_key(co, 7)->epoch, 7); + assert_int_equal(epoch_lookup_decrypt_key(co, 8)->epoch, 8); + assert_int_equal(epoch_lookup_decrypt_key(co, 20)->epoch, 20); + assert_int_equal(epoch_lookup_decrypt_key(co, 21)->epoch, 21); + assert_null(epoch_lookup_decrypt_key(co, 22)); + assert_null(epoch_lookup_decrypt_key(co, 23)); +} + +void +crypto_test_epoch_key_overflow(void **state) +{ + struct epoch_test_state *data = *state; + struct crypto_options *co = &data->co; + + /* Modify the receive epoch and keys to have a very high epoch to test + * the end of array. Iterating through all 16k keys takes a 2-3s, so we + * avoid this for the unit test */ + co->key_ctx_bi.decrypt.epoch = 16000; + co->key_ctx_bi.encrypt.epoch = 16000; + + co->epoch_key_send.epoch = 16000; + co->epoch_key_recv.epoch = 16000 + co->epoch_data_keys_future_count; + + for (uint16_t i = 0; i < co->epoch_data_keys_future_count; i++) + { + co->epoch_data_keys_future[i].epoch = 16001 + i; + } + + /* Move the last few keys until we are close to the limit */ + while (co->key_ctx_bi.decrypt.epoch < (UINT16_MAX - 40)) + { + epoch_replace_update_recv_key(co, co->key_ctx_bi.decrypt.epoch + 10); + } + + /* Looking up this key should still work as it will not break the limit + * when generating keys */ + assert_int_equal(epoch_lookup_decrypt_key(co, UINT16_MAX - 34)->epoch, UINT16_MAX - 34); + assert_int_equal(epoch_lookup_decrypt_key(co, UINT16_MAX - 33)->epoch, UINT16_MAX - 33); + + /* This key is no longer eligible for decrypting as the 32 future keys + * would be larger than uint16_t maximum */ + assert_null(epoch_lookup_decrypt_key(co, UINT16_MAX - 32)); + assert_null(epoch_lookup_decrypt_key(co, UINT16_MAX)); + + /* Check that moving to the last possible epoch works */ + epoch_replace_update_recv_key(co, UINT16_MAX - 33); + assert_int_equal(epoch_lookup_decrypt_key(co, UINT16_MAX - 33)->epoch, UINT16_MAX - 33); + assert_null(epoch_lookup_decrypt_key(co, UINT16_MAX - 32)); + assert_null(epoch_lookup_decrypt_key(co, UINT16_MAX)); +} + +void +epoch_test_derive_data_key(void **state) +{ + struct epoch_key e17 = { .epoch = 17, .epoch_key = { 19, 12 }}; + struct key_type kt = { 0 }; + struct key_parameters key_parameters = { 0 }; + init_key_type(&kt, "AES-192-GCM", "none", true, false); + + + epoch_data_key_derive(&key_parameters, &e17, &kt); + + assert_int_equal(key_parameters.cipher_size, 24); + assert_int_equal(key_parameters.hmac_size, 12); + + uint8_t exp_cipherkey[24] = + {0x00, 0x62, 0x84, 0xcb, 0x31, 0x57, 0xc7, 0x97, + 0x8d, 0xe8, 0xfb, 0x6e, 0xdc, 0x60, 0x38, 0xc3, + 0xa4, 0xb9, 0xa1, 0xea, 0xf4, 0x01, 0x86, 0xbc }; + + uint8_t exp_impl_iv[12] = + { 0xb4, 0x32, 0xeb, 0x4e, 0x61, 0x4b, 0xa2, 0xf3, + 0x5d, 0x86, 0x22, 0x1f }; + + assert_memory_equal(key_parameters.cipher, exp_cipherkey, sizeof(exp_cipherkey)); + assert_memory_equal(key_parameters.hmac, exp_impl_iv, sizeof(exp_impl_iv)); +} + int main(void) { + int prestate_num13 = 13; + int prestate_num16 = 16; + int prestate_num32 = 32; + openvpn_unit_test_setup(); const struct CMUnitTest tests[] = { cmocka_unit_test(crypto_pem_encode_decode_loopback), @@ -602,6 +831,23 @@ cmocka_unit_test(crypto_test_hkdf_expand_testa2), cmocka_unit_test(crypto_test_hkdf_expand_testa3), cmocka_unit_test(crypto_test_ovpn_label_expand), + cmocka_unit_test_prestate_setup_teardown(crypto_test_epoch_key_generation, + crypto_test_epoch_setup, + crypto_test_epoch_teardown, + &prestate_num16), + cmocka_unit_test_prestate_setup_teardown(crypto_test_epoch_key_rotation, + crypto_test_epoch_setup, + crypto_test_epoch_teardown, + &prestate_num13), + cmocka_unit_test_prestate_setup_teardown(crypto_test_epoch_key_receive_lookup, + crypto_test_epoch_setup, + crypto_test_epoch_teardown, + &prestate_num13), + cmocka_unit_test_prestate_setup_teardown(crypto_test_epoch_key_overflow, + crypto_test_epoch_setup, + crypto_test_epoch_teardown, + &prestate_num32), + cmocka_unit_test(epoch_test_derive_data_key) }; #if defined(ENABLE_CRYPTO_OPENSSL)