From 421c9520fd7584d992a0fd0de8465a818eabdeb4 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 12 Jul 2023 10:51:24 -0400 Subject: [PATCH 01/91] transferred to new repo --- .gitignore | 15 + Cargo.toml | 21 + README.md | 21 + rustfmt.toml | 8 + src/applicationlayer.rs | 253 ++ src/crypto/Cargo.toml | 30 + src/crypto/LICENSE | 15 + src/crypto/README.md | 7 + src/crypto/build.rs | 106 + src/crypto/src/aes_fruity.rs | 257 ++ src/crypto/src/aes_gmac_siv_fruity.rs | 472 +++ src/crypto/src/aes_gmac_siv_openssl.rs | 246 ++ src/crypto/src/aes_openssl.rs | 122 + src/crypto/src/aes_tests.rs | 3677 ++++++++++++++++++++++++ src/crypto/src/cipher_ctx.rs | 172 ++ src/crypto/src/constant.rs | 4 + src/crypto/src/error.rs | 348 +++ src/crypto/src/hash.rs | 294 ++ src/crypto/src/lib.rs | 59 + src/crypto/src/mimcvdf.rs | 141 + src/crypto/src/p384.rs | 406 +++ src/crypto/src/p384_builtin.rs | 1098 +++++++ src/crypto/src/poly1305.rs | 48 + src/crypto/src/random.rs | 177 ++ src/crypto/src/salsa.rs | 267 ++ src/crypto/src/secret.rs | 131 + src/crypto/src/typestate.rs | 223 ++ src/crypto/src/x25519.rs | 171 ++ src/error.rs | 106 + src/frag_cache.rs | 306 ++ src/fragged.rs | 136 + src/handshake_cache.rs | 101 + src/lib.rs | 22 + src/log_event.rs | 74 + src/main.rs | 333 +++ src/proto.rs | 293 ++ src/symmetric_state.rs | 152 + src/utils/Cargo.toml | 22 + src/utils/rustfmt.toml | 1 + src/utils/src/arc_pool.rs | 769 +++++ src/utils/src/arrayvec.rs | 369 +++ src/utils/src/base24.rs | 131 + src/utils/src/base62.rs | 187 ++ src/utils/src/blob.rs | 150 + src/utils/src/buffer.rs | 752 +++++ src/utils/src/canonicalarc.rs | 80 + src/utils/src/cast.rs | 36 + src/utils/src/defer.rs | 25 + src/utils/src/dictionary.rs | 209 ++ src/utils/src/error.rs | 61 + src/utils/src/exitcode.rs | 22 + src/utils/src/flatsortedmap.rs | 86 + src/utils/src/gate.rs | 35 + src/utils/src/hex.rs | 124 + src/utils/src/indexed_heap.rs | 210 ++ src/utils/src/io.rs | 48 + src/utils/src/json.rs | 202 ++ src/utils/src/lib.rs | 114 + src/utils/src/marshalable.rs | 169 ++ src/utils/src/memory.rs | 121 + src/utils/src/pool.rs | 251 ++ src/utils/src/reaper.rs | 57 + src/utils/src/ringbuffer.rs | 120 + src/utils/src/rwu_lock.rs | 63 + src/utils/src/str.rs | 56 + src/utils/src/sync.rs | 47 + src/utils/src/varint.rs | 118 + src/zssp.rs | 2603 +++++++++++++++++ 68 files changed, 17550 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 rustfmt.toml create mode 100644 src/applicationlayer.rs create mode 100644 src/crypto/Cargo.toml create mode 100644 src/crypto/LICENSE create mode 100644 src/crypto/README.md create mode 100644 src/crypto/build.rs create mode 100644 src/crypto/src/aes_fruity.rs create mode 100644 src/crypto/src/aes_gmac_siv_fruity.rs create mode 100644 src/crypto/src/aes_gmac_siv_openssl.rs create mode 100644 src/crypto/src/aes_openssl.rs create mode 100644 src/crypto/src/aes_tests.rs create mode 100644 src/crypto/src/cipher_ctx.rs create mode 100644 src/crypto/src/constant.rs create mode 100644 src/crypto/src/error.rs create mode 100644 src/crypto/src/hash.rs create mode 100644 src/crypto/src/lib.rs create mode 100644 src/crypto/src/mimcvdf.rs create mode 100644 src/crypto/src/p384.rs create mode 100644 src/crypto/src/p384_builtin.rs create mode 100644 src/crypto/src/poly1305.rs create mode 100644 src/crypto/src/random.rs create mode 100644 src/crypto/src/salsa.rs create mode 100644 src/crypto/src/secret.rs create mode 100644 src/crypto/src/typestate.rs create mode 100644 src/crypto/src/x25519.rs create mode 100644 src/error.rs create mode 100644 src/frag_cache.rs create mode 100644 src/fragged.rs create mode 100644 src/handshake_cache.rs create mode 100644 src/lib.rs create mode 100644 src/log_event.rs create mode 100644 src/main.rs create mode 100644 src/proto.rs create mode 100644 src/symmetric_state.rs create mode 100644 src/utils/Cargo.toml create mode 120000 src/utils/rustfmt.toml create mode 100644 src/utils/src/arc_pool.rs create mode 100644 src/utils/src/arrayvec.rs create mode 100644 src/utils/src/base24.rs create mode 100644 src/utils/src/base62.rs create mode 100644 src/utils/src/blob.rs create mode 100644 src/utils/src/buffer.rs create mode 100644 src/utils/src/canonicalarc.rs create mode 100644 src/utils/src/cast.rs create mode 100644 src/utils/src/defer.rs create mode 100644 src/utils/src/dictionary.rs create mode 100644 src/utils/src/error.rs create mode 100644 src/utils/src/exitcode.rs create mode 100644 src/utils/src/flatsortedmap.rs create mode 100644 src/utils/src/gate.rs create mode 100644 src/utils/src/hex.rs create mode 100644 src/utils/src/indexed_heap.rs create mode 100644 src/utils/src/io.rs create mode 100644 src/utils/src/json.rs create mode 100644 src/utils/src/lib.rs create mode 100644 src/utils/src/marshalable.rs create mode 100644 src/utils/src/memory.rs create mode 100644 src/utils/src/pool.rs create mode 100644 src/utils/src/reaper.rs create mode 100644 src/utils/src/ringbuffer.rs create mode 100644 src/utils/src/rwu_lock.rs create mode 100644 src/utils/src/str.rs create mode 100644 src/utils/src/sync.rs create mode 100644 src/utils/src/varint.rs create mode 100644 src/zssp.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7dd2bd4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +/target +/**/target +/**/Cargo.lock + +.DS_* +.Icon* +._* +*.o +*.so +*.dylib +*.dSYM +*.a +/.idea +/.nova +*.secret diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..742fa91 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[package] +authors = ["ZeroTier, Inc. ", "Adam Ierymenko "] +edition = "2021" +license = "MPL-2.0" +name = "zssp" +version = "0.1.0" + + +[lib] +name = "zssp" +path = "src/lib.rs" +doc = true + +[[bin]] +name = "zssp_test" +path = "src/main.rs" +doc = false + +[dependencies] +pqc_kyber = { version = "0.4.0", default-features = false, features = ["kyber1024", "std"] } +hex-literal = "0.3.4" diff --git a/README.md b/README.md new file mode 100644 index 0000000..bdb94cd --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +ZeroTier Secure Socket Protocol +====== + +# Introduction + +ZeroTier Secure Socket Protocol (ZSSP) is a [Noise](http://noiseprotocol.org) protocol implementation using NIST/FIPS/CfSC compliant cryptographic primitives plus post-quantum forward secrecy via [Kyber1024](https://pq-crystals.org/kyber/). It also includes built-in support for fragmentation and defragmentation of large messages with strong resistance against denial of service attacks targeted against the fragmentation protocol. + +Specifically ZSSP implements the [Noise XK](http://noiseprotocol.org/noise.html#interactive-handshake-patterns-fundamental) interactive handshake pattern which provides strong forward secrecy not only for data but for the identities of the two participants in the sesssion. The XK pattern was chosen instead of the more popular IK pattern used in popular Noise implementations like Wireguard due to ZeroTier identities being long lived and potentially tied to the real world identity of the user. As a result a Noise pattern providing identity forward secrecy was considered preferable as it offers some level of deniability for recorded traffic even after secrec key compromise. + +Hybrid post-quantum forward secrecy using Kyber1024 is performed alongside Noise with the result being mixed in alongside an optional pre-shared key at the end of session negotiation. + +ZSSP is designed for use in ZeroTier 2 but is payload-agnostic and could easily be adapted for use in other projects. + +## Cryptographic Primitives Used + + - AES-256-GCM: Authenticated encryption + - HMAC-SHA384: Key mixing, sub-key derivation in key-based KDF construction + - NIST P-384 ECDH: Elliptic curve key exchange during initial handshake and for periodic re-keying during the session + - Kyber1024: Quantum attack resistant lattice-based key exchange during initial handshake + - AES-256-ECB: Single 128-bit block encryption of header information to harden the fragmentation protocol against denial of service attack (see section on header protection) + diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..3a3929c --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,8 @@ +max_width = 150 +edition = "2021" +newline_style = "Unix" +struct_lit_width = 60 +tab_spaces = 4 +use_small_heuristics = "Default" +single_line_if_else_max_width = 0 +use_try_shorthand = true diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs new file mode 100644 index 0000000..3858423 --- /dev/null +++ b/src/applicationlayer.rs @@ -0,0 +1,253 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::sync::Arc; + +use zerotier_crypto::p384::{P384KeyPair, P384PublicKey}; + +use crate::{log_event::LogEvent, Session, RATCHET_FINGERPRINT_SIZE, RATCHET_KEY_SIZE}; + +/// Trait to implement to integrate the session into an application. +/// +/// Templating the session on this trait lets the code here be almost entirely transport, OS, +/// and use case independent. +/// +/// The constants exposed in this trait can be redefined from their defaults to change rekey +/// and negotiation timeout behavior. Both sides of a ZSSP session **must** have these constants +/// set to the same values. Changing these constants is generally discouraged unless you know +/// what you are doing. +pub trait ApplicationLayer: Sized { + /// Retry interval for outgoing connection initiation or rekey attempts. + /// + /// Retry attempts will be no more often than this, but the delay may end up being + /// slightly more in some cases depending on where in the cycle the initial attempt + /// falls. + /// + /// Default value is 1 second. + const RETRY_INTERVAL_MS: i64 = 1000; + /// Timeout for how long Alice should wait for Bob to confirm that the Noise_XK handshake + /// was completed successfully. The handshake attempt will be assumed as failed and + /// restarted if Bob does not respond by this cut-off. + /// + /// Default is 10 seconds. + const INITIAL_OFFER_TIMEOUT_MS: i64 = 10 * 1000; + /// Timeout for how long ZSSP should wait before expiring and closing a session when it has + /// lingered in certain states for too long, primarily the rekeying states. + /// If a remote peer does not send the correct information to rekey a session before this + /// timeout then the session will close. + /// + /// Default is 1 minute. + const EXPIRATION_TIMEOUT_MS: i64 = 60 * 1000; + /// Start attempting to rekey after a key has been in use for this many milliseconds. + /// + /// Default is 1 hour. + const REKEY_AFTER_TIME_MS: i64 = 1000 * 60 * 60; + /// Maximum random jitter to subtract from the rekey after time timer. + /// Must be greater than 0 and less than u32::MAX. + /// This prevents rekeying from occurring predictably on the hour, so traffic analysis is harder. + /// + /// Default is 10 minutes. + const REKEY_AFTER_TIME_MAX_JITTER_MS: i64 = 1000 * 60 * 10; + /// Rekey after this many key uses. + /// + /// The default is 1/4 the recommended NIST limit for AES-GCM. Unless you are transferring + /// a massive amount of data REKEY_AFTER_TIME_MS is probably going to kick in first. + const REKEY_AFTER_USES: u64 = 1073741824; + + /// Hard expiration of a key after this many uses. + /// + /// Attempting to encrypt more than this many messages with a key will cause a hard error + /// and prevent all encryption. + /// This should basically never occur in practice because of rekeying. + /// + /// Default value is 2^32 - 1, one less than NIST's recommended limit. + /// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf + const EXPIRE_AFTER_USES: u64 = 4294967295; + + /// Determines how computationally difficult the proof of work is when Bob challenges Alice. + /// It is extremely computationally expensive on Bob to process Alice's initiation packet. So + /// Bob has the option to challenge Alice to prove ownership of address and to prove work before + /// they attempt process Alice's initiation packet. + /// The amount of computational work Alice has to prove increases exponentially with this value. + /// + /// This value must be between 0 and 32 (inclusive). + /// + /// Default is 13, which, on a modern processor, ensures Alice will have to do about as much + /// computational work as Bob will when they process Alice's initiation packet. + const PROOF_OF_WORK_BIT_DIFFICULTY: u32 = 13; + + /// Type for arbitrary opaque object for use by the application that is attached to + /// each session. + type Data; + + /// Data type for incoming packet buffers. + /// + /// This can be something like `Vec` or `Box<[u8]>` or it can be something like a pooled + /// reusable buffer that automatically returns to its pool when ZSSP is done with it. ZSSP may + /// hold these for a short period of time when assembling fragmented packets on the receive + /// path. + type IncomingPacketBuffer: AsRef<[u8]> + AsMut<[u8]>; + /// Data type for giving ZSSP temporary ownership of a buffer containing the local party's + /// identity. + /// It will be dropped as soon as the session is established. + type LocalIdentityBlob: AsRef<[u8]>; + + /// Get this node's static key in serialized form and the P-384 static key it contains. + fn local_s_keypair(&self) -> &P384KeyPair; + /// Save the given ratchet state to persistent storage. + /// A ratchet state consists of a ratchet number, a ratchet fingerprint, and a ratchet key. + /// + /// Ratchet states are identified by their ratchet number, the latest ratchet state with a + /// specific ratchet number should overwrite any previous ratchet state with the same number. + /// Only the `ratchet_number` and `ratchet_number - 1` ratchet states should be saved to persistent + /// storage, when a new one is saved the `ratchet_number - 2` ratchet state should be deleted. + /// + /// The `last_confirmed_ratchet_number` specifies which of the two saved ratchet states should + /// be used if this local peer needs to re-open this session (i.e. after a system restart). + /// This number should also be saved to persistent storage. + /// + /// A ratchet fingerprint is a 32 byte string unique for each ratchet key, it should be + /// possible to quickly look up the `ratchet_key` from just its `ratchet_fingerprint`. + /// + /// If this returns `Err(())`, the packet which triggered this function to be called will be + /// dropped, and no session state will be mutated, preserving synchronization. The remote peer + /// will eventually resend that packet and so this function will be called again. + /// + /// If persistent storage is supported, this function should not return until the ratchet state + /// is saved, otherwise it is possible, albeit unlikely, for a sudden restart of the local + /// machine to put our ratchet state out of sync with the remote peer. If this happens the only + /// fix is to restart the entire ratchet chain from zero. + /// + /// This function may also save state to volatile storage, or potentially not even save it at + /// all, in which case all peers which connect to us will always have to allow us to downgrade + /// the ratchet chain to zero. Otherwise they might not consider us to be "authentic". + #[allow(unused)] + fn save_ratchet_state( + &self, + alice_s_public: &P384PublicKey, + application_data: &Self::Data, + ratchet_action: SaveRatchetAction, + latest_ratchet_number: u64, + latest_ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], + latest_ratchet_key: &[u8; RATCHET_KEY_SIZE], + current_time: i64, + ) -> Result<(), ()> { + Ok(()) + } + /// This function is called if we, as Alice, attempted to open a session with Bob using a + /// non-zero ratchet key, but Bob does not have this ratchet key and wants to downgrade + /// to the zero ratchet key. + /// + /// If it returns true Alice will downgrade their ratchet number to 0, potentially ending their + /// current ratchet chain. + /// If it returns false then we will consider Bob as having failed authentication, and this + /// packet will be dropped. The session will continue attempting to connect to Bob. + /// + /// This function must deterministically return either true or false for a given session. + /// + /// It is a bad sign that Bob has somehow forgotten Alice's ratchet key, it either means at + /// least one party is misconfigured and got their ratchet keys corrupted or lost, or Bob has + /// been compromised and is being impersonated. An attacker must at least have Bob's private + /// static key to be able to ask Alice to downgrade. + /// + /// If Alice does decide to reconnect without a ratchet key, be sure to generate some warning + /// that something has gone wrong and Bob could not be fully authenticated. + #[allow(unused)] + fn allow_downgrade(&self, session: &Arc>, current_time: i64) -> bool { + true + } + /// Lookup a specific ratchet key based on its ratchet fingerprint. + /// This function will be called whenever Alice attempts to connect to us with a non-zero + /// ratchet key. + /// + /// If the ratchet key was found, the function should return `RatchetAction::Found`. This will + /// cause us to connect to Alice using the returned ratchet number and ratchet key. + /// We don't know Alice's static identity at this point in the handshake, so a + /// `RemotePeerIdentifier` must be returned, so when Alice does send their identity we + /// can verify it matches what we expect. + /// + /// If the ratchet key could not be found, the application may choose between returning + /// `RatchetAction::Downgrade` or `RatchetAction::Ignore`. + /// If `RatchetAction::Downgrade` is returned we will attempt to convince Alice to downgrade + /// to the zero ratchet key, restarting the ratchet chain. + /// If `RatchetAction::Ignore` is returned Alice's connection will be silently dropped. + #[allow(unused)] + fn lookup_ratchet(&self, ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], current_time: i64) -> Result { + Ok(GetRatchetAction::Downgrade) + } + /// This function will be called whenever Alice's initial Hello packet contains the zero ratchet + /// key. Brand new peers will always connect to Bob with the zero ratchet key, but from then on + /// they should be using non-zero ratchet keys. + /// + /// If this returns true, we will attempt to connect to Alice with the zero ratchet key. + /// If this returns false, Alice's connection will be silently dropped. + /// If this function is configured to always return false, it means peers will not be able to + /// connect to us unless they had a prior-established ratchet key with us. This is the best way + /// for the paranoid to enforce a manual allow-list. + #[allow(unused)] + fn allow_zero_ratchet(&self, current_time: i64) -> bool { + true + } + #[allow(unused)] + #[inline] + fn event_log<'a>(&self, event: LogEvent<'a, Self>, current_time: i64) {} +} +pub enum GetRatchetAction { + Found(u64, [u8; RATCHET_KEY_SIZE]), + Downgrade, + Ignore, +} +/// Only 2 ratchet states may be saved at one time. +/// If a 3rd ratchet state needs to be saved the 1st should be deleted, if it was not already deleted. +/// The "previous ratchet state" may be the zero ratchet state. +pub enum SaveRatchetAction { + /// Save the given new ratchet state and set the previous saved ratchet state as + /// the confirmed ratchet state, if it was not already. + /// If there are currently two saved states delete the oldest state and replace it with this one. + SaveAsUnconfirmed, + /// Save the given new ratchet state and set it as the confirmed ratchet state. Keep the previous + /// ratchet state saved and searchable until it is explicitly deleted. + /// If there are currently two saved states delete the oldest state and replace it with this one. + SaveAsConfirmed, + /// The given ratchet state will be identical to that saved during a previous call to + /// `SaveAsUnconfirmedAndConfirmPrevious`. Set the given ratchet state as the + /// confirmed ratchet state and permanently delete the previous ratchet state. + ConfirmLatestAndDeletePrevious, + /// The given ratchet state will be identical to that saved during a previous call to + /// `SaveAsConfirmed`. Permanently delete the previous ratchet state, as in delete the ratchet + /// state with ratchet number one less than the given ratchet state. + DeletePrevious, +} +use SaveRatchetAction::*; +impl SaveRatchetAction { + /// If this is true then this is the first time the latest ratchet state has ever been seen, + /// so it ought to be immediately saved. + pub fn save_latest(&self) -> bool { + match self { + SaveAsUnconfirmed => true, + SaveAsConfirmed => true, + _ => false, + } + } + /// If this is true then only the latest ratchet state should be saved. + /// Any previous ratchet states should be deleted now. + pub fn delete_previous(&self) -> bool { + match self { + ConfirmLatestAndDeletePrevious => true, + DeletePrevious => true, + _ => false, + } + } + pub fn confirm_latest(&self) -> bool { + match self { + ConfirmLatestAndDeletePrevious => true, + SaveAsConfirmed => true, + _ => false, + } + } +} diff --git a/src/crypto/Cargo.toml b/src/crypto/Cargo.toml new file mode 100644 index 0000000..9a7e18a --- /dev/null +++ b/src/crypto/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "zerotier-crypto" +authors = ["Steven Fackler "] +license = "Apache-2.0" +description = "OpenSSL bindings" +readme = "README.md" +keywords = ["crypto", "tls", "ssl", "dtls"] +categories = ["cryptography", "api-bindings"] +edition = "2021" +version = "0.1.0" + + +[dependencies] +ed25519-dalek = { version = "1.0.1", features = ["std", "u64_backend"], default-features = false } +poly1305 = { version = "0.8.0", features = [], default-features = false } +x25519-dalek = { version = "1.2.0", features = ["std", "u64_backend"], default-features = false } +cfg-if = "1.0" +foreign-types = "0.5.0" +libc = "0.2" +lazy_static = "^1" +rand_core = "0.6.4" +ctor = "^0" +#ed25519-dalek still uses rand_core 0.5.1, and that version is incompatible with 0.6.4, so we need to import and implement both. +rand_core_051 = { package = "rand_core", version = "0.5.1" } + +ffi = { package = "openssl-sys", version = "0.9.80", path = "../openssl-sys" } + +[dev-dependencies] +hex = "0.4.3" +hex-literal = "0.3.4" diff --git a/src/crypto/LICENSE b/src/crypto/LICENSE new file mode 100644 index 0000000..f259067 --- /dev/null +++ b/src/crypto/LICENSE @@ -0,0 +1,15 @@ +Copyright 2011-2017 Google Inc. + 2013 Jack Lloyd + 2013-2014 Steven Fackler + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/src/crypto/README.md b/src/crypto/README.md new file mode 100644 index 0000000..3006f04 --- /dev/null +++ b/src/crypto/README.md @@ -0,0 +1,7 @@ +# ZeroTier Cryptography Library + +------ + +Most of this library is just glue to provide a simple safe API around things like OpenSSL or OS-specific crypto APIs. + +It is very important that this library is only linked to OpenSSL versions greater than 1.1.0. 1.1.0 introduced no-hassle threadsafety which we take advantage of. If we want a version prior to 1.1.0 we will have to add conditional threadsafety code. diff --git a/src/crypto/build.rs b/src/crypto/build.rs new file mode 100644 index 0000000..444b5e4 --- /dev/null +++ b/src/crypto/build.rs @@ -0,0 +1,106 @@ +#![allow(clippy::inconsistent_digit_grouping, clippy::uninlined_format_args, clippy::unusual_byte_groupings)] + +use std::env; + +fn main() { + if env::var("DEP_OPENSSL_LIBRESSL").is_ok() { + println!("cargo:rustc-cfg=libressl"); + } + + if env::var("CARGO_FEATURE_UNSTABLE_BORINGSSL").is_ok() { + println!("cargo:rustc-cfg=boringssl"); + return; + } + + if let Ok(v) = env::var("DEP_OPENSSL_LIBRESSL_VERSION") { + println!("cargo:rustc-cfg=libressl{}", v); + } + + if let Ok(vars) = env::var("DEP_OPENSSL_CONF") { + for var in vars.split(',') { + println!("cargo:rustc-cfg=osslconf=\"{}\"", var); + } + } + + if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") { + let version = u64::from_str_radix(&version, 16).unwrap(); + + if version >= 0x1_00_01_00_0 { + println!("cargo:rustc-cfg=ossl101"); + } + if version >= 0x1_00_02_00_0 { + println!("cargo:rustc-cfg=ossl102"); + } + if version >= 0x1_01_00_00_0 { + println!("cargo:rustc-cfg=ossl110"); + } + if version >= 0x1_01_00_07_0 { + println!("cargo:rustc-cfg=ossl110g"); + } + if version >= 0x1_01_00_08_0 { + println!("cargo:rustc-cfg=ossl110h"); + } + if version >= 0x1_01_01_00_0 { + println!("cargo:rustc-cfg=ossl111"); + } + if version >= 0x3_00_00_00_0 { + println!("cargo:rustc-cfg=ossl300"); + } + } + + if let Ok(version) = env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER") { + let version = u64::from_str_radix(&version, 16).unwrap(); + + if version >= 0x2_05_01_00_0 { + println!("cargo:rustc-cfg=libressl251"); + } + + if version >= 0x2_06_01_00_0 { + println!("cargo:rustc-cfg=libressl261"); + } + + if version >= 0x2_07_00_00_0 { + println!("cargo:rustc-cfg=libressl270"); + } + + if version >= 0x2_07_01_00_0 { + println!("cargo:rustc-cfg=libressl271"); + } + + if version >= 0x2_07_03_00_0 { + println!("cargo:rustc-cfg=libressl273"); + } + + if version >= 0x2_08_00_00_0 { + println!("cargo:rustc-cfg=libressl280"); + } + + if version >= 0x2_09_01_00_0 { + println!("cargo:rustc-cfg=libressl291"); + } + + if version >= 0x3_02_01_00_0 { + println!("cargo:rustc-cfg=libressl321"); + } + + if version >= 0x3_03_02_00_0 { + println!("cargo:rustc-cfg=libressl332"); + } + + if version >= 0x3_04_00_00_0 { + println!("cargo:rustc-cfg=libressl340"); + } + + if version >= 0x3_05_00_00_0 { + println!("cargo:rustc-cfg=libressl350"); + } + + if version >= 0x3_06_00_00_0 { + println!("cargo:rustc-cfg=libressl360"); + } + + if version >= 0x3_06_01_00_0 { + println!("cargo:rustc-cfg=libressl361"); + } + } +} diff --git a/src/crypto/src/aes_fruity.rs b/src/crypto/src/aes_fruity.rs new file mode 100644 index 0000000..3ac9885 --- /dev/null +++ b/src/crypto/src/aes_fruity.rs @@ -0,0 +1,257 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. + +// MacOS implementation of AES primitives since CommonCrypto seems to be faster than OpenSSL, especially on ARM64. +use std::os::raw::{c_int, c_void}; +use std::ptr::{null, null_mut}; +use std::sync::Mutex; + +use crate::constant::*; +use crate::secure_eq; + +#[allow(non_upper_case_globals, unused)] +const kCCModeECB: i32 = 1; +#[allow(non_upper_case_globals, unused)] +const kCCModeCTR: i32 = 4; +#[allow(non_upper_case_globals, unused)] +const kCCModeGCM: i32 = 11; +#[allow(non_upper_case_globals, unused)] +const kCCEncrypt: i32 = 0; +#[allow(non_upper_case_globals, unused)] +const kCCDecrypt: i32 = 1; +#[allow(non_upper_case_globals, unused)] +const kCCAlgorithmAES: i32 = 0; +#[allow(non_upper_case_globals, unused)] +const kCCOptionECBMode: i32 = 2; + +extern "C" { + fn CCCryptorCreateWithMode( + op: i32, + mode: i32, + alg: i32, + padding: i32, + iv: *const c_void, + key: *const c_void, + key_len: usize, + tweak: *const c_void, + tweak_len: usize, + num_rounds: c_int, + options: i32, + cryyptor_ref: *mut *mut c_void, + ) -> i32; + fn CCCryptorUpdate( + cryptor_ref: *mut c_void, + data_in: *const c_void, + data_in_len: usize, + data_out: *mut c_void, + data_out_len: usize, + data_out_written: *mut usize, + ) -> i32; + //fn CCCryptorReset(cryptor_ref: *mut c_void, iv: *const c_void) -> i32; + fn CCCryptorRelease(cryptor_ref: *mut c_void) -> i32; + fn CCCryptorGCMSetIV(cryptor_ref: *mut c_void, iv: *const c_void, iv_len: usize) -> i32; + fn CCCryptorGCMAddAAD(cryptor_ref: *mut c_void, aad: *const c_void, len: usize) -> i32; + fn CCCryptorGCMEncrypt(cryptor_ref: *mut c_void, data_in: *const c_void, data_in_len: usize, data_out: *mut c_void) -> i32; + fn CCCryptorGCMDecrypt(cryptor_ref: *mut c_void, data_in: *const c_void, data_in_len: usize, data_out: *mut c_void) -> i32; + fn CCCryptorGCMFinal(cryptor_ref: *mut c_void, tag: *mut c_void, tag_len: *mut usize) -> i32; + fn CCCryptorGCMReset(cryptor_ref: *mut c_void) -> i32; +} + +pub struct AesGcm(*mut c_void); + +impl Drop for AesGcm { + #[inline(always)] + fn drop(&mut self) { + unsafe { CCCryptorRelease(self.0) }; + } +} + +impl AesGcm { + pub fn new(k: &[u8; AES_256_KEY_SIZE]) -> Self { + unsafe { + let mut ptr: *mut c_void = null_mut(); + assert_eq!( + CCCryptorCreateWithMode( + if ENCRYPT { + kCCEncrypt + } else { + kCCDecrypt + }, + kCCModeGCM, + kCCAlgorithmAES, + 0, + null(), + k.as_ptr().cast(), + AES_256_KEY_SIZE, + null(), + 0, + 0, + 0, + &mut ptr, + ), + 0 + ); + AesGcm(ptr) + } + } + + #[inline(always)] + pub fn reset_init_gcm(&mut self, iv: &[u8]) { + assert_eq!(iv.len(), AES_GCM_NONCE_SIZE); + unsafe { + assert_eq!(CCCryptorGCMReset(self.0), 0); + assert_eq!(CCCryptorGCMSetIV(self.0, iv.as_ptr().cast(), AES_GCM_NONCE_SIZE), 0); + } + } + + #[inline(always)] + pub fn aad(&mut self, aad: &[u8]) { + unsafe { + assert_eq!(CCCryptorGCMAddAAD(self.0, aad.as_ptr().cast(), aad.len()), 0); + } + } + + #[inline(always)] + pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) { + unsafe { + assert_eq!(input.len(), output.len()); + if ENCRYPT { + assert_eq!( + CCCryptorGCMEncrypt(self.0, input.as_ptr().cast(), input.len(), output.as_mut_ptr().cast()), + 0 + ); + } else { + assert_eq!( + CCCryptorGCMDecrypt(self.0, input.as_ptr().cast(), input.len(), output.as_mut_ptr().cast()), + 0 + ); + } + } + } + + #[inline(always)] + pub fn crypt_in_place(&mut self, data: &mut [u8]) { + unsafe { + if ENCRYPT { + assert_eq!(CCCryptorGCMEncrypt(self.0, data.as_ptr().cast(), data.len(), data.as_mut_ptr().cast()), 0); + } else { + assert_eq!(CCCryptorGCMDecrypt(self.0, data.as_ptr().cast(), data.len(), data.as_mut_ptr().cast()), 0); + } + } + } + + #[inline(always)] + fn finish(&mut self) -> [u8; AES_GCM_TAG_SIZE] { + let mut tag = 0_u128.to_ne_bytes(); + unsafe { + let mut tag_len = AES_GCM_TAG_SIZE; + if CCCryptorGCMFinal(self.0, tag.as_mut_ptr().cast(), &mut tag_len) != 0 { + debug_assert!(false); + tag.fill(0); + } + } + tag + } +} + +impl AesGcm { + /// Produce the gcm authentication tag. + #[inline(always)] + pub fn finish_encrypt(&mut self) -> [u8; AES_GCM_TAG_SIZE] { + self.finish() + } +} +impl AesGcm { + /// Check the gcm authentication tag. Outputs true if it matches the just decrypted message, outputs false otherwise. + #[inline(always)] + pub fn finish_decrypt(&mut self, expected_tag: &[u8]) -> bool { + secure_eq(&self.finish(), expected_tag) + } +} + +pub struct Aes(Mutex<*mut c_void>); +unsafe impl Send for Aes {} +unsafe impl Sync for Aes {} + +impl Drop for Aes { + #[inline(always)] + fn drop(&mut self) { + let p = self.0.lock().unwrap(); + unsafe { + CCCryptorRelease(*p); + } + } +} + +impl Aes { + pub fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self { + unsafe { + let mut p = null_mut(); + assert_eq!( + CCCryptorCreateWithMode( + if ENCRYPT { + kCCEncrypt + } else { + kCCDecrypt + }, + kCCModeECB, + kCCAlgorithmAES, + 0, + null(), + key.as_ptr().cast(), + AES_256_KEY_SIZE, + null(), + 0, + 0, + kCCOptionECBMode, + &mut p, + ), + 0 + ); + Self(Mutex::new(p)) + } + } + pub fn reset(&self, key: &[u8; AES_256_KEY_SIZE]) { + let mut p = self.0.lock().unwrap(); + unsafe { + CCCryptorRelease(*p); + assert_eq!( + CCCryptorCreateWithMode( + if ENCRYPT { + kCCEncrypt + } else { + kCCDecrypt + }, + kCCModeECB, + kCCAlgorithmAES, + 0, + null(), + key.as_ptr().cast(), + AES_256_KEY_SIZE, + null(), + 0, + 0, + kCCOptionECBMode, + &mut *p, + ), + 0 + ); + } + } + + #[inline(always)] + pub fn crypt_block_in_place(&self, data: &mut [u8]) { + assert_eq!(data.len(), AES_BLOCK_SIZE); + unsafe { + let mut data_out_written = 0; + let p = self.0.lock().unwrap(); + CCCryptorUpdate( + *p, + data.as_ptr().cast(), + AES_BLOCK_SIZE, + data.as_mut_ptr().cast(), + AES_BLOCK_SIZE, + &mut data_out_written, + ); + } + } +} diff --git a/src/crypto/src/aes_gmac_siv_fruity.rs b/src/crypto/src/aes_gmac_siv_fruity.rs new file mode 100644 index 0000000..3a09f5d --- /dev/null +++ b/src/crypto/src/aes_gmac_siv_fruity.rs @@ -0,0 +1,472 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. + +// AES-GMAC-SIV implemented using MacOS/iOS CommonCrypto (MacOS 10.13 or newer required). + +use std::os::raw::{c_int, c_void}; +use std::ptr::{null, null_mut}; + +#[allow(non_upper_case_globals)] +const kCCModeECB: i32 = 1; +#[allow(non_upper_case_globals)] +const kCCModeCTR: i32 = 4; +#[allow(non_upper_case_globals)] +const kCCModeGCM: i32 = 11; +#[allow(non_upper_case_globals)] +const kCCEncrypt: i32 = 0; +#[allow(non_upper_case_globals)] +const kCCDecrypt: i32 = 1; +#[allow(non_upper_case_globals)] +const kCCAlgorithmAES: i32 = 0; +#[allow(non_upper_case_globals)] +const kCCOptionECBMode: i32 = 2; + +extern "C" { + fn CCCryptorCreateWithMode( + op: i32, + mode: i32, + alg: i32, + padding: i32, + iv: *const c_void, + key: *const c_void, + key_len: usize, + tweak: *const c_void, + tweak_len: usize, + num_rounds: c_int, + options: i32, + cryyptor_ref: *mut *mut c_void, + ) -> i32; + fn CCCryptorUpdate( + cryptor_ref: *mut c_void, + data_in: *const c_void, + data_in_len: usize, + data_out: *mut c_void, + data_out_len: usize, + data_out_written: *mut usize, + ) -> i32; + fn CCCryptorReset(cryptor_ref: *mut c_void, iv: *const c_void) -> i32; + fn CCCryptorRelease(cryptor_ref: *mut c_void) -> i32; + fn CCCryptorGCMSetIV(cryptor_ref: *mut c_void, iv: *const c_void, iv_len: usize) -> i32; + fn CCCryptorGCMAddAAD(cryptor_ref: *mut c_void, aad: *const c_void, len: usize) -> i32; + fn CCCryptorGCMFinalize(cryptor_ref: *mut c_void, tag: *mut c_void, tag_len: usize) -> i32; + fn CCCryptorGCMReset(cryptor_ref: *mut c_void) -> i32; +} + +pub struct AesCtr(*mut c_void); + +impl Drop for AesCtr { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + CCCryptorRelease(self.0); + } + } + } +} + +impl AesCtr { + /// Construct a new AES-CTR cipher. + /// Key must be 16, 24, or 32 bytes in length or a panic will occur. + pub fn new(k: &[u8]) -> Self { + if k.len() != 32 && k.len() != 24 && k.len() != 16 { + panic!("AES supports 128, 192, or 256 bits keys"); + } + unsafe { + let mut ptr: *mut c_void = null_mut(); + let result = CCCryptorCreateWithMode( + kCCEncrypt, + kCCModeCTR, + kCCAlgorithmAES, + 0, + crate::ZEROES.as_ptr().cast(), + k.as_ptr().cast(), + k.len(), + null(), + 0, + 0, + 0, + &mut ptr, + ); + if result != 0 { + panic!("CCCryptorCreateWithMode for CTR mode returned {}", result); + } + AesCtr(ptr) + } + } + + /// Initialize AES-CTR for encryption or decryption with the given IV. + /// If it's already been used, this also resets the cipher. There is no separate reset. + pub fn init(&mut self, iv: &[u8]) { + unsafe { + if iv.len() == 16 { + if CCCryptorReset(self.0, iv.as_ptr().cast()) != 0 { + panic!("CCCryptorReset for CTR mode failed (old MacOS bug)"); + } + } else if iv.len() < 16 { + let mut iv2 = [0_u8; 16]; + iv2[0..iv.len()].copy_from_slice(iv); + if CCCryptorReset(self.0, iv2.as_ptr().cast()) != 0 { + panic!("CCCryptorReset for CTR mode failed (old MacOS bug)"); + } + } else { + panic!("CTR IV must be less than or equal to 16 bytes in length"); + } + } + } + + /// Encrypt or decrypt (same operation with CTR mode) + #[inline(always)] + pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) { + unsafe { + assert!(output.len() >= input.len()); + let mut data_out_written: usize = 0; + CCCryptorUpdate( + self.0, + input.as_ptr().cast(), + input.len(), + output.as_mut_ptr().cast(), + output.len(), + &mut data_out_written, + ); + } + } + + /// Encrypt or decrypt in place (same operation with CTR mode) + #[inline(always)] + pub fn crypt_in_place(&mut self, data: &mut [u8]) { + unsafe { + let mut data_out_written: usize = 0; + CCCryptorUpdate( + self.0, + data.as_ptr().cast(), + data.len(), + data.as_mut_ptr().cast(), + data.len(), + &mut data_out_written, + ); + } + } +} + +unsafe impl Send for AesCtr {} + +#[repr(align(8))] +pub struct AesGmacSiv { + tag: [u8; 16], + tmp: [u8; 16], + ctr: *mut c_void, + ecb_enc: *mut c_void, + ecb_dec: *mut c_void, + gmac: *mut c_void, +} + +impl Drop for AesGmacSiv { + fn drop(&mut self) { + unsafe { + if !self.ctr.is_null() { + CCCryptorRelease(self.ctr); + } + if !self.ecb_enc.is_null() { + CCCryptorRelease(self.ecb_enc); + } + if !self.ecb_dec.is_null() { + CCCryptorRelease(self.ecb_dec); + } + if !self.gmac.is_null() { + CCCryptorRelease(self.gmac); + } + } + } +} + +impl AesGmacSiv { + /// Create a new keyed instance of AES-GMAC-SIV + /// The key may be of size 16, 24, or 32 bytes (128, 192, or 256 bits). Any other size will panic. + /// Two keys are required: one for GMAC and one for AES-CTR. + pub fn new(k0: &[u8], k1: &[u8]) -> Self { + if k0.len() != 32 && k0.len() != 24 && k0.len() != 16 { + panic!("AES supports 128, 192, or 256 bits keys"); + } + if k1.len() != k0.len() { + panic!("k0 and k1 must be of the same size"); + } + let mut c: AesGmacSiv = AesGmacSiv { + tag: [0_u8; 16], + tmp: [0_u8; 16], + ctr: null_mut(), + ecb_enc: null_mut(), + ecb_dec: null_mut(), + gmac: null_mut(), + }; + unsafe { + let result = CCCryptorCreateWithMode( + kCCEncrypt, + kCCModeCTR, + kCCAlgorithmAES, + 0, + crate::ZEROES.as_ptr().cast(), + k1.as_ptr().cast(), + k1.len(), + null(), + 0, + 0, + 0, + &mut c.ctr, + ); + if result != 0 { + panic!("CCCryptorCreateWithMode for CTR mode returned {}", result); + } + let result = CCCryptorCreateWithMode( + kCCEncrypt, + kCCModeECB, + kCCAlgorithmAES, + 0, + crate::ZEROES.as_ptr().cast(), + k1.as_ptr().cast(), + k1.len(), + null(), + 0, + 0, + kCCOptionECBMode, + &mut c.ecb_enc, + ); + if result != 0 { + panic!("CCCryptorCreateWithMode for ECB encrypt mode returned {}", result); + } + let result = CCCryptorCreateWithMode( + kCCDecrypt, + kCCModeECB, + kCCAlgorithmAES, + 0, + crate::ZEROES.as_ptr().cast(), + k1.as_ptr().cast(), + k1.len(), + null(), + 0, + 0, + kCCOptionECBMode, + &mut c.ecb_dec, + ); + if result != 0 { + panic!("CCCryptorCreateWithMode for ECB decrypt mode returned {}", result); + } + let result = CCCryptorCreateWithMode( + kCCEncrypt, + kCCModeGCM, + kCCAlgorithmAES, + 0, + crate::ZEROES.as_ptr().cast(), + k0.as_ptr().cast(), + k0.len(), + null(), + 0, + 0, + 0, + &mut c.gmac, + ); + if result != 0 { + panic!("CCCryptorCreateWithMode for GCM (GMAC) mode returned {}", result); + } + } + c + } + + /// Reset to prepare for another encrypt or decrypt operation. + #[inline(always)] + pub fn reset(&mut self) { + unsafe { + CCCryptorGCMReset(self.gmac); + } + } + + /// Initialize for encryption. + #[inline(always)] + pub fn encrypt_init(&mut self, iv: &[u8]) { + self.tag[0..8].copy_from_slice(iv); + self.tag[8..12].fill(0); + unsafe { + CCCryptorGCMSetIV(self.gmac, self.tag.as_ptr().cast(), 12); + } + } + + /// Set additional authenticated data (data to be authenticated but not encrypted). + /// This can currently only be called once. Multiple calls will result in corrupt data. + #[inline(always)] + pub fn encrypt_set_aad(&mut self, data: &[u8]) { + unsafe { + CCCryptorGCMAddAAD(self.gmac, data.as_ptr().cast(), data.len()); + } + let pad = data.len() & 0xf; + if pad != 0 { + unsafe { + CCCryptorGCMAddAAD(self.gmac, crate::ZEROES.as_ptr().cast(), 16 - pad); + } + } + } + + /// Feed plaintext in for the first encryption pass. + /// This may be called more than once. + #[inline(always)] + pub fn encrypt_first_pass(&mut self, plaintext: &[u8]) { + unsafe { + CCCryptorGCMAddAAD(self.gmac, plaintext.as_ptr().cast(), plaintext.len()); + } + } + + /// Finish first pass and begin second pass. + #[inline(always)] + pub fn encrypt_first_pass_finish(&mut self) { + unsafe { + CCCryptorGCMFinalize(self.gmac, self.tmp.as_mut_ptr().cast(), 16); + let tmp = self.tmp.as_mut_ptr().cast::(); + *self.tag.as_mut_ptr().cast::().offset(1) = *tmp ^ *tmp.offset(1); + let mut data_out_written: usize = 0; + CCCryptorUpdate( + self.ecb_enc, + self.tag.as_ptr().cast(), + 16, + self.tag.as_mut_ptr().cast(), + 16, + &mut data_out_written, + ); + } + self.tmp.copy_from_slice(&self.tag); + self.tmp[12] &= 0x7f; + unsafe { + if CCCryptorReset(self.ctr, self.tmp.as_ptr().cast()) != 0 { + panic!("CCCryptorReset for CTR mode failed (old MacOS bug)"); + } + } + } + + /// Feed plaintext for second pass and write ciphertext to supplied buffer. + /// This may be called more than once. + #[inline(always)] + pub fn encrypt_second_pass(&mut self, plaintext: &[u8], ciphertext: &mut [u8]) { + unsafe { + assert!(ciphertext.len() >= plaintext.len()); + let mut data_out_written: usize = 0; + CCCryptorUpdate( + self.ctr, + plaintext.as_ptr().cast(), + plaintext.len(), + ciphertext.as_mut_ptr().cast(), + ciphertext.len(), + &mut data_out_written, + ); + } + } + + /// Encrypt plaintext in place. + /// This may be called more than once. + #[inline(always)] + pub fn encrypt_second_pass_in_place(&mut self, plaintext_to_ciphertext: &mut [u8]) { + unsafe { + let mut data_out_written: usize = 0; + CCCryptorUpdate( + self.ctr, + plaintext_to_ciphertext.as_ptr().cast(), + plaintext_to_ciphertext.len(), + plaintext_to_ciphertext.as_mut_ptr().cast(), + plaintext_to_ciphertext.len(), + &mut data_out_written, + ); + } + } + + /// Finish second pass and return a reference to the tag for this message. + /// The tag returned remains valid until reset() is called. + #[inline(always)] + pub fn encrypt_second_pass_finish(&mut self) -> &[u8; 16] { + return &self.tag; + } + + #[inline(always)] + fn decrypt_init_internal(&mut self) { + self.tmp[12] &= 0x7f; + unsafe { + if CCCryptorReset(self.ctr, self.tmp.as_ptr().cast()) != 0 { + panic!("CCCryptorReset for CTR mode failed (old MacOS bug)"); + } + let mut data_out_written = 0; + CCCryptorUpdate( + self.ecb_dec, + self.tag.as_ptr().cast(), + 16, + self.tag.as_mut_ptr().cast(), + 16, + &mut data_out_written, + ); + let tmp = self.tmp.as_mut_ptr().cast::(); + *tmp = *self.tag.as_mut_ptr().cast::(); + *tmp.add(1) = 0; + CCCryptorGCMSetIV(self.gmac, self.tmp.as_ptr().cast(), 12); + } + } + + /// Initialize this cipher for decryption. + /// The supplied tag must be 16 bytes in length. Any other length will panic. + #[inline(always)] + pub fn decrypt_init(&mut self, tag: &[u8]) { + self.tmp.copy_from_slice(tag); + self.tag.copy_from_slice(tag); + self.decrypt_init_internal(); + } + + /// Set additional authenticated data to be checked. + #[inline(always)] + pub fn decrypt_set_aad(&mut self, data: &[u8]) { + self.encrypt_set_aad(data); + } + + /// Decrypt ciphertext and write to plaintext. + /// This may be called more than once. + #[inline(always)] + pub fn decrypt(&mut self, ciphertext: &[u8], plaintext: &mut [u8]) { + unsafe { + let mut data_out_written = 0; + CCCryptorUpdate( + self.ctr, + ciphertext.as_ptr().cast(), + ciphertext.len(), + plaintext.as_mut_ptr().cast(), + plaintext.len(), + &mut data_out_written, + ); + CCCryptorGCMAddAAD(self.gmac, plaintext.as_ptr().cast(), plaintext.len()); + } + } + + /// Decrypt ciphertext in place. + /// This may be called more than once. + #[inline(always)] + pub fn decrypt_in_place(&mut self, ciphertext_to_plaintext: &mut [u8]) { + unsafe { + let mut data_out_written = 0; + CCCryptorUpdate( + self.ctr, + ciphertext_to_plaintext.as_ptr().cast(), + ciphertext_to_plaintext.len(), + ciphertext_to_plaintext.as_mut_ptr().cast(), + ciphertext_to_plaintext.len(), + &mut data_out_written, + ); + CCCryptorGCMAddAAD(self.gmac, ciphertext_to_plaintext.as_ptr().cast(), ciphertext_to_plaintext.len()); + } + } + + /// Finish decryption and returns the decrypted tag if the message appears valid. + #[inline(always)] + pub fn decrypt_finish(&mut self) -> Option<&[u8; 16]> { + unsafe { + CCCryptorGCMFinalize(self.gmac, self.tmp.as_mut_ptr().cast(), 16); + let tmp = self.tmp.as_mut_ptr().cast::(); + if *self.tag.as_mut_ptr().cast::().offset(1) == *tmp ^ *tmp.offset(1) { + Some(&self.tag) + } else { + None + } + } + } +} + +unsafe impl Send for AesGmacSiv {} diff --git a/src/crypto/src/aes_gmac_siv_openssl.rs b/src/crypto/src/aes_gmac_siv_openssl.rs new file mode 100644 index 0000000..2771ecc --- /dev/null +++ b/src/crypto/src/aes_gmac_siv_openssl.rs @@ -0,0 +1,246 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +use std::ptr; + +use crate::{cipher_ctx::CipherCtx, ZEROES}; + +/// AES-GMAC-SIV encryptor/decryptor. +pub struct AesGmacSiv { + tag: [u8; 16], + tmp: [u8; 16], + ecb_enc: CipherCtx, + ecb_dec: CipherCtx, + ctr: CipherCtx, + gmac: CipherCtx, +} + +impl AesGmacSiv { + /// Create a new keyed instance of AES-GMAC-SIV + /// The key may be of size 16, 24, or 32 bytes (128, 192, or 256 bits). Any other size will panic. + pub fn new(k0: &[u8], k1: &[u8]) -> Self { + let gmac = CipherCtx::new().unwrap(); + unsafe { + let t = match k0.len() { + 16 => ffi::EVP_aes_128_gcm(), + 24 => ffi::EVP_aes_192_gcm(), + 32 => ffi::EVP_aes_256_gcm(), + _ => panic!("Aes KEY_SIZE must be 16, 24 or 32"), + }; + gmac.cipher_init::(t, k0.as_ptr(), ptr::null_mut()).unwrap(); + } + let ctr = CipherCtx::new().unwrap(); + unsafe { + let t = match k1.len() { + 16 => ffi::EVP_aes_128_ctr(), + 24 => ffi::EVP_aes_192_ctr(), + 32 => ffi::EVP_aes_256_ctr(), + _ => panic!("Aes KEY_SIZE must be 16, 24 or 32"), + }; + ctr.cipher_init::(t, k1.as_ptr(), ptr::null_mut()).unwrap(); + } + let ecb_enc = CipherCtx::new().unwrap(); + unsafe { + let t = match k1.len() { + 16 => ffi::EVP_aes_128_ecb(), + 24 => ffi::EVP_aes_192_ecb(), + 32 => ffi::EVP_aes_256_ecb(), + _ => panic!("Aes KEY_SIZE must be 16, 24 or 32"), + }; + ecb_enc.cipher_init::(t, k1.as_ptr(), ptr::null_mut()).unwrap(); + ffi::EVP_CIPHER_CTX_set_padding(ecb_enc.as_ptr(), 0); + } + let ecb_dec = CipherCtx::new().unwrap(); + unsafe { + let t = match k1.len() { + 16 => ffi::EVP_aes_128_ecb(), + 24 => ffi::EVP_aes_192_ecb(), + 32 => ffi::EVP_aes_256_ecb(), + _ => panic!("Aes KEY_SIZE must be 16, 24 or 32"), + }; + ecb_dec.cipher_init::(t, k1.as_ptr(), ptr::null_mut()).unwrap(); + ffi::EVP_CIPHER_CTX_set_padding(ecb_dec.as_ptr(), 0); + } + + AesGmacSiv { + tag: [0_u8; 16], + tmp: [0_u8; 16], + ecb_dec, + ecb_enc, + ctr, + gmac, + } + } + + /// Reset to prepare for another encrypt or decrypt operation. + #[inline(always)] + pub fn reset(&mut self) {} + + /// Initialize for encryption. + #[inline(always)] + pub fn encrypt_init(&mut self, iv: &[u8]) { + self.tag[0..8].copy_from_slice(iv); + self.tag[8..12].fill(0); + unsafe { + self.gmac + .cipher_init::(ptr::null_mut(), ptr::null_mut(), self.tag[0..12].as_ptr()) + .unwrap(); + } + } + + /// Set additional authenticated data (data to be authenticated but not encrypted). + /// This can currently only be called once. Multiple calls will result in corrupt data. + #[inline(always)] + pub fn encrypt_set_aad(&mut self, data: &[u8]) { + unsafe { + self.gmac.update::(data, ptr::null_mut()).unwrap(); + let mut pad = data.len() & 0xf; + if pad != 0 { + pad = 16 - pad; + self.gmac.update::(&ZEROES[0..pad], ptr::null_mut()).unwrap(); + } + } + } + + /// Feed plaintext in for the first encryption pass. + /// This may be called more than once. + #[inline(always)] + pub fn encrypt_first_pass(&mut self, plaintext: &[u8]) { + unsafe { + self.gmac.update::(plaintext, ptr::null_mut()).unwrap(); + } + } + + /// Finish first pass and begin second pass. + #[inline(always)] + pub fn encrypt_first_pass_finish(&mut self) { + unsafe { + self.gmac.finalize::(ptr::null_mut()).unwrap(); + self.gmac.tag(&mut self.tmp).unwrap(); + } + + self.tag[8] = self.tmp[0] ^ self.tmp[8]; + self.tag[9] = self.tmp[1] ^ self.tmp[9]; + self.tag[10] = self.tmp[2] ^ self.tmp[10]; + self.tag[11] = self.tmp[3] ^ self.tmp[11]; + self.tag[12] = self.tmp[4] ^ self.tmp[12]; + self.tag[13] = self.tmp[5] ^ self.tmp[13]; + self.tag[14] = self.tmp[6] ^ self.tmp[14]; + self.tag[15] = self.tmp[7] ^ self.tmp[15]; + + let mut tag_tmp = [0_u8; 16]; + + unsafe { + self.ecb_enc.update::(&self.tag, tag_tmp.as_mut_ptr()).unwrap(); + } + self.tag.copy_from_slice(&tag_tmp); + self.tmp.copy_from_slice(&tag_tmp); + + self.tmp[12] &= 0x7f; + + unsafe { + self.ctr.cipher_init::(ptr::null_mut(), ptr::null_mut(), self.tmp.as_ptr()).unwrap(); + } + } + + /// Feed plaintext for second pass and write ciphertext to supplied buffer. + /// This may be called more than once. + #[inline(always)] + pub fn encrypt_second_pass(&mut self, plaintext: &[u8], ciphertext: &mut [u8]) { + unsafe { + self.ctr.update::(plaintext, ciphertext.as_mut_ptr()).unwrap(); + } + } + + /// Encrypt plaintext in place. + /// This may be called more than once. + #[inline(always)] + pub fn encrypt_second_pass_in_place(&mut self, plaintext_to_ciphertext: &mut [u8]) { + unsafe { + let out = plaintext_to_ciphertext.as_mut_ptr(); + self.ctr.update::(plaintext_to_ciphertext, out).unwrap(); + } + } + + /// Finish second pass and return a reference to the tag for this message. + /// The tag returned remains valid until reset() is called. + #[inline(always)] + pub fn encrypt_second_pass_finish(&mut self) -> &[u8; 16] { + &self.tag + } + + /// Initialize this cipher for decryption. + /// The supplied tag must be 16 bytes in length. Any other length will panic. + #[inline(always)] + pub fn decrypt_init(&mut self, tag: &[u8]) { + self.tmp.copy_from_slice(tag); + self.tmp[12] &= 0x7f; + + unsafe { + self.ctr + .cipher_init::(ptr::null_mut(), ptr::null_mut(), self.tmp.as_ptr()) + .unwrap(); + } + + let mut tag_tmp = [0_u8; 16]; + + unsafe { + self.ecb_dec.update::(tag, tag_tmp.as_mut_ptr()).unwrap(); + } + self.tag.copy_from_slice(&tag_tmp); + tag_tmp[8..12].fill(0); + + unsafe { + self.gmac.cipher_init::(ptr::null_mut(), ptr::null_mut(), tag_tmp.as_ptr()).unwrap(); + } + } + + /// Set additional authenticated data to be checked. + #[inline(always)] + pub fn decrypt_set_aad(&mut self, data: &[u8]) { + self.encrypt_set_aad(data); + } + + /// Decrypt ciphertext and write to plaintext. + /// This may be called more than once. + #[inline(always)] + pub fn decrypt(&mut self, ciphertext: &[u8], plaintext: &mut [u8]) { + unsafe { + self.ctr.update::(ciphertext, plaintext.as_mut_ptr()).unwrap(); + self.gmac.update::(plaintext, ptr::null_mut()).unwrap(); + } + } + + /// Decrypt ciphertext in place. + /// This may be called more than once. + #[inline(always)] + pub fn decrypt_in_place(&mut self, ciphertext_to_plaintext: &mut [u8]) { + self.decrypt( + unsafe { std::slice::from_raw_parts(ciphertext_to_plaintext.as_ptr(), ciphertext_to_plaintext.len()) }, + ciphertext_to_plaintext, + ); + } + + /// Finish decryption and return true if authentication appears valid. + /// If this returns false the message should be dropped. + #[inline(always)] + pub fn decrypt_finish(&mut self) -> Option<&[u8; 16]> { + unsafe { + self.gmac.finalize::(self.tmp.as_mut_ptr()).unwrap(); + self.gmac.tag(&mut self.tmp).unwrap(); + } + if (self.tag[8] == self.tmp[0] ^ self.tmp[8]) + && (self.tag[9] == self.tmp[1] ^ self.tmp[9]) + && (self.tag[10] == self.tmp[2] ^ self.tmp[10]) + && (self.tag[11] == self.tmp[3] ^ self.tmp[11]) + && (self.tag[12] == self.tmp[4] ^ self.tmp[12]) + && (self.tag[13] == self.tmp[5] ^ self.tmp[13]) + && (self.tag[14] == self.tmp[6] ^ self.tmp[14]) + && (self.tag[15] == self.tmp[7] ^ self.tmp[15]) + { + Some(&self.tag) + } else { + None + } + } +} + +unsafe impl Send for AesGmacSiv {} diff --git a/src/crypto/src/aes_openssl.rs b/src/crypto/src/aes_openssl.rs new file mode 100644 index 0000000..840a47d --- /dev/null +++ b/src/crypto/src/aes_openssl.rs @@ -0,0 +1,122 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. + +use std::{mem::MaybeUninit, ptr, sync::Mutex}; + +use crate::{cipher_ctx::CipherCtx, constant::*}; + +/// An OpenSSL AES_GCM context. Automatically frees itself on drop. +/// The current interface is custom made for ZeroTier, but could easily be adapted for other uses. +/// Whether `ENCRYPT` is true or false decides respectively whether this context encrypts or decrypts. +/// Even though OpenSSL lets you set this dynamically almost no operations work when you do this +/// without resetting the context. +/// +/// This object cannot be mutated by multiple threads at the same time so wrap it in a Mutex if +/// you need to do this. As far as I have read a Mutex can safely implement Send and Sync. +pub struct AesGcm(CipherCtx); + +impl AesGcm { + /// Create an AesGcm context with the given key. + /// OpenSSL internally processes and caches this key, so it is recommended to reuse this context whenever encrypting under the same key. Call `reset_init_gcm` to change the IV for each reuse. + pub fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self { + let ctx = CipherCtx::new().unwrap(); + unsafe { + let t = ffi::EVP_aes_256_gcm(); + ctx.cipher_init::(t, key.as_ptr(), ptr::null()).unwrap(); + ffi::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + } + + AesGcm(ctx) + } + + /// Set the IV of this AesGcm context. This call resets the IV but leaves the key and encryption algorithm alone. + /// This method must be called before any other method on AesGcm. + /// `iv` must be exactly 12 bytes in length, because that is what Aes supports. + pub fn reset_init_gcm(&mut self, iv: &[u8]) { + debug_assert_eq!(iv.len(), AES_GCM_NONCE_SIZE, "Aes IV must be 12 bytes long"); + unsafe { + self.0.cipher_init::(ptr::null(), ptr::null(), iv.as_ptr()).unwrap(); + } + } + + /// Add additional authentication data to AesGcm (same operation with CTR mode). + #[inline(always)] + pub fn aad(&mut self, aad: &[u8]) { + unsafe { self.0.update::(aad, ptr::null_mut()).unwrap() }; + } + + /// Encrypt or decrypt (same operation with CTR mode) + #[inline(always)] + pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) { + debug_assert!(output.len() >= input.len(), "output buffer must fit the size of the input buffer"); + unsafe { self.0.update::(input, output.as_mut_ptr()).unwrap() }; + } + + /// Encrypt or decrypt in place (same operation with CTR mode). + #[inline(always)] + pub fn crypt_in_place(&mut self, data: &mut [u8]) { + let ptr = data.as_mut_ptr(); + unsafe { self.0.update::(data, ptr).unwrap() } + } +} +impl AesGcm { + /// Produce the gcm authentication tag. + #[inline(always)] + pub fn finish_encrypt(&mut self) -> [u8; AES_GCM_TAG_SIZE] { + unsafe { + let mut tag = MaybeUninit::<[u8; AES_GCM_TAG_SIZE]>::uninit(); + self.0.finalize::(tag.as_mut_ptr().cast()).unwrap(); + self.0.tag(&mut *tag.as_mut_ptr()).unwrap(); + tag.assume_init() + } + } +} +impl AesGcm { + /// Check the gcm authentication tag. Outputs true if it matches the just decrypted message, outputs false otherwise. + #[inline(always)] + pub fn finish_decrypt(&mut self, expected_tag: &[u8]) -> bool { + debug_assert_eq!(expected_tag.len(), AES_GCM_TAG_SIZE); + if self.0.set_tag(expected_tag).is_ok() { + unsafe { self.0.finalize::(ptr::null_mut()).is_ok() } + } else { + false + } + } +} + +/// An OpenSSL AES_ECB context. Automatically frees itself on drop. +/// AES_ECB is very insecure if used incorrectly so its public interface supports only exactly what +/// ZeroTier uses it for. +pub struct Aes(Mutex); +unsafe impl Send for Aes {} +unsafe impl Sync for Aes {} + +impl Aes { + /// Create an AesEcb context with the given key. + /// OpenSSL internally processes and caches this key, so it is recommended to reuse this context + /// whenever encrypting under the same key. + pub fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self { + let ctx = CipherCtx::new().unwrap(); + unsafe { + let t = ffi::EVP_aes_256_ecb(); + ctx.cipher_init::(t, key.as_ptr(), ptr::null()).unwrap(); + ffi::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + } + + Aes(Mutex::new(ctx)) + } + pub fn reset(&self, key: &[u8; AES_256_KEY_SIZE]) { + let ctx = self.0.lock().unwrap(); + unsafe { + ctx.cipher_init::(ptr::null(), key.as_ptr(), ptr::null()).unwrap(); + } + } + + /// Do not ever encrypt the same plaintext twice. Make sure data is always different between calls. + #[inline(always)] + pub fn crypt_block_in_place(&self, data: &mut [u8]) { + debug_assert_eq!(data.len(), AES_BLOCK_SIZE, "Incorrect Aes block size"); + let ptr = data.as_mut_ptr(); + let ctx = self.0.lock().unwrap(); + unsafe { ctx.update::(data, ptr).unwrap() } + } +} diff --git a/src/crypto/src/aes_tests.rs b/src/crypto/src/aes_tests.rs new file mode 100644 index 0000000..9a7a188 --- /dev/null +++ b/src/crypto/src/aes_tests.rs @@ -0,0 +1,3677 @@ +#[cfg(test)] +mod test { + use crate::aes::AesGcm; + use crate::aes_gmac_siv::AesGmacSiv; + use hex_literal::hex; + use std::time::SystemTime; + + fn to_hex(b: &[u8]) -> String { + let mut s = String::new(); + for c in b.iter() { + s = format!("{}{:0>2x}", s, *c); + } + s + } + + #[test] + fn aes_256_gcm() { + let key = [1u8; 32]; + let mut enc = AesGcm::::new(&key); + let mut dec = AesGcm::::new(&key); + + let plain = [2u8; 127]; + let iv0 = [3u8; 12]; + let iv1 = [4u8; 12]; + let mut tag_out; + let mut cipher_out = [0u8; 127]; + let mut plain_out = [0u8; 127]; + + enc.reset_init_gcm(&iv0); + enc.crypt(&plain, &mut cipher_out); + tag_out = enc.finish_encrypt(); + + dec.reset_init_gcm(&iv0); + dec.crypt(&cipher_out, &mut plain_out); + assert!(dec.finish_decrypt(&tag_out)); + + assert_eq!(plain, plain_out); + + enc.reset_init_gcm(&iv1); + enc.crypt(&plain, &mut cipher_out); + tag_out = enc.finish_encrypt(); + + dec.reset_init_gcm(&iv1); + dec.crypt(&cipher_out, &mut plain_out); + assert!(dec.finish_decrypt(&tag_out)); + + assert_eq!(plain, plain_out); + + enc.reset_init_gcm(&iv0); + enc.crypt(&plain, &mut cipher_out); + tag_out = enc.finish_encrypt(); + + dec.reset_init_gcm(&iv1); + dec.crypt(&cipher_out, &mut plain_out); + assert!(!dec.finish_decrypt(&tag_out)); + } + + #[test] + fn aes_256_gcm_quick_benchmark() { + let mut buf = [0_u8; 12345]; + for i in 1..12345 { + buf[i] = i as u8; + } + let iv = [1_u8; 12]; + + let mut c = AesGcm::::new(&[1_u8; 32]); + + let benchmark_iterations: usize = 80000; + let start = SystemTime::now(); + for _ in 0..benchmark_iterations { + c.reset_init_gcm(&iv); + c.crypt_in_place(&mut buf); + } + let duration = SystemTime::now().duration_since(start).unwrap(); + println!( + " AES-256-GCM encrypt benchmark: {} MiB/sec", + (((benchmark_iterations * buf.len()) as f64) / 1048576.0) / duration.as_secs_f64() + ); + + let mut c = AesGcm::::new(&[1_u8; 32]); + + let start = SystemTime::now(); + for _ in 0..benchmark_iterations { + c.reset_init_gcm(&iv); + c.crypt_in_place(&mut buf); + } + let duration = SystemTime::now().duration_since(start).unwrap(); + println!( + " AES-256-GCM decrypt benchmark: {} MiB/sec", + (((benchmark_iterations * buf.len()) as f64) / 1048576.0) / duration.as_secs_f64() + ); + } + + #[test] + fn aes_gcm_test_vectors() { + // Even though we are just wrapping other implementations, it's still good to test thoroughly! + for tv in NIST_AES_GCM_TEST_VECTORS.iter() { + let mut gcm = AesGcm::new(&tv.key); + gcm.reset_init_gcm(tv.nonce); + gcm.aad(tv.aad); + let mut ciphertext = Vec::new(); + ciphertext.resize(tv.plaintext.len(), 0); + gcm.crypt(tv.plaintext, ciphertext.as_mut()); + let mut tag = gcm.finish_encrypt(); + assert!(tag.eq(tv.tag)); + assert!(ciphertext.as_slice().eq(tv.ciphertext)); + + let mut gcm = AesGcm::new(&tv.key); + gcm.reset_init_gcm(tv.nonce); + gcm.aad(tv.aad); + let mut ct_copy = ciphertext.clone(); + gcm.crypt_in_place(ct_copy.as_mut()); + assert!(gcm.finish_decrypt(&tag)); + + gcm.reset_init_gcm(tv.nonce); + gcm.aad(tv.aad); + gcm.crypt_in_place(ciphertext.as_mut()); + tag[0] ^= 1; + assert!(!gcm.finish_decrypt(&tag)); + } + } + + #[test] + fn aes_gmac_siv_test_vectors() { + let mut test_pt = [0_u8; 65536]; + let mut test_ct = [0_u8; 65536]; + let mut test_aad = [0_u8; 65536]; + for i in 0..65536 { + test_pt[i] = i as u8; + test_aad[i] = i as u8; + } + let mut c = AesGmacSiv::new(TV0_KEYS[0], TV0_KEYS[1]); + for (test_length, expected_ct_sha384, expected_tag) in TEST_VECTORS.iter() { + test_ct.fill(0); + c.reset(); + c.encrypt_init(&(*test_length as u64).to_le_bytes()); + c.encrypt_set_aad(&test_aad[0..*test_length]); + c.encrypt_first_pass(&test_pt[0..*test_length]); + c.encrypt_first_pass_finish(); + c.encrypt_second_pass(&test_pt[0..*test_length], &mut test_ct[0..*test_length]); + let tag = c.encrypt_second_pass_finish(); + let ct_hash = crate::hash::SHA384::hash(&test_ct[0..*test_length]).to_vec(); + //println!("{} {} {}", *test_length, to_hex(ct_hash.as_slice()), to_hex(tag)); + if !to_hex(ct_hash.as_slice()).eq(*expected_ct_sha384) { + panic!("test vector failed (ciphertest)"); + } + if !to_hex(tag).eq(*expected_tag) { + panic!("test vector failed (tag)"); + } + } + } + + #[test] + fn aes_gmac_siv_encrypt_decrypt() { + let aes_key_0: [u8; 32] = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + ]; + let aes_key_1: [u8; 32] = [ + 2, 3, 4, 5, 6, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + ]; + let iv: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; + + let mut buf = [0_u8; 12345]; + for i in 1..12345 { + buf[i] = i as u8; + } + + let mut c = AesGmacSiv::new(&aes_key_0, &aes_key_1); + + for _ in 0..256 { + c.reset(); + c.encrypt_init(&iv); + c.encrypt_first_pass(&buf); + c.encrypt_first_pass_finish(); + c.encrypt_second_pass_in_place(&mut buf); + let tag = *c.encrypt_second_pass_finish(); + let sha = crate::hash::SHA384::hash(&buf).to_vec(); + let sha = to_hex(sha.as_slice()); + if sha != "4dc97c10abb6112a3907e5eb588ea5123719442b715da994d9756b003677719824326973960268823d924f66491a16e6" { + panic!("encrypt result hash check failed! {}", sha); + } + //println!("Encrypt OK, tag: {}, hash: {}", to_hex(&tag), sha); + + c.reset(); + c.decrypt_init(&tag); + c.decrypt_in_place(&mut buf); + let _ = c.decrypt_finish().expect("decrypt_finish() failed!"); + for i in 1..12345 { + if buf[i] != (i & 0xff) as u8 { + panic!("decrypt data check failed!"); + } + } + //println!("Decrypt OK"); + } + //println!("Encrypt/decrypt test OK"); + + let benchmark_iterations: usize = 80000; + let start = SystemTime::now(); + for _ in 0..benchmark_iterations { + c.reset(); + c.encrypt_init(&iv); + c.encrypt_first_pass(&buf); + c.encrypt_first_pass_finish(); + c.encrypt_second_pass_in_place(&mut buf); + let _ = c.encrypt_second_pass_finish(); + } + let duration = SystemTime::now().duration_since(start).unwrap(); + println!( + " AES-GMAC-SIV (legacy) encrypt benchmark: {} MiB/sec", + (((benchmark_iterations * buf.len()) as f64) / 1048576.0) / duration.as_secs_f64() + ); + let start = SystemTime::now(); + for _ in 0..benchmark_iterations { + c.reset(); + c.decrypt_init(&buf[0..16]); // we don't care if decryption is successful to benchmark, so anything will do + c.decrypt_in_place(&mut buf); + c.decrypt_finish(); + } + let duration = SystemTime::now().duration_since(start).unwrap(); + println!( + " AES-GMAC-SIV (legacy) decrypt benchmark: {} MiB/sec", + (((benchmark_iterations * buf.len()) as f64) / 1048576.0) / duration.as_secs_f64() + ); + } + + struct GcmTV { + pub key: &'static K, + pub nonce: &'static [u8; 12], + pub aad: &'static [u8], + pub plaintext: &'static [u8], + pub ciphertext: &'static [u8], + pub tag: &'static [u8; 16], + } + + /// AES-GMAC-SIV test keys. + const TV0_KEYS: [&[u8]; 2] = [ + "00000000000000000000000000000000".as_bytes(), + "11111111111111111111111111111111".as_bytes(), + ]; + + /// AES-GMAC-SIV test vectors. + /// Test vectors consist of a series of input sizes, a SHA384 hash of a resulting ciphertext, and an expected tag. + /// Input is a standard byte array consisting of bytes 0, 1, 2, 3, ..., 255 and then cycling back to 0 over and over + /// and is provided both as ciphertext and associated data (AAD). + #[allow(unused)] + const TEST_VECTORS: [(usize, &str, &str); 85] = [ + ( + 0, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", + "43847e644239134deccf5538162c861e", + ), + ( + 777, + "aabf892f18a620b9c3bae91bb03a74c84193e4a7b64916c6bc88b885b9ebed4134495e5f22f12e3046fbb3f26fa111a7", + "b8c318b5dcc1d672114a6f7be54ef289", + ), + ( + 1554, + "648f551df29217f0e634b72ba6973c0eb95c7d4be8b135e550d8bcdf65b75980881bc0e03cf22589e04bedc7da1804cd", + "535b8ddd51ec82a1e850906fe321b21a", + ), + ( + 2331, + "bfbfdffea40062e23bbdf0835e1d38d1623bebca7407908bbc6d5b3f2bfd062a2d237f091affda7348094fafda0bd1a7", + "4f521876fbb2c563051196b33c20c822", + ), + ( + 3108, + "cc6035cab70f3a3298a5c4956ff07f179acf3771bb915c590a8a19fe5133d6d8a81c118148394dfb364af5c2fbdaadeb", + "d3adfa578c8bcd738c55ffc527358cef", + ), + ( + 3885, + "15ec2760a21c25f9870a84ee757f3da2c261a950c2f692d75ff9e99b2d50c826c21e27e49c4cd3450fedc7e60371589f", + "a4c22d6c3d773634c2dc057e1f7c6738", + ), + ( + 4662, + "c2afad6f034704300c34f143dcdcb86c9b954cec1ebf22e7071f288c58a2ae430d3e3748d214d1021472793d3f337dc6", + "c0601cb6cd4883102f70570c2cdc0ab6", + ), + ( + 5439, + "8fee067f5a7a475a630f9db8b2eb80c1edc40eb4246a0f1c078e535df7d06451c6a9bde1a23ba70285690dd7100a8626", + "7352239f2302b08844309d28b13fa867", + ), + ( + 6216, + "60095b4172438aee61e65f5379f4ef276c3632d4ac74eea7723a2201823432614aba7b4670d9bf7a5b9126ca38f3b88a", + "c0f0b0aa651965f8514b473c5406285e", + ), + ( + 6993, + "10e754dd08b4d2a6c109fb01fce2b57d54743947e14a7e67d7efd0608baf91f7fc42a53328fe8c18d234abad8ebcdff0", + "58444988a62a99060728a7637c8499eb", + ), + ( + 7770, + "1abc4a5dcd2696336bd0e8af20fe7fc261aa424b52cfb5ad80ee7c7c793ac44f11db3506cdbbbaed0f80000925d08d52", + "e8065c563bc6018cdcbf9aaafef767e6", + ), + ( + 8547, + "26aaf74ae8bfc6aaf45ceee0476ea0a484304f5c36050d3e2265cb194a2f7c308213314232270608b6d3f1c11b834e33", + "ec50e4b3f6e4b3de24b3476623d08157", + ), + ( + 9324, + "863206305d466aa9c0d0ec674572069f61fe5009767f99ec8832912725c28c49d6a106ad3f55372c922e4e169fc382ce", + "0cfac64f49e0f128d0a18d293878f222", + ), + ( + 10101, + "bd0c0950b947a6c34f1fa6e877433b42c039a8ea7b37634c40fb47efae4958ba74ef0991cfedf3c82a0b87ef59635071", + "e0220a02b74259eeebbebede847d50f9", + ), + ( + 10878, + "d7b9901af1dacf6a8c369b993ba1c607f9b7f073d02311c72d8449d3494d477ffc8344a1d8b488020ccfc7c80fbd27e1", + "ebe3933146734a6ade2b434f2bcd78ae", + ), + ( + 11655, + "0ba265e3ef0bebf01a4f3490da462c7730aad6aa6c70bb9ce64a36d26d24fe213660e60e4d3301329170471f11ff8ca2", + "ec3dd4bf4cb7d527a86dd559c773a87b", + ), + ( + 12432, + "c3b6755a1be922ec71c1e187ead36c4e6fc307c72969c64ca1e9b7339d61e1a93a74a315fd73bed8fa5797b78b19dbe5", + "5b58dcf392749bcef91056ba9475d0ef", + ), + ( + 13209, + "2fb1a67151183daa2f0d7f0064534497357f173161349dd008499a8c1a123cc942662ecc426e2ad7743fe0ab9f5d7be1", + "c011260d328d310e2ab606aa1ef8afd4", + ), + ( + 13986, + "6afae2a07ce9bfe30fbbfb7dcf32d755bcf357334dc5c309e58cab38ebe559f25b313a0b3ca32ff1dc41f7b99718f653", + "011bf43cfbbb7ae5986f8e0fc87771a9", + ), + ( + 14763, + "cc6215c115eb6411f4712c2289f5bf0ccb5151635f9f9ceac7c1b62d8d2f4d26498079d0289f83aeb26e97b5b924ffc4", + "a015034a8d5bc83cc76c6983a5ba19ab", + ), + ( + 15540, + "3cebce794e947341c4ceec444ca43c6ac57c6f58de462bfec7566cbd59a1b6f2eae774120e29521e76120a604d1a12d9", + "d373cd2bd9000655141ac632880eca40", + ), + ( + 16317, + "899147b98d78bb5d137dc7c4f03be7eca82bcca19cc3a701261332923707aed2e6719d35d2f2bf067cd1d193a53529cf", + "ed223b64529299c787f49d631ce181c1", + ), + ( + 17094, + "aecd1830958b994b2c331b90e7d8ff79f27c83a71f5797a65ade3a30b4fa5928e79140bcd03f375591d53df96fea1a4d", + "948a7c253d54bb6b65d78530c0eb7aab", + ), + ( + 17871, + "e677ffd4ecaba5899659fefe5fe8e643004392be3be6dc5a801409870ac1e3398f47cc1d83f7a4c41925b6337e01f7fd", + "156a600c336f3ac034ca90034aa22635", + ), + ( + 18648, + "4ee50f4a98d0bbd160add6acf76765ccdac0c1cd0bb2adbbcb22dd012a1121620b739a120df7dc4091e684ddf28eb726", + "75873467b416a7b025f9f1b015bf653a", + ), + ( + 19425, + "aa025f32c0575af7209828fc7fc4591b41fa7cfb485e26c5401e63ca1fa05776f8b8af1769a15e81f2c663bca9b02ab3", + "5679efa7a4404e1e5c9b372782a41bf2", + ), + ( + 20202, + "6e77ab62d2affeb27f4ef326191b3df3863c338a629f64a785505f4a5968ff59bc011c7a27951cb00e2e7d9b9bd32fec", + "36a9c4515d34f9bb962d8876ab3b5c86", + ), + ( + 20979, + "1625b4f0e65fc66f11ba3ee6b3e20c732535654c447df6b517ced113107a1057a64477faa2af4a5ede4034bf3cff98ea", + "9058044e0f71c28d4f8d3281a3aec024", + ), + ( + 21756, + "94efe6aa55bd77bfa58c185dec313a41003f9bef02568e72c337be4de1b46c6e5bb9a9329b4f108686489b8bc9d5f4f0", + "8d6d2c90590268a26f5e7d76351f48c1", + ), + ( + 22533, + "7327a05fdb0ac92433dfc2c85c5e96e6ddcbdb01e079f8dafbee79c14cb4d5fd46047acd6bb0e09a98f6dd03dced2a0a", + "4e0f0a394f85bca35c68ef667aa9c244", + ), + ( + 23310, + "93da9e356efbc8b5ae366256f4c6fc11c11fc347aaa879d591b7c1262d90adf98925f571914696054f1d09c74783561e", + "8c83c157be439280afc790ee3fd667eb", + ), + ( + 24087, + "99b91be5ffca51b1cbc7410798b1540b5b1a3356f801ed4dc54812919c08ca5a9adc218bc51e594d97b46445a1515506", + "9436ff05729a77f673e815e464aeaa75", + ), + ( + 24864, + "074253ad5d5a5d2b072e7aeaffa04a06119ec812a88ca43481fe5e2dce02cf6736952095cd342ec70b833c12fc1777f4", + "69d8951b96866a08efbb65f2bc31cfbc", + ), + ( + 25641, + "c0a301f90597c05cf19e60c35378676764086b7156e455f4800347f8a6e733d644e4cc709fb9d95a9211f3e1e10c762a", + "3561c9802143c306ecc5e07e3b976d9e", + ), + ( + 26418, + "3c839e59d945b841acb604e1b9ae3df36a291444ce0bcae336ee875beaf208bf10af7342b375429ecb92ec54d11a5907", + "3032ffdb8daee11b2e739132c6175615", + ), + ( + 27195, + "3dc59b16603950dfc26a90bf036712eb088412e8de4d1b27c3fa6be6502ac12d89d194764fb53c3dc7d90fa696ba5a16", + "49436717edff7cd67c9a1be16d524f07", + ), + ( + 27972, + "4fbc0d40ff13376b8ed5382890cdea337b4a0c9c31b477c4008d2ef8299bd5ab771ba70b1b4b743f8f7caa1f0164d1a1", + "64a9856a3bb81dc81ff1bc1025192dc9", + ), + ( + 28749, + "6ab191aa6327f229cc94e8c7b1b7ee30bc723e6aeaf3050eb7d14cb491c3513254e9b19894c2b4f071d298401fd31945", + "101f2ffea60f246a3b57c4a530d67cf1", + ), + ( + 29526, + "d06dece58e6c7345986aae4b7f15b3317653f5387d6262f389b5cbbe804568124a876eabb89204e96b3c0f7b552df3c4", + "5c0e873adba65a9f4cb24cce4f194b18", + ), + ( + 30303, + "7a33c1268eafdc1f89ad460fa4ded8d3df9a3cabe4339706877878c64a2c8080cf3fa5ea7f2f24744e3341476b1eb5a5", + "b7dc708fc46ce5cde24a31ad549fec83", + ), + ( + 31080, + "37bf1f9fca6d705b989b2d63259ca924dc860fc6027e07d9aad79b94841227739774f5d324590df45d8f41249ef742ea", + "8ead50308c281e699b79b69dad7ecb91", + ), + ( + 31857, + "91b120c73be86f9d53326fa707cfa1411e5ac76ab998a2d7ebd73a75e3b1a04c9f0855d102184b8a3fd5d99818b0b134", + "6056d09595bd16bfa317c6f87ce64bb7", + ), + ( + 32634, + "42cc255c06184ead57b27efd0cefb0f2c788c8962a6fd15db3f25533a7f49700bca85af916f9e985f1941a6e66943b38", + "3b15e332d2f53bb97e1a9d03e6113b97", + ), + ( + 33411, + "737f8bb8f3fd03a9d13e50abba3a42f4491c36eda3eb215085abda733227ec490cb863ffbd68f915c8fb2926a899fbc3", + "b2c647d25c46aab4d4a5ede4a3b4576d", + ), + ( + 34188, + "e9caa36505e19628175d1ce8b933267380099753a41e503fa2f894cea17b7692f0b27079ed33cdd1293db9a35722d561", + "a2882adfd00f22823250215b12b3a1fd", + ), + ( + 34965, + "81ddc348ebbdfb963daa5d0c1b51bbb73cacd883d4fc4316db6bd3388779beff7be0655bbac73951f89dc53832199c11", + "f33106eb8104f3780350c6d4f82333ad", + ), + ( + 35742, + "308ce31daf40dab707e2cb4c4a5307bc403e24c971ae1e30e998449f804a167fe5f2cf617d585851b6fe9f2b4209f09c", + "44070ac90cbf350ab92289cc063e978c", + ), + ( + 36519, + "71f51b4bddbe8a52f18be75f9bdb3fca0773901b794de845450fb308c34775ede1a6da9a82b61e9682a29a3ef71274e2", + "0e387704298c444bf3afba0edc0c1c1c", + ), + ( + 37296, + "478ac94eee8c5f96210003fcb478392b91f2ef6fc3a729774e5fe82a2d8d0abc54ae1d25b3eaefb061e2bd43b70ca4ea", + "fb65ebeda52cd5848d303c0677cecb7f", + ), + ( + 38073, + "bc3a9390618da7d644be932627353e2c92024df939d2d8497fba61fae3dd822cdd3e130c1707f4a9d5d4a0cbb4b3e0b3", + "d790d529a837ec79f7cc3f66ed9a399f", + ), + ( + 38850, + "ef0e63a53a10e56477c47e13320b8a7d330aee3a4363c850edc56c0707a2686478e5a5193f54ceb33467ab7e8a22aa21", + "6f2c18742f106f16fc290767342fb62b", + ), + ( + 39627, + "c16f63533c099d872d9a01c326db7756e7eb488c756b9a6ebf575993d8ea2eb45c572b2e162f061e145710e0e21e8e18", + "a57afde7938b223ae5e109a03db4ee4c", + ), + ( + 40404, + "ade484ae8c13465a73589ef14789bb6891c933453e198df84edd34b4ac5c83aa90f2cf61fa072fa4d8f5b5c4cd68fa9e", + "a01d13009db86ac442f7afd39d83309f", + ), + ( + 41181, + "6c5c7eed0e043a0bd60bcac9b5b546e150028d70c1efefc9ff69037ef4dc1a36878b171b9f2a639df822d11054a0e405", + "6321c8622ca5866c875d340206d06a28", + ), + ( + 41958, + "dd311c54222fb0d92858719cf5b1c51bb5e3ca2539ffd68f1dd6c7e38969495be935804855ccdcc4b4cf221fcdbda886", + "cf401eb819b5dc5cd8c909aae9b3b34b", + ), + ( + 42735, + "31cda9d663199b32eff042dd16c0b909ba999641e77ba751c91752bfc4d595e17ec6467119e74a600b72da72ba287d0a", + "12fd6298ab5d744eb6ade3106565afad", + ), + ( + 43512, + "11b014057d51a8384d549d5d083c4406b575df6a9295853dd8f2f84f078cc241bb90495a119126b10b9510efcb68c0d3", + "a48a49eea5dc90359ef21f32132f8604", + ), + ( + 44289, + "b44f5dbeecd76ee7efe3fb4dfe10ba8135d7a5e4d104149f4a91c5c6ee9446d9be19fb4c9ba668b074466d3892e22228", + "07e1cbb7a19174d9b1e4d5a2c741cc14", + ), + ( + 45066, + "d87bbba3a3c739cab622386c89aeb685a70009fab1a606bd34622adfa3a75a05b58d56ee6b9874d414db38a6a32927b3", + "a27cd252712cd2a1a2d95dea39f888d4", + ), + ( + 45843, + "abb90e60ea13c6cb3b401b8e271637416b87fbede165dde7be1d34abe4427dae4b39b499352cacac909bc43fb94028c8", + "df3ae762b9257936feda435a61a9c3a1", + ), + ( + 46620, + "56d1132ee6e0f85543950d2d9667244b66b0ce6414eacd1859b128ed0b9026b31a25bfdcce3d1a0ce7c39d99f609c89c", + "cfe7c3c3f1cb615e2d210cc8136443e6", + ), + ( + 47397, + "ecb023ec4c23cf95d1848a38b359f1f590f172dee9d8fb1be6bc9c4fb2ce96f612d60d7b111de539ab8313a87b821176", + "501d24752bf55cb12239863981898a07", + ), + ( + 48174, + "34236ab60f05bb510aa0880fec358fb2002903efa14c912cab8a399e09418f97223ca2f7b8d6798c11d39e79032eaaa8", + "4ecaba4eae886aa429927188abab9623", + ), + ( + 48951, + "55e8b40fad90a3d8c85a0f4d5bcf5975b8a6e2fb78377109f5b607a5e367187fbbc9a1e978aab3228fbf43ad23d0ad13", + "84c43bc30eb4a67230b6c634fe3c7782", + ), + ( + 49728, + "14b1f896d0d01ecff4e456c3c392b1ca2bad9f1ef07713f84cdd89e663aa27ca77d80213ed57a89431eb992b11d98749", + "7f58c2f9a249f70fe1c6f9b4f65e5a1d", + ), + ( + 50505, + "1335b1fb56196e0b371fa53ab7445845fdefcea3eb2833478deb3526e2ec888945e95ee8239b52caae5b9920ba4f43bb", + "5fd729126b236ce3e0686fc706dce20f", + ), + ( + 51282, + "0d1983a6cab870c5e78f89a11dd30e7d2c71a3882f8bba3e71dc1b96a2d9fc6cc6d91d683b74456b886de34df792cfda", + "7731ae6e6c54dfde12f6116357e812ea", + ), + ( + 52059, + "9d619fb4aa8441baaefed7b778693c291f2c1441b206ec135930fac3529d26587ac36f4472949e0b198b51c0c5a9d0f8", + "39db2c996aea28996e03d576c118630f", + ), + ( + 52836, + "31dca4fa285878ba3efc3b66a248a078b69a11c3c73f81077377c4ffcb7002627aad5faa955e3141c1d8508aad68c8f6", + "32ac1e5a09e7e629ff95f30aa9b69c00", + ), + ( + 53613, + "931a9969cf2bb02302c32b1eecd4933805e2da403d85aaf98c82c68129fb95f089eb85c65a6fcbc7d81bedb39de0cabb", + "1a6f54b87c12868da530eac94d99eb31", + ), + ( + 54390, + "2f0742565801a37810ecb3f50a6f782e73a369a790d1a6a85135e7ffa12fc063db8909ab9eca7cf7308832887a6149d1", + "1b18ed6a8f901b7947626216839f0643", + ), + ( + 55167, + "901defbd308b54deef89acd0d94e4387b370f9d2e6f870d72da2e447ed3ebe69c5f9f144488bd6207a732102160bff47", + "1e0e6a05fcc0794121f617e28cfac1a0", + ), + ( + 55944, + "df984a5f7475250155dc4733a746e98446dc93a56a3f3bff691ddfef7deefb32b1da1b0e7e15cce443831ebfb3e30ada", + "876121af882d0ebeae38f111f3d4b6e8", + ), + ( + 56721, + "acb693ed837b33561408cb1eed636e0082ac404f3fd72d277fa146ae5cd81a1fde3645f4cdc7babd8ba044b78075cb67", + "5b90ed6c7943fc6da623c536e2ff1352", + ), + ( + 57498, + "dffb54bf5938e812076cfbf15cd524d72a189566c7980363a49dd89fb49e230d9742ef0b0e1ac543dca14366d735d152", + "22aee072457306e32747fbbbc3ae127c", + ), + ( + 58275, + "92dbc245a980fc78974f7a27e62c22b12a00be9d3ef8d3718ff85f6d5fbcbf1d9d1e0f0a3daeb8c2628d090550a0ff6b", + "5fa348117faba4ac8c9d9317ff44cd2d", + ), + ( + 59052, + "57721475cb719691850696d9a8ad4c28ca8ef9a7d45874ca21df4df250cb87ea60c464f4e3252e2d6161ed36c4b56d75", + "24d92ae7cac56d9c0276b06f7428d5df", + ), + ( + 59829, + "d0936026440b5276747cb9fb7dc96de5d4e7846c233ca5f6f9354b2b39f760333483cbe99ffa905facb347242f58a7ef", + "05c57068e183f9d835e7f461202f923c", + ), + ( + 60606, + "7b3bb3527b73a8692f076f6a503b2e09b427119543c7812db73c7c7fb2d43af9ecbd2a8a1452ac8ada96ad0bad7bb185", + "f958635a193fec0bfb958e97961381df", + ), + ( + 61383, + "ff0d00255a36747eced86acfccd0cf9ef09faa9f44c8cf382efec462e7ead66e562a971060c3f32798ba142d9e1640a2", + "838159b222e56aadde8229ed56a14095", + ), + ( + 62160, + "15806e088ed1428cd73ede3fecf5b60e2a616f1925004dadd2cab8e847059f795659659e82a4554f270baf88bf60af63", + "fed2aa0c9c0a73d499cc970aef21c52f", + ), + ( + 62937, + "cfad71b23b6da51256bd1ddbd1ac77977fe10b2ad0a830a23a794cef914bf71a9519d78a5f83fc411e8d8db996a45d4e", + "e1ea412fd3e1bd91c24b6b6445e8ff43", + ), + ( + 63714, + "7d03a3698a79b1af1663e3e485c2efdc306ecd87b2644f2e01d83a35999d6cdf12241b6114d60d107c10c0d0c9cc0d23", + "e6a3c3f3fd2d9cfcdc06cca2f59e9a83", + ), + ( + 64491, + "e12b168cce0e82ed1db88df549f39b3ff40b5884a09fceae69c4c3db13c1c37ea79531c47b2700d1c27774a1ab7e8b35", + "4cbb14d789f5cd8eca49ce9e1d442ea1", + ), + ( + 65268, + "056c9d1172cfa76ce7f19c605e5969c284b82dca155dc9c1ed58062ab4d5a7704e27fe69f3aa745b73f45f1cd0ee57df", + "8195187f092d52c2a8695b680568b934", + ), + ]; + + /// + const NIST_AES_GCM_TEST_VECTORS: &[GcmTV<[u8; 32]>] = &[ + GcmTV { + key: &hex!("b52c505a37d78eda5dd34f20c22540ea1b58963cf8e5bf8ffa85f9f2492505b4"), + nonce: &hex!("516c33929df5a3284ff463d7"), + plaintext: b"", + aad: b"", + ciphertext: b"", + tag: &hex!("bdc1ac884d332457a1d2664f168c76f0"), + }, + GcmTV { + key: &hex!("5fe0861cdc2690ce69b3658c7f26f8458eec1c9243c5ba0845305d897e96ca0f"), + nonce: &hex!("770ac1a5a3d476d5d96944a1"), + plaintext: b"", + aad: b"", + ciphertext: b"", + tag: &hex!("196d691e1047093ca4b3d2ef4baba216"), + }, + GcmTV { + key: &hex!("7620b79b17b21b06d97019aa70e1ca105e1c03d2a0cf8b20b5a0ce5c3903e548"), + nonce: &hex!("60f56eb7a4b38d4f03395511"), + plaintext: b"", + aad: b"", + ciphertext: b"", + tag: &hex!("f570c38202d94564bab39f75617bc87a"), + }, + GcmTV { + key: &hex!("7e2db00321189476d144c5f27e787087302a48b5f7786cd91e93641628c2328b"), + nonce: &hex!("ea9d525bf01de7b2234b606a"), + plaintext: b"", + aad: b"", + ciphertext: b"", + tag: &hex!("db9df5f14f6c9f2ae81fd421412ddbbb"), + }, + GcmTV { + key: &hex!("a23dfb84b5976b46b1830d93bcf61941cae5e409e4f5551dc684bdcef9876480"), + nonce: &hex!("5aa345908048de10a2bd3d32"), + plaintext: b"", + aad: b"", + ciphertext: b"", + tag: &hex!("f28217649230bd7a40a9a4ddabc67c43"), + }, + GcmTV { + key: &hex!("dfe928f86430b78add7bb7696023e6153d76977e56103b180253490affb9431c"), + nonce: &hex!("1dd0785af9f58979a10bd62d"), + plaintext: b"", + aad: b"", + ciphertext: b"", + tag: &hex!("a55eb09e9edef58d9f671d72207f8b3c"), + }, + GcmTV { + key: &hex!("34048db81591ee68224956bd6989e1630fcf068d7ff726ae81e5b29f548cfcfb"), + nonce: &hex!("1621d34cff2a5b250c7b76fc"), + plaintext: b"", + aad: b"", + ciphertext: b"", + tag: &hex!("4992ec3d57cccfa58fd8916c59b70b11"), + }, + GcmTV { + key: &hex!("a1114f8749c72b8cef62e7503f1ad921d33eeede32b0b5b8e0d6807aa233d0ad"), + nonce: &hex!("a190ed3ff2e238be56f90bd6"), + plaintext: b"", + aad: b"", + ciphertext: b"", + tag: &hex!("c8464d95d540fb191156fbbc1608842a"), + }, + GcmTV { + key: &hex!("ddbb99dc3102d31102c0e14b238518605766c5b23d9bea52c7c5a771042c85a0"), + nonce: &hex!("95d15ed75c6a109aac1b1d86"), + plaintext: b"", + aad: b"", + ciphertext: b"", + tag: &hex!("813d1da3775cacd78e96d86f036cff96"), + }, + GcmTV { + key: &hex!("1faa506b8f13a2e6660af78d92915adf333658f748f4e48fa20135a29e9abe5f"), + nonce: &hex!("e50f278d3662c99d750f60d3"), + plaintext: b"", + aad: b"", + ciphertext: b"", + tag: &hex!("aec7ece66b7344afd6f6cc7419cf6027"), + }, + GcmTV { + key: &hex!("f30b5942faf57d4c13e7a82495aedf1b4e603539b2e1599317cc6e53225a2493"), + nonce: &hex!("336c388e18e6abf92bb739a9"), + plaintext: b"", + aad: b"", + ciphertext: b"", + tag: &hex!("ddaf8ef4cb2f8a6d401f3be5ff0baf6a"), + }, + GcmTV { + key: &hex!("daf4d9c12c5d29fc3fa936532c96196e56ae842e47063a4b29bfff2a35ed9280"), + nonce: &hex!("5381f21197e093b96cdac4fa"), + plaintext: b"", + aad: b"", + ciphertext: b"", + tag: &hex!("7f1832c7f7cd7812a004b79c3d399473"), + }, + GcmTV { + key: &hex!("6b524754149c81401d29a4b8a6f4a47833372806b2d4083ff17f2db3bfc17bca"), + nonce: &hex!("ac7d3d618ab690555ec24408"), + plaintext: b"", + aad: b"", + ciphertext: b"", + tag: &hex!("db07a885e2bd39da74116d06c316a5c9"), + }, + GcmTV { + key: &hex!("cff083303ff40a1f66c4aed1ac7f50628fe7e9311f5d037ebf49f4a4b9f0223f"), + nonce: &hex!("45d46e1baadcfbc8f0e922ff"), + plaintext: b"", + aad: b"", + ciphertext: b"", + tag: &hex!("1687c6d459ea481bf88e4b2263227906"), + }, + GcmTV { + key: &hex!("3954f60cddbb39d2d8b058adf545d5b82490c8ae9283afa5278689041d415a3a"), + nonce: &hex!("8fb3d98ef24fba03746ac84f"), + plaintext: b"", + aad: b"", + ciphertext: b"", + tag: &hex!("7fb130855dfe7a373313361f33f55237"), + }, + GcmTV { + key: &hex!("78dc4e0aaf52d935c3c01eea57428f00ca1fd475f5da86a49c8dd73d68c8e223"), + nonce: &hex!("d79cf22d504cc793c3fb6c8a"), + plaintext: b"", + aad: &hex!("b96baa8c1c75a671bfb2d08d06be5f36"), + ciphertext: b"", + tag: &hex!("3e5d486aa2e30b22e040b85723a06e76"), + }, + GcmTV { + key: &hex!("4457ff33683cca6ca493878bdc00373893a9763412eef8cddb54f91318e0da88"), + nonce: &hex!("699d1f29d7b8c55300bb1fd2"), + plaintext: b"", + aad: &hex!("6749daeea367d0e9809e2dc2f309e6e3"), + ciphertext: b"", + tag: &hex!("d60c74d2517fde4a74e0cd4709ed43a9"), + }, + GcmTV { + key: &hex!("4d01c96ef9d98d4fb4e9b61be5efa772c9788545b3eac39eb1cacb997a5f0792"), + nonce: &hex!("32124a4d9e576aea2589f238"), + plaintext: b"", + aad: &hex!("d72bad0c38495eda50d55811945ee205"), + ciphertext: b"", + tag: &hex!("6d6397c9e2030f5b8053bfe510f3f2cf"), + }, + GcmTV { + key: &hex!("8378193a4ce64180814bd60591d1054a04dbc4da02afde453799cd6888ee0c6c"), + nonce: &hex!("bd8b4e352c7f69878a475435"), + plaintext: b"", + aad: &hex!("1c6b343c4d045cbba562bae3e5ff1b18"), + ciphertext: b"", + tag: &hex!("0833967a6a53ba24e75c0372a6a17bda"), + }, + GcmTV { + key: &hex!("22fc82db5b606998ad45099b7978b5b4f9dd4ea6017e57370ac56141caaabd12"), + nonce: &hex!("880d05c5ee599e5f151e302f"), + plaintext: b"", + aad: &hex!("3e3eb5747e390f7bc80e748233484ffc"), + ciphertext: b"", + tag: &hex!("2e122a478e64463286f8b489dcdd09c8"), + }, + GcmTV { + key: &hex!("fc00960ddd698d35728c5ac607596b51b3f89741d14c25b8badac91976120d99"), + nonce: &hex!("a424a32a237f0df530f05e30"), + plaintext: b"", + aad: &hex!("cfb7e05e3157f0c90549d5c786506311"), + ciphertext: b"", + tag: &hex!("dcdcb9e4004b852a0da12bdf255b4ddd"), + }, + GcmTV { + key: &hex!("69749943092f5605bf971e185c191c618261b2c7cc1693cda1080ca2fd8d5111"), + nonce: &hex!("bd0d62c02ee682069bd1e128"), + plaintext: b"", + aad: &hex!("6967dce878f03b643bf5cdba596a7af3"), + ciphertext: b"", + tag: &hex!("378f796ae543e1b29115cc18acd193f4"), + }, + GcmTV { + key: &hex!("fc4875db84819834b1cb43828d2f0ae3473aa380111c2737e82a9ab11fea1f19"), + nonce: &hex!("da6a684d3ff63a2d109decd6"), + plaintext: b"", + aad: &hex!("91b6fa2ab4de44282ffc86c8cde6e7f5"), + ciphertext: b"", + tag: &hex!("504e81d2e7877e4dad6f31cdeb07bdbd"), + }, + GcmTV { + key: &hex!("9f9fe7d2a26dcf59d684f1c0945b5ffafe0a4746845ed317d35f3ed76c93044d"), + nonce: &hex!("13b59971cd4dd36b19ac7104"), + plaintext: b"", + aad: &hex!("190a6934f45f89c90067c2f62e04c53b"), + ciphertext: b"", + tag: &hex!("4f636a294bfbf51fc0e131d694d5c222"), + }, + GcmTV { + key: &hex!("ab9155d7d81ba6f33193695cf4566a9b6e97a3e409f57159ae6ca49655cca071"), + nonce: &hex!("26a9f8d665d163ddb92d035d"), + plaintext: b"", + aad: &hex!("4a203ac26b951a1f673c6605653ec02d"), + ciphertext: b"", + tag: &hex!("437ea77a3879f010691e288d6269a996"), + }, + GcmTV { + key: &hex!("0f1c62dd80b4a6d09ee9d787b1b04327aa361529ffa3407560414ac47b7ef7bc"), + nonce: &hex!("c87613a3b70d2a048f32cb9a"), + plaintext: b"", + aad: &hex!("8f23d404be2d9e888d219f1b40aa29e8"), + ciphertext: b"", + tag: &hex!("36d8a309acbb8716c9c08c7f5de4911e"), + }, + GcmTV { + key: &hex!("f3e954a38956df890255f01709e457b33f4bfe7ecb36d0ee50f2500471eebcde"), + nonce: &hex!("9799abd3c52110c704b0f36a"), + plaintext: b"", + aad: &hex!("ddb70173f44157755b6c9b7058f40cb7"), + ciphertext: b"", + tag: &hex!("b323ae3abcb415c7f420876c980f4858"), + }, + GcmTV { + key: &hex!("0625316534fbd82fe8fdea50fa573c462022c42f79e8b21360e5a6dce66dde28"), + nonce: &hex!("da64a674907cd6cf248f5fbb"), + plaintext: b"", + aad: &hex!("f24d48e04f5a0d987ba7c745b73b0364"), + ciphertext: b"", + tag: &hex!("df360b810f27e794673a8bb2dc0d68b0"), + }, + GcmTV { + key: &hex!("28f045ac7c4fe5d4b01a9dcd5f1ad3efff1c4f170fc8ab8758d97292868d5828"), + nonce: &hex!("5d85de95b0bdc44514143919"), + plaintext: b"", + aad: &hex!("601d2158f17ab3c7b4dcb6950fbdcdde"), + ciphertext: b"", + tag: &hex!("42c3f527418cf2c3f5d5010ccba8f271"), + }, + GcmTV { + key: &hex!("19310eed5f5f44eb47075c105eb31e36bbfd1310f741b9baa66a81138d357242"), + nonce: &hex!("a1247120138fa4f0e96c992c"), + plaintext: b"", + aad: &hex!("29d746414333e0f72b4c3f44ec6bfe42"), + ciphertext: b"", + tag: &hex!("d5997e2f956df3fa2c2388e20f30c480"), + }, + GcmTV { + key: &hex!("886cff5f3e6b8d0e1ad0a38fcdb26de97e8acbe79f6bed66959a598fa5047d65"), + nonce: &hex!("3a8efa1cd74bbab5448f9945"), + plaintext: b"", + aad: &hex!("519fee519d25c7a304d6c6aa1897ee1eb8c59655"), + ciphertext: b"", + tag: &hex!("f6d47505ec96c98a42dc3ae719877b87"), + }, + GcmTV { + key: &hex!("6937a57d35fe6dc3fc420b123bccdce874bd4c18f2e7c01ce2faf33d3944fd9d"), + nonce: &hex!("a87247797b758467b96310f3"), + plaintext: b"", + aad: &hex!("ead961939a33dd578f8e93db8b28a1c85362905f"), + ciphertext: b"", + tag: &hex!("599de3ecf22cb867f03f7f6d9fd7428a"), + }, + GcmTV { + key: &hex!("e65a331776c9dcdf5eba6c59e05ec079d97473bcdce84daf836be323456263a0"), + nonce: &hex!("ca731f768da01d02eb8e727e"), + plaintext: b"", + aad: &hex!("d7274586517bf1d8da866f4a47ad0bcf2948a862"), + ciphertext: b"", + tag: &hex!("a8abe7a8085f25130a7206d37a8aaf6d"), + }, + GcmTV { + key: &hex!("77bb1b6ef898683c981b2fc899319ffbb6000edca22566b634db3a3c804059e5"), + nonce: &hex!("354a19283769b3b991b05a4c"), + plaintext: b"", + aad: &hex!("b5566251a8a8bec212dc08113229ff8590168800"), + ciphertext: b"", + tag: &hex!("e5c2dccf8fc7f296cac95d7071cb8d7d"), + }, + GcmTV { + key: &hex!("2a43308d520a59ed51e47a3a915e1dbf20a91f0886506e481ad3de65d50975b4"), + nonce: &hex!("bcbf99733d8ec90cb23e6ce6"), + plaintext: b"", + aad: &hex!("eb88288729289d26fe0e757a99ad8eec96106053"), + ciphertext: b"", + tag: &hex!("01b0196933aa49123eab4e1571250383"), + }, + GcmTV { + key: &hex!("2379b35f85102db4e7aecc52b705bc695d4768d412e2d7bebe999236783972ff"), + nonce: &hex!("918998c4801037b1cd102faa"), + plaintext: b"", + aad: &hex!("b3722309e0f066225e8d1659084ebb07a93b435d"), + ciphertext: b"", + tag: &hex!("dfb18aee99d1f67f5748d4b4843cb649"), + }, + GcmTV { + key: &hex!("98b3cb7537167e6d14a2a8b2310fe94b715c729fdf85216568150b556d0797ba"), + nonce: &hex!("bca5e2e5a6b30f18d263c6b2"), + plaintext: b"", + aad: &hex!("260d3d72db70d677a4e3e1f3e11431217a2e4713"), + ciphertext: b"", + tag: &hex!("d6b7560f8ac2f0a90bad42a6a07204bc"), + }, + GcmTV { + key: &hex!("30341ae0f199b10a15175d00913d5029526ab7f761c0b936a7dd5f1b1583429d"), + nonce: &hex!("dbe109a8ce5f7b241e99f7af"), + plaintext: b"", + aad: &hex!("fe4bdee5ca9c4806fa024715fbf66ab845285fa7"), + ciphertext: b"", + tag: &hex!("ae91daed658e26c0d126575147af9899"), + }, + GcmTV { + key: &hex!("8232b6a1d2e367e9ce1ea8d42fcfc83a4bc8bdec465c6ba326e353ad9255f207"), + nonce: &hex!("cd2fb5ff9cf0f39868ad8685"), + plaintext: b"", + aad: &hex!("02418b3dde54924a9628de06004c0882ae4ec3bb"), + ciphertext: b"", + tag: &hex!("d5308f63708675ced19b2710afd2db49"), + }, + GcmTV { + key: &hex!("f9a132a50a508145ffd8294e68944ea436ce0f9a97e181f5e0d6c5d272311fc1"), + nonce: &hex!("892991b54e94b9d57442ccaf"), + plaintext: b"", + aad: &hex!("4e0fbd3799da250fa27911b7e68d7623bfe60a53"), + ciphertext: b"", + tag: &hex!("89881d5f786e6d53e0d19c3b4e6887d8"), + }, + GcmTV { + key: &hex!("0e3746e5064633ea9311b2b8427c536af92717de20eeb6260db1333c3d8a8114"), + nonce: &hex!("f84c3a1c94533f7f25cec0ac"), + plaintext: b"", + aad: &hex!("8c0d41e6135338c8d3e63e2a5fa0a9667ec9a580"), + ciphertext: b"", + tag: &hex!("479ccfe9241de2c474f2edebbb385c09"), + }, + GcmTV { + key: &hex!("b997e9b0746abaaed6e64b63bdf64882526ad92e24a2f5649df055c9ec0f1daa"), + nonce: &hex!("f141d8d71b033755022f0a7d"), + plaintext: b"", + aad: &hex!("681d6583f527b1a92f66caae9b1d4d028e2e631e"), + ciphertext: b"", + tag: &hex!("b30442a6395ec13246c48b21ffc65509"), + }, + GcmTV { + key: &hex!("87660ec1700d4e9f88a323a49f0b871e6aaf434a2d8448d04d4a22f6561028e0"), + nonce: &hex!("2a07b42593cd24f0a6fe406c"), + plaintext: b"", + aad: &hex!("1dd239b57185b7e457ced73ebba043057f049edd"), + ciphertext: b"", + tag: &hex!("df7a501049b37a534098cb45cb9c21b7"), + }, + GcmTV { + key: &hex!("ea4792e1f1717b77a00de4d109e627549b165c82af35f33ca7e1a6b8ed62f14f"), + nonce: &hex!("7453cc8b46fe4b93bcc48381"), + plaintext: b"", + aad: &hex!("46d98970a636e7cd7b76fc362ae88298436f834f"), + ciphertext: b"", + tag: &hex!("518dbacd36be6fba5c12871678a55516"), + }, + GcmTV { + key: &hex!("34892cdd1d48ca166f7ba73182cb97336c2c754ac160a3e37183d6fb5078cec3"), + nonce: &hex!("ed3198c5861b78c71a6a4eec"), + plaintext: b"", + aad: &hex!("a6fa6d0dd1e0b95b4609951bbbe714de0ae0ccfa"), + ciphertext: b"", + tag: &hex!("c6387795096b348ecf1d1f6caaa3c813"), + }, + GcmTV { + key: &hex!("f4069bb739d07d0cafdcbc609ca01597f985c43db63bbaaa0debbb04d384e49c"), + nonce: &hex!("d25ff30fdc3d464fe173e805"), + plaintext: b"", + aad: &hex!("3e1449c4837f0892f9d55127c75c4b25d69be334baf5f19394d2d8bb460cbf2120e14736d0f634aa792feca20e455f11"), + ciphertext: b"", + tag: &hex!("805ec2931c2181e5bfb74fa0a975f0cf"), + }, + GcmTV { + key: &hex!("62189dcc4beb97462d6c0927d8a270d39a1b07d72d0ad28840badd4f68cf9c8b"), + nonce: &hex!("859fda5247c888823a4b8032"), + plaintext: b"", + aad: &hex!("b28d1621ee110f4c9d709fad764bba2dd6d291bc003748faac6d901937120d41c1b7ce67633763e99e05c71363fceca8"), + ciphertext: b"", + tag: &hex!("27330907d0002880bbb4c1a1d23c0be2"), + }, + GcmTV { + key: &hex!("59012d85a1b90aeb0359e6384c9991e7be219319f5b891c92c384ade2f371816"), + nonce: &hex!("3c9cde00c23912cff9689c7c"), + plaintext: b"", + aad: &hex!("e5daf473a470860b55210a483c0d1a978d8add843c2c097f73a3cda49ac4a614c8e887d94e6692309d2ed97ebe1eaf5d"), + ciphertext: b"", + tag: &hex!("048239e4e5c2c8b33890a7c950cda852"), + }, + GcmTV { + key: &hex!("4be09b408ad68b890f94be5efa7fe9c917362712a3480c57cd3844935f35acb7"), + nonce: &hex!("8f350bd3b8eea173fc7370bc"), + plaintext: b"", + aad: &hex!("2819d65aec942198ca97d4435efd9dd4d4393b96cf5ba44f09bce4ba135fc8636e8275dcb515414b8befd32f91fc4822"), + ciphertext: b"", + tag: &hex!("a133cb7a7d0471dbac61fb41589a2efe"), + }, + GcmTV { + key: &hex!("13cb965a4d9d1a36efad9f6ca1ba76386a5bb160d80b0917277102357ac7afc8"), + nonce: &hex!("f313adec42a66d13c3958180"), + plaintext: b"", + aad: &hex!("717b48358898e5ccfea4289049adcc1bb0db3b3ebd1767ac24fb2b7d37dc80ea2316c17f14fb51b5e18cd5bb09afe414"), + ciphertext: b"", + tag: &hex!("81b4ef7a84dc4a0b1fddbefe37f53852"), + }, + GcmTV { + key: &hex!("d27f1bebbbdef0edca393a6261b0338abbc491262eab0737f55246458f6668cc"), + nonce: &hex!("fc062f857886e278f3a567d2"), + plaintext: b"", + aad: &hex!("2bae92dea64aa99189de8ea4c046745306002e02cfb46a41444ce8bfcc329bd4205963d9ab5357b026a4a34b1a861771"), + ciphertext: b"", + tag: &hex!("5c5a6c4613f1e522596330d45f243fdd"), + }, + GcmTV { + key: &hex!("7b4d19cd3569f74c7b5df61ab78379ee6bfa15105d21b10bf6096699539006d0"), + nonce: &hex!("fbed5695c4a739eded97b1e3"), + plaintext: b"", + aad: &hex!("c6f2e5d663bfaf668d014550ef2e66bf89978799a785f1f2c79a2cb3eb3f2fd4076207d5f7e1c284b4af5cffc4e46198"), + ciphertext: b"", + tag: &hex!("7101b434fb90c7f95b9b7a0deeeb5c81"), + }, + GcmTV { + key: &hex!("d3431488d8f048590bd76ec66e71421ef09f655d7cf8043bf32f75b4b2e7efcc"), + nonce: &hex!("cc766e98b40a81519fa46392"), + plaintext: b"", + aad: &hex!("93320179fdb40cbc1ccf00b872a3b4a5f6c70b56e43a84fcac5eb454a0a19a747d452042611bf3bbaafd925e806ffe8e"), + ciphertext: b"", + tag: &hex!("3afcc336ce8b7191eab04ad679163c2a"), + }, + GcmTV { + key: &hex!("a440948c0378561c3956813c031f81573208c7ffa815114ef2eee1eb642e74c6"), + nonce: &hex!("c1f4ffe54b8680832eed8819"), + plaintext: b"", + aad: &hex!("253438f132b18e8483074561898c5652b43a82cc941e8b4ae37e792a8ed6ec5ce2bcec9f1ffcf4216e46696307bb774a"), + ciphertext: b"", + tag: &hex!("129445f0a3c979a112a3afb10a24e245"), + }, + GcmTV { + key: &hex!("798706b651033d9e9bf2ce064fb12be7df7308cf45df44776588cd391c49ff85"), + nonce: &hex!("5a43368a39e7ffb775edfaf4"), + plaintext: b"", + aad: &hex!("926b74fe6381ebd35757e42e8e557601f2287bfc133a13fd86d61c01aa84f39713bf99a8dc07b812f0274c9d3280a138"), + ciphertext: b"", + tag: &hex!("89fe481a3d95c03a0a9d4ee3e3f0ed4a"), + }, + GcmTV { + key: &hex!("c3aa2a39a9fef4a466618d1288bb62f8da7b1cb760ccc8f1be3e99e076f08eff"), + nonce: &hex!("9965ba5e23d9453d7267ca5b"), + plaintext: b"", + aad: &hex!("93efb6a2affc304cb25dfd49aa3e3ccdb25ceac3d3cea90dd99e38976978217ad5f2b990d10b91725c7fd2035ecc6a30"), + ciphertext: b"", + tag: &hex!("00a94c18a4572dcf4f9e2226a03d4c07"), + }, + GcmTV { + key: &hex!("14e06858008f7e77186a2b3a7928a0c7fcee22136bc36f53553f20fa5c37edcd"), + nonce: &hex!("32ebe0dc9ada849b5eda7b48"), + plaintext: b"", + aad: &hex!("6c0152abfa485b8cd67c154a5f0411f22121379774d745f40ee577b028fd0e188297581561ae972223d75a24b488aed7"), + ciphertext: b"", + tag: &hex!("2625b0ba6ee02b58bc529e43e2eb471b"), + }, + GcmTV { + key: &hex!("fbb56b11c51a093ce169a6990399c4d741f62b3cc61f9e8a609a1b6ae8e7e965"), + nonce: &hex!("9c5a953247e91aceceb9defb"), + plaintext: b"", + aad: &hex!("46cb5c4f617916a9b1b2e03272cb0590ce716498533047d73c81e4cbe9278a3686116f5632753ea2df52efb3551aea2d"), + ciphertext: b"", + tag: &hex!("4f3b82e6be4f08756071f2c46c31fedf"), + }, + GcmTV { + key: &hex!("b303bf02f6a8dbb5bc4baccab0800db5ee06de648e2fae299b95f135c9b107cc"), + nonce: &hex!("906495b67ef4ce00b44422fa"), + plaintext: b"", + aad: &hex!("872c6c370926535c3fa1baec031e31e7c6c82808c8a060742dbef114961c314f1986b2131a9d91f30f53067ec012c6b7"), + ciphertext: b"", + tag: &hex!("64dde37169082d181a69107f60c5c6bb"), + }, + GcmTV { + key: &hex!("29f5f8075903063cb6d7050669b1f74e08a3f79ef566292dfdef1c06a408e1ab"), + nonce: &hex!("35f25c48b4b5355e78b9fb3a"), + plaintext: b"", + aad: &hex!("107e2e23159fc5c0748ca7a077e5cc053fa5c682ff5269d350ee817f8b5de4d3972041d107b1e2f2e54ca93b72cd0408"), + ciphertext: b"", + tag: &hex!("fee5a9baebb5be0165deaa867e967a9e"), + }, + GcmTV { + key: &hex!("03ccb7dbc7b8425465c2c3fc39ed0593929ffd02a45ff583bd89b79c6f646fe9"), + nonce: &hex!("fd119985533bd5520b301d12"), + plaintext: b"", + aad: &hex!("98e68c10bf4b5ae62d434928fc6405147c6301417303ef3a703dcfd2c0c339a4d0a89bd29fe61fecf1066ab06d7a5c31a48ffbfed22f749b17e9bd0dc1c6f8fbd6fd4587184db964d5456132106d782338c3f117ec05229b0899"), + ciphertext: b"", + tag: &hex!("cf54e7141349b66f248154427810c87a"), + }, + GcmTV { + key: &hex!("57e112cd45f2c57ddb819ea651c206763163ef016ceead5c4eae40f2bbe0e4b4"), + nonce: &hex!("188022c2125d2b1fcf9e4769"), + plaintext: b"", + aad: &hex!("09c8f445ce5b71465695f838c4bb2b00624a1c9185a3d552546d9d2ee4870007aaf3007008f8ae9affb7588b88d09a90e58b457f88f1e3752e3fb949ce378670b67a95f8cf7f5c7ceb650efd735dbc652cae06e546a5dbd861bd"), + ciphertext: b"", + tag: &hex!("9efcddfa0be21582a05749f4050d29fe"), + }, + GcmTV { + key: &hex!("a4ddf3cab7453aaefad616fd65d63d13005e9459c17d3173cd6ed7f2a86c921f"), + nonce: &hex!("06177b24c58f3be4f3dd4920"), + plaintext: b"", + aad: &hex!("f95b046d80485e411c56b834209d3abd5a8a9ddf72b1b916679adfdde893044315a5f4967fd0405ec297aa332f676ff0fa5bd795eb609b2e4f088db1cdf37ccff0735a5e53c4c12173a0026aea42388a7d7153a8830b8a901cf9"), + ciphertext: b"", + tag: &hex!("9d1bd8ecb3276906138d0b03fcb8c1bb"), + }, + GcmTV { + key: &hex!("24a92b24e85903cd4aaabfe07c310df5a4f8f459e03a63cbd1b47855b09c0be8"), + nonce: &hex!("22e756dc898d4cf122080612"), + plaintext: b"", + aad: &hex!("2e01b2536dbe376be144296f5c38fb099e008f962b9f0e896334b6408393bff1020a0e442477abfdb1727213b6ccc577f5e16cb057c8945a07e307264b65979aed96b5995f40250ffbaaa1a1f0eccf394015f6290f5e64dfe5ca"), + ciphertext: b"", + tag: &hex!("0d7f1aed4708a03b0c80b2a18785c96d"), + }, + GcmTV { + key: &hex!("15276fc64438578e0ec53366b90a0e23d93910fec10dc3003d9b3f3fa72db702"), + nonce: &hex!("c5e931946d5caebc227656d2"), + plaintext: b"", + aad: &hex!("3f967c83ba02e77c14e9d41185eb87f172250e93edb0f82b6742c124298ab69418358eddefa39fedc3cade9d80f036d864a59ead37c87727c56c701a8cd9634469ff31c704f5ee39354157e6558467b92824da36b1c071bedfe9"), + ciphertext: b"", + tag: &hex!("a0ffa19adcf31d061cd0dd46d24015ef"), + }, + GcmTV { + key: &hex!("ec09804a048bb854c71618b5a3a1c590910fc8a68455139b719486d2280ea59a"), + nonce: &hex!("d0b1247e7121a9276ac18ca3"), + plaintext: b"", + aad: &hex!("66b1d39d414596308e866b04476e053b71acd1cd07ce80939577ebbeace0430f7e4c0c185fe1d97ac7569950c83db40bbed0f1d173e1aa0dc28b4773705032d97551f7fcef7f55e4b69f88df650032dfc5232c156641104b5397"), + ciphertext: b"", + tag: &hex!("8440e6d864ab778f9be478f203162d86"), + }, + GcmTV { + key: &hex!("4adf86bfa547725e4b80365a5a327c107040facfff007dc35102066bd6a995c4"), + nonce: &hex!("b1018cc331911255a55a0795"), + plaintext: b"", + aad: &hex!("053ca4428c990b4456d3c1895d5d52deff675896de9faa53d8cf241255f4a31dc3399f15d83be380256616e5af043abfb37552655adf4f2e68dda24bc3736951134f359d9c0e288bb798b6c3ea46239231a3cb280066db9862e7"), + ciphertext: b"", + tag: &hex!("c7424f38084930bfc5edc1fcf1e7608d"), + }, + GcmTV { + key: &hex!("3c92e0d1e39a3c766573c4646c768c402ccff48a56682a93433512abf0456e00"), + nonce: &hex!("d57f319e590191841d2b98bd"), + plaintext: b"", + aad: &hex!("840d9394aa240e52ba152151c12acd1cd44881e8549dc832b71a45da7efcc74fb7e844d9fec25e5d497b8fb8f47f328c8d99045a19e366e6ce5e19dc26f67a81a94fa6c97c314d886e7b56eff144c09f6fa519db6308bc73422e"), + ciphertext: b"", + tag: &hex!("cb4ef72dbda4914d7434f9686f823e2f"), + }, + GcmTV { + key: &hex!("b66ba39733888a9e0a2e30452844161dc33cb383c02ce16c4efad5452509b5b5"), + nonce: &hex!("937cb665e37059b2e40359f2"), + plaintext: b"", + aad: &hex!("dbcd9694a8834860034e8ede3a5bd419fcf91c005ad99f488aa623f581622093f9d41e6a68e20fd202f302bcfc4417ca89090bfcd4d5224e8ff4eb5bbae4ecb27baa239f59c2f99cd47c0a269c497906b41a8f320a3dd2dc2de2"), + ciphertext: b"", + tag: &hex!("bdc8249302d9d666cf7168317c118743"), + }, + GcmTV { + key: &hex!("2f9fcd1043455695638c991a1b1d35ad57c18ef0727322747b7991abc3d787f3"), + nonce: &hex!("d06cf548f62869f4bed7a318"), + plaintext: b"", + aad: &hex!("432023c12cf1f614e1005112a17dbe6c5d54022a95cf6335a5bc55004c75f09a5699739ecf928e1c78d03dad5096a17a084afe1cc22041bbdfb5985bd08b0dcc59d2b08cd86b7aad597c4cd7b4ba6d6a7370b83995a6511a1f9e"), + ciphertext: b"", + tag: &hex!("322eb84fb6884f10cfb766c2e3ec779e"), + }, + GcmTV { + key: &hex!("21c5839a63e1230c06b086341c96ab74585e69bced94332caeb1fa77d510c24f"), + nonce: &hex!("5ab6e5ed6ee733be7250858c"), + plaintext: b"", + aad: &hex!("c92f08e30f67d42516133c48e97b65cc9e124365e110aba5e7b2cbe83debcc99edf4eb0007af052bda22d85900271b1897af4fd9ace6a2d09d984ac3de79d05de0b105a81b12542b2c48e27d409fd6992dd062d6055d6fc66842"), + ciphertext: b"", + tag: &hex!("53b0e450309d146459f2a1e46c9d9e23"), + }, + GcmTV { + key: &hex!("25a144f0fdba184125d81a87e7ed82fad33c701a094a67a81fe4692dc69afa31"), + nonce: &hex!("8bf575c5c2b45b4efc6746e4"), + plaintext: b"", + aad: &hex!("2a367cb0d3b7c5b8320b3cf95e82b6ba0bba1d09a2055885dedd9ef5641623682212103238b8f775cce42ddfd4f66382f2c3a5e8d6dff9163ced83580a75705574026b55db90f75f8abb3014c9a707021dedc075da38bebbf0a0"), + ciphertext: b"", + tag: &hex!("0e2ce9cac8dfcedb0572ec6cab621efd"), + }, + GcmTV { + key: &hex!("42bc841b3b03a807cd366a35ecec8a6aebef7c4cba0ec8cb8da0da41df8ccef1"), + nonce: &hex!("1bd46f85df5f4b3a126ee315"), + plaintext: b"", + aad: &hex!("ede3dcddbdc7d8e5d034c01661332ec349cb4e7a9fbaaf7abe2c647587db86cd427ce66908e070bc49ef838747e06b45ac486dfbea6f8698b4625e21e69db8327ec05cfd74accbe67ab644948cdb554af179a1e264e08fe16641"), + ciphertext: b"", + tag: &hex!("633ab6aaf5b32b53a794f6be6262fc5f"), + }, + GcmTV { + key: &hex!("c25b8500be73210596fc4a9fb4d84d1a3379a91e3f0a6cc4177d996046627679"), + nonce: &hex!("b56c48c0c4cd318b20437002"), + plaintext: b"", + aad: &hex!("bcd14dd043fdc8c327957e1c1428698543ec8602521a7c74788d296d37d4828f10f90656883d2531c702ebda2dc0a68dab00154577454455fad986ff8e0973098dbf370ff703ed98222b945726ed9be7909210ddbc672e99fdd9"), + ciphertext: b"", + tag: &hex!("8171d4ff60fe7ef6de0288326aa73223"), + }, + GcmTV { + key: &hex!("dd95259bc8eefa3e493cb1a6ba1d8ee2b341d5230d50363094a2cc3433b3d9b9"), + nonce: &hex!("a1a6ced084f4f13990750a9e"), + plaintext: b"", + aad: &hex!("d46db90e13684b26149cb3b7f776e228a0538fa1892c418aaad07aa08d3076f4a52bee8f130ff560db2b8d1009e9260fa6233fc22733e050c9e4f7cc699062765e261dffff1159e9060b26c8065dfab04055b58c82c340d987c9"), + ciphertext: b"", + tag: &hex!("9e120b01899fe2cb3e3a0b0c05045940"), + }, + GcmTV { + key: &hex!("31bdadd96698c204aa9ce1448ea94ae1fb4a9a0b3c9d773b51bb1822666b8f22"), + nonce: &hex!("0d18e06c7c725ac9e362e1ce"), + plaintext: &hex!("2db5168e932556f8089a0622981d017d"), + aad: b"", + ciphertext: &hex!("fa4362189661d163fcd6a56d8bf0405a"), + tag: &hex!("d636ac1bbedd5cc3ee727dc2ab4a9489"), + }, + GcmTV { + key: &hex!("460fc864972261c2560e1eb88761ff1c992b982497bd2ac36c04071cbb8e5d99"), + nonce: &hex!("8a4a16b9e210eb68bcb6f58d"), + plaintext: &hex!("99e4e926ffe927f691893fb79a96b067"), + aad: b"", + ciphertext: &hex!("133fc15751621b5f325c7ff71ce08324"), + tag: &hex!("ec4e87e0cf74a13618d0b68636ba9fa7"), + }, + GcmTV { + key: &hex!("f78a2ba3c5bd164de134a030ca09e99463ea7e967b92c4b0a0870796480297e5"), + nonce: &hex!("2bb92fcb726c278a2fa35a88"), + plaintext: &hex!("f562509ed139a6bbe7ab545ac616250c"), + aad: b"", + ciphertext: &hex!("e2f787996e37d3b47294bf7ebba5ee25"), + tag: &hex!("00f613eee9bdad6c9ee7765db1cb45c0"), + }, + GcmTV { + key: &hex!("48e6af212da1386500454c94a201640c2151b28079240e40d72d2a5fd7d54234"), + nonce: &hex!("ef0ff062220eb817dc2ece94"), + plaintext: &hex!("c7afeecec1408ad155b177c2dc7138b0"), + aad: b"", + ciphertext: &hex!("9432a620e6a22307e06a321d66846fd4"), + tag: &hex!("e3ea499192f2cd8d3ab3edfc55897415"), + }, + GcmTV { + key: &hex!("79cd8d750fc8ea62a2714edcd9b32867c7c4da906c56e23a644552f5b812e75a"), + nonce: &hex!("9bbfdb81015d2b57dead2de5"), + plaintext: &hex!("f980ad8c55ebd31ee6f98f44e92bff55"), + aad: b"", + ciphertext: &hex!("41a34d1e759c859e91b8cf5d3ded1970"), + tag: &hex!("68cd98406d5b322571e750c30aa49834"), + }, + GcmTV { + key: &hex!("130ae450c18efb851057aaa79575a0a090194be8b2c95469a0e8e380a8f48f42"), + nonce: &hex!("b269115396f81b39e0c38f47"), + plaintext: &hex!("036cf36280dee8355c82abc4c1fdb778"), + aad: b"", + ciphertext: &hex!("09f7568fd8181652e556f0dda5a49ed5"), + tag: &hex!("d10b61947cae275b7034f5259ba6fc28"), + }, + GcmTV { + key: &hex!("9c7121289aefc67090cabed53ad11658be72a5372761b9d735e81d2bfc0e3267"), + nonce: &hex!("ade1702d2051b8dd203b5419"), + plaintext: &hex!("b95bcaa2b31403d76859a4c301c50b56"), + aad: b"", + ciphertext: &hex!("628285e6489090dde1b9a60674785003"), + tag: &hex!("9f516af3f3b93d610edbc5ba6e2d115f"), + }, + GcmTV { + key: &hex!("0400b42897011fc20fd2280a52ef905d6ebf1b055b48c97067bd786d678ec4ea"), + nonce: &hex!("0abfb0a41496b453358409d9"), + plaintext: &hex!("20c8230191e35f4e9b269d59cf5521f6"), + aad: b"", + ciphertext: &hex!("dd8c38087daffbbb3ebb57ebf5ee5f78"), + tag: &hex!("bfb07aa5049ee350ec6fb1397f37087b"), + }, + GcmTV { + key: &hex!("56690798978c154ff250ba78e463765f2f0ce69709a4551bd8cb3addeda087b6"), + nonce: &hex!("cf37c286c18ad4ea3d0ba6a0"), + plaintext: &hex!("2d328124a8d58d56d0775eed93de1a88"), + aad: b"", + ciphertext: &hex!("3b0a0267f6ecde3a78b30903ebd4ca6e"), + tag: &hex!("1fd2006409fc636379f3d4067eca0988"), + }, + GcmTV { + key: &hex!("8a02a33bdf87e7845d7a8ae3c8727e704f4fd08c1f2083282d8cb3a5d3cedee9"), + nonce: &hex!("599f5896851c968ed808323b"), + plaintext: &hex!("4ade8b32d56723fb8f65ce40825e27c9"), + aad: b"", + ciphertext: &hex!("cb9133796b9075657840421a46022b63"), + tag: &hex!("a79e453c6fad8a5a4c2a8e87821c7f88"), + }, + GcmTV { + key: &hex!("23aaa78a5915b14f00cf285f38ee275a2db97cb4ab14d1aac8b9a73ff1e66467"), + nonce: &hex!("4a675ec9be1aab9632dd9f59"), + plaintext: &hex!("56659c06a00a2e8ed1ac60572eee3ef7"), + aad: b"", + ciphertext: &hex!("e6c01723bfbfa398d9c9aac8c683bb12"), + tag: &hex!("4a2f78a9975d4a1b5f503a4a2cb71553"), + }, + GcmTV { + key: &hex!("fe647f72e95c469027f4d7778429a2e8e90d090268d4fa7df44f65c0af84190a"), + nonce: &hex!("4f40ae2a83a9b480e4686c90"), + plaintext: &hex!("31fd6cce3f0d2b0d18e0af01c4b5609e"), + aad: b"", + ciphertext: &hex!("54c769fd542f0d3022f1335a7c410b61"), + tag: &hex!("106cb7cbcd967da6cad646039c753474"), + }, + GcmTV { + key: &hex!("fce205515f0551b1797128a2132d8e002ea5ab1beb99c5e7e8329398cf478e10"), + nonce: &hex!("20209a0d4a3b9bfddeef39a0"), + plaintext: &hex!("7d663e31a2f6ffef17e536684dae2e87"), + aad: b"", + ciphertext: &hex!("6529712030fb659dc11ab719f6a4c402"), + tag: &hex!("58699464d062aba505508c576c4e07dd"), + }, + GcmTV { + key: &hex!("cd33003ff18f6f3369dd9a35381261ba660ce0a769864475152e677066540337"), + nonce: &hex!("20bffe9064ce76d275204138"), + plaintext: &hex!("acaf53d4dd2fe12cd44450b0d9adcc92"), + aad: b"", + ciphertext: &hex!("a669fda0444b180165f90815dc992b33"), + tag: &hex!("6e31f5a56c4790cedcc2368c51d0639b"), + }, + GcmTV { + key: &hex!("381873b5f9579d8241f0c61f0d9e327bb9f678691714aaa48ea7d92678d43fe7"), + nonce: &hex!("3fc8bec23603158e012d65e5"), + plaintext: &hex!("7b622e9b408fe91f6fa800ecef838d36"), + aad: b"", + ciphertext: &hex!("8ca4de5b4e2ab22431a009f3ddd01bae"), + tag: &hex!("b3a7f80e3edf322622731550164cd747"), + }, + GcmTV { + key: &hex!("92e11dcdaa866f5ce790fd24501f92509aacf4cb8b1339d50c9c1240935dd08b"), + nonce: &hex!("ac93a1a6145299bde902f21a"), + plaintext: &hex!("2d71bcfa914e4ac045b2aa60955fad24"), + aad: &hex!("1e0889016f67601c8ebea4943bc23ad6"), + ciphertext: &hex!("8995ae2e6df3dbf96fac7b7137bae67f"), + tag: &hex!("eca5aa77d51d4a0a14d9c51e1da474ab"), + }, + GcmTV { + key: &hex!("7da3bccaffb3464178ca7c722379836db50ce0bfb47640b9572163865332e486"), + nonce: &hex!("c04fd2e701c3dc62b68738b3"), + plaintext: &hex!("fd671cab1ee21f0df6bb610bf94f0e69"), + aad: &hex!("fec0311013202e4ffdc4204926ae0ddf"), + ciphertext: &hex!("6be61b17b7f7d494a7cdf270562f37ba"), + tag: &hex!("5e702a38323fe1160b780d17adad3e96"), + }, + GcmTV { + key: &hex!("a359b9584beec189527f8842dda6b6d4c6a5db2f889635715fa3bcd7967c0a71"), + nonce: &hex!("8616c4cde11b34a944caba32"), + plaintext: &hex!("33a46b7539d64c6e1bdb91ba221e3007"), + aad: &hex!("e1796fca20cb3d3ab0ade69b2a18891e"), + ciphertext: &hex!("b0d316e95f3f3390ba10d0274965c62b"), + tag: &hex!("aeaedcf8a012cc32ef25a62790e9334c"), + }, + GcmTV { + key: &hex!("8c83238e7b3b58278200b54940d779d0a0750673aab0bf2f5808dd15dc1a8c49"), + nonce: &hex!("70f8f4ebe408f61a35077956"), + plaintext: &hex!("6e57f8572dd5b2247410f0d4c7424186"), + aad: &hex!("e1cbf83924f1b8d1014b97db56c25a15"), + ciphertext: &hex!("4a11acb9611251df01f79f16f8201ffb"), + tag: &hex!("9732be4ad0569586753d90fabb06f62c"), + }, + GcmTV { + key: &hex!("fe21919bb320af8744c9e862b5b7cf8b81ad3ad1fb0e7d7d710a688d3eed154b"), + nonce: &hex!("38bc3917aa1925f40850c082"), + plaintext: &hex!("aea53b1ea79a71c3a4b83c92a0c979f1"), + aad: &hex!("f24102fa7e6b819bb3ff47f90844db9c"), + ciphertext: &hex!("2fb8b697bf8f7a2eea25fe702a3ae0a9"), + tag: &hex!("5be77e827737ad7c4f79e0e343fe010d"), + }, + GcmTV { + key: &hex!("499e8a3f39ac4abc62dd4e1a6133042e74785972b6b501bfaffefc8bb29fd312"), + nonce: &hex!("5c728dbbef9dcc0ff483e891"), + plaintext: &hex!("b44014c7fc6b3f15d126a881fbe2bd2b"), + aad: &hex!("82300dab592f840ae991efa3623a6203"), + ciphertext: &hex!("578fe5e1aef7619f392c027c838a239e"), + tag: &hex!("49fdc724f05eb56ea9e3fd14b61ad567"), + }, + GcmTV { + key: &hex!("2775d3e7a8fc665bb9a59edc22eb136add194824ed8f2adb449177404c739716"), + nonce: &hex!("73f16c054e166696df679a2e"), + plaintext: &hex!("c9f3bce40310b6c0a3fd62742e4f3617"), + aad: &hex!("23199a1c9b7244913952ca4f7e7444f4"), + ciphertext: &hex!("72c85c10756266d00a9a4340b2cb3137"), + tag: &hex!("5881e4565b42394e62d5daf0d1ebc593"), + }, + GcmTV { + key: &hex!("425a341c67e6d873870f54e2cc5a2984c734e81729c0dbaaeee050309f1ce674"), + nonce: &hex!("0c09b7b4e9e097317b791433"), + plaintext: &hex!("76dda644b3faca509b37def0319f30cc"), + aad: &hex!("4300a721547846761e4bf8df2b6ec1d6"), + ciphertext: &hex!("1dd80daa0fc9e47e43897c64a6663f5e"), + tag: &hex!("5d69b34d8c3b12f783faaea7e93685db"), + }, + GcmTV { + key: &hex!("dd5c48988a6e9f9f60be801ba5c090f224a1b53d6601ec5858eab7b7784a8d5e"), + nonce: &hex!("43562d48cd4110a66d9ca64e"), + plaintext: &hex!("2cda2761fd0be2b03f9714fce8d0e303"), + aad: &hex!("55e568309fc6cb0fb0e0e7d2511d4116"), + ciphertext: &hex!("f2cfb6f5446e7aa172adfcd66b92a98d"), + tag: &hex!("e099c64d2966e780ce7d2eaae97f47d8"), + }, + GcmTV { + key: &hex!("2bdad9c3e5de6e4e101b7f16e727c690db95eacf4b0ccbdec7aab6fb9fc80486"), + nonce: &hex!("a5cf3967d244074d2153c576"), + plaintext: &hex!("84c867ec36cc6fe3487f5192fdfd390b"), + aad: &hex!("6bdae72b5ed0e4d1f10064ebd02cf85c"), + ciphertext: &hex!("53c8fa437c1b5fa91abbd6508b3878ce"), + tag: &hex!("7859593d127324be8b9cf1d43ead4d82"), + }, + GcmTV { + key: &hex!("01e92afdb5d956be12d38b09252966c5728d26f3c72e54bb62bbc55ae590e716"), + nonce: &hex!("886e55364eeb90e87ac79bbe"), + plaintext: &hex!("6c6570385f3d6d937e54a3a2e95bc9eb"), + aad: &hex!("c76aabb7f44b942a81feb50249d2131a"), + ciphertext: &hex!("423b749a507f437b431114962180d352"), + tag: &hex!("54d859320a49281368297da7d4e37326"), + }, + GcmTV { + key: &hex!("46921319217598cb64256fe49abca1f18a9d1dbca360f8630afb5c6137cb42b5"), + nonce: &hex!("290827cf981415760ec3b37a"), + plaintext: &hex!("480d32b191c2e201aed03680f93ea2da"), + aad: &hex!("535ee80b12f581baaf8027e6e3900e31"), + ciphertext: &hex!("89ace4f73583fb1ac260dea99b54055e"), + tag: &hex!("7b8b8358363c175a66e6fb48d1bc2222"), + }, + GcmTV { + key: &hex!("e18cd9b01b59bc0de1502efb74c3642997fe7dfb8d80c8a73caffe7726807d33"), + nonce: &hex!("bd087b384c40841b3839ba02"), + plaintext: &hex!("62f7f3a12b8c5f6747fcfe192d850b19"), + aad: &hex!("fe69f837961b1d83f27fbf68e6791a1c"), + ciphertext: &hex!("bacfccf6397424e96caf761e71dd3e3a"), + tag: &hex!("9c9a5b65420f83e766c7c051680e8e58"), + }, + GcmTV { + key: &hex!("68ee463b3153d9a042e5e3685def6f90f7659a203441de337fb94831cbeae9b2"), + nonce: &hex!("9c4a9254c485236cf838de7e"), + plaintext: &hex!("73731054514f3fb0102c7a1df809f212"), + aad: &hex!("d55820e7acbb27d23c7df32938cf7d42"), + ciphertext: &hex!("13b7823cac37f40eb811e3c966d16a67"), + tag: &hex!("76288c33a66ff6451e2cec6c4ba4935e"), + }, + GcmTV { + key: &hex!("64bd594daf279e3172f9aa713b35b7fce8f43083792bc7d1f10919131f400a7b"), + nonce: &hex!("339a2c40e9d9507c34228649"), + plaintext: &hex!("2b794cb4c98450463a3e225ab33f3f30"), + aad: &hex!("2b9544807b362ebfd88146e2b02c9270"), + ciphertext: &hex!("434d703b8d1069ad8036288b7c2d1ae6"), + tag: &hex!("7d31e397c0c943cbb16cfb9539a6a17d"), + }, + GcmTV { + key: &hex!("83688deb4af8007f9b713b47cfa6c73e35ea7a3aa4ecdb414dded03bf7a0fd3a"), + nonce: &hex!("0b459724904e010a46901cf3"), + plaintext: &hex!("33d893a2114ce06fc15d55e454cf90c3"), + aad: &hex!("794a14ccd178c8ebfd1379dc704c5e208f9d8424"), + ciphertext: &hex!("cc66bee423e3fcd4c0865715e9586696"), + tag: &hex!("0fb291bd3dba94a1dfd8b286cfb97ac5"), + }, + GcmTV { + key: &hex!("013f549af9ecc2ee0259d5fc2311059cb6f10f6cd6ced3b543babe7438a88251"), + nonce: &hex!("e45e759a3bfe4b652dc66d5b"), + plaintext: &hex!("79490d4d233ba594ece1142e310a9857"), + aad: &hex!("b5fe530a5bafce7ae79b3c15471fa68334ab378e"), + ciphertext: &hex!("619443034e4437b893a45a4c89fad851"), + tag: &hex!("6da8a991b690ff6a442087a356f8e9e3"), + }, + GcmTV { + key: &hex!("4b2815c531d2fceab303ec8bca739a97abca9373b7d415ad9d6c6fa9782518cc"), + nonce: &hex!("47d647a72b3b5fe19f5d80f7"), + plaintext: &hex!("d3f6a645779e07517bd0688872e0a49b"), + aad: &hex!("20fd79bd0ee538f42b7264a5d098af9a30959bf5"), + ciphertext: &hex!("00be3b295899c455110a0ae833140c4d"), + tag: &hex!("d054e3997c0085e87055b79829ec3629"), + }, + GcmTV { + key: &hex!("2503b909a569f618f7eb186e4c4b81dbfe974c553e2a16a29aea6846293e1a51"), + nonce: &hex!("e4fa3dc131a910c75f61a38b"), + plaintext: &hex!("188d542f8a815695c48c3a882158958c"), + aad: &hex!("f80edf9b51f8fd66f57ce9af5967ec028245eb6e"), + ciphertext: &hex!("4d39b5494ca12b770099a8eb0c178aca"), + tag: &hex!("adda54ad0c7f848c1c72758406b49355"), + }, + GcmTV { + key: &hex!("6c8f34f14569f625aad7b232f59fa8b187ab24fadcdbaf7d8eb45da8f914e673"), + nonce: &hex!("6e2f886dd97be0e4c5bd488b"), + plaintext: &hex!("ac8aa71cfbf1e968ef5515531576e314"), + aad: &hex!("772ec23e49dbe1d923b1018fc2bef4b579e46241"), + ciphertext: &hex!("cb0ce70345e950b429e710c47d9c8d9b"), + tag: &hex!("9dceea98c438b1d9c154e5386180966d"), + }, + GcmTV { + key: &hex!("182fe560614e1c6adfd1566ac44856df723dcb7e171a7c5796b6d3f83ef3d233"), + nonce: &hex!("8484abca6877a8622bfd2e3c"), + plaintext: &hex!("92ca46b40f2c75755a28943a68a8d81c"), + aad: &hex!("2618c0f7fe97772a0c97638cca238a967987c5e5"), + ciphertext: &hex!("ed1941b330f4275d05899f8677d73637"), + tag: &hex!("3fe93f1f5ffa4844963de1dc964d1996"), + }, + GcmTV { + key: &hex!("65a290b2fabe7cd5fb2f6d627e9f1f79c2c714bffb4fb86e9df3e5eab28320ed"), + nonce: &hex!("5a5ed4d5592a189f0737cf47"), + plaintext: &hex!("662dda0f9c8f92bc906e90288100501c"), + aad: &hex!("ad1c7f7a7fb7f8fef4819c1dd1a67e007c99a87b"), + ciphertext: &hex!("8eb7cb5f0418da43f7e051c588776186"), + tag: &hex!("2b15399ee23690bbf5252fb26a01ae34"), + }, + GcmTV { + key: &hex!("7b720d31cd62966dd4d002c9ea41bcfc419e6d285dfab0023ba21b34e754cb2f"), + nonce: &hex!("e1fb1f9229b451b72f89c333"), + plaintext: &hex!("1aa2948ed804f24e5d783b1bc959e086"), + aad: &hex!("7fdae42d0cf6a13873d3092c41dd3a19a9ea90f9"), + ciphertext: &hex!("8631d3c6b6647866b868421b6a3a548a"), + tag: &hex!("a31febbe169d8d6f391a5e60ef6243a0"), + }, + GcmTV { + key: &hex!("a2aec8f3438ab4d6d9ae566a2cf9101ad3a3cc20f83674c2e208e8ca5abac2bb"), + nonce: &hex!("815c020686c52ae5ddc81680"), + plaintext: &hex!("a5ccf8b4eac22f0e1aac10b8d62cdc69"), + aad: &hex!("86120ce3aa81445a86d971fdb7b3b33c07b25bd6"), + ciphertext: &hex!("364c9ade7097e75f99187e5571ec2e52"), + tag: &hex!("64c322ae7a8dbf3d2407b12601e50942"), + }, + GcmTV { + key: &hex!("e5104cfcbfa30e56915d9cf79efcf064a1d4ce1919b8c20de47eab0c106d67c1"), + nonce: &hex!("d1a5ec793597745c7a31b605"), + plaintext: &hex!("7b6b303381441f3fdf9a0cf79ee2e9e0"), + aad: &hex!("9931678430ff3aa765b871b703dfcc43fb1b8594"), + ciphertext: &hex!("425d48a76001bed9da270636be1f770b"), + tag: &hex!("76ff43a157a6748250a3fdee7446ed22"), + }, + GcmTV { + key: &hex!("f461d1b75a72d942aa096384dc20cf8514a9ad9a9720660add3f318284ca3014"), + nonce: &hex!("d0495f25874e5714a1149e94"), + plaintext: &hex!("d9e4b967fdca8c8bae838a5da95d7cce"), + aad: &hex!("1133f372e3db22456e7ea92f29dff7f1d92864d3"), + ciphertext: &hex!("1df711e6fbcba22b0564c6e36051a3f7"), + tag: &hex!("f0563b7494d5159289b644afc4e8e397"), + }, + GcmTV { + key: &hex!("a9a98ef5076ceb45c4b60a93aeba102507f977bc9b70ded1ad7d422108cdaa65"), + nonce: &hex!("54a1bc67e3a8a3e44deec232"), + plaintext: &hex!("ede93dd1eaa7c9859a0f709f86a48776"), + aad: &hex!("10cfef05e2cd1edd30db5c028bd936a03df03bdc"), + ciphertext: &hex!("3d3b61f553ab59a9f093cac45afa5ac0"), + tag: &hex!("7814cfc873b3398d997d8bb38ead58ef"), + }, + GcmTV { + key: &hex!("d9e17c9882600dd4d2edbeae9a224d8588ff5aa210bd902d1080a6911010c5c5"), + nonce: &hex!("817f3501e977a45a9e110fd4"), + plaintext: &hex!("d74d968ea80121aea0d7a2a45cd5388c"), + aad: &hex!("d216284811321b7591528f0af5a3f2768429e4e8"), + ciphertext: &hex!("1587c8b00e2c197f32a21019feeee99a"), + tag: &hex!("63ea43c03d00f8ae5724589cb6f64480"), + }, + GcmTV { + key: &hex!("ec251b45cb70259846db530aff11b63be00a951827020e9d746659bef2b1fd6f"), + nonce: &hex!("e41652e57b624abd84fe173a"), + plaintext: &hex!("75023f51ba81b680b44ea352c43f700c"), + aad: &hex!("92dd2b00b9dc6c613011e5dee477e10a6e52389c"), + ciphertext: &hex!("29274599a95d63f054ae0c9b9df3e68d"), + tag: &hex!("eb19983b9f90a0e9f556213d7c4df0f9"), + }, + GcmTV { + key: &hex!("61f71fdbe29f56bb0fdf8a9da80cef695c969a2776a88e62cb3d39fca47b18e3"), + nonce: &hex!("77f1d75ab0e3a0ed9bf2b981"), + plaintext: &hex!("110a5c09703482ef1343396d0c3852d3"), + aad: &hex!("c882691811d3de6c927d1c9f2a0f15f782d55c21"), + ciphertext: &hex!("7e9daa4983283facd29a93037eb70bb0"), + tag: &hex!("244930965913ebe0fa7a0eb547b159fb"), + }, + GcmTV { + key: &hex!("e4fed339c7b0cd267305d11ab0d5c3273632e8872d35bdc367a1363438239a35"), + nonce: &hex!("0365882cf75432cfd23cbd42"), + plaintext: &hex!("fff39a087de39a03919fbd2f2fa5f513"), + aad: &hex!("8a97d2af5d41160ac2ff7dd8ba098e7aa4d618f0f455957d6a6d0801796747ba57c32dfbaaaf15176528fe3a0e4550c9"), + ciphertext: &hex!("8d9e68f03f7e5f4a0ffaa7650d026d08"), + tag: &hex!("3554542c478c0635285a61d1b51f6afa"), + }, + GcmTV { + key: &hex!("bd93c7bfc850b33c86484e04859ed374beaee9d613bdca6f072d1d182aeebd04"), + nonce: &hex!("6414c7749effb9af7e5c4762"), + plaintext: &hex!("b6de1699931f2252efc98d491d22ee12"), + aad: &hex!("76f43d5664c7ac1b4de43f2e2c4bc71f6918e0762f40e5dd5597ef4ff215855a4fd26d3ea6ccbd4e10789948fa692433"), + ciphertext: &hex!("a6c7e52f2018b823506e48064ffe6ee4"), + tag: &hex!("175e653c9036f66835f10cf1c82d1741"), + }, + GcmTV { + key: &hex!("df0125a826c7fe49243d89cbdd7562aafd2103fa2783cf901976b5f5d481cdcb"), + nonce: &hex!("f63c1461b2964929d035d9bf"), + plaintext: &hex!("cc27ff68f981e4d6fb1918427c3d6b9e"), + aad: &hex!("0bf602ec47593e44ac1b88244455fa04359e338057b0a0ba057cb506d546d4d6d8538640fe7dd3d5864bd33b5a33d768"), + ciphertext: &hex!("b8fa150af93078574ac7c4615f88647d"), + tag: &hex!("4584553ac3ccdf8b0efae517652d3a18"), + }, + GcmTV { + key: &hex!("d33ea320cec0e43dfc1e3d1d8ccca2dd7e30ad3ea18ad7141cc83645d18771ae"), + nonce: &hex!("540009f321f41d00202e473b"), + plaintext: &hex!("e56cdd522d526d8d0cd18131a19ee4fd"), + aad: &hex!("a41162e1fe875a81fbb5667f73c5d4cbbb9c3956002f7867047edec15bdcac1206e519ee9c238c371a38a485c710da60"), + ciphertext: &hex!("8b624b6f5483f42f36c85dc7cf3e9609"), + tag: &hex!("2651e978d9eaa6c5f4db52391ac9bc7c"), + }, + GcmTV { + key: &hex!("7f35f5979b23321e6449f0f5ef99f2e7b796d52d560cc77aabfb621dbf3a6530"), + nonce: &hex!("cf0f6f3eed4cf374da714c77"), + plaintext: &hex!("4e9f53affdb5b1e91bf423d29c54401a"), + aad: &hex!("a676d35d93e12bfe0603f6aef2c3dd892a9b1ad22d476c3509d313256d4e98e4dda4e46e93b54cf59c2b90608a8fb3ad"), + ciphertext: &hex!("1714d55ef83df2927ee95ff22f1d90e6"), + tag: &hex!("4962a91d1071dd2c05934968d21eb43c"), + }, + GcmTV { + key: &hex!("06ecc134993506cf539b1e797a519fe1d9f34321fe6a0b05f1936285c35c93a4"), + nonce: &hex!("f2190861d1140bd080d79906"), + plaintext: &hex!("519c1fc45a628ec16c515427796711f7"), + aad: &hex!("a04f2723c2521181437ad63f7910481d5de98f3e2561cec3a177bdbcb5048619738852e0fb212a3caa741a353e4e89a8"), + ciphertext: &hex!("b36c793224ce3bb1b54144398fbdedb6"), + tag: &hex!("0030e6e84f6f8eb474ce8e071c2953dd"), + }, + GcmTV { + key: &hex!("734fa8b423b91e0ecccc7f554480eef57a82423a9f92b28d464320fba405a71c"), + nonce: &hex!("a6b5c78bb5791f4d121390ce"), + plaintext: &hex!("b496a99b39e0e94bb5829cfc3d7b3856"), + aad: &hex!("9ce25ff9b55dfa04e4271999a47cba8af8e83a390b090d1c4306b40ce8882624b662ff5867896396789295c19ec80d07"), + ciphertext: &hex!("904081a40484bb6454fc52cb6674e737"), + tag: &hex!("6a0787cf3921a71c35b5054954527823"), + }, + GcmTV { + key: &hex!("d106280b84f25b294f71c261f66a65c2efd9680e19f50316d237975052796392"), + nonce: &hex!("cfc6aa2aeba468c66bf4553f"), + plaintext: &hex!("57e937f8b9b814e965bb569fcf63aaac"), + aad: &hex!("012a43f9903a3808bf34fd6f77d831d9154205ded589964cae60d2e49c856b7a4100a55c8cd02f5e476f62e988dcbd2b"), + ciphertext: &hex!("c835f5d4fd30fe9b2edb4aff24803c60"), + tag: &hex!("e88426bb4619807f18a9cc9839754777"), + }, + GcmTV { + key: &hex!("81eb63bc47aba313d964a5335cfb039051520b3112fa54cab368e5243947d450"), + nonce: &hex!("18cc5dd875753ff51cc6f441"), + plaintext: &hex!("45f51399dff6a0dcd43f35256616d6be"), + aad: &hex!("24f766c56777312494245a4e6c7dbebbae4026e0907eadbc20a488982678161de7b924473c0a81ee59a0fa6905952b33"), + ciphertext: &hex!("a2fc7b0784ec4233142f9cde12ab9e98"), + tag: &hex!("4e60b8561cacfe7133740cd2bddefaa0"), + }, + GcmTV { + key: &hex!("0a997863786a4e97332224ed484ffca508b166f0603687200d99fd6accd45d83"), + nonce: &hex!("7a9acabd4b8d3e1036293a07"), + plaintext: &hex!("9d2c9ff39f57c96ecce287c68c5cd6eb"), + aad: &hex!("525fc5ac7fe93c183a3ef7c75e3fbd52dce956855aff385966f4d79966bdb3ec2019c466584d21bfee74511a77d82adb"), + ciphertext: &hex!("238441c65b2a1c41b302da0f52d40770"), + tag: &hex!("c351d93ab9491cdfb7fa15e7a251de22"), + }, + GcmTV { + key: &hex!("acbfeb7c595b704960c1097e93d3906534c23444c8acc1f8e969ce6c3fe8a46b"), + nonce: &hex!("28922ecac3013806c11660e6"), + plaintext: &hex!("e0d8c52d60c6ed6980abd4348f3f96f1"), + aad: &hex!("b1fe886107013ebdeb19315a9d096ed81803951a508f56f68202a7df00bebae0742dd1128c200952a049ef0cd7cfe4e6"), + ciphertext: &hex!("56fe1cf2c1d193b9b33badbf846f52cc"), + tag: &hex!("1cb4c14f50a54a64813ffc810f31f9f8"), + }, + GcmTV { + key: &hex!("f6e768475c33269596da1f5a5a38547a885006bebb9134e21274d8456e9f5529"), + nonce: &hex!("3579e5ac51d1f1b82ea352ca"), + plaintext: &hex!("0aa481f856f8b96547672e5ae5370f9e"), + aad: &hex!("6929b6053ba148304366164f79b1b9f592c9cb9bce65094cec5cb8b0fc63e20d86b17c8bf5a7b089a63c5eac1824ee93"), + ciphertext: &hex!("b2f4edf5f0b0bfc590fead6239b0f2fb"), + tag: &hex!("2540ceb5ef247c95d63df84c46468533"), + }, + GcmTV { + key: &hex!("2ca76112300bed65b87ba6ec887cd514f4633c1c96565fec8e3e69ae2ba88401"), + nonce: &hex!("964864510a8c957dcfb97d2f"), + plaintext: &hex!("0aff24b4c5aa45b81ce08ec2439be446"), + aad: &hex!("5aebdfd153a18763f36ecc9e8e9a01cb7b3f21e435b35b0da937c67e87c9ec058d08060a95e1eda0a5ab6546cca45094"), + ciphertext: &hex!("03da1f5a1403dbdd9f75a26113608ec0"), + tag: &hex!("a1c215d0c552a6061aa2b60afc3667a6"), + }, + GcmTV { + key: &hex!("c0ff018b6c337dde685c8279cf6de59d7ce4b288032b819e074b671e72abbc91"), + nonce: &hex!("f12e6b1e85f87ef4c9ccbb7b"), + plaintext: &hex!("f7512bbfa2d40d14be71b70f70701c99"), + aad: &hex!("0577e8d28c0e9e5cde3c8b2a1a2aa8e2fc3ec8e96768405fcfbd623be7fc4e2e395c59b5b3a8ea117ef211320bc1f857"), + ciphertext: &hex!("0187b4c2d52486b4417e5a013d553e5e"), + tag: &hex!("dba451e7339be8ebed3ea9683d1b4552"), + }, + GcmTV { + key: &hex!("d90c6948ac2353867e943069196a2c4d0c4d51e34e2505661b1d76f3e5f17ac5"), + nonce: &hex!("07e5623f474e2f0fe9f4c7d2"), + plaintext: &hex!("8a9fb1b384c0d1728099a4f7cb002f07"), + aad: &hex!("0de97574ae1bc6d3ef06c6ce03513ca47dff4728803e0aacc50564ee32b775fd535f5c8c30186550d99bff6f384af2dd"), + ciphertext: &hex!("4234a3a9fb199c3b293357983e8ac30b"), + tag: &hex!("d51e6f071dbab126f5fc9732967108ef"), + }, + GcmTV { + key: &hex!("80d755e24d129e68a5259ec2cf618e39317074a83c8961d3768ceb2ed8d5c3d7"), + nonce: &hex!("7598c07ba7b16cd12cf50813"), + plaintext: &hex!("5e7fd1298c4f15aa0f1c1e47217aa7a9"), + aad: &hex!("0e94f4c48fd0c9690c853ad2a5e197c5de262137b69ed0cdfa28d8d12413e4ffff15374e1cccb0423e8ed829a954a335ed705a272ad7f9abd1057c849bb0d54b768e9d79879ec552461cc04adb6ca0040c5dd5bc733d21a93702"), + ciphertext: &hex!("5762a38cf3f2fdf3645d2f6696a7eead"), + tag: &hex!("8a6708e69468915c5367573924fe1ae3"), + }, + GcmTV { + key: &hex!("dda7977efa1be95a0e41ed8bcd2aa648621945c95a9e28b63919e1d92d269fc3"), + nonce: &hex!("053f6e1be42af8894a6e86a0"), + plaintext: &hex!("6fa9b08176e9963927afba1e5f969a42"), + aad: &hex!("cb5114a001989339657427eb88329d6ce9c69694dc91a69b7557d62184e57832ec76d162fc9c47490bb3d78e5899445cecf85d36cb1f07fed5a3d82aaf7e9590f3ed74ad13b13c8adbfc7f29d7b151448d6f29d11d0bd3d03b76"), + ciphertext: &hex!("d4adbff3ec8edade29b9a1b748c31b54"), + tag: &hex!("3b331733c753858c22d309ceb0f9488c"), + }, + GcmTV { + key: &hex!("d7da934ad057dc06bd1ec234fcc4efdc5119037a440b5827de25915f22dd47e5"), + nonce: &hex!("1b54c4ea37d2395ef70dcc72"), + plaintext: &hex!("86d5567658361198348207ede7a46da6"), + aad: &hex!("735de4596a80e64e38a12ab24ef73881d6ed3b533cb2c101025c3615acd2114150feeca84ade4e563bc4a300eb4a0cd97a184a293f0ac063e4f3c61e7fcdb331bcc6459fafaf0e2dda881f34eb717f4ee8c4b6890d3ef59721f3"), + ciphertext: &hex!("70a1c1d7c200ba5ae1b6f29917bb19f2"), + tag: &hex!("a25d51cccb198bed33de0b98df249c2d"), + }, + GcmTV { + key: &hex!("930ebb4b9b9c35094be374cc0b700c437b3c46b45d489a716c30f93cd5f986c9"), + nonce: &hex!("7a21e5febd82ec9b97bfbe83"), + plaintext: &hex!("980086665d08a365f6bbe20ae51116f7"), + aad: &hex!("9f2ed5f6cf9e2d6505d3c99a8f81a7dfc5658dd085eba966c8b3206230973a086ec36fe948573baee108fca941bce53dad73180877cd497976209c1adf8a9861f0215560df064caf0ef2f99445c11816f5b8deeafedd682b5fb2"), + ciphertext: &hex!("05baaefdeb0c33674a8064a2e9951aaf"), + tag: &hex!("2ec7efd2564d4e09a6ab852f3af49939"), + }, + GcmTV { + key: &hex!("70213d8949a65f463d13206071fab1b4c6b614fd3cee0d340d2d806de6714a93"), + nonce: &hex!("f8529d3e4f155cbb1ffb3d0a"), + plaintext: &hex!("47d47a5fd32a2a416f921cc7f00c0f81"), + aad: &hex!("112360db39b867dabaaa1d777bd881df2104b69fba15a4f37a832f5da38ad8a8c7c46db93e5b4eadf8b9a5a75508ad1457994c133c5ac85509eedfb13b90a2cf6c56a3c778582939362008608b08f9c4866a0e38744572114598"), + ciphertext: &hex!("b220b69bd851a17fbc5b725fb912f11e"), + tag: &hex!("4c3436943d58501c0826ae5827bc063e"), + }, + GcmTV { + key: &hex!("7a5834230ebbbf616630f2edb3ad4320182433c0546ac1e34bc9fd046e4a0ed9"), + nonce: &hex!("d27dd6212b6defdcbbc701bb"), + plaintext: &hex!("b4def1251427ade064a9614e353dda3f"), + aad: &hex!("3bc12f3bb88ea4f8a2184959bb9cd68911a78458b27e9b528ccecafe7f13f303dc714722875f26b136d18a3acfe82b53ad5e13c71f3f6db4b0fd59fffd9cd4422c73f2c31ac97010e5edf5950dc908e8df3d7e1cbf7c34a8521e"), + ciphertext: &hex!("88f94965b4350750e11a2dc139ccaef1"), + tag: &hex!("8a61f0166e70c9bfdd198403e53a68a5"), + }, + GcmTV { + key: &hex!("c3f10586f246aacadcce3701441770c03cfec940afe1908c4c537df4e01c50a0"), + nonce: &hex!("4f52faa1fa67a0e5f4196452"), + plaintext: &hex!("79d97ea3a2edd65045821ea745a44742"), + aad: &hex!("46f9a22b4e52e1526513a952dbee3b91f69595501e0177d50ff364638588c08d92fab8c58a969bdcc84c468d8498c4f06392b99ed5e0c484507fc48dc18d87c40e2ed848b43150be9d36f14cf2cef1310ba4a745adcc7bdc41f6"), + ciphertext: &hex!("560cf716e56190e9397c2f103629eb1f"), + tag: &hex!("ff7c9124879644e80555687d273c55d8"), + }, + GcmTV { + key: &hex!("ad70ebcf889e88b867ded0e4838ca66d6991499046a5671d99e91ed463ae78b1"), + nonce: &hex!("561e13b335718fcbee364100"), + plaintext: &hex!("82d5568872a4cef12238c0feb14f0fb4"), + aad: &hex!("e037bd7306eec185b9cb4e3bf295232da19005957086d62e6fb342284f05feaa0e81d6c95071e7e4d7b6aad7b00f7e7863dd0fc16303a8304bb8855305f28067f4be71eed95ff90e046382116229f0fd3d2c3ef2e87e0d0e7950"), + ciphertext: &hex!("771c6d091f8190ddbdb8886d9ce2ebd5"), + tag: &hex!("5009abd1ebeb26dab852346ea6d8aee3"), + }, + GcmTV { + key: &hex!("a452fa24b381e7165ee90f3371c2b0db2176f848a0354c78e92f2f1f89bbc511"), + nonce: &hex!("4bd904dfe18241eb5455d912"), + plaintext: &hex!("3f43df23ea940f3680a4b679b56db579"), + aad: &hex!("64f1a9d21deb183cff84f1aef5be83dbfc72e275f229eb5d59ace143605e8901dfa8f4724be24c86b5429bc84b629971fe1f9663b7537427b45dfb67d5f04506df4ee2c33d7f15af9f6e86058b131b7e6042b43a55bf6915f048"), + ciphertext: &hex!("c054974c4562f8536aef2734f10e09fc"), + tag: &hex!("2c5cafaf7b1f7581c5ec13080994e33c"), + }, + GcmTV { + key: &hex!("209ea3c4dd0420a4d63dbb72099a0202c9b0709f3b1221565f890511eef8005b"), + nonce: &hex!("43775083e4008816129f5d40"), + plaintext: &hex!("b4967f8c4fb1b34b6ff43a22d34fae5c"), + aad: &hex!("9abc653a2347fc6e5a8cb9bdc251dff7c56109797c387494c0ed55570330961eb5b11087603e08ad293d0dd55571008e62d1163f67cf829e28d27beba65553bd11d8838f8a7a5f1fe05500befbaf97839801e99ecf998882c707"), + ciphertext: &hex!("a8d22a6e25232938d3f8600a66be80da"), + tag: &hex!("2ef93cc03c17bbfb6626144697fd2422"), + }, + GcmTV { + key: &hex!("dabd63ac5274b26842c2695c9850d7accc1693ee2aeee1e2e1338bbbc5b80f87"), + nonce: &hex!("fd6790d620f12870b1d99b31"), + plaintext: &hex!("4a28048f5683679a557630a661f030e2"), + aad: &hex!("e4a06b9b205a7faadb21dc7fea8a0de0e013d717b61b24ec42f81afc8cdbc055573e971375da2fa5103a091317eab13b6a110ea211af257feabf52abafec23fd5b114b013d5c052199020573f8b7b7ae6958f733e87efa0426c2"), + ciphertext: &hex!("196d0345df259b47665bc233b798ebba"), + tag: &hex!("b0729d8b427ad048a7396cedf2257338"), + }, + GcmTV { + key: &hex!("b238df5e52e649d4b0a05e53020ac59e7d5bf49b8d04f8c30c356ed62dba9ed1"), + nonce: &hex!("f153f093c9a3479f999eda04"), + plaintext: &hex!("d48e779766afa73d7e04fc6fc3fa825e"), + aad: &hex!("45b5df0c15140e5ce7a19f4e02834e6027971e3e0e719626c29081a6301e95c71214345afac1908bb75ff2d3281261e6c5f41dc4e4796f054174a64f8e177f3f33321edfbd263e204135699428a09f34eb344211bfb9fac9afba"), + ciphertext: &hex!("b1989eb510843d8f35205dc3f949522f"), + tag: &hex!("616089990729228f673099514824d9b4"), + }, + GcmTV { + key: &hex!("f3dc2456d3b8947591a2d82b7319226b0f346cd4361bcc13b56da43e072a2774"), + nonce: &hex!("7a8acb5a84d7d01e3c00499e"), + plaintext: &hex!("ad075da908231ff9aae30daa6b847143"), + aad: &hex!("5e6be069effee27d34a8087c0d193f9f13e6440dc9fabfe24f6c867f831d06789d0dce92b2e3ff3ab9fe14202a8b42f384c25e3f3753dd503ec907a9b877f1707d64e4ac42909a7dee00c87c4a09d04de331515460ed101f5187"), + ciphertext: &hex!("9f224f2a1a1fbaade8b87b748971c0ac"), + tag: &hex!("cb5089d9dfaebf98e4b36ebc5f9a1a50"), + }, + GcmTV { + key: &hex!("f5a56b69a1562c77e8edebc327a20295c2eba7d406d899a622c53539626c9d72"), + nonce: &hex!("a395b8aca4508a6a5f3cb4d8"), + plaintext: &hex!("7de4638701bd2b600d7f8d26da7a75bc"), + aad: &hex!("2e4fca2b163e4403971716015386cd81bdd1e57f00f2936da408098341011f2644a38ddad799f70eaa54f6e430d4853ff2b9c44a35123670879a83120bd555c76b95b70de0c8054f9d08539a5795e70a2446d7b9fab3f7887c6b"), + ciphertext: &hex!("6508be2698ba9889b4e445b99190a5c5"), + tag: &hex!("3394106f257c2e15c815430f60bc24ba"), + }, + GcmTV { + key: &hex!("376371a780947256c52f07d80bb25a4d7e919ca8bd693b1a0ccbca748d2ce620"), + nonce: &hex!("27d7170f6f70f2fc40dfca78"), + plaintext: &hex!("7a279f9f8568b7c307490549b259226c"), + aad: &hex!("272c3559398ad774fa4b6895afc92870b2b92d310fa0debf0b7960e1fe38bfda64acd2fef26d6b177d8ab11d8afceee77374c6c18ad405d5ae323ad65fb6b04f0c809319133712f47636c5e042f15ed02f37ee7a10c643d7b178"), + ciphertext: &hex!("32284379d8c40ec18ee5774085d7d870"), + tag: &hex!("dcdee1a757f9758c944d296b1dabe7b2"), + }, + GcmTV { + key: &hex!("82c4f12eeec3b2d3d157b0f992d292b237478d2cecc1d5f161389b97f999057a"), + nonce: &hex!("7b40b20f5f397177990ef2d1"), + plaintext: &hex!("982a296ee1cd7086afad976945"), + aad: b"", + ciphertext: &hex!("ec8e05a0471d6b43a59ca5335f"), + tag: &hex!("113ddeafc62373cac2f5951bb9165249"), + }, + GcmTV { + key: &hex!("db4340af2f835a6c6d7ea0ca9d83ca81ba02c29b7410f221cb6071114e393240"), + nonce: &hex!("40e438357dd80a85cac3349e"), + plaintext: &hex!("8ddb3397bd42853193cb0f80c9"), + aad: b"", + ciphertext: &hex!("b694118c85c41abf69e229cb0f"), + tag: &hex!("c07f1b8aafbd152f697eb67f2a85fe45"), + }, + GcmTV { + key: &hex!("acad4a3588a7c5ec67832baee242b007c8f42ed7425d5a7e57b1070b7be2677e"), + nonce: &hex!("b11704ba368abadf8b0c2b98"), + plaintext: &hex!("2656b5fbec8a3666cad5f460b7"), + aad: b"", + ciphertext: &hex!("35c7114cabe39203df19413a99"), + tag: &hex!("16f4c7e5becf00db1223476a14c43ebc"), + }, + GcmTV { + key: &hex!("e5a0eb92cc2b064e1bc80891faf1fab5e9a17a9c3a984e25416720e30e6c2b21"), + nonce: &hex!("4742357c335913153ff0eb0f"), + plaintext: &hex!("8499893e16b0ba8b007d54665a"), + aad: b"", + ciphertext: &hex!("eb8e6175f1fe38eb1acf95fd51"), + tag: &hex!("88a8b74bb74fda553e91020a23deed45"), + }, + GcmTV { + key: &hex!("e78c477053f5dae5c02941061d397bc38dda5de3c9c8660a19de66c56c57fd22"), + nonce: &hex!("4f52c67c2bb748d192a5a4e2"), + plaintext: &hex!("91593e21e1f883af5c32d9be07"), + aad: b"", + ciphertext: &hex!("e37fbc56b0af200a7aa1bbe34e"), + tag: &hex!("29fe54eaaccf5e382601a15603c9f28c"), + }, + GcmTV { + key: &hex!("d0b13482037639aa797471a52b60f353b42e0ed271daa4f38a9293191cb78b72"), + nonce: &hex!("40fb7cae46adf3771bf3756a"), + plaintext: &hex!("938f40ac8e0e3b956aac5e9184"), + aad: b"", + ciphertext: &hex!("7dca05a1abe81928ccfb2164dd"), + tag: &hex!("5ea53ee170d9ab5f6cc047854e47cf60"), + }, + GcmTV { + key: &hex!("46da5ec688feead76a1ddcd60befb45074a2ef2254d7be26abdfd84629dbbc32"), + nonce: &hex!("9fb3b2b03925f476fc9a35f3"), + plaintext: &hex!("a41adc9fb4e25a8adef1180ec8"), + aad: b"", + ciphertext: &hex!("f55d4cbe9b14cea051fe7a2477"), + tag: &hex!("824753da0113d21186699dbb366c0589"), + }, + GcmTV { + key: &hex!("de3adf89f2fe246c07b0ce035f4af73cf2f65e5034dcfecfe9d7690ae1bdbd96"), + nonce: &hex!("a94aa4df0d8451644a5056c0"), + plaintext: &hex!("96825f6d6301db14a8d78fc2f4"), + aad: b"", + ciphertext: &hex!("784c6c3c24a022637cbc907c48"), + tag: &hex!("1eeaeddcdb4c72c4e8966950a319a4ef"), + }, + GcmTV { + key: &hex!("03c362288883327f6289bc1824e1c329ce485e0ce0e8d3405245283cf0f2eae2"), + nonce: &hex!("5de9f882c915c72729b2245c"), + plaintext: &hex!("f5c1c8d41de01d9c08d9f47ece"), + aad: b"", + ciphertext: &hex!("61af621953a126a2d1de559e92"), + tag: &hex!("fbdeb761238f2b70c5fb3dde0a7978f3"), + }, + GcmTV { + key: &hex!("e9ead7c59100b768aa6367d80c04a49bcd19fa8cc2e158dc8edeec3ea39b657d"), + nonce: &hex!("e81854665d2e0a97150fbab3"), + plaintext: &hex!("f8ccf69c52a873695367a42940"), + aad: b"", + ciphertext: &hex!("af2a7199602ee9ed2020c7b4cd"), + tag: &hex!("29715945ab1c034ecfcd91a466fc822e"), + }, + GcmTV { + key: &hex!("bc3e5b0fe423205904c32f870b9adec9d736a1616624043e819533fa97ed9b79"), + nonce: &hex!("335fe5180135673ce1a75144"), + plaintext: &hex!("295df9665eef999204f92acf24"), + aad: b"", + ciphertext: &hex!("3ac2a8a1b505a84677adfdb396"), + tag: &hex!("21f20aa0bb77d46d7290bc9c97a7a7bd"), + }, + GcmTV { + key: &hex!("ce889c73e0d64e272aba4bf9777afc7ee6457ddc9626ad931708ed7530d71b99"), + nonce: &hex!("fe61a6cda62fecd4e3b0c562"), + plaintext: &hex!("e2ae40ba5b4103b1a3066c1b57"), + aad: b"", + ciphertext: &hex!("185aa3508a37e6712b28191ec2"), + tag: &hex!("9ec1d567585aa467730cce92e536728e"), + }, + GcmTV { + key: &hex!("41e0cb1aed2fe53e0b688acb042a0c710a3c3ae3205b07c0af5191073abdfba9"), + nonce: &hex!("2f56e35216d88d34d08f6872"), + plaintext: &hex!("6482df0e4150e73dac51dc3220"), + aad: b"", + ciphertext: &hex!("9cb09b9927dfbe0f228e0a4307"), + tag: &hex!("fe7e87a596d63e2ab2aae46b64d466e8"), + }, + GcmTV { + key: &hex!("52a7662954d525cb00602b1ff5e937d41065ac4b921e284ffac73c04cfd462a0"), + nonce: &hex!("baffe73856ab1a47fb1feebf"), + plaintext: &hex!("9d0b5ca712f97caa1875d3ad87"), + aad: b"", + ciphertext: &hex!("fd01165380aedd6be226a66af3"), + tag: &hex!("35a492e39952c26456850b0172d723d1"), + }, + GcmTV { + key: &hex!("c4badb9766986faeb888b1db33060a9cd1f02e1afe7aaaea072d905750cb7352"), + nonce: &hex!("cc6966e9d81a298a561416d4"), + plaintext: &hex!("de68fb51731b45e7c2c5063923"), + aad: b"", + ciphertext: &hex!("f5be41f2c8c32e01098d433057"), + tag: &hex!("c82b1b012916ab6ed851d59829dad8ab"), + }, + GcmTV { + key: &hex!("dad89d9be9bba138cdcf8752c45b579d7e27c3dbb40f53e771dd8cfd500aa2d5"), + nonce: &hex!("cfb2aec82cfa6c7d89ee72ff"), + plaintext: &hex!("b526ba1050177d05b0f72f8d67"), + aad: &hex!("6e43784a91851a77667a02198e28dc32"), + ciphertext: &hex!("8b29e66e924ecae84f6d8f7d68"), + tag: &hex!("1e365805c8f28b2ed8a5cadfd9079158"), + }, + GcmTV { + key: &hex!("0d35d3dbd99cd5e088caf686b1cead9defe0c6001463e92e6d9fcdc2b0dcbaf6"), + nonce: &hex!("f9139eb9368d69ac48479d1f"), + plaintext: &hex!("5e2103eb3e739298c9f5c6ba0e"), + aad: &hex!("825cc713bb41c789c1ace0f2d0dd3377"), + ciphertext: &hex!("8ff3870eec0176d9f0c6c1b1a2"), + tag: &hex!("344234475538dc78c01f249f673e0862"), + }, + GcmTV { + key: &hex!("d35d64f1872bdcb422228f0d63f8e48977ed68d143f648ae2cd852f944b0e6dd"), + nonce: &hex!("0b2184aadbe8b515924dda5e"), + plaintext: &hex!("c8f999aa1a08871d74db490cf3"), + aad: &hex!("888f328d9e9eebbb9cb2704b5b880d66"), + ciphertext: &hex!("ad0d5e7c1065a34b27a256d144"), + tag: &hex!("8c8e7076950f7f2aeba62e1e761650d5"), + }, + GcmTV { + key: &hex!("9484b7ce3c118a8a2d556c2f7ba41fca34f60c9ea1070171459c9e7487c9537e"), + nonce: &hex!("87bc033522ae84d2abe863c5"), + plaintext: &hex!("14d8004793190563825e273dda"), + aad: &hex!("07ee18737b9bf8223979a01c59a90eb4"), + ciphertext: &hex!("43034a2c57ccacc367796d766a"), + tag: &hex!("4c981ca8b6e9e52092f5435e7ef55fbb"), + }, + GcmTV { + key: &hex!("4f4539e4a80ec01a14d6bb1bae0010f8a8b3f2cd0ac01adf239a9b2b755f0614"), + nonce: &hex!("2b6f00ce1570432bf52fdcac"), + plaintext: &hex!("820cc9389e7e74ca1cbb5a5fe6"), + aad: &hex!("0d72a13effe40544c57cc18005b998cb"), + ciphertext: &hex!("99553fdf3e777e2a4b3b6a5538"), + tag: &hex!("3cbf51640a3a93c3662c738e98fb36a2"), + }, + GcmTV { + key: &hex!("2f5e93ee24a8cd2fc6d3765f12d2179ddb8397783e136af9e0ac75f16fca451e"), + nonce: &hex!("0dc3c70a191f3722641fd701"), + plaintext: &hex!("4e96463793cdeda403668c4aee"), + aad: &hex!("ebab30cbcc99905354e4ee6f07c7db87"), + ciphertext: &hex!("ab03f8ca7b1b150bdc26d4e691"), + tag: &hex!("020546afff4290c4c8ef7fc38035ebfd"), + }, + GcmTV { + key: &hex!("a902e15d06ef5ad334d0ec6502e936ee53ef3f3608f7708848b11cefa92983d1"), + nonce: &hex!("b9f3e966efa43ab4aca1f2d8"), + plaintext: &hex!("393ff3dfe51cd43543e4e29fcc"), + aad: &hex!("2eaa35c00bf1cf8a81919bd04b43fd97"), + ciphertext: &hex!("7e8928b450c622ac8efe29d5a0"), + tag: &hex!("5a285de95990aef171629350bbcaf46e"), + }, + GcmTV { + key: &hex!("96657976da7692004e271b594e8304f77db9c9e77859246bb30a16239ba76a53"), + nonce: &hex!("79226100afea30644876e79a"), + plaintext: &hex!("2b0833a065c3853ee27c8968d0"), + aad: &hex!("ede7a9072a0086b9a1e55d900747cf76"), + ciphertext: &hex!("19373168f1a4052a57c6b8146f"), + tag: &hex!("debbf044325384b90a0c442d95455fb9"), + }, + GcmTV { + key: &hex!("630ea13eb5f52378b976ba2662f824dc622920759a15d2e341c446b03ea7bd5c"), + nonce: &hex!("0f9ebe47682f93d44c4db314"), + plaintext: &hex!("5c734964878a4250a3bf61fdd6"), + aad: &hex!("5ad8e9cffe622e9f35bdb185473868e5"), + ciphertext: &hex!("67cb6d943340d002d3323fcc4e"), + tag: &hex!("f5dc0f88f236560c4e2a6d6c15d3c0de"), + }, + GcmTV { + key: &hex!("c64f8a3ac230dce61b53d7b584f2309384274d4b32d404bc0c491f129781e52d"), + nonce: &hex!("7f4b3bcf763f9e2d08516a6d"), + plaintext: &hex!("fe581128ae9832d27ec58bd7ac"), + aad: &hex!("89ed6945547ee5998de1bb2d2f0bef1e"), + ciphertext: &hex!("81d7a8fdaf42b5716b892199c9"), + tag: &hex!("8183aaff4c0973fe56c02c2e0c7e4457"), + }, + GcmTV { + key: &hex!("dd73670fb221f7ee185f5818065e22dda3780fc900fc02ef00232c661d7bffce"), + nonce: &hex!("c33de65344cfbf228e1652bd"), + plaintext: &hex!("ada4d98147b30e5a901229952a"), + aad: &hex!("e1a5e52427f1c5b887575a6f2c445429"), + ciphertext: &hex!("6ed4e4bd1f953d47c5288c48f4"), + tag: &hex!("404e3a9b9f5ddab9ee169a7c7c2cf7af"), + }, + GcmTV { + key: &hex!("f6c5d9562b7dbdd0bf628ddc9d660c27841b06a638f56601f408f23aa2f66f4e"), + nonce: &hex!("67280bcb945ba6eda1c6c80a"), + plaintext: &hex!("f4caead242d180fbd2e6d32d0c"), + aad: &hex!("5b33716567b6c67b78ea5cd9349bcaaf"), + ciphertext: &hex!("fdfa39517d89ea47e6ccb0f831"), + tag: &hex!("91f9b540ca90e310a1f5c12c03d8c25e"), + }, + GcmTV { + key: &hex!("ce1d242f13de7638b870e0aa85843ea43a9255a4fa4d32057347f38e0267daeb"), + nonce: &hex!("86562be4621b4d5eb1983075"), + plaintext: &hex!("d20e59a8ef1a7de9096c3e6746"), + aad: &hex!("d48a9490a0b7deb023460608b7db79ce"), + ciphertext: &hex!("35ce69fb15d01159c52266537c"), + tag: &hex!("dc48f7b8d3feeeb26fcf63c0d2a889ec"), + }, + GcmTV { + key: &hex!("512753cea7c8a6165f2ebbd3768cc7b951029bd527b126233cf0841aff7568c7"), + nonce: &hex!("b79221802d8d97978041fe84"), + plaintext: &hex!("c63d6c1006b615275c085730b1"), + aad: &hex!("22fa0605b955a33468f3e60160b907f2"), + ciphertext: &hex!("bdb5d7f24732bdba1d2a429108"), + tag: &hex!("fca923d2941a6fd9d596b86c3afb0ad9"), + }, + GcmTV { + key: &hex!("e7b18429e3edded2d992ca27afab99e438b8aff25fc8460201fabe08e7d48ec2"), + nonce: &hex!("9db9b7320aaac68538e37bf7"), + plaintext: &hex!("c4713bc67a59928eee50039901"), + aad: &hex!("283e12a26e1646087b5b9d8c123dde1f"), + ciphertext: &hex!("a5932f92bda107d28f2a8aaa74"), + tag: &hex!("9a1357fd8ed21fe14d1ca2e597c3ef17"), + }, + GcmTV { + key: &hex!("69b458f2644af9020463b40ee503cdf083d693815e2659051ae0d039e606a970"), + nonce: &hex!("8d1da8ab5f91ccd09205944b"), + plaintext: &hex!("f3e0e09224256bf21a83a5de8d"), + aad: &hex!("036ad5e5494ef817a8af2f5828784a4bfedd1653"), + ciphertext: &hex!("c0a62d77e6031bfdc6b13ae217"), + tag: &hex!("a794a9aaee48cd92e47761bf1baff0af"), + }, + GcmTV { + key: &hex!("97431e565e8370a4879de962746a2fd67eca868b1c8e51eece2c1f94f74af407"), + nonce: &hex!("17fb63066e2726d282ecc610"), + plaintext: &hex!("e21629cc973fbe40176e621d9d"), + aad: &hex!("78e7374da7c77be5938de8dd76cf0308618306a9"), + ciphertext: &hex!("80dbd469de480389ba6c2fca52"), + tag: &hex!("4e284abb8b4f9f13c7497ae56df05fa5"), + }, + GcmTV { + key: &hex!("2b14ad68f442f7f92a72c7ba909bcf995c827b439d39a02f77c9bf8f84ab04dc"), + nonce: &hex!("4c847ea59f83d82b0ac0bc37"), + plaintext: &hex!("b3c4b26ebbfc717f51e874587d"), + aad: &hex!("8eb650f662be23191e88f1cd0422e57453090e21"), + ciphertext: &hex!("3e288478688e60178920090814"), + tag: &hex!("a928dc026986823062f37ec825c67b95"), + }, + GcmTV { + key: &hex!("11f41bf7d4b9ac7b0035ce54481ed1502ff05cfae02ffba9e502f61bfe785351"), + nonce: &hex!("06f5cf8c12c236e094c32014"), + plaintext: &hex!("bee374a32293cad5e1b28419b3"), + aad: &hex!("d15cbde6290b7723625c99ffa82a9c4c03ed214d"), + ciphertext: &hex!("3f8122deb6dbe0ff596441203d"), + tag: &hex!("60ef7f3723710b9ab744f8eea00267f7"), + }, + GcmTV { + key: &hex!("18ca572da055a2ebb479be6d6d7164e78f592b159cdea76e9fe208062d7b3fa1"), + nonce: &hex!("1b041e534ae20748262f3929"), + plaintext: &hex!("cda2fa0015361ecf684c6ba7d1"), + aad: &hex!("e8a925d7ce18dd456b071cb4c46655940efbe991"), + ciphertext: &hex!("740d8d578e2e7522c31019f471"), + tag: &hex!("f2eeb5af1bfedd10570a137fe2566c3f"), + }, + GcmTV { + key: &hex!("0de2ac5bfec9e8a859c3b6b86dde0537029cdca2d0844bf3e1d98f370e199be1"), + nonce: &hex!("1778e308e0221288f1eb4c5a"), + plaintext: &hex!("575d93a3416763cbd371b5a671"), + aad: &hex!("1362264f5655f71986aa788efd48f6fc13bb6ab4"), + ciphertext: &hex!("8f8df7ca83bf876b63c78e2c9a"), + tag: &hex!("16c74e315aab97efafbe95c9dcaa2d0c"), + }, + GcmTV { + key: &hex!("b381535a085bc4808fa7a139c7204e8a87c7145dfc8f3900df1fa9a9844fab35"), + nonce: &hex!("21ddc54d3c633f4a344a0e42"), + plaintext: &hex!("e4d958cee583010bbfd3a53021"), + aad: &hex!("7ac3ba600e08363ddb57c45a8670bb4abb869db0"), + ciphertext: &hex!("c42c81a312759cdb032aafe852"), + tag: &hex!("0c472591db3df8a7c67164591542dcc9"), + }, + GcmTV { + key: &hex!("29f21e5029ea4964b96dc6f4c34b2df4cce02f2fcf0f168ffd470e7858e0a0ad"), + nonce: &hex!("63a1c1ccc328280a90ff96fe"), + plaintext: &hex!("dc12113764c13c21432ca1ba33"), + aad: &hex!("454f447433f0948581956c4be1b19d932e89b492"), + ciphertext: &hex!("1cb45aac5def93daef806b781e"), + tag: &hex!("f4b0723c89607b66c392049ba042db63"), + }, + GcmTV { + key: &hex!("2733d3aa52a9d70a9fbd6ce2364bb5f9004902aa5eeb17446e08f2bdcc41db15"), + nonce: &hex!("196c4addb84a58beb3674a7a"), + plaintext: &hex!("cbc50cafda2544bcd291e8a025"), + aad: &hex!("c9826fe31f29b55b9d0f9da9795869a1a98befe5"), + ciphertext: &hex!("7a89cc58ccb97ad3e54ca4a9c8"), + tag: &hex!("3990d9aba210182996fdbd91c2ae4801"), + }, + GcmTV { + key: &hex!("0c4b9005b407415c19672bcd0ebe169f66fe404f22529baf55568e0901e94922"), + nonce: &hex!("e51381e959a1f5688c938576"), + plaintext: &hex!("c6179bd3451d9299b727e8bd0a"), + aad: &hex!("0b512faeb4da740dcc1e30d3c7ea61035e8570b7"), + ciphertext: &hex!("4d3fe086c990f16020b4c5eed6"), + tag: &hex!("9ff2297845814719f851ab0943117efb"), + }, + GcmTV { + key: &hex!("fee442ba37c351ec094a48794216a51d208c6a5ba0e5bdb8f3c0f0dfc1e4ed63"), + nonce: &hex!("a666f2f0d42214dbaa6a2658"), + plaintext: &hex!("a2cf3ea0e43e435261cb663a3b"), + aad: &hex!("7198c12810345403862c5374092cc79b669baecc"), + ciphertext: &hex!("713d4050f8c7fd63c0c1bf2ad9"), + tag: &hex!("250a35e2b45ba6b0fe24512f8213d8cb"), + }, + GcmTV { + key: &hex!("77f754d0cf7dbdaf75cfe965ab131e8cd39087ee6d986dec4ad2ff08ebd7f14b"), + nonce: &hex!("e28a14f3107ca190d824ed5f"), + plaintext: &hex!("54a97a74889e55d8043451c796"), + aad: &hex!("1decf0cbc50a9da6dad4a785a941e4b95ce5aaa8"), + ciphertext: &hex!("eedbf8dd81eb19184589dcb157"), + tag: &hex!("7749edd752fab7e50dbc3b0b47678bf6"), + }, + GcmTV { + key: &hex!("0523f232001e68bd65a79837bbaf70ec2e20851301d8e12fddb5926acb2100cb"), + nonce: &hex!("2bb8d5cb3ceb15107582e1fa"), + plaintext: &hex!("6b4cdc9f9c5082d86a1d2e68fe"), + aad: &hex!("1f55bba71cb63df431ef8832c77499ee3c502067"), + ciphertext: &hex!("079fe90ef517ed2f614a3cd8ce"), + tag: &hex!("539c30590a2527f1d52dfae92920794c"), + }, + GcmTV { + key: &hex!("54c56ee869ebb112a408717eb40af6937fe51eb061b42277a10537e7db346b6a"), + nonce: &hex!("5bfb63e2f3e5b2e1b4343480"), + plaintext: &hex!("75f9496b8d0ca96ed3af02dcab"), + aad: &hex!("740ab07b9c5de2afa37f0788ae5230535c18203d"), + ciphertext: &hex!("827902e58c4c8b7af976f61842"), + tag: &hex!("036ee6473c2138f2a2c2841438cb0edc"), + }, + GcmTV { + key: &hex!("d968ffdbed6ffc259b4310e2e97e42d877ef5d86d2169928c51031983779a485"), + nonce: &hex!("633d0d8d3613c83b40df99dd"), + plaintext: &hex!("08cfc65fea9b07f0c01d29dfdf"), + aad: &hex!("9aadc8d8975ec0a3f5c960ce72aaec8ef0b42034"), + ciphertext: &hex!("7b450f162bdedc301b96a3ac36"), + tag: &hex!("970d97344b1451f3f969aeb972d352e6"), + }, + GcmTV { + key: &hex!("5f671466378f470ba5f5160e2209f3d95a48b7e560625d5a08654414de23aee2"), + nonce: &hex!("6b3c08a663d04132243dd96c"), + plaintext: &hex!("c428592d9f8a7f107ec4d0df05"), + aad: &hex!("12965559c31d538f937bda6eee9c93b0387318dc5d9496fb1c3a0b9b978dbfebff2a5823974ee9d679834dbe59f7ec51"), + ciphertext: &hex!("1d8d7fe4357080c817303ce19c"), + tag: &hex!("e88d6b566fdc7b4fd62106bd2eb806ec"), + }, + GcmTV { + key: &hex!("fbcc2e7faa4295080e40b141bef829ba9d34e0691231ad6c62b5109009d74b5e"), + nonce: &hex!("7f35d9ec651c5b0966573e2f"), + plaintext: &hex!("cdd251d449551fec080425d565"), + aad: &hex!("6330d16002a8fd51762043f2df06ecc9c535c96ebe33526d8faf767c2c2af3cd01f4e02fa102f15ce0236d9c9cef26de"), + ciphertext: &hex!("514c5523024dd4c7d59bd73b15"), + tag: &hex!("d3a399843e5776aa348e3e5e56482fff"), + }, + GcmTV { + key: &hex!("04ef660ec041f5c0c24209f959ccf1a2a7cdb0dba22b134ea9f75e6f1efdae4a"), + nonce: &hex!("0f5f6fbca29358217c8a6b67"), + plaintext: &hex!("0835b312191f30f931e65aa05f"), + aad: &hex!("505e205d13ec945391c7d6516af86255e82f38433f40404d4f1e42d23b33eb9e6dea5820dad60622d3a825fc8f01a5d2"), + ciphertext: &hex!("5ddc0f5963f0290c1a0fb65be7"), + tag: &hex!("106d1f8d26abe4b4b1e590cd5d85e737"), + }, + GcmTV { + key: &hex!("42d3ff74284395fb9db9b8c7a444fa400f7fc6b985a7fec2478667c7f17cf3ba"), + nonce: &hex!("89230fbed59d1226a093ad28"), + plaintext: &hex!("d8339e3618ba57a243a27c85d6"), + aad: &hex!("60342f97310446266b2e47b18e008979d07fc181151ac0939b495e7f31de1d0e74042532840ab91686efd7a402d27a94"), + ciphertext: &hex!("9bb6fa36fa167016109d521ac0"), + tag: &hex!("600909ef32ca62951ecbdc811caa7778"), + }, + GcmTV { + key: &hex!("e115c6468606a5f9b8e9a7c220d7d7684d686c9210a669770b6e4bf24447cd17"), + nonce: &hex!("029c7c9ee2d3ab26843e8b41"), + plaintext: &hex!("7abf84842f9867cfc5eabc7032"), + aad: &hex!("1befd9f97f99fc096deafde5e158ac86716c0ba32454988fe48ba4737684361849a221c03fc0948cb25b5f29d6a0cb2a"), + ciphertext: &hex!("851c7047fb09646fbddb824531"), + tag: &hex!("d0ac4110c8d768f0a804ecda387cfa30"), + }, + GcmTV { + key: &hex!("56552f0cef34673a4c958ff55ad0b32c6ababa06cb3ae90178ab1c9a1f29c0e5"), + nonce: &hex!("b34d24935407e8592247ffff"), + plaintext: &hex!("dbd6cc358b28ab66a69f5238d4"), + aad: &hex!("b199437da189486a8fd1c2fa1fe3ebbb116f0ef41415bb7c8065272fb0b2fe8edca9cd0d4255d467e77f2834be557474"), + ciphertext: &hex!("76dc8d035e5ca4001e4e3fcb18"), + tag: &hex!("49c01f735da1131cd42b01b746fd38de"), + }, + GcmTV { + key: &hex!("d4f405ba556e6fe74b7e6dbdd7a8eae36376d1ca7a98d567d108729aeae5c326"), + nonce: &hex!("df6637c98a6592843e0b81ef"), + plaintext: &hex!("abe87641e9a5169f90179d3099"), + aad: &hex!("a5328cbabdfe6c3c1d4f5152189072dade71e2bacd857d3ce37ee9e3161eb0f20de5a29b7999fd9c7c60cdc03751bd1b"), + ciphertext: &hex!("06f9cf9677745e78c6c02bf06b"), + tag: &hex!("5a3a76da0703c24a9588afb2ac1a9e13"), + }, + GcmTV { + key: &hex!("4f667f65ea4569264456e25de498579036d6a604c18baf770bb626d8a1c68e4f"), + nonce: &hex!("43e27d275abefdd45137c8ff"), + plaintext: &hex!("eaa2498ce27e5658489381b6ec"), + aad: &hex!("264b807b4631d7c87ee9f1507082f5af9218f531b4630141f3c94939aa7cf81c71ea540783995560bf7e6e02d196227f"), + ciphertext: &hex!("bac018bf2e7090e7f217ab3365"), + tag: &hex!("13e5a16a9ce7a88cda640de2c4fdc07e"), + }, + GcmTV { + key: &hex!("f5624a166759ef0b8168af6565649f7797fa92476e008c407458101e75831312"), + nonce: &hex!("521ca79ffc8930349abfc052"), + plaintext: &hex!("1fab3def2ea13e815f8746093b"), + aad: &hex!("6e2771ecd637361cb6b947148910f7d9206d6af176c510bb5dd5bc9b97ac015fb05537affbc1756625715374172fb456"), + ciphertext: &hex!("ca72ff15a7eb62a2839bcf0c43"), + tag: &hex!("475fff6d9e2382583c9614020844b92a"), + }, + GcmTV { + key: &hex!("ac1383a3c783d3d0667e944cbe1a6159647b96afa922557eb1cb6407546b98ca"), + nonce: &hex!("70366112dbe1bd905b900e3a"), + plaintext: &hex!("b8dd871f9d866867efbe551c3b"), + aad: &hex!("b7c1865927737bee802415277cf1a25b7380774a9d27b6a3253f077d36e9c4142df2bbbf3c03414ac09161626ce9367c"), + ciphertext: &hex!("ba181874380841791f64881534"), + tag: &hex!("c5641edf42c446873372bbbde1146642"), + }, + GcmTV { + key: &hex!("f37499d9b6ad2e7618e30a23082673008f3ae1938b9397c02a4da2453fb7e403"), + nonce: &hex!("18e112ea6a998d6f9705f7e0"), + plaintext: &hex!("31560b2114a248ffe0696fa130"), + aad: &hex!("736f1a71fb259f46c6519bb87451f238f47d80c74a016604499b02568f1c7bedf70f9597d7b62c1698c4f2631f4e9706"), + ciphertext: &hex!("0163f558be0142ebabde29a7bc"), + tag: &hex!("45579ce07ee64cdac3a7a42109ff44e7"), + }, + GcmTV { + key: &hex!("50b7f5118ef7ee22b107d93ceab9881ef9658931e80385d1ae92501b95e47d62"), + nonce: &hex!("d5113665039169978b7dc4db"), + plaintext: &hex!("9ba4cd5e600277f4c786ce827e"), + aad: &hex!("68ff6c63e94cb7dd2b8413662a56c88dc130b79b8b2e2388c1089b61fa51ea37819109b5ef64da1250f5d6b5d74cc392"), + ciphertext: &hex!("67842199482b28be56f7570d11"), + tag: &hex!("79e03841843fe32337b7c7409a2153bc"), + }, + GcmTV { + key: &hex!("d396941c9c59e6a7bc7d71bd56daf6eabe4bfb943151cdb9895103384b8f38b4"), + nonce: &hex!("f408f8c21f3825d7a87643ed"), + plaintext: &hex!("dc8ad6a50812b25f1b0af70bee"), + aad: &hex!("947bd9a904e03fdd2c91d038d26d48ac6e32afcad908eacd42a25f6240964656d5a493242d3f8a19119a4cd9957d9c42"), + ciphertext: &hex!("57e6d821079bb8a79027f30e25"), + tag: &hex!("de8c26d5a3da6be24b3f6ea1e2a0f0c6"), + }, + GcmTV { + key: &hex!("eca22b3a29761fd40031b5c27d60adbcfac3a8e87feb9380c429cfbcda27bd06"), + nonce: &hex!("4e6fe3d1f989d2efb8293168"), + plaintext: &hex!("44d6a6af7d90be17aac02049a4"), + aad: &hex!("29beb1f0bb6b568268b9c7383991a09fd03da7e1639488169e4f58ec6451cad6d4c62086eee59df64e52a36527733d8c"), + ciphertext: &hex!("9aaa295bb3db7f6335a4c8cf2f"), + tag: &hex!("55f7577163a130c0dbcde243ef216885"), + }, + GcmTV { + key: &hex!("fa3ce8b099f3a392624bc433b5265235b65c0952cfc54817be2a8003d057903c"), + nonce: &hex!("3168b4e50efe96b3d3aed600"), + plaintext: &hex!("84ed3ccd428d3783ecea180b3b"), + aad: &hex!("d451fa64d73b7d7eee8f8143c40bab8e3f7a58ee018acda23224974f64ac7e1e389f5058ec08664bf56492b932d15f42"), + ciphertext: &hex!("ee2bd527568a4e7537c8f939b6"), + tag: &hex!("f4615f7dfdffec8a2d52c992456210ad"), + }, + GcmTV { + key: &hex!("ff9506b4d46ba54128876fadfcc673a4c927c618ea7d95cfcaa508cbc8f7fc66"), + nonce: &hex!("3742ad2208a0484345eee1be"), + plaintext: &hex!("7fd0d6cadc92cad27bb2d7d8c8"), + aad: &hex!("f1360a27fdc244be8739d85af6491c762a693aafe668c449515fdeeedb6a90aeee3891bbc8b69adc6a6426cb12fcdebc32c9f58c5259d128b91efa28620a3a9a0168b0ff5e76951cb41647ba4aa1f87fac0d97ac580e42cffc7e"), + ciphertext: &hex!("bdb8346b28eb4d7226493611a6"), + tag: &hex!("7484d827b767647f44c7f94a39f8175c"), + }, + GcmTV { + key: &hex!("b65b7e27d552395f5f444f031d5118fb4fb226deb0ac4e82784b901accd43c51"), + nonce: &hex!("2493026855dd1c1da3af7b7e"), + plaintext: &hex!("8adb36d2c2358e505b5d214ad0"), + aad: &hex!("b78e31b1793c2b758494e9c8ae7d3cee6e3697d40ffba04d3c6cbe25e12eeea365d5a2e7b46c4245771b7b2eb2062a640e6090d9f81caf63207865bb4f2c4cf6af81898560e3aeaa521dcd2c336e0ec57faffef58683a72710b9"), + ciphertext: &hex!("e9f19548d66ef3c16b711b89e2"), + tag: &hex!("e7efc91bbf2026c3519010d65628e85f"), + }, + GcmTV { + key: &hex!("8e4f8859bc838f6a2e7deb1849c27b78878285e00caad67507d5e79105669674"), + nonce: &hex!("e71d0ebb691a4c31fdd9879c"), + plaintext: &hex!("bd1713d8d276df4367bf3cbb81"), + aad: &hex!("47ca6cef3ca77997ef1b04e3721469be440ad6812aa3674ae92ca016b391d202e29932edfa83029eccae90bd8dbe4b434e7304b28fe249b380b2c3c49324fd5b3e469e3e135abc1c9fd77828b409c7482e6a63461c0597b14e5c"), + ciphertext: &hex!("eecbfb74e314628b0e3f827881"), + tag: &hex!("c9ea890294d7e10f38b88e7c7493c5f8"), + }, + GcmTV { + key: &hex!("2530cdcb2a789000822588a31bdc87c09234838da2d6ae1259c7049186525f11"), + nonce: &hex!("0c509faa257dbb0e743a53ac"), + plaintext: &hex!("a8edc524930ce4c20897c66f75"), + aad: &hex!("92a92cb8c1984ede806028cc45ac95574167ee83f03a707cc4b0fb8ad70907e0016e38b650f4a75bc83a625e3c670701d43bfb0326d1c4fe7c68410733c0c874c920389d164bf67a9032e2e837f5e9e324b97932d1f917ba7dca"), + ciphertext: &hex!("1f658c7a1f41152b22999ed1b7"), + tag: &hex!("cf3e4fef775d9c6ff3695be2602a90d8"), + }, + GcmTV { + key: &hex!("54c31fb2fb4aab6a82ce188e6afa71a3354811099d1203fe1f991746f7342f90"), + nonce: &hex!("f0fe974bdbe1694dc3b06cc6"), + plaintext: &hex!("fbb7b3730f0cd7b1052a5298ee"), + aad: &hex!("2879e05e0f8dd4402425eabb0dc184dcd07d46d54d775d7c2b76b0f76b3eed5f7ca93c6ae71bf509c270490269ea869ed6603fdf7113aa625648ab8ed88210f8b30ec9c94bca5757ca3d77491f64109101165636b068e3095cb4"), + ciphertext: &hex!("3a5a2a8aa93c462cfb80f1f728"), + tag: &hex!("59ef9d54ee01fb6cd54bd0e08f74096f"), + }, + GcmTV { + key: &hex!("8084061d0f7858a65c3a3557215ed46f1590278ca97a45dcb095d2a0979f2e3f"), + nonce: &hex!("6973898b1a8f72856415675b"), + plaintext: &hex!("200d0445cb09eb52f54d2f74c6"), + aad: &hex!("8b543e294546848c3308ccea302f0238b7dffc1706d03657c190ea745cc75bcd5a437993e787828ea7fe42fea1d5c6f7229a72ea65f0d0c190989a590ab49c54726633282c689eef8cf852af263b5edf63e449fd5440730003ca"), + ciphertext: &hex!("ec242c358193ca6187c89aa7a5"), + tag: &hex!("967428ac6956525ba81d5901ed259407"), + }, + GcmTV { + key: &hex!("2aad7db82df4a0d2ec85218da9d61ade98f65feeb8532d8eb728ef8aac220da6"), + nonce: &hex!("029ac2e9f5dc3d76b0d1f9df"), + plaintext: &hex!("ba363912f6207c54aecd26b627"), + aad: &hex!("d6f4b6232d17b1bc307912a15f39ccd185a465ee860279e98eb9551498d7b078271ebabdda7211e6b4ab187043171bc5e4bf9ffcf89a778430e735df29410a45ca354b0003433c6bc8593ee82e7c096a32eac76d11daa7d64150"), + ciphertext: &hex!("bfcad32611da275a0f0821517c"), + tag: &hex!("9ea37bdcaafad69caf06d67fb18dd001"), + }, + GcmTV { + key: &hex!("f70bb950ab56f12f1efc2376d32a59d16ef3ef5969e0106ab40cc314c9b0c7e8"), + nonce: &hex!("3b3b29ba422c2bacafeeb8b3"), + plaintext: &hex!("029929277043dc0379f152a484"), + aad: &hex!("464ac0c84b9ff17a0e7c39a65f89682a89b8787553a6275f0d55effaabef2114072c739f9831a5d5a5133ae4de14eb51346b318b255a1bff57e50c433e1e69a00fe1a8b6f6b621d515d670d89e148f6b65d6eb4c54878cb819ce"), + ciphertext: &hex!("c0b97d6d1a95d708d6dc7d2b95"), + tag: &hex!("322eb4395bf4d4dd070b8f9f6195f8ee"), + }, + GcmTV { + key: &hex!("f4950f01cb11fdd9afb297f7aa852facfac354ff96557befa5f657678de6cefb"), + nonce: &hex!("aba7d864f29cbc449cd93e33"), + plaintext: &hex!("e6daf59ef54ac7405984fc4c4e"), + aad: &hex!("852f624cea7a8c20e189e0c79f578c0d770c4bf7c4e691649eba992f6de89d7bf2078aff94803a3dc62628e02a80a01957722e2a931fc56283d84ab68ce11ae867835c2d9700df130048ea8eaaca41f1a9059be2acaea6e0f7f2"), + ciphertext: &hex!("d01d36ff8009b4082279abb906"), + tag: &hex!("d9a36c8008493bd95c09049299cbd075"), + }, + GcmTV { + key: &hex!("714261ef4f02fb4efb0e6b5aed96d7b3ceac6551a57cf679da179c01aac5ee0e"), + nonce: &hex!("3b7d15c7fd877461a789255a"), + plaintext: &hex!("815de8b0382fe60cb0d3782ee9"), + aad: &hex!("7621e58152336ee415f037f2e11581fe4da545c18d6e80177d5ab5dda89a25e8057d6fccec3757759a6e86e631080c0b17baa8be0b8fe579d3bfa97937ee242b6faacfc09425853df4dc26bc263ed1083a73ffc978c9265f8069"), + ciphertext: &hex!("29c566ea47752a31a380fd0e7c"), + tag: &hex!("b279340a384dbbae721c54e9183b3966"), + }, + GcmTV { + key: &hex!("53459ba5a2e49d1a7c2fb6ad9e6961b4dbe5158cb9266eff425d6dcccaaf8073"), + nonce: &hex!("3c97dc635a75fbe2c33c9a41"), + plaintext: &hex!("03fbfe5842ed781990ca8be728"), + aad: &hex!("7fe308afe58a927680bee3368301f4dc7c47811fc09f1b9922a092a497b9c6b67c857fdcc32da1011acb110b3c1475bef303f1a609479485cc400ee8f38381c45d078708ad49f226f95dd9c81478d1ee2b53c3b906d96f8ddd76"), + ciphertext: &hex!("5865e5a1ec711732a4ee871bff"), + tag: &hex!("856a653ec214178096bed423e30a36e9"), + }, + GcmTV { + key: &hex!("f0501583c226d2519ed23fcc6f2cffd2f013eb91aa07b3a5a2073d6e2bd10cef"), + nonce: &hex!("29a922ad9bdeddc2e298b99f"), + plaintext: &hex!("035eb6922345c02a81435d9e77"), + aad: &hex!("d84f54bac09ea92afe0a7335cb0bb5f68425490fd2fb6c3b99218f49856ed427ec902e510b899d54951fe84cdbfd112608d1e999f64ecc9cd4be3a0114c1c34875dbf35a1b0be421659f99d69b32e968cebfca6f95837e3edeb4"), + ciphertext: &hex!("095971f99af467805a62bfb882"), + tag: &hex!("d5ff2b7beac260e517ea3eca13ff1e77"), + }, + GcmTV { + key: &hex!("78e6789b596c71cb3becc833cf823d2ebb18ca2e26c27e26a55ef95df7353971"), + nonce: &hex!("65da9c7a9f17b11246bcf8db"), + plaintext: &hex!("003e82a147df3c953400f87ab5"), + aad: &hex!("d49aee7ffd31e7c8d831d97ae894a00473adbc5071f6099d567caaef85c295d5143a1316ff82753cc35d3efc60f7e5101ddd811336b404d598f6c439cce6b47fcbebb15d1c342e4151b355025a03b4397260b4a7e6444fa57b5b"), + ciphertext: &hex!("abcceced40209fc30a5590fee8"), + tag: &hex!("0a203973b81375949ebd932597efd495"), + }, + GcmTV { + key: &hex!("816b3e6ca31d59688c20bcd1fa4285197735d8734289ca19a4730e56f1631ccf"), + nonce: &hex!("4c191ac994f86985c180ccd4"), + plaintext: &hex!("b2060dd86bc307133b7d365830"), + aad: &hex!("b3dcd643c68ccce186570c63288c8722b8a13dfaf9e71f44f1eeb454a44dddf5f955540cd46c9f3b6f820588f71936d7a8c54c7b7bc43f58bb48e6416149feae7a3f8d8198a970811627489266a871e8cb87878cdb3a48be65f5"), + ciphertext: &hex!("53e65880ad0012a75f1188996f"), + tag: &hex!("9ca8a71a45eb4402a6b03106bae330d1"), + }, + GcmTV { + key: &hex!("a07ba57478061bd7abddd762971cf2e47141891f76c3d1c150b53eee5704557d"), + nonce: &hex!("5adfb85b2d9e239c5146501d"), + plaintext: &hex!("67c8824c1837cfdec6edcd719c"), + aad: &hex!("937b3ed73e67ca0b02f9eb736a668362d4d0447c15f6083099a7f90c7c49318dd72f6baa74da22ff53b56c24fb9a1b1d6c4e29f4ac4d917220ebe3c8d760999da7be9e1e8f6a171133640c9196f9ee3cdb76a5a342a95a05c8c4"), + ciphertext: &hex!("1eb85c6682850e849eb37927e5"), + tag: &hex!("8079f705cf551a5484132cd0f0c5297c"), + }, + GcmTV { + key: &hex!("268ed1b5d7c9c7304f9cae5fc437b4cd3aebe2ec65f0d85c3918d3d3b5bba89b"), + nonce: &hex!("9ed9d8180564e0e945f5e5d4"), + plaintext: &hex!("fe29a40d8ebf57262bdb87191d01843f4ca4b2de97d88273154a0b7d9e2fdb80"), + aad: b"", + ciphertext: &hex!("791a4a026f16f3a5ea06274bf02baab469860abde5e645f3dd473a5acddeecfc"), + tag: &hex!("05b2b74db0662550435ef1900e136b15"), + }, + GcmTV { + key: &hex!("c772a8d5e9f3384f16be2c34bf9afd9ebf86b69e6f610cd195a9db169e9be17e"), + nonce: &hex!("9b8e079f9971d7352e6810a3"), + plaintext: &hex!("7f13fcaf0db79d792823a9271b1213a98d116eff7e8e3c86ddeb6a0a03f13afa"), + aad: b"", + ciphertext: &hex!("d29e2bf3518668a14f17a3e4e76e1b43685734b801118d33a23238f34d18aa40"), + tag: &hex!("8e02b0b7d172cf5e2578f5b30fac2e7a"), + }, + GcmTV { + key: &hex!("d5924b31676e2354fe7dafffaf529749598ea1bf5e4c44f5b60240e09d8036aa"), + nonce: &hex!("5d847784f0bcd79cb84fcf1d"), + plaintext: &hex!("6fd80c8f0d4de081a93c16b84dec697a1e4f9d80a6af497c561572645eac0d63"), + aad: b"", + ciphertext: &hex!("282cc9d2308a443019cfdc4d79854accc7731ee36902bafe3ffaca6484327b82"), + tag: &hex!("4dc5e0f2ab91bdfd31f2bdcf06af9667"), + }, + GcmTV { + key: &hex!("b328c6d7946221a08c4f0509b52992a139890cdd8eae1956851f110c49602cb5"), + nonce: &hex!("1a433c33ca12ce26cf3dffff"), + plaintext: &hex!("217bdc314a4d335c72b5267b424fc8e31f4bb118e6cfaeacf5548f4ba8f51980"), + aad: b"", + ciphertext: &hex!("a322944e07bf84ab424ffa75fd0309e8691c9036b08f344ba76ce0774f43b351"), + tag: &hex!("14dd6b1c2b224533ccc9fee8d2881358"), + }, + GcmTV { + key: &hex!("c2080965d21d229c0d0d6c56cbce83880120c21a48172a64560b90dc4ce1ffbe"), + nonce: &hex!("928d6c0195f5f0974f38730b"), + plaintext: &hex!("864397271e1b242aa1dff38e78aa89353e1554ba907318a0aaad44f26fcd567d"), + aad: b"", + ciphertext: &hex!("7de4f941f44bd0f268b2a47b9c4927cc10537bbed739d52ab099fde4033041d1"), + tag: &hex!("b51a59931817257619e7be1091128c49"), + }, + GcmTV { + key: &hex!("dd6b7e2584edf1f1e6c2c0dd1f72161a92d2cba99856554f820de1256d48c099"), + nonce: &hex!("fe9d553c75067e8dbae1ab67"), + plaintext: &hex!("f9f86f7762859f11d6e7ef56178657ddcded532843446f86a23eac35aa2dd3c0"), + aad: b"", + ciphertext: &hex!("f7aaa1711c8092783b05b4e5e6c9c6944e991bd59c94b9d0356df00a66e2db5b"), + tag: &hex!("c61edd176c8322a01d8c5f3df09252e9"), + }, + GcmTV { + key: &hex!("37f39137416bafde6f75022a7a527cc593b6000a83ff51ec04871a0ff5360e4e"), + nonce: &hex!("a291484c3de8bec6b47f525f"), + plaintext: &hex!("fafd94cede8b5a0730394bec68a8e77dba288d6ccaa8e1563a81d6e7ccc7fc97"), + aad: b"", + ciphertext: &hex!("44dc868006b21d49284016565ffb3979cc4271d967628bf7cdaf86db888e92e5"), + tag: &hex!("01a2b578aa2f41ec6379a44a31cc019c"), + }, + GcmTV { + key: &hex!("a2ef619054164073c06a191b6431c4c0bc2690508dcb6e88a8396a1391291483"), + nonce: &hex!("16c6d20224b556a8ad7e6007"), + plaintext: &hex!("949a9f85966f4a317cf592e70c5fb59c4cacbd08140c8169ba10b2e8791ae57b"), + aad: b"", + ciphertext: &hex!("b5054a392e5f0672e7922ac243b93b432e8c58274ff4a6d3aa8cb654e494e2f2"), + tag: &hex!("cf2bbdb740369c140e93e251e6f5c875"), + }, + GcmTV { + key: &hex!("76f386bc8b93831903901b5eda1f7795af8adcecffa8aef004b754a353c62d8e"), + nonce: &hex!("96618b357c41f41a2c48343b"), + plaintext: &hex!("36108edad5de3bfb0258df7709fbbb1a157c36321f8de72eb8320e9aa1794933"), + aad: b"", + ciphertext: &hex!("b2093a4fc8ff0daefc1c786b6b04324a80d77941a88e0a7a6ef0a62beb8ed283"), + tag: &hex!("e55ea0456af9cdff2cad4eebbf00da1b"), + }, + GcmTV { + key: &hex!("6fb2d130bbad1924cab37d071553b12169e978a805bf74cb4c23d5ccd393d7bb"), + nonce: &hex!("76826741225a391fdce4d3b6"), + plaintext: &hex!("c49b80080e2efeb5724b9e5b53ba0c302e97bd16f1a6bbec01e1ca6c35a42a3c"), + aad: b"", + ciphertext: &hex!("62fbe5466a7ff83ff719f4927e00e9319e1bb7e835c5d6b4e9d4bc5a8d6e2beb"), + tag: &hex!("df72da7a66cb5257836f3c19ecadcd55"), + }, + GcmTV { + key: &hex!("402e8113970257d9437807620098370243536a105cca4fbc81a1ff2d48874f48"), + nonce: &hex!("c924c19c4d14905a2bdf63bf"), + plaintext: &hex!("917b9585f65e59bf4d242bb0802966045dd29fbc66911277baecdfcc818c3c35"), + aad: b"", + ciphertext: &hex!("5b6594edcddbb338f4e813687f4f23a75a64c21e3cf5d2e7c9af0f7e3ee3e616"), + tag: &hex!("f1cccd93a4411247c8b6830addd72c6f"), + }, + GcmTV { + key: &hex!("2aac499cb0eb72b4598acff4330df6cd764978997d5ace51da88e0c18671bde9"), + nonce: &hex!("fd16cdc39d7f0b92e1f95c97"), + plaintext: &hex!("e7b75bfa35c9a004d0b68265623a9b06b6d4493ea0ad4f6c777ba5add8c7bbbb"), + aad: b"", + ciphertext: &hex!("c3d0a0f7ce9720c95aac86151aad634884ddfa62df58f18394537f6504d9a8aa"), + tag: &hex!("76749a1ec70236b267fc340d5fbb6da3"), + }, + GcmTV { + key: &hex!("a2a502d6bb19089351e228d5cbff203e54fc31f2772253df08557875d964c231"), + nonce: &hex!("0ebb5af4a462a1e6ded7164a"), + plaintext: &hex!("bbecc89450c07b8de631155e5d7cc7a9d26376bb57d7458d49b4c36e140490f3"), + aad: b"", + ciphertext: &hex!("fd09c950890441fcaaa8809a8998079abb88741c6672abae12383ffd724f8299"), + tag: &hex!("22fac246058bf142c5f26812a635b480"), + }, + GcmTV { + key: &hex!("ce2d289e20c76f75c135c8118d5cbf5f2828026f0b639588a3eb4ad752cea548"), + nonce: &hex!("bb08526dd8bd1c3bb58d0999"), + plaintext: &hex!("56f5db1e796a0c4633a8d570182c39e3c8451e7ba485b98d38a2c926a1b92a46"), + aad: b"", + ciphertext: &hex!("a41005df18734d4f3f99f19ef8fc43b16ef431207cb0466341bf164b58e23533"), + tag: &hex!("a45c2a1ef6aec75cc22d71807dab3c27"), + }, + GcmTV { + key: &hex!("66e418d0ec97b420b1b5365d1b6d5cd7c5ac1a5653739120d4aec3c94c93c287"), + nonce: &hex!("989f94480266e3652488184e"), + plaintext: &hex!("e5052b19d7f827fd60f45c8925809fd2217ec4d16aa89bbf95c86a1c1e42bd36"), + aad: b"", + ciphertext: &hex!("f341630574ee92942cf4c5ecd3721ae74b32c557379dfe8351bd1c6661a240da"), + tag: &hex!("e85fb655ef432e19580e0426dd405a3e"), + }, + GcmTV { + key: &hex!("37ccdba1d929d6436c16bba5b5ff34deec88ed7df3d15d0f4ddf80c0c731ee1f"), + nonce: &hex!("5c1b21c8998ed6299006d3f9"), + plaintext: &hex!("ad4260e3cdc76bcc10c7b2c06b80b3be948258e5ef20c508a81f51e96a518388"), + aad: &hex!("22ed235946235a85a45bc5fad7140bfa"), + ciphertext: &hex!("3b335f8b08d33ccdcad228a74700f1007542a4d1e7fc1ebe3f447fe71af29816"), + tag: &hex!("1fbf49cc46f458bf6e88f6370975e6d4"), + }, + GcmTV { + key: &hex!("2c11470e6f136bec73351619288f819fb2bbba451857aadfb78384074612778a"), + nonce: &hex!("4e6cc2bcc15a46d51e88958d"), + plaintext: &hex!("3b3186a02475f536d80d8bd326ecc8b33dd04f66f8ba1d20917952410b05c2ed"), + aad: &hex!("05d29369922fdac1a7b37f07953fe175"), + ciphertext: &hex!("6380945a08977e87b294b9e412a26aebeeb8960c512439bac36636763cd91c0c"), + tag: &hex!("1029a3c4be1d90123c1b404513efde53"), + }, + GcmTV { + key: &hex!("df25ea377c784d743846555a10cfaa044936535649e94da21811bad9cea957b5"), + nonce: &hex!("35f5f8e950c1f57ad3dfb1fa"), + plaintext: &hex!("98941a807ac8f16eef0b3d3c7bbdfd55d01736c5b3360d92b4358a5a8919380b"), + aad: &hex!("28eb4677110ccb6edc8d2013dc8f46ec"), + ciphertext: &hex!("24a07532e981aaf3106eab8dfbb2d2078342e2eaee027e148f06aca68f6a1c50"), + tag: &hex!("131373ed4a0e3f584ae978d42daa6f3a"), + }, + GcmTV { + key: &hex!("106168ea651f22c54196a06f1a10bcf4e620d93e4dc0824d798f44f9219c6177"), + nonce: &hex!("4064dcbd631cf20b05ae22de"), + plaintext: &hex!("b0d3da2b96b8889c92e445abbea4c6d0d5d44d7fbcc7dade4c92f6bcddbf06e1"), + aad: &hex!("a36e2fb9cd96a8ca9ae2b193aa498efd"), + ciphertext: &hex!("f55a6d8a6965ea451637bec7548cfb1ffe59fc0ce6ea6a937cb5dd32b3d45d5f"), + tag: &hex!("8d1bf2715041f817f11631fc9910c629"), + }, + GcmTV { + key: &hex!("272d1649a3dd804de0962d3e07064a7054c00a6234ab1b0cdcf685ab394837e5"), + nonce: &hex!("955b5897f6b9806bbec5c33e"), + plaintext: &hex!("36e57c29c08c51ad7fa91c0416f976cfd011780eb44cc5abd34c7b431b093b8d"), + aad: &hex!("33e618ecbbe5eb0566df21c3c34b7e25"), + ciphertext: &hex!("cd6aeb345081dc0bb2c8b4d19b280658fb87c0f2bd0f4c9da694dc1feeb32f4e"), + tag: &hex!("dd37eac6bd6a4d3618241738779735d7"), + }, + GcmTV { + key: &hex!("3dab6a51bb7af334dd4b79a7d139550c88f0778d43c21fc4ad33f983a13515cb"), + nonce: &hex!("362eaa67cab3d1ed48e9f388"), + plaintext: &hex!("3eb7f5f0a4ca9aa7000497602c6124433a60a8fcd91b20175b4ee87e6b10a2d7"), + aad: &hex!("52852150786e6547a2618e15c77110b6"), + ciphertext: &hex!("cc3316041b88733839249b756ffa00bbec6211942f604f26c4a35ed32e6eeaff"), + tag: &hex!("5936c5500240d50c0da0fcdc248f176e"), + }, + GcmTV { + key: &hex!("0ea606521b935d5b4b66df89fb372d35c4d6d2c03767367e38de0d4c27761d56"), + nonce: &hex!("0d3168318a4f76392699640b"), + plaintext: &hex!("f450b36d6c49411897bce39001d73ff01b5e8566179e36dacac7064cab5c6270"), + aad: &hex!("3bd8849070cf034c4298f40f33b0b839"), + ciphertext: &hex!("3b15fad18726c4eaa70502b3f3b32c5092d1d92835e6460665fc50dda953a191"), + tag: &hex!("11fd3fddf61e010c17fbedd4bd5fb012"), + }, + GcmTV { + key: &hex!("c8c4f9e0bd289ef1bd16104a8074fb073dd9035ab937ab076fb5801e2295aa2f"), + nonce: &hex!("be699d9d98ec1f724da8bd0f"), + plaintext: &hex!("49fe9407a719d41e658587809cfed7a5b49941c2d6378f3c0afe612f54f058a1"), + aad: &hex!("a985c7489732038c3190cb52be23737c"), + ciphertext: &hex!("17a9aaa6a3c68ba1f6cb26fdd6536c207e3c9ce58f43e4ecfd38d3387a798a0f"), + tag: &hex!("d832cb4814142562fedfe45b36126cb8"), + }, + GcmTV { + key: &hex!("52d0f20b0ca7a6f9e5c5b8549d5910f1b5b344fc6852392f983558e3c593be24"), + nonce: &hex!("d5c618a940a5a5d9cc813f27"), + plaintext: &hex!("a9fed8a29355685321f978e59c40135309306cd41b25349fe671dc7990951c68"), + aad: &hex!("61823f7e39ed76143ca7249d149bdf57"), + ciphertext: &hex!("509c540e558d0bf0a3b776cddfbfddc15486748a7f9952b17c1cbd6869c263f4"), + tag: &hex!("42e35ee3f7119f87fb52b5d75b8ab8ec"), + }, + GcmTV { + key: &hex!("5d291a8f1a6433a41076702d9d8a8c196e464550ed900ce8c2a36f4d10483954"), + nonce: &hex!("c4ba743ee692e5d00b5ae2c6"), + plaintext: &hex!("605d519b26182458fea68dddd86033390fc545f843ae817850a2a4574add015d"), + aad: &hex!("878fa6720ab30e0287f6903acd2dca19"), + ciphertext: &hex!("1c2f153f2374d3945cca9757dc18d9a15a93276526285a6e316ee32a72092c34"), + tag: &hex!("e7905e856c88c6ece4bb47781becf923"), + }, + GcmTV { + key: &hex!("09e2724d4017cd57e967000e4da2cd5c5c18ccfb06c33b7ce62a7641e4bb0b73"), + nonce: &hex!("9ea18b420a10177289ab370b"), + plaintext: &hex!("6f5dfa86d5df4febd752265c56390049e7cda60c2644c84ab413932faad15b15"), + aad: &hex!("a8e77939423d5894d307fd60278d162a"), + ciphertext: &hex!("35e37a9b913eb58b72262e92d7584d44bf9a8442f1b2f3da3a5d05ec6a2a31e2"), + tag: &hex!("1a95023b1a4a3e885520ec79e1a3aef9"), + }, + GcmTV { + key: &hex!("8544a9f4f6c0efdff3da90cfa3ee53fbe1f8de159d29537c803e1651da153718"), + nonce: &hex!("be406029a1d0c25d09af94cf"), + plaintext: &hex!("7e88a65646ed138b7c749366d16e41dbafd9987ad2373bb9d0b6ce0c1a4d6661"), + aad: &hex!("599dbb73897d045a1bd87385e60323a2"), + ciphertext: &hex!("38ffbf9ffff8d6a92090584e6dace1c6a47d3d5709a25e470557d5c8f5dd1851"), + tag: &hex!("d5b2e83c47df404de9a7cd95d3cbe7ab"), + }, + GcmTV { + key: &hex!("35b9d2a5db3b06e7720cec794dae615029a491c417f235498e0496cd8183d1bf"), + nonce: &hex!("b382987916e19752dd9ecc0c"), + plaintext: &hex!("76b290496901c5824ad167433dbb6d6b5856d41913ee97ec81e70cf6a170e35c"), + aad: &hex!("e0aa3a1f1df601366c59a390f4f06c3b"), + ciphertext: &hex!("78347400d6799e77e11e76c0ecfd311becf31f74f14b3a71e6d526ce57015c8b"), + tag: &hex!("bf8dec2feac7cfe9f330bdfc92737b33"), + }, + GcmTV { + key: &hex!("d707eab3c167b73efeb08c50e12b1569a275487ea136f52736c0f3ce66b69fa3"), + nonce: &hex!("11116f34182e52428642e747"), + plaintext: &hex!("a0c4818362035b16b50de445d558ea5cf8844bf5c84b96232999a2279806cc45"), + aad: &hex!("ae9f90331800c358716c92667f79f748"), + ciphertext: &hex!("91c77404b20028ef0fd4dd7f8b65b6594af94a1e7fc79cfbdb108265354fc71b"), + tag: &hex!("6c3410d4b915dbad745715202c04e9a4"), + }, + GcmTV { + key: &hex!("405d13ee48d3b9fc26bcfca776b2af6c745d8fc34171622f8c6c4be5a54b8b65"), + nonce: &hex!("add1524abb1b846f0f6577da"), + plaintext: &hex!("e06475990d6e3990266de1bd025c3b1910c0736c81050885f2bfc13ec78e9d96"), + aad: &hex!("0b1c4c3ba877bca5846b2c1f2b0e2105"), + ciphertext: &hex!("6399f7e6d6c680fc41bac8bee3836b9a4241403d5a19e4919f396ce37b238d38"), + tag: &hex!("e754f400d76c76e03c63ea88cf64ccba"), + }, + GcmTV { + key: &hex!("5853c020946b35f2c58ec427152b840420c40029636adcbb027471378cfdde0f"), + nonce: &hex!("eec313dd07cc1b3e6b068a47"), + plaintext: &hex!("ce7458e56aef9061cb0c42ec2315565e6168f5a6249ffd31610b6d17ab64935e"), + aad: &hex!("1389b522c24a774181700553f0246bbabdd38d6f"), + ciphertext: &hex!("eadc3b8766a77ded1a58cb727eca2a9790496c298654cda78febf0da16b6903b"), + tag: &hex!("3d49a5b32fde7eafcce90079217ffb57"), + }, + GcmTV { + key: &hex!("5019ac0617fea10517a2a2714e6cd369c681be340c2a24611306edcd9d5c3928"), + nonce: &hex!("fd1fa6b5cab9aa8d56418abb"), + plaintext: &hex!("4349221f6647a906a47e64b5a7a1deb2f7caf5c3fef16f0b968d625bca363dca"), + aad: &hex!("953bcbd731a139c5de3a2b75e9ffa4f48018266a"), + ciphertext: &hex!("dbce650508dab5f499767651ee734692f7b157341977692d2ca879799e8f54aa"), + tag: &hex!("20239e97e2db4985f07e271ba545bbbf"), + }, + GcmTV { + key: &hex!("c8cee90a8b9ad6094d469e5d1edc30d667608e89b26200cac77efd7e52af36fd"), + nonce: &hex!("5a1aa9c8e635281ee1fb9df7"), + plaintext: &hex!("728d9221891bd75c8e60b7dd6f53edcfd1ab1cebc63a6ce54be220b5b362233b"), + aad: &hex!("0538b3b64da72aac591bc59991a140eff206b3f7"), + ciphertext: &hex!("b753eb6b87f0c8778c3ea3a74fba3b31ced6d2da94d43d482ab0431806a80d75"), + tag: &hex!("b21d29cf6fd04571ffcaf317d384df11"), + }, + GcmTV { + key: &hex!("b4b77710f86ffd463fc14bb9eaa4424b2b3a581778e5511a094a08fb204cab59"), + nonce: &hex!("3e4b12bf55633bf48d104620"), + plaintext: &hex!("6f44a8df11dce27df075ea10ddeb7566ca6c988a334cf56e8540f71166d7c0d1"), + aad: &hex!("3e3b4c9369266266098326217b5677a40297cb87"), + ciphertext: &hex!("31f82f5cb1cd5c4b4819b61aa9377abebe8fca76978b1199178462c7c1c4e2b2"), + tag: &hex!("1b3a535768e8480d75ec91b2e7b55efd"), + }, + GcmTV { + key: &hex!("0a8fb75498a139223c763d52bbe3d42f813de370fa36b81edc4553d4219d2d5d"), + nonce: &hex!("7d6cb675fded3efef908a11a"), + plaintext: &hex!("81b69ca354de3b04d76ee62334cb981e55f0210f1174d391655d0f6712921a0e"), + aad: &hex!("2314ad86b248f1ed2878e7c562b533bf2dda5a29"), + ciphertext: &hex!("6a23d30737f4a72b1e07ba23d17fde43a4498e2e60d3e1b0c8e6ea26a2bb331a"), + tag: &hex!("7fcac442fb657910c62a74b1d0638902"), + }, + GcmTV { + key: &hex!("a84315058849690c2b88062aef81134d338526baa7090e865fcaad94bbf51ca5"), + nonce: &hex!("a487cfa701447b495aab41e0"), + plaintext: &hex!("18074e14dc0a14d4439f1d710927ed8c200154c8492f77f10f653e0bf6070ca6"), + aad: &hex!("7c4416b0cf13ac76bec6687a6840dc703e91bb86"), + ciphertext: &hex!("80f40b7e335d40fc5859e87f385e14798a253818e8ad73b1799c1419638246a4"), + tag: &hex!("b4c7c76d8863e784eb6029cd160ef6de"), + }, + GcmTV { + key: &hex!("82833bcaaec56f6abbb3378f7d65daf6e6f6f2a0d1e858c7219f53a7840f4e00"), + nonce: &hex!("4bc9b028a00be8feb5232978"), + plaintext: &hex!("d9b2383123a27a93bce85add8392b938093b40e82f182e484bf4f84fa3bfb3f0"), + aad: &hex!("76fc8ed57154cd8a9b3d02c87061edd2a8157811"), + ciphertext: &hex!("383efe971438cd2b2cbb399d74a3fb3eedd394f1862addc58e9fdd4c421402d2"), + tag: &hex!("fd803c4fa917f7ff649a6aac013a96b1"), + }, + GcmTV { + key: &hex!("ee4634c49c5672c660968a42862698f6c1b2c7b79efd1605c24af8ff9ff8366c"), + nonce: &hex!("877912b2f35888d2810612cc"), + plaintext: &hex!("9512a5268a0cb3fbd916ddb820dce77f1e0dbb52c8ffc7a74be077119e9245e4"), + aad: &hex!("93bd669db4f1354ef6c8addb0cf729e46d5c3846"), + ciphertext: &hex!("69af0ac954e0d69043851d89f1538ebcb42769857eba27dbe4ad4fd60fd75537"), + tag: &hex!("3ee443873e2f7f7ea601fe3d7e5211e2"), + }, + GcmTV { + key: &hex!("442f4bbc468433411e49486a15c5eed577f5007380ff126d9974f3bd3fe4e3c4"), + nonce: &hex!("1e7133aaa8af826dc646ec62"), + plaintext: &hex!("7f8069e5c356ece135d98bb563c8b411ea90ea3b673dfd92e1ba9c459efae61f"), + aad: &hex!("577662f611446b5b31814930029edb949a30dcb9"), + ciphertext: &hex!("b962952750eb2bce313e1a85a72e3c9cc2ea7e58c353ea37df2c9f0723995ca7"), + tag: &hex!("e633fe9f10cedf0f0d02aa2ddcf47d86"), + }, + GcmTV { + key: &hex!("3a29aec009f44fdd2b1bc07cb7836f29d8589774bd0d74089a68d9e67827d6d8"), + nonce: &hex!("a42c5fb61573c72688ac31d8"), + plaintext: &hex!("d36eb81506c0a0e4ebcac9b4b1acebb38b94b8f2ce3d6f85a8f705fa40cb987a"), + aad: &hex!("2ee2582d544e1663f1d7a0b5033bcb0fce13b3e5"), + ciphertext: &hex!("179ef449daaacb961f88c39b4457d6638f304762bd695924ca9ebd01a3e99b9f"), + tag: &hex!("1fee176c7a5d214748e1d47b77f4bcc8"), + }, + GcmTV { + key: &hex!("ed47660054294f3c913c97b869317cbddc395d757bef7d29b8ccbdd2c54e99d3"), + nonce: &hex!("770a00642c67eff93c9f1f56"), + plaintext: &hex!("034193397cbd0eb414459273a88808db2d0711e46f80d7883212c443d9e31b54"), + aad: &hex!("06210fca2018d2357256c09197730e9777caea96"), + ciphertext: &hex!("6a250ebd3390229d46b691142743dba1c432c0feaa0f0dd19d0ce4e6a8918d80"), + tag: &hex!("a5f6e975592b472907c34b93bfc69dde"), + }, + GcmTV { + key: &hex!("9539844493362dc3f913308f7e12a2a0e02afdbd8869877b30ce0397fb0349dc"), + nonce: &hex!("eadda3132079195a54fde2c1"), + plaintext: &hex!("62349a0b1e40a9f31eadf27073682da15f0a05cf4566ee718b28325f7d8eaba0"), + aad: &hex!("0ae4a90cb292c4e519b525755af6c720b3145a1e"), + ciphertext: &hex!("ad6c9521bf78d1d95673edd150f2b8dd28f10625d67fa25f1fb42d132ba7fcfa"), + tag: &hex!("916242a9cb80dffcb6d3ae05c278819a"), + }, + GcmTV { + key: &hex!("3b4eb08d27ae0b77605ae628a1b54a5402026550679fab0a20752bee510d3d92"), + nonce: &hex!("28a20c40f49a00493da3488a"), + plaintext: &hex!("c8a47edcf84872f53f96ef41ce05ca37cbc3854b556d6e606f0a8a32d0861907"), + aad: &hex!("0591390e2d14ebe62aeb1741c26448ce55b28cab"), + ciphertext: &hex!("a3e8cbf84df8529838f79315c7f1a0b7bb3ad4c4d036ec317b1810b274ee3080"), + tag: &hex!("0a8f66daeb7f0a88756909c4e93fcd36"), + }, + GcmTV { + key: &hex!("0cccea8f1f6ce141690e246cf4cb9f35b66baf6e6986b8e0b4cfdd13fcdbc8c3"), + nonce: &hex!("929f07be5aa7bae7607bae3c"), + plaintext: &hex!("9fa5214c599523c695d37937b02f78837f6406960b2a03bf9a6db34bd35e3dc7"), + aad: &hex!("b851e610be70a994808b34ca73f45f1ea973de65"), + ciphertext: &hex!("917ecc8b00b53f7fb0732d66848a106e91f60acf2dcf180832a74d5993c658da"), + tag: &hex!("2959e20746bbb6ab66dfd29b9477799a"), + }, + GcmTV { + key: &hex!("ecbfaef2345b34f31fbf6d68efb385e5833df8b6e6ae621ede02baf9735d2dba"), + nonce: &hex!("50c3527b1a35ccb318b446de"), + plaintext: &hex!("634f6dd60783d1f952353fd1d359b9ee4f4afa53cc13e81c5adfe24b46baf08f"), + aad: &hex!("f8981548bde6ee6c1745f947de191bf29997fadf"), + ciphertext: &hex!("705e5f67ab889ba238118e3fd9b90b68be801995ae307378d93b50977cf90588"), + tag: &hex!("12d14468ac18cc9936bd565f8ad42d0d"), + }, + GcmTV { + key: &hex!("dc776f0156c15d032623854b625c61868e5db84b7b6f9fbd3672f12f0025e0f6"), + nonce: &hex!("67130951c4a57f6ae7f13241"), + plaintext: &hex!("9378a727a5119595ad631b12a5a6bc8a91756ef09c8d6eaa2b718fe86876da20"), + aad: &hex!("fd0920faeb7b212932280a009bac969145e5c316cf3922622c3705c3457c4e9f124b2076994323fbcfb523f8ed16d241"), + ciphertext: &hex!("6d958c20870d401a3c1f7a0ac092c97774d451c09f7aae992a8841ff0ab9d60d"), + tag: &hex!("b876831b4ecd7242963b040aa45c4114"), + }, + GcmTV { + key: &hex!("07b3b8735d67a05632c557076ac41293f52540bac0521573e8c0414ec36f7220"), + nonce: &hex!("0046420eee8d56de35e2f7d5"), + plaintext: &hex!("4835d489828325a0cb38a59fc29cfeedccae25f2e9c399281d9b7641fb609765"), + aad: &hex!("d51cedf9a30e476de37c90b2f60882193630c7497a921ab01590a26bce8cb247e3b5590e7b07b955956ca89c7a041988"), + ciphertext: &hex!("46eb31cd98b6cc3ecafe1cd1fc2d45fa693667cbd3a7d2c5f8c10296827ea83c"), + tag: &hex!("36cd4e76dd0679887477bfb96cf1c5f6"), + }, + GcmTV { + key: &hex!("0219f14b9ca6506c1388177c4ae6ee64ad2ac0256ebbf8c219b40df6e8571d70"), + nonce: &hex!("3420a87c4b9b23ba81eb221e"), + plaintext: &hex!("348f7a4ca944f252e4562c66dacf01fb10d70a3c8f5b280a2829567a2a94e47e"), + aad: &hex!("54dc2277b8d1aae660ffcc326e2c5d9e16b8ca17288601aacd02b3eea8bc5cc60718639aa189506b7b333b87da86e940"), + ciphertext: &hex!("58c92119bfb6ad53e387cac6728ce73b82e18f6e5bfbfca5f5acc370cd8c76a4"), + tag: &hex!("e7f9e3e3dae6d0a3470d8f597291180c"), + }, + GcmTV { + key: &hex!("87440ee7f6febf3e14ef0a917a87c5d61260fefc979eeaeac0a64662c98cb4f7"), + nonce: &hex!("7c48bc75e58f21cc9989d691"), + plaintext: &hex!("f8e40a6a985f424898a7996307a077c487406c5312eefe055ea5b17a4b22087b"), + aad: &hex!("e0c66e5db1c7665a015ba7e21e08ff3de5b4a5fcd5d35e41db7e97ccd0c3df657ae803c3529d375420ad75ac9621cea0"), + ciphertext: &hex!("5a118fc3dbdaf6bc9490d372b7623af76da7841bf9820a9c6624a15eff6a69c2"), + tag: &hex!("0ddc2ae087d9b8ca2249ea5aa3dbd4c7"), + }, + GcmTV { + key: &hex!("b12425796f63bf5435740f9039fa66367fc7702d675c61b2dec4435feeea07f8"), + nonce: &hex!("f26727053e6d67c2d2bf1e69"), + plaintext: &hex!("9df079d98a6e4dbe277a8545f4f6c19fe130f4a84bdd6b760a049fba21d4e99a"), + aad: &hex!("e50fca2e5a81ae56ca07f34c4b5da140d368cceab08494f5e28f746cbfefdc285b79b33cf4969fe618b77ab7baafe271"), + ciphertext: &hex!("845f00202e2e894516d8f4a4021430e531967098c9a94024c7113c9a1b91c8cd"), + tag: &hex!("3566c75967ae00198e39ebe9f0ac697f"), + }, + GcmTV { + key: &hex!("674dfb625b8b0ce1dadbbbcbf7e151c5b2cecf0a1bc4e07f4734f3a6792350cd"), + nonce: &hex!("99e7b76e6686449616ad36c7"), + plaintext: &hex!("0a744a72e536a0484db47091609228d803bcfa9a8daf579e3039e3645f7688e2"), + aad: &hex!("2ab1573e5a94ca2997590840bd9c62e6add55e4d3eac12c895d2ec637791caa41d46ed91e6064db627e1fbef71d31d01"), + ciphertext: &hex!("e550ee77069709f5199be3c618f2a4178e4d719ab73df41cbfe32c52777138ff"), + tag: &hex!("134ac3fa8bd4af7ee836f4a3421d9e99"), + }, + GcmTV { + key: &hex!("10c1de5f741560dae5be23e15649f0114db52949560bb6cdf2d4883247392ee1"), + nonce: &hex!("7cf73c1472cd60d8d35fde51"), + plaintext: &hex!("05becd366aebaa2e609f507dd2dd4433b2aba0634b0eb9a5bf7ded4cc8fbed72"), + aad: &hex!("d3fa8b6f607a20a18dd7eac85eabef69d4fb5a074d8e7d1bf15d07732ed80e020163b475f209c4b0cbfa00d65d1e82ef"), + ciphertext: &hex!("280f0c306e1a3aab8ff9ab3e4a9adc2e9ae4e4e1a06f190d11b3b4dc4280e4f3"), + tag: &hex!("3bc8be845bf5ff844c07337c2cfd5f80"), + }, + GcmTV { + key: &hex!("e8d6ab5e514645dd7e051b028f5bfe624c72f44f30279577365aea65d4a8a819"), + nonce: &hex!("30b0d654ee5b79c2cfb24100"), + plaintext: &hex!("19be7e0feedd402bf4b05995a38e5f423c033de016e3ae83ea8c3c1cba658e1e"), + aad: &hex!("082e534bf860d0061ec2dad34d6b0db8cba1c651f2c705356ff271e47365b0b18f8ddb3a3c2269b437fb0703c9ad367a"), + ciphertext: &hex!("8573800c737d2480b2885ce714ac6a15f23287b1d12949a3d76effbe82b593bd"), + tag: &hex!("50110884292151f51213ccb2fe934d88"), + }, + GcmTV { + key: &hex!("2d1eaf5e62ca80fd1515a811c0e4c045aba8c769df03d57f7493eb623ed8b941"), + nonce: &hex!("abf190b05df2e6556cb34b47"), + plaintext: &hex!("9c7cd522ed5c0af3e57da08d2653ef77eb973734f360572bbcb15a2a6cbd60b9"), + aad: &hex!("75ab9bd39c24e498a54d85a8b76a4126dc1879f2a30270a42609763e045a4021785b6134f283fd81c195c3188e78752d"), + ciphertext: &hex!("5fdfdaccb105e5408c375af8ca63a67afaba7ccbcd591acca9a86d92f92fd0f7"), + tag: &hex!("49940b7610618b3a5cb3912339e06b3c"), + }, + GcmTV { + key: &hex!("b6020677e098c59e19eacf26732473d843aafd6bf999c707bb08ab896406918d"), + nonce: &hex!("807167ef2b84b32d1df4a94c"), + plaintext: &hex!("3199d6b95d133ba5b7eadc420080a0b249c84f4960bd369d6bf9e313627cf670"), + aad: &hex!("06225d410ada3e04157da7e5481d7d9f2285845824aac0c0e033244ed4c1b19615354c224ba8b7093c5651d10ef952fe"), + ciphertext: &hex!("4618adbfa5ea4ee260e310140b385232b7c3ad46887aa2107f7dafffd85cda22"), + tag: &hex!("2d76307bf55826dfeb58a171b6fa80e4"), + }, + GcmTV { + key: &hex!("f75456c4918d0bea72f546a9a1e2db0b6ab9bcd9782b5eb1c2700e729921d666"), + nonce: &hex!("c75b83134e7b9188e5800ffe"), + plaintext: &hex!("f9a23abbd0f2b367ce16c2a0613cd293ac7e66cbe020eaeb5deb09d5031fd992"), + aad: &hex!("5ef46c9eb5865cab2c8a35f9c4c434614a6c9f1b5c479739f7434d3326cff1e70b0d2877c084a71c7a9d33d258d304bb"), + ciphertext: &hex!("56e4efe6c0944153b65ed4909845219842b9b88f54d8d8394051132afb95d391"), + tag: &hex!("255e2c8c43f8979c440c3581bff6cf65"), + }, + GcmTV { + key: &hex!("9831c5c12e53e8a961642e93ddb2e13a38506acd0cf422e6ad9fbaeabce7b3f2"), + nonce: &hex!("bff29de3d6869e5fa75b96f9"), + plaintext: &hex!("b1edbed58ed34e99f718db0608e54dd31883baec1c8a0799c4ff8a5dad468de4"), + aad: &hex!("67ebeecb74cc81fdfee8065f8b1c1f5012bf788953bec9525e896611b827084a8e6baa0ce40ee70bc699b152bc6ed903"), + ciphertext: &hex!("13845db7e33bab1f5766a7fadfb942748e779753d97f143e645ccfcbd7c23b23"), + tag: &hex!("10dbe8a3e1901c8b88b0ab1441664d32"), + }, + GcmTV { + key: &hex!("a02c2d4a43f0f7f1db57c07f13f07f588edfe069a9d83c9b76e9511946c4fc48"), + nonce: &hex!("84677438592dcaf683d08a67"), + plaintext: &hex!("ad5a884dad20ffa88794c4fca39f2ca01c6f67657ab38e5cf86ac5597318ef07"), + aad: &hex!("d5dea0cd6080af49a1c6b4d69ace674a622f84f9f190b2db8a22e084a66500b52ff20a8d04f62a7aeaedb67e2258598c"), + ciphertext: &hex!("83da16ae07ee0e885484c1330a6255a6e7ac22915c63cbefaabc6f9f059dd69d"), + tag: &hex!("42c4a270705493d85ad7bbcfda86dffb"), + }, + GcmTV { + key: &hex!("feba412b641bc762bfa79ef17c3ea16e5630605470db096e36ffd33813641ace"), + nonce: &hex!("e3633f21e7c63a459d5d1670"), + plaintext: &hex!("9326572bd33551322ca42fcfb7cef8be41d78725f392c34907ecd1fe5572bff1"), + aad: &hex!("b7ee0233863b0e185b2f46181eb5fc0718832e1e76e7d4115a4c1f7e998c41319ccef44f5db89e8c5f077bd553d7bf42"), + ciphertext: &hex!("5019ea98cc9dc9368432c6d58f9e144f55446e763c0a8b4d8a6ce26f3dd95260"), + tag: &hex!("1010beb9cd6e9b611280a5395f08bca9"), + }, + GcmTV { + key: &hex!("21bd5691f7af1ce765f099e3c5c09786936982834efd81dd5527c7c322f90e83"), + nonce: &hex!("36a59e523df04bc7feb74944"), + plaintext: &hex!("77e539dfdab4cfb9309a75c2ee9f9e9aa1b4651568b05390d73da19f12ccbe78"), + aad: &hex!("48aef5872f67f524b54598781c3b28f9cbcf353066c3670370fca44e132761203100b5e6c7352a930f7e9cbf28a8e1ce"), + ciphertext: &hex!("c21483731f7fe1b8a17d6e133eda16db7d73ddd7e34b47eec2f99b3bbc9669aa"), + tag: &hex!("15f9265bc523298cefb20337f878b283"), + }, + GcmTV { + key: &hex!("26bf255bee60ef0f653769e7034db95b8c791752754e575c761059e9ee8dcf78"), + nonce: &hex!("cecd97ab07ce57c1612744f5"), + plaintext: &hex!("96983917a036650763aca2b4e927d95ffc74339519ed40c4336dba91edfbf9ad"), + aad: &hex!("afebbe9f260f8c118e52b84d8880a34622675faef334cdb41be9385b7d059b79c0f8a432d25f8b71e781b177fce4d4c57ac5734543e85d7513f96382ff4b2d4b95b2f1fdbaf9e78bbd1db13a7dd26e8a4ac83a3e8ab42d1d545f"), + ciphertext: &hex!("e34b1540a769f7913331d66796e00bdc3ee0f258cf244eb7663375cc5ad6c658"), + tag: &hex!("3841f02beb7a7fca7e578922d0a2f80c"), + }, + GcmTV { + key: &hex!("74ce3121c18bbff4756ad10d0f293bb1ea3f93490daad0249cd3b05e223c9747"), + nonce: &hex!("81107afb4c264f65ae0002b1"), + plaintext: &hex!("7a133385ead593c3907806bec12240943f00a8c3c1b0ac73b8b81af2d3192c6f"), + aad: &hex!("f00847f848d758494afd90b6c49375e0e76e26dcba284e9a608eae33b87ad2deac28ccf40d2db154bbe10dc0fd69b09c9b8920f0f74ea62dd68df275074e288e76a290336b3bf6b485c0159525c362092408f51167c8e59e218f"), + ciphertext: &hex!("64bd17f3e8f71a4844b970d4ebc119961812efb9015b818e8d88b906d5efbd76"), + tag: &hex!("46d0e42aa046237efee17eab6d9cfb75"), + }, + GcmTV { + key: &hex!("4c669a1969c97d56da30a46236c15407e06aada686205eed3bd7796b02c97a4b"), + nonce: &hex!("0a07758d5ad44766e051da6c"), + plaintext: &hex!("cd59bb307be76f11304f69ac8b151e1628ac61dec81086e7f24fd5bd83df8856"), + aad: &hex!("0b8277114cbf7ee16c9bbda1ab40419a02e469ebb295883f0a833c3cb755ded44a3c410034a201f7d91b43519fbabb55b974834be5d5afc7aea7c84b44a14e8e16dd68a3e8cc79ad2bf76d0ceb33d58ddb6378b45681ceaa0f2f"), + ciphertext: &hex!("bc62ce0b23cf4aa8e16b4450c8ab8c629a53949f01e68b875ecc5c45ff6d3ab0"), + tag: &hex!("5ffeda728914031006f271c3d9986f2d"), + }, + GcmTV { + key: &hex!("a23296632913051e438114deb782fb955b75acc35e86e7e9fdaf4e9025b87f12"), + nonce: &hex!("ad50db40f80f15214e43ffd7"), + plaintext: &hex!("b71116cc27b5a5844d9b51a4a720cb3f06d55d6aaeaeaf921236424db8617204"), + aad: &hex!("a6f96f5a89bfd8c8f34cd07045270d80e58ea62f1f0b10f2506a954f272af0bc71df96ad3fa8eed52c45e0b868091dc4f75d9e0eaf15a0a858a71bf7036c5607110cbfe47ad9b6d02e942fcfae88d4c792a1f824e60e3cf98a37"), + ciphertext: &hex!("8e9e4b0ac93ab8e73688d6b4723d8c5ef399ead72246c7aa7a0783a8bfe29936"), + tag: &hex!("b7dea91e4b357ce805edeea3f91392d2"), + }, + GcmTV { + key: &hex!("4036a07bdd4e10eb545f3d9124c9f766d2d0c8c59fc0d5835ac55dcfaebfc3a1"), + nonce: &hex!("815828fbb964497cdadccaad"), + plaintext: &hex!("717f22faff8066182e46d32dbac7831ec24272871c45c7c12ca779f868e7739a"), + aad: &hex!("0bc0e3931388bcb091463bae2989a93bde103bc14fc5d39f9448ca90367e86336b188f73218b2b0ab72a9a564ad5ff32544c5afeacecadfa55d2fb66925a88299dbf58f425cf49e31f42ac4edace743fdf9680d20ec845afc278"), + ciphertext: &hex!("e8c3b0342964c7a71f084d44ba2f93742bccd9821b30087d11b53bbe8b085808"), + tag: &hex!("86ddd9c469849cb6b100c339ca62717d"), + }, + GcmTV { + key: &hex!("714bc3ba3839ac6707863a40aa3db5a2eebcb38dc6ec6d22b083cef244fb09f7"), + nonce: &hex!("2cfe1c51d894e5ef2f5a2c3c"), + plaintext: &hex!("0cc4a18bbfea87de0ac3446c777be38ca843d16f93be2c12c790fda4de94c9bf"), + aad: &hex!("84e3d46af2ecb717a39024d62bbc24d119f5aff57569dfef94e7db71ad5aff864abacdc5f8554e18ed5129cfb3366d349c52b3d1a111b867e8772140749e7f33e2e64259968486e32f047d21120da73c77757c4595ccac1b5713"), + ciphertext: &hex!("0857c8fb93412fde69bad287b43deea36506d7ee061d6844d00a7e77418f702f"), + tag: &hex!("24a9e5290957074807d55ad705adaa89"), + }, + GcmTV { + key: &hex!("2f93b5a37be1a43853bf1fd578061d0744e6bd89337cde20177d1e95a2b642c4"), + nonce: &hex!("52b6d91557ae15aa792ce4b7"), + plaintext: &hex!("0fcaa316a135d81052509dd85f688aed2e5fd4261e174f435cf1c4115aa6f354"), + aad: &hex!("992ba9efa287a5c3e5177bd4931af498982a1728b56b3d7c4b28476905e29f83326c4f3223a28844fc9b9d84d4f6cd859074aff647a35dde28e1ee889faab3bb9c09a4c3fbf2a16460d48a40dc53378d4673f4325e6aa3992a71"), + ciphertext: &hex!("f99774cef3c15af33cda3cb449cd335ffe4f27435edf83aff4a4f4c2d2df6647"), + tag: &hex!("c5e09b83b1c2cc81e48a1f7c62b7bb35"), + }, + GcmTV { + key: &hex!("531ca845af7bf731c49c3136407322b1c0f6b32b8eaebf03744b2edc1202d096"), + nonce: &hex!("baf13b85202bbfc899fc73f7"), + plaintext: &hex!("d4e9783f537c738200e7ba7526605f359a98c9f10cafaa2f433c40f3e5081a36"), + aad: &hex!("e2ba9cf548b4f6fb206f224250d85af327fde8d08916686ae770203dc29c694f8902b02222fd287f28ce6091006368c3949bea2937ff0bdedb7dbbd013ccf0a15ee0af8c56fe211b7c311e182f27707f59e09492b3604e80c6c5"), + ciphertext: &hex!("642f544929202128a783b985d36f60964c7d78e1d41f5d1bfe27de3ae0180df3"), + tag: &hex!("e333528c59ee1909750ed72fd1309ee1"), + }, + GcmTV { + key: &hex!("3add17568daa9d441aa7a89bf88fa4e6998a921d57e494a254080445bc9b6f35"), + nonce: &hex!("b290f4a52496380218c3dcf5"), + plaintext: &hex!("2c6908cb34215f89a3f3a3c892e8887f2efa496a15ab913fc7d34cc70c0dff79"), + aad: &hex!("0bc9cc13eb2890aa60515c2297a99f092f6e516236c0dec9f986ea98b8a180680f2c6c20bd4354c33433a4c6f6a25e632f90ebef3a383c3592268b483eebf5f5db006929e7987edbcac4755d3afd1cdf9b02954ebd4fef53d5f6"), + ciphertext: &hex!("2cf3beae94fd5e6a4126a8ec8a7166b0aacb8b8bbce45d6106b78d3456d05149"), + tag: &hex!("ce1509b1bd5c47a593702618b0d79f6c"), + }, + GcmTV { + key: &hex!("1c1dcfd4c4cc4beb71d6e368f739d8e681dfe48fbae39728386c9dfc08825743"), + nonce: &hex!("0deceb69ce0dc776a3a71b4c"), + plaintext: &hex!("b12700258ace7b16e40f4e86886892837168b256a170937a3b89063a9a0d68f7"), + aad: &hex!("a3af2db672292431fa8ee1fa5b197593b13e58a68c4129401d0942474d5f4cbe62093aaa5453f6d355d2f4b6dc8abde58ce863d1be5f9ecf39730a49565b3b6882a0a641c0b5d156a4107309dd150fd1f1634ea4e5100b3d4f88"), + ciphertext: &hex!("3ea7f1c0d613323e095558ddde53247420fa0eef17997a1e9c5ba93d5f24c46f"), + tag: &hex!("70534a87c258905d35806f4439f6906e"), + }, + GcmTV { + key: &hex!("f2724153aac9d50f350878d3c498bc3dd782d90cce5cce4ae14126c0e1fbb3cf"), + nonce: &hex!("1c07b61c5316659bad65cca9"), + plaintext: &hex!("067ccbd0206f1f05d2872210dc5717a0585e8195d72afd0c77da11b9b3710e44"), + aad: &hex!("e69db7fcd3b590a6d32052612034036d5c8bffa5e5e9b742ffe75a9fbba89dd576dec08154cf4e6d36f0fdd4419bdf50adc1974a80ea313421c926dffa87565b4bd0c1e84f2ff305af91877f830f145bb13dfa7efa5e3aa682e6"), + ciphertext: &hex!("9aba433eef383466a1291bd486c3ce5e0ed126010e0a77bf037c5eaed2c72460"), + tag: &hex!("f30a155e35400bb0540883e8e09b4afd"), + }, + GcmTV { + key: &hex!("a2544eb2047c97cfcaf0ec1427c5df395472285233a93ffccda8fee660aced56"), + nonce: &hex!("a751bea3c769bb5db25ab109"), + plaintext: &hex!("b9514cc01a357605918f9cc19123dcc8db328c605ca0eb9d69d871afeea1dcfb"), + aad: &hex!("eb9e09884de1454d6aeb0d6c82375f2428992031ea6cabf6a29aa6a4de49a353e4ffae043dad18ae651b20b7bca13f5c327ca9f132014bfa86e716d4724e05a1ef675521a6607a536756e6a8c16bb885b64815f1eb5ec282ce8e"), + ciphertext: &hex!("cb442b17088f6ac5f24c7a04f0050559386f3a57131b92a54142c7a556fdb935"), + tag: &hex!("5f80c5c0cdf0c7890bfd1fbd58c33081"), + }, + GcmTV { + key: &hex!("ceb057782efb1e85d805448af946a9b4d4128bf09a12473cce1e8ef8bfd2869d"), + nonce: &hex!("406f9730e9b1e421e428439b"), + plaintext: &hex!("0815723d5367b1328cac632fa26e23f2b814a1d59a2971d94d02ebd7ecf5c14a"), + aad: &hex!("0772ae00e1ca05d096cf533fd3de2818ac783edfca0eee7686a6290f3357481e883fb2f895b9a4f4004c56b8a1265242cfdf1fb4af7edc41ed78c5f4ffe9c4080d4a17318f9c56ecdb3a06f3c748535387d56a096943a76d46f6"), + ciphertext: &hex!("9d82355d8e460896201be15fd95fed48a8524666d987ab078550883034d0253c"), + tag: &hex!("a0bee8ac0e636d64d3b1eb33fd6f21d4"), + }, + GcmTV { + key: &hex!("7dbdbdfe36d4936940ad6d6f76c67c2851a0477f0aa7d6797bfdf2b7878ef7e0"), + nonce: &hex!("bc672b224b4b6b91fc3fd697"), + plaintext: &hex!("dfea463d35f0fa20487b606d6ccfd422a5b707f16527b422bf1d68a77db67e9c"), + aad: &hex!("faacb84ec7cfadd731de2f7c0892d7e38cbfb782b48412331af0b3eab602a722cad1069dea0052beb5ca70e2ee476c340c6193bcc60f939aabe446bf3ce958fe11a2ffc90241f0a7e4e274f0c1441def795893895bd848bf0f0e"), + ciphertext: &hex!("0ddc2281b1fcb904864a43657bc72357cf73fc1f16520caad7cddde10f846bd9"), + tag: &hex!("9d96699450aa9707695e5de56597101b"), + }, + GcmTV { + key: &hex!("187214df6e2d80ee8e9aae1fc569acd41589e952ddcbe8da018550d103767122"), + nonce: &hex!("56db334422b6c5e93460d013"), + plaintext: &hex!("53355283186719a9146c7305e3d1959a11ccf197570b855a43cbc7563a053c73"), + aad: &hex!("cbedb7ccfbf56dfd72e530bfe16b4f5aac48a90204bcb7a8cae1046010882cfc8b526e7562a7880914e61b60cbd605165242737d85eeed583c98cab3443874e5989ec9cde001adf7de9c9967de5178f75b8412b0c4d6fec5af72"), + ciphertext: &hex!("c2262585966bc9c23dc7cc1059d060211e86f3b3161d38b153635fbea4a28c05"), + tag: &hex!("a94297c584dfcd10ee5df19a2ee5c3d2"), + }, + GcmTV { + key: &hex!("1fded32d5999de4a76e0f8082108823aef60417e1896cf4218a2fa90f632ec8a"), + nonce: &hex!("1f3afa4711e9474f32e70462"), + plaintext: &hex!("06b2c75853df9aeb17befd33cea81c630b0fc53667ff45199c629c8e15dce41e530aa792f796b8138eeab2e86c7b7bee1d40b0"), + aad: b"", + ciphertext: &hex!("91fbd061ddc5a7fcc9513fcdfdc9c3a7c5d4d64cedf6a9c24ab8a77c36eefbf1c5dc00bc50121b96456c8cd8b6ff1f8b3e480f"), + tag: &hex!("30096d340f3d5c42d82a6f475def23eb"), + }, + GcmTV { + key: &hex!("b405ac89724f8b555bfee1eaa369cd854003e9fae415f28c5a199d4d6efc83d6"), + nonce: &hex!("cec71a13b14c4d9bd024ef29"), + plaintext: &hex!("ab4fd35bef66addfd2856b3881ff2c74fdc09c82abe339f49736d69b2bd0a71a6b4fe8fc53f50f8b7d6d6d6138ab442c7f653f"), + aad: b"", + ciphertext: &hex!("69a079bca9a6a26707bbfa7fd83d5d091edc88a7f7ff08bd8656d8f2c92144ff23400fcb5c370b596ad6711f386e18f2629e76"), + tag: &hex!("6d2b7861a3c59ba5a3e3a11c92bb2b14"), + }, + GcmTV { + key: &hex!("fad40c82264dc9b8d9a42c10a234138344b0133a708d8899da934bfee2bdd6b8"), + nonce: &hex!("0dade2c95a9b85a8d2bc13ef"), + plaintext: &hex!("664ea95d511b2cfdb9e5fb87efdd41cbfb88f3ff47a7d2b8830967e39071a89b948754ffb0ed34c357ed6d4b4b2f8a76615c03"), + aad: b"", + ciphertext: &hex!("ea94dcbf52b22226dda91d9bfc96fb382730b213b66e30960b0d20d2417036cbaa9e359984eea947232526e175f49739095e69"), + tag: &hex!("5ca8905d469fffec6fba7435ebdffdaf"), + }, + GcmTV { + key: &hex!("aa5fca688cc83283ecf39454679948f4d30aa8cb43db7cc4da4eff1669d6c52f"), + nonce: &hex!("4b2d7b699a5259f9b541fa49"), + plaintext: &hex!("c691f3b8f3917efb76825108c0e37dc33e7a8342764ce68a62a2dc1a5c940594961fcd5c0df05394a5c0fff66c254c6b26a549"), + aad: b"", + ciphertext: &hex!("2cd380ebd6b2cf1b80831cff3d6dc2b6770778ad0d0a91d03eb8553696800f84311d337302519d1036feaab8c8eb845882c5f0"), + tag: &hex!("5de4ef67bf8896fbe82c01dca041d590"), + }, + GcmTV { + key: &hex!("1c7690d5d845fceabba227b11ca221f4d6d302233641016d9cd3a158c3e36017"), + nonce: &hex!("93bca8de6b11a4830c5f5f64"), + plaintext: &hex!("3c79a39878a605f3ac63a256f68c8a66369cc3cd7af680d19692b485a7ba58ce1d536707c55eda5b256c8b29bbf0b4cbeb4fc4"), + aad: b"", + ciphertext: &hex!("c9e48684df13afccdb1d9ceaa483759022e59c3111188c1eceb02eaf308035b0428db826de862d925a3c55af0b61fd8f09a74d"), + tag: &hex!("8f577e8730c19858cad8e0124f311dd9"), + }, + GcmTV { + key: &hex!("dbdb5132f126e62ce5b74bf85a2ac33b276588a3fc91d1bb5c7405a1bf68418b"), + nonce: &hex!("64f9e16489995e1a99568118"), + plaintext: &hex!("b2740a3d5647aa5aaeb98a2e7bbf31edaea1ebacd63ad96b4e2688f1ff08af8ee4071bf26941c517d74523668ca1f9dfdbcaab"), + aad: b"", + ciphertext: &hex!("e5fec362d26a1286b7fd2ec0fa876017437c7bce242293ff03d72c2f321d9e39316a6aa7404a65ccd84890c2f527c1232b58d5"), + tag: &hex!("dfa591ee2372699758d2cc43bfcbd2ba"), + }, + GcmTV { + key: &hex!("8433a85f16c7c921476c83d042cb713eb11a83fc0cffe31dde97907f060b4ee9"), + nonce: &hex!("55ffc85ffd1cdea8b8c48382"), + plaintext: &hex!("23bc3983ba5b3be91c8a6aa148a99995241ee9e82ce44e1184beb742affbe48f545c9a980480cf1fab758a46e4711ea9267466"), + aad: b"", + ciphertext: &hex!("2f4bdc7b8b8cec1863e3145871554778c43963b527f8413bb9779935c138a34d86d7c76a9e6af689902f316191e12f34126a42"), + tag: &hex!("7dc63156b12c9868e6b9a5843df2d79e"), + }, + GcmTV { + key: &hex!("5d7bf55457929c65e4f2a97cbdcc9b432405b1352451ccc958bceebce557491d"), + nonce: &hex!("f45ae70c264ed6e1cc132978"), + plaintext: &hex!("ba5ac2a16d84b0df5a6e40f097d9d44bf21de1fcec06e4c7857463963e5c65c936d37d78867f253ce25690811bf39463e5702a"), + aad: b"", + ciphertext: &hex!("47c16f87ebf00ba3e50416b44b99976c2db579423c3a3420479c477cd5ef57621c9c0cee7520acb55e739cc5435bc8665a2a0c"), + tag: &hex!("456054ecb55cf7e75f9543def2c6e98c"), + }, + GcmTV { + key: &hex!("595f259c55abe00ae07535ca5d9b09d6efb9f7e9abb64605c337acbd6b14fc7e"), + nonce: &hex!("92f258071d79af3e63672285"), + plaintext: &hex!("a6fee33eb110a2d769bbc52b0f36969c287874f665681477a25fc4c48015c541fbe2394133ba490a34ee2dd67b898177849a91"), + aad: b"", + ciphertext: &hex!("bbca4a9e09ae9690c0f6f8d405e53dccd666aa9c5fa13c8758bc30abe1ddd1bcce0d36a1eaaaaffef20cd3c5970b9673f8a65c"), + tag: &hex!("26ccecb9976fd6ac9c2c0f372c52c821"), + }, + GcmTV { + key: &hex!("251227f72c481a7e064cbbaa5489bc85d740c1e6edea2282154507877ed56819"), + nonce: &hex!("db7193d9cd7aeced99062a1c"), + plaintext: &hex!("cccffd58fded7e589481da18beec51562481f4b28c2944819c37f7125d56dceca0ef0bb6f7d7eeb5b7a2bd6b551254e9edff3a"), + aad: b"", + ciphertext: &hex!("1cc08d75a03d32ee9a7ae88e0071406dbee1c306383cf41731f3c547f3377b92f7cc28b3c1066601f54753fbd689af5dbc5448"), + tag: &hex!("a0c7b7444229a8cfef24a31ee2de9961"), + }, + GcmTV { + key: &hex!("f256504fc78fff7139c42ed1510edf9ac5de27da706401aa9c67fd982d435911"), + nonce: &hex!("8adcf2d678abcef9dd45e8f9"), + plaintext: &hex!("d1b6db2b2c81751170d9e1a39997539e3e926ca4a43298cdd3eb6fe8678b508cdb90a8a94171abe2673894405eda5977694d7a"), + aad: b"", + ciphertext: &hex!("76205d63b9c5144e5daa8ac7e51f19fa96e71a3106ab779b67a8358ab5d60ef77197706266e2c214138334a3ed66ceccb5a6cd"), + tag: &hex!("c1fe53cf85fbcbff932c6e1d026ea1d5"), + }, + GcmTV { + key: &hex!("21d296335f58515a90537a6ca3a38536eba1f899a2927447a3be3f0add70bea5"), + nonce: &hex!("2be3ad164fcbcf8ee6708535"), + plaintext: &hex!("ad278650092883d348be63e991231ef857641e5efc0cab9bb28f360becc3c103d2794785024f187beaf9665b986380c92946a7"), + aad: b"", + ciphertext: &hex!("b852aeba704e9d89448ba180a0bfde9e975a21cc073d0c02701215872ed7469f00fe349294ba2d72bf3c7780b72c76101ba148"), + tag: &hex!("bdd6d708b45ae54cd8482e4c5480a3c1"), + }, + GcmTV { + key: &hex!("d42380580e3491ddfbc0ec32424e3a281cbe71aa7505ff5ab8d24e64fbe47518"), + nonce: &hex!("fbed88de61d605a7137ffeb2"), + plaintext: &hex!("4887a6ef947888bf80e4c40d9769650506eb4f4a5fd241b42c9046e3a2cf119db002f89a9eba1d11b7a378be6b27d6f8fc86c9"), + aad: b"", + ciphertext: &hex!("87aa27f96187ce27e26caf71ba5ba4e37705fd86ca9291ea68d6c6f9030291cdbff58bff1e6741590b268367e1f1b8c4b94cd4"), + tag: &hex!("d1690a6fe403c4754fd3773d89395ecd"), + }, + GcmTV { + key: &hex!("5511727ecd92acec510d5d8c0c49b3caacd2140431cf51e09437ebd8ca82e2ce"), + nonce: &hex!("ae80d03696e23464c881ccff"), + plaintext: &hex!("184b086646ef95111ccb3d319f3124f4d4d241f9d731ce26662ea39e43457e30b0bd739b5d5dbceb353ce0c3647a3a4c87e3b0"), + aad: b"", + ciphertext: &hex!("aa28cb257698963dfc3e3fe86368d881ac066eb8ee215a7c0ed72e4d081db0b940071e2e64ff6204960da8e3464daf4cb7f37b"), + tag: &hex!("c1578aa6e3325ee4b5e9fb9ee62a7028"), + }, + GcmTV { + key: &hex!("d48f3072bbd535a2df0a2864feb33b488596cd523ad1623b1cefe7b8cbefcf4a"), + nonce: &hex!("bbf2a537d285444d94f5e944"), + plaintext: &hex!("060c585bd51539afdd8ff871440db36bfdce33b7f039321b0a63273a318bd25375a2d9615b236cfe63d627c6c561535ddfb6bd"), + aad: b"", + ciphertext: &hex!("993d5d692c218570d294ab90d5f7aa683dc0e470efac279a776040f3b49386813f68b0db6a7aef59025cc38520fb318a1eac55"), + tag: &hex!("8cd808438a8f5b6a69ff3ae255bf2cb2"), + }, + GcmTV { + key: &hex!("5fe01c4baf01cbe07796d5aaef6ec1f45193a98a223594ae4f0ef4952e82e330"), + nonce: &hex!("bd587321566c7f1a5dd8652d"), + plaintext: &hex!("881dc6c7a5d4509f3c4bd2daab08f165ddc204489aa8134562a4eac3d0bcad7965847b102733bb63d1e5c598ece0c3e5dadddd"), + aad: &hex!("9013617817dda947e135ee6dd3653382"), + ciphertext: &hex!("16e375b4973b339d3f746c1c5a568bc7526e909ddff1e19c95c94a6ccff210c9a4a40679de5760c396ac0e2ceb1234f9f5fe26"), + tag: &hex!("abd3d26d65a6275f7a4f56b422acab49"), + }, + GcmTV { + key: &hex!("885a9b124137e40bd0f697771317e401ce36327e61a8f9d0b80f4798f30a731d"), + nonce: &hex!("beebc2f5a26fd2cab1e9c395"), + plaintext: &hex!("427ec568ad8367c202f5d9999240f9994cc113500154f7f49e9ca27cc8154143b855238bca5c7bd6d9852b4eebd41e4eb98f16"), + aad: &hex!("2e8bdde32258a5fcd8cd21037d0545eb"), + ciphertext: &hex!("a1d83aab6864db463d9d7c22419462bde0740355c1147c62b4c4f23ceeaf65b16b873b1cc7e698dff6e3d19cf9da33e8cbcba7"), + tag: &hex!("4fdbfd5210afa3556ec0fdc48b98e1eb"), + }, + GcmTV { + key: &hex!("21c190e2b52e27b107f7a24b913a34bd5b7022060c5a4dec9ab289ff8ae67e2d"), + nonce: &hex!("b28a61e6c1dfa7f76d086063"), + plaintext: &hex!("4e1b9528cf46b1dd889858d3904d41d3174dcb225923f923d80adbfe6eec144b1d4eb3690d0b8519c99beaee25bb50fd2d148f"), + aad: &hex!("d80657377ddbbed1f9b8d824b3c4d876"), + ciphertext: &hex!("7126fa807aa6b61a60958fe4cc8682bb256e5bbdc499d04a6caa81b23f9e67d3da4cf1994b5a8ecc7bce641864d0519a6509cd"), + tag: &hex!("d3e96568f2cd1a48771ee4f67ad042c1"), + }, + GcmTV { + key: &hex!("11c33ae37680130c51ed11bfaf0fcb6ed4fc7d903ff432b811763d2c7ef83a33"), + nonce: &hex!("0f224d26dbf632cebdce3b8b"), + plaintext: &hex!("f8a2affe5a7e67f2c62622e4a56804b48e529d1faf9096f94409224129921ce46aed898dd5391746e8170e05f91e0524166625"), + aad: &hex!("dee803732ff662cba9f861227f8b67cf"), + ciphertext: &hex!("3856558375c363b25e8f9e9e2eb63cf0e76a1c6e228893c7b22da4a69b682528b4a4ca2b99e7a537390e2d1e05a68f3e39c4e9"), + tag: &hex!("9b12691b2002ca9227035c68ea941ef3"), + }, + GcmTV { + key: &hex!("3b291794fbb9152c3e4f4de4608a9137d277bd651f97e738afaa548d97b4ec60"), + nonce: &hex!("4d1c69c6da96c085d31422ba"), + plaintext: &hex!("21b3ca1f47a0c7f6ebd097eda69d9e5b5fbf5c24d781658003cfd443ae7096be19e1cd3c14fe9738efb00847697fccb466ae1b"), + aad: &hex!("f3a5fa61a4e987413a8fab4aa51d895d"), + ciphertext: &hex!("6c1439cd2cb564e7944fd52f316e84aeffc3fd8024df5a7d95a87c4d31a0f8ea17f21442c709a83b326d067d5f8e3005ebe22a"), + tag: &hex!("e58048f2c1f806e09552c2e5cdf1b9d9"), + }, + GcmTV { + key: &hex!("8e7a8e7b129326e5410c8ae67fbd318de1909caba1d2b79210793c6b2c6e61c7"), + nonce: &hex!("8e48513fdd971861ef7b5dc3"), + plaintext: &hex!("ef6b4145910139293631db87a0d7782a1d95db568e857598128582e8914b4fa7c03c1b83e5624a2eb4c340c8ad7e6736a3e700"), + aad: &hex!("80bb66a4727095b6c201fb3d82b0fcf5"), + ciphertext: &hex!("e302687c0548973897a27c31911fc87ee93d8758c4ded68d6bd6415eaaf86bcc45fa6a1ef8a6ae068820549b170405b3fc0925"), + tag: &hex!("ff5c193952558e5a120e672f566be411"), + }, + GcmTV { + key: &hex!("d687e0262f7af2768570df90b698094e03b668ce6183b6c6b6ca385dcd622729"), + nonce: &hex!("50f6904f2d8466daa33c2461"), + plaintext: &hex!("79e3067d94464e019a7c8af10b53adf5b09426d35f2257c3cbaffe1ff720565c07e77aeef06f9d03a2353053992073a4ed1fc8"), + aad: &hex!("e8fa99432929d66f10205ad3e9592151"), + ciphertext: &hex!("18f6e6aeecc8dc5a3d0b63a2a8b7bfaf695bd9c49a7392dbfa8ed44771eebe27f94589d8a430da4cf03a8693bc7525e1fcac82"), + tag: &hex!("3c864eaa1b0ae44a7f0ad9ba287ba800"), + }, + GcmTV { + key: &hex!("26dc5ce74b4d64d1dc2221cdd6a63d7a9226134708299cd719a68f636b6b5ebd"), + nonce: &hex!("0294c54ff4ed30782222c834"), + plaintext: &hex!("ae4c7f040d3a5ff108e29381e7a0830221d5378b13b87ef0703c327686d30af004902d4ddb59d5787fecea4731eaa8042443d5"), + aad: &hex!("2a9fb326f98bbe2d2cf57bae9ecbeff7"), + ciphertext: &hex!("9601aec6bc6e8a09d054a01e500a4e4cdcc7c2cf83122656be7c26fc7dc1a773a40be7e8a049a6cdf059e93a23ca441ef1ca96"), + tag: &hex!("b620a8a0c8fe6117f22735c0ca29434c"), + }, + GcmTV { + key: &hex!("7fa0644efc7f2e8df4b311f54ba8b8c975b2c2aa97962f8ca8a322541bedaa9d"), + nonce: &hex!("5e774e45a07eeb9721734412"), + plaintext: &hex!("84d1c75455e4c57419a9d78a90efc232c179517fe94aff53a4b8f7575db5af627f3d008006f216ecfc49ab8da8927ff5dc3959"), + aad: &hex!("6ad673daa8c412bf280ea39ba0d9b6d4"), + ciphertext: &hex!("e2f00b5a86b3dec2b77e54db328c8d954d4b716f9735e5798b05d65c512674d56e88bda0d486685a45d5c249719884329e3297"), + tag: &hex!("0ce8eb54d5ad35dd2cb3fa75e7b70e33"), + }, + GcmTV { + key: &hex!("91d0429f2c45cf8ab01d50b9f04daaaccbe0503c9f115f9457c83a043dc83b23"), + nonce: &hex!("34401d8d922eebac1829f22e"), + plaintext: &hex!("d600d82a3c20c94792362959de440c93119a718ac749fa88aa606fc99cb02b4ca9ba958d28dc85f0523c99d82f43f58c5f979b"), + aad: &hex!("1b29de9321aebc3ff9d1c2507aee80e9"), + ciphertext: &hex!("84cbc9936eb7270080bb7024780113d064eccb63d3da0bd6bce4f8737d28304bfb6102f3ae9c394cc6452633fc551582bbfe1d"), + tag: &hex!("e132dc8a31d21f24ea0e69dfb6b26557"), + }, + GcmTV { + key: &hex!("44e6411b9fbfcef387d0ca07b719181c7567e27dba59e8e1c3cc1763cfeaca04"), + nonce: &hex!("25a1cfd97bd8e63de5d65974"), + plaintext: &hex!("db28a592b1f3603c287991a69cc64eacdd62046445a8ba4067575f12553de155d06a9b40ddf58fec56c8171687b9cb54b1f346"), + aad: &hex!("4b1751b074ab649d27fd3f2c4d7ee33a"), + ciphertext: &hex!("36bf6bb761b2248fe71a620e34e9d18e12a74ca42c9a9a21d30345995a83eb44bcae3c67c020730cd8d5e51a741694cc396469"), + tag: &hex!("e69ebf80a88d6eca41ae87cdcab4e1f2"), + }, + GcmTV { + key: &hex!("a94bfcefae90f9078860db80ccc50819eadf7cce29df3279f94f5eea97009ef2"), + nonce: &hex!("f481bcb7f5da296e9454ff78"), + plaintext: &hex!("97d0c7dfcab32a386f51d92e89333ec84eecd552e68d14cf48b75067bf0e1946ad03a5d063b852ca053c929088af45d0884a88"), + aad: &hex!("9f80d845577818df9ba984ee552ae203"), + ciphertext: &hex!("18a1c9bfe1b1dfdd06e465df347c1e942b37b3e48cb0c905841a593b5b0d0330feb3b8970dbc9429252a897f0f8e12860ea39a"), + tag: &hex!("10cf4d335b8d8e7e8bbaf49222a1cd66"), + }, + GcmTV { + key: &hex!("a50a60e568ff35a610ef9479c08bbc7bb64c373fc853f37fa6b350250a26f232"), + nonce: &hex!("5ada1d4aca883d7bd6fa869f"), + plaintext: &hex!("9ea44e72a1d21395cd81d20db05816441010efd8f811b75bb143ab47f55eefce4eec5f606fa5d98b260d7e5df4a7474cbd8599"), + aad: &hex!("cc7a7a541be7a6d1b846354cb6a571e6"), + ciphertext: &hex!("4165b135187faeb395d4531c062738e0d47df8bed91982eb32e391a6b3711f117b6fae0afde791de3e72fcf96d2b53ff1a621a"), + tag: &hex!("e2cbfea2100585b2cbe5107da17ff77a"), + }, + GcmTV { + key: &hex!("5ff3311461d247ceb1eaf591292fcba54308dd3484fd1851e09a12b8f6663fc1"), + nonce: &hex!("61af2e6aec183129cf053c2b"), + plaintext: &hex!("920df8b2888a74022ede6919ed0bf48ccf51e395fe5bfa69a6209ff9a46674024eaa4f43ae2c933730b9fdc8ad216130447cc8"), + aad: &hex!("5eafed6674f2ae83397df923e059db49"), + ciphertext: &hex!("0e35e1208168b639e012df398bc8bf2b19b08d46af0353cd78f6d1b7ae14e6224c1da6fdc9433b171f1cd2b512d5f1acd84f03"), + tag: &hex!("5bc77eb02e4d51e2019446b468498d0e"), + }, + GcmTV { + key: &hex!("42e93547eee7e18ec9620dd3dc0e2b1cf3e5d448198a902ded3f935da9d35b33"), + nonce: &hex!("e02e12ba92a6046af11adf0e"), + plaintext: &hex!("6c3704b32527ace3d5236687c4a98a1ad5a4f83c04af2f62c9e87e7f3d0469327919d810bb6c44fd3c9b146852583a44ed2f3c"), + aad: &hex!("ac3d536981e3cabc81211646e14f2f92"), + ciphertext: &hex!("8b6506af703ae3158eb61e2f9c2b63de403b2ebc6b1e6759ceb99c08aa66cb07d1d913ac4acd7af9b9e03b3af602bcaf2bb65e"), + tag: &hex!("a6ce2ccb236fc99e87b76cc412a79031"), + }, + GcmTV { + key: &hex!("24501ad384e473963d476edcfe08205237acfd49b5b8f33857f8114e863fec7f"), + nonce: &hex!("9ff18563b978ec281b3f2794"), + plaintext: &hex!("27f348f9cdc0c5bd5e66b1ccb63ad920ff2219d14e8d631b3872265cf117ee86757accb158bd9abb3868fdc0d0b074b5f01b2c"), + aad: &hex!("adb5ec720ccf9898500028bf34afccbcaca126ef"), + ciphertext: &hex!("eb7cb754c824e8d96f7c6d9b76c7d26fb874ffbf1d65c6f64a698d839b0b06145dae82057ad55994cf59ad7f67c0fa5e85fab8"), + tag: &hex!("bc95c532fecc594c36d1550286a7a3f0"), + }, + GcmTV { + key: &hex!("fb43f5ab4a1738a30c1e053d484a94254125d55dccee1ad67c368bc1a985d235"), + nonce: &hex!("9fbb5f8252db0bca21f1c230"), + plaintext: &hex!("34b797bb82250e23c5e796db2c37e488b3b99d1b981cea5e5b0c61a0b39adb6bd6ef1f50722e2e4f81115cfcf53f842e2a6c08"), + aad: &hex!("98f8ae1735c39f732e2cbee1156dabeb854ec7a2"), + ciphertext: &hex!("871cd53d95a8b806bd4821e6c4456204d27fd704ba3d07ce25872dc604ea5c5ea13322186b7489db4fa060c1fd4159692612c8"), + tag: &hex!("07b48e4a32fac47e115d7ac7445d8330"), + }, + GcmTV { + key: &hex!("9f953b9f2f3bb4103a4b34d8ca2ec3720df7fedf8c69cac900bd75338beababe"), + nonce: &hex!("eb731ae04e39f3eb88cc77fa"), + plaintext: &hex!("3b80d5ac12ba9dad9d9ff30a73732674e11c9edf9bb057fd1c6adc97cf6c5fa3ee8690ad4c51b10b3bd5da9a28e6275cbe28cb"), + aad: &hex!("d44a07d869ac0d89b15262a1e8e1aa74f09bcb82"), + ciphertext: &hex!("1533ce8e2fc6ab485aef6fcfb08ded83ae549a7111fce2a1d8a3f691f35182ce46fce6204d7dafb8d3206c4e4b645bc3f5afd1"), + tag: &hex!("f09265c21f90ef79b309a93db73d9290"), + }, + GcmTV { + key: &hex!("2426e2d1cd9545ec2fb7ab9137ad852734333925bfc5674763d6ee906e81c091"), + nonce: &hex!("49a094a71d393b36daa4a591"), + plaintext: &hex!("7cbe7982d365a55d147c954583f9760a09948ab73ebbe1b2c1d69ed58e092a347392192cfe8bce18ca43ee19af7652331bd92c"), + aad: &hex!("177309cfc913e3f5c093e8b1319ba81826d43ce5"), + ciphertext: &hex!("cab992e17cf6ec69fd3c67ea0424bcd67475a7f1f16e6733c4419d1b5a755f78d6eda8e368360d403800a08f0d52b4bc0aa0ab"), + tag: &hex!("b125f8caee9e54b9f9414b1c09021ed8"), + }, + GcmTV { + key: &hex!("8dc1b24bcbbee3cb8e14b344166d461d00c7490041edc9fa07e19cc82a3ed9c4"), + nonce: &hex!("31768ad18c971b188d947019"), + plaintext: &hex!("84e4f79dbb7209cbaf70e4fefe137c494786c899602783e9c034296978d7f0c571f7ea9d80ed0cc4723124872d7326890300c1"), + aad: &hex!("eb3673b64560cca7bda76a1de7ae1014ee1acaee"), + ciphertext: &hex!("2402acd865d4b731bc9395eae0e57d38fdf5ce847ac7aef75791a52c7573ea9b3a296e62cb1ed97c4bd34be50ee7f3d75747cf"), + tag: &hex!("665abb725498ede2b0df655fc1765a2b"), + }, + GcmTV { + key: &hex!("bc898f643a5f2cd864c10b507b4b803b4ff4ace61fadcc7bcd98af394731b791"), + nonce: &hex!("cc447d83c0a6734a79778c64"), + plaintext: &hex!("124eb963cdb56fa49c70a9b1aa682445c55065f26859f1d16eef7cfe491587533eedd7e23deabddfc5550c2fa6a08b17822699"), + aad: &hex!("e932bd2e0e6c550d136f725e14c53d27ffb20f6a"), + ciphertext: &hex!("45d8908ef9eef369e78b7ea0b7d023a92c63648271927efe9b0220eb09ed96f3b635c6ec8bfc68b4c228b712494bb37f4c7f1a"), + tag: &hex!("47899857494bac28d2176a9c923026b2"), + }, + GcmTV { + key: &hex!("8e82a85466ee024eb1ae10c4982d6a95e6dbe5582299ab37fe89a9db80ab51a6"), + nonce: &hex!("04cfd489e18eeb7a4a8ab36b"), + plaintext: &hex!("3aa2e4eaed18c4602715ae77379e9083708af9f9b49031324d41abca61440319c8c8e6dbcc20006a825b12ced00b2286848a94"), + aad: &hex!("7bb54b1a6ed0ca387268a146430c0bfa2602a8fd"), + ciphertext: &hex!("674b1391937074642408eeae9b748ca629da9fd00281824f5a108f6078ee78f98749392bb6e29b53e53e4b11739ac53a8e653b"), + tag: &hex!("e320a873a9c2e8ef455698c37ea59a6d"), + }, + GcmTV { + key: &hex!("f1f2c5503ebf35ac1373c29e2305e963f89f6ed015a181b70fb549429805d5d9"), + nonce: &hex!("2fb5c6a24f406872755db05c"), + plaintext: &hex!("b4a2809198035c277637bb1c2927fb5c60b49ef9087c800012d8663d997983fcb78d51a054114a24e1e1b5214b58e7dee47195"), + aad: &hex!("92c1f3489aed90aedafb55562a34b3f4be29e101"), + ciphertext: &hex!("f051a3a968278a46630b2894a0d386c18fa034960d8ddd14e88e1071afbbca5baf02967c2270117b4fb2bd4cfd032174505f99"), + tag: &hex!("6f1db5293660b6904f7f008e409bdc06"), + }, + GcmTV { + key: &hex!("f0338d26d74bd1768da5bb79c59fab2b4abe1966324048790c44bc98a6b34b6c"), + nonce: &hex!("c8269e4406fa0be1cf057b2f"), + plaintext: &hex!("323c373e4d85a1fd21f387fdd8c7e6aeebd5aae893d7af286cb214600cba8b9eb06df085a2dc5aed870259f7f3cc81d3eb53bd"), + aad: &hex!("13fb0edcba095cef9c4343a0629fd5020f03729d"), + ciphertext: &hex!("08572b9cf9bcfd21d4403a1218d94476b9ee8c3b94c56625c21ccaf4c0efa34cf22a532389210793699c9de1ab14f8c4c52928"), + tag: &hex!("29968c9fb610940cee9fd5b2f7c8ba21"), + }, + GcmTV { + key: &hex!("a67648285b65b9196060aaa02af279170164353e38fb77c3968c403cfa9acdc8"), + nonce: &hex!("0822d6b3e91eccb7e14245fd"), + plaintext: &hex!("b5d271768c12ccabf89eb2d58cbde840c26d1c9b3692581f90c8b0d7b2cff31ae9192d284f5448de7d924a7b08f115edae75aa"), + aad: &hex!("0d9a5af7ac27438d92534d97ff4378274790e59f"), + ciphertext: &hex!("b59041eed7abc2ff507d1932b5c55ac52728e5ac6648dcc74b38870db6181b1989f95a0144f0db368ec50414cfda0b977141e3"), + tag: &hex!("1d12ce89e1261d73470f3ae36ab87288"), + }, + GcmTV { + key: &hex!("51162b2435f3cf43471f4cc0ffac98b438501ee9b887843a66e9951ca35b8767"), + nonce: &hex!("dcb902eaa837ed22bf5fa636"), + plaintext: &hex!("3edf43358f5109a4dfb4a02987170a67cdd170f6028f7708bdd7726f476b882b9640270f2270f7babfa384181c8e58c15d04c4"), + aad: &hex!("4d459905ff89aed07dcda43a3d191a3da9309faa"), + ciphertext: &hex!("046a2313d36cbc43b6d0787e5ef37d153090a31d0f6656004034be72b9b07ace3a8abe8614362282d87da40c29c60a1a9f5c40"), + tag: &hex!("c7410b5cb94d2877c189983791cee82e"), + }, + GcmTV { + key: &hex!("2fa2beb1cde2226f28fb42a5fb0af3fc58fbb76bf14aa436e6535d466456a0f4"), + nonce: &hex!("50190514a3740b3c0b1df576"), + plaintext: &hex!("a5e0b4837dfca263ba286abf7940b6e70fabb55d8dee5028617c1190fbd327f79b79d2f34db6076ab07cecff7114b15ca02a33"), + aad: &hex!("25142928c1ae9c7b850309e07df359389db539fc"), + ciphertext: &hex!("850fd22bd0897b98ce40bc6c1345a9d59abf796b1b8c34ee8b377e54ee7d59dec05c022ecae96ffdfa1311bdd4e7a9d35aac47"), + tag: &hex!("4b5ab89b4f627ca32d12a1791c286870"), + }, + GcmTV { + key: &hex!("a92a797ce2b2f382030b77a1abe94c8076eee88de2dc4929350b244dbdaddd30"), + nonce: &hex!("716f577401a7893c42c91710"), + plaintext: &hex!("9d26ff79a89720fab6e4cda85887e3c0c3f86a4670d065c8ea68042b6f9f16dd2c5b31acb36331f5b1e50f08c492dc12eebd9e"), + aad: &hex!("8642681f1839b88990c2a939f00c9b90766dadac"), + ciphertext: &hex!("3080bcf3604cf81f5f2c6edc80dfe5d877168a9903598a700a0bbae188fadc7a8b76a04b40400f9252d7f9437fa8f024a3bdeb"), + tag: &hex!("8fc56f6bf48efb00476886b2a03ecb89"), + }, + GcmTV { + key: &hex!("89d0723e5a087456b7b709b8b21be380b463ba3dc9b79170e9947526798fe91c"), + nonce: &hex!("68e2f307b7d49d4d9c041755"), + plaintext: &hex!("7fe2afb710e8fd49cca1c2ba8fd0814594fba4d667017630e170a8a379fa5837bf370ca1cd4c98bd8c4f13eb7068ffa71ab07c"), + aad: &hex!("b34805b30703a62b6d37c93f2443e1a33154b5fb"), + ciphertext: &hex!("b841012752bbf1dfa7b59366dbf353bf98b61ff2e6e7a13d64d9dcb58b771003c8842ac002aac1fa8ca00a21eaf101ab44f380"), + tag: &hex!("73a93e2722db63c2bbf470d5193b2230"), + }, + GcmTV { + key: &hex!("329a6e94b1cce693e445694650d62b8c2c9ab03a09e6d4eca05c48291e576b89"), + nonce: &hex!("78f471bc32f8637a213e87ac"), + plaintext: &hex!("65264d75e1a176a7e966e59109cd074ac5d54740eb0c58084af023e5599eb611846199579d95ba94b6d25ee4d9074b9714f231"), + aad: &hex!("c00c465524e2e2f8a55c0793ed9af851be45a70e"), + ciphertext: &hex!("964d665d1e3c1018dfd883e217cfe4c856cc844f7644b53bb68fbe66f8541fa43ac54e92a2b194d6d8929fe031e94b3e70eca0"), + tag: &hex!("fd511385711236f2e99e6da5042007b7"), + }, + GcmTV { + key: &hex!("463b412911767d57a0b33969e674ffe7845d313b88c6fe312f3d724be68e1fca"), + nonce: &hex!("611ce6f9a6880750de7da6cb"), + plaintext: &hex!("e7d1dcf668e2876861940e012fe52a98dacbd78ab63c08842cc9801ea581682ad54af0c34d0d7f6f59e8ee0bf4900e0fd85042"), + aad: &hex!("0a682fbc6192e1b47a5e0868787ffdafe5a50cead3575849990cdd2ea9b3597749403efb4a56684f0c6bde352d4aeec5"), + ciphertext: &hex!("8886e196010cb3849d9c1a182abe1eeab0a5f3ca423c3669a4a8703c0f146e8e956fb122e0d721b869d2b6fcd4216d7d4d3758"), + tag: &hex!("2469cecd70fd98fec9264f71df1aee9a"), + }, + GcmTV { + key: &hex!("55f9171a03c21e09e3a5fd771e56bffb775ebb190319f3dc214c4b19f72e5482"), + nonce: &hex!("14f3bf95a08e8f52eb46fbf9"), + plaintext: &hex!("af6b17fd67bc1173b063fc6f0941483cee9cbbbbed3a4dcff55a74b0c9535b977efa640e5b1a30faa859fd3daa8dd780cc94a0"), + aad: &hex!("bac1ddefd111d471e75f0efb0f8127b4da923ecc788a5c91e3e2f65e2943e4caf42f54896604af19ed0b4d8697d45ab9"), + ciphertext: &hex!("3ae8678089522371fe4bd4da99ffd83a32988e0728aa3a4970ded1fe73bc30c2eb1fe24c0ff5ab549ac7e567d7036628fd718d"), + tag: &hex!("cf59603e05f4ed1d2da04e19399b8512"), + }, + GcmTV { + key: &hex!("54601d1538e5f04dc3fe95e483e40dec0aaa58375dc868da167c9a599ed345d9"), + nonce: &hex!("c5150872e45c341c2b99c69a"), + plaintext: &hex!("ae87c08c7610a125e7aa6f93fac0f80472530b2ce4d7194f5f4cb8ac025323c6c43a806788ef50c5028764ec32f2839005c813"), + aad: &hex!("93cd7ee8648a64c59d54cdac455b05ffdfc2effe8b19b50babd8c1a8c21f5dc8dc6050e2347f4cd28701594b9f8d4de5"), + ciphertext: &hex!("d5f005dc67bdc9738407ce2401977f59c9c83520e262d0c8db7fe47ae0eada30d674694f008e222f9733a6e63d81499e247567"), + tag: &hex!("3470155144c74929980134db6995dd88"), + }, + GcmTV { + key: &hex!("e966c470cbecc819260640d5404c84382e6e649da96d29cad2d4412e671ed802"), + nonce: &hex!("b3a92d6f49fe2cb9c144d339"), + plaintext: &hex!("7adf6fcb41d59b8d2b663010c3d4cf5f5f0b95cf754f76f8626c4428467e5c6684e77e7857b1cc755762e9ea9117e3bb077040"), + aad: &hex!("dfa62a3a4b5b3af6770cfd3cef3bbb4cce3f64925782a9a8a6e15fe3744d8f9310400dd04e8d7966c03850539e440aa5"), + ciphertext: &hex!("5f5b09486e6cd2a854e5622b4988e2408fddaca42c21d946c5cd789fe5a1306ef33c8cd44467ad7aa4c8152bce656a20367284"), + tag: &hex!("2b388109afdada6473435230d747b4eb"), + }, + GcmTV { + key: &hex!("4a8a12c0575ec65ae1c5784d2829bc7b04818eb00bd4c90a0d032ea281076e27"), + nonce: &hex!("959f113b705397fb738018b0"), + plaintext: &hex!("0c5571195586e4fc7096fb86cfcd6684081446f3d7adc33a897f03ac4ff6c3cc2019b67bd3184c86070764f6deaa8a10d0d81f"), + aad: &hex!("adb8bc96142a1025122dc22f826957197af33dcdcf6b7ab56bc1a5e17e8534e48b8daf685faf9543bb343614bdf6737f"), + ciphertext: &hex!("84212d5991231d35c4e8621163e5b370a0105a05856866e74df72c0808c062981570d32d274ea732fa4d29f9cfa7839cadbe6a"), + tag: &hex!("39cee3b8fa0bf92605666ccd9eb19840"), + }, + GcmTV { + key: &hex!("6197a4fa7cfcedeff223f69ea68b4ddf54b683350c20875be353077e9bbce346"), + nonce: &hex!("1a69ecabd42c53c0ec64fcd0"), + plaintext: &hex!("40a487b4daf866c20f3c4911a0586709c3344aa988dc9c464bcf36cc4e3d92701e611e60cf69f3edbf76cd27ff6ba935026d7f"), + aad: &hex!("b20a7ca5b5b603f661587e01f7ef171823ef463c187ded77a3d616400cc1d2b0b688ac9e927498341560cbc8eb9a4198"), + ciphertext: &hex!("06420fa038ee62db30cc05bfe34c8d2c39a9d439653907c512ed606511921fe76110913a5bfb6b6c7b23d7f8883f5ab65f4b14"), + tag: &hex!("4d3097c9919002cd1da83f29820312ed"), + }, + GcmTV { + key: &hex!("c9dbe185023ecaa78be9bfac1b91b9da6bd7c11349feb69e6b0be83a838e77b2"), + nonce: &hex!("8940fa7c6afd3f7a09ec93b6"), + plaintext: &hex!("075be0d61273e6975978d0b88b3fa38fc398d4d0f22a342a8afa5562af0e7c8fa548f0d8faec898a20c97e851754992c1ed4a3"), + aad: &hex!("f17bd357608365e66b98e49191cdc2a3813bba5a1b7988aa8aaaaad4b86d0ef4e2698cad799d63fcd2a5e87c0e3e929a"), + ciphertext: &hex!("615c1097d577363a77bfc7dd57179acb68166e78021b3397d7029ce33cbc848f036b9c07989eeb9f42aeaeebe8542f103b1d32"), + tag: &hex!("a22ab25fd8a6127469e8ce9ff686d575"), + }, + GcmTV { + key: &hex!("e6cdcf497a6e119009bf43ac183d2dd4d4e967964ef92811f69eb18d92923305"), + nonce: &hex!("3e88459a76e1dcc890788297"), + plaintext: &hex!("72a3dfb555ba0029fc3d1c85b836f76135bd1858189efdde2db29045f2c26e6a65627d81a0b85ca42e8269d432a41154e929ac"), + aad: &hex!("a359f86ec918537d80a84da7b66bca700c1ff9ec7f8695a30808d484da218d15ae89c5f943e71778445130191f779001"), + ciphertext: &hex!("9ae3f8ccae0bb5789b1105118760c406e41175a76612435cb0c8be225ea6b368c9d08c9d9a24b512d1458e94af79e3060ab69e"), + tag: &hex!("ac3bbc8fd6a7097df6f298411c23e385"), + }, + GcmTV { + key: &hex!("de5531b50888b61d63af2210ee23f46d91a5e60312bd578584af586bf22ea756"), + nonce: &hex!("0fde8689b0348bbcfaa89fec"), + plaintext: &hex!("80621e54eef1c92afb1f64ed860e39311eea7e2cca6f5624008c1d2e581d7112b7ee0b559fc3db575b7b7c42ee4f2a20442dc0"), + aad: &hex!("22db97cd5f359f12aec66c51c7da79ba629db4c8c7e5501be2ec1e4cc3f3944b6e3057d093bc68b735b5156950f91804"), + ciphertext: &hex!("933018419a32b7bf65f9777c44889a44b32d61ceddbb46839366ce2ca2ffeb1833f46559e59c93bb07f622d9633f13932cf7f1"), + tag: &hex!("25023a4ee9bdbf525cfef888e2480f86"), + }, + GcmTV { + key: &hex!("bc0c6368a9bb2622f6d5ba12de581f003336c298adac34499bf26b11e630f891"), + nonce: &hex!("2aa8f30b567cf1edd818e42d"), + plaintext: &hex!("1dcc1a3167fba55c00d3383e26d386eaa0449154599992da7f7f6598f41b3eb8e4d0a9143dfcab963f5c390a6ae2010fbcf6ec"), + aad: &hex!("0e28ebf87eb757e83031fb836f7b049a46bd740b0a39c9b798d2407e1150da86dfe84121c7c98449559453ad7558e779"), + ciphertext: &hex!("78d00a6e3302369817b9cf1f24ea13c41751382e3fea74403d094737e32fb507184cfebce48d10b4ce8db12ef961e4df2c8e95"), + tag: &hex!("c0aff3594f86b58e229c7ad05c2b84f0"), + }, + GcmTV { + key: &hex!("5d98a0c7ad6f9c0b116613ca5082250356a6a9bca55fe1a4a2962b733214dac4"), + nonce: &hex!("8b2d8e8d83bdd6a3125dd997"), + plaintext: &hex!("4f3685c2cfbc856379d1fd00f9611fe4c0a4b9c4013fe1bee144449709a6a7e31ff6fb0da74ed464b066b03b50f19cd7f5f9bc"), + aad: &hex!("2f20636d46ce37e9bb0ca0c41d819e3eabcedacbd1ca3ced112d3ad620bbd3b2effe80d3ec8760706e8f14db83139a70"), + ciphertext: &hex!("8e178c0e3e5d22b3be897e0b8879b0d53fef2efb9946ccff6d717b001e3033f2cc22d01d9551e9c0749de704fbe3189328cbb0"), + tag: &hex!("541b7db823e37b5ed323626b9c6748f6"), + }, + GcmTV { + key: &hex!("d80a2703e982de1a2fe706ffe6e389f351ab356ccf056df045e2941b42ef21a4"), + nonce: &hex!("1521ab8f7242cba05427f429"), + plaintext: &hex!("6f9fde28e85776a49cfbad1459d94611757a3cd996aa6e2d702d0483a4d88d532131ebd405b351226b16d19d30d32807a1d511"), + aad: &hex!("5395de90d6bec7c159ab9d6cfa663bdc6295d025e1fcc8b760b9ba42d785eda218dabc6fa7c0f733ad77f61682bff2db"), + ciphertext: &hex!("1e72a8495ceadaf0d31b28ba7cb7c37ccb117761d38fe7dd98eb230ff4ea0b400401e9b5311a7be9b2a533523ad469e2fdb233"), + tag: &hex!("bb174b7624c935ff75b3b77ff7068a98"), + }, + GcmTV { + key: &hex!("6d5c69d7135c0b5b7fef512c127fa788092f1a908358ab658b8f23e463409aa5"), + nonce: &hex!("b36cccad38cd6148a384a026"), + plaintext: &hex!("b4e74f5c56f2ea056d9ff931525944dfad207e063ba226c354e0320a50449967e964580d9b57028c14005aba6865f8bc6a3ef8"), + aad: &hex!("b19f4616bb1452251a2a7dbf78f920194f139e0424d27683621d1ee1e865737c2466e058439c8e122e582a7b63607ce9"), + ciphertext: &hex!("1ce12cd5502efa9ea259584ae9b3c7dbd9444380d4b77a2c787f9b2257019b23ee183dffebb3106a26b18d8a23445626a578e2"), + tag: &hex!("62945e31bae3181855b69c37898ac5bf"), + }, + GcmTV { + key: &hex!("e6afe3c4db2c1d13edb1c5931b2b4b515ec0fd6201139ee1ea55cec92263830e"), + nonce: &hex!("358bd9ea64177d1e23a41726"), + plaintext: &hex!("710bb3394b094ee7d053bc6599b26dafd337e8a61c580d0446c3bf195e77ca5132c8ec3a47a61579dce38360bba7c65e4d5634"), + aad: &hex!("7e0f841cddd7eeebd1ec7b7b8d0e2f71656e5e9ff3cfa739c0b9d0ec4941a0b3f3b396690dbe5f5082d6fb6dd701c68d"), + ciphertext: &hex!("4574a8db515b41c14c2a962dff34e2161a7195c491b11b79889aff93c5b79a6455df9fe8ef5c5b9edb5da1aa9fe66058b9065f"), + tag: &hex!("7c928d7f5cbac9bb4b5928fe727899eb"), + }, + GcmTV { + key: &hex!("5cb962278d79417b7795499e8b92befe4228f3ba5f31992201aa356a6d139a67"), + nonce: &hex!("76f7e7608f09a05f336994cf"), + plaintext: &hex!("2e12cbd468086aa70e2ecd1ddef561e85c225dd083e5956f5c67503344b0ea982bb5044dafbcc02a5b9be1e9b988902d80172b"), + aad: &hex!("032de3fdec273fc8446c2bf767e201f2c7c190acf9d6d321a24a0462cbc3356e798fe23d6c1b4fe83be9c95d71c05504"), + ciphertext: &hex!("c959344a46aa5216d2b37c832436eb72a4a363a6df5642cfbbfd640dea1d64c80bd97eabc1aab192969ee0b799e592a13d2351"), + tag: &hex!("51b227eaf7228a4419f2f3b79b53463a"), + }, + GcmTV { + key: &hex!("148579a3cbca86d5520d66c0ec71ca5f7e41ba78e56dc6eebd566fed547fe691"), + nonce: &hex!("b08a5ea1927499c6ecbfd4e0"), + plaintext: &hex!("9d0b15fdf1bd595f91f8b3abc0f7dec927dfd4799935a1795d9ce00c9b879434420fe42c275a7cd7b39d638fb81ca52b49dc41"), + aad: &hex!("e4f963f015ffbb99ee3349bbaf7e8e8e6c2a71c230a48f9d59860a29091d2747e01a5ca572347e247d25f56ba7ae8e05cde2be3c97931292c02370208ecd097ef692687fecf2f419d3200162a6480a57dad408a0dfeb492e2c5d"), + ciphertext: &hex!("2097e372950a5e9383c675e89eea1c314f999159f5611344b298cda45e62843716f215f82ee663919c64002a5c198d7878fd3f"), + tag: &hex!("adbecdb0d5c2224d804d2886ff9a5760"), + }, + GcmTV { + key: &hex!("e49af19182faef0ebeeba9f2d3be044e77b1212358366e4ef59e008aebcd9788"), + nonce: &hex!("e7f37d79a6a487a5a703edbb"), + plaintext: &hex!("461cd0caf7427a3d44408d825ed719237272ecd503b9094d1f62c97d63ed83a0b50bdc804ffdd7991da7a5b6dcf48d4bcd2cbc"), + aad: &hex!("19a9a1cfc647346781bef51ed9070d05f99a0e0192a223c5cd2522dbdf97d9739dd39fb178ade3339e68774b058aa03e9a20a9a205bc05f32381df4d63396ef691fefd5a71b49a2ad82d5ea428778ca47ee1398792762413cff4"), + ciphertext: &hex!("32ca3588e3e56eb4c8301b009d8b84b8a900b2b88ca3c21944205e9dd7311757b51394ae90d8bb3807b471677614f4198af909"), + tag: &hex!("3e403d035c71d88f1be1a256c89ba6ad"), + }, + GcmTV { + key: &hex!("c277df045d0a1a3956958f271055c229d2634427b1d73e99d54920da69f72e01"), + nonce: &hex!("79e24f84bc77a21a6cb14ee2"), + plaintext: &hex!("5ca68d858cc30b1cb0514c4e9de98e1a1a835df401f69e9ec6f1bcb1158f09114dff551683b3827457f77e17a7097b1ea69eac"), + aad: &hex!("ca09282238d492029afbd30ea9b4aa9d448d77b4b41a791c35ebe3f8e5034ac71210117a843fae647cea020712c27e5c8f85acf933d5e28430c7770862d8dbb197cbbcfe49dd63f6aa05fbd13e32c459342698dfee5935c7c321"), + ciphertext: &hex!("5c5223c8eda59a8dc28b08e6c21482a46e5d84d32c7050bf144fc57f4e8094de133198da7b4b8398b167204aff837da15d9ab2"), + tag: &hex!("378885950a4491bee3cd681d3c957b9a"), + }, + GcmTV { + key: &hex!("4d07f78d19e6d8bb32bf209f138307890f0f1ae39362779ff2bf1f9b734fe653"), + nonce: &hex!("d983a5d5af78a3b1cd5fbd58"), + plaintext: &hex!("94f0bbc4340d97d854e25cc7ce85ea1e781e68bf6f639e0a981bb03e3c209cbf5127171cb0fff65bc3ecac92774d10146d1ac5"), + aad: &hex!("a3dc9ff9210bc4b3276909883db2c2aa0762cd22b46901a248c0372d073e7778b9c1d8469b26bb42406e484ef7747f71dea785fc0020a2eac17e0ac3fbe0453629efd68d5678fbecc10af8ffbe7828f826defb638763f4ecfe82"), + ciphertext: &hex!("6543b4d97fccd273b36436fef719ac31bf0e5c4c058ea71aea2a0e5b60e329be6ea81ce386e6e9fe4480e58363c3b2036865ac"), + tag: &hex!("924cf7c0770f228a4b92e9b2a11fc70b"), + }, + GcmTV { + key: &hex!("9572b9c57abdf1caae3bebc0e4bbf9e556b5cbacca2c4756050fefd10a666155"), + nonce: &hex!("de292a9858caaccdcab6a433"), + plaintext: &hex!("6f420a32708ccd4df0d3149e8c1d88dceba66ee4546f38db07046ebf30f47627f7fdda1dd79783adabe5f6b6853857b99b864c"), + aad: &hex!("a042d97a9b8f6caf51c5f24522d7ed83e2c5d8ec6b37ef2598134a30e57319300c3fdf92fb1d9797f5ef00971f662aae768f69f9ca0455bd6d1059d5f85b8ecb977006b833f90ac2d5bbf4498c83f4d1a42584c0dfc4a2e2453c"), + ciphertext: &hex!("a9af961d61ab578cc1348eb6f729603f481c5d9bf9bee3a13eda022bd09c03a4f207c21c45c0232a9742ae8f0c54b4278a3a63"), + tag: &hex!("eff9bb26156ec76f0060cd93a959e055"), + }, + GcmTV { + key: &hex!("3cc8671c4d25c3cbc887f4dcbd64e531e91cf6252f6ee9c29d9988d20ab6747f"), + nonce: &hex!("f960a09c0b5067280926a9c3"), + plaintext: &hex!("5b58717b0b32076566b58bf37c6133e61468b2be67715fb0007fe390c4b5578decf55502a4e3c12e7bdf0ba98784d126e4753a"), + aad: &hex!("79d73a7ff86698e6114a0f465373fbee029e042424c439b22e3ad37b36b9e02bab82e16844114e99e39c169f462fe61b87c4627c394384acc9531680706e4e56491a304c6075cca37c64db24468c1fb9519605c83f0ee3e0316a"), + ciphertext: &hex!("1d0be097470c1ac30619f63c3961152ab27db88ce694b7bba4db185cb31803cc7bab890e931c90766621bfe5d887eb0cd6995d"), + tag: &hex!("dbd57ea091ff16fc7dbc5435030cc74e"), + }, + GcmTV { + key: &hex!("882068be4552d7ad224fc8fa2af00d6abf76ccf1a7689d75f6f0e9bd82c1215e"), + nonce: &hex!("890a5315992f12674d1c8018"), + plaintext: &hex!("8464c03e0280cb1f63c054a24a050e980f60cc7313f09f2092c45d77bbe9ad2a8c1f6cdca2acd8c57c87e887edadb66bcb66c4"), + aad: &hex!("916721df816b1cad531dee8e4a8e634d43ed87db99609bcc986d16bfac2cff577d536d749a5c3625de53c5351825c228911f0a64be1fc9738a26394efe5332c0762bf59b65d3f1c5aafa9ca2e63eccd59568e6c0269950911a71"), + ciphertext: &hex!("020e297d907177dba12dde4bfe1b0ff9b6a9d9db0695193e4181449e157137b59b488616ba151b06d889f8498ce373d2396ab9"), + tag: &hex!("e48537ecb27460b477a6e7c3463dbcb0"), + }, + GcmTV { + key: &hex!("4deadcf0f7e19231f8afcb6fb902b105bef23f2fa9323a51833ff8368ccb4f91"), + nonce: &hex!("6d4d01abd587ed110e512ed2"), + plaintext: &hex!("75686e0fdd3fd96f3e6dfafd7a2a907f9f375d93943cb2229bd72b032bf624af4fc72071289386e3dccc45959e47ab42b261a2"), + aad: &hex!("31a2797318104b2dc9977e599435b041c56bafe5e7d901a58614c2d3fb9d220e3fd3e2828cef69e0604ed73340cb1e21967294dcd874893942442200b2a5b860ee8cf91e1d8eb3d364d0e43e84f6379f434a1ae17c236b216842"), + ciphertext: &hex!("8feaf9a089599812117a67aed2f4bf3431ff1f6cfd64ea5ff475287abb4ff1ab6b3e4f8a55d1c6b3f08594f403e771ec7e9956"), + tag: &hex!("5040407621712e053591179e1689698e"), + }, + GcmTV { + key: &hex!("80f1c515f10d79cdbee275213aa9ac0845e2cf42874f7e695081cb103abf1a27"), + nonce: &hex!("399d5f9b218b62ff60c267bd"), + plaintext: &hex!("9e95221873f65282dd1ec75494d2500e62a2b6edda5a6f33b3d4dd7516ef25cf4154472e61c6aed2749c5a7d86637052b00f54"), + aad: &hex!("d2a8fff8ae24a6a5efc75764549a765222df317e323a798cbb8a23d1af8fdf8a3b767f55703b1c0feba3912d4234441978191262f1999c69caa4e9a3e0454c143af0022cd6e44cec14149f9e9964a1f2c5e5a6e3e768bd870060"), + ciphertext: &hex!("4f996562e23ebbfd4fe26523aee9525b13d6e134e72d21bdc7f195c6403501fd8300b6e597b668f199f93591ba742a91b54454"), + tag: &hex!("2da1c7325f58575d275abf96c7fa9e51"), + }, + GcmTV { + key: &hex!("c2c6e9be5a480a4a56bfcd0e268faa2276093bd1f7e8ce61e746d003decc761e"), + nonce: &hex!("c1541eb25721d4856df8f928"), + plaintext: &hex!("87d22e0318fbbb420b86b0585bd12c14645ff2c742e5639b3a114cc96c5f738edfbe2055116f259e3d6c14cb6d8fca45708289"), + aad: &hex!("f34e79e5fe437eda03ccfef2f1d6319df51a71c9891863e4b98a7298bd64490460354db5a28b0fadcb815024ea17f3b84810e27954afb1fdf44f0defb930b1793684a781310b9af95b4bcf0a727a2cb0ac529b805811b3721d98"), + ciphertext: &hex!("b5d6e57c7aa0240e0b6e332d3b3323b525a3d8a553ad041ba599e909188da537c3293d1687fb967882d16a5615b84e95f9dd77"), + tag: &hex!("1cce334cec4b51216cac0fc620cdadf9"), + }, + GcmTV { + key: &hex!("ea0d6184a71456e27f9ac82dfc7f6694c898f7c0d19d1cb0db4e575dd0094bb6"), + nonce: &hex!("5018fb816d515511bfb939d5"), + plaintext: &hex!("083147d0c80f134f7393855c8a95bf6e6abd6f9a7b1fca584e8bfc6b5dc13a8edbfd473e232c041d9be9ee7709dc86b3aa320a"), + aad: &hex!("8bc6bd0a263212bd7281fd1a45e512fca104f859358eae9293a297c529a0abaffd8a77507b9069040f2b3141a7620691e110a8b593b956d8e3e71694506b89018a03861c1ba6082687adce15a874c73477430cef075eba077a93"), + ciphertext: &hex!("f0a5c4941782e2f2941dd05acee29b65341773f2e8d51935a3f4fa6f268ff030c880976cf1ee858f6571abd8411b695a2fadf0"), + tag: &hex!("067d8cc2d38c30697272daa00c7f70cf"), + }, + GcmTV { + key: &hex!("c624feb6cb0d78d634b627134c692f0bf5debf84d8639e22ff27ce2ace49d438"), + nonce: &hex!("a54f4f1204255f6b312222cd"), + plaintext: &hex!("ec34f45c1b70fd56518cc5c404cc13330ab7d51c10f4d2cfeb26b097ae76897191ec1b3953b0086e425c7da221d29f65d5ccf3"), + aad: &hex!("d9099ba6be50dca77e0b9803766ad993132479fbab43b8f4126a7f9ef673ac0caf2de235e1e84ad9fe505c43d1ac779f5072c025c14ea0d930ce39db8c5930baada23b3e4654470e559fcb6eb1c133a77318b87cc7913e12d404"), + ciphertext: &hex!("713d28a5123d65e82cca6e7fd919e1e5e3bdaab12ae715cf8b7c974eb5f62be8c3b42637074c6b891f6c6033eb4b7e61db9f0b"), + tag: &hex!("01ededff6e4d1dce4ac790218e208ebe"), + }, + GcmTV { + key: &hex!("1afc68b32596198ae0f3a8612751c2413322e8054ff2ac6bede3d4a1ee20ee62"), + nonce: &hex!("356860e76e794492de6a68f3"), + plaintext: &hex!("293041038f9e8edee23d2f18bce87b522380f1fa18b3021830a54ab891da8548095228ed9860176152e27945d66254f0db8590"), + aad: &hex!("205e44009e0ef963838aff615b35c9f1271d487cf719677d956718bce8ab676cceb636ad381432c5c790c26b07051b661a2fec4e607f9644f84993c8335db21ae36b6008bab2883ad7541809bf5f49272295c1c1f1cf8c678553"), + ciphertext: &hex!("e06109680d5fefd345665ec9a5b2e7bf3ece3af1b62841a95c453e7753b5a1d6d8a10b3c6c42df1f23832b74e74871821f1c0b"), + tag: &hex!("953d8d04f70e2af055ac902a455235b2"), + }, + GcmTV { + key: &hex!("f61b723359e798fefecc26b10b168dc331c639079598f1f651166cc58c671ee1"), + nonce: &hex!("b07e9407b592d4fd95509343"), + plaintext: &hex!("2724f1ad6b5b409a59c7f2ff649eb24b4a33a03d7a0426e29a6ea3aa91b4f00699fbed75bb7189964303e2e9fe3a7e5f74b7a1"), + aad: &hex!("1429c6f27828cb94ad5e62451da10fd574660cec2b8f279a19bbb8a167a630d3ac60db04e8faa02204792e49aed4501844a419d3ecdff0d03799866fee81a91187b08a44d5bb617ff3b2cef79cd48750ea20903e1d3627a17730"), + ciphertext: &hex!("362bad8de943dce8f53edf682d02e1d893c23c5272b13fd35b492f8477083a8c34027db32b6131931f03555ac5fbc6dbb13801"), + tag: &hex!("a51775606343755691f125019b44fdfc"), + }, + GcmTV { + key: &hex!("6be7f4d18ff0fbdd9b3b3cacaba4629a0c617387079add62f6ce1584b33faad1"), + nonce: &hex!("fda568c9cb13d9c176bcef03"), + plaintext: &hex!("4df668e99d5068604a48bcca5baa8245435928558a83d68d7b0b081861224e9bd39ea8f2d55a635949e66c6f6a7ff5cc34dd94"), + aad: &hex!("11ebeb97dd4a9925c1fbe2b9af77392058d2d971e42db15da39f090d7bc132573c34bf7d92a2d72dc66ee6840c3ff07985b8976ee8d8f36bf47ae330b899fdc60652dd5a23c45f3680f11951f019e0697c8acfcaa95f01b9c7dd"), + ciphertext: &hex!("488b40ad594e1845ccdd9e9467fc5e1afbbfde34e57d45bfcd30b61cc326d57fe8e3f31a39cdebf00f60bbd2c3cdf69f756eff"), + tag: &hex!("3bf3fbab9b48486fd08a5552604df639"), + }, + ]; +} diff --git a/src/crypto/src/cipher_ctx.rs b/src/crypto/src/cipher_ctx.rs new file mode 100644 index 0000000..0cfcb90 --- /dev/null +++ b/src/crypto/src/cipher_ctx.rs @@ -0,0 +1,172 @@ +use std::ptr::{self, NonNull}; + +use crate::error::{cvt, cvt_p, ErrorStack}; +use libc::{c_int, c_void}; + +extern "C" { + fn EVP_CIPHER_CTX_free(ctx: *mut ffi::EVP_CIPHER_CTX); + fn EVP_CIPHER_CTX_new() -> *mut ffi::EVP_CIPHER_CTX; + + fn EVP_EncryptInit_ex(ctx: *mut ffi::EVP_CIPHER_CTX, cipher: *const ffi::EVP_CIPHER, engine: *mut c_void, key: *const u8, iv: *const u8) + -> c_int; + fn EVP_DecryptInit_ex(ctx: *mut ffi::EVP_CIPHER_CTX, cipher: *const ffi::EVP_CIPHER, engine: *mut c_void, key: *const u8, iv: *const u8) + -> c_int; + fn EVP_EncryptUpdate(ctx: *mut ffi::EVP_CIPHER_CTX, out: *mut u8, outl: *mut c_int, in_: *const u8, inl: c_int) -> c_int; + fn EVP_DecryptUpdate(ctx: *mut ffi::EVP_CIPHER_CTX, out: *mut u8, outl: *mut c_int, in_: *const u8, inl: c_int) -> c_int; + fn EVP_EncryptFinal_ex(ctx: *mut ffi::EVP_CIPHER_CTX, out: *mut u8, outl: *mut c_int) -> c_int; + fn EVP_DecryptFinal_ex(ctx: *mut ffi::EVP_CIPHER_CTX, out: *mut u8, outl: *mut c_int) -> c_int; + fn EVP_CIPHER_CTX_ctrl(ctx: *mut ffi::EVP_CIPHER_CTX, type_: c_int, arg: c_int, ptr: *mut c_void) -> c_int; + +} + +pub struct CipherCtx(NonNull); +impl Drop for CipherCtx { + fn drop(&mut self) { + unsafe { + EVP_CIPHER_CTX_free(self.0.as_ptr()); + } + } +} + +impl CipherCtx { + /// Creates a new context. + pub fn new() -> Result { + unsafe { + let ptr = cvt_p(EVP_CIPHER_CTX_new())?; + Ok(CipherCtx(NonNull::new_unchecked(ptr))) + } + } +} +impl CipherCtx { + /// Initializes the context for encryption or decryption. + /// All pointer fields can be null, in which case the corresponding field in the context is not updated. + pub unsafe fn cipher_init(&self, t: *const ffi::EVP_CIPHER, key: *const u8, iv: *const u8) -> Result<(), ErrorStack> { + let evp_f = if ENCRYPT { + EVP_EncryptInit_ex + } else { + EVP_DecryptInit_ex + }; + + // OpenSSL will usually leak a static amount of memory per cipher given here. + cvt(evp_f(self.0.as_ptr(), t, ptr::null_mut(), key, iv))?; + Ok(()) + } + + /// Writes data into the context. + /// + /// Providing no output buffer will cause the input to be considered additional authenticated data (AAD). + /// + /// Returns the number of bytes written to `output`. + /// + /// This function is the same as [`Self::cipher_update`] but with the + /// output size check removed. It can be used when the exact + /// buffer size control is maintained by the caller. + /// + /// SAFETY: The caller is expected to provide `output` buffer + /// large enough to contain correct number of bytes. For streaming + /// ciphers the output buffer size should be at least as big as + /// the input buffer. For block ciphers the size of the output + /// buffer depends on the state of partially updated blocks. + pub unsafe fn update(&self, input: &[u8], output: *mut u8) -> Result<(), ErrorStack> { + let evp_f = if ENCRYPT { + EVP_EncryptUpdate + } else { + EVP_DecryptUpdate + }; + + let mut outlen = 0; + + cvt(evp_f(self.0.as_ptr(), output, &mut outlen, input.as_ptr(), input.len() as c_int))?; + + Ok(()) + } + + /// Finalizes the encryption or decryption process. + /// + /// Any remaining data will be written to the output buffer. + /// + /// Returns the number of bytes written to `output`. + /// + /// This function is the same as [`Self::cipher_final`] but with + /// the output buffer size check removed. + /// + /// SAFETY: The caller is expected to provide `output` buffer + /// large enough to contain correct number of bytes. For streaming + /// ciphers the output buffer can be empty, for block ciphers the + /// output buffer should be at least as big as the block. + pub unsafe fn finalize(&self, output: *mut u8) -> Result<(), ErrorStack> { + let evp_f = if ENCRYPT { + EVP_EncryptFinal_ex + } else { + EVP_DecryptFinal_ex + }; + let mut outl = 0; + + cvt(evp_f(self.0.as_ptr(), output, &mut outl))?; + + Ok(()) + } + + /// Retrieves the calculated authentication tag from the context. + /// + /// This should be called after [`Self::cipher_final`], and is only supported by authenticated ciphers. + /// + /// The size of the buffer indicates the size of the tag. While some ciphers support a range of tag sizes, it is + /// recommended to pick the maximum size. + pub fn tag(&self, tag: &mut [u8]) -> Result<(), ErrorStack> { + unsafe { + cvt(EVP_CIPHER_CTX_ctrl( + self.0.as_ptr(), + ffi::EVP_CTRL_GCM_GET_TAG, + tag.len() as c_int, + tag.as_mut_ptr() as *mut _, + ))?; + } + + Ok(()) + } + + /// Sets the authentication tag for verification during decryption. + #[allow(unused)] + pub fn set_tag(&self, tag: &[u8]) -> Result<(), ErrorStack> { + unsafe { + cvt(EVP_CIPHER_CTX_ctrl( + self.0.as_ptr(), + ffi::EVP_CTRL_GCM_SET_TAG, + tag.len() as c_int, + tag.as_ptr() as *mut _, + ))?; + } + + Ok(()) + } + pub fn as_ptr(&self) -> *mut ffi::EVP_CIPHER_CTX { + self.0.as_ptr() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn aes_128_ecb() { + let key = [1u8; 16]; + let ctx = CipherCtx::new().unwrap(); + unsafe { + ctx.cipher_init::(ffi::EVP_aes_128_ecb(), key.as_ptr(), ptr::null()).unwrap(); + ffi::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + assert_eq!(ffi::EVP_CIPHER_CTX_get_block_size(ctx.as_ptr()) as usize, 16); + + let origin = [2u8; 16]; + let mut val = origin; + let p = val.as_mut_ptr(); + + ctx.update::(&val, p).unwrap(); + ctx.cipher_init::(ptr::null(), key.as_ptr(), ptr::null()).unwrap(); + ctx.update::(&val, p).unwrap(); + + assert_eq!(val, origin); + } + } +} diff --git a/src/crypto/src/constant.rs b/src/crypto/src/constant.rs new file mode 100644 index 0000000..e9f0467 --- /dev/null +++ b/src/crypto/src/constant.rs @@ -0,0 +1,4 @@ +pub const AES_256_KEY_SIZE: usize = 32; +pub const AES_BLOCK_SIZE: usize = 16; +pub const AES_GCM_TAG_SIZE: usize = 16; +pub const AES_GCM_NONCE_SIZE: usize = 12; diff --git a/src/crypto/src/error.rs b/src/crypto/src/error.rs new file mode 100644 index 0000000..74135e6 --- /dev/null +++ b/src/crypto/src/error.rs @@ -0,0 +1,348 @@ +use cfg_if::cfg_if; +use libc::{c_char, c_int}; +use std::borrow::Cow; +use std::error; +use std::ffi::CStr; +use std::fmt; +use std::io; +use std::ptr; +use std::str; + +type ErrType = libc::c_ulong; + +/// Collection of [`Error`]s from OpenSSL. +/// +/// [`Error`]: struct.Error.html +#[derive(Debug, Clone)] +pub struct ErrorStack(Vec); + +impl ErrorStack { + /// Returns the contents of the OpenSSL error stack. + #[cold] + #[inline(never)] + pub fn get() -> ErrorStack { + let mut vec = vec![]; + while let Some(err) = Error::get() { + vec.push(err); + } + ErrorStack(vec) + } + + /// Pushes the errors back onto the OpenSSL error stack. + pub fn put(&self) { + for error in self.errors() { + error.put(); + } + } +} + +impl ErrorStack { + /// Returns the errors in the stack. + pub fn errors(&self) -> &[Error] { + &self.0 + } +} + +impl fmt::Display for ErrorStack { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.0.is_empty() { + return fmt.write_str("OpenSSL error"); + } + + let mut first = true; + for err in &self.0 { + if !first { + fmt.write_str(", ")?; + } + write!(fmt, "{}", err)?; + first = false; + } + Ok(()) + } +} + +impl error::Error for ErrorStack {} + +impl From for io::Error { + fn from(e: ErrorStack) -> io::Error { + io::Error::new(io::ErrorKind::Other, e) + } +} + +impl From for fmt::Error { + fn from(_: ErrorStack) -> fmt::Error { + fmt::Error + } +} + +/// An error reported from OpenSSL. +#[derive(Clone)] +pub struct Error { + code: ErrType, + file: ShimStr, + line: c_int, + func: Option, + data: Option>, +} + +unsafe impl Sync for Error {} +unsafe impl Send for Error {} + +impl Error { + /// Returns the first error on the OpenSSL error stack. + pub fn get() -> Option { + unsafe { + let mut file = ptr::null(); + let mut line = 0; + let mut func = ptr::null(); + let mut data = ptr::null(); + let mut flags = 0; + match ERR_get_error_all(&mut file, &mut line, &mut func, &mut data, &mut flags) { + 0 => None, + code => { + // The memory referenced by data is only valid until that slot is overwritten + // in the error stack, so we'll need to copy it off if it's dynamic + let data = if flags & ffi::ERR_TXT_STRING != 0 { + let bytes = CStr::from_ptr(data as *const _).to_bytes(); + let data = str::from_utf8(bytes).unwrap(); + #[cfg(not(boringssl))] + let data = if flags & ffi::ERR_TXT_MALLOCED != 0 { + Cow::Owned(data.to_string()) + } else { + Cow::Borrowed(data) + }; + #[cfg(boringssl)] + let data = Cow::Borrowed(data); + Some(data) + } else { + None + }; + + let file = ShimStr::new(file); + + let func = if func.is_null() { + None + } else { + Some(ShimStr::new(func)) + }; + + Some(Error { code, file, line, func, data }) + } + } + } + } + + /// Pushes the error back onto the OpenSSL error stack. + pub fn put(&self) { + self.put_error(); + + unsafe { + let data = match self.data { + Some(Cow::Borrowed(data)) => Some((data.as_ptr() as *mut c_char, 0)), + Some(Cow::Owned(ref data)) => { + let ptr = ffi::CRYPTO_malloc((data.len() + 1) as _, concat!(file!(), "\0").as_ptr() as _, line!() as _) as *mut c_char; + if ptr.is_null() { + None + } else { + ptr::copy_nonoverlapping(data.as_ptr(), ptr as *mut u8, data.len()); + *ptr.add(data.len()) = 0; + Some((ptr, ffi::ERR_TXT_MALLOCED)) + } + } + None => None, + }; + if let Some((ptr, flags)) = data { + ffi::ERR_set_error_data(ptr, flags | ffi::ERR_TXT_STRING); + } + } + } + + #[cfg(ossl300)] + fn put_error(&self) { + unsafe { + ffi::ERR_new(); + ffi::ERR_set_debug(self.file.as_ptr(), self.line, self.func.as_ref().map_or(ptr::null(), |s| s.as_ptr())); + ffi::ERR_set_error(ffi::ERR_GET_LIB(self.code), ffi::ERR_GET_REASON(self.code), ptr::null()); + } + } + + /// Returns the raw OpenSSL error code for this error. + pub fn code(&self) -> ErrType { + self.code + } + + /// Returns the name of the library reporting the error, if available. + pub fn library(&self) -> Option<&'static str> { + unsafe { + let cstr = ffi::ERR_lib_error_string(self.code); + if cstr.is_null() { + return None; + } + let bytes = CStr::from_ptr(cstr as *const _).to_bytes(); + Some(str::from_utf8(bytes).unwrap()) + } + } + + /// Returns the name of the function reporting the error. + pub fn function(&self) -> Option> { + self.func.as_ref().map(|s| s.as_str()) + } + + /// Returns the reason for the error. + pub fn reason(&self) -> Option<&'static str> { + unsafe { + let cstr = ffi::ERR_reason_error_string(self.code); + if cstr.is_null() { + return None; + } + let bytes = CStr::from_ptr(cstr as *const _).to_bytes(); + Some(str::from_utf8(bytes).unwrap()) + } + } + + /// Returns the name of the source file which encountered the error. + pub fn file(&self) -> RetStr<'_> { + self.file.as_str() + } + + /// Returns the line in the source file which encountered the error. + pub fn line(&self) -> u32 { + self.line as u32 + } + + /// Returns additional data describing the error. + #[allow(clippy::option_as_ref_deref)] + pub fn data(&self) -> Option<&str> { + self.data.as_ref().map(|s| &**s) + } +} + +impl fmt::Debug for Error { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = fmt.debug_struct("Error"); + builder.field("code", &self.code()); + if let Some(library) = self.library() { + builder.field("library", &library); + } + if let Some(function) = self.function() { + builder.field("function", &function); + } + if let Some(reason) = self.reason() { + builder.field("reason", &reason); + } + builder.field("file", &self.file()); + builder.field("line", &self.line()); + if let Some(data) = self.data() { + builder.field("data", &data); + } + builder.finish() + } +} + +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "error:{:08X}", self.code())?; + match self.library() { + Some(l) => write!(fmt, ":{}", l)?, + None => write!(fmt, ":lib({})", ffi::ERR_GET_LIB(self.code()))?, + } + match self.function() { + Some(f) => write!(fmt, ":{}", f)?, + None => write!(fmt, ":func({})", ffi::ERR_GET_FUNC(self.code()))?, + } + match self.reason() { + Some(r) => write!(fmt, ":{}", r)?, + None => write!(fmt, ":reason({})", ffi::ERR_GET_REASON(self.code()))?, + } + write!(fmt, ":{}:{}:{}", self.file(), self.line(), self.data().unwrap_or("")) + } +} + +impl error::Error for Error {} + +cfg_if! { + if #[cfg(ossl300)] { + use std::ffi::{CString}; + use ffi::ERR_get_error_all; + + type RetStr<'a> = &'a str; + + #[derive(Clone)] + struct ShimStr(CString); + + impl ShimStr { + unsafe fn new(s: *const c_char) -> Self { + ShimStr(CStr::from_ptr(s).to_owned()) + } + + fn as_ptr(&self) -> *const c_char { + self.0.as_ptr() + } + + fn as_str(&self) -> &str { + self.0.to_str().unwrap() + } + } + } else { + #[allow(bad_style)] + unsafe extern "C" fn ERR_get_error_all( + file: *mut *const c_char, + line: *mut c_int, + func: *mut *const c_char, + data: *mut *const c_char, + flags: *mut c_int, + ) -> ErrType { + let code = ffi::ERR_get_error_line_data(file, line, data, flags); + *func = ffi::ERR_func_error_string(code); + code + } + + type RetStr<'a> = &'static str; + + #[derive(Clone)] + struct ShimStr(*const c_char); + + impl ShimStr { + unsafe fn new(s: *const c_char) -> Self { + ShimStr(s) + } + + fn as_ptr(&self) -> *const c_char { + self.0 + } + + fn as_str(&self) -> &'static str { + unsafe { + CStr::from_ptr(self.0).to_str().unwrap() + } + } + } + } +} + +#[inline] +pub fn cvt_p(r: *mut T) -> Result<*mut T, ErrorStack> { + if r.is_null() { + Err(ErrorStack::get()) + } else { + Ok(r) + } +} + +#[inline] +pub fn cvt(r: c_int) -> Result { + if r <= 0 { + Err(ErrorStack::get()) + } else { + Ok(r) + } +} + +#[inline] +pub fn cvt_n(r: c_int) -> Result { + if r < 0 { + Err(ErrorStack::get()) + } else { + Ok(r) + } +} diff --git a/src/crypto/src/hash.rs b/src/crypto/src/hash.rs new file mode 100644 index 0000000..edc29e6 --- /dev/null +++ b/src/crypto/src/hash.rs @@ -0,0 +1,294 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. + +use std::ffi::c_void; +use std::io::Write; +use std::mem::MaybeUninit; +use std::os::raw::{c_int, c_uint}; +use std::ptr::null; + +use crate::secret::Secret; + +pub const SHA512_HASH_SIZE: usize = 64; +pub const SHA384_HASH_SIZE: usize = 48; +pub const HMAC_SHA512_SIZE: usize = 64; +pub const HMAC_SHA384_SIZE: usize = 48; + +pub struct SHA512(ffi::SHA512_CTX); + +impl SHA512 { + #[inline(always)] + pub fn hash(data: &[u8]) -> [u8; SHA512_HASH_SIZE] { + unsafe { + let mut hash = MaybeUninit::<[u8; SHA512_HASH_SIZE]>::uninit(); + ffi::SHA512(data.as_ptr(), data.len(), hash.as_mut_ptr() as *mut _); + hash.assume_init() + } + } + + /// Creates a new hasher. + #[inline(always)] + pub fn new() -> Self { + unsafe { + let mut ctx = MaybeUninit::uninit(); + ffi::SHA512_Init(ctx.as_mut_ptr()); + SHA512(ctx.assume_init()) + } + } + + #[inline(always)] + pub fn reset(&mut self) { + unsafe { ffi::SHA512_Init(&mut self.0) }; + } + + /// Feeds some data into the hasher. + /// + /// This can be called multiple times. + #[inline(always)] + pub fn update(&mut self, buf: &[u8]) { + unsafe { + ffi::SHA512_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); + } + } + + /// Returns the hash of the data. + #[inline(always)] + pub fn finish(&mut self) -> [u8; SHA512_HASH_SIZE] { + unsafe { + let mut hash = MaybeUninit::<[u8; SHA512_HASH_SIZE]>::uninit(); + ffi::SHA512_Final(hash.as_mut_ptr() as *mut _, &mut self.0); + hash.assume_init() + } + } +} + +impl Write for SHA512 { + #[inline(always)] + fn write(&mut self, b: &[u8]) -> std::io::Result { + self.update(b); + Ok(b.len()) + } + + #[inline(always)] + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +unsafe impl Send for SHA512 {} + +pub struct SHA384(ffi::SHA512_CTX); + +impl SHA384 { + #[inline(always)] + pub fn hash(data: &[u8]) -> [u8; SHA384_HASH_SIZE] { + unsafe { + let mut hash = MaybeUninit::<[u8; SHA384_HASH_SIZE]>::uninit(); + ffi::SHA384(data.as_ptr(), data.len(), hash.as_mut_ptr() as *mut _); + hash.assume_init() + } + } + + #[inline(always)] + pub fn new() -> Self { + unsafe { + let mut ctx = MaybeUninit::uninit(); + ffi::SHA384_Init(ctx.as_mut_ptr()); + SHA384(ctx.assume_init()) + } + } + + #[inline(always)] + pub fn reset(&mut self) { + unsafe { + ffi::SHA384_Init(&mut self.0); + } + } + + #[inline(always)] + pub fn update(&mut self, buf: &[u8]) { + unsafe { + ffi::SHA384_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); + } + } + + #[inline(always)] + pub fn finish(&mut self) -> [u8; SHA384_HASH_SIZE] { + unsafe { + let mut hash = MaybeUninit::<[u8; SHA384_HASH_SIZE]>::uninit(); + ffi::SHA384_Final(hash.as_mut_ptr() as *mut _, &mut self.0); + hash.assume_init() + } + } +} + +impl Write for SHA384 { + #[inline(always)] + fn write(&mut self, b: &[u8]) -> std::io::Result { + self.update(b); + Ok(b.len()) + } + + #[inline(always)] + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +unsafe impl Send for SHA384 {} + +//#[link(name="crypto")] +extern "C" { + fn HMAC_CTX_new() -> *mut c_void; + fn HMAC_CTX_reset(ctx: *mut c_void) -> c_int; + fn HMAC_Init_ex(ctx: *mut c_void, key: *const c_void, key_len: c_int, evp_md: *const c_void, _impl: *const c_void) -> c_int; + fn HMAC_Update(ctx: *mut c_void, data: *const c_void, len: usize) -> c_int; + fn HMAC_Final(ctx: *mut c_void, output: *mut c_void, output_len: *mut c_uint) -> c_int; + fn HMAC_CTX_free(ctx: *mut c_void); + fn EVP_sha384() -> *const c_void; + fn EVP_sha512() -> *const c_void; +} + +pub struct HMACSHA512 { + ctx: *mut c_void, + evp_md: *const c_void, +} + +impl HMACSHA512 { + #[inline(always)] + pub fn new(key: &[u8]) -> Self { + unsafe { + let hm = Self { ctx: HMAC_CTX_new(), evp_md: EVP_sha512() }; + assert!(!hm.ctx.is_null()); + assert_ne!(HMAC_Init_ex(hm.ctx, key.as_ptr().cast(), key.len() as c_int, hm.evp_md, null()), 0); + hm + } + } + + #[inline(always)] + pub fn reset(&mut self, key: &[u8]) { + unsafe { + assert_ne!(HMAC_CTX_reset(self.ctx), 0); + assert_ne!(HMAC_Init_ex(self.ctx, key.as_ptr().cast(), key.len() as c_int, self.evp_md, null()), 0); + } + } + + #[inline(always)] + pub fn update(&mut self, b: &[u8]) { + unsafe { + assert_ne!(HMAC_Update(self.ctx, b.as_ptr().cast(), b.len()), 0); + } + } + + #[inline(always)] + pub fn finish_into(&mut self, md: &mut [u8]) { + unsafe { + debug_assert_eq!(md.len(), HMAC_SHA512_SIZE); + let mut mdlen = HMAC_SHA512_SIZE as c_uint; + assert_ne!(HMAC_Final(self.ctx, md.as_mut_ptr().cast(), &mut mdlen), 0); + debug_assert_eq!(mdlen, HMAC_SHA512_SIZE as c_uint); + } + } + + #[inline(always)] + pub fn finish(&mut self) -> [u8; HMAC_SHA512_SIZE] { + let mut tmp = [0u8; HMAC_SHA512_SIZE]; + self.finish_into(&mut tmp); + tmp + } +} + +impl Drop for HMACSHA512 { + #[inline(always)] + fn drop(&mut self) { + unsafe { HMAC_CTX_free(self.ctx) }; + } +} + +unsafe impl Send for HMACSHA512 {} + +pub struct HMACSHA384 { + ctx: *mut c_void, + evp_md: *const c_void, +} + +impl HMACSHA384 { + #[inline(always)] + pub fn new(key: &[u8]) -> Self { + unsafe { + let hm = Self { ctx: HMAC_CTX_new(), evp_md: EVP_sha384() }; + assert!(!hm.ctx.is_null()); + assert_ne!(HMAC_Init_ex(hm.ctx, key.as_ptr().cast(), key.len() as c_int, hm.evp_md, null()), 0); + hm + } + } + + #[inline(always)] + pub fn reset(&mut self, key: &[u8]) { + unsafe { + assert_ne!(HMAC_CTX_reset(self.ctx), 0); + assert_ne!(HMAC_Init_ex(self.ctx, key.as_ptr().cast(), key.len() as c_int, self.evp_md, null()), 0); + } + } + + #[inline(always)] + pub fn update(&mut self, b: &[u8]) { + unsafe { + assert_ne!(HMAC_Update(self.ctx, b.as_ptr().cast(), b.len()), 0); + } + } + + #[inline(always)] + pub fn finish_into(&mut self, md: &mut [u8]) { + unsafe { + assert_eq!(md.len(), HMAC_SHA384_SIZE); + let mut mdlen = HMAC_SHA384_SIZE as c_uint; + assert_ne!(HMAC_Final(self.ctx, md.as_mut_ptr().cast(), &mut mdlen), 0); + assert_eq!(mdlen, HMAC_SHA384_SIZE as c_uint); + } + } + + #[inline(always)] + pub fn finish(&mut self) -> [u8; HMAC_SHA384_SIZE] { + let mut tmp = [0u8; HMAC_SHA384_SIZE]; + self.finish_into(&mut tmp); + tmp + } +} + +impl Drop for HMACSHA384 { + #[inline(always)] + fn drop(&mut self) { + unsafe { HMAC_CTX_free(self.ctx) }; + } +} + +unsafe impl Send for HMACSHA384 {} + +#[inline(always)] +pub fn hmac_sha512(key: &[u8], msg: &[u8]) -> [u8; HMAC_SHA512_SIZE] { + let mut hm = HMACSHA512::new(key); + hm.update(msg); + hm.finish() +} + +pub fn hmac_sha512_secret256(key: &[u8], msg: &[u8]) -> Secret<32> { + let mut hm = HMACSHA512::new(key); + hm.update(msg); + let mut md = [0u8; HMAC_SHA512_SIZE]; + hm.finish_into(&mut md); + // With such a simple procedure hopefully the compiler implements the following line as a move from md and not a copy + // If not we ought to change this code so we don't leak a secret value on the stack + unsafe { Secret::from_bytes(&md[0..32]) } +} +pub fn hmac_sha512_secret(key: &[u8], msg: &[u8]) -> Secret { + let mut hm = HMACSHA512::new(key); + hm.update(msg); + Secret::move_bytes(hm.finish()) +} + +#[inline(always)] +pub fn hmac_sha384(key: &[u8], msg: &[u8]) -> [u8; HMAC_SHA384_SIZE] { + let mut hm = HMACSHA384::new(key); + hm.update(msg); + hm.finish() +} diff --git a/src/crypto/src/lib.rs b/src/crypto/src/lib.rs new file mode 100644 index 0000000..c83b8e2 --- /dev/null +++ b/src/crypto/src/lib.rs @@ -0,0 +1,59 @@ +mod cipher_ctx; +mod error; + +pub mod hash; +pub mod mimcvdf; +pub mod p384; +pub mod random; +pub mod secret; + +pub mod constant; +pub mod poly1305; +pub mod salsa; +pub mod typestate; +pub mod x25519; + +#[cfg(target_os = "macos")] +pub mod aes_fruity; +#[cfg(target_os = "macos")] +pub use aes_fruity as aes; + +#[cfg(not(target_os = "macos"))] +pub mod aes_openssl; +#[cfg(not(target_os = "macos"))] +pub use aes_openssl as aes; + +mod aes_tests; + +#[cfg(target_os = "macos")] +pub mod aes_gmac_siv_fruity; +#[cfg(target_os = "macos")] +pub use aes_gmac_siv_fruity as aes_gmac_siv; + +#[cfg(not(target_os = "macos"))] +pub mod aes_gmac_siv_openssl; +#[cfg(not(target_os = "macos"))] +pub use aes_gmac_siv_openssl as aes_gmac_siv; +use ctor::ctor; + +#[ctor] +fn openssl_init() { + ffi::init(); +} + +/// Constant time byte slice equality. +#[inline] +pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { + let (a, b) = (a.as_ref(), b.as_ref()); + if a.len() == b.len() { + let mut x = 0u8; + for (aa, bb) in a.iter().zip(b.iter()) { + x |= *aa ^ *bb; + } + x == 0 + } else { + false + } +} + +pub const ZEROES: [u8; 64] = [0_u8; 64]; diff --git a/src/crypto/src/mimcvdf.rs b/src/crypto/src/mimcvdf.rs new file mode 100644 index 0000000..bfb4992 --- /dev/null +++ b/src/crypto/src/mimcvdf.rs @@ -0,0 +1,141 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +/* + * MIMC is a hash function originally designed for use with STARK and SNARK proofs. It's based + * on modular multiplication and exponentiation instead of the usual bit twiddling or ARX + * operations that underpin more common hash algorithms. + * + * It's useful as a verifiable delay function because it can be computed in both directions with + * one direction taking orders of magnitude longer than the other. The "backward" direction is + * used as the delay function as it requires modular exponentiation which is inherently more + * compute intensive. The "forward" direction simply requires modular cubing which is two modular + * multiplications and is much faster. + * + * It's also nice because it's incredibly simple with a tiny code footprint. + * + * This is used for anti-DOS and anti-spamming delay functions. It's not used for anything + * really "cryptographically hard," and if it were broken cryptographically it would still be + * useful as a VDF as long as the break didn't yield a significantly faster way of computing a + * delay proof than the straightforward iterative way implemented here. + * + * Here are two references on MIMC with the first being the original paper and the second being + * a blog post describing its use as a VDF. + * + * https://eprint.iacr.org/2016/492.pdf + * https://vitalik.ca/general/2018/07/21/starks_part_3.html + */ + +// p = 2^127 - 39, the largest 127-bit prime of the form 6k + 5 +const PRIME: u128 = 170141183460469231731687303715884105689; + +// (2p - 1) / 3 +const PRIME_2P_MINUS_1_DIV_3: u128 = 113427455640312821154458202477256070459; + +// Randomly generated round constants, each modulo PRIME. +const K_COUNT_MASK: usize = 31; +const K: [u128; 32] = [ + 0x1fdd07a761b611bb1ab9419a70599a7c, + 0x23056b05d5c6b925e333d7418047650a, + 0x77a638f9b437a307f8866fbd2672c705, + 0x60213dab83bab91d1c310bd87e9da332, + 0xf56bc883301ab373179e46b098b7a7, + 0x7914a0dbd2f971344173b350c28a838, + 0x44bb64af5e446e6ebdc068d10d318f26, + 0x1bca1921fd328bb725ae0cbcbc20a263, + 0xafa963242f5216a7da1cd5328b23659, + 0x7fe17c43782b883a63ee0a790e0b2b77, + 0x23bb62abf728bf453200ee528f902c33, + 0x75ec0c055be14955db6878567e3c0465, + 0x7902bb57876e0b08b4de02a66755e5d7, + 0xe5d7094f37b615f5a1e1594b0390de8, + 0x12d4ddee90653a26f5de63ff4651f2d, + 0xce4a15bc35633b5ed8bcae2c93d739c, + 0x23f25b935e52df87255db8c608ef9ab4, + 0x611a08d7464fb984c98104d77f1609a7, + 0x7aa825876a7f6acde5efa57992da9c43, + 0x2be9686f630fa28a0a0e1081a59755b4, + 0x50060dac9ac4656ba3f8ee7592f4e28a, + 0x4113abff6f5bb303eac2ca809d4d529d, + 0x2af9d01d4e753feb5834c14ca0543397, + 0x73c2d764691ced2b823dda887e22ae85, + 0x5b53dcd4750ff888dca2497cec4dacb7, + 0x5d8984a52c2d8f3cc9bcf61ef29f8a1, + 0x588d8cc99533d649aabb5f0f552140e, + 0x4dae04985fde8c8464ba08aaa7d8761e, + 0x53f0c4740b8c3bda3fc05109b9a2b71, + 0x3e918c88a6795e3bf840e0b74d91b9d7, + 0x1dbcb30d724f11200aebb1dff87def91, + 0x6086b0af0e1e68558170239d23be9780, +]; + +fn mulmod(mut a: u128, mut b: u128) -> u128 { + let mut res: u128 = 0; + a %= M; + loop { + if (b & 1) != 0 { + res = res.wrapping_add(a) % M; + } + b = b.wrapping_shr(1); + if b != 0 { + a = a.wrapping_shl(1) % M; + } else { + return res; + } + } +} + +#[inline(always)] +fn powmod(mut base: u128, mut exp: u128) -> u128 { + let mut res: u128 = 1; + loop { + if (exp & 1) != 0 { + res = mulmod::(base, res); + } + exp = exp.wrapping_shr(1); + if exp != 0 { + base = mulmod::(base, base); + } else { + return res; + } + } +} + +/// Compute MIMC for the given number of iterations and return a proof that can be checked much more quickly. +pub fn delay(mut input: u128, rounds: usize) -> u128 { + debug_assert!(rounds > 0); + input %= PRIME; + for r in 1..(rounds + 1) { + input = powmod::(input ^ K[(rounds - r) & K_COUNT_MASK], PRIME_2P_MINUS_1_DIV_3); + } + input +} + +/// Quickly verify the result of delay() given the returned proof, original input, and original number of rounds. +pub fn verify(mut proof: u128, original_input: u128, rounds: usize) -> bool { + debug_assert!(rounds > 0); + for r in 0..rounds { + proof = mulmod::(proof, mulmod::(proof, proof)) ^ K[r & K_COUNT_MASK]; + } + proof == (original_input % PRIME) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn delay_and_verify() { + for i in 1..5 { + let input = (crate::random::xorshift64_random() as u128).wrapping_mul(crate::random::xorshift64_random() as u128); + let proof = delay(input, i * 3); + //println!("{}", proof); + assert!(verify(proof, input, i * 3)); + } + } +} diff --git a/src/crypto/src/p384.rs b/src/crypto/src/p384.rs new file mode 100644 index 0000000..5cd4be5 --- /dev/null +++ b/src/crypto/src/p384.rs @@ -0,0 +1,406 @@ +// Version using OpenSSL's ECC +use std::os::raw::{c_int, c_ulong, c_void}; +use std::sync::Mutex; +use std::{mem, ptr}; + +use lazy_static::lazy_static; + +use crate::error::{cvt, cvt_n, cvt_p, ErrorStack}; +use crate::hash::SHA384; +use crate::secret::Secret; +use crate::secure_eq; + +pub const P384_PUBLIC_KEY_SIZE: usize = 49; +pub const P384_SECRET_KEY_SIZE: usize = 48; +pub const P384_ECDSA_SIGNATURE_SIZE: usize = 96; +pub const P384_ECDH_SHARED_SECRET_SIZE: usize = 48; + +extern "C" { + fn ECDH_compute_key(out: *mut u8, outlen: c_ulong, pub_key: *const ffi::EC_POINT, ecdh: *mut ffi::EC_KEY, kdf: *const c_void) -> c_int; +} +/// A NIST P-384 ECDH/ECDSA public key. +pub struct P384PublicKey { + /// OpenSSL does not guarantee threadsafety for this object (even though it could) so we have + /// to wrap this in a mutex. + key: Mutex, + bytes: [u8; P384_PUBLIC_KEY_SIZE], +} + +unsafe impl Send for P384PublicKey {} +unsafe impl Sync for P384PublicKey {} + +impl P384PublicKey { + /// Create a p384 public key from raw bytes. + /// `buffer` must have length `P384_PUBLIC_KEY_SIZE`. + pub fn from_bytes(buffer: &[u8]) -> Option { + if buffer.len() == P384_PUBLIC_KEY_SIZE { + unsafe { + // Write the buffer into OpenSSL. + let key = OSSLKey::pub_from_slice(buffer).ok()?; + // Get OpenSSL to double check if this final key makes sense. + // It will be read-only after this point. + if ffi::EC_KEY_check_key(key.0) == 1 { + let mut bytes = [0u8; P384_PUBLIC_KEY_SIZE]; + bytes.clone_from_slice(buffer); + return Some(Self { key: Mutex::new(key), bytes }); + } + } + } + None + } + + /// Verify the ECDSA/SHA384 signature. + pub fn verify(&self, msg: &[u8], signature: &[u8]) -> bool { + if signature.len() == P384_ECDSA_SIGNATURE_SIZE { + const CAP: usize = P384_ECDSA_SIGNATURE_SIZE / 2; + unsafe { + // Write the raw bytes into OpenSSL. + let r = OSSLBN::from_slice(&signature[0..CAP]); + let s = OSSLBN::from_slice(&signature[CAP..]); + if let (Ok(r), Ok(s)) = (r, s) { + // Create the OpenSSL object that actually supports verification. + if let Ok(sig) = cvt_p(ffi::ECDSA_SIG_new()) { + let is_valid = if ffi::ECDSA_SIG_set0(sig, r.0, s.0) == 1 { + // For some reason this one random function, `ECDSA_SIG_set0`, takes + // ownership of its parameters. I've double checked and it is the only one + // we call that does that. We `forget` the memory so we don't double free. + mem::forget(r); + mem::forget(s); + // Digest the message. + let data = &SHA384::hash(msg); + + let key = self.key.lock().unwrap(); + // Actually perform the verification. + ffi::ECDSA_do_verify(data.as_ptr(), data.len() as c_int, sig, key.0) == 1 + } else { + false + }; + // Guarantee signature free. + ffi::ECDSA_SIG_free(sig); + return is_valid; + } + } + } + } + false + } + + pub fn as_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE] { + &self.bytes + } +} +impl Clone for P384PublicKey { + fn clone(&self) -> Self { + Self { + key: Mutex::new(self.key.lock().unwrap().clone_public().unwrap()), + bytes: self.bytes, + } + } +} +impl PartialEq for P384PublicKey { + fn eq(&self, other: &Self) -> bool { + secure_eq(&self.bytes, &other.bytes) + } +} + +/// A NIST P-384 ECDH/ECDSA public/private key pair. +pub struct P384KeyPair { + /// OpenSSL does not guarantee threadsafety for this object (even though it could) so we have + /// to wrap this in a mutex. + pair: Mutex, + pub_bytes: [u8; P384_PUBLIC_KEY_SIZE], +} + +unsafe impl Send for P384KeyPair {} +unsafe impl Sync for P384KeyPair {} + +impl P384KeyPair { + /// Randomly generate a new p384 keypair. + pub fn generate() -> P384KeyPair { + unsafe { + let pair = OSSLKey::new().unwrap(); + // Ask OpenSSL to securely generate the keypair. + cvt(ffi::EC_KEY_generate_key(pair.0)).unwrap(); + // Read out the raw public key into a buffer. + let public_key = ffi::EC_KEY_get0_public_key(pair.0); + let mut buffer = [0_u8; P384_PUBLIC_KEY_SIZE]; + let bnc = OSSLBNC::new().unwrap(); + let len = ffi::EC_POINT_point2oct( + GROUP_P384.0, + public_key, + ffi::point_conversion_form_t::POINT_CONVERSION_COMPRESSED, + buffer.as_mut_ptr(), + P384_PUBLIC_KEY_SIZE, + bnc.0, + ); + if len <= 0 { + Err::<(), _>(ErrorStack::get()).unwrap(); + } + Self { pair: Mutex::new(pair), pub_bytes: buffer } + } + } + + /// Create a p384 keypair from raw bytes. + /// `public_bytes` should have length `P384_PUBLIC_KEY_SIZE` and `secret_bytes` should have length + /// `P384_SECRET_KEY_SIZE`. + pub fn from_bytes(public_bytes: &[u8], secret_bytes: &[u8]) -> Option { + if public_bytes.len() == P384_PUBLIC_KEY_SIZE && secret_bytes.len() == P384_SECRET_KEY_SIZE { + unsafe { + // Write the raw bytes into OpenSSL. + let pair = OSSLKey::pub_from_slice(public_bytes).ok()?; + let private = OSSLBN::from_slice(secret_bytes).ok()?; + // Tell OpenSSL to assign the private key to the public key. + // This makes the public key into a proper keypair. + if cvt(ffi::EC_KEY_set_private_key(pair.0, private.0)).is_ok() { + // Get OpenSSL to double check if this final key makes sense. + // It will be read-only after this point. + if ffi::EC_KEY_check_key(pair.0) == 1 { + let mut pub_bytes = [0u8; P384_PUBLIC_KEY_SIZE]; + pub_bytes.clone_from_slice(public_bytes); + return Some(Self { pair: Mutex::new(pair), pub_bytes }); + } + } + } + } + None + } + /// Create a new `P384PublicKey` object that only contains the public key from + /// this keypair. This object can be safely sent to a different thread. + pub fn to_public_key(&self) -> P384PublicKey { + let key = self.pair.lock().unwrap().clone_public().unwrap(); + P384PublicKey { key: Mutex::new(key), bytes: self.pub_bytes } + } + /// Get the raw bytes that uniquely define the public key. + pub fn public_key_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE] { + &self.pub_bytes + } + + /// Clone the raw bytes that uniquely define the secret key. + /// They are wrapped in a container which will erase them on drop. + /// + /// **Only write these to 100% trusted storage mediums. Avoid calling this function in general.** + pub fn secret_key_bytes(&self) -> Secret { + unsafe { + let mut tmp: Secret = Secret::default(); + let keypair = self.pair.lock().unwrap(); + // Get a temporary handle to the private key. + let ptr = ffi::EC_KEY_get0_private_key(keypair.0); + // Read the key's raw bytes out of OpenSSL. + let size = cvt_n(ffi::BN_bn2bin(ptr, tmp.as_bytes_mut().as_mut_ptr())).unwrap() as usize; + drop(keypair); + + // Double check big-endian-ness. + tmp.0.copy_within(..size, P384_SECRET_KEY_SIZE - size); + tmp + } + } + + /// Sign a message with ECDSA/SHA384. + pub fn sign(&self, msg: &[u8]) -> [u8; P384_ECDSA_SIGNATURE_SIZE] { + // Digest the message. + let data = &SHA384::hash(msg); + unsafe { + let keypair = self.pair.lock().unwrap(); + // Actually create the signature with ECDSA. + let sig = cvt_p(ffi::ECDSA_do_sign(data.as_ptr(), data.len() as c_int, keypair.0)); + drop(keypair); + let sig = sig.unwrap(); + + // Get handles to the OpenSSL objects that actually support reading out into bytes. + let mut r = ptr::null(); + let mut s = ptr::null(); + ffi::ECDSA_SIG_get0(sig, &mut r, &mut s); + if r.is_null() || s.is_null() { + ffi::ECDSA_SIG_free(sig); + Err::<(), _>(ErrorStack::get()).unwrap(); + } + // Determine the size of the buffers to guarantee sanity and big-endian-ness. + let r_len = ((ffi::BN_num_bits(r) + 7) / 8) as usize; + let s_len = ((ffi::BN_num_bits(s) + 7) / 8) as usize; + const CAP: usize = P384_ECDSA_SIGNATURE_SIZE / 2; + if !(r_len > 0 && s_len > 0 && r_len <= CAP && s_len <= CAP) { + ffi::ECDSA_SIG_free(sig); + Err::<(), _>(ErrorStack::get()).unwrap(); + } + + let mut b = [0_u8; P384_ECDSA_SIGNATURE_SIZE]; + // Read the signature's raw bytes out of OpenSSL. + ffi::BN_bn2bin(r, b[(CAP - r_len)..CAP].as_mut_ptr()); + ffi::BN_bn2bin(s, b[(P384_ECDSA_SIGNATURE_SIZE - s_len)..P384_ECDSA_SIGNATURE_SIZE].as_mut_ptr()); + ffi::ECDSA_SIG_free(sig); + b + } + } + + /// Perform ECDH key agreement, returning the raw (un-hashed!) ECDH secret. + /// + /// This secret should not be used directly. It should be hashed and perhaps used in a KDF. + pub fn agree(&self, other_public: &P384PublicKey) -> Option> { + let keypair = self.pair.lock().unwrap(); + let other_key = other_public.key.lock().unwrap(); + unsafe { + let mut s: Secret = Secret::default(); + // Ask OpenSSL to perform DH between the keypair and the other key's public key object. + if ECDH_compute_key( + s.as_bytes_mut().as_mut_ptr(), + P384_ECDH_SHARED_SECRET_SIZE as c_ulong, + ffi::EC_KEY_get0_public_key(other_key.0), + keypair.0, + ptr::null(), + ) == P384_ECDH_SHARED_SECRET_SIZE as c_int + { + Some(s) + } else { + None + } + } + } +} + +/// OpenSSL wrapper for a BN_CTX handle that guarantees free will be called. +struct OSSLBNC(*mut ffi::BN_CTX); +impl OSSLBNC { + unsafe fn new() -> Result { + cvt_p(ffi::BN_CTX_new()).map(Self) + } +} +impl Drop for OSSLBNC { + fn drop(&mut self) { + unsafe { + ffi::BN_CTX_free(self.0); + } + } +} +/// OpenSSL wrapper for a BIGNUM handle that guarantees free will be called. +struct OSSLBN(*mut ffi::BIGNUM); +impl OSSLBN { + /// We would use OpenSSL's newer API for p384 if it actually supported raw byte encodings of keys. + /// Until then we are stuck with the old API. + unsafe fn from_slice(n: &[u8]) -> Result { + cvt_p(ffi::BN_bin2bn(n.as_ptr(), n.len() as c_int, ptr::null_mut())).map(Self) + } +} +impl Drop for OSSLBN { + fn drop(&mut self) { + unsafe { + ffi::BN_free(self.0); + } + } +} +/// OpenSSL wrapper for a EC_KEY handle that guarantees free will be called. +struct OSSLKey(*mut ffi::EC_KEY); +impl OSSLKey { + /// Create an empty key, guaranteeing to the caller it has the correct group and will be freed. + unsafe fn new() -> Result { + let key = cvt_p(ffi::EC_KEY_new())?; + cvt(ffi::EC_KEY_set_group(key, GROUP_P384.0))?; + Ok(Self(key)) + } + /// Create a key, guaranteeing to the caller it has the correct group, has a public key and will be freed. + /// + /// We would use OpenSSL's newer API for p384 if it actually supported raw byte encodings of keys. + /// Until then we are stuck with the old API. + unsafe fn pub_from_slice(buffer: &[u8]) -> Result> { + /// The public key is an ec_point, we need to be sure we free its memory + struct Point(*mut ffi::EC_POINT); + impl Point { + unsafe fn new() -> Result { + cvt_p(ffi::EC_POINT_new(GROUP_P384.0)).map(Self) + } + } + impl Drop for Point { + fn drop(&mut self) { + unsafe { + ffi::EC_POINT_free(self.0); + } + } + } + let bnc = OSSLBNC::new()?; + let point = Point::new()?; + // Ask OpenSSL to read the raw bytes into the OpenSSL object. + cvt(ffi::EC_POINT_oct2point(GROUP_P384.0, point.0, buffer.as_ptr(), buffer.len(), bnc.0))?; + // Check if the object is valid. + if cvt_n(ffi::EC_POINT_is_on_curve(GROUP_P384.0, point.0, bnc.0))? == 1 { + // Create an OpenSSL key and guarantee to the caller that the key was initialized with a + // public key. + let ec_key = OSSLKey::new()?; + cvt(ffi::EC_KEY_set_public_key(ec_key.0, point.0))?; + Ok(ec_key) + } else { + Err(None) + } + } + /// Create a `Send`-able clone of the public key. We don't reference count for this reason. + fn clone_public(&self) -> Result { + unsafe { + let point = ffi::EC_KEY_get0_public_key(self.0); + // Create an OpenSSL key and guarantee to the caller that the key was initialized with a + // public key. + let key = OSSLKey::new()?; + cvt(ffi::EC_KEY_set_public_key(key.0, point))?; + Ok(key) + } + } +} +impl Drop for OSSLKey { + fn drop(&mut self) { + unsafe { + ffi::EC_KEY_free(self.0); + } + } +} +/// OpenSSL wrapper for a EC_GROUP that is used to tell rust that an OpenSSL EC_GROUP is threadsafe. +/// We only ever instantiate one of these with lazy_static. It is never freed. +struct OSSLGroup(*mut ffi::EC_GROUP); +impl OSSLGroup { + unsafe fn p384() -> Self { + Self(cvt_p(ffi::EC_GROUP_new_by_curve_name(ffi::NID_secp384r1)).unwrap()) + } +} +unsafe impl Send for OSSLGroup {} +unsafe impl Sync for OSSLGroup {} +lazy_static! { + static ref GROUP_P384: OSSLGroup = unsafe { OSSLGroup::p384() }; +} + +#[cfg(test)] +mod tests { + use crate::{p384::P384KeyPair, secure_eq}; + + #[test] + fn generate_sign_verify_agree() { + let kp = P384KeyPair::generate(); + let kp2 = P384KeyPair::generate(); + let kp_pub = kp.to_public_key(); + let kp2_pub = kp2.to_public_key(); + + let sig = kp.sign(&[0_u8; 16]); + if !kp_pub.verify(&[0_u8; 16], &sig) { + panic!("ECDSA verify failed"); + } + if kp_pub.verify(&[1_u8; 16], &sig) { + panic!("ECDSA verify succeeded for incorrect message"); + } + + let sec0 = kp.agree(&kp2_pub).unwrap(); + let sec1 = kp2.agree(&kp_pub).unwrap(); + if !secure_eq(&sec0, &sec1) { + panic!("ECDH secrets do not match"); + } + + let pkb = kp.public_key_bytes(); + let skb = kp.secret_key_bytes(); + let kp3 = P384KeyPair::from_bytes(pkb, skb.as_ref()).unwrap(); + + let pkb3 = kp3.public_key_bytes(); + let skb3 = kp3.secret_key_bytes(); + + assert_eq!(pkb, pkb3); + assert_eq!(skb.as_bytes(), skb3.as_bytes()); + + let sig = kp3.sign(&[3_u8; 16]); + if !kp_pub.verify(&[3_u8; 16], &sig) { + panic!("ECDSA verify failed (from key reconstructed from bytes)"); + } + } +} diff --git a/src/crypto/src/p384_builtin.rs b/src/crypto/src/p384_builtin.rs new file mode 100644 index 0000000..24e2362 --- /dev/null +++ b/src/crypto/src/p384_builtin.rs @@ -0,0 +1,1098 @@ +// This is small and relatively fast but may not be constant time and hasn't been well audited, so we don't +// use it by default. It's left here though in case it proves useful in the future on embedded systems. +#[cfg(target_feature = "builtin_nist_ecc")] +mod builtin { + use crate::hash::SHA384; + use crate::secret::Secret; + + // EASY-ECC by Kenneth MacKay + // https://github.com/esxgx/easy-ecc (no longer there, but search GitHub for forks) + // + // Translated directly from C to Rust using https://c2rust.com and then hacked a bit + // to eliminate some dependencies. The translated code still has a lot of gratuitous + // "as"es but they're not consequential. + // + // It inherits its original BSD 2-Clause license, not ZeroTier's license. + + pub mod libc { + pub type c_uchar = u8; + pub type c_ulong = u64; + pub type c_long = i64; + pub type c_uint = u32; + pub type c_int = i32; + pub type c_ulonglong = u64; + pub type c_longlong = i64; + } + + pub type uint8_t = libc::c_uchar; + pub type uint64_t = libc::c_ulong; + pub type uint = libc::c_uint; + pub type uint128_t = u128; + pub struct EccPoint { + pub x: [u64; 6], + pub y: [u64; 6], + } + static mut curve_p: [uint64_t; 6] = [ + 0xffffffff as libc::c_uint as uint64_t, + 0xffffffff00000000 as libc::c_ulong, + 0xfffffffffffffffe as libc::c_ulong, + 0xffffffffffffffff as libc::c_ulong, + 0xffffffffffffffff as libc::c_ulong, + 0xffffffffffffffff as libc::c_ulong, + ]; + static mut curve_b: [uint64_t; 6] = [ + 0x2a85c8edd3ec2aef as libc::c_long as uint64_t, + 0xc656398d8a2ed19d as libc::c_ulong, + 0x314088f5013875a as libc::c_long as uint64_t, + 0x181d9c6efe814112 as libc::c_long as uint64_t, + 0x988e056be3f82d19 as libc::c_ulong, + 0xb3312fa7e23ee7e4 as libc::c_ulong, + ]; + static mut curve_G: EccPoint = { + let mut init = EccPoint { + x: [ + 0x3a545e3872760ab7 as libc::c_long as uint64_t, + 0x5502f25dbf55296c as libc::c_long as uint64_t, + 0x59f741e082542a38 as libc::c_long as uint64_t, + 0x6e1d3b628ba79b98 as libc::c_long as uint64_t, + 0x8eb1c71ef320ad74 as libc::c_ulong, + 0xaa87ca22be8b0537 as libc::c_ulong, + ], + y: [ + 0x7a431d7c90ea0e5f as libc::c_long as uint64_t, + 0xa60b1ce1d7e819d as libc::c_long as uint64_t, + 0xe9da3113b5f0b8c0 as libc::c_ulong, + 0xf8f41dbd289a147c as libc::c_ulong, + 0x5d9e98bf9292dc29 as libc::c_long as uint64_t, + 0x3617de4a96262c6f as libc::c_long as uint64_t, + ], + }; + init + }; + static mut curve_n: [uint64_t; 6] = [ + 0xecec196accc52973 as libc::c_ulong, + 0x581a0db248b0a77a as libc::c_long as uint64_t, + 0xc7634d81f4372ddf as libc::c_ulong, + 0xffffffffffffffff as libc::c_ulong, + 0xffffffffffffffff as libc::c_ulong, + 0xffffffffffffffff as libc::c_ulong, + ]; + + unsafe fn getRandomNumber(mut p_vli: *mut uint64_t) -> libc::c_int { + crate::random::fill_bytes_secure(&mut *std::ptr::slice_from_raw_parts_mut(p_vli.cast(), 48)); + return 1 as libc::c_int; + } + + unsafe fn vli_clear(mut p_vli: *mut uint64_t) { + let mut i: uint = 0; + i = 0 as libc::c_int as uint; + while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { + *p_vli.offset(i as isize) = 0 as libc::c_int as uint64_t; + i = i.wrapping_add(1) + } + } + /* Returns 1 if p_vli == 0, 0 otherwise. */ + + unsafe fn vli_isZero(mut p_vli: *mut uint64_t) -> libc::c_int { + let mut i: uint = 0; + i = 0 as libc::c_int as uint; + while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { + if *p_vli.offset(i as isize) != 0 { + return 0 as libc::c_int; + } + i = i.wrapping_add(1) + } + return 1 as libc::c_int; + } + /* Returns nonzero if bit p_bit of p_vli is set. */ + + unsafe fn vli_testBit(mut p_vli: *mut uint64_t, mut p_bit: uint) -> uint64_t { + return *p_vli.offset(p_bit.wrapping_div(64 as libc::c_int as libc::c_uint) as isize) + & (1 as libc::c_int as uint64_t) << p_bit.wrapping_rem(64 as libc::c_int as libc::c_uint); + } + /* Counts the number of 64-bit "digits" in p_vli. */ + + unsafe fn vli_numDigits(mut p_vli: *mut uint64_t) -> uint { + let mut i: libc::c_int = 0; + /* Search from the end until we find a non-zero digit. + We do it in reverse because we expect that most digits will be nonzero. */ + i = 48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int; + while i >= 0 as libc::c_int && *p_vli.offset(i as isize) == 0 as libc::c_int as libc::c_ulong { + i -= 1 + } + return (i + 1 as libc::c_int) as uint; + } + /* Counts the number of bits required for p_vli. */ + + unsafe fn vli_numBits(mut p_vli: *mut uint64_t) -> uint { + let mut i: uint = 0; + let mut l_digit: uint64_t = 0; + let mut l_numDigits: uint = vli_numDigits(p_vli); + if l_numDigits == 0 as libc::c_int as libc::c_uint { + return 0 as libc::c_int as uint; + } + l_digit = *p_vli.offset(l_numDigits.wrapping_sub(1 as libc::c_int as libc::c_uint) as isize); + i = 0 as libc::c_int as uint; + while l_digit != 0 { + l_digit >>= 1 as libc::c_int; + i = i.wrapping_add(1) + } + return l_numDigits + .wrapping_sub(1 as libc::c_int as libc::c_uint) + .wrapping_mul(64 as libc::c_int as libc::c_uint) + .wrapping_add(i); + } + /* Sets p_dest = p_src. */ + + unsafe fn vli_set(mut p_dest: *mut uint64_t, mut p_src: *mut uint64_t) { + let mut i: uint = 0; + i = 0 as libc::c_int as uint; + while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { + *p_dest.offset(i as isize) = *p_src.offset(i as isize); + i = i.wrapping_add(1) + } + } + /* Returns sign of p_left - p_right. */ + + unsafe fn vli_cmp(mut p_left: *mut uint64_t, mut p_right: *mut uint64_t) -> libc::c_int { + let mut i: libc::c_int = 0; + i = 48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int; + while i >= 0 as libc::c_int { + if *p_left.offset(i as isize) > *p_right.offset(i as isize) { + return 1 as libc::c_int; + } else { + if *p_left.offset(i as isize) < *p_right.offset(i as isize) { + return -(1 as libc::c_int); + } + } + i -= 1 + } + return 0 as libc::c_int; + } + /* Computes p_result = p_in << c, returning carry. Can modify in place (if p_result == p_in). 0 < p_shift < 64. */ + + unsafe fn vli_lshift(mut p_result: *mut uint64_t, mut p_in: *mut uint64_t, mut p_shift: uint) -> uint64_t { + let mut l_carry: uint64_t = 0 as libc::c_int as uint64_t; + let mut i: uint = 0; + i = 0 as libc::c_int as uint; + while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { + let mut l_temp: uint64_t = *p_in.offset(i as isize); + *p_result.offset(i as isize) = l_temp << p_shift | l_carry; + l_carry = l_temp >> (64 as libc::c_int as libc::c_uint).wrapping_sub(p_shift); + i = i.wrapping_add(1) + } + return l_carry; + } + /* Computes p_vli = p_vli >> 1. */ + + unsafe fn vli_rshift1(mut p_vli: *mut uint64_t) { + let mut l_end: *mut uint64_t = p_vli; + let mut l_carry: uint64_t = 0 as libc::c_int as uint64_t; + p_vli = p_vli.offset((48 as libc::c_int / 8 as libc::c_int) as isize); + loop { + let fresh0 = p_vli; + p_vli = p_vli.offset(-1); + if !(fresh0 > l_end) { + break; + } + let mut l_temp: uint64_t = *p_vli; + *p_vli = l_temp >> 1 as libc::c_int | l_carry; + l_carry = l_temp << 63 as libc::c_int + } + } + /* Computes p_result = p_left + p_right, returning carry. Can modify in place. */ + + unsafe fn vli_add(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t, mut p_right: *mut uint64_t) -> uint64_t { + let mut l_carry: uint64_t = 0 as libc::c_int as uint64_t; + let mut i: uint = 0; + i = 0 as libc::c_int as uint; + while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { + let mut l_sum: uint64_t = (*p_left.offset(i as isize)) + .wrapping_add(*p_right.offset(i as isize)) + .wrapping_add(l_carry); + if l_sum != *p_left.offset(i as isize) { + l_carry = (l_sum < *p_left.offset(i as isize)) as libc::c_int as uint64_t + } + *p_result.offset(i as isize) = l_sum; + i = i.wrapping_add(1) + } + return l_carry; + } + /* Computes p_result = p_left - p_right, returning borrow. Can modify in place. */ + + unsafe fn vli_sub(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t, mut p_right: *mut uint64_t) -> uint64_t { + let mut l_borrow: uint64_t = 0 as libc::c_int as uint64_t; + let mut i: uint = 0; + i = 0 as libc::c_int as uint; + while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { + let mut l_diff: uint64_t = (*p_left.offset(i as isize)) + .wrapping_sub(*p_right.offset(i as isize)) + .wrapping_sub(l_borrow); + if l_diff != *p_left.offset(i as isize) { + l_borrow = (l_diff > *p_left.offset(i as isize)) as libc::c_int as uint64_t + } + *p_result.offset(i as isize) = l_diff; + i = i.wrapping_add(1) + } + return l_borrow; + } + /* Computes p_result = p_left * p_right. */ + + unsafe fn vli_mult(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t, mut p_right: *mut uint64_t) { + let mut r01: uint128_t = 0 as libc::c_int as uint128_t; + let mut r2: uint64_t = 0 as libc::c_int as uint64_t; + let mut i: uint = 0; + let mut k: uint = 0; + /* Compute each digit of p_result in sequence, maintaining the carries. */ + k = 0 as libc::c_int as uint; + while k < (48 as libc::c_int / 8 as libc::c_int * 2 as libc::c_int - 1 as libc::c_int) as libc::c_uint { + let mut l_min: uint = if k < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { + 0 as libc::c_int as libc::c_uint + } else { + k.wrapping_add(1 as libc::c_int as libc::c_uint) + .wrapping_sub((48 as libc::c_int / 8 as libc::c_int) as libc::c_uint) + }; + i = l_min; + while i <= k && i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { + let mut l_product: uint128_t = + (*p_left.offset(i as isize) as uint128_t).wrapping_mul(*p_right.offset(k.wrapping_sub(i) as isize) as u128); + r01 = (r01 as u128).wrapping_add(l_product) as uint128_t as uint128_t; + r2 = (r2 as libc::c_ulong).wrapping_add((r01 < l_product) as libc::c_int as libc::c_ulong) as uint64_t as uint64_t; + i = i.wrapping_add(1) + } + *p_result.offset(k as isize) = r01 as uint64_t; + r01 = r01 >> 64 as libc::c_int | (r2 as uint128_t) << 64 as libc::c_int; + r2 = 0 as libc::c_int as uint64_t; + k = k.wrapping_add(1) + } + *p_result.offset((48 as libc::c_int / 8 as libc::c_int * 2 as libc::c_int - 1 as libc::c_int) as isize) = r01 as uint64_t; + } + /* Computes p_result = p_left^2. */ + + unsafe fn vli_square(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t) { + let mut r01: uint128_t = 0 as libc::c_int as uint128_t; + let mut r2: uint64_t = 0 as libc::c_int as uint64_t; + let mut i: uint = 0; + let mut k: uint = 0; + k = 0 as libc::c_int as uint; + while k < (48 as libc::c_int / 8 as libc::c_int * 2 as libc::c_int - 1 as libc::c_int) as libc::c_uint { + let mut l_min: uint = if k < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { + 0 as libc::c_int as libc::c_uint + } else { + k.wrapping_add(1 as libc::c_int as libc::c_uint) + .wrapping_sub((48 as libc::c_int / 8 as libc::c_int) as libc::c_uint) + }; + i = l_min; + while i <= k && i <= k.wrapping_sub(i) { + let mut l_product: uint128_t = + (*p_left.offset(i as isize) as uint128_t).wrapping_mul(*p_left.offset(k.wrapping_sub(i) as isize) as u128); + if i < k.wrapping_sub(i) { + r2 = (r2 as u128).wrapping_add(l_product >> 127 as libc::c_int) as uint64_t as uint64_t; + l_product = (l_product as u128).wrapping_mul(2 as libc::c_int as u128) as uint128_t as uint128_t + } + r01 = (r01 as u128).wrapping_add(l_product) as uint128_t as uint128_t; + r2 = (r2 as libc::c_ulong).wrapping_add((r01 < l_product) as libc::c_int as libc::c_ulong) as uint64_t as uint64_t; + i = i.wrapping_add(1) + } + *p_result.offset(k as isize) = r01 as uint64_t; + r01 = r01 >> 64 as libc::c_int | (r2 as uint128_t) << 64 as libc::c_int; + r2 = 0 as libc::c_int as uint64_t; + k = k.wrapping_add(1) + } + *p_result.offset((48 as libc::c_int / 8 as libc::c_int * 2 as libc::c_int - 1 as libc::c_int) as isize) = r01 as uint64_t; + } + /* #if SUPPORTS_INT128 */ + /* SUPPORTS_INT128 */ + /* Computes p_result = (p_left + p_right) % p_mod. + Assumes that p_left < p_mod and p_right < p_mod, p_result != p_mod. */ + + unsafe fn vli_modAdd(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t, mut p_right: *mut uint64_t, mut p_mod: *mut uint64_t) { + let mut l_carry: uint64_t = vli_add(p_result, p_left, p_right); + if l_carry != 0 || vli_cmp(p_result, p_mod) >= 0 as libc::c_int { + /* p_result > p_mod (p_result = p_mod + remainder), so subtract p_mod to get remainder. */ + vli_sub(p_result, p_result, p_mod); + }; + } + /* Computes p_result = (p_left - p_right) % p_mod. + Assumes that p_left < p_mod and p_right < p_mod, p_result != p_mod. */ + + unsafe fn vli_modSub(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t, mut p_right: *mut uint64_t, mut p_mod: *mut uint64_t) { + let mut l_borrow: uint64_t = vli_sub(p_result, p_left, p_right); + if l_borrow != 0 { + /* In this case, p_result == -diff == (max int) - diff. + Since -x % d == d - x, we can get the correct result from p_result + p_mod (with overflow). */ + vli_add(p_result, p_result, p_mod); + }; + } + //#elif ECC_CURVE == secp384r1 + + unsafe fn omega_mult(mut p_result: *mut uint64_t, mut p_right: *mut uint64_t) { + let mut l_tmp: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut l_carry: uint64_t = 0; + let mut l_diff: uint64_t = 0; + /* Multiply by (2^128 + 2^96 - 2^32 + 1). */ + vli_set(p_result, p_right); /* 1 */ + l_carry = vli_lshift(l_tmp.as_mut_ptr(), p_right, 32 as libc::c_int as uint); /* 2^96 + 1 */ + *p_result.offset((1 as libc::c_int + 48 as libc::c_int / 8 as libc::c_int) as isize) = l_carry.wrapping_add(vli_add( + p_result.offset(1 as libc::c_int as isize), + p_result.offset(1 as libc::c_int as isize), + l_tmp.as_mut_ptr(), + )); /* 2^128 + 2^96 + 1 */ + *p_result.offset((2 as libc::c_int + 48 as libc::c_int / 8 as libc::c_int) as isize) = vli_add( + p_result.offset(2 as libc::c_int as isize), + p_result.offset(2 as libc::c_int as isize), + p_right, + ); /* 2^128 + 2^96 - 2^32 + 1 */ + l_carry = (l_carry as libc::c_ulong).wrapping_add(vli_sub(p_result, p_result, l_tmp.as_mut_ptr())) as uint64_t as uint64_t; + l_diff = (*p_result.offset((48 as libc::c_int / 8 as libc::c_int) as isize)).wrapping_sub(l_carry); + if l_diff > *p_result.offset((48 as libc::c_int / 8 as libc::c_int) as isize) { + /* Propagate borrow if necessary. */ + let mut i: uint = 0; + i = (1 as libc::c_int + 48 as libc::c_int / 8 as libc::c_int) as uint; + loop { + let ref mut fresh1 = *p_result.offset(i as isize); + *fresh1 = (*fresh1).wrapping_sub(1); + if *p_result.offset(i as isize) != -(1 as libc::c_int) as uint64_t { + break; + } + i = i.wrapping_add(1) + } + } + *p_result.offset((48 as libc::c_int / 8 as libc::c_int) as isize) = l_diff; + } + /* Computes p_result = p_product % curve_p + see PDF "Comparing Elliptic Curve Cryptography and RSA on 8-bit CPUs" + section "Curve-Specific Optimizations" */ + + unsafe fn vli_mmod_fast(mut p_result: *mut uint64_t, mut p_product: *mut uint64_t) { + let mut l_tmp: [uint64_t; 12] = std::mem::MaybeUninit::uninit().assume_init(); + while vli_isZero(p_product.offset((48 as libc::c_int / 8 as libc::c_int) as isize)) == 0 { + /* While c1 != 0 */ + let mut l_carry: uint64_t = 0 as libc::c_int as uint64_t; /* tmp = w * c1 */ + let mut i: uint = 0; /* p = c0 */ + vli_clear(l_tmp.as_mut_ptr()); + vli_clear(l_tmp.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize)); + omega_mult(l_tmp.as_mut_ptr(), p_product.offset((48 as libc::c_int / 8 as libc::c_int) as isize)); + vli_clear(p_product.offset((48 as libc::c_int / 8 as libc::c_int) as isize)); + /* (c1, c0) = c0 + w * c1 */ + i = 0 as libc::c_int as uint; + while i < (48 as libc::c_int / 8 as libc::c_int + 3 as libc::c_int) as libc::c_uint { + let mut l_sum: uint64_t = (*p_product.offset(i as isize)).wrapping_add(l_tmp[i as usize]).wrapping_add(l_carry); + if l_sum != *p_product.offset(i as isize) { + l_carry = (l_sum < *p_product.offset(i as isize)) as libc::c_int as uint64_t + } + *p_product.offset(i as isize) = l_sum; + i = i.wrapping_add(1) + } + } + while vli_cmp(p_product, curve_p.as_mut_ptr()) > 0 as libc::c_int { + vli_sub(p_product, p_product, curve_p.as_mut_ptr()); + } + vli_set(p_result, p_product); + } + //#endif + /* Computes p_result = (p_left * p_right) % curve_p. */ + + unsafe fn vli_modMult_fast(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t, mut p_right: *mut uint64_t) { + let mut l_product: [uint64_t; 12] = std::mem::MaybeUninit::uninit().assume_init(); + vli_mult(l_product.as_mut_ptr(), p_left, p_right); + vli_mmod_fast(p_result, l_product.as_mut_ptr()); + } + /* Computes p_result = p_left^2 % curve_p. */ + + unsafe fn vli_modSquare_fast(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t) { + let mut l_product: [uint64_t; 12] = std::mem::MaybeUninit::uninit().assume_init(); + vli_square(l_product.as_mut_ptr(), p_left); + vli_mmod_fast(p_result, l_product.as_mut_ptr()); + } + /* Computes p_result = (1 / p_input) % p_mod. All VLIs are the same size. + See "From Euclid's GCD to Montgomery Multiplication to the Great Divide" + https://labs.oracle.com/techrep/2001/smli_tr-2001-95.pdf */ + + unsafe fn vli_modInv(mut p_result: *mut uint64_t, mut p_input: *mut uint64_t, mut p_mod: *mut uint64_t) { + let mut a: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut b: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut u: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut v: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut l_carry: uint64_t = 0; + let mut l_cmpResult: libc::c_int = 0; + if vli_isZero(p_input) != 0 { + vli_clear(p_result); + return; + } + vli_set(a.as_mut_ptr(), p_input); + vli_set(b.as_mut_ptr(), p_mod); + vli_clear(u.as_mut_ptr()); + u[0 as libc::c_int as usize] = 1 as libc::c_int as uint64_t; + vli_clear(v.as_mut_ptr()); + loop { + l_cmpResult = vli_cmp(a.as_mut_ptr(), b.as_mut_ptr()); + if !(l_cmpResult != 0 as libc::c_int) { + break; + } + l_carry = 0 as libc::c_int as uint64_t; + if a[0 as libc::c_int as usize] & 1 as libc::c_int as libc::c_ulong == 0 { + vli_rshift1(a.as_mut_ptr()); + if u[0 as libc::c_int as usize] & 1 as libc::c_int as libc::c_ulong != 0 { + l_carry = vli_add(u.as_mut_ptr(), u.as_mut_ptr(), p_mod) + } + vli_rshift1(u.as_mut_ptr()); + if l_carry != 0 { + u[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] = + (u[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] as libc::c_ulonglong + | 0x8000000000000000 as libc::c_ulonglong) as uint64_t + } + } else if b[0 as libc::c_int as usize] & 1 as libc::c_int as libc::c_ulong == 0 { + vli_rshift1(b.as_mut_ptr()); + if v[0 as libc::c_int as usize] & 1 as libc::c_int as libc::c_ulong != 0 { + l_carry = vli_add(v.as_mut_ptr(), v.as_mut_ptr(), p_mod) + } + vli_rshift1(v.as_mut_ptr()); + if l_carry != 0 { + v[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] = + (v[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] as libc::c_ulonglong + | 0x8000000000000000 as libc::c_ulonglong) as uint64_t + } + } else if l_cmpResult > 0 as libc::c_int { + vli_sub(a.as_mut_ptr(), a.as_mut_ptr(), b.as_mut_ptr()); + vli_rshift1(a.as_mut_ptr()); + if vli_cmp(u.as_mut_ptr(), v.as_mut_ptr()) < 0 as libc::c_int { + vli_add(u.as_mut_ptr(), u.as_mut_ptr(), p_mod); + } + vli_sub(u.as_mut_ptr(), u.as_mut_ptr(), v.as_mut_ptr()); + if u[0 as libc::c_int as usize] & 1 as libc::c_int as libc::c_ulong != 0 { + l_carry = vli_add(u.as_mut_ptr(), u.as_mut_ptr(), p_mod) + } + vli_rshift1(u.as_mut_ptr()); + if l_carry != 0 { + u[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] = + (u[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] as libc::c_ulonglong + | 0x8000000000000000 as libc::c_ulonglong) as uint64_t + } + } else { + vli_sub(b.as_mut_ptr(), b.as_mut_ptr(), a.as_mut_ptr()); + vli_rshift1(b.as_mut_ptr()); + if vli_cmp(v.as_mut_ptr(), u.as_mut_ptr()) < 0 as libc::c_int { + vli_add(v.as_mut_ptr(), v.as_mut_ptr(), p_mod); + } + vli_sub(v.as_mut_ptr(), v.as_mut_ptr(), u.as_mut_ptr()); + if v[0 as libc::c_int as usize] & 1 as libc::c_int as libc::c_ulong != 0 { + l_carry = vli_add(v.as_mut_ptr(), v.as_mut_ptr(), p_mod) + } + vli_rshift1(v.as_mut_ptr()); + if l_carry != 0 { + v[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] = + (v[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] as libc::c_ulonglong + | 0x8000000000000000 as libc::c_ulonglong) as uint64_t + } + } + } + vli_set(p_result, u.as_mut_ptr()); + } + /* ------ Point operations ------ */ + /* Returns 1 if p_point is the point at infinity, 0 otherwise. */ + + unsafe fn EccPoint_isZero(mut p_point: *mut EccPoint) -> libc::c_int { + return (vli_isZero((*p_point).x.as_mut_ptr()) != 0 && vli_isZero((*p_point).y.as_mut_ptr()) != 0) as libc::c_int; + } + /* Point multiplication algorithm using Montgomery's ladder with co-Z coordinates. + From http://eprint.iacr.org/2011/338.pdf + */ + /* Double in place */ + + unsafe fn EccPoint_double_jacobian(mut X1: *mut uint64_t, mut Y1: *mut uint64_t, mut Z1: *mut uint64_t) { + /* t1 = X, t2 = Y, t3 = Z */ + let mut t4: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); /* t4 = y1^2 */ + let mut t5: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); /* t5 = x1*y1^2 = A */ + if vli_isZero(Z1) != 0 { + return; + } /* t4 = y1^4 */ + vli_modSquare_fast(t4.as_mut_ptr(), Y1); /* t2 = y1*z1 = z3 */ + vli_modMult_fast(t5.as_mut_ptr(), X1, t4.as_mut_ptr()); /* t3 = z1^2 */ + vli_modSquare_fast(t4.as_mut_ptr(), t4.as_mut_ptr()); /* t1 = x1 + z1^2 */ + vli_modMult_fast(Y1, Y1, Z1); /* t3 = 2*z1^2 */ + vli_modSquare_fast(Z1, Z1); /* t3 = x1 - z1^2 */ + vli_modAdd(X1, X1, Z1, curve_p.as_mut_ptr()); /* t1 = x1^2 - z1^4 */ + vli_modAdd(Z1, Z1, Z1, curve_p.as_mut_ptr()); /* t3 = 2*(x1^2 - z1^4) */ + vli_modSub(Z1, X1, Z1, curve_p.as_mut_ptr()); /* t1 = 3*(x1^2 - z1^4) */ + vli_modMult_fast(X1, X1, Z1); + vli_modAdd(Z1, X1, X1, curve_p.as_mut_ptr()); + vli_modAdd(X1, X1, Z1, curve_p.as_mut_ptr()); + if vli_testBit(X1, 0 as libc::c_int as uint) != 0 { + let mut l_carry: uint64_t = vli_add(X1, X1, curve_p.as_mut_ptr()); + vli_rshift1(X1); + let ref mut fresh2 = *X1.offset((48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as isize); + *fresh2 |= l_carry << 63 as libc::c_int + } else { + vli_rshift1(X1); + } + /* t1 = 3/2*(x1^2 - z1^4) = B */ + vli_modSquare_fast(Z1, X1); /* t3 = B^2 */ + vli_modSub(Z1, Z1, t5.as_mut_ptr(), curve_p.as_mut_ptr()); /* t3 = B^2 - A */ + vli_modSub(Z1, Z1, t5.as_mut_ptr(), curve_p.as_mut_ptr()); /* t3 = B^2 - 2A = x3 */ + vli_modSub(t5.as_mut_ptr(), t5.as_mut_ptr(), Z1, curve_p.as_mut_ptr()); /* t5 = A - x3 */ + vli_modMult_fast(X1, X1, t5.as_mut_ptr()); /* t1 = B * (A - x3) */ + vli_modSub(t4.as_mut_ptr(), X1, t4.as_mut_ptr(), curve_p.as_mut_ptr()); /* t4 = B * (A - x3) - y1^4 = y3 */ + vli_set(X1, Z1); + vli_set(Z1, Y1); + vli_set(Y1, t4.as_mut_ptr()); + } + /* Modify (x1, y1) => (x1 * z^2, y1 * z^3) */ + + unsafe fn apply_z(mut X1: *mut uint64_t, mut Y1: *mut uint64_t, mut Z: *mut uint64_t) { + let mut t1: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); /* z^2 */ + vli_modSquare_fast(t1.as_mut_ptr(), Z); /* x1 * z^2 */ + vli_modMult_fast(X1, X1, t1.as_mut_ptr()); /* z^3 */ + vli_modMult_fast(t1.as_mut_ptr(), t1.as_mut_ptr(), Z); + vli_modMult_fast(Y1, Y1, t1.as_mut_ptr()); + /* y1 * z^3 */ + } + /* P = (x1, y1) => 2P, (x2, y2) => P' */ + + unsafe fn XYcZ_initial_double( + mut X1: *mut uint64_t, + mut Y1: *mut uint64_t, + mut X2: *mut uint64_t, + mut Y2: *mut uint64_t, + mut p_initialZ: *mut uint64_t, + ) { + let mut z: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + vli_set(X2, X1); + vli_set(Y2, Y1); + vli_clear(z.as_mut_ptr()); + z[0 as libc::c_int as usize] = 1 as libc::c_int as uint64_t; + if !p_initialZ.is_null() { + vli_set(z.as_mut_ptr(), p_initialZ); + } + apply_z(X1, Y1, z.as_mut_ptr()); + EccPoint_double_jacobian(X1, Y1, z.as_mut_ptr()); + apply_z(X2, Y2, z.as_mut_ptr()); + } + /* Input P = (x1, y1, Z), Q = (x2, y2, Z) + Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3) + or P => P', Q => P + Q + */ + + unsafe fn XYcZ_add(mut X1: *mut uint64_t, mut Y1: *mut uint64_t, mut X2: *mut uint64_t, mut Y2: *mut uint64_t) { + /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ + let mut t5: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); /* t5 = x2 - x1 */ + vli_modSub(t5.as_mut_ptr(), X2, X1, curve_p.as_mut_ptr()); /* t5 = (x2 - x1)^2 = A */ + vli_modSquare_fast(t5.as_mut_ptr(), t5.as_mut_ptr()); /* t1 = x1*A = B */ + vli_modMult_fast(X1, X1, t5.as_mut_ptr()); /* t3 = x2*A = C */ + vli_modMult_fast(X2, X2, t5.as_mut_ptr()); /* t4 = y2 - y1 */ + vli_modSub(Y2, Y2, Y1, curve_p.as_mut_ptr()); /* t5 = (y2 - y1)^2 = D */ + vli_modSquare_fast(t5.as_mut_ptr(), Y2); /* t5 = D - B */ + vli_modSub(t5.as_mut_ptr(), t5.as_mut_ptr(), X1, curve_p.as_mut_ptr()); /* t5 = D - B - C = x3 */ + vli_modSub(t5.as_mut_ptr(), t5.as_mut_ptr(), X2, curve_p.as_mut_ptr()); /* t3 = C - B */ + vli_modSub(X2, X2, X1, curve_p.as_mut_ptr()); /* t2 = y1*(C - B) */ + vli_modMult_fast(Y1, Y1, X2); /* t3 = B - x3 */ + vli_modSub(X2, X1, t5.as_mut_ptr(), curve_p.as_mut_ptr()); /* t4 = (y2 - y1)*(B - x3) */ + vli_modMult_fast(Y2, Y2, X2); /* t4 = y3 */ + vli_modSub(Y2, Y2, Y1, curve_p.as_mut_ptr()); + vli_set(X2, t5.as_mut_ptr()); + } + /* Input P = (x1, y1, Z), Q = (x2, y2, Z) + Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3) + or P => P - Q, Q => P + Q + */ + + unsafe fn XYcZ_addC(mut X1: *mut uint64_t, mut Y1: *mut uint64_t, mut X2: *mut uint64_t, mut Y2: *mut uint64_t) { + /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ + let mut t5: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); /* t5 = x2 - x1 */ + let mut t6: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); /* t5 = (x2 - x1)^2 = A */ + let mut t7: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); /* t1 = x1*A = B */ + vli_modSub(t5.as_mut_ptr(), X2, X1, curve_p.as_mut_ptr()); /* t3 = x2*A = C */ + vli_modSquare_fast(t5.as_mut_ptr(), t5.as_mut_ptr()); /* t4 = y2 + y1 */ + vli_modMult_fast(X1, X1, t5.as_mut_ptr()); /* t4 = y2 - y1 */ + vli_modMult_fast(X2, X2, t5.as_mut_ptr()); /* t6 = C - B */ + vli_modAdd(t5.as_mut_ptr(), Y2, Y1, curve_p.as_mut_ptr()); /* t2 = y1 * (C - B) */ + vli_modSub(Y2, Y2, Y1, curve_p.as_mut_ptr()); /* t6 = B + C */ + vli_modSub(t6.as_mut_ptr(), X2, X1, curve_p.as_mut_ptr()); /* t3 = (y2 - y1)^2 */ + vli_modMult_fast(Y1, Y1, t6.as_mut_ptr()); /* t3 = x3 */ + vli_modAdd(t6.as_mut_ptr(), X1, X2, curve_p.as_mut_ptr()); /* t7 = B - x3 */ + vli_modSquare_fast(X2, Y2); /* t4 = (y2 - y1)*(B - x3) */ + vli_modSub(X2, X2, t6.as_mut_ptr(), curve_p.as_mut_ptr()); /* t4 = y3 */ + vli_modSub(t7.as_mut_ptr(), X1, X2, curve_p.as_mut_ptr()); /* t7 = (y2 + y1)^2 = F */ + vli_modMult_fast(Y2, Y2, t7.as_mut_ptr()); /* t7 = x3' */ + vli_modSub(Y2, Y2, Y1, curve_p.as_mut_ptr()); /* t6 = x3' - B */ + vli_modSquare_fast(t7.as_mut_ptr(), t5.as_mut_ptr()); /* t6 = (y2 + y1)*(x3' - B) */ + vli_modSub(t7.as_mut_ptr(), t7.as_mut_ptr(), t6.as_mut_ptr(), curve_p.as_mut_ptr()); /* t2 = y3' */ + vli_modSub(t6.as_mut_ptr(), t7.as_mut_ptr(), X1, curve_p.as_mut_ptr()); + vli_modMult_fast(t6.as_mut_ptr(), t6.as_mut_ptr(), t5.as_mut_ptr()); + vli_modSub(Y1, t6.as_mut_ptr(), Y1, curve_p.as_mut_ptr()); + vli_set(X1, t7.as_mut_ptr()); + } + + unsafe fn EccPoint_mult(mut p_result: *mut EccPoint, mut p_point: *mut EccPoint, mut p_scalar: *mut uint64_t, mut p_initialZ: *mut uint64_t) { + /* R0 and R1 */ + let mut Rx: [[uint64_t; 6]; 2] = std::mem::MaybeUninit::uninit().assume_init(); + let mut Ry: [[uint64_t; 6]; 2] = std::mem::MaybeUninit::uninit().assume_init(); + let mut z: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut i: libc::c_int = 0; + let mut nb: libc::c_int = 0; + vli_set(Rx[1 as libc::c_int as usize].as_mut_ptr(), (*p_point).x.as_mut_ptr()); + vli_set(Ry[1 as libc::c_int as usize].as_mut_ptr(), (*p_point).y.as_mut_ptr()); + XYcZ_initial_double( + Rx[1 as libc::c_int as usize].as_mut_ptr(), + Ry[1 as libc::c_int as usize].as_mut_ptr(), + Rx[0 as libc::c_int as usize].as_mut_ptr(), + Ry[0 as libc::c_int as usize].as_mut_ptr(), + p_initialZ, + ); + i = vli_numBits(p_scalar).wrapping_sub(2 as libc::c_int as libc::c_uint) as libc::c_int; + while i > 0 as libc::c_int { + nb = (vli_testBit(p_scalar, i as uint) == 0) as libc::c_int; + XYcZ_addC( + Rx[(1 as libc::c_int - nb) as usize].as_mut_ptr(), + Ry[(1 as libc::c_int - nb) as usize].as_mut_ptr(), + Rx[nb as usize].as_mut_ptr(), + Ry[nb as usize].as_mut_ptr(), + ); + XYcZ_add( + Rx[nb as usize].as_mut_ptr(), + Ry[nb as usize].as_mut_ptr(), + Rx[(1 as libc::c_int - nb) as usize].as_mut_ptr(), + Ry[(1 as libc::c_int - nb) as usize].as_mut_ptr(), + ); + i -= 1 + } + nb = (vli_testBit(p_scalar, 0 as libc::c_int as uint) == 0) as libc::c_int; + XYcZ_addC( + Rx[(1 as libc::c_int - nb) as usize].as_mut_ptr(), + Ry[(1 as libc::c_int - nb) as usize].as_mut_ptr(), + Rx[nb as usize].as_mut_ptr(), + Ry[nb as usize].as_mut_ptr(), + ); + /* Find final 1/Z value. */ + vli_modSub( + z.as_mut_ptr(), + Rx[1 as libc::c_int as usize].as_mut_ptr(), + Rx[0 as libc::c_int as usize].as_mut_ptr(), + curve_p.as_mut_ptr(), + ); /* X1 - X0 */ + vli_modMult_fast(z.as_mut_ptr(), z.as_mut_ptr(), Ry[(1 as libc::c_int - nb) as usize].as_mut_ptr()); /* Yb * (X1 - X0) */ + vli_modMult_fast(z.as_mut_ptr(), z.as_mut_ptr(), (*p_point).x.as_mut_ptr()); /* xP * Yb * (X1 - X0) */ + vli_modInv(z.as_mut_ptr(), z.as_mut_ptr(), curve_p.as_mut_ptr()); /* 1 / (xP * Yb * (X1 - X0)) */ + vli_modMult_fast(z.as_mut_ptr(), z.as_mut_ptr(), (*p_point).y.as_mut_ptr()); /* yP / (xP * Yb * (X1 - X0)) */ + vli_modMult_fast(z.as_mut_ptr(), z.as_mut_ptr(), Rx[(1 as libc::c_int - nb) as usize].as_mut_ptr()); /* Xb * yP / (xP * Yb * (X1 - X0)) */ + /* End 1/Z calculation */ + XYcZ_add( + Rx[nb as usize].as_mut_ptr(), + Ry[nb as usize].as_mut_ptr(), + Rx[(1 as libc::c_int - nb) as usize].as_mut_ptr(), + Ry[(1 as libc::c_int - nb) as usize].as_mut_ptr(), + ); + apply_z( + Rx[0 as libc::c_int as usize].as_mut_ptr(), + Ry[0 as libc::c_int as usize].as_mut_ptr(), + z.as_mut_ptr(), + ); + vli_set((*p_result).x.as_mut_ptr(), Rx[0 as libc::c_int as usize].as_mut_ptr()); + vli_set((*p_result).y.as_mut_ptr(), Ry[0 as libc::c_int as usize].as_mut_ptr()); + } + + unsafe fn ecc_bytes2native(mut p_native: *mut uint64_t, mut p_bytes: *const uint8_t) { + let mut i: libc::c_uint = 0; + i = 0 as libc::c_int as libc::c_uint; + while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { + let mut p_digit: *const uint8_t = p_bytes.offset( + (8 as libc::c_int as libc::c_uint) + .wrapping_mul(((48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as libc::c_uint).wrapping_sub(i)) + as isize, + ); + *p_native.offset(i as isize) = (*p_digit.offset(0 as libc::c_int as isize) as uint64_t) << 56 as libc::c_int + | (*p_digit.offset(1 as libc::c_int as isize) as uint64_t) << 48 as libc::c_int + | (*p_digit.offset(2 as libc::c_int as isize) as uint64_t) << 40 as libc::c_int + | (*p_digit.offset(3 as libc::c_int as isize) as uint64_t) << 32 as libc::c_int + | (*p_digit.offset(4 as libc::c_int as isize) as uint64_t) << 24 as libc::c_int + | (*p_digit.offset(5 as libc::c_int as isize) as uint64_t) << 16 as libc::c_int + | (*p_digit.offset(6 as libc::c_int as isize) as uint64_t) << 8 as libc::c_int + | *p_digit.offset(7 as libc::c_int as isize) as uint64_t; + i = i.wrapping_add(1) + } + } + + unsafe fn ecc_native2bytes(mut p_bytes: *mut uint8_t, mut p_native: *const uint64_t) { + let mut i: libc::c_uint = 0; + i = 0 as libc::c_int as libc::c_uint; + while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { + let mut p_digit: *mut uint8_t = p_bytes.offset( + (8 as libc::c_int as libc::c_uint) + .wrapping_mul(((48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as libc::c_uint).wrapping_sub(i)) + as isize, + ); + *p_digit.offset(0 as libc::c_int as isize) = (*p_native.offset(i as isize) >> 56 as libc::c_int) as uint8_t; + *p_digit.offset(1 as libc::c_int as isize) = (*p_native.offset(i as isize) >> 48 as libc::c_int) as uint8_t; + *p_digit.offset(2 as libc::c_int as isize) = (*p_native.offset(i as isize) >> 40 as libc::c_int) as uint8_t; + *p_digit.offset(3 as libc::c_int as isize) = (*p_native.offset(i as isize) >> 32 as libc::c_int) as uint8_t; + *p_digit.offset(4 as libc::c_int as isize) = (*p_native.offset(i as isize) >> 24 as libc::c_int) as uint8_t; + *p_digit.offset(5 as libc::c_int as isize) = (*p_native.offset(i as isize) >> 16 as libc::c_int) as uint8_t; + *p_digit.offset(6 as libc::c_int as isize) = (*p_native.offset(i as isize) >> 8 as libc::c_int) as uint8_t; + *p_digit.offset(7 as libc::c_int as isize) = *p_native.offset(i as isize) as uint8_t; + i = i.wrapping_add(1) + } + } + /* Compute a = sqrt(a) (mod curve_p). */ + + unsafe fn mod_sqrt(mut a: *mut uint64_t) { + let mut i: libc::c_uint = 0; + let mut p1: [uint64_t; 6] = [1 as libc::c_int as uint64_t, 0, 0, 0, 0, 0]; + let mut l_result: [uint64_t; 6] = [1 as libc::c_int as uint64_t, 0, 0, 0, 0, 0]; + /* Since curve_p == 3 (mod 4) for all supported curves, we can + compute sqrt(a) = a^((curve_p + 1) / 4) (mod curve_p). */ + vli_add(p1.as_mut_ptr(), curve_p.as_mut_ptr(), p1.as_mut_ptr()); /* p1 = curve_p + 1 */ + i = vli_numBits(p1.as_mut_ptr()).wrapping_sub(1 as libc::c_int as libc::c_uint); /* -a = 3 */ + while i > 1 as libc::c_int as libc::c_uint { + vli_modSquare_fast(l_result.as_mut_ptr(), l_result.as_mut_ptr()); /* y = x^2 */ + if vli_testBit(p1.as_mut_ptr(), i) != 0 { + vli_modMult_fast(l_result.as_mut_ptr(), l_result.as_mut_ptr(), a); + /* y = x^2 - 3 */ + } /* y = x^3 - 3x */ + i = i.wrapping_sub(1) + } /* y = x^3 - 3x + b */ + vli_set(a, l_result.as_mut_ptr()); + } + + unsafe fn ecc_point_decompress(mut p_point: *mut EccPoint, mut p_compressed: *const uint8_t) { + let mut _3: [uint64_t; 6] = [3 as libc::c_int as uint64_t, 0, 0, 0, 0, 0]; + ecc_bytes2native((*p_point).x.as_mut_ptr(), p_compressed.offset(1 as libc::c_int as isize)); + vli_modSquare_fast((*p_point).y.as_mut_ptr(), (*p_point).x.as_mut_ptr()); + vli_modSub( + (*p_point).y.as_mut_ptr(), + (*p_point).y.as_mut_ptr(), + _3.as_mut_ptr(), + curve_p.as_mut_ptr(), + ); + vli_modMult_fast((*p_point).y.as_mut_ptr(), (*p_point).y.as_mut_ptr(), (*p_point).x.as_mut_ptr()); + vli_modAdd( + (*p_point).y.as_mut_ptr(), + (*p_point).y.as_mut_ptr(), + curve_b.as_mut_ptr(), + curve_p.as_mut_ptr(), + ); + mod_sqrt((*p_point).y.as_mut_ptr()); + if (*p_point).y[0 as libc::c_int as usize] & 0x1 as libc::c_int as libc::c_ulong + != (*p_compressed.offset(0 as libc::c_int as isize) as libc::c_int & 0x1 as libc::c_int) as libc::c_ulong + { + vli_sub((*p_point).y.as_mut_ptr(), curve_p.as_mut_ptr(), (*p_point).y.as_mut_ptr()); + }; + } + pub unsafe fn ecc_make_key(mut p_publicKey: *mut uint8_t, mut p_privateKey: *mut uint8_t) -> libc::c_int { + let mut l_private: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut l_public: EccPoint = std::mem::MaybeUninit::uninit().assume_init(); + let mut l_tries: libc::c_uint = 0 as libc::c_int as libc::c_uint; + loop { + if getRandomNumber(l_private.as_mut_ptr()) == 0 || { + let fresh3 = l_tries; + l_tries = l_tries.wrapping_add(1); + (fresh3) >= 1024 as libc::c_int as libc::c_uint + } { + return 0 as libc::c_int; + } + if !(vli_isZero(l_private.as_mut_ptr()) != 0) { + /* Make sure the private key is in the range [1, n-1]. + For the supported curves, n is always large enough that we only need to subtract once at most. */ + if vli_cmp(curve_n.as_mut_ptr(), l_private.as_mut_ptr()) != 1 as libc::c_int { + vli_sub(l_private.as_mut_ptr(), l_private.as_mut_ptr(), curve_n.as_mut_ptr()); + } + EccPoint_mult(&mut l_public, &mut curve_G, l_private.as_mut_ptr(), 0 as *mut uint64_t); + } + if !(EccPoint_isZero(&mut l_public) != 0) { + break; + } + } + ecc_native2bytes(p_privateKey, l_private.as_mut_ptr() as *const uint64_t); + ecc_native2bytes(p_publicKey.offset(1 as libc::c_int as isize), l_public.x.as_mut_ptr() as *const uint64_t); + *p_publicKey.offset(0 as libc::c_int as isize) = + (2 as libc::c_int as libc::c_ulong).wrapping_add(l_public.y[0 as libc::c_int as usize] & 0x1 as libc::c_int as libc::c_ulong) as uint8_t; + return 1 as libc::c_int; + } + pub unsafe fn ecdh_shared_secret(mut p_publicKey: *const uint8_t, mut p_privateKey: *const uint8_t, mut p_secret: *mut uint8_t) -> libc::c_int { + let mut l_public: EccPoint = std::mem::MaybeUninit::uninit().assume_init(); + let mut l_private: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut l_random: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + if getRandomNumber(l_random.as_mut_ptr()) == 0 { + return 0 as libc::c_int; + } + ecc_point_decompress(&mut l_public, p_publicKey); + ecc_bytes2native(l_private.as_mut_ptr(), p_privateKey); + let mut l_product: EccPoint = EccPoint { x: [0; 6], y: [0; 6] }; + EccPoint_mult(&mut l_product, &mut l_public, l_private.as_mut_ptr(), l_random.as_mut_ptr()); + ecc_native2bytes(p_secret, l_product.x.as_mut_ptr() as *const uint64_t); + return (EccPoint_isZero(&mut l_product) == 0) as libc::c_int; + } + /* -------- ECDSA code -------- */ + /* Computes p_result = (p_left * p_right) % p_mod. */ + + unsafe fn vli_modMult(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t, mut p_right: *mut uint64_t, mut p_mod: *mut uint64_t) { + let mut l_product: [uint64_t; 12] = std::mem::MaybeUninit::uninit().assume_init(); + let mut l_modMultiple: [uint64_t; 12] = std::mem::MaybeUninit::uninit().assume_init(); + let mut l_digitShift: uint = 0; + let mut l_bitShift: uint = 0; + let mut l_productBits: uint = 0; + let mut l_modBits: uint = vli_numBits(p_mod); + vli_mult(l_product.as_mut_ptr(), p_left, p_right); + l_productBits = vli_numBits(l_product.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize)); + if l_productBits != 0 { + l_productBits = (l_productBits as libc::c_uint).wrapping_add((48 as libc::c_int / 8 as libc::c_int * 64 as libc::c_int) as libc::c_uint) + as uint as uint + } else { + l_productBits = vli_numBits(l_product.as_mut_ptr()) + } + if l_productBits < l_modBits { + /* l_product < p_mod. */ + vli_set(p_result, l_product.as_mut_ptr()); + return; + } + /* Shift p_mod by (l_leftBits - l_modBits). This multiplies p_mod by the largest + power of two possible while still resulting in a number less than p_left. */ + vli_clear(l_modMultiple.as_mut_ptr()); + vli_clear(l_modMultiple.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize)); + l_digitShift = l_productBits.wrapping_sub(l_modBits).wrapping_div(64 as libc::c_int as libc::c_uint); + l_bitShift = l_productBits.wrapping_sub(l_modBits).wrapping_rem(64 as libc::c_int as libc::c_uint); + if l_bitShift != 0 { + l_modMultiple[l_digitShift.wrapping_add((48 as libc::c_int / 8 as libc::c_int) as libc::c_uint) as usize] = + vli_lshift(l_modMultiple.as_mut_ptr().offset(l_digitShift as isize), p_mod, l_bitShift) + } else { + vli_set(l_modMultiple.as_mut_ptr().offset(l_digitShift as isize), p_mod); + } + /* Subtract all multiples of p_mod to get the remainder. */ + vli_clear(p_result); /* Use p_result as a temp var to store 1 (for subtraction) */ + *p_result.offset(0 as libc::c_int as isize) = 1 as libc::c_int as uint64_t; + while l_productBits > (48 as libc::c_int / 8 as libc::c_int * 64 as libc::c_int) as libc::c_uint + || vli_cmp(l_modMultiple.as_mut_ptr(), p_mod) >= 0 as libc::c_int + { + let mut l_cmp: libc::c_int = vli_cmp( + l_modMultiple.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize), + l_product.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize), + ); + if l_cmp < 0 as libc::c_int + || l_cmp == 0 as libc::c_int && vli_cmp(l_modMultiple.as_mut_ptr(), l_product.as_mut_ptr()) <= 0 as libc::c_int + { + if vli_sub(l_product.as_mut_ptr(), l_product.as_mut_ptr(), l_modMultiple.as_mut_ptr()) != 0 { + /* borrow */ + vli_sub( + l_product.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize), + l_product.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize), + p_result, + ); + } + vli_sub( + l_product.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize), + l_product.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize), + l_modMultiple.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize), + ); + } + let mut l_carry: uint64_t = + (l_modMultiple[(48 as libc::c_int / 8 as libc::c_int) as usize] & 0x1 as libc::c_int as libc::c_ulong) << 63 as libc::c_int; + vli_rshift1(l_modMultiple.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize)); + vli_rshift1(l_modMultiple.as_mut_ptr()); + l_modMultiple[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] |= l_carry; + l_productBits = l_productBits.wrapping_sub(1) + } + vli_set(p_result, l_product.as_mut_ptr()); + } + + unsafe fn umax(mut a: uint, mut b: uint) -> uint { + a.max(b) + } + pub unsafe fn ecdsa_sign(mut p_privateKey: *const uint8_t, mut p_hash: *const uint8_t, mut p_signature: *mut uint8_t) -> libc::c_int { + let mut k: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut l_tmp: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut l_s: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut p: EccPoint = std::mem::MaybeUninit::uninit().assume_init(); + let mut l_tries: libc::c_uint = 0 as libc::c_int as libc::c_uint; + loop { + if getRandomNumber(k.as_mut_ptr()) == 0 || { + let fresh4 = l_tries; + l_tries = l_tries.wrapping_add(1); + (fresh4) >= 1024 as libc::c_int as libc::c_uint + } { + return 0 as libc::c_int; + } + if !(vli_isZero(k.as_mut_ptr()) != 0) { + if vli_cmp(curve_n.as_mut_ptr(), k.as_mut_ptr()) != 1 as libc::c_int { + vli_sub(k.as_mut_ptr(), k.as_mut_ptr(), curve_n.as_mut_ptr()); + } + /* tmp = k * G */ + EccPoint_mult(&mut p, &mut curve_G, k.as_mut_ptr(), 0 as *mut uint64_t); + /* r = x1 (mod n) */ + if vli_cmp(curve_n.as_mut_ptr(), p.x.as_mut_ptr()) != 1 as libc::c_int { + vli_sub(p.x.as_mut_ptr(), p.x.as_mut_ptr(), curve_n.as_mut_ptr()); + /* s = r*d */ + } + } /* s = e + r*d */ + if !(vli_isZero(p.x.as_mut_ptr()) != 0) { + break; /* k = 1 / k */ + } + } /* s = (e + r*d) / k */ + ecc_native2bytes(p_signature, p.x.as_mut_ptr() as *const uint64_t); + ecc_bytes2native(l_tmp.as_mut_ptr(), p_privateKey); + vli_modMult(l_s.as_mut_ptr(), p.x.as_mut_ptr(), l_tmp.as_mut_ptr(), curve_n.as_mut_ptr()); + ecc_bytes2native(l_tmp.as_mut_ptr(), p_hash); + vli_modAdd(l_s.as_mut_ptr(), l_tmp.as_mut_ptr(), l_s.as_mut_ptr(), curve_n.as_mut_ptr()); + vli_modInv(k.as_mut_ptr(), k.as_mut_ptr(), curve_n.as_mut_ptr()); + vli_modMult(l_s.as_mut_ptr(), l_s.as_mut_ptr(), k.as_mut_ptr(), curve_n.as_mut_ptr()); + ecc_native2bytes(p_signature.offset(48 as libc::c_int as isize), l_s.as_mut_ptr() as *const uint64_t); + return 1 as libc::c_int; + } + pub unsafe fn ecdsa_verify(mut p_publicKey: *const uint8_t, mut p_hash: *const uint8_t, mut p_signature: *const uint8_t) -> libc::c_int { + let mut u1: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut u2: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut z: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut l_public: EccPoint = std::mem::MaybeUninit::uninit().assume_init(); + let mut l_sum: EccPoint = std::mem::MaybeUninit::uninit().assume_init(); + let mut rx: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut ry: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut tx: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut ty: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut tz: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut l_r: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + let mut l_s: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); + ecc_point_decompress(&mut l_public, p_publicKey); + ecc_bytes2native(l_r.as_mut_ptr(), p_signature); + ecc_bytes2native(l_s.as_mut_ptr(), p_signature.offset(48 as libc::c_int as isize)); + if vli_isZero(l_r.as_mut_ptr()) != 0 || vli_isZero(l_s.as_mut_ptr()) != 0 { + /* r, s must not be 0. */ + return 0 as libc::c_int; + } + if vli_cmp(curve_n.as_mut_ptr(), l_r.as_mut_ptr()) != 1 as libc::c_int || vli_cmp(curve_n.as_mut_ptr(), l_s.as_mut_ptr()) != 1 as libc::c_int + { + /* r, s must be < n. */ + return 0 as libc::c_int; + } + /* Calculate u1 and u2. */ + vli_modInv(z.as_mut_ptr(), l_s.as_mut_ptr(), curve_n.as_mut_ptr()); /* Z = s^-1 */ + ecc_bytes2native(u1.as_mut_ptr(), p_hash); /* u1 = e/s */ + vli_modMult(u1.as_mut_ptr(), u1.as_mut_ptr(), z.as_mut_ptr(), curve_n.as_mut_ptr()); /* u2 = r/s */ + vli_modMult(u2.as_mut_ptr(), l_r.as_mut_ptr(), z.as_mut_ptr(), curve_n.as_mut_ptr()); + /* Calculate l_sum = G + Q. */ + vli_set(l_sum.x.as_mut_ptr(), l_public.x.as_mut_ptr()); /* Z = x2 - x1 */ + vli_set(l_sum.y.as_mut_ptr(), l_public.y.as_mut_ptr()); /* Z = 1/Z */ + vli_set(tx.as_mut_ptr(), curve_G.x.as_mut_ptr()); + vli_set(ty.as_mut_ptr(), curve_G.y.as_mut_ptr()); + vli_modSub(z.as_mut_ptr(), l_sum.x.as_mut_ptr(), tx.as_mut_ptr(), curve_p.as_mut_ptr()); + XYcZ_add(tx.as_mut_ptr(), ty.as_mut_ptr(), l_sum.x.as_mut_ptr(), l_sum.y.as_mut_ptr()); + vli_modInv(z.as_mut_ptr(), z.as_mut_ptr(), curve_p.as_mut_ptr()); + apply_z(l_sum.x.as_mut_ptr(), l_sum.y.as_mut_ptr(), z.as_mut_ptr()); + /* Use Shamir's trick to calculate u1*G + u2*Q */ + let mut l_points: [*mut EccPoint; 4] = [0 as *mut EccPoint, &mut curve_G, &mut l_public, &mut l_sum]; /* Z = x2 - x1 */ + let mut l_numBits: uint = umax(vli_numBits(u1.as_mut_ptr()), vli_numBits(u2.as_mut_ptr())); /* Z = 1/Z */ + let mut l_point: *mut EccPoint = l_points[((vli_testBit(u1.as_mut_ptr(), l_numBits.wrapping_sub(1 as libc::c_int as libc::c_uint)) != 0) + as libc::c_int + | ((vli_testBit(u2.as_mut_ptr(), l_numBits.wrapping_sub(1 as libc::c_int as libc::c_uint)) != 0) as libc::c_int) << 1 as libc::c_int) + as usize]; + vli_set(rx.as_mut_ptr(), (*l_point).x.as_mut_ptr()); + vli_set(ry.as_mut_ptr(), (*l_point).y.as_mut_ptr()); + vli_clear(z.as_mut_ptr()); + z[0 as libc::c_int as usize] = 1 as libc::c_int as uint64_t; + let mut i: libc::c_int = 0; + i = l_numBits.wrapping_sub(2 as libc::c_int as libc::c_uint) as libc::c_int; + while i >= 0 as libc::c_int { + EccPoint_double_jacobian(rx.as_mut_ptr(), ry.as_mut_ptr(), z.as_mut_ptr()); + let mut l_index: libc::c_int = (vli_testBit(u1.as_mut_ptr(), i as uint) != 0) as libc::c_int + | ((vli_testBit(u2.as_mut_ptr(), i as uint) != 0) as libc::c_int) << 1 as libc::c_int; + let mut l_point_0: *mut EccPoint = l_points[l_index as usize]; + if !l_point_0.is_null() { + vli_set(tx.as_mut_ptr(), (*l_point_0).x.as_mut_ptr()); + vli_set(ty.as_mut_ptr(), (*l_point_0).y.as_mut_ptr()); + apply_z(tx.as_mut_ptr(), ty.as_mut_ptr(), z.as_mut_ptr()); + vli_modSub(tz.as_mut_ptr(), rx.as_mut_ptr(), tx.as_mut_ptr(), curve_p.as_mut_ptr()); + XYcZ_add(tx.as_mut_ptr(), ty.as_mut_ptr(), rx.as_mut_ptr(), ry.as_mut_ptr()); + vli_modMult_fast(z.as_mut_ptr(), z.as_mut_ptr(), tz.as_mut_ptr()); + } + i -= 1 + } + vli_modInv(z.as_mut_ptr(), z.as_mut_ptr(), curve_p.as_mut_ptr()); + apply_z(rx.as_mut_ptr(), ry.as_mut_ptr(), z.as_mut_ptr()); + /* v = x1 (mod n) */ + if vli_cmp(curve_n.as_mut_ptr(), rx.as_mut_ptr()) != 1 as libc::c_int { + vli_sub(rx.as_mut_ptr(), rx.as_mut_ptr(), curve_n.as_mut_ptr()); + } + /* Accept only if v == r. */ + return (vli_cmp(rx.as_mut_ptr(), l_r.as_mut_ptr()) == 0 as libc::c_int) as libc::c_int; + } + + #[derive(Clone, PartialEq, Eq)] + pub struct P384PublicKey([u8; 49]); + + impl P384PublicKey { + pub fn from_bytes(b: &[u8]) -> Option { + if b.len() == 49 { + Some(Self(b.try_into().unwrap())) + } else { + None + } + } + + pub fn verify(&self, msg: &[u8], signature: &[u8]) -> bool { + if signature.len() == 96 { + unsafe { + return ecdsa_verify(self.0.as_ptr().cast(), SHA384::hash(msg).as_ptr().cast(), signature.as_ptr().cast()) != 0; + } + } + return false; + } + + pub fn as_bytes(&self) -> &[u8; 49] { + &self.0 + } + } + + #[derive(Clone, PartialEq, Eq)] + pub struct P384KeyPair(P384PublicKey, Secret<48>); + + impl P384KeyPair { + pub fn generate() -> P384KeyPair { + let mut kp = Self(P384PublicKey([0_u8; 49]), Secret::new()); + unsafe { ecc_make_key(kp.0 .0.as_mut_ptr().cast(), kp.1 .0.as_mut_ptr().cast()) }; + kp + } + + pub fn from_bytes(public_bytes: &[u8], secret_bytes: &[u8]) -> Option { + if public_bytes.len() == 49 && secret_bytes.len() == 48 { + Some(Self( + P384PublicKey(public_bytes.try_into().unwrap()), + Secret(secret_bytes.try_into().unwrap()), + )) + } else { + None + } + } + + pub fn public_key(&self) -> &P384PublicKey { + &self.0 + } + + pub fn public_key_bytes(&self) -> &[u8; 49] { + &self.0 .0 + } + + pub fn secret_key_bytes(&self) -> Secret<48> { + self.1.clone() + } + + pub fn sign(&self, msg: &[u8]) -> [u8; 96] { + let msg = SHA384::hash(msg); + let mut sig = [0_u8; 96]; + unsafe { + ecdsa_sign(self.1 .0.as_ptr().cast(), msg.as_ptr().cast(), sig.as_mut_ptr().cast()); + } + sig + } + + pub fn agree(&self, other_public: &P384PublicKey) -> Option> { + let mut k = Secret::new(); + unsafe { + ecdh_shared_secret(other_public.0.as_ptr().cast(), self.1 .0.as_ptr().cast(), k.0.as_mut_ptr().cast()); + } + Some(k) + } + } + + impl P384KeyPair {} +} + +#[cfg(target_feature = "builtin_nist_ecc")] +pub use builtin::*; + diff --git a/src/crypto/src/poly1305.rs b/src/crypto/src/poly1305.rs new file mode 100644 index 0000000..b49183a --- /dev/null +++ b/src/crypto/src/poly1305.rs @@ -0,0 +1,48 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. + +use poly1305::universal_hash::KeyInit; + +/// The poly1305 message authentication function. +pub struct Poly1305(poly1305::Poly1305, [u8; 16], usize); + +pub const POLY1305_ONE_TIME_KEY_SIZE: usize = 32; +pub const POLY1305_MAC_SIZE: usize = 16; + +#[inline(always)] +pub fn compute(one_time_key: &[u8], message: &[u8]) -> [u8; POLY1305_MAC_SIZE] { + poly1305::Poly1305::new(poly1305::Key::from_slice(one_time_key)) + .compute_unpadded(message) + .into() +} + +#[cfg(test)] +mod tests { + use crate::poly1305::*; + + const TV0_INPUT: [u8; 32] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + const TV0_KEY: [u8; 32] = [ + 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f, 0x72, + 0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35, + ]; + const TV0_TAG: [u8; 16] = [ + 0x49, 0xec, 0x78, 0x09, 0x0e, 0x48, 0x1e, 0xc6, 0xc2, 0x6b, 0x33, 0xb9, 0x1c, 0xcc, 0x03, 0x07, + ]; + + const TV1_INPUT: [u8; 12] = [0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21]; + const TV1_KEY: [u8; 32] = [ + 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f, 0x72, + 0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35, + ]; + const TV1_TAG: [u8; 16] = [ + 0xa6, 0xf7, 0x45, 0x00, 0x8f, 0x81, 0xc9, 0x16, 0xa2, 0x0d, 0xcc, 0x74, 0xee, 0xf2, 0xb2, 0xf0, + ]; + + #[test] + fn poly1305() { + assert_eq!(TV0_TAG, compute(&TV0_KEY, &TV0_INPUT)); + assert_eq!(TV1_TAG, compute(&TV1_KEY, &TV1_INPUT)); + } +} diff --git a/src/crypto/src/random.rs b/src/crypto/src/random.rs new file mode 100644 index 0000000..34d20d3 --- /dev/null +++ b/src/crypto/src/random.rs @@ -0,0 +1,177 @@ +use std::sync::Mutex; + +use libc::c_int; + +use crate::error::{cvt, ErrorStack}; + +/// Fill buffer with cryptographically strong pseudo-random bytes. +fn rand_bytes(buf: &mut [u8]) -> Result<(), ErrorStack> { + unsafe { + assert!(buf.len() <= c_int::max_value() as usize); + cvt(ffi::RAND_bytes(buf.as_mut_ptr(), buf.len() as c_int)).map(|_| ()) + } +} + +pub fn next_u32_secure() -> u32 { + unsafe { + let mut tmp = [0u32; 1]; + rand_bytes(&mut *(tmp.as_mut_ptr().cast::<[u8; 4]>())).unwrap(); + tmp[0] + } +} + +pub fn next_u64_secure() -> u64 { + unsafe { + let mut tmp = [0u64; 1]; + rand_bytes(&mut *(tmp.as_mut_ptr().cast::<[u8; 8]>())).unwrap(); + tmp[0] + } +} + +pub fn next_u128_secure() -> u128 { + unsafe { + let mut tmp = [0u128; 1]; + rand_bytes(&mut *(tmp.as_mut_ptr().cast::<[u8; 16]>())).unwrap(); + tmp[0] + } +} + +#[inline(always)] +pub fn fill_bytes_secure(dest: &mut [u8]) { + rand_bytes(dest).unwrap(); +} + +#[inline(always)] +pub fn get_bytes_secure() -> [u8; COUNT] { + let mut tmp = [0u8; COUNT]; + rand_bytes(&mut tmp).unwrap(); + tmp +} + +pub struct SecureRandom; + +impl Default for SecureRandom { + #[inline(always)] + fn default() -> Self { + Self + } +} + +impl SecureRandom { + #[inline(always)] + pub fn get() -> Self { + Self + } +} + +impl rand_core::RngCore for SecureRandom { + #[inline(always)] + fn next_u32(&mut self) -> u32 { + next_u32_secure() + } + + #[inline(always)] + fn next_u64(&mut self) -> u64 { + next_u64_secure() + } + + #[inline(always)] + fn fill_bytes(&mut self, dest: &mut [u8]) { + fill_bytes_secure(dest); + } + + #[inline(always)] + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + fill_bytes_secure(dest); + Ok(()) + } +} + +/// ed25519-dalek still uses rand_core 0.5.1, and that version is incompatible with 0.6.4, so we need to import and implement both. +impl rand_core_051::RngCore for SecureRandom { + #[inline(always)] + fn next_u32(&mut self) -> u32 { + next_u32_secure() + } + + #[inline(always)] + fn next_u64(&mut self) -> u64 { + next_u64_secure() + } + + #[inline(always)] + fn fill_bytes(&mut self, dest: &mut [u8]) { + fill_bytes_secure(dest); + } + + #[inline(always)] + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_051::Error> { + fill_bytes_secure(dest); + Ok(()) + } +} + +impl rand_core::CryptoRng for SecureRandom {} +impl rand_core_051::CryptoRng for SecureRandom {} + +unsafe impl Sync for SecureRandom {} +unsafe impl Send for SecureRandom {} + +/// xorshift* by Marsaglia. +/// Simple and deterministic which makes it good for testing. +pub struct Xorshift64Star(pub u64); +impl Xorshift64Star { + #[inline(always)] + pub fn new(seed: u64) -> Self { + Self(seed) + } +} +impl rand_core::RngCore for Xorshift64Star { + #[inline(always)] + fn next_u32(&mut self) -> u32 { + self.next_u64() as u32 + } + #[inline(always)] + fn next_u64(&mut self) -> u64 { + self.0 ^= self.0.wrapping_shr(12); + self.0 ^= self.0.wrapping_shl(25); + self.0 ^= self.0.wrapping_shr(27); + self.0.wrapping_mul(0x2545F4914F6CDD1Du64) + } + + #[inline(always)] + fn fill_bytes(&mut self, dest: &mut [u8]) { + // This could be faster with manual unrolling + let mut r = self.next_u64().to_ne_bytes(); + let mut n = 0; + for byte in dest { + *byte = r[n]; + n += 1; + if n >= 8 { + r = self.next_u64().to_ne_bytes(); + n = 0 + } + } + } + + #[inline(always)] + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.fill_bytes(dest); + Ok(()) + } +} + +/// Get a non-cryptographic random number. +pub fn xorshift64_random() -> u64 { + static XORSHIFT64_STATE: Mutex = Mutex::new(0); + let mut x = XORSHIFT64_STATE.lock().unwrap(); + while *x == 0 { + *x = next_u64_secure(); + } + *x ^= x.wrapping_shr(12); + *x ^= x.wrapping_shl(25); + *x ^= x.wrapping_shr(27); + let r = *x; + drop(x); + r.wrapping_mul(0x2545F4914F6CDD1Du64) +} diff --git a/src/crypto/src/salsa.rs b/src/crypto/src/salsa.rs new file mode 100644 index 0000000..7c1f79b --- /dev/null +++ b/src/crypto/src/salsa.rs @@ -0,0 +1,267 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. + +use std::convert::TryInto; +use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut}; + +const CONSTANTS: [u32; 4] = [ + u32::from_le_bytes(*b"expa"), + u32::from_le_bytes(*b"nd 3"), + u32::from_le_bytes(*b"2-by"), + u32::from_le_bytes(*b"te k"), +]; + +/// Salsa stream cipher implementation supporting 8, 12, or 20 rounds. +/// +/// WARNING: this has a major limitation/caveat. If you call crypt() with plaintext whose +/// size is not a multiple of 64, subsequent calls to crypt() will not be properly aligned. +/// This is okay for uses in ZeroTier but might break other cases. Salsa is deprecated as +/// transport encryption in ZeroTier anyway, but is still used to derive addresses from +/// identity public keys. +pub struct Salsa { + state: [u32; 16], +} + +impl Salsa { + /// Create a new Salsa cipher given a 256-bit key and a 64-bit IV. + pub fn new(key: &[u8], iv: &[u8]) -> Self { + assert!(ROUNDS == 8 || ROUNDS == 12 || ROUNDS == 20); + assert!(key.len() >= 32); + assert!(iv.len() >= 8); + Self { + state: [ + CONSTANTS[0], + u32::from_le_bytes((&key[0..4]).try_into().unwrap()), + u32::from_le_bytes((&key[4..8]).try_into().unwrap()), + u32::from_le_bytes((&key[8..12]).try_into().unwrap()), + u32::from_le_bytes((&key[12..16]).try_into().unwrap()), + CONSTANTS[1], + u32::from_le_bytes((&iv[0..4]).try_into().unwrap()), + u32::from_le_bytes((&iv[4..8]).try_into().unwrap()), + 0, + 0, + CONSTANTS[2], + u32::from_le_bytes((&key[16..20]).try_into().unwrap()), + u32::from_le_bytes((&key[20..24]).try_into().unwrap()), + u32::from_le_bytes((&key[24..28]).try_into().unwrap()), + u32::from_le_bytes((&key[28..32]).try_into().unwrap()), + CONSTANTS[3], + ], + } + } + + #[inline] + pub fn crypt(&mut self, mut plaintext: &[u8], mut ciphertext: &mut [u8]) { + let (j0, j1, j2, j3, j4, j5, j6, j7, mut j8, mut j9, j10, j11, j12, j13, j14, j15) = ( + self.state[0], + self.state[1], + self.state[2], + self.state[3], + self.state[4], + self.state[5], + self.state[6], + self.state[7], + self.state[8], + self.state[9], + self.state[10], + self.state[11], + self.state[12], + self.state[13], + self.state[14], + self.state[15], + ); + + while !plaintext.is_empty() { + let ( + mut x0, + mut x1, + mut x2, + mut x3, + mut x4, + mut x5, + mut x6, + mut x7, + mut x8, + mut x9, + mut x10, + mut x11, + mut x12, + mut x13, + mut x14, + mut x15, + ) = (j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15); + + for _ in 0..(ROUNDS / 2) { + x4 ^= x0.wrapping_add(x12).rotate_left(7); + x8 ^= x4.wrapping_add(x0).rotate_left(9); + x12 ^= x8.wrapping_add(x4).rotate_left(13); + x0 ^= x12.wrapping_add(x8).rotate_left(18); + x9 ^= x5.wrapping_add(x1).rotate_left(7); + x13 ^= x9.wrapping_add(x5).rotate_left(9); + x1 ^= x13.wrapping_add(x9).rotate_left(13); + x5 ^= x1.wrapping_add(x13).rotate_left(18); + x14 ^= x10.wrapping_add(x6).rotate_left(7); + x2 ^= x14.wrapping_add(x10).rotate_left(9); + x6 ^= x2.wrapping_add(x14).rotate_left(13); + x10 ^= x6.wrapping_add(x2).rotate_left(18); + x3 ^= x15.wrapping_add(x11).rotate_left(7); + x7 ^= x3.wrapping_add(x15).rotate_left(9); + x11 ^= x7.wrapping_add(x3).rotate_left(13); + x15 ^= x11.wrapping_add(x7).rotate_left(18); + x1 ^= x0.wrapping_add(x3).rotate_left(7); + x2 ^= x1.wrapping_add(x0).rotate_left(9); + x3 ^= x2.wrapping_add(x1).rotate_left(13); + x0 ^= x3.wrapping_add(x2).rotate_left(18); + x6 ^= x5.wrapping_add(x4).rotate_left(7); + x7 ^= x6.wrapping_add(x5).rotate_left(9); + x4 ^= x7.wrapping_add(x6).rotate_left(13); + x5 ^= x4.wrapping_add(x7).rotate_left(18); + x11 ^= x10.wrapping_add(x9).rotate_left(7); + x8 ^= x11.wrapping_add(x10).rotate_left(9); + x9 ^= x8.wrapping_add(x11).rotate_left(13); + x10 ^= x9.wrapping_add(x8).rotate_left(18); + x12 ^= x15.wrapping_add(x14).rotate_left(7); + x13 ^= x12.wrapping_add(x15).rotate_left(9); + x14 ^= x13.wrapping_add(x12).rotate_left(13); + x15 ^= x14.wrapping_add(x13).rotate_left(18); + } + + x0 = x0.wrapping_add(j0); + x1 = x1.wrapping_add(j1); + x2 = x2.wrapping_add(j2); + x3 = x3.wrapping_add(j3); + x4 = x4.wrapping_add(j4); + x5 = x5.wrapping_add(j5); + x6 = x6.wrapping_add(j6); + x7 = x7.wrapping_add(j7); + x8 = x8.wrapping_add(j8); + x9 = x9.wrapping_add(j9); + x10 = x10.wrapping_add(j10); + x11 = x11.wrapping_add(j11); + x12 = x12.wrapping_add(j12); + x13 = x13.wrapping_add(j13); + x14 = x14.wrapping_add(j14); + x15 = x15.wrapping_add(j15); + + j8 = j8.wrapping_add(1); + j9 = j9.wrapping_add((j8 == 0) as u32); + + if plaintext.len() >= 64 { + #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] + { + // Slightly faster keystream XOR for little-endian platforms with unaligned load/store. + unsafe { + *ciphertext.as_mut_ptr().cast::() = *plaintext.as_ptr().cast::() ^ x0; + *ciphertext.as_mut_ptr().cast::().add(1) = *plaintext.as_ptr().cast::().add(1) ^ x1; + *ciphertext.as_mut_ptr().cast::().add(2) = *plaintext.as_ptr().cast::().add(2) ^ x2; + *ciphertext.as_mut_ptr().cast::().add(3) = *plaintext.as_ptr().cast::().add(3) ^ x3; + *ciphertext.as_mut_ptr().cast::().add(4) = *plaintext.as_ptr().cast::().add(4) ^ x4; + *ciphertext.as_mut_ptr().cast::().add(5) = *plaintext.as_ptr().cast::().add(5) ^ x5; + *ciphertext.as_mut_ptr().cast::().add(6) = *plaintext.as_ptr().cast::().add(6) ^ x6; + *ciphertext.as_mut_ptr().cast::().add(7) = *plaintext.as_ptr().cast::().add(7) ^ x7; + *ciphertext.as_mut_ptr().cast::().add(8) = *plaintext.as_ptr().cast::().add(8) ^ x8; + *ciphertext.as_mut_ptr().cast::().add(9) = *plaintext.as_ptr().cast::().add(9) ^ x9; + *ciphertext.as_mut_ptr().cast::().add(10) = *plaintext.as_ptr().cast::().add(10) ^ x10; + *ciphertext.as_mut_ptr().cast::().add(11) = *plaintext.as_ptr().cast::().add(11) ^ x11; + *ciphertext.as_mut_ptr().cast::().add(12) = *plaintext.as_ptr().cast::().add(12) ^ x12; + *ciphertext.as_mut_ptr().cast::().add(13) = *plaintext.as_ptr().cast::().add(13) ^ x13; + *ciphertext.as_mut_ptr().cast::().add(14) = *plaintext.as_ptr().cast::().add(14) ^ x14; + *ciphertext.as_mut_ptr().cast::().add(15) = *plaintext.as_ptr().cast::().add(15) ^ x15; + } + } + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))] + { + // Portable keystream XOR with alignment-safe access and native to little-endian conversion. + let keystream = [ + x0.to_le(), + x1.to_le(), + x2.to_le(), + x3.to_le(), + x4.to_le(), + x5.to_le(), + x6.to_le(), + x7.to_le(), + x8.to_le(), + x9.to_le(), + x10.to_le(), + x11.to_le(), + x12.to_le(), + x13.to_le(), + x14.to_le(), + x15.to_le(), + ]; + for i in 0..64 { + ciphertext[i] = plaintext[i] ^ unsafe { *keystream.as_ptr().cast::().add(i) }; + } + } + + plaintext = &plaintext[64..]; + ciphertext = &mut ciphertext[64..]; + } else { + let keystream = [ + x0.to_le(), + x1.to_le(), + x2.to_le(), + x3.to_le(), + x4.to_le(), + x5.to_le(), + x6.to_le(), + x7.to_le(), + x8.to_le(), + x9.to_le(), + x10.to_le(), + x11.to_le(), + x12.to_le(), + x13.to_le(), + x14.to_le(), + x15.to_le(), + ]; + for i in 0..plaintext.len() { + ciphertext[i] = plaintext[i] ^ unsafe { *keystream.as_ptr().cast::().add(i) }; + } + break; + } + } + + self.state[8] = j8; + self.state[9] = j9; + } + + #[inline(always)] + pub fn crypt_in_place(&mut self, data: &mut [u8]) { + unsafe { + self.crypt( + &*slice_from_raw_parts(data.as_ptr(), data.len()), + &mut *slice_from_raw_parts_mut(data.as_mut_ptr(), data.len()), + ) + } + } +} + +#[cfg(test)] +mod tests { + use crate::salsa::*; + + const SALSA_20_TV0_KEY: [u8; 32] = [ + 0x0f, 0x62, 0xb5, 0x08, 0x5b, 0xae, 0x01, 0x54, 0xa7, 0xfa, 0x4d, 0xa0, 0xf3, 0x46, 0x99, 0xec, 0x3f, 0x92, 0xe5, 0x38, 0x8b, 0xde, 0x31, + 0x84, 0xd7, 0x2a, 0x7d, 0xd0, 0x23, 0x76, 0xc9, 0x1c, + ]; + const SALSA_20_TV0_IV: [u8; 8] = [0x28, 0x8f, 0xf6, 0x5d, 0xc4, 0x2b, 0x92, 0xf9]; + const SALSA_20_TV0_KS: [u8; 64] = [ + 0x5e, 0x5e, 0x71, 0xf9, 0x01, 0x99, 0x34, 0x03, 0x04, 0xab, 0xb2, 0x2a, 0x37, 0xb6, 0x62, 0x5b, 0xf8, 0x83, 0xfb, 0x89, 0xce, 0x3b, 0x21, + 0xf5, 0x4a, 0x10, 0xb8, 0x10, 0x66, 0xef, 0x87, 0xda, 0x30, 0xb7, 0x76, 0x99, 0xaa, 0x73, 0x79, 0xda, 0x59, 0x5c, 0x77, 0xdd, 0x59, 0x54, + 0x2d, 0xa2, 0x08, 0xe5, 0x95, 0x4f, 0x89, 0xe4, 0x0e, 0xb7, 0xaa, 0x80, 0xa8, 0x4a, 0x61, 0x76, 0x66, 0x3f, + ]; + + #[test] + fn salsa20() { + let mut s20 = Salsa::<20>::new(&SALSA_20_TV0_KEY, &SALSA_20_TV0_IV); + let mut ks = [0_u8; 64]; + s20.crypt_in_place(&mut ks); + assert_eq!(ks, SALSA_20_TV0_KS); + + let mut s20 = Salsa::<20>::new(&SALSA_20_TV0_KEY, &SALSA_20_TV0_IV); + let mut ks = [0_u8; 32]; + s20.crypt_in_place(&mut ks); + assert_eq!(ks, &SALSA_20_TV0_KS[..32]); + } +} diff --git a/src/crypto/src/secret.rs b/src/crypto/src/secret.rs new file mode 100644 index 0000000..f985356 --- /dev/null +++ b/src/crypto/src/secret.rs @@ -0,0 +1,131 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. + +use std::{convert::TryInto, ffi::c_void}; + +extern "C" { + fn OPENSSL_cleanse(ptr: *mut c_void, len: usize); +} + +/// Container for secrets that clears them on drop. +/// +/// We can't be totally sure that things like libraries are doing this and it's +/// hard to get every use of a secret anywhere, but using this in our code at +/// least reduces the number of secrets that are left lying around in memory. +/// +/// This is generally a low-risk thing since it's process memory that's protected, +/// but it's still not a bad idea due to things like swap or obscure side channel +/// attacks that allow memory to be read. +#[derive(Clone, PartialEq, Eq)] +#[repr(transparent)] +pub struct Secret(pub [u8; L]); + +impl Secret { + /// Create a new all-zero secret. + #[inline(always)] + pub fn new() -> Self { + Self([0_u8; L]) + } + + /// Moves bytes into secret, will panic if the slice does not match the size of this secret. + #[inline(always)] + pub fn move_bytes(b: [u8; L]) -> Self { + Self(b) + } + + /// Copy bytes into secret, then nuke the previous value, will panic if the slice does not match the size of this secret. + #[inline(always)] + pub fn from_bytes_then_nuke(b: &mut [u8]) -> Self { + let ret = Self(b.try_into().unwrap()); + unsafe { OPENSSL_cleanse(b.as_mut_ptr().cast(), L) }; + ret + } + #[inline(always)] + pub unsafe fn from_bytes(b: &[u8]) -> Self { + Self(b.try_into().unwrap()) + } + + #[inline(always)] + pub fn as_bytes(&self) -> &[u8; L] { + &self.0 + } + #[inline(always)] + pub fn as_ptr(&self) -> *const u8 { + self.0.as_ptr() + } + + #[inline(always)] + pub fn as_bytes_mut(&mut self) -> &mut [u8; L] { + &mut self.0 + } + + /// Get the first N bytes of this secret as a fixed length array. + #[inline(always)] + pub fn first_n(&self) -> &[u8; N] { + assert!(N <= L); + unsafe { &*self.0.as_ptr().cast() } + } + + /// Clone the first N bytes of this secret as another secret. + #[inline(always)] + pub fn first_n_clone(&self) -> Secret { + Secret::(*self.first_n()) + } + + pub fn overwrite(&mut self, src: &Self) { + self.0.copy_from_slice(&src.0); + } + pub fn overwrite_first_n(&mut self, src: &Secret) { + let amount = N.min(L); + self.0[..amount].copy_from_slice(&src.0[..amount]); + } + + /// Destroy the contents of this secret, ignoring normal Rust mutability constraints. + /// + /// This can be used to force a secret to be forgotten under e.g. key lifetime exceeded or error conditions. + #[inline(always)] + pub fn nuke(&self) { + unsafe { OPENSSL_cleanse(self.0.as_ptr().cast_mut().cast(), L) }; + } +} + +impl Drop for Secret { + #[inline(always)] + fn drop(&mut self) { + unsafe { OPENSSL_cleanse(self.0.as_mut_ptr().cast(), L) }; + } +} + +impl Default for Secret { + #[inline(always)] + fn default() -> Self { + Self([0_u8; L]) + } +} + +impl AsRef<[u8]> for Secret { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsRef<[u8; L]> for Secret { + #[inline(always)] + fn as_ref(&self) -> &[u8; L] { + &self.0 + } +} + +impl AsMut<[u8]> for Secret { + #[inline(always)] + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +impl AsMut<[u8; L]> for Secret { + #[inline(always)] + fn as_mut(&mut self) -> &mut [u8; L] { + &mut self.0 + } +} diff --git a/src/crypto/src/typestate.rs b/src/crypto/src/typestate.rs new file mode 100644 index 0000000..0e7ca04 --- /dev/null +++ b/src/crypto/src/typestate.rs @@ -0,0 +1,223 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. + +use std::fmt::Debug; +use std::hash::Hash; +use std::ops::{Deref, DerefMut}; + +/// Typestate indicating that a credential or other object has been internally validated. +#[repr(transparent)] +pub struct Valid(T); + +impl AsRef for Valid { + #[inline(always)] + fn as_ref(&self) -> &T { + &self.0 + } +} + +impl AsMut for Valid { + #[inline(always)] + fn as_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl Deref for Valid { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Valid { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Clone for Valid +where + T: Clone, +{ + #[inline(always)] + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl PartialEq for Valid +where + T: PartialEq, +{ + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } +} + +impl Eq for Valid where T: Eq {} + +impl Ord for Valid +where + T: Ord, +{ + #[inline(always)] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl PartialOrd for Valid +where + T: PartialOrd, +{ + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Hash for Valid +where + T: Hash, +{ + #[inline(always)] + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +impl Debug for Valid +where + T: Debug, +{ + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Valid").field(&self.0).finish() + } +} + +impl Valid { + #[inline(always)] + pub fn remove_typestate(self) -> T { + self.0 + } + + #[inline(always)] + pub fn mark_valid(o: T) -> Self { + Self(o) + } +} + +/// Typestate indicating that a credential or other object has been externally validated. +/// +/// This is more appropriate for certificates signed by an external authority. +#[repr(transparent)] +pub struct Verified(T); + +impl AsRef for Verified { + #[inline(always)] + fn as_ref(&self) -> &T { + &self.0 + } +} + +impl AsMut for Verified { + #[inline(always)] + fn as_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl Deref for Verified { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Verified { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Clone for Verified +where + T: Clone, +{ + #[inline(always)] + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl PartialEq for Verified +where + T: PartialEq, +{ + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + self.0.eq(&other.0) + } +} + +impl Eq for Verified where T: Eq {} + +impl Ord for Verified +where + T: Ord, +{ + #[inline(always)] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl PartialOrd for Verified +where + T: PartialOrd, +{ + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Hash for Verified +where + T: Hash, +{ + #[inline(always)] + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +impl Debug for Verified +where + T: Debug, +{ + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Valid").field(&self.0).finish() + } +} + +impl Verified { + #[inline(always)] + pub fn remove_typestate(self) -> T { + self.0 + } + + #[inline(always)] + pub fn mark_verified(o: T) -> Self { + Self(o) + } +} diff --git a/src/crypto/src/x25519.rs b/src/crypto/src/x25519.rs new file mode 100644 index 0000000..47041ff --- /dev/null +++ b/src/crypto/src/x25519.rs @@ -0,0 +1,171 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. + +use std::convert::TryInto; +use std::io::Write; + +use ed25519_dalek::Digest; + +use crate::random::SecureRandom; +use crate::secret::Secret; + +pub const C25519_PUBLIC_KEY_SIZE: usize = 32; +pub const C25519_SECRET_KEY_SIZE: usize = 32; +pub const C25519_SHARED_SECRET_SIZE: usize = 32; +pub const ED25519_PUBLIC_KEY_SIZE: usize = 32; +pub const ED25519_SECRET_KEY_SIZE: usize = 32; +pub const ED25519_SIGNATURE_SIZE: usize = 64; + +/// Curve25519 key pair for ECDH key agreement. +pub struct X25519KeyPair(x25519_dalek::StaticSecret, Secret<32>, x25519_dalek::PublicKey); + +impl X25519KeyPair { + pub fn generate() -> X25519KeyPair { + let sk = x25519_dalek::StaticSecret::new(SecureRandom::get()); + let sk2 = Secret(sk.to_bytes()); + let pk = x25519_dalek::PublicKey::from(&sk); + X25519KeyPair(sk, sk2, pk) + } + + pub fn from_bytes(public_key: &[u8], secret_key: &[u8]) -> Option { + if public_key.len() == 32 && secret_key.len() == 32 { + /* NOTE: we keep the original secret separately from x25519_dalek's StaticSecret + * due to how "clamping" is done in the old C++ code vs x25519_dalek. Clamping + * is explained here: + * + * https://www.jcraige.com/an-explainer-on-ed25519-clamping + * + * The old code does clamping at the time of use. In other words the code that + * performs things like key agreement or signing clamps the secret before doing + * the operation. The x25519_dalek code does clamping at generation or when + * from() is used to get a key from a raw byte array. + * + * Unfortunately this introduces issues when interoperating with old code. The + * old system generates secrets that are not clamped (since they're clamped at + * use!) and assumes that these exact binary keys will be preserved in e.g. + * identities. So to preserve this behavior we store the secret separately + * so secret_bytes() will return it as-is. + * + * The new code will still clamp at generation resulting in secrets that are + * pre-clamped, but the old code won't care about this. It's only a problem when + * going the other way. + * + * This has no cryptographic implication since regardless of where, the clamping + * is done. It's just an API thing. + */ + let pk: [u8; 32] = public_key.try_into().unwrap(); + let sk_orig: Secret<32> = Secret(secret_key.try_into().unwrap()); + let pk = x25519_dalek::PublicKey::from(pk); + let sk = x25519_dalek::StaticSecret::from(sk_orig.0); + Some(X25519KeyPair(sk, sk_orig, pk)) + } else { + None + } + } + + #[inline(always)] + pub fn public_bytes(&self) -> [u8; C25519_PUBLIC_KEY_SIZE] { + self.2.to_bytes() + } + + #[inline(always)] + pub fn secret_bytes(&self) -> &Secret<32> { + &self.1 + } + + /// Execute ECDH agreement and return a raw (un-hashed) shared secret key. + pub fn agree(&self, their_public: &[u8]) -> Secret<{ C25519_SHARED_SECRET_SIZE }> { + let pk: [u8; 32] = their_public.try_into().unwrap(); + let pk = x25519_dalek::PublicKey::from(pk); + let sec = self.0.diffie_hellman(&pk); + Secret(sec.to_bytes()) + } +} + +impl Clone for X25519KeyPair { + fn clone(&self) -> Self { + Self( + x25519_dalek::StaticSecret::from(self.0.to_bytes()), + self.1.clone(), + x25519_dalek::PublicKey::from(self.1 .0), + ) + } +} + +/// Ed25519 key pair for EDDSA signatures. +pub struct Ed25519KeyPair(ed25519_dalek::Keypair, Secret<32>); + +impl Ed25519KeyPair { + pub fn generate() -> Ed25519KeyPair { + let mut rng = SecureRandom::get(); + let kp = ed25519_dalek::Keypair::generate(&mut rng); + let sk2 = Secret(kp.secret.to_bytes()); + Ed25519KeyPair(kp, sk2) + } + + pub fn from_bytes(public_bytes: &[u8], secret_bytes: &[u8]) -> Option { + if public_bytes.len() == ED25519_PUBLIC_KEY_SIZE && secret_bytes.len() == ED25519_SECRET_KEY_SIZE { + let pk = ed25519_dalek::PublicKey::from_bytes(public_bytes); + let sk = ed25519_dalek::SecretKey::from_bytes(secret_bytes); + if pk.is_ok() && sk.is_ok() { + // See comment in from_bytes() in C25519KeyPair for an explanation of the copy of the secret here. + let pk = pk.unwrap(); + let sk = sk.unwrap(); + let sk2 = Secret(sk.to_bytes()); + Some(Ed25519KeyPair(ed25519_dalek::Keypair { public: pk, secret: sk }, sk2)) + } else { + None + } + } else { + None + } + } + + #[inline(always)] + pub fn public_bytes(&self) -> [u8; ED25519_PUBLIC_KEY_SIZE] { + self.0.public.to_bytes() + } + + #[inline(always)] + pub fn secret_bytes(&self) -> &Secret<32> { + &self.1 + } + + pub fn sign(&self, msg: &[u8]) -> [u8; ED25519_SIGNATURE_SIZE] { + let mut h = ed25519_dalek::Sha512::new(); + let _ = h.write_all(msg); + self.0.sign_prehashed(h.clone(), None).unwrap().to_bytes() + } + + /// Create a signature with the first 32 bytes of the SHA512 hash appended. + /// ZeroTier does this for legacy reasons, but it's ignored in newer versions. + pub fn sign_zt(&self, msg: &[u8]) -> [u8; 96] { + let mut h = ed25519_dalek::Sha512::new(); + let _ = h.write_all(msg); + let sig = self.0.sign_prehashed(h.clone(), None).unwrap(); + let s = sig.as_ref(); + let mut s2 = [0_u8; 96]; + s2[0..64].copy_from_slice(s); + let h = h.finalize(); + s2[64..96].copy_from_slice(&h.as_slice()[0..32]); + s2 + } +} + +impl Clone for Ed25519KeyPair { + fn clone(&self) -> Self { + Self(ed25519_dalek::Keypair::from_bytes(&self.0.to_bytes()).unwrap(), self.1.clone()) + } +} + +pub fn ed25519_verify(public_key: &[u8], signature: &[u8], msg: &[u8]) -> bool { + if public_key.len() == 32 && signature.len() >= 64 { + ed25519_dalek::PublicKey::from_bytes(public_key).map_or(false, |pk| { + let mut h = ed25519_dalek::Sha512::new(); + let _ = h.write_all(msg); + let sig: [u8; 64] = signature[0..64].try_into().unwrap(); + pk.verify_prehashed(h, None, &ed25519_dalek::Signature::from(sig)).is_ok() + }) + } else { + false + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..f332468 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +#[derive(Debug, PartialEq, Eq)] +pub enum OpenError { + /// An invalid parameter was supplied to the function. + InvalidPublicKey, + + /// Local identity blob is too large to send, even with fragmentation. + DataTooLarge, +} +#[derive(Debug, PartialEq, Eq)] +pub enum SendError { + /// An invalid parameter was supplied to the function. + InvalidParameter, + + /// The session has been marked as expired and refuses to send data. + /// Several components of ZSSP can cause this to occur, but the most likely situation to be seen + /// in practice is where rekeying repeatedly fails due to exceedingly bad network conditions. + /// + /// The associated session will no longer send or receive data and must be immediately dropped. + SessionExpired, + + /// Attempt to send using a session without a shared symmetric key. + /// The caller should wait until the handshake has completed. + SessionNotEstablished, + + /// Data object is too large to send, even with fragmentation. + DataTooLarge, +} +/// A type of fault occurred because we received a bad packet. +/// +/// An unauthenticated attacker can intentionally trigger any of these, so it is best to +/// treat these as raw user input that needs to be sanitize. +#[derive(Debug, PartialEq, Eq)] +pub enum FaultType { + /// The received packet was addressed to an unrecognized local session. + UnknownLocalKeyId, + + /// The received packet from the remote peer was not well formed. + InvalidPacket, + + /// Packet failed one or more authentication (MAC) checks. + FailedAuthentication, + + /// Packet counter was repeated or outside window of allowed counter values. + ExpiredCounter, + + /// Packet contained protocol control parameters that are disallowed at this point in + /// time by ZSSP. + OutOfSequence, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum ReceiveError { + /// A type of fault that can occur because a remote peer sent us a bad packet. + /// Such packets will be ignored by ZSSP but a user of ZSSP might want to log + /// them for debugging or tracing. + /// + /// Because an unauthenticated remote peer can force these to occur with specific + /// contained information, it is recommended in production to either drop these + /// immediately, or log them safely to a local output stream and then drop them. + ByzantineFault { + /// The type of fault that has occurred. Be cautious if you choose to read this + /// value, as an attacker has control over it. + error: FaultType, + /// Some byzantine faults within ZSSP are naturally occurring, i.e. they can occur + /// between two well behaved and trusted parties executing the protocol. + /// This boolean is true if this is one of these faults. If you go to the file and + /// line number specified by this error you will find a comment describing + /// how and why exactly this fault can occur naturally. + /// + /// Faults that can occur because the underlying communication medium is lossy and + /// sequentially inconsistent (as in UDP) are considered naturally occurring. + /// However ZSSP considers faults that occur because data integrity has not been + /// persevered (i.e. bits have been flipped) to be unnatural. + /// ZSSP also considers collisions of what are supposed to be uniform random + /// numbers to be unnatural. + is_naturally_occurring: bool, + /// The file of this implementation of ZSSP from which this error was generated. + file: &'static str, + /// The line number of this implementation of ZSSP from which this error was + /// generated. As such this number uniquely identifies each possible fault that + /// can occur during ZSSP. Advanced user can use this information to debug more + /// complicated usages of ZSSP. + line: u32, + }, + + /// The caller supplied data buffer is too small to receive data from the remote peer. + /// An attacker can cause this to occur, so users should place a hard upper limit on + /// how large their supplied data buffers can be. + DataBufferTooSmall, + + /// Rekeying failed and session secret has reached its hard usage count limit. + /// The associated session will no longer function and has to be dropped. + MaxKeyLifetimeExceeded, + + /// One of the ratchet saving or lookup functions returned an error, so the packet had to be + /// dropped. + RatchetIoError, +} diff --git a/src/frag_cache.rs b/src/frag_cache.rs new file mode 100644 index 0000000..d77a7f2 --- /dev/null +++ b/src/frag_cache.rs @@ -0,0 +1,306 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::collections::hash_map::RandomState; +use std::hash::{BuildHasher, Hash, Hasher}; +use std::mem::MaybeUninit; + +use crate::fragged::Assembled; +use crate::proto::{MAX_FRAGMENTS, MAX_UNASSOCIATED_FRAGMENTS, MAX_UNASSOCIATED_PACKETS, MAX_UNASSOCIATED_PACKET_SIZE}; + +struct PacketMetadata { + key: u64, + frags_idx: u32, + fragment_have: u64, + fragment_count: u32, + packet_size: u32, + creation_time: i64, +} +pub(crate) struct UnassociatedFragCache { + dos_salt: RandomState, + frags_first_unused: usize, + frags_unused_size: usize, + map: [PacketMetadata; MAX_UNASSOCIATED_PACKETS], + frags: [MaybeUninit; MAX_UNASSOCIATED_FRAGMENTS], + map_idx: [u32; MAX_UNASSOCIATED_FRAGMENTS], +} +/// A combination of a hash table cache and a ring buffer for unassociated fragments. +/// Designed specifically to be extremely DDOS resistant. +/// This datastructure takes raw unauthenticated fragments straight from the network. +impl UnassociatedFragCache { + pub(crate) fn new() -> Self { + Self { + dos_salt: RandomState::new(), + frags_first_unused: 0, + frags_unused_size: MAX_UNASSOCIATED_FRAGMENTS, + map: std::array::from_fn(|_| PacketMetadata { + key: 0, + frags_idx: 0, + fragment_have: 0, + fragment_count: 0, + packet_size: 0, + creation_time: 0, + }), + frags: std::array::from_fn(|_| MaybeUninit::zeroed()), + map_idx: std::array::from_fn(|_| u32::MAX), + } + } + /// Add a fragment and return an assembled packet container if all fragments have been received. + /// Will check that aad is the same for all fragments. + pub(crate) fn assemble( + &mut self, + nonce: [u8; 10], + remote_address: impl Hash, + fragment_size: usize, + fragment: Fragment, + fragment_no: u8, + fragment_count: u8, + timeout: i64, + current_time: i64, + ret_assembled: &mut Assembled, + ) { + debug_assert!(MAX_FRAGMENTS < MAX_UNASSOCIATED_FRAGMENTS); + if fragment_no >= fragment_count || (fragment_count as usize) > MAX_FRAGMENTS || fragment_size > MAX_UNASSOCIATED_PACKET_SIZE { + return; + } + + let mut hasher = self.dos_salt.build_hasher(); + remote_address.hash(&mut hasher); + hasher.write(&nonce); + let mut key = hasher.finish(); + if key == 0 { + key = 1; + } + + let map_len = self.map.len(); + let idx0 = (key as usize) % map_len; + let mut idx1 = (key as usize) / map_len % (map_len - 1); + if idx0 == idx1 { + idx1 = map_len - 1; + } + + // Open hash lookup of just 2 slots. + // To DOS, an adversary would either need to volumetrically spam the defrag table to keep most slots full + // or replay Alice's packet header from a spoofed physical path before Alice's packet is fully processed. + // Volumetric spam is quite difficult since without the `dos_salt` value an adversary cannot + // control which slots their fragments index to. And since Alice's packet header has a randomly + // generated counter value replaying it in time requires extreme amounts of network control. + let idx = if self.map[idx0].key == key { + idx0 + } else if self.map[idx1].key == key { + idx1 + } else if self.map[idx0].key == 0 || self.map[idx1].key == 0 { + if (fragment_count as usize) > self.frags_unused_size { + // There are not enough free fragment slots so attempt to expire a bunch of entries. + self.check_for_expiry(timeout, current_time); + } + if self.map[idx0].key == 0 { + idx0 + } else { + idx1 + } + } else { + // No room for a new entry so attempt to expire a bunch of entries. + self.check_for_expiry(timeout, current_time); + if self.map[idx0].key == 0 { + idx0 + } else if self.map[idx1].key == 0 { + idx1 + } else { + // Give up and drop the fragment. + return; + } + }; + + if self.map[idx].key == 0 { + // This is a new entry so initialize it. + if (fragment_count as usize) <= self.frags_unused_size { + let mut entry = &mut self.map[idx]; + entry.key = key; + entry.frags_idx = self.frags_first_unused as u32; + entry.fragment_have = 0; + entry.fragment_count = fragment_count as u32; + entry.packet_size = 0; + entry.creation_time = current_time; + + for _ in 0..(entry.fragment_count as usize) { + self.map_idx[self.frags_first_unused] = idx as u32; + self.frags_first_unused = (self.frags_first_unused + 1) % self.frags.len(); + self.frags_unused_size -= 1; + } + } else { + // If there are not enough free fragment slots by this point we just drop the fragment. + return; + } + } + let mut entry = &mut self.map[idx]; + + let new_size = entry.packet_size + fragment_size as u32; + let got = 1u64.wrapping_shl(fragment_no as u32); + if got & entry.fragment_have == 0 && fragment_count == entry.fragment_count as u8 && new_size <= MAX_UNASSOCIATED_PACKET_SIZE as u32 { + entry.packet_size = new_size; + entry.fragment_have |= got; + + let frag_idx = (entry.frags_idx as usize + fragment_no as usize) % self.frags.len(); + self.frags[frag_idx].write(fragment); + + if entry.fragment_have == 1u64.wrapping_shl(fragment_count as u32) - 1 { + ret_assembled.empty(); + ret_assembled.1 = fragment_count as usize; + let start_idx = entry.frags_idx as usize; + // This is a ring buffer copy into ret_assembled. + // The fragments are moved into the `ret_assembled` container and returned. + // That container will drop them when it is dropped. + if start_idx + ret_assembled.1 <= self.frags.len() { + // Copy does not occur at the buffer's boundary + unsafe { + std::ptr::copy_nonoverlapping(&self.frags[start_idx], &mut ret_assembled.0[0], ret_assembled.1); + } + } else { + // Copy does occur at the buffer's boundary + let first_chunk_size = self.frags.len() - start_idx; + let second_chunk_size = ret_assembled.1 - first_chunk_size; + unsafe { + std::ptr::copy_nonoverlapping(&self.frags[start_idx], &mut ret_assembled.0[0], first_chunk_size); + std::ptr::copy_nonoverlapping(&self.frags[0], &mut ret_assembled.0[first_chunk_size], second_chunk_size); + } + } + self.invalidate::(idx); + } + } + } + pub(crate) fn check_for_expiry(&mut self, timeout: i64, current_time: i64) { + while self.frags_unused_size < self.frags.len() { + // Check if we can drop the entry at the start of the ring buffer. + let frag_idx = (self.frags_first_unused + self.frags_unused_size) % self.frags.len(); + let map_idx = self.map_idx[frag_idx] as usize; + debug_assert!(map_idx < self.map.len()); + + let entry = &mut self.map[map_idx]; + if entry.creation_time + timeout < current_time { + self.invalidate::(map_idx); + } else { + break; + } + } + } + + fn invalidate(&mut self, idx: usize) { + let entry = &mut self.map[idx]; + let start_idx = entry.frags_idx as usize; + for fragment_no in 0..(entry.fragment_count as usize) { + let frag_idx = (start_idx + fragment_no) % self.frags.len(); + self.map_idx[frag_idx] = u32::MAX; + // DROP is only false when we have moved the fragments out of this entry, and so we can't free them + // Otherwise we need to manually drop all of the fragments that this entry owns. + if DROP && entry.fragment_have & 1u64.wrapping_shl(fragment_no as u32) > 0 { + unsafe { self.frags[frag_idx].assume_init_drop() }; + } + } + entry.key = 0; + entry.frags_idx = 0; + entry.fragment_have = 0; + entry.fragment_count = 0; + entry.packet_size = 0; + entry.creation_time = 0; + let mut frags_first_used = (self.frags_first_unused + self.frags_unused_size) % self.frags.len(); + if frags_first_used == start_idx { + // `frags_unused_size` is pointing to the slot we just emptied. + // Move `frags_unused_size` to point at the first non-empty slot. + while self.frags_unused_size < self.frags.len() { + if self.map_idx[frags_first_used] == u32::MAX { + self.frags_unused_size += 1; + frags_first_used = (self.frags_first_unused + self.frags_unused_size) % self.frags.len(); + } else { + break; + } + } + } + } +} +impl Drop for UnassociatedFragCache { + fn drop(&mut self) { + for i in 0..self.map.len() { + if self.map[i].key != 0 { + self.invalidate::(i); + } + } + } +} + +#[test] +fn test_cache() { + use zerotier_crypto::random; + + let mut cache = UnassociatedFragCache::new(); + let mut assembled = Assembled::new(); + + let mut time = 1; + let mut in_progress = Vec::new(); + let mut in_progress_fragments = 0; + // A basic fuzzer for testing the cache. + for i in 0..5000u32 { + let fragment_count = (random::xorshift64_random() as usize % MAX_FRAGMENTS) + 1; + let r = random::xorshift64_random() as u8; + if r & 1 == 0 { + let mut packet = Vec::new(); + for j in 0..fragment_count { + packet.push((j as u8, vec![0, 1, 2, 3, 4, 5, 6, r])); + in_progress_fragments += 1; + } + in_progress.push((i, fragment_count as u8, packet)); + } else { + assembled.empty(); + let drop = random::xorshift64_random() as usize % (2 * fragment_count); + for j in 0..fragment_count { + if drop != j { + let fragment = vec![0, 1, 2, 3, 4, 5, 6, r]; + // If the timeout is 1 we should be guaranteed to get our packet cached. + let mut nonce = [0; 10]; + nonce[..4].copy_from_slice(&i.to_be_bytes()); + cache.assemble(nonce, 0, fragment.len(), fragment, j as u8, fragment_count as u8, 1, time, &mut assembled); + time += 1; + } + } + if drop >= fragment_count { + assert!(!assembled.is_empty(), "Packet was dropped from the cache when it shouldn't have"); + assert_eq!(assembled.as_ref().len(), fragment_count, "Cache returned the wrong packet"); + for j in 0..fragment_count { + assert_eq!(assembled.as_ref()[j][7], r, "Cache returned a corrupted packet"); + } + } else { + assert!(assembled.is_empty(), "Cache returned an incomplete packet"); + } + } + if r > 200 { + if in_progress.len() > 0 { + let to_remain = (random::xorshift64_random() as usize % in_progress_fragments) + 16; + while in_progress_fragments > to_remain { + let (id, fragment_count, mut packet) = in_progress.swap_remove(random::xorshift64_random() as usize % in_progress.len()); + for _ in 0..((random::xorshift64_random() as usize % packet.len()) + 1) { + let (no, fragment) = packet.swap_remove(random::xorshift64_random() as usize % packet.len()); + + assembled.empty(); + let mut nonce = [0; 10]; + nonce[..4].copy_from_slice(&id.to_be_bytes()); + cache.assemble(nonce, 0, fragment.len(), fragment, no, fragment_count, 1000, time, &mut assembled); + time += 1; + in_progress_fragments -= 1; + + if packet.len() > 0 { + assert!(assembled.is_empty(), "Cache returned an incomplete packet"); + } + } + if packet.len() > 0 { + in_progress.push((id, fragment_count, packet)); + } + } + } + } + } +} diff --git a/src/fragged.rs b/src/fragged.rs new file mode 100644 index 0000000..a9eb2cc --- /dev/null +++ b/src/fragged.rs @@ -0,0 +1,136 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::mem::{needs_drop, zeroed, MaybeUninit}; +use std::ptr::slice_from_raw_parts; + +use crate::proto::MAX_FRAGMENTS; + +pub(crate) struct Assembled(pub(crate) [MaybeUninit; MAX_FRAGMENTS], pub(crate) usize); + +impl Assembled { + pub(crate) fn new() -> Self { + Self(unsafe { MaybeUninit::<[MaybeUninit<_>; MAX_FRAGMENTS]>::uninit().assume_init() }, 0) + } + pub(crate) fn is_empty(&self) -> bool { + self.1 == 0 + } + pub(crate) fn empty(&mut self) { + for i in 0..self.1 { + unsafe { + self.0.get_unchecked_mut(i).assume_init_drop(); + } + } + self.1 = 0; + } +} +impl AsRef<[Fragment]> for Assembled { + #[inline(always)] + fn as_ref(&self) -> &[Fragment] { + unsafe { &*slice_from_raw_parts(self.0.as_ptr().cast::(), self.1) } + } +} +impl Drop for Assembled { + #[inline(always)] + fn drop(&mut self) { + self.empty() + } +} + +/// Fast packet defragmenter +pub struct Fragged { + count: u32, + have: u64, + nonce: [u8; 10], + size: usize, + frags: [MaybeUninit; MAX_FRAGMENTS], +} + +impl Fragged { + #[inline(always)] + pub fn new() -> Self { + debug_assert!(MAX_FRAGMENTS <= 64); + unsafe { zeroed() } + } + + /// Add a fragment and return an assembled packet container if all fragments have been received. + /// + /// When a fully assembled packet is returned the internal state is reset and this object can + /// be reused to assemble another packet. + /// + /// Will check that aad is the same for all fragments. + #[inline] + pub(crate) fn assemble( + &mut self, + nonce: [u8; 10], + fragment: Fragment, + fragment_no: u8, + fragment_count: u8, + ret_assembled: &mut Assembled, + ) { + if fragment_no < fragment_count && (fragment_count as usize) <= MAX_FRAGMENTS { + // If the counter has changed, reset the structure to receive a new packet. + if nonce != self.nonce { + self.drop_in_place(); + self.count = fragment_count as u32; + self.nonce = nonce; + self.size = 0; + } + + let got = 1u64.wrapping_shl(fragment_no as u32); + if got & self.have == 0 && self.count as u8 == fragment_count { + self.have |= got; + unsafe { + self.frags.get_unchecked_mut(fragment_no as usize).write(fragment); + } + if self.have == 1u64.wrapping_shl(self.count) - 1 { + self.have = 0; + self.count = 0; + self.nonce = [0; 10]; + self.size = 0; + // Setting 'have' to 0 resets the state of this object, and the fragments + // are effectively moved into the Assembled<> container and returned. That + // container will drop them when it is dropped. + ret_assembled.empty(); + ret_assembled.1 = fragment_count as usize; + unsafe { + std::ptr::copy_nonoverlapping(&self.frags[0], &mut ret_assembled.0[0], ret_assembled.1); + } + } + } + } + } + + /// Drops any remaining fragments and resets this object. + #[inline(always)] + pub fn drop_in_place(&mut self) { + if needs_drop::() { + let mut have = self.have; + let mut i = 0; + while have != 0 { + if (have & 1) != 0 { + debug_assert!(i < MAX_FRAGMENTS); + unsafe { self.frags.get_unchecked_mut(i).assume_init_drop() }; + } + have = have.wrapping_shr(1); + i += 1; + } + } + self.have = 0; + self.count = 0; + self.nonce = [0; 10]; + self.size = 0; + } +} + +impl Drop for Fragged { + #[inline(always)] + fn drop(&mut self) { + self.drop_in_place(); + } +} diff --git a/src/handshake_cache.rs b/src/handshake_cache.rs new file mode 100644 index 0000000..a69ce68 --- /dev/null +++ b/src/handshake_cache.rs @@ -0,0 +1,101 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::num::NonZeroU32; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, RwLock}; + +use crate::zssp::NoiseXKBobHandshakeState; +use crate::{proto::MAX_UNASSOCIATED_HANDSHAKE_STATES, ApplicationLayer}; + +pub(crate) struct UnassociatedHandshakeCache { + has_pending: AtomicBool, // Allowed to be falsely positive + cache: RwLock>, +} +/// SoA format +struct CacheInner { + local_ids: [Option; MAX_UNASSOCIATED_HANDSHAKE_STATES], + timeouts: [i64; MAX_UNASSOCIATED_HANDSHAKE_STATES], + handshakes: [Option>>; MAX_UNASSOCIATED_HANDSHAKE_STATES], +} + +/// Linear-search cache for capping the memory consumption of handshake data. +/// Designed specifically to have short and simple code that clearly bounds above +/// memory consumption. +impl UnassociatedHandshakeCache { + pub(crate) fn new() -> Self { + Self { + has_pending: AtomicBool::new(false), + cache: RwLock::new(CacheInner { + local_ids: std::array::from_fn(|_| None), + timeouts: std::array::from_fn(|_| 0), + handshakes: std::array::from_fn(|_| None), + }), + } + } + pub(crate) fn get(&self, local_id: NonZeroU32) -> Option>> { + let cache = self.cache.read().unwrap(); + for (i, id) in cache.local_ids.iter().enumerate() { + if *id == Some(local_id) { + return cache.handshakes[i].clone(); + } + } + None + } + pub(crate) fn insert(&self, local_id: NonZeroU32, state: Arc>, current_time: i64) { + let mut cache = self.cache.write().unwrap(); + let mut idx = 0; + for i in 0..cache.local_ids.len() { + if cache.local_ids[i].is_none() || cache.timeouts[i] < current_time { + idx = i; + break; + } else if cache.local_ids[i] == Some(local_id) { + return; + } + } + cache.local_ids[idx] = Some(local_id); + cache.timeouts[idx] = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); + cache.handshakes[idx] = Some(state); + self.has_pending.store(true, Ordering::Release); + } + pub(crate) fn remove(&self, local_id: NonZeroU32) -> bool { + let mut cache = self.cache.write().unwrap(); + for (i, id) in cache.local_ids.iter().enumerate() { + if *id == Some(local_id) { + cache.local_ids[i] = None; + cache.timeouts[i] = 0; + cache.handshakes[i] = None; + return true; + } + } + return false; + } + pub(crate) fn service(&self, current_time: i64) { + // Only check for expiration if we have a pending packet. + // This check is allowed to have false positives for simplicity's sake. + if self.has_pending.swap(false, Ordering::Acquire) { + // Check for packet expiration + let mut cache = self.cache.write().unwrap(); + let mut has_pending = false; + for i in 0..cache.local_ids.len() { + if cache.local_ids[i].is_some() { + if cache.timeouts[i] < current_time { + cache.local_ids[i] = None; + cache.timeouts[i] = 0; + cache.handshakes[i] = None; + } else { + has_pending = true; + } + } + } + if has_pending { + self.has_pending.store(true, Ordering::Release); + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2bcbcc0 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +mod applicationlayer; +mod frag_cache; +mod fragged; +mod handshake_cache; +mod log_event; +mod proto; +mod symmetric_state; +mod zssp; + +pub mod error; +pub use crate::applicationlayer::{ApplicationLayer, GetRatchetAction, SaveRatchetAction}; +pub use crate::log_event::LogEvent; +pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_FINGERPRINT_SIZE, RATCHET_KEY_SIZE}; +pub use crate::zssp::{AcceptSessionAction, Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/log_event.rs b/src/log_event.rs new file mode 100644 index 0000000..3c033f7 --- /dev/null +++ b/src/log_event.rs @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ +use std::sync::Arc; + +use crate::{ApplicationLayer, Session}; + +/// ZSSP events that might be interesting to log or aggregate into metrics. +pub enum LogEvent<'a, Application: ApplicationLayer> { + ServiceXK1Resend(&'a Arc>), + ServiceXK3Resend(&'a Arc>), + ServiceXKTimeout(&'a Arc>), + ServiceKKStart(&'a Arc>), + ServiceKK1Resend(&'a Arc>), + ServiceKK2Resend(&'a Arc>), + ServiceKKTimeout(&'a Arc>), + ServiceKeyConfirmResend(&'a Arc>), + ServiceKeyConfirmTimeout(&'a Arc>), + /// `(fragment_count, fragment_no, packet_type)` + ReceiveUnassociatedFragment(u8, u8, u8), + ReceiveUncheckedXK1, + ReceiveCheckXK1Challenge(bool), + ReceiveValidXK1, + ReceiveUncheckedDOSChallenge, + ReceiveValidDOSChallenge(&'a Arc>), + ReceiveUncheckedXK2, + ReceiveValidXK2(&'a Arc>), + ReceiveUncheckedXK3, + ReceiveValidXK3(&'a Application::Data), + ReceiveUncheckedKK1, + ReceiveValidKK1(&'a Arc>), + ReceiveUncheckedKK2, + ReceiveValidKK2(&'a Arc>), + ReceiveValidKeyConfirm(&'a Arc>), + ReceiveValidKeyDelete(&'a Arc>), +} +impl<'a, Application: ApplicationLayer> std::fmt::Debug for LogEvent<'a, Application> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use LogEvent::*; + match self { + ServiceXK1Resend(_) => write!(f, "ServiceXK1Resend"), + ServiceXK3Resend(_) => write!(f, "ServiceXK3Resend"), + ServiceXKTimeout(_) => write!(f, "ServiceXKTimeout"), + ServiceKKStart(_) => write!(f, "ServiceKKStart"), + ServiceKK1Resend(_) => write!(f, "ServiceKK1Resend"), + ServiceKK2Resend(_) => write!(f, "ServiceKK2Resend"), + ServiceKKTimeout(_) => write!(f, "ServiceKKTimeout"), + ServiceKeyConfirmResend(_) => write!(f, "ServiceKeyConfirmResend"), + ServiceKeyConfirmTimeout(_) => write!(f, "ServiceKeyConfirmTimeout"), + ReceiveUnassociatedFragment(arg0, arg1, arg2) => { + f.debug_tuple("ReceiveUnassociatedFragment").field(arg0).field(arg1).field(arg2).finish() + } + ReceiveUncheckedXK1 => write!(f, "ReceiveUncheckedXK1"), + ReceiveCheckXK1Challenge(arg0) => f.debug_tuple("ReceiveCheckXK1Challenge").field(arg0).finish(), + ReceiveValidXK1 => write!(f, "ReceiveValidXK1"), + ReceiveUncheckedDOSChallenge => write!(f, "ReceiveUncheckedDOSChallenge"), + ReceiveValidDOSChallenge(_) => write!(f, "ReceiveValidDOSChallenge"), + ReceiveUncheckedXK2 => write!(f, "ReceiveUncheckedXK2"), + ReceiveValidXK2(_) => write!(f, "ReceiveValidXK2"), + ReceiveUncheckedXK3 => write!(f, "ReceiveUncheckedXK3"), + ReceiveValidXK3(_) => write!(f, "ReceiveValidXK3"), + ReceiveUncheckedKK1 => write!(f, "ReceiveUncheckedKK1"), + ReceiveValidKK1(_) => write!(f, "ReceiveValidKK1"), + ReceiveUncheckedKK2 => write!(f, "ReceiveUncheckedKK2"), + ReceiveValidKK2(_) => write!(f, "ReceiveValidKK2"), + ReceiveValidKeyConfirm(_) => write!(f, "ReceiveValidKeyConfirm"), + ReceiveValidKeyDelete(_) => write!(f, "ReceiveValidKeyDelete"), + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..975276d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,333 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ +use std::iter::ExactSizeIterator; +use std::str::FromStr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{mpsc, Arc, Mutex}; +use std::thread; +use std::time::Duration; + +use zerotier_crypto::p384::{P384KeyPair, P384PublicKey}; +use zerotier_crypto::{random, secure_eq}; +use zssp::{ + AcceptSessionAction, GetRatchetAction, IncomingSessionAction, LogEvent, SaveRatchetAction, Session, RATCHET_FINGERPRINT_SIZE, RATCHET_KEY_SIZE, +}; + +const TEST_MTU: usize = 1500; + +struct TestApplication { + name: &'static str, + identity_key: P384KeyPair, + ratchets: Mutex<(u64, [(u64, [u8; RATCHET_FINGERPRINT_SIZE], [u8; RATCHET_KEY_SIZE]); 2])>, +} + +impl zssp::ApplicationLayer for TestApplication { + const REKEY_AFTER_TIME_MS: i64 = 4000; + const REKEY_AFTER_TIME_MAX_JITTER_MS: i64 = 2000; + + const RETRY_INTERVAL_MS: i64 = 250; + const INITIAL_OFFER_TIMEOUT_MS: i64 = 2000; + const EXPIRATION_TIMEOUT_MS: i64 = 60000; + + type Data = (); + type IncomingPacketBuffer = Vec; + type LocalIdentityBlob = [u8; 0]; + + fn local_s_keypair(&self) -> &P384KeyPair { + &self.identity_key + } + fn save_ratchet_state( + &self, + _: &P384PublicKey, + _: &Self::Data, + action: SaveRatchetAction, + ratchet_number: u64, + ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], + ratchet_key: &[u8; RATCHET_KEY_SIZE], + _: i64, + ) -> Result<(), ()> { + let latest_idx = ratchet_number as usize % 2; + let mut ratchets = self.ratchets.lock().unwrap(); + if action.save_latest() { + ratchets.1[latest_idx] = (ratchet_number, *ratchet_fingerprint, *ratchet_key); + } + if action.confirm_latest() { + ratchets.0 = ratchet_number; + } + if action.delete_previous() { + ratchets.1[latest_idx ^ 1] = (0, [0; RATCHET_FINGERPRINT_SIZE], [0; RATCHET_KEY_SIZE]); + } + Ok(()) + } + fn lookup_ratchet(&self, ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], _: i64) -> Result { + let r = self.ratchets.lock().unwrap(); + for state in &r.1 { + if secure_eq(&state.1, ratchet_fingerprint) { + return Ok(GetRatchetAction::Found(state.0, state.2)); + } + } + panic!() + } + fn allow_zero_ratchet(&self, _: i64) -> bool { + true + } + fn allow_downgrade(&self, _: &Arc>, _: i64) -> bool { + true + } + fn event_log(&self, event: LogEvent<'_, Self>, _: i64) { + println!("> [{}] {:?}", self.name, event); + match event { + LogEvent::ServiceKKTimeout(_) => panic!(), + _ => (), + } + } +} + +fn alice_main( + run: &AtomicBool, + packet_success_rate: u32, + alice_app: &TestApplication, + bob_app: &TestApplication, + alice_out: mpsc::SyncSender>, + alice_in: mpsc::Receiver>, +) { + let startup_time = std::time::Instant::now(); + let context = zssp::Context::::new(); + let mut data_buf = [0u8; 65536]; + let mut next_service = startup_time.elapsed().as_millis() as i64 + 500; + let test_data = [1u8; TEST_MTU * 10]; + let mut up = false; + let mut alice_session = None; + + while run.load(Ordering::Relaxed) { + if alice_session.is_none() { + up = false; + let ratchets = alice_app.ratchets.lock().unwrap(); + let ratchet_state = ratchets.1[ratchets.0 as usize % 2]; + alice_session = Some( + context + .open( + alice_app, + |b| alice_out.send(b.to_vec()).is_ok(), + TEST_MTU, + bob_app.identity_key.to_public_key(), + (), + Some(ratchet_state), + [], + startup_time.elapsed().as_millis() as i64, + ) + .unwrap(), + ); + println!("[alice] opening session"); + } + let current_time = startup_time.elapsed().as_millis() as i64; + loop { + let pkt = alice_in.try_recv(); + if let Ok(pkt) = pkt { + if (random::xorshift64_random() as u32) <= packet_success_rate { + use zssp::SessionEvent::*; + match context.receive( + alice_app, + || panic!(), + |_, _, _| panic!(), + |b| alice_out.send(b.to_vec()).is_ok(), + TEST_MTU, + |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)), + &0, + &mut data_buf, + pkt, + current_time, + ) { + Ok(zssp::ReceiveResult::Unassociated) => { + //println!("[alice] ok"); + } + Ok(zssp::ReceiveResult::Session(_, event)) => match event { + Established(ratchet_number) => { + up = true; + println!("[alice] new ratchet key #{}", ratchet_number); + } + Data(data) => { + assert!(!data.is_empty()); + //println!("[alice] received {}", data.len()); + } + NewSession(..) => panic!(), + Ratchet(ratchet_number) => { + println!("[alice] new ratchet key #{}", ratchet_number); + } + Rejected => panic!(), + Control => (), + }, + Ok(zssp::ReceiveResult::Rejected) => {} + Err(e) => { + println!("[alice] ERROR {:?}", e); + if let zssp::error::ReceiveError::ByzantineFault { is_naturally_occurring, .. } = e { + assert!(is_naturally_occurring) + } + } + } + } + } else { + break; + } + } + + if up { + context + .send( + alice_session.as_ref().unwrap(), + |b| alice_out.send(b.to_vec()).is_ok(), + &mut data_buf[..TEST_MTU], + &test_data[..1400 + ((random::xorshift64_random() as usize) % (test_data.len() - 1400))], + current_time, + ) + .unwrap(); + } else { + thread::sleep(Duration::from_millis(10)); + } + // TODO: we need to more comprehensively test if re-opening the session works + if (random::xorshift64_random() as u32) <= ((u32::MAX as f64) * 0.00000025) as u32 { + alice_session = None; + } + + if current_time >= next_service { + next_service = current_time + + context.service( + alice_app, + |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)), + current_time, + ); + } + } +} + +fn bob_main( + run: &AtomicBool, + packet_success_rate: u32, + _alice_app: &TestApplication, + bob_app: &TestApplication, + bob_out: mpsc::SyncSender>, + bob_in: mpsc::Receiver>, +) { + let startup_time = std::time::Instant::now(); + let context = zssp::Context::::new(); + let mut data_buf = [0u8; 65536]; + let mut data_buf_2 = [0u8; TEST_MTU]; + let mut last_speed_metric = startup_time.elapsed().as_millis() as i64; + let mut next_service = last_speed_metric + 500; + let mut transferred = 0u64; + + let mut bob_session = None; + + while run.load(Ordering::Relaxed) { + let pkt = bob_in.recv_timeout(Duration::from_millis(100)); + let current_time = startup_time.elapsed().as_millis() as i64; + + if let Ok(pkt) = pkt { + if (random::xorshift64_random() as u32) <= packet_success_rate { + use zssp::SessionEvent::*; + match context.receive( + bob_app, + || IncomingSessionAction::Allow, + |_, _, _| AcceptSessionAction::Accept(()), + |b| bob_out.send(b.to_vec()).is_ok(), + TEST_MTU, + |_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)), + &0, + &mut data_buf, + pkt, + current_time, + ) { + Ok(zssp::ReceiveResult::Unassociated) => {} + Ok(zssp::ReceiveResult::Session(s, event)) => match event { + NewSession(ratchet_number) => { + println!("[bob] new session, took {}s", current_time as f32 / 1000.0); + let _ = bob_session.replace(s); + println!("[bob] new ratchet key #{}", ratchet_number); + } + Data(data) => { + assert!(!data.is_empty()); + //println!("[bob] received {}", data.len()); + context + .send(&s, |b| bob_out.send(b.to_vec()).is_ok(), &mut data_buf_2, data.as_mut(), current_time) + .unwrap(); + transferred += data.len() as u64 * 2; // *2 because we are also sending this many bytes back + } + Established(_) => panic!(), + Rejected => panic!(), + Ratchet(ratchet_number) => { + println!("[bob] new ratchet key #{}", ratchet_number); + } + Control => (), + }, + Ok(zssp::ReceiveResult::Rejected) => {} + Err(e) => { + println!("[bob] ERROR {:?}", e); + if let zssp::error::ReceiveError::ByzantineFault { is_naturally_occurring, .. } = e { + assert!(is_naturally_occurring) + } + } + } + } + } + + let speed_metric_elapsed = current_time - last_speed_metric; + if speed_metric_elapsed >= 10000 { + last_speed_metric = current_time; + println!( + "[bob] throughput: {} MiB/sec (combined input and output)", + ((transferred as f64) / 1048576.0) / ((speed_metric_elapsed as f64) / 1000.0) + ); + transferred = 0; + } + + if current_time >= next_service { + next_service = current_time + + context.service( + bob_app, + |_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)), + current_time, + ); + } + } +} + +fn main() { + let run = AtomicBool::new(true); + + let alice_app = TestApplication { + name: "alice", + identity_key: P384KeyPair::generate(), + ratchets: Mutex::new((0, std::array::from_fn(|_| (0, [0u8; RATCHET_FINGERPRINT_SIZE], [0u8; RATCHET_KEY_SIZE])))), + }; + let bob_app = TestApplication { + name: "bob", + identity_key: P384KeyPair::generate(), + ratchets: Mutex::new((0, std::array::from_fn(|_| (0, [0u8; RATCHET_FINGERPRINT_SIZE], [0u8; RATCHET_KEY_SIZE])))), + }; + + let (alice_out, bob_in) = mpsc::sync_channel::>(256); + let (bob_out, alice_in) = mpsc::sync_channel::>(256); + + let args = std::env::args(); + let packet_success_rate = if args.len() <= 1 { + let default_success_rate = 1.0; + ((u32::MAX as f64) * default_success_rate) as u32 + } else { + ((u32::MAX as f64) * f64::from_str(args.last().unwrap().as_str()).unwrap()) as u32 + }; + + thread::scope(|ts| { + ts.spawn(|| alice_main(&run, packet_success_rate, &alice_app, &bob_app, alice_out, alice_in)); + ts.spawn(|| bob_main(&run, packet_success_rate, &alice_app, &bob_app, bob_out, bob_in)); + + thread::sleep(Duration::from_secs(60 * 60)); + + run.store(false, Ordering::SeqCst); + println!("finished"); + }); +} diff --git a/src/proto.rs b/src/proto.rs new file mode 100644 index 0000000..5fa7e68 --- /dev/null +++ b/src/proto.rs @@ -0,0 +1,293 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::hash::Hasher; +use std::mem::size_of; + +use hex_literal::hex; +use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; +use zerotier_crypto::constant::AES_GCM_TAG_SIZE; +use zerotier_crypto::hash::{SHA512, SHA512_HASH_SIZE}; +use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE; + +/// Minimum size of a valid physical ZSSP packet of any type. Anything smaller is discarded. +pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; + +/// Minimum physical MTU for ZSSP to function. +pub const MIN_TRANSPORT_MTU: usize = 128; + +pub const RATCHET_KEY_SIZE: usize = 32; + +pub const RATCHET_FINGERPRINT_SIZE: usize = 32; + +/// The application has the ability to attach a data payload to Alice's handshake. +/// It will be the first payload Bob receives from Alice. +/// The application also must attach a static public identity to their handshake. +/// The combined size of both in bytes must be at most this value. +/// +/// If not ZSSP will return `OpenError::DataTooLarge` and refuse to create a session object. +pub const MAX_IDENTITY_BLOB_SIZE: usize = NoiseXKPattern3::MAX_SIZE - NoiseXKPattern3::MIN_SIZE; + +/// Initial value of 'h'. +/// echo -n 'Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512' | shasum -a 512 +pub(crate) const INITIAL_H: [u8; SHA512_HASH_SIZE] = + hex!("cd1f422196a5a614e24392cf34dcbf340ee61ad6ee6834274ff35fd42a7a5c44d04a045101555548a291778dd036b93ae21005a26c003213f57a5df9fb17f745"); +/// Initial value of 'ck' for rekeying. +/// echo -n 'Noise_KKpsk0_P384_AESGCM_SHA512' | shasum -a 512 +pub(crate) const INITIAL_H_REKEY: [u8; SHA512_HASH_SIZE] = + hex!("daeedd651ac9c5173f2eaaff996beebac6f3f1bfe9a70bb1cc54fa1fb2bf46260d71a3c4fb4d4ee36f654c31773a8a15e5d5be974a0668dc7db70f4e13ed172e"); + +pub(crate) const SESSION_ID_SIZE: usize = 4; + +pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_1: u8 = 0; +pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_2: u8 = 1; +pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_3: u8 = 2; +pub(crate) const PACKET_TYPE_KEY_CONFIRM: u8 = 3; +pub(crate) const PACKET_TYPE_KEY_DELETE: u8 = 4; +pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_1: u8 = 5; +pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_2: u8 = 6; +pub(crate) const PACKET_TYPE_SESSION_REJECTED: u8 = 7; +pub(crate) const PACKET_TYPE_DATA: u8 = 8; +pub(crate) const PACKET_TYPE_BOB_DOS_CHALLENGE: u8 = 9; +pub(crate) const PACKET_TYPE_RANGE_TRANSPORT: std::ops::Range = 3..9; + +/// Noise asks that the counter be initialized to 0 but for out of order reasons we have +/// to start it at 1. +/// Since with unreliable transport the first counter could always end up dropped this is +/// functionally equivalent to initializing to 0. +pub(crate) const INIT_COUNTER: u64 = 0; +pub(crate) const LABEL_RATCHET_STATE: u8 = b'R'; +pub(crate) const LABEL_HEADER_KEY: u8 = b'H'; +pub(crate) const LABEL_KEX_KEY: u8 = b'K'; + +/// Size of keys used during derivation, mixing, etc. +pub(crate) const NOISE_HASHLEN: usize = SHA512_HASH_SIZE; + +pub(crate) const HEADER_SIZE: usize = 16; +pub(crate) const HEADER_PROTECT_ENC_START: usize = 4; +pub(crate) const HEADER_PROTECT_ENC_END: usize = 20; +pub(crate) const CHALLENGE_COUNTER_SIZE: usize = 8; +pub(crate) const CHALLENGE_MAC_SIZE: usize = 16; +pub(crate) const CHALLENGE_POW_SIZE: usize = 8; +pub(crate) const CHALLENGE_SALT_SIZE: usize = 32; + +pub(crate) const MAX_NOISE_HANDSHAKE_SIZE: usize = MAX_FRAGMENTS * MIN_TRANSPORT_MTU; +pub(crate) const CONTROL_PACKET_MAX_SIZE: usize = HEADER_SIZE + NoiseKKPattern1or2::SIZE + AES_GCM_TAG_SIZE; +pub(crate) const CONTROL_PACKET_MIN_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; + +/// Determines the number of counters a session will remember. If a counter arrives over +/// this amount out of order relative to other received counters, it is likely to be +/// rejected on the basis that the session can't remember if this counter was replayed. +/// Increasing this value makes a session consume more memory. +pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 64; +/// Maximum number of counter steps that the counter is allowed to skip ahead. +/// This cannot be changed away from 2^24 without changing the header nonce handling code. +pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 16777216; +/// Similar to `COUNTER_WINDOW_MAX_OOO`, except this governs the receive context challenge +/// counter rather than the session counter. +/// When Bob issues a challenge to Alice to mitigate DDOS, Bob will only accept Alice's +/// response once, and then its attached counter is added to the window. +pub(crate) const CHALLENGE_COUNTER_WINDOW_MAX_OOO: usize = 32; +/// We hard-expire the Noise counter long before we reach u64::MAX because of the ABA problem. +/// Over (1<<16) threads would have to attempt to increment the counter at the same time +/// to overflow it. +/// Having (1<<16) threads active at the same time would crash basically any system. +pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - (1 << 16); + +/// Maximum number of fragments a single packet may be split into. If a packet cannot fit +/// into this number of fragments it will be dropped. +pub(crate) const MAX_FRAGMENTS: usize = 48; // hard protocol max: 63 +/// Maximum window over which session packets may be reordered to be defragmented and +/// reassembled. Out of order fragments may be dropped in favor of newer fragments. +/// Increasing this value makes a session consume more significantly more memory. +pub(crate) const SESSION_MAX_FRAGMENTS_OOO: usize = 32; + +/// The maximum number of unassociated packets that a receive context will cache. +/// Additional packets will either be dropped or cause a different packet to be dropped +/// from the cache. +/// Larger values consume more memory but provide better reliability and DDOS resistance. +pub(crate) const MAX_UNASSOCIATED_PACKETS: usize = 32; +/// The maximum number of fragments of unassociated packets that a receive context will +/// cache. +/// All unassociated fragments share the same buffer, when it fills up additional +/// fragments will be dropped or cause other fragments to be dropped from the cache. +/// Larger values consume more memory but provide better reliability and DDOS resistance. +pub(crate) const MAX_UNASSOCIATED_FRAGMENTS: usize = 32 * 32; +/// The maximum number of `NoiseXKBobHandshakeState` that a receive context will cache. +/// These are extremely large and since Alice has not been authenticated we put a hard +/// limit to how many we cache. +/// Larger values consume more memory but provide better reliability and DDOS resistance. +pub(crate) const MAX_UNASSOCIATED_HANDSHAKE_STATES: usize = 32; + +/// The maximum size a packet that is not associated to a session may be. +/// Excludes the size of headers for fragmentation. +pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = NoiseXKPattern1::SIZE - HEADER_SIZE; + +/* +XKhfs+psk2: + <- s + ... + -> e, es, e1 + <- e, ee, ekem1, psk + -> s, se +*/ +/* +KKpsk0: + -> s + <- s + ... + -> psk, e, es, ss + <- e, ee, se +*/ +/* +Header: + [0..4] recipient key id +-- start AES(ck_es * h_e_e1_p) encrypted block -- + [4] fragment count (1..255) + [5] fragment number (0..254) + [6] reserved zero +-- start AES-GCM Nonce -- + [7] packet type + [8..16] 64-bit counter or packet id +*/ +/// The first packet in Noise_XK exchange containing Alice's ephemeral keys, key id, +/// and a random symmetric key to protect header fragmentation fields for this session. +#[repr(C, packed)] +pub(crate) struct NoiseXKPattern1 { + pub header: [u8; HEADER_SIZE], + /// -- start prologue -- + pub alice_key_id: [u8; SESSION_ID_SIZE], + /// -- end prologue -- + pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], + /// -- start AES-GCM(k_es) encrypted section + pub noise_e1: [u8; KYBER_PUBLICKEYBYTES], + /// -- end encrypted section + pub e1_gcm_tag: [u8; AES_GCM_TAG_SIZE], + /// -- start AES-GCM(k_es) encrypted section + pub ratchet_fingerprint: [u8; RATCHET_FINGERPRINT_SIZE], + /// -- end encrypted section + pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], + pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE], + pub challenge_mac: [u8; CHALLENGE_MAC_SIZE], + pub challenge_pow: [u8; CHALLENGE_POW_SIZE], +} + +impl NoiseXKPattern1 { + pub const PROLOGUE_START: usize = HEADER_SIZE; + pub const PROLOGUE_END: usize = Self::PROLOGUE_START + SESSION_ID_SIZE; + pub const E1_ENC_START: usize = Self::PROLOGUE_END + P384_PUBLIC_KEY_SIZE; + pub const E1_AUTH_START: usize = Self::E1_ENC_START + KYBER_PUBLICKEYBYTES; + pub const P_ENC_START: usize = Self::E1_AUTH_START + AES_GCM_TAG_SIZE; + pub const P_AUTH_START: usize = Self::P_ENC_START + RATCHET_FINGERPRINT_SIZE; + pub const P_AUTH_END: usize = Self::P_AUTH_START + AES_GCM_TAG_SIZE; + pub const SIZE: usize = Self::P_AUTH_END + CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; +} + +#[repr(C, packed)] +pub(crate) struct BobDOSChallenge { + pub header: [u8; HEADER_SIZE], + pub alice_key_id: [u8; SESSION_ID_SIZE], + pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE], + pub challenge_mac: [u8; CHALLENGE_MAC_SIZE], + pub prior_challenge_pow: [u8; CHALLENGE_POW_SIZE], +} + +impl BobDOSChallenge { + pub const SIZE: usize = HEADER_SIZE + SESSION_ID_SIZE + CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; +} + +/// The response to NoiseXKPattern1 containing Bob's ephemeral keys. +#[repr(C, packed)] +pub(crate) struct NoiseXKPattern2 { + pub header: [u8; HEADER_SIZE], + pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], + /// -- start AES-GCM(k_es_ee) encrypted section + pub noise_ekem1: [u8; KYBER_CIPHERTEXTBYTES], + /// -- end encrypted section + pub ekem1_gcm_tag: [u8; AES_GCM_TAG_SIZE], + /// -- start AES-GCM(k_es_ee_ekem1_psk) encrypted section + pub bob_key_id: [u8; SESSION_ID_SIZE], + /// -- end encrypted section + pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], +} + +impl NoiseXKPattern2 { + pub const EKEM1_ENC_START: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE; + pub const EKEM1_AUTH_START: usize = Self::EKEM1_ENC_START + KYBER_CIPHERTEXTBYTES; + pub const P_ENC_START: usize = Self::EKEM1_AUTH_START + AES_GCM_TAG_SIZE; + pub const P_AUTH_START: usize = Self::P_ENC_START + SESSION_ID_SIZE; + pub const P_AUTH_END: usize = Self::P_AUTH_START + AES_GCM_TAG_SIZE; + pub const SIZE: usize = Self::P_AUTH_END; +} + +/// Alice's final response containing her identity (she already knows Bob's) and meta-data. +/// While Alice's response does match what is described in this struct, +/// this struct is unused because it would contain variable length fields. +/// It is present here for documentation purposes. +#[repr(C, packed)] +pub(crate) struct NoiseXKPattern3 { + pub header: [u8; HEADER_SIZE], + /// -- start AES-GCM(k_es_ee_ekem1_psk) encrypted section + pub noise_s: [u8; P384_PUBLIC_KEY_SIZE], + /// -- end encrypted section + pub s_gcm_tag: [u8; AES_GCM_TAG_SIZE], + /// -- start AES-GCM(k_es_ee_ekem1_psk_se) encrypted section + pub alice_blob: [u8; 0], + /// -- end encrypted section + pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], +} +impl NoiseXKPattern3 { + pub const MIN_SIZE: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; + pub const MAX_SIZE: usize = MAX_NOISE_HANDSHAKE_SIZE; +} + +#[repr(C, packed)] +pub(crate) struct NoiseKKPattern1or2 { + pub header: [u8; HEADER_SIZE], + pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], + pub key_id: [u8; SESSION_ID_SIZE], + pub gcm_tag: [u8; AES_GCM_TAG_SIZE], + pub kek_tag: [u8; AES_GCM_TAG_SIZE], +} +impl NoiseKKPattern1or2 { + pub const ENC_START: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE; + pub const AUTH_START: usize = Self::ENC_START + SESSION_ID_SIZE; + pub const AUTH_END: usize = Self::AUTH_START + AES_GCM_TAG_SIZE; + pub const SIZE: usize = Self::AUTH_END + AES_GCM_TAG_SIZE; +} + +// Annotate only these structs as being compatible with byte_array_as_proto_buffer(). These structs +// are packed flat buffers containing only byte or byte array fields, making them safe to treat +// this way even on architectures that require type size aligned access. +pub(crate) trait ProtocolFlatBuffer {} +impl ProtocolFlatBuffer for NoiseXKPattern1 {} +impl ProtocolFlatBuffer for NoiseXKPattern2 {} +impl ProtocolFlatBuffer for NoiseKKPattern1or2 {} +impl ProtocolFlatBuffer for BobDOSChallenge {} + +#[inline(always)] +pub(crate) fn byte_array_as_proto_buffer(b: &[u8]) -> &B { + assert_eq!(b.len(), size_of::()); + unsafe { &*b.as_ptr().cast() } +} + +#[inline(always)] +pub(crate) fn byte_array_as_proto_buffer_mut(b: &mut [u8]) -> &mut B { + assert_eq!(b.len(), size_of::()); + unsafe { &mut *b.as_mut_ptr().cast() } +} +/// Trick rust into letting us use a hasher that returns more than 64 bits. +pub(crate) struct SHAHasher<'a>(pub &'a mut SHA512); +impl<'a> Hasher for SHAHasher<'a> { + fn finish(&self) -> u64 { + panic!() + } + fn write(&mut self, bytes: &[u8]) { + self.0.update(bytes) + } +} diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs new file mode 100644 index 0000000..6d1697f --- /dev/null +++ b/src/symmetric_state.rs @@ -0,0 +1,152 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ +use zerotier_crypto::constant::AES_256_KEY_SIZE; +use zerotier_crypto::hash::{HMACSHA512, HMAC_SHA512_SIZE}; +use zerotier_crypto::secret::Secret; + +use crate::proto::NOISE_HASHLEN; + +#[derive(Clone)] +pub(crate) struct SymmetricState { + chaining_key: Secret, + token_counter: u8, +} + +impl SymmetricState { + pub(crate) fn new(h: [u8; NOISE_HASHLEN]) -> Self { + debug_assert_eq!(NOISE_HASHLEN, HMAC_SHA512_SIZE); + Self { chaining_key: Secret(h), token_counter: b'P' } + } + /// Corresponds to Noise `MixKey`. + pub(crate) fn mix_key(&mut self, input_key_material: &[u8]) { + let mut next_ck = Secret::new(); + + self.kbkdf(input_key_material, self.label(), 2, next_ck.as_bytes_mut(), None, None); + self.token_counter += 1; + + self.chaining_key.overwrite(&next_ck); + // We don't need a key at this step of Noise, so generating that key and calling + // `InitializeKey` would be completely pointless. + } + /// Corresponds to Noise `MixKey` followed by `InitializeKey`. + #[inline(always)] + pub(crate) fn mix_key_initialize_key(&mut self, input_key_material: &[u8]) -> Secret { + let mut next_ck = Secret::new(); + let mut temp_k = [0u8; NOISE_HASHLEN]; + + self.kbkdf(input_key_material, self.label(), 2, next_ck.as_bytes_mut(), Some(&mut temp_k), None); + self.token_counter += 1; + + self.chaining_key.overwrite(&next_ck); + Secret::from_bytes_then_nuke(&mut temp_k[..AES_256_KEY_SIZE]) + } + /// Corresponds to Noise `MixKeyAndHash`. + pub(crate) fn mix_key_and_hash(&mut self, input_key_material: &[u8]) -> [u8; NOISE_HASHLEN] { + let mut next_ck = Secret::new(); + let mut temp_h = [0u8; NOISE_HASHLEN]; + + self.kbkdf(input_key_material, self.label(), 3, next_ck.as_bytes_mut(), Some(&mut temp_h), None); + self.token_counter += 1; + + self.chaining_key.overwrite(&next_ck); + temp_h + } + /// Corresponds to Noise `MixKeyAndHash` followed by `InitializeKey`. + pub(crate) fn mix_key_and_hash_initialize_key(&mut self, input_key_material: &[u8]) -> ([u8; NOISE_HASHLEN], Secret) { + let mut next_ck = Secret::new(); + let mut temp_h = [0u8; NOISE_HASHLEN]; + let mut temp_k = [0u8; NOISE_HASHLEN]; + + self.kbkdf( + input_key_material, + self.label(), + 3, + next_ck.as_bytes_mut(), + Some(&mut temp_h), + Some(&mut temp_k), + ); + self.token_counter += 1; + + self.chaining_key.overwrite(&next_ck); + (temp_h, Secret::from_bytes_then_nuke(&mut temp_k[..AES_256_KEY_SIZE])) + } + /// Get an additional symmetric key (ASK) that is a collision resistant hash of the transcript, + /// is forward secrect and is cryptographically independent from all other produced keys. + /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. + /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. + #[inline(always)] + pub(crate) fn get_ask2(&self, label: u8, noise_h: &[u8; NOISE_HASHLEN]) -> (Secret, Secret) { + let mut temp_k1 = [0u8; NOISE_HASHLEN]; + let mut temp_k2 = [0u8; NOISE_HASHLEN]; + self.kbkdf(noise_h, [b'A', b'S', b'K', label], 2, &mut temp_k1, Some(&mut temp_k2), None); + ( + Secret::from_bytes_then_nuke(&mut temp_k1[..AES_256_KEY_SIZE]), + Secret::from_bytes_then_nuke(&mut temp_k2[..AES_256_KEY_SIZE]), + ) + } + /// Corresponds to Noise `Split`. + #[inline(always)] + pub(crate) fn split(self) -> (Secret, Secret) { + let mut temp_k1 = [0u8; NOISE_HASHLEN]; + let mut temp_k2 = [0u8; NOISE_HASHLEN]; + self.kbkdf(&[], self.label(), 2, &mut temp_k1, Some(&mut temp_k2), None); + // Normally KBKDF would not truncate to derive the correct length of AES keys, + // but Noise specifies that the AES keys be truncated from NOISE_HASHLEN to AES_256_KEY_SIZE. + ( + Secret::from_bytes_then_nuke(&mut temp_k1[..AES_256_KEY_SIZE]), + Secret::from_bytes_then_nuke(&mut temp_k2[..AES_256_KEY_SIZE]), + ) + } + #[inline(always)] + fn label(&self) -> [u8; 4] { + [b'Z', b'S', b'S', self.token_counter] + } + /// HMAC-SHA512 key derivation based on KBKDF Counter Mode: + /// https://csrc.nist.gov/publications/detail/sp/800-108/rev-1/final. + /// Cryptographically this isn't meaningfully different from + /// `HKDF(self.chaining_key, input_key_material)` but this is how NIST rolls. + /// These are the values we have assigned to the 4 variables involved in their KDF: + /// * K_IN = `input_key_material` + /// * Label = `label` + /// * Context = `self.chaining_key` + /// * L = `num_outputs*512u16` + /// We have intentionally made every input small and fixed size to avoid unnecessary complexity + /// and data representation ambiguity. + #[inline(always)] + fn kbkdf( + &self, + input_key_material: &[u8], + label: [u8; 4], + num_outputs: u16, + output1: &mut [u8; NOISE_HASHLEN], + output2: Option<&mut [u8; NOISE_HASHLEN]>, + output3: Option<&mut [u8; NOISE_HASHLEN]>, + ) { + let l = &(num_outputs * 512u16).to_be_bytes(); + + let mut hm = HMACSHA512::new(input_key_material); + hm.update(&[1, label[0], label[1], label[2], label[3], 0x00]); + hm.update(self.chaining_key.as_bytes()); + hm.update(l); + *output1 = hm.finish(); + if let Some(output2) = output2 { + hm.reset(input_key_material); + hm.update(&[2, label[0], label[1], label[2], label[3], 0x00]); + hm.update(self.chaining_key.as_bytes()); + hm.update(l); + *output2 = hm.finish(); + } + if let Some(output3) = output3 { + hm.reset(input_key_material); + hm.update(&[3, label[0], label[1], label[2], label[3], 0x00]); + hm.update(self.chaining_key.as_bytes()); + hm.update(l); + *output3 = hm.finish(); + } + } +} diff --git a/src/utils/Cargo.toml b/src/utils/Cargo.toml new file mode 100644 index 0000000..b548cf6 --- /dev/null +++ b/src/utils/Cargo.toml @@ -0,0 +1,22 @@ +[package] +authors = ["ZeroTier, Inc. "] +edition = "2021" +license = "MPL-2.0" +name = "zerotier-utils" +version = "0.1.0" + +[features] +default = [] +tokio = ["dep:tokio"] + +[dependencies] +serde = { version = "^1", features = ["derive"], default-features = false } +serde_json = { version = "^1", features = ["std"], default-features = false } +tokio = { version = "^1", default-features = false, features = ["fs", "io-util", "io-std", "net", "process", "rt", "rt-multi-thread", "signal", "sync", "time"], optional = true } + +[target."cfg(windows)".dependencies] +winapi = { version = "^0", features = ["handleapi", "ws2ipdef", "ws2tcpip"] } + +[target."cfg(not(windows))".dependencies] +libc = "^0" +signal-hook = "^0" diff --git a/src/utils/rustfmt.toml b/src/utils/rustfmt.toml new file mode 120000 index 0000000..39f97b0 --- /dev/null +++ b/src/utils/rustfmt.toml @@ -0,0 +1 @@ +../rustfmt.toml \ No newline at end of file diff --git a/src/utils/src/arc_pool.rs b/src/utils/src/arc_pool.rs new file mode 100644 index 0000000..1b9131b --- /dev/null +++ b/src/utils/src/arc_pool.rs @@ -0,0 +1,769 @@ +use std::fmt::{Debug, Display}; +use std::marker::PhantomData; +use std::mem::{self, ManuallyDrop, MaybeUninit}; +use std::num::NonZeroU64; +use std::ops::Deref; +use std::ptr::{self, NonNull}; +use std::sync::{ + atomic::{AtomicPtr, AtomicU32, Ordering}, + Mutex, RwLock, RwLockReadGuard, +}; + +const DEFAULT_L: usize = 64; + +union SlotState { + empty_next: *mut Slot, + full_obj: ManuallyDrop, +} +struct Slot { + obj: SlotState, + free_lock: RwLock<()>, + ref_count: AtomicU32, + uid: u64, +} + +struct PoolMem { + mem: [MaybeUninit>; L], + pre: *mut PoolMem, +} + +/// A generic, *thread-safe*, fixed-sized memory allocator for instances of `T`. +/// New instances of `T` are packed together into arrays of size `L`, and allocated in bulk as one memory arena from the global allocator. +/// Arenas from the global allocator are not deallocated until the pool is dropped, and are re-used as instances of `T` are allocated and freed. +/// +/// This specific datastructure also supports generational indexing, which means that an arbitrary number of non-owning references to allocated instances of `T` can be generated safely. These references can outlive the underlying `T` they reference, and will safely report upon dereference that the original underlying `T` is gone. +/// +/// Atomic reference counting is also implemented allowing for exceedingly complex models of shared ownership. Multiple copies of both strong and weak references to the underlying `T` can be generated that are all memory safe and borrow-checked. +/// +/// Allocating from a pool results in very little internal and external fragmentation in the global heap, thus saving significant amounts of memory from being used by one's program. Pools also allocate memory significantly faster on average than the global allocator. This specific pool implementation supports guaranteed constant time `alloc` and `free`. +pub struct Pool(Mutex<(*mut Slot, u64, *mut PoolMem, usize)>); +unsafe impl Send for Pool {} +unsafe impl Sync for Pool {} + +impl Pool { + pub const DEFAULT_L: usize = DEFAULT_L; + + /// Creates a new `Pool` with packing length `L`. Packing length determines the number of instances of `T` that will fit in a page before it becomes full. Once all pages in a `Pool` are full a new page is allocated from the Global allocator. Larger values of `L` are generally faster, but the returns are diminishing and vary by platform. + /// + /// A `Pool` cannot be interacted with directly, it requires a `impl StaticPool for Pool` implementation. See the `static_pool!` macro for automatically generated trait implementation. + #[inline] + pub const fn new() -> Self { + Pool(Mutex::new((ptr::null_mut(), 1, ptr::null_mut(), usize::MAX))) + } + + #[inline(always)] + fn create_arr() -> [MaybeUninit>; L] { + unsafe { MaybeUninit::<[MaybeUninit>; L]>::uninit().assume_init() } + } + + /// Allocates uninitialized memory for an instance `T`. The returned pointer points to this memory. It is undefined what will be contained in this memory, it must be initialized before being used. This pointer must be manually freed from the pool using `Pool::free_ptr` before being dropped, otherwise its memory will be leaked. If the pool is dropped before this pointer is freed, the destructor of `T` will not be run and this pointer will point to invalid memory. + unsafe fn alloc_ptr(&self, obj: T) -> NonNull> { + let mut mutex = self.0.lock().unwrap(); + let (mut first_free, uid, mut head_arena, mut head_size) = *mutex; + + let slot_ptr = if let Some(mut slot_ptr) = NonNull::new(first_free) { + let slot = slot_ptr.as_mut(); + let _announce_free = slot.free_lock.write().unwrap(); + debug_assert_eq!(slot.uid, 0); + first_free = slot.obj.empty_next; + slot.ref_count = AtomicU32::new(1); + slot.uid = uid; + slot.obj.full_obj = ManuallyDrop::new(obj); + slot_ptr + } else { + if head_size >= L { + let new = Box::leak(Box::new(PoolMem { pre: head_arena, mem: Self::create_arr() })); + head_arena = new; + head_size = 0; + } + let slot = Slot { + obj: SlotState { full_obj: ManuallyDrop::new(obj) }, + free_lock: RwLock::new(()), + ref_count: AtomicU32::new(1), + uid, + }; + let slot_ptr = &mut (*head_arena).mem[head_size]; + let slot_ptr = NonNull::new_unchecked(slot_ptr.write(slot)); + head_size += 1; + // We do not have to hold the free lock since we know this slot has never been touched before and nothing external references it + slot_ptr + }; + + *mutex = (first_free, uid.wrapping_add(1), head_arena, head_size); + slot_ptr + } + /// Frees memory allocated from the pool by `Pool::alloc_ptr`. This must be called only once on only pointers returned by `Pool::alloc_ptr` from the same pool. Once memory is freed the content of the memory is undefined, it should not be read or written. + /// + /// `drop` will be called on the `T` pointed to, be sure it has not been called already. + /// + /// The free lock must be held by the caller. + unsafe fn free_ptr(&self, mut slot_ptr: NonNull>) { + let slot = slot_ptr.as_mut(); + slot.uid = 0; + ManuallyDrop::::drop(&mut slot.obj.full_obj); + //linked-list insert + let mut mutex = self.0.lock().unwrap(); + + slot.obj.empty_next = mutex.0; + mutex.0 = slot_ptr.as_ptr(); + } +} +impl Drop for Pool { + fn drop(&mut self) { + let mutex = self.0.lock().unwrap(); + let (_, _, mut head_arena, _) = *mutex; + unsafe { + while !head_arena.is_null() { + let mem = Box::from_raw(head_arena); + head_arena = mem.pre; + drop(mem); + } + } + drop(mutex); + } +} + +pub trait StaticPool { + /// Must return a pointer to an instance of a `Pool` with a static lifetime. That pointer must be cast to a `*const ()` to make the borrow-checker happy. + /// + /// **Safety**: The returned pointer must have originally been a `&'static Pool` reference. So it must have had a matching `T` and `L` and it must have the static lifetime. + /// + /// In order to borrow-split allocations from a `Pool`, we need to force the borrow-checker to not associate the lifetime of an instance of `T` with the lifetime of the pool. Otherwise the borrow-checker would require every allocated `T` to have the `'static` lifetime, to match the pool's lifetime. + /// The simplest way I have found to do this is to return the pointer to the static pool as an anonymous, lifetimeless `*const ()`. This introduces unnecessary safety concerns surrounding pointer casting unfortunately. If there is a better way to borrow-split from a pool I will gladly implement it. + unsafe fn get_static_pool() -> *const (); + + /// Allocates memory for an instance `T` and puts its pointer behind a memory-safe Arc. This `PoolArc` automatically frees itself on drop, and will cause the borrow checker to complain if you attempt to drop the pool before you drop this box. + /// + /// This `PoolArc` supports the ability to generate weak, non-owning references to the allocated `T`. + #[inline(always)] + fn alloc(obj: T) -> PoolArc + where + Self: Sized, + { + unsafe { + PoolArc { + ptr: (*Self::get_static_pool().cast::>()).alloc_ptr(obj), + _p: PhantomData, + } + } + } +} + +/// A rust-style RAII wrapper that drops and frees memory allocated from a pool automatically, the same as an `Arc`. This will run the destructor of `T` in place within the pool before freeing it, correctly maintaining the invariants that the borrow checker and rust compiler expect of generic types. +pub struct PoolArc, const L: usize = DEFAULT_L> { + ptr: NonNull>, + _p: PhantomData<*const OriginPool>, +} + +impl, const L: usize> PoolArc { + /// Obtain a non-owning reference to the `T` contained in this `PoolArc`. This reference has the special property that the underlying `T` can be dropped from the pool while neither making this reference invalid or unsafe nor leaking the memory of `T`. Instead attempts to `grab` the reference will safely return `None`. + /// + /// `T` is guaranteed to be dropped when all `PoolArc` are dropped, regardless of how many `PoolWeakRef` still exist. + #[inline] + pub fn downgrade(&self) -> PoolWeakRef { + unsafe { + // Since this is a Arc we know for certain the object has not been freed, so we don't have to hold the free lock + PoolWeakRef { + ptr: self.ptr, + uid: NonZeroU64::new_unchecked(self.ptr.as_ref().uid), + _p: PhantomData, + } + } + } + /// Returns a number that uniquely identifies this allocated `T` within this pool. No other instance of `T` may have this uid. + pub fn uid(&self) -> NonZeroU64 { + unsafe { NonZeroU64::new_unchecked(self.ptr.as_ref().uid) } + } +} + +impl, const L: usize> Deref for PoolArc { + type Target = T; + #[inline] + fn deref(&self) -> &Self::Target { + unsafe { &self.ptr.as_ref().obj.full_obj } + } +} +impl, const L: usize> Clone for PoolArc { + fn clone(&self) -> Self { + unsafe { + self.ptr.as_ref().ref_count.fetch_add(1, Ordering::Relaxed); + } + Self { ptr: self.ptr, _p: PhantomData } + } +} +impl, const L: usize> Drop for PoolArc { + #[inline] + fn drop(&mut self) { + unsafe { + let slot = self.ptr.as_ref(); + if slot.ref_count.fetch_sub(1, Ordering::AcqRel) == 1 { + let _announce_free = slot.free_lock.write().unwrap(); + // We have to check twice in case a weakref was upgraded before the lock was acquired + if slot.ref_count.load(Ordering::Relaxed) == 0 { + (*OriginPool::get_static_pool().cast::>()).free_ptr(self.ptr); + } + } + } + } +} +unsafe impl, const L: usize> Send for PoolArc where T: Send {} +unsafe impl, const L: usize> Sync for PoolArc where T: Sync {} +impl, const L: usize> Debug for PoolArc +where + T: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("PoolArc").field(self.deref()).finish() + } +} +impl, const L: usize> Display for PoolArc +where + T: Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.deref().fmt(f) + } +} + +/// A non-owning reference to a `T` allocated by a pool. This reference has the special property that the underlying `T` can be dropped from the pool while neither making this reference invalid nor leaking the memory of `T`. Instead attempts to `grab` this reference will safely return `None` if the underlying `T` has been freed by any thread. +/// +/// Due to their thread safety and low overhead a `PoolWeakRef` implements clone and copy. +/// +/// The lifetime of this reference is tied to the lifetime of the pool it came from, because if it were allowed to live longer than its origin pool, it would no longer be safe to dereference and would most likely segfault. Instead the borrow-checker will enforce that this reference has a shorter lifetime that its origin pool. +/// +/// For technical reasons a `RwLock>` will always be the fastest implementation of a `PoolWeakRefSwap`, which is why this library does not provide a `PoolWeakRefSwap` type. +pub struct PoolWeakRef, const L: usize = DEFAULT_L> { + /// A number that uniquely identifies this allocated `T` within this pool. No other instance of `T` may have this uid. This value is read-only. + pub uid: NonZeroU64, + ptr: NonNull>, + _p: PhantomData<*const OriginPool>, +} + +impl, const L: usize> PoolWeakRef { + /// Obtains a lock that allows the `T` contained in this `PoolWeakRef` to be dereferenced in a thread-safe manner. This lock does not prevent other threads from accessing `T` at the same time, so `T` ought to use interior mutability if it needs to be mutated in a thread-safe way. What this lock does guarantee is that `T` cannot be destructed and freed while it is being held. + /// + /// Do not attempt from within the same thread to drop the `PoolArc` that owns this `T` before dropping this lock, or else the thread will deadlock. Rust makes this quite hard to do accidentally but it's not strictly impossible. + #[inline] + pub fn grab<'b>(&self) -> Option> { + unsafe { + let slot = self.ptr.as_ref(); + let prevent_free_lock = slot.free_lock.read().unwrap(); + if slot.uid == self.uid.get() { + Some(PoolGuard(prevent_free_lock, &slot.obj.full_obj)) + } else { + None + } + } + } + /// Attempts to create an owning `PoolArc` from this `PoolWeakRef` of the underlying `T`. Will return `None` if the underlying `T` has already been dropped. + pub fn upgrade(&self) -> Option> { + unsafe { + let slot = self.ptr.as_ref(); + let _prevent_free_lock = slot.free_lock.read().unwrap(); + if slot.uid == self.uid.get() { + self.ptr.as_ref().ref_count.fetch_add(1, Ordering::Relaxed); + Some(PoolArc { ptr: self.ptr, _p: PhantomData }) + } else { + None + } + } + } +} +impl, const L: usize> Clone for PoolWeakRef { + fn clone(&self) -> Self { + Self { uid: self.uid, ptr: self.ptr, _p: PhantomData } + } +} +impl, const L: usize> Copy for PoolWeakRef {} +unsafe impl, const L: usize> Send for PoolWeakRef where T: Send {} +unsafe impl, const L: usize> Sync for PoolWeakRef where T: Sync {} +impl, const L: usize> Debug for PoolWeakRef +where + T: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let inner = self.grab(); + f.debug_tuple("PoolWeakRef").field(&inner).finish() + } +} +impl, const L: usize> Display for PoolWeakRef +where + T: Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(inner) = self.grab() { + inner.fmt(f) + } else { + f.write_str("Empty") + } + } +} + +/// A multithreading lock guard that prevents another thread from freeing the underlying `T` while it is held. It does not prevent other threads from accessing the underlying `T`. +/// +/// If the same thread that holds this guard attempts to free `T` before dropping the guard, it will deadlock. +pub struct PoolGuard<'a, T>(RwLockReadGuard<'a, ()>, &'a T); +impl<'a, T> Deref for PoolGuard<'a, T> { + type Target = T; + #[inline] + fn deref(&self) -> &Self::Target { + &*self.1 + } +} +impl<'a, T> Debug for PoolGuard<'a, T> +where + T: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("PoolGuard").field(self.deref()).finish() + } +} +impl<'a, T> Display for PoolGuard<'a, T> +where + T: Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.deref().fmt(f) + } +} + +/// Allows for the Atomic Swapping and Loading of a `PoolArc`, similar to how a `RwLock>` would function, but much faster and less verbose. +pub struct PoolArcSwap, const L: usize = DEFAULT_L> { + ptr: AtomicPtr>, + reads: AtomicU32, + _p: PhantomData<*const OriginPool>, +} +impl, const L: usize> PoolArcSwap { + /// Creates a new `PoolArcSwap`, consuming `arc` in the process. + pub fn new(mut arc: PoolArc) -> Self { + unsafe { + let ret = Self { + ptr: AtomicPtr::new(arc.ptr.as_mut()), + reads: AtomicU32::new(0), + _p: arc._p, + }; + // Suppress reference decrement on new + mem::forget(arc); + ret + } + } + /// Atomically swaps the currently stored `PoolArc` with a new one, returning the previous one. + pub fn swap(&self, arc: PoolArc) -> PoolArc { + unsafe { + let pre_ptr = self.ptr.swap(arc.ptr.as_ptr(), Ordering::Relaxed); + + while self.reads.load(Ordering::Acquire) > 0 { + std::hint::spin_loop() + } + + mem::forget(arc); + PoolArc { ptr: NonNull::new_unchecked(pre_ptr), _p: self._p } + } + } + + /// Atomically loads and clones the currently stored `PoolArc`, guaranteeing that the underlying `T` cannot be freed while the clone is held. + pub fn load(&self) -> PoolArc { + unsafe { + self.reads.fetch_add(1, Ordering::Acquire); + let ptr = self.ptr.load(Ordering::Relaxed); + (*ptr).ref_count.fetch_add(1, Ordering::Relaxed); + self.reads.fetch_sub(1, Ordering::Release); + PoolArc { ptr: NonNull::new_unchecked(ptr), _p: self._p } + } + } +} +impl, const L: usize> Drop for PoolArcSwap { + #[inline] + fn drop(&mut self) { + unsafe { + let pre = self.ptr.load(Ordering::SeqCst); + PoolArc { _p: self._p, ptr: NonNull::new_unchecked(pre) }; + } + } +} +unsafe impl, const L: usize> Send for PoolArcSwap where T: Send {} +unsafe impl, const L: usize> Sync for PoolArcSwap where T: Sync {} +impl, const L: usize> Debug for PoolArcSwap +where + T: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("PoolArcSwap").field(&self.load()).finish() + } +} +impl, const L: usize> Display for PoolArcSwap +where + T: Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (&self.load()).fmt(f) + } +} + +/// Another implementation of a `PoolArcSwap` utalizing a RwLock instead of atomics. +/// This implementation has slower a `load` but a faster `swap` than the previous implementation of `PoolArcSwap`. +/// If you plan on swapping way more often than loading, this may be a better choice. +pub struct PoolArcSwapRw, const L: usize = DEFAULT_L> { + ptr: RwLock>>, + _p: PhantomData<*const OriginPool>, +} + +impl, const L: usize> PoolArcSwapRw { + /// Creates a new `PoolArcSwap`, consuming `arc` in the process. + pub fn new(arc: PoolArc) -> Self { + let ret = Self { ptr: RwLock::new(arc.ptr), _p: arc._p }; + mem::forget(arc); + ret + } + + /// Atomically swaps the currently stored `PoolArc` with a new one, returning the previous one. + pub fn swap(&self, arc: PoolArc) -> PoolArc { + let mut w = self.ptr.write().unwrap(); + let pre = PoolArc { ptr: *w, _p: self._p }; + *w = arc.ptr; + mem::forget(arc); + pre + } + + /// Atomically loads and clones the currently stored `PoolArc`, guaranteeing that the underlying `T` cannot be freed while the clone is held. + pub fn load(&self) -> PoolArc { + let r = self.ptr.read().unwrap(); + unsafe { + r.as_ref().ref_count.fetch_add(1, Ordering::Relaxed); + } + let pre = PoolArc { ptr: *r, _p: self._p }; + pre + } +} +impl, const L: usize> Drop for PoolArcSwapRw { + #[inline] + fn drop(&mut self) { + let w = self.ptr.write().unwrap(); + PoolArc { ptr: *w, _p: self._p }; + } +} +unsafe impl, const L: usize> Send for PoolArcSwapRw where T: Send {} +unsafe impl, const L: usize> Sync for PoolArcSwapRw where T: Sync {} +impl, const L: usize> Debug for PoolArcSwapRw +where + T: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("PoolArcSwapRw").field(&self.load()).finish() + } +} +impl, const L: usize> Display for PoolArcSwapRw +where + T: Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (&self.load()).fmt(f) + } +} + +/// Automatically generates valid implementations of `StaticPool` onto a chosen identifier, allowing this module to allocate instances of `T` with `alloc`. Users have to generate implementations clientside because rust does not allow for generic globals. +/// +/// The chosen identifier is declared to be a struct with no fields, and instead contains a static global `Pool` for every implementation of `StaticPool` requested. +/// +/// # Example +/// ``` +/// use zerotier_utils::arc_pool::{static_pool, StaticPool, Pool, PoolArc}; +/// +/// static_pool!(pub StaticPool MyPools { +/// Pool, Pool<&u32, 12> +/// }); +/// +/// struct Container { +/// item: PoolArc +/// } +/// +/// let object = 1u32; +/// let arc_object = MyPools::alloc(object); +/// let arc_ref = MyPools::alloc(&object); +/// let arc_container = Container {item: MyPools::alloc(object)}; +/// +/// assert_eq!(*arc_object, **arc_ref); +/// assert_eq!(*arc_object, *arc_container.item); +/// ``` +#[macro_export] +macro_rules! __static_pool__ { + ($m:ident $s:ident { $($($p:ident)::+<$t:ty$(, $l:tt)?>),+ $(,)?}) => { + struct $s {} + $( + impl $m<$t$(, $l)?> for $s { + #[inline(always)] + unsafe fn get_static_pool() -> *const () { + static POOL: $($p)::+<$t$(, $l)?> = $($p)::+::new(); + (&POOL as *const $($p)::+<$t$(, $l)?>).cast() + } + } + )* + }; + ($m:ident::$n:ident $s:ident { $($($p:ident)::+<$t:ty$(, $l:tt)?>),+ $(,)?}) => { + struct $s {} + $( + impl $m::$n<$t$(, $l)?> for $s { + #[inline(always)] + unsafe fn get_static_pool() -> *const () { + static POOL: $($p)::+<$t$(, $l)?> = $($p)::+::new(); + (&POOL as *const $($p)::+<$t$(, $l)?>).cast() + } + } + )* + }; + (pub $m:ident $s:ident { $($($p:ident)::+<$t:ty$(, $l:tt)?>),+ $(,)?}) => { + pub struct $s {} + $( + impl $m<$t$(, $l)?> for $s { + #[inline(always)] + unsafe fn get_static_pool() -> *const () { + static POOL: $($p)::+<$t$(, $l)?> = $($p)::+::new(); + (&POOL as *const $($p)::+<$t$(, $l)?>).cast() + } + } + )* + }; + (pub $m:ident::$n:ident $s:ident { $($($p:ident)::+<$t:ty$(, $l:tt)?>),+ $(,)?}) => { + pub struct $s {} + $( + impl $m::$n<$t$(, $l)?> for $s { + #[inline(always)] + unsafe fn get_static_pool() -> *const () { + static POOL: $($p)::+<$t$(, $l)?> = $($p)::+::new(); + (&POOL as *const $($p)::+<$t$(, $l)?>).cast() + } + } + )* + }; +} +pub use __static_pool__ as static_pool; + +#[cfg(test)] +mod tests { + use super::*; + use std::{ + sync::{atomic::AtomicU64, Arc}, + thread, + }; + + fn rand(r: &mut u32) -> u32 { + /* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */ + *r ^= *r << 13; + *r ^= *r >> 17; + *r ^= *r << 5; + *r + } + const fn prob(p: u64) -> u32 { + (p * (u32::MAX as u64) / 100) as u32 + } + fn rand_idx<'a, T>(v: &'a [T], r: &mut u32) -> Option<&'a T> { + if v.len() > 0 { + Some(&v[(rand(r) as usize) % v.len()]) + } else { + None + } + } + fn rand_i<'a, T>(v: &'a [T], r: &mut u32) -> Option { + if v.len() > 0 { + Some((rand(r) as usize) % v.len()) + } else { + None + } + } + + struct Item { + a: u32, + count: &'static AtomicU64, + b: u32, + } + impl Item { + fn new(r: u32, count: &'static AtomicU64) -> Item { + count.fetch_add(1, Ordering::Relaxed); + Item { a: r, count, b: r } + } + fn check(&self, id: u32) { + assert_eq!(self.a, self.b); + assert_eq!(self.a, id); + } + } + impl Drop for Item { + fn drop(&mut self) { + let _a = self.count.fetch_sub(1, Ordering::Relaxed); + assert_eq!(self.a, self.b); + } + } + + const POOL_U32_LEN: usize = (5 * 12) << 2; + static_pool!(StaticPool TestPools { + Pool, Pool + }); + + #[test] + fn usage() { + let num1 = TestPools::alloc(1u32); + let num2 = TestPools::alloc(2u32); + let num3 = TestPools::alloc(3u32); + let num4 = TestPools::alloc(4u32); + let num2_weak = num2.downgrade(); + + assert_eq!(*num2_weak.grab().unwrap(), 2); + drop(num2); + + assert_eq!(*num1, 1); + assert_eq!(*num3, 3); + assert_eq!(*num4, 4); + assert!(num2_weak.grab().is_none()); + } + #[test] + fn single_thread() { + let mut history = Vec::new(); + + let num1 = TestPools::alloc(1u32); + let num2 = TestPools::alloc(2u32); + let num3 = TestPools::alloc(3u32); + let num4 = TestPools::alloc(4u32); + let num2_weak = num2.downgrade(); + + for i in 0..1000 { + history.push(TestPools::alloc(i as u32)); + } + for i in 0..100 { + let arc = history.remove((i * 10) % history.len()); + assert!(*arc < 1000); + } + for i in 0..1000 { + history.push(TestPools::alloc(i as u32)); + } + + assert_eq!(*num2_weak.grab().unwrap(), 2); + drop(num2); + + assert_eq!(*num1, 1); + assert_eq!(*num3, 3); + assert_eq!(*num4, 4); + assert!(num2_weak.grab().is_none()); + } + + #[test] + fn multi_thread() { + const N: usize = 12345; + static COUNT: AtomicU64 = AtomicU64::new(0); + + let mut joins = Vec::new(); + for i in 0..32 { + joins.push(thread::spawn(move || { + let r = &mut (i + 1234); + + let mut items_dup = Vec::new(); + let mut items = Vec::new(); + for _ in 0..N { + let p = rand(r); + if p < prob(30) { + let id = rand(r); + let s = TestPools::alloc(Item::new(id, &COUNT)); + items.push((id, s.clone(), s.downgrade())); + s.check(id); + } else if p < prob(60) { + if let Some((id, s, w)) = rand_idx(&items, r) { + items_dup.push((*id, s.clone(), (*w).clone())); + s.check(*id); + } + } else if p < prob(80) { + if let Some(i) = rand_i(&items, r) { + let (id, s, w) = items.swap_remove(i); + w.grab().unwrap().check(id); + s.check(id); + } + } else if p < prob(100) { + if let Some(i) = rand_i(&items_dup, r) { + let (id, s, w) = items_dup.swap_remove(i); + w.grab().unwrap().check(id); + s.check(id); + } + } + } + for (id, s, w) in items_dup { + s.check(id); + w.grab().unwrap().check(id); + } + for (id, s, w) in items { + s.check(id); + w.grab().unwrap().check(id); + drop(s); + assert!(w.grab().is_none()) + } + })); + } + for j in joins { + j.join().unwrap(); + } + assert_eq!(COUNT.load(Ordering::Relaxed), 0); + } + + #[test] + fn multi_thread_swap() { + const N: usize = 1234; + static COUNT: AtomicU64 = AtomicU64::new(0); + + let s = Arc::new(PoolArcSwap::new(TestPools::alloc(Item::new(0, &COUNT)))); + + for _ in 0..123 { + let mut joins = Vec::new(); + for _ in 0..8 { + let swaps = s.clone(); + joins.push(thread::spawn(move || { + let r = &mut 1474; + let mut new = TestPools::alloc(Item::new(rand(r), &COUNT)); + for _ in 0..N { + new = swaps.swap(new); + } + })); + } + for j in joins { + j.join().unwrap(); + } + } + drop(s); + assert_eq!(COUNT.load(Ordering::Relaxed), 0); + } + + #[test] + fn multi_thread_swap_load() { + const N: usize = 12345; + static COUNT: AtomicU64 = AtomicU64::new(0); + + let s: Arc<[_; 8]> = Arc::new(std::array::from_fn(|i| PoolArcSwap::new(TestPools::alloc(Item::new(i as u32, &COUNT))))); + + let mut joins = Vec::new(); + + for i in 0..4 { + let swaps = s.clone(); + joins.push(thread::spawn(move || { + let r = &mut (i + 2783); + for _ in 0..N { + if let Some(s) = rand_idx(&swaps[..], r) { + let new = TestPools::alloc(Item::new(rand(r), &COUNT)); + let _a = s.swap(new); + } + } + })); + } + for i in 0..28 { + let swaps = s.clone(); + joins.push(thread::spawn(move || { + let r = &mut (i + 4136); + for _ in 0..N { + if let Some(s) = rand_idx(&swaps[..], r) { + let _a = s.load(); + assert_eq!(_a.a, _a.b); + } + } + })); + } + for j in joins { + j.join().unwrap(); + } + drop(s); + assert_eq!(COUNT.load(Ordering::Relaxed), 0); + } +} diff --git a/src/utils/src/arrayvec.rs b/src/utils/src/arrayvec.rs new file mode 100644 index 0000000..b8a8a07 --- /dev/null +++ b/src/utils/src/arrayvec.rs @@ -0,0 +1,369 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::fmt::Debug; +use std::io::Write; +use std::mem::{needs_drop, size_of, MaybeUninit}; +use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut}; + +use serde::ser::SerializeSeq; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[derive(Clone, Copy, Debug)] +pub struct OutOfCapacityError(pub T); + +impl std::fmt::Display for OutOfCapacityError { + fn fmt(&self, stream: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt("ArrayVec out of space", stream) + } +} + +impl ::std::error::Error for OutOfCapacityError { + fn description(&self) -> &str { + "ArrayVec out of space" + } +} + +/// A simple vector backed by a static sized array with no memory allocations and no overhead construction. +pub struct ArrayVec { + pub(crate) s: usize, + pub(crate) a: [MaybeUninit; C], +} + +impl Default for ArrayVec { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + +impl PartialEq for ArrayVec { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + let tmp: &[T] = self.as_ref(); + tmp.eq(other.as_ref()) + } +} + +impl Eq for ArrayVec {} + +impl Clone for ArrayVec { + #[inline] + fn clone(&self) -> Self { + debug_assert!(self.s <= C); + Self { + s: self.s, + a: unsafe { + let mut tmp: [MaybeUninit; C] = MaybeUninit::uninit().assume_init(); + for i in 0..self.s { + tmp.get_unchecked_mut(i).write(self.a[i].assume_init_ref().clone()); + } + tmp + }, + } + } +} + +impl From<[T; S]> for ArrayVec { + #[inline] + fn from(v: [T; S]) -> Self { + if S <= C { + let mut tmp = Self::new(); + for i in 0..S { + tmp.push(v[i].clone()); + } + tmp + } else { + panic!(); + } + } +} + +impl ToString for ArrayVec { + #[inline] + fn to_string(&self) -> String { + crate::hex::to_string(self.as_bytes()) + } +} + +impl Debug for ArrayVec { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_string().as_str()) + } +} + +impl Write for ArrayVec { + #[inline] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + for i in buf.iter() { + if self.try_push(*i).is_err() { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "ArrayVec out of space")); + } + } + Ok(buf.len()) + } + + #[inline(always)] + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl TryFrom> for ArrayVec { + type Error = OutOfCapacityError; + + #[inline(always)] + fn try_from(mut value: Vec) -> Result { + let mut tmp = Self::new(); + for x in value.drain(..) { + tmp.try_push(x)?; + } + Ok(tmp) + } +} + +impl TryFrom<&Vec> for ArrayVec { + type Error = OutOfCapacityError; + + #[inline(always)] + fn try_from(value: &Vec) -> Result { + let mut tmp = Self::new(); + for x in value.iter() { + tmp.try_push(x.clone())?; + } + Ok(tmp) + } +} + +impl TryFrom<&[T]> for ArrayVec { + type Error = OutOfCapacityError; + + #[inline(always)] + fn try_from(value: &[T]) -> Result { + let mut tmp = Self::new(); + for x in value.iter() { + tmp.try_push(x.clone())?; + } + Ok(tmp) + } +} + +impl ArrayVec { + #[inline(always)] + pub fn new() -> Self { + assert_eq!(size_of::<[T; C]>(), size_of::<[MaybeUninit; C]>()); + Self { s: 0, a: unsafe { MaybeUninit::uninit().assume_init() } } + } + + #[inline] + pub fn push(&mut self, v: T) { + let i = self.s; + if i < C { + unsafe { self.a.get_unchecked_mut(i).write(v) }; + self.s = i + 1; + } else { + panic!(); + } + } + + #[inline] + pub fn try_push(&mut self, v: T) -> Result<(), OutOfCapacityError> { + if self.s < C { + let i = self.s; + unsafe { self.a.get_unchecked_mut(i).write(v) }; + self.s = i + 1; + Ok(()) + } else { + Err(OutOfCapacityError(v)) + } + } + + #[inline(always)] + pub fn as_bytes(&self) -> &[T] { + unsafe { &*slice_from_raw_parts(self.a.as_ptr().cast(), self.s) } + } + + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.s == 0 + } + + #[inline(always)] + pub fn len(&self) -> usize { + self.s + } + + #[inline(always)] + pub fn capacity_remaining(&self) -> usize { + C - self.s + } + + #[inline(always)] + pub fn iter(&self) -> impl DoubleEndedIterator { + self.as_ref().iter() + } + + #[inline(always)] + pub fn iter_mut(&mut self) -> impl DoubleEndedIterator { + self.as_mut().iter_mut() + } + + #[inline(always)] + pub fn first(&self) -> Option<&T> { + if self.s != 0 { + Some(unsafe { self.a.get_unchecked(0).assume_init_ref() }) + } else { + None + } + } + + #[inline(always)] + pub fn last(&self) -> Option<&T> { + if self.s != 0 { + Some(unsafe { self.a.get_unchecked(self.s - 1).assume_init_ref() }) + } else { + None + } + } + + #[inline] + pub fn pop(&mut self) -> Option { + if self.s > 0 { + let i = self.s - 1; + debug_assert!(i < C); + self.s = i; + Some(unsafe { self.a.get_unchecked(i).assume_init_read() }) + } else { + None + } + } + + #[inline] + pub fn clear(&mut self) { + if needs_drop::() { + for i in 0..self.s { + unsafe { self.a.get_unchecked_mut(i).assume_init_drop() }; + } + } + self.s = 0; + } +} + +impl ArrayVec +where + T: Copy, +{ + /// Push a slice of copyable objects, panic if capacity exceeded. + #[inline] + pub fn push_slice(&mut self, v: &[T]) { + let start = self.s; + let end = self.s + v.len(); + if end <= C { + for i in start..end { + unsafe { self.a.get_unchecked_mut(i).write(*v.get_unchecked(i - start)) }; + } + self.s = end; + } else { + panic!(); + } + } +} + +impl Drop for ArrayVec { + #[inline(always)] + fn drop(&mut self) { + if needs_drop::() { + for i in 0..self.s { + unsafe { self.a.get_unchecked_mut(i).assume_init_drop() }; + } + } + } +} + +impl AsRef<[T]> for ArrayVec { + #[inline(always)] + fn as_ref(&self) -> &[T] { + unsafe { &*slice_from_raw_parts(self.a.as_ptr().cast(), self.s) } + } +} + +impl AsMut<[T]> for ArrayVec { + #[inline(always)] + fn as_mut(&mut self) -> &mut [T] { + unsafe { &mut *slice_from_raw_parts_mut(self.a.as_mut_ptr().cast(), self.s) } + } +} + +impl Serialize for ArrayVec { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.len()))?; + let sl: &[T] = self.as_ref(); + for i in 0..self.s { + seq.serialize_element(&sl[i])?; + } + seq.end() + } +} + +struct ArrayVecVisitor<'de, T: Deserialize<'de>, const L: usize>(std::marker::PhantomData<&'de T>); + +impl<'de, T: Deserialize<'de>, const L: usize> serde::de::Visitor<'de> for ArrayVecVisitor<'de, T, L> { + type Value = ArrayVec; + + #[inline] + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str(format!("array of up to {} elements", L).as_str()) + } + + #[inline] + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut a = ArrayVec::::new(); + while let Some(x) = seq.next_element()? { + a.push(x); + } + Ok(a) + } +} + +impl<'de, T: Deserialize<'de> + 'de, const L: usize> Deserialize<'de> for ArrayVec { + #[inline] + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_seq(ArrayVecVisitor(std::marker::PhantomData::default())) + } +} + +#[cfg(test)] +mod tests { + use super::ArrayVec; + + #[test] + fn array_vec() { + let mut v = ArrayVec::::new(); + for i in 0..128 { + v.push(i); + } + assert_eq!(v.len(), 128); + assert!(v.try_push(1000).is_err()); + assert_eq!(v.len(), 128); + for _ in 0..128 { + assert!(v.pop().is_some()); + } + assert!(v.pop().is_none()); + } +} diff --git a/src/utils/src/base24.rs b/src/utils/src/base24.rs new file mode 100644 index 0000000..9148de4 --- /dev/null +++ b/src/utils/src/base24.rs @@ -0,0 +1,131 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::io::Write; + +use crate::error::InvalidParameterError; + +/// All unambiguous letters, thus easy to type on the alphabetic keyboards on phones without extra shift taps. +/// The letters 'l' and 'u' are skipped. +const BASE24_ALPHABET: [u8; 24] = [ + b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'v', b'w', b'x', b'y', b'z', +]; +/// Reverse table for BASE24 alphabet, indexed relative to 'a' or 'A'. +const BASE24_ALPHABET_INV: [u8; 26] = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 255, 11, 12, 13, 14, 15, 16, 17, 18, 255, 19, 20, 21, 22, 23, +]; + +/// Encode a byte slice into base24 ASCII format (no padding) +pub fn encode_into(mut b: &[u8], s: &mut String) { + while b.len() >= 4 { + let mut n = u32::from_le_bytes(b[..4].try_into().unwrap()); + for _ in 0..6 { + s.push(BASE24_ALPHABET[(n % 24) as usize] as char); + n /= 24; + } + s.push(BASE24_ALPHABET[n as usize] as char); + b = &b[4..]; + } + + if !b.is_empty() { + let mut n = 0u32; + for i in 0..b.len() { + n |= (b[i] as u32).wrapping_shl((i as u32) * 8); + } + for _ in 0..(b.len() * 2) { + s.push(BASE24_ALPHABET[(n % 24) as usize] as char); + n /= 24; + } + } +} + +fn decode_up_to_u32(s: &[u8]) -> Result { + let mut n = 0u32; + for c in s.iter().rev() { + let mut c = *c; + if (97..=122).contains(&c) { + c -= 97; + } else if (65..=90).contains(&c) { + c -= 65; + } else { + return Err(InvalidParameterError("invalid base24 character")); + } + let i = BASE24_ALPHABET_INV[c as usize]; + if i == 255 { + return Err(InvalidParameterError("invalid base24 character")); + } + n *= 24; + n = n.wrapping_add(i as u32); + } + Ok(n) +} + +/// Decode a base24 ASCII slice into bytes (no padding, length determines output length) +pub fn decode_into(s: &[u8], b: &mut W) -> Result<(), InvalidParameterError> { + let mut s = s; + + while s.len() >= 7 { + let _ = b.write_all(&decode_up_to_u32(&s[..7])?.to_le_bytes()); + s = &s[7..]; + } + + if !s.is_empty() { + let _ = b.write_all( + &decode_up_to_u32(s)?.to_le_bytes()[..match s.len() { + 2 => 1, + 4 => 2, + 6 => 3, + _ => return Err(InvalidParameterError("invalid base24 length")), + }], + ); + } + + Ok(()) +} + +#[inline] +pub fn decode_into_slice(s: &[u8], mut b: &mut [u8]) -> Result<(), InvalidParameterError> { + decode_into(s, &mut b) +} + +pub fn encode(b: &[u8]) -> String { + let mut tmp = String::with_capacity(((b.len() / 4) * 7) + 2); + encode_into(b, &mut tmp); + tmp +} + +pub fn decode(s: &[u8]) -> Result, InvalidParameterError> { + let mut tmp = Vec::with_capacity(((s.len() / 7) * 4) + 2); + decode_into(s, &mut tmp)?; + Ok(tmp) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn encode_decode() { + let mut tmp = [0xffu8; 256]; + for _ in 0..3 { + let mut s = String::with_capacity(1024); + let mut v: Vec = Vec::with_capacity(256); + for i in 1..256 { + s.clear(); + encode_into(&tmp[..i], &mut s); + //println!("{}", s); + v.clear(); + decode_into(s.as_str().as_bytes(), &mut v).expect("decode error"); + assert!(v.as_slice().eq(&tmp[..i])); + } + for b in tmp.iter_mut() { + *b -= 3; + } + } + } +} diff --git a/src/utils/src/base62.rs b/src/utils/src/base62.rs new file mode 100644 index 0000000..344ac27 --- /dev/null +++ b/src/utils/src/base62.rs @@ -0,0 +1,187 @@ +use std::io::Write; + +use super::arrayvec::ArrayVec; +use super::memory; + +const MAX_LENGTH_WORDS: usize = 128; + +/// Encode a byte array into a base62 string. +/// +/// The pad_output_to_length parameter outputs base62 zeroes at the end to ensure that the output +/// string is at least a given length. Set this to zero if you don't want to pad the output. This +/// has no effect on decoded output length. +pub fn encode_into(b: &[u8], s: &mut String, pad_output_to_length: usize) { + assert!(b.len() <= MAX_LENGTH_WORDS * 4); + let mut n: ArrayVec = ArrayVec::new(); + + let mut i = 0; + let len_words = b.len() & usize::MAX.wrapping_shl(2); + while i < len_words { + n.push(u32::from_le(memory::load_raw(&b[i..]))); + i += 4; + } + if i < b.len() { + let mut w = 0u32; + let mut shift = 0u32; + while i < b.len() { + w |= (b[i] as u32).wrapping_shl(shift); + i += 1; + shift += 8; + } + n.push(w); + } + + let mut string_len = 0; + while !n.is_empty() { + s.push(b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"[big_div_rem::(&mut n) as usize] as char); + string_len += 1; + } + while string_len < pad_output_to_length { + s.push('0'); + string_len += 1; + } +} + +/// Decode Base62 into a vector or other output. +/// +/// Note that base62 doesn't have a way to know the output length. Decoding may be short if there were +/// trailing zeroes in the input. The output length parameter specifies the expected length of the +/// output, which will be zero padded if decoded data does not reach it. If decoded data exceeds this +/// length an error is returned. +pub fn decode_into(s: &[u8], b: &mut W, output_length: usize) -> std::io::Result<()> { + let mut n: ArrayVec = ArrayVec::new(); + + for c in s.iter().rev() { + let mut c = *c as u32; + // 0..9, A..Z, or a..z + if (48..=57).contains(&c) { + c -= 48; + } else if (65..=90).contains(&c) { + c -= 65 - 10; + } else if (97..=122).contains(&c) { + c -= 97 - (10 + 26); + } else { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid base62")); + } + big_mul::(&mut n); + big_add(&mut n, c); + } + + let mut bc = output_length; + for w in n.iter() { + if bc > 0 { + let l = bc.min(4); + b.write_all(&w.to_le_bytes()[..l])?; + bc -= l; + } else { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "data too large")); + } + } + while bc > 0 { + b.write_all(&[0])?; + bc -= 1; + } + + Ok(()) +} + +#[inline] +pub fn decode_into_slice(s: &[u8], mut b: &mut [u8]) -> std::io::Result<()> { + let l = b.len(); + decode_into(s, &mut b, l) +} + +/// Decode into and return an array whose length is the desired output_length. +/// None is returned if there is an error. +#[inline] +pub fn decode(s: &[u8]) -> Option<[u8; L]> { + let mut buf = [0u8; L]; + let mut w = &mut buf[..]; + if decode_into(s, &mut w, L).is_ok() { + Some(buf) + } else { + None + } +} + +#[inline(always)] +fn big_div_rem(n: &mut ArrayVec) -> u32 { + while let Some(&0) = n.last() { + n.pop(); + } + let mut rem = 0; + for word in n.iter_mut().rev() { + let temp = (rem as u64).wrapping_shl(32) | (*word as u64); + let (a, b) = (temp / D, temp % D); + *word = a as u32; + rem = b as u32; + } + while let Some(&0) = n.last() { + n.pop(); + } + rem +} + +#[inline(always)] +fn big_add(n: &mut ArrayVec, i: u32) { + let mut carry = i as u64; + for word in n.iter_mut() { + let res = (*word as u64).wrapping_add(carry); + *word = res as u32; + carry = res.wrapping_shr(32); + } + if carry > 0 { + n.push(carry as u32); + } +} + +#[inline(always)] +fn big_mul(n: &mut ArrayVec) { + while let Some(&0) = n.last() { + n.pop(); + } + let mut carry = 0; + for word in n.iter_mut() { + let temp = (*word as u64).wrapping_mul(M).wrapping_add(carry); + *word = temp as u32; + carry = temp.wrapping_shr(32); + } + if carry != 0 { + n.push(carry as u32); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn div_rem() { + let mut n = ArrayVec::::new(); + n.push_slice(&[0xdeadbeef, 0xfeedfeed, 0xcafebabe, 0xf00dd00d]); + let rem = big_div_rem::<4, 63>(&mut n); + let nn = n.as_ref(); + assert!(nn[0] == 0xaa23440b && nn[1] == 0xa696103c && nn[2] == 0x89513fea && nn[3] == 0x03cf7514 && rem == 58); + } + + #[test] + fn encode_decode() { + let mut test = [0xff; 64]; + for tl in 1..64 { + let test = &mut test[..tl]; + test.fill(0xff); + let mut b = Vec::with_capacity(1024); + for _ in 0..10 { + let mut s = String::with_capacity(1024); + encode_into(test, &mut s, 86); + b.clear(); + //println!("{}", s); + assert!(decode_into(s.as_bytes(), &mut b, test.len()).is_ok()); + assert_eq!(b.as_slice(), test); + for c in test.iter_mut() { + *c = crate::rand() as u8; + } + } + } + } +} diff --git a/src/utils/src/blob.rs b/src/utils/src/blob.rs new file mode 100644 index 0000000..46430a8 --- /dev/null +++ b/src/utils/src/blob.rs @@ -0,0 +1,150 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::fmt::Debug; +use std::hash::Hash; + +use serde::ser::SerializeTuple; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::hex; + +/// Fixed size Serde serializable byte array. +/// This makes it easier to deal with blobs larger than 32 bytes (due to serde array limitations) +#[repr(transparent)] +#[derive(Clone, Eq, PartialEq)] +pub struct Blob([u8; L]); + +impl Blob { + #[inline(always)] + pub fn as_bytes(&self) -> &[u8; L] { + &self.0 + } + + #[inline(always)] + pub const fn len(&self) -> usize { + L + } +} + +impl From<[u8; L]> for Blob { + #[inline(always)] + fn from(a: [u8; L]) -> Self { + Self(a) + } +} + +impl From<&[u8; L]> for Blob { + #[inline(always)] + fn from(a: &[u8; L]) -> Self { + Self(*a) + } +} + +impl Default for Blob { + #[inline(always)] + fn default() -> Self { + unsafe { std::mem::zeroed() } + } +} + +impl AsRef<[u8; L]> for Blob { + #[inline(always)] + fn as_ref(&self) -> &[u8; L] { + &self.0 + } +} + +impl AsMut<[u8; L]> for Blob { + #[inline(always)] + fn as_mut(&mut self) -> &mut [u8; L] { + &mut self.0 + } +} + +impl ToString for Blob { + #[inline(always)] + fn to_string(&self) -> String { + hex::to_string(&self.0) + } +} + +impl PartialOrd for Blob { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for Blob { + #[inline(always)] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl Hash for Blob { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +impl Debug for Blob { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_string().as_str()) + } +} + +impl Serialize for Blob { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut t = serializer.serialize_tuple(L)?; + for i in self.0.iter() { + t.serialize_element(i)?; + } + t.end() + } +} + +struct BlobVisitor; + +impl<'de, const L: usize> serde::de::Visitor<'de> for BlobVisitor { + type Value = Blob; + + #[inline] + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str(format!("array of {} bytes", L).as_str()) + } + + #[inline] + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut blob = Blob::::default(); + for i in 0..L { + blob.0[i] = seq.next_element()?.ok_or_else(|| serde::de::Error::invalid_length(i, &self))?; + } + Ok(blob) + } +} + +impl<'de, const L: usize> Deserialize<'de> for Blob { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_tuple(L, BlobVisitor::) + } +} diff --git a/src/utils/src/buffer.rs b/src/utils/src/buffer.rs new file mode 100644 index 0000000..2626803 --- /dev/null +++ b/src/utils/src/buffer.rs @@ -0,0 +1,752 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::error::Error; +use std::fmt::{Debug, Display}; +use std::io::{Read, Write}; +use std::mem::{size_of, MaybeUninit}; + +use crate::memory; +use crate::pool::PoolFactory; +use crate::unlikely_branch; +use crate::varint; + +const OUT_OF_BOUNDS_MSG: &str = "Buffer access out of bounds"; + +pub struct OutOfBoundsError; + +impl Display for OutOfBoundsError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(OUT_OF_BOUNDS_MSG) + } +} + +impl Debug for OutOfBoundsError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(self, f) + } +} + +impl Error for OutOfBoundsError {} + +impl From for std::io::Error { + fn from(_: OutOfBoundsError) -> Self { + std::io::Error::new(std::io::ErrorKind::Other, OUT_OF_BOUNDS_MSG) + } +} + +/// An I/O buffer with extensions for efficiently reading and writing various objects. +/// +/// WARNING: Structures can only be handled through raw read/write here if they are +/// tagged a Copy, meaning they are safe to just copy as raw memory. Care must also +/// be taken to ensure that access to them is safe on architectures that do not support +/// unaligned access. In vl1/protocol.rs this is accomplished by only using byte arrays +/// (including for integers) and accessing via things like u64::from_be_bytes() etc. +/// +/// Needless to say anything with non-Copy internal members or that depends on Drop to +/// not leak resources or other higher level semantics won't work here, but Rust should +/// not let you tag that as Copy in safe code. +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Buffer(usize, [u8; L]); + +impl Default for Buffer { + #[inline(always)] + fn default() -> Self { + unsafe { std::mem::zeroed() } + } +} + +impl Buffer { + pub const CAPACITY: usize = L; + + /// Create an empty zeroed buffer. + #[inline(always)] + pub fn new() -> Self { + unsafe { std::mem::zeroed() } + } + + /// Create an empty zeroed buffer on the heap without intermediate stack allocation. + /// This can be used to allocate buffers too large for the stack. + #[inline(always)] + pub fn new_boxed() -> Box { + unsafe { Box::from_raw(std::alloc::alloc_zeroed(std::alloc::Layout::new::()).cast()) } + } + + /// Create an empty buffer without internally zeroing its memory. + /// + /// This is unsafe because unwritten memory in the buffer will have undefined contents. + /// This means that some of the append_X_get_mut() functions may return mutable references to + /// undefined memory contents rather than zeroed memory. + #[inline(always)] + pub unsafe fn new_without_memzero() -> Self { + Self(0, MaybeUninit::uninit().assume_init()) + } + + pub const fn capacity(&self) -> usize { + Self::CAPACITY + } + + pub fn from_bytes(b: &[u8]) -> Result { + let l = b.len(); + if l <= L { + let mut tmp = Self::new(); + tmp.0 = l; + tmp.1[0..l].copy_from_slice(b); + Ok(tmp) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn as_bytes(&self) -> &[u8] { + &self.1[..self.0] + } + + #[inline(always)] + pub fn as_bytes_mut(&mut self) -> &mut [u8] { + &mut self.1[..self.0] + } + + #[inline(always)] + pub fn as_ptr(&self) -> *const u8 { + self.1.as_ptr() + } + + #[inline(always)] + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.1.as_mut_ptr() + } + + #[inline(always)] + pub fn as_bytes_starting_at(&self, start: usize) -> Result<&[u8], OutOfBoundsError> { + if start <= self.0 { + Ok(&self.1[start..self.0]) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn as_bytes_starting_at_mut(&mut self, start: usize) -> Result<&mut [u8], OutOfBoundsError> { + if start <= self.0 { + Ok(&mut self.1[start..self.0]) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn as_byte_range(&self, start: usize, end: usize) -> Result<&[u8], OutOfBoundsError> { + if end <= self.0 { + Ok(&self.1[start..end]) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn clear(&mut self) { + self.1[0..self.0].fill(0); + self.0 = 0; + } + + /// Load array into buffer. + /// This will panic if the array is larger than L. + #[inline(always)] + pub fn set_to(&mut self, b: &[u8]) { + let len = b.len(); + self.0 = len; + self.1[0..len].copy_from_slice(b); + } + + #[inline(always)] + pub fn len(&self) -> usize { + self.0 + } + + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.0 == 0 + } + + /// Set the size of this buffer's data. + /// + /// This will panic if the specified size is larger than L. If the size is larger + /// than the current size uninitialized space will be zeroed. + pub fn set_size(&mut self, s: usize) { + let prev_len = self.0; + self.0 = s; + if s > prev_len { + self.1[prev_len..s].fill(0); + } + } + + /// Get a mutable reference to the entire buffer regardless of the current 'size'. + #[inline(always)] + pub unsafe fn entire_buffer_mut(&mut self) -> &mut [u8; L] { + &mut self.1 + } + + /// Set the size of the data in this buffer without checking bounds or zeroing new space. + #[inline(always)] + pub unsafe fn set_size_unchecked(&mut self, s: usize) { + self.0 = s; + } + + /// Get a byte from this buffer without checking bounds. + #[inline(always)] + pub unsafe fn get_unchecked(&self, i: usize) -> u8 { + *self.1.get_unchecked(i) + } + + /// Erase the first N bytes of this buffer, copying remaining bytes to the front. + pub fn erase_first_n(&mut self, i: usize) -> Result<(), OutOfBoundsError> { + if i < self.0 { + let l = self.0; + self.1.copy_within(i..l, 0); + self.0 = l - i; + Ok(()) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + /// Append a structure and return a mutable reference to its memory. + #[inline(always)] + pub fn append_struct_get_mut(&mut self) -> Result<&mut T, OutOfBoundsError> { + let ptr = self.0; + let end = ptr + size_of::(); + if end <= L { + self.0 = end; + Ok(unsafe { &mut *self.1.as_mut_ptr().add(ptr).cast() }) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + /// Append a fixed size array and return a mutable reference to its memory. + #[inline(always)] + pub fn append_bytes_fixed_get_mut(&mut self) -> Result<&mut [u8; S], OutOfBoundsError> { + let ptr = self.0; + let end = ptr + S; + if end <= L { + self.0 = end; + Ok(unsafe { &mut *self.1.as_mut_ptr().add(ptr).cast() }) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + /// Append a runtime sized array and return a mutable reference to its memory. + #[inline(always)] + pub fn append_bytes_get_mut(&mut self, s: usize) -> Result<&mut [u8], OutOfBoundsError> { + let ptr = self.0; + let end = ptr + s; + if end <= L { + self.0 = end; + Ok(&mut self.1[ptr..end]) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn append_padding(&mut self, b: u8, count: usize) -> Result<(), OutOfBoundsError> { + let ptr = self.0; + let end = ptr + count; + if end <= L { + self.0 = end; + self.1[ptr..end].fill(b); + Ok(()) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn append_bytes(&mut self, buf: &[u8]) -> Result<(), OutOfBoundsError> { + let ptr = self.0; + let end = ptr + buf.len(); + if end <= L { + self.0 = end; + self.1[ptr..end].copy_from_slice(buf); + Ok(()) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn append_bytes_fixed(&mut self, buf: &[u8; S]) -> Result<(), OutOfBoundsError> { + let ptr = self.0; + let end = ptr + S; + if end <= L { + self.0 = end; + self.1[ptr..end].copy_from_slice(buf); + Ok(()) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn append_u8(&mut self, i: u8) -> Result<(), OutOfBoundsError> { + let ptr = self.0; + if ptr < L { + self.0 = ptr + 1; + self.1[ptr] = i; + Ok(()) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn append_u16(&mut self, i: u16) -> Result<(), OutOfBoundsError> { + let ptr = self.0; + let end = ptr + 2; + if end <= L { + self.0 = end; + memory::store_raw(i.to_be(), &mut self.1[ptr..]); + Ok(()) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn append_u32(&mut self, i: u32) -> Result<(), OutOfBoundsError> { + let ptr = self.0; + let end = ptr + 4; + if end <= L { + self.0 = end; + memory::store_raw(i.to_be(), &mut self.1[ptr..]); + Ok(()) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn append_u64(&mut self, i: u64) -> Result<(), OutOfBoundsError> { + let ptr = self.0; + let end = ptr + 8; + if end <= L { + self.0 = end; + memory::store_raw(i.to_be(), &mut self.1[ptr..]); + Ok(()) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn append_u64_le(&mut self, i: u64) -> Result<(), OutOfBoundsError> { + let ptr = self.0; + let end = ptr + 8; + if end <= L { + self.0 = end; + memory::store_raw(i.to_be(), &mut self.1[ptr..]); + Ok(()) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + pub fn append_varint(&mut self, i: u64) -> Result<(), OutOfBoundsError> { + if varint::write(self, i).is_ok() { + Ok(()) + } else { + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn bytes_fixed_at(&self, ptr: usize) -> Result<&[u8; S], OutOfBoundsError> { + if (ptr + S) <= self.0 { + unsafe { Ok(&*self.1.as_ptr().cast::().add(ptr).cast::<[u8; S]>()) } + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn bytes_fixed_mut_at(&mut self, ptr: usize) -> Result<&mut [u8; S], OutOfBoundsError> { + if (ptr + S) <= self.0 { + unsafe { Ok(&mut *self.1.as_mut_ptr().cast::().add(ptr).cast::<[u8; S]>()) } + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn struct_at(&self, ptr: usize) -> Result<&T, OutOfBoundsError> { + if (ptr + size_of::()) <= self.0 { + unsafe { Ok(&*self.1.as_ptr().cast::().add(ptr).cast::()) } + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn struct_mut_at(&mut self, ptr: usize) -> Result<&mut T, OutOfBoundsError> { + if (ptr + size_of::()) <= self.0 { + unsafe { Ok(&mut *self.1.as_mut_ptr().cast::().add(ptr).cast::()) } + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn u8_at(&self, ptr: usize) -> Result { + if ptr < self.0 { + Ok(self.1[ptr]) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn u16_at(&self, ptr: usize) -> Result { + let end = ptr + 2; + debug_assert!(end <= L); + if end <= self.0 { + Ok(u16::from_be(memory::load_raw(&self.1[ptr..]))) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn u32_at(&self, ptr: usize) -> Result { + let end = ptr + 4; + debug_assert!(end <= L); + if end <= self.0 { + Ok(u32::from_be(memory::load_raw(&self.1[ptr..]))) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn u64_at(&self, ptr: usize) -> Result { + let end = ptr + 8; + debug_assert!(end <= L); + if end <= self.0 { + Ok(u64::from_be(memory::load_raw(&self.1[ptr..]))) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn read_struct(&self, cursor: &mut usize) -> Result<&T, OutOfBoundsError> { + let ptr = *cursor; + let end = ptr + size_of::(); + debug_assert!(end <= L); + if end <= self.0 { + *cursor = end; + unsafe { Ok(&*self.1.as_ptr().cast::().add(ptr).cast::()) } + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn read_bytes_fixed(&self, cursor: &mut usize) -> Result<&[u8; S], OutOfBoundsError> { + let ptr = *cursor; + let end = ptr + S; + debug_assert!(end <= L); + if end <= self.0 { + *cursor = end; + unsafe { Ok(&*self.1.as_ptr().cast::().add(ptr).cast::<[u8; S]>()) } + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn read_bytes(&self, l: usize, cursor: &mut usize) -> Result<&[u8], OutOfBoundsError> { + let ptr = *cursor; + let end = ptr + l; + debug_assert!(end <= L); + if end <= self.0 { + *cursor = end; + Ok(&self.1[ptr..end]) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + pub fn read_varint(&self, cursor: &mut usize) -> Result { + let c = *cursor; + if c < self.0 { + let mut a = &self.1[c..]; + varint::read(&mut a) + .map(|r| { + *cursor = c + r.1; + debug_assert!(*cursor <= self.0); + r.0 + }) + .map_err(|_| OutOfBoundsError) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn read_u8(&self, cursor: &mut usize) -> Result { + let ptr = *cursor; + debug_assert!(ptr < L); + if ptr < self.0 { + *cursor = ptr + 1; + Ok(self.1[ptr]) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn read_u16(&self, cursor: &mut usize) -> Result { + let ptr = *cursor; + let end = ptr + 2; + debug_assert!(end <= L); + if end <= self.0 { + *cursor = end; + Ok(u16::from_be(memory::load_raw(&self.1[ptr..]))) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn read_u32(&self, cursor: &mut usize) -> Result { + let ptr = *cursor; + let end = ptr + 4; + debug_assert!(end <= L); + if end <= self.0 { + *cursor = end; + Ok(u32::from_be(memory::load_raw(&self.1[ptr..]))) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + + #[inline(always)] + pub fn read_u64(&self, cursor: &mut usize) -> Result { + let ptr = *cursor; + let end = ptr + 8; + debug_assert!(end <= L); + if end <= self.0 { + *cursor = end; + Ok(u64::from_be(memory::load_raw(&self.1[ptr..]))) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } +} + +impl Write for Buffer { + #[inline(always)] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + let ptr = self.0; + let end = ptr + buf.len(); + if end <= L { + self.0 = end; + self.1[ptr..end].copy_from_slice(buf); + Ok(buf.len()) + } else { + unlikely_branch(); + Err(std::io::Error::new(std::io::ErrorKind::Other, OUT_OF_BOUNDS_MSG)) + } + } + + #[inline(always)] + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +impl AsRef<[u8]> for Buffer { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl AsMut<[u8]> for Buffer { + #[inline(always)] + fn as_mut(&mut self) -> &mut [u8] { + self.as_bytes_mut() + } +} + +impl From<[u8; L]> for Buffer { + #[inline(always)] + fn from(a: [u8; L]) -> Self { + Self(L, a) + } +} + +impl From<&[u8; L]> for Buffer { + #[inline(always)] + fn from(a: &[u8; L]) -> Self { + Self(L, *a) + } +} + +/// Implements std::io::Read for a buffer and a cursor. +pub struct BufferReader<'a, 'b, const L: usize>(&'a Buffer, &'b mut usize); + +impl<'a, 'b, const L: usize> BufferReader<'a, 'b, L> { + #[inline(always)] + pub fn new(b: &'a Buffer, cursor: &'b mut usize) -> Self { + Self(b, cursor) + } +} + +impl<'a, 'b, const L: usize> Read for BufferReader<'a, 'b, L> { + #[inline(always)] + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + buf.copy_from_slice(self.0.read_bytes(buf.len(), self.1)?); + Ok(buf.len()) + } +} + +pub struct PooledBufferFactory; + +impl PooledBufferFactory { + #[inline(always)] + pub fn new() -> Self { + Self {} + } +} + +impl PoolFactory> for PooledBufferFactory { + #[inline(always)] + fn create(&self) -> Buffer { + Buffer::new() + } + + #[inline(always)] + fn reset(&self, obj: &mut Buffer) { + obj.clear(); + } +} + +#[cfg(test)] +mod tests { + use super::Buffer; + + #[test] + fn buffer_basic_u64() { + let mut b = Buffer::<8>::new(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + assert!(b.append_u64(1234).is_ok()); + assert_eq!(b.len(), 8); + assert!(!b.is_empty()); + assert_eq!(b.read_u64(&mut 0).unwrap(), 1234); + b.clear(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + } + + #[test] + fn buffer_basic_u32() { + let mut b = Buffer::<4>::new(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + assert!(b.append_u32(1234).is_ok()); + assert_eq!(b.len(), 4); + assert!(!b.is_empty()); + assert_eq!(b.read_u32(&mut 0).unwrap(), 1234); + b.clear(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + } + + #[test] + fn buffer_basic_u16() { + let mut b = Buffer::<2>::new(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + assert!(b.append_u16(1234).is_ok()); + assert_eq!(b.len(), 2); + assert!(!b.is_empty()); + assert_eq!(b.read_u16(&mut 0).unwrap(), 1234); + b.clear(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + } + + #[test] + fn buffer_basic_u8() { + let mut b = Buffer::<1>::new(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + assert!(b.append_u8(128).is_ok()); + assert_eq!(b.len(), 1); + assert!(!b.is_empty()); + assert_eq!(b.read_u8(&mut 0).unwrap(), 128); + b.clear(); + assert_eq!(b.len(), 0); + assert!(b.is_empty()); + } + + #[test] + fn buffer_sizing() { + const SIZE: usize = 100; + + for _ in 0..1000 { + let v = [0u8; SIZE]; + let mut b = Buffer::::new(); + assert!(b.append_bytes(&v).is_ok()); + assert_eq!(b.len(), SIZE); + b.set_size(10); + assert_eq!(b.len(), 10); + unsafe { + b.set_size_unchecked(8675309); + } + assert_eq!(b.len(), 8675309); + } + } +} diff --git a/src/utils/src/canonicalarc.rs b/src/utils/src/canonicalarc.rs new file mode 100644 index 0000000..c1c7d42 --- /dev/null +++ b/src/utils/src/canonicalarc.rs @@ -0,0 +1,80 @@ +use std::borrow::Borrow; +use std::hash::{Hash, Hasher}; +use std::ops::Deref; +use std::sync::Arc; + +/// Wrapper around an Arc that causes it to hash and compare (equality) by its pointer identity. +/// +/// This can be used as e.g. a key in a HashMap to index by concrete object identity rather than the +/// value of the object contained in Arc<>. +#[repr(transparent)] +pub struct CanonicalArc(Arc); + +impl CanonicalArc { + #[inline(always)] + pub fn cast_arc_ref(r: &Arc) -> &Self { + // Should be safe since this is #[repr(transparent)] + debug_assert_eq!(std::mem::size_of::>(), std::mem::size_of::>()); + unsafe { std::mem::transmute(r) } + } +} + +impl Hash for CanonicalArc { + #[inline(always)] + fn hash(&self, state: &mut H) { + Arc::as_ptr(&self.0).hash(state) + } +} + +impl PartialEq for CanonicalArc { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} + +impl Eq for CanonicalArc {} + +impl AsRef> for CanonicalArc { + #[inline(always)] + fn as_ref(&self) -> &Arc { + &self.0 + } +} + +impl Deref for CanonicalArc { + type Target = Arc; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for CanonicalArc { + #[inline(always)] + fn from(value: Arc) -> Self { + Self(value) + } +} + +impl From> for Arc { + #[inline(always)] + fn from(value: CanonicalArc) -> Self { + value.0 + } +} + +impl Clone for CanonicalArc { + #[inline(always)] + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Borrow> for CanonicalArc { + #[inline(always)] + fn borrow(&self) -> &Arc { + &self.0 + } +} diff --git a/src/utils/src/cast.rs b/src/utils/src/cast.rs new file mode 100644 index 0000000..38406aa --- /dev/null +++ b/src/utils/src/cast.rs @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::any::TypeId; +use std::mem::size_of; + +/// Returns true if two types are in fact the same type. +#[inline(always)] +pub fn same_type() -> bool { + TypeId::of::() == TypeId::of::() && size_of::() == size_of::() +} + +/// Cast a reference if the types are equal, such as from a specific type to a generic that it implements. +#[inline(always)] +pub fn cast_ref(u: &U) -> Option<&V> { + if same_type::() { + Some(unsafe { std::mem::transmute::<&U, &V>(u) }) + } else { + None + } +} + +/// Cast a reference if the types are equal, such as from a specific type to a generic that it implements. +#[inline(always)] +pub fn cast_mut(u: &mut U) -> Option<&mut V> { + if same_type::() { + Some(unsafe { std::mem::transmute::<&mut U, &mut V>(u) }) + } else { + None + } +} diff --git a/src/utils/src/defer.rs b/src/utils/src/defer.rs new file mode 100644 index 0000000..90e398e --- /dev/null +++ b/src/utils/src/defer.rs @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +struct Defer(Option); + +impl Drop for Defer { + fn drop(&mut self) { + if let Some(f) = self.0.take() { + f() + } + } +} + +/// Defer execution of a closure until the return value is dropped. +/// +/// This mimics the defer statement in Go, allowing you to always do some cleanup at +/// the end of a function no matter where it exits. +pub fn defer(f: F) -> impl Drop { + Defer(Some(f)) +} diff --git a/src/utils/src/dictionary.rs b/src/utils/src/dictionary.rs new file mode 100644 index 0000000..393b1c8 --- /dev/null +++ b/src/utils/src/dictionary.rs @@ -0,0 +1,209 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::collections::BTreeMap; +use std::io::Write; + +use crate::hex; + +const BOOL_TRUTH: &str = "1tTyY"; + +/// Dictionary is an extremely simple key=value serialization format. +/// +/// It's designed for extreme parsing simplicity and is human readable if keys and values are strings. +/// It also supports binary keys and values which will be minimally escaped but render the result not +/// entirely human readable. Keys are serialized in natural sort order so the result can be consistently +/// checksummed or hashed. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Dictionary(pub(crate) BTreeMap>); + +fn write_escaped(mut b: &[u8], w: &mut W) -> std::io::Result<()> { + while !b.is_empty() { + match b[0] { + 0 => { + w.write_all(&[b'\\', b'0'])?; + } + b'\n' => { + w.write_all(&[b'\\', b'n'])?; + } + b'\r' => { + w.write_all(&[b'\\', b'r'])?; + } + b'=' => { + w.write_all(&[b'\\', b'e'])?; + } + b'\\' => { + w.write_all(&[b'\\', b'\\'])?; + } + _ => { + w.write_all(&b[..1])?; + } + } + b = &b[1..]; + } + Ok(()) +} + +fn append_printable(s: &mut String, b: &[u8]) { + for c in b { + let c = *c as char; + if c.is_alphanumeric() || c.is_whitespace() { + s.push(c); + } else { + s.push('\\'); + s.push('x'); + s.push(hex::HEX_CHARS[((c as u8) >> 4) as usize] as char); + s.push(hex::HEX_CHARS[((c as u8) & 0xf) as usize] as char); + } + } +} + +impl Dictionary { + pub fn new() -> Self { + Self(BTreeMap::new()) + } + + pub fn clear(&mut self) { + self.0.clear() + } + + #[inline(always)] + pub fn len(&self) -> usize { + self.0.len() + } + + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn get_str(&self, k: &str) -> Option<&str> { + self.0.get(k).and_then(|v| std::str::from_utf8(v.as_slice()).ok()) + } + + pub fn get_bytes(&self, k: &str) -> Option<&[u8]> { + self.0.get(k).map(|v| v.as_slice()) + } + + pub fn get_u64(&self, k: &str) -> Option { + self.get_str(k).and_then(|s| u64::from_str_radix(s, 16).ok()) + } + + pub fn get_i64(&self, k: &str) -> Option { + self.get_str(k).and_then(|s| i64::from_str_radix(s, 16).ok()) + } + + pub fn get_bool(&self, k: &str) -> Option { + self.0 + .get(k) + .and_then(|v| v.first().map_or(Some(false), |c| Some(BOOL_TRUTH.contains(*c as char)))) + } + + pub fn set_str(&mut self, k: &str, v: &str) { + let _ = self.0.insert(String::from(k), v.as_bytes().to_vec()); + } + + pub fn set_u64(&mut self, k: &str, v: u64) { + let _ = self.0.insert(String::from(k), hex::to_vec_u64(v, true)); + } + + pub fn set_bytes(&mut self, k: &str, v: Vec) { + let _ = self.0.insert(String::from(k), v); + } + + pub fn set_bool(&mut self, k: &str, v: bool) { + let _ = self.0.insert( + String::from(k), + vec![if v { + b'1' + } else { + b'0' + }], + ); + } + + pub fn write_to(&self, w: &mut W) -> std::io::Result<()> { + for kv in self.0.iter() { + write_escaped(kv.0.as_bytes(), w)?; + w.write_all(&[b'='])?; + write_escaped(kv.1.as_slice(), w)?; + w.write_all(&[b'\n'])?; + } + Ok(()) + } + + pub fn to_bytes(&self) -> Vec { + let mut b: Vec = Vec::with_capacity(32 * self.0.len()); + let _ = self.write_to(&mut b); + b + } + + pub fn from_bytes(b: &[u8]) -> Option { + let mut d = Dictionary::new(); + let mut kv: [Vec; 2] = [Vec::new(), Vec::new()]; + let mut state = 0; + let mut escape = false; + for c in b { + let c = *c; + if escape { + escape = false; + kv[state].push(match c { + b'0' => 0, + b'n' => b'\n', + b'r' => b'\r', + b'e' => b'=', + _ => c, // =, \, and escapes before other characters are unnecessary but not errors + }); + } else if c == b'\\' { + escape = true; + } else if c == b'=' { + if state != 0 { + return None; + } + state = 1; + } else if c == b'\n' { + if state != 1 { + return None; + } + state = 0; + if !kv[0].is_empty() + && String::from_utf8(kv[0].clone()).map_or(true, |key| { + d.0.insert(key, kv[1].clone()); + false + }) + { + return None; + } + kv[0].clear(); + kv[1].clear(); + } else if c != b'\r' { + kv[state].push(c); + } + } + Some(d) + } + + pub fn iter(&self) -> impl Iterator)> { + self.0.iter() + } +} + +impl ToString for Dictionary { + /// Get the dictionary in an always readable format with non-printable characters replaced by '\xXX'. + /// This is not a serializable output that can be re-imported. Use write_to() for that. + fn to_string(&self) -> String { + let mut s = String::new(); + for kv in self.0.iter() { + append_printable(&mut s, kv.0.as_bytes()); + s.push('='); + append_printable(&mut s, kv.1.as_slice()); + s.push('\n'); + } + s + } +} diff --git a/src/utils/src/error.rs b/src/utils/src/error.rs new file mode 100644 index 0000000..a958e77 --- /dev/null +++ b/src/utils/src/error.rs @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::error::Error; +use std::fmt::{Debug, Display}; + +pub struct UnexpectedError; + +impl Display for UnexpectedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("UnexpectedError") + } +} + +impl Debug for UnexpectedError { + #[inline(always)] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ::fmt(self, f) + } +} + +impl Error for UnexpectedError {} + +pub struct InvalidFormatError; + +impl Display for InvalidFormatError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("InvalidFormatError") + } +} + +impl Debug for InvalidFormatError { + #[inline(always)] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ::fmt(self, f) + } +} + +impl Error for InvalidFormatError {} + +pub struct InvalidParameterError(pub &'static str); + +impl Display for InvalidParameterError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "InvalidParameterError: {}", self.0) + } +} + +impl Debug for InvalidParameterError { + #[inline(always)] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ::fmt(self, f) + } +} + +impl Error for InvalidParameterError {} diff --git a/src/utils/src/exitcode.rs b/src/utils/src/exitcode.rs new file mode 100644 index 0000000..7cb96c3 --- /dev/null +++ b/src/utils/src/exitcode.rs @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +// These were taken from BSD sysexits.h to provide some standard for process exit codes. + +pub const OK: i32 = 0; + +pub const ERR_USAGE: i32 = 64; +pub const ERR_DATA_FORMAT: i32 = 65; +pub const ERR_NO_INPUT: i32 = 66; +pub const ERR_SERVICE_UNAVAILABLE: i32 = 69; +pub const ERR_INTERNAL: i32 = 70; +pub const ERR_OSERR: i32 = 71; +pub const ERR_OSFILE: i32 = 72; +pub const ERR_IOERR: i32 = 74; +pub const ERR_NOPERM: i32 = 77; +pub const ERR_CONFIG: i32 = 78; diff --git a/src/utils/src/flatsortedmap.rs b/src/utils/src/flatsortedmap.rs new file mode 100644 index 0000000..9e22f59 --- /dev/null +++ b/src/utils/src/flatsortedmap.rs @@ -0,0 +1,86 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::borrow::Cow; +use std::iter::{FromIterator, Iterator}; + +use serde::{Deserialize, Serialize}; + +/// A simple flat sorted map backed by a vector and binary search. +/// +/// This doesn't support gradual adding of keys or removal of keys, but only construction +/// from an iterator of keys and values. It also implements Serialize and Deserialize and +/// is mainly intended for memory and space efficient serializable lookup tables. +/// +/// If the iterator supplies more than one key with different values, which of these is +/// included is undefined. +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] +#[repr(transparent)] +pub struct FlatSortedMap<'a, K: Eq + Ord + Clone, V: Clone>(Cow<'a, [(K, V)]>); + +impl<'a, K: Eq + Ord + Clone, V: Clone> FromIterator<(K, V)> for FlatSortedMap<'a, K, V> { + #[inline] + fn from_iter>(iter: T) -> Self { + let mut tmp = Vec::from_iter(iter); + tmp.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + tmp.dedup_by(|a, b| a.0.eq(&b.0)); + Self(Cow::Owned(tmp)) + } +} + +impl<'a, K: Eq + Ord + Clone, V: Clone> Default for FlatSortedMap<'a, K, V> { + #[inline(always)] + fn default() -> Self { + Self(Cow::Owned(Vec::new())) + } +} + +impl<'a, K: Eq + Ord + Clone, V: Clone> FlatSortedMap<'a, K, V> { + #[inline] + pub fn get(&self, k: &K) -> Option<&V> { + if let Ok(idx) = self.0.binary_search_by(|a| a.0.cmp(k)) { + Some(unsafe { &self.0.get_unchecked(idx).1 }) + } else { + None + } + } + + #[inline] + pub fn contains(&self, k: &K) -> bool { + self.0.binary_search_by(|a| a.0.cmp(k)).is_ok() + } + + /// Returns true if this map is valid, meaning that it contains only one of each key and is sorted. + #[inline] + pub fn is_valid(&self) -> bool { + let l = self.0.len(); + if l > 1 { + for i in 1..l { + if unsafe { !self.0.get_unchecked(i - 1).0.cmp(&self.0.get_unchecked(i).0).is_lt() } { + return false; + } + } + } + true + } + + #[inline(always)] + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + #[inline(always)] + pub fn len(&self) -> usize { + self.0.len() + } + + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} diff --git a/src/utils/src/gate.rs b/src/utils/src/gate.rs new file mode 100644 index 0000000..e3a8aa6 --- /dev/null +++ b/src/utils/src/gate.rs @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +/// Boolean rate limiter with normal (non-atomic) semantics. +#[repr(transparent)] +pub struct IntervalGate(i64); + +impl Default for IntervalGate { + #[inline(always)] + fn default() -> Self { + Self(crate::NEVER_HAPPENED_TICKS) + } +} + +impl IntervalGate { + #[inline(always)] + pub fn new(initial_ts: i64) -> Self { + Self(initial_ts) + } + + #[inline(always)] + pub fn gate(&mut self, time: i64) -> bool { + if (time - self.0) >= FREQ { + self.0 = time; + true + } else { + false + } + } +} diff --git a/src/utils/src/hex.rs b/src/utils/src/hex.rs new file mode 100644 index 0000000..0fbad1d --- /dev/null +++ b/src/utils/src/hex.rs @@ -0,0 +1,124 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +pub const HEX_CHARS: [u8; 16] = [ + b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f', +]; + +/// Encode a byte slice to a hexadecimal string. +pub fn to_string(b: &[u8]) -> String { + let mut s = String::with_capacity(b.len() * 2); + s.reserve(b.len() * 2); + for c in b { + let x = *c as usize; + s.push(HEX_CHARS[x >> 4] as char); + s.push(HEX_CHARS[x & 0xf] as char); + } + s +} + +/// Encode an unsigned 64-bit value as a hexadecimal string. +pub fn to_string_u64(mut i: u64, skip_leading_zeroes: bool) -> String { + let mut s = String::with_capacity(16); + for _ in 0..16 { + let ii = i >> 60; + if ii != 0 || !s.is_empty() || !skip_leading_zeroes { + s.push(HEX_CHARS[ii as usize] as char); + } + i = i.wrapping_shl(4); + } + s +} + +/// Encode an unsigned 64-bit value as a hexadecimal ASCII string. +pub fn to_vec_u64(mut i: u64, skip_leading_zeroes: bool) -> Vec { + let mut s = Vec::with_capacity(16); + for _ in 0..16 { + let ii = i >> 60; + if ii != 0 || !s.is_empty() || !skip_leading_zeroes { + s.push(HEX_CHARS[ii as usize]); + } + i = i.wrapping_shl(4); + } + s +} + +/// Decode a hex string, ignoring all non-hexadecimal characters. +pub fn from_string(s: &str) -> Vec { + let mut b: Vec = Vec::with_capacity((s.len() / 2) + 1); + let mut byte = 0_u8; + let mut have_8: bool = false; + for cc in s.as_bytes() { + let c = *cc; + if (48..=57).contains(&c) { + byte = (byte.wrapping_shl(4)) | (c - 48); + if have_8 { + b.push(byte); + } + have_8 = !have_8; + } else if (65..=70).contains(&c) { + byte = (byte.wrapping_shl(4)) | (c - 55); + if have_8 { + b.push(byte); + } + have_8 = !have_8; + } else if (97..=102).contains(&c) { + byte = (byte.wrapping_shl(4)) | (c - 87); + if have_8 { + b.push(byte); + } + have_8 = !have_8; + } + } + b +} + +pub fn from_string_u64(s: &str) -> u64 { + let mut n = 0u64; + let mut byte = 0_u8; + let mut have_8: bool = false; + for cc in s.as_bytes() { + let c = *cc; + if (48..=57).contains(&c) { + byte = (byte.wrapping_shl(4)) | (c - 48); + if have_8 { + n = n.wrapping_shl(8); + n |= byte as u64; + } + have_8 = !have_8; + } else if (65..=70).contains(&c) { + byte = (byte.wrapping_shl(4)) | (c - 55); + if have_8 { + n = n.wrapping_shl(8); + n |= byte as u64; + } + have_8 = !have_8; + } else if (97..=102).contains(&c) { + byte = (byte.wrapping_shl(4)) | (c - 87); + if have_8 { + n = n.wrapping_shl(8); + n |= byte as u64; + } + have_8 = !have_8; + } + } + n +} + +/// Encode bytes from 'b' into hex characters in 'dest' and return the number of hex characters written. +/// This will panic if the destination slice is smaller than twice the length of the source. +pub fn to_hex_bytes(b: &[u8], dest: &mut [u8]) -> usize { + let mut j = 0; + for c in b { + let x = *c as usize; + dest[j] = HEX_CHARS[x >> 4]; + dest[j + 1] = HEX_CHARS[x & 0xf]; + j += 2; + } + j +} diff --git a/src/utils/src/indexed_heap.rs b/src/utils/src/indexed_heap.rs new file mode 100644 index 0000000..2838c1d --- /dev/null +++ b/src/utils/src/indexed_heap.rs @@ -0,0 +1,210 @@ +#[derive(Eq, PartialEq, Hash, Clone, Copy)] +pub struct BinaryHeapIndex(usize, u64); + +const RESERVED_MARKER: u64 = 1; +const EMPTY_MARKER: u64 = 0; + +/// A simple Priority Queue built from a binary heap and a generational array. +/// Entries in the queue are accessed and updated by their generational index. +/// This allows for extremely simple memory management and fast queue updates. +pub struct IndexedBinaryHeap { + generation: u64, + free_list_head: usize, + data: Vec<(T, P, usize)>, + map: Vec<(usize, u64)>, +} + +impl IndexedBinaryHeap { + pub fn new() -> Self { + Self { + generation: 1, + free_list_head: usize::MAX, + data: Vec::new(), + map: Vec::new(), + } + } + pub fn with_capacity(capacity: usize) -> Self { + Self { + generation: 1, + free_list_head: usize::MAX, + data: Vec::with_capacity(capacity), + map: Vec::with_capacity(capacity), + } + } + pub fn peek(&self) -> Option<(&T, &P, BinaryHeapIndex)> { + self.data + .first() + .map(|entry| (&entry.0, &entry.1, BinaryHeapIndex(entry.2, self.map[entry.2].1))) + } + pub fn peek_mut(&mut self) -> Option<(&mut T, &P, BinaryHeapIndex)> { + self.data + .first_mut() + .map(|entry| (&mut entry.0, &entry.1, BinaryHeapIndex(entry.2, self.map[entry.2].1))) + } + #[inline] + fn swap(&mut self, a: usize, b: usize) { + self.map[self.data[a].2].0 = b; + self.map[self.data[b].2].0 = a; + self.data.swap(a, b); + } + fn bubble_down(&mut self, mut parent_idx: usize) { + loop { + let child0_idx = parent_idx * 2 + 1; + let child1_idx = child0_idx + 1; + if child0_idx < self.data.len() { + let largest_child = if child1_idx < self.data.len() && self.data[child1_idx].1 > self.data[child0_idx].1 { + child1_idx + } else { + child0_idx + }; + if self.data[largest_child].1 > self.data[parent_idx].1 { + self.swap(parent_idx, largest_child); + parent_idx = largest_child; + } else { + break; + } + } else { + break; + } + } + } + fn bubble_up(&mut self, mut child_idx: usize) { + while child_idx > 0 { + let parent_idx = (child_idx - 1) / 2; + if self.data[child_idx].1 > self.data[parent_idx].1 { + self.swap(parent_idx, child_idx); + child_idx = parent_idx; + } else { + break; + } + } + } + fn remove_idx(&mut self, data_idx: usize) -> (T, P) { + self.swap(data_idx, self.data.len() - 1); + let ret = self.data.pop().unwrap(); + self.map[ret.2] = (self.free_list_head, EMPTY_MARKER); + self.free_list_head = ret.2; + + self.bubble_down(data_idx); + + (ret.0, ret.1) + } + fn deref_index(&self, idx: BinaryHeapIndex) -> Option { + (idx.0 < self.map.len() && self.map[idx.0].1 == idx.1).then(|| self.map[idx.0].0) + } + pub fn pop(&mut self) -> Option<(T, P)> { + (self.data.len() > 0).then(|| self.remove_idx(0)) + } + /// Add an item to the queue and get back a generational index which allows for quick updating + /// of this item and its priority. + pub fn push(&mut self, item: T, priority: P) -> BinaryHeapIndex { + let idx = self.reserve_index(); + self.push_reserved(idx, item, priority); + idx + } + /// Reserve a generational index. It will not have an associated item until + /// `push_reserved` is called. + /// If the index is dropped without having been associated with a queue item it will be leaked, + /// causing the queue to consume a few bytes more of memory than it should. + pub fn reserve_index(&mut self) -> BinaryHeapIndex { + self.generation += 1; + if self.free_list_head != usize::MAX { + let pre_head = self.free_list_head; + self.free_list_head = self.map[pre_head].0; + self.map[pre_head] = (0, RESERVED_MARKER); + BinaryHeapIndex(pre_head, self.generation) + } else { + self.map.push((0, RESERVED_MARKER)); + BinaryHeapIndex(self.map.len() - 1, self.generation) + } + } + /// Add an item to the queue with the specific reserved index. + /// If this index already has an associated item this will return false. + pub fn push_reserved(&mut self, idx: BinaryHeapIndex, item: T, priority: P) -> bool { + if idx.0 < self.map.len() && self.map[idx.0].1 == RESERVED_MARKER { + let data_idx = self.data.len(); + self.map[idx.0] = (data_idx, idx.1); + + self.data.push((item, priority, idx.0)); + self.bubble_up(data_idx); + true + } else { + false + } + } + pub fn change_priority(&mut self, idx: BinaryHeapIndex, new_priority: P) -> Option

{ + self.deref_index(idx).map(|data_idx| { + let c = if self.data[data_idx].1 < new_priority { + 1 + } else if self.data[data_idx].1 > new_priority { + 2 + } else { + 3 + }; + let old_priority = std::mem::replace(&mut self.data[data_idx].1, new_priority); + if c == 1 { + self.bubble_up(data_idx); + } else if c == 2 { + self.bubble_down(data_idx); + } + old_priority + }) + } + pub fn change_item(&mut self, idx: BinaryHeapIndex, new_item: T) -> Option { + self.deref_index(idx) + .map(|data_idx| std::mem::replace(&mut self.data[data_idx].0, new_item)) + } + pub fn get(&self, idx: BinaryHeapIndex) -> Option<(&T, &P)> { + self.deref_index(idx).map(|data_idx| (&self.data[data_idx].0, &self.data[data_idx].1)) + } + pub fn get_mut(&mut self, idx: BinaryHeapIndex) -> Option<(&mut T, &P)> { + self.deref_index(idx).map(|data_idx| { + let entry = &mut self.data[data_idx]; + (&mut entry.0, &entry.1) + }) + } + /// Remove this index and its associated item from the queue, returning the item if it exists. + /// This can also be used to remove reserved indices from the queue. + pub fn remove(&mut self, idx: BinaryHeapIndex) -> Option<(T, P)> { + if idx.0 < self.map.len() { + if self.map[idx.0].1 == RESERVED_MARKER { + self.map[idx.0] = (self.free_list_head, EMPTY_MARKER); + self.free_list_head = idx.0; + None + } else if self.map[idx.0].1 == idx.1 { + Some(self.remove_idx(self.map[idx.0].0)) + } else { + None + } + } else { + None + } + } + pub fn clear(&mut self) { + self.free_list_head = usize::MAX; + self.map.clear(); + self.data.clear(); + } +} + +#[test] +fn test() { + let mut queue = IndexedBinaryHeap::new(); + let r0 = queue.push(1234, 1234); + for i in 0..100 { + queue.push(2 * i, 2 * i); + } + let r1 = queue.push(1234, 12); + assert_eq!(queue.remove(r0), Some((1234, 1234))); + for i in (0..100).rev() { + queue.push(2 * i + 1, 2 * i + 1); + } + assert_eq!(queue.change_priority(r1, 1234), Some(12)); + assert_eq!(queue.remove(r0), None); + let mut last = usize::MAX; + while let Some((i, j)) = queue.pop() { + assert_eq!(i, j); + assert!(i <= last); + last = i; + } +} diff --git a/src/utils/src/io.rs b/src/utils/src/io.rs new file mode 100644 index 0000000..a67f83b --- /dev/null +++ b/src/utils/src/io.rs @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::fs::File; +use std::io::Read; +use std::path::Path; + +/// Default sanity limit parameter for read_limit() used throughout the service. +pub const DEFAULT_FILE_IO_READ_LIMIT: usize = 262144; + +/// Convenience function to read up to limit bytes from a file. +/// +/// If the file is larger than limit, the excess is not read. +pub fn read_limit>(path: P, limit: usize) -> std::io::Result> { + let mut f = File::open(path)?; + let bytes = f.metadata()?.len().min(limit as u64) as usize; + let mut v: Vec = Vec::with_capacity(bytes); + v.resize(bytes, 0); + f.read_exact(v.as_mut_slice())?; + Ok(v) +} + +/// Set permissions on a file or directory to be most restrictive (visible only to the service's user). +#[cfg(unix)] +pub fn fs_restrict_permissions>(path: P) -> bool { + unsafe { + let c_path = std::ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap(); + libc::chmod( + c_path.as_ptr(), + if path.as_ref().is_dir() { + 0o700 + } else { + 0o600 + }, + ) == 0 + } +} + +/// Set permissions on a file or directory to be most restrictive (visible only to the service's user). +#[cfg(windows)] +pub fn fs_restrict_permissions>(path: P) -> bool { + todo!() +} diff --git a/src/utils/src/json.rs b/src/utils/src/json.rs new file mode 100644 index 0000000..63f63e0 --- /dev/null +++ b/src/utils/src/json.rs @@ -0,0 +1,202 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use serde::de::DeserializeOwned; +use serde::Serialize; +use serde_json::ser::Formatter; + +/// Recursively patch a JSON object. +/// +/// This is slightly different from a usual JSON merge. For objects in the target their fields +/// are updated by recursively calling json_patch if the same field is present in the source. +/// If the source tries to set an object to something other than another object, this is ignored. +/// Other fields are replaced. This is used for RESTful config object updates. The depth limit +/// field is to prevent stack overflows via the API. +pub fn json_patch(target: &mut serde_json::value::Value, source: &serde_json::value::Value, depth_limit: usize) { + if target.is_object() { + if source.is_object() { + let target = target.as_object_mut().unwrap(); + let source = source.as_object().unwrap(); + for kv in target.iter_mut() { + let _ = source.get(kv.0).map(|new_value| { + if depth_limit > 0 { + json_patch(kv.1, new_value, depth_limit - 1) + } + }); + } + for kv in source.iter() { + if !target.contains_key(kv.0) && !kv.1.is_null() { + target.insert(kv.0.clone(), kv.1.clone()); + } + } + } + } else if *target != *source { + *target = source.clone(); + } +} + +/// Patch a serializable object with the fields present in a JSON object. +/// +/// If there are no changes, None is returned. The depth limit is passed through to json_patch and +/// should be set to a sanity check value to prevent overflows. +pub fn json_patch_object(obj: O, patch: &str, depth_limit: usize) -> Result, serde_json::Error> { + serde_json::from_str::(patch).map_or_else(Err, |patch| { + serde_json::value::to_value(&obj).map_or_else(Err, |mut obj_value| { + json_patch(&mut obj_value, &patch, depth_limit); + serde_json::value::from_value::(obj_value).map_or_else(Err, |obj_merged| { + if obj == obj_merged { + Ok(None) + } else { + Ok(Some(obj_merged)) + } + }) + }) + }) +} + +/// Shortcut to use serde_json to serialize an object, returns "null" on error. +pub fn to_json(o: &O) -> String { + serde_json::to_string(o).unwrap_or("null".into()) +} + +/// Shortcut to use serde_json to serialize an object, returns "null" on error. +pub fn to_json_pretty(o: &O) -> String { + let mut buf = Vec::new(); + let mut ser = serde_json::Serializer::with_formatter(&mut buf, PrettyFormatter::new()); + if o.serialize(&mut ser).is_ok() { + String::from_utf8(buf).unwrap_or_else(|_| "null".into()) + } else { + "null".into() + } +} + +/// JSON formatter that looks a bit better than the Serde default. +pub struct PrettyFormatter<'a> { + current_indent: usize, + has_value: bool, + indent: &'a [u8], +} + +fn indent(wr: &mut W, n: usize, s: &[u8]) -> std::io::Result<()> +where + W: ?Sized + std::io::Write, +{ + for _ in 0..n { + wr.write_all(s)?; + } + Ok(()) +} + +impl<'a> PrettyFormatter<'a> { + pub fn new() -> Self { + Self::with_indent(b" ") + } + + pub fn with_indent(indent: &'a [u8]) -> Self { + Self { current_indent: 0, has_value: false, indent } + } +} + +impl<'a> Default for PrettyFormatter<'a> { + fn default() -> Self { + Self::new() + } +} + +impl<'a> Formatter for PrettyFormatter<'a> { + fn begin_array(&mut self, writer: &mut W) -> std::io::Result<()> + where + W: ?Sized + std::io::Write, + { + self.current_indent += 1; + self.has_value = false; + writer.write_all(b"[") + } + + fn end_array(&mut self, writer: &mut W) -> std::io::Result<()> + where + W: ?Sized + std::io::Write, + { + self.current_indent -= 1; + if self.has_value { + writer.write_all(b" ]") + } else { + writer.write_all(b"]") + } + } + + fn begin_array_value(&mut self, writer: &mut W, first: bool) -> std::io::Result<()> + where + W: ?Sized + std::io::Write, + { + if first { + writer.write_all(b" ")?; + } else { + writer.write_all(b", ")?; + } + Ok(()) + } + + fn end_array_value(&mut self, _writer: &mut W) -> std::io::Result<()> + where + W: ?Sized + std::io::Write, + { + self.has_value = true; + Ok(()) + } + + fn begin_object(&mut self, writer: &mut W) -> std::io::Result<()> + where + W: ?Sized + std::io::Write, + { + self.current_indent += 1; + self.has_value = false; + writer.write_all(b"{") + } + + fn end_object(&mut self, writer: &mut W) -> std::io::Result<()> + where + W: ?Sized + std::io::Write, + { + self.current_indent -= 1; + + if self.has_value { + writer.write_all(b"\n")?; + indent(writer, self.current_indent, self.indent)?; + } + + writer.write_all(b"}") + } + + fn begin_object_key(&mut self, writer: &mut W, first: bool) -> std::io::Result<()> + where + W: ?Sized + std::io::Write, + { + if first { + writer.write_all(b"\n")?; + } else { + writer.write_all(b",\n")?; + } + indent(writer, self.current_indent, self.indent) + } + + fn begin_object_value(&mut self, writer: &mut W) -> std::io::Result<()> + where + W: ?Sized + std::io::Write, + { + writer.write_all(b": ") + } + + fn end_object_value(&mut self, _writer: &mut W) -> std::io::Result<()> + where + W: ?Sized + std::io::Write, + { + self.has_value = true; + Ok(()) + } +} diff --git a/src/utils/src/lib.rs b/src/utils/src/lib.rs new file mode 100644 index 0000000..7ea9b63 --- /dev/null +++ b/src/utils/src/lib.rs @@ -0,0 +1,114 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +pub mod arrayvec; +pub mod base24; +pub mod base62; +pub mod blob; +pub mod buffer; +pub mod canonicalarc; +pub mod cast; +pub mod defer; +pub mod dictionary; +pub mod error; +#[allow(unused)] +pub mod exitcode; +pub mod flatsortedmap; +pub mod gate; +pub mod hex; +pub mod indexed_heap; +pub mod io; +pub mod json; +pub mod marshalable; +pub mod memory; +pub mod pool; +#[cfg(feature = "tokio")] +pub mod reaper; +pub mod ringbuffer; +pub mod rwu_lock; +pub mod str; +pub mod sync; +pub mod varint; + +#[cfg(feature = "tokio")] +pub use tokio; + +/// Initial value that should be used for monotonic tick time variables. +pub const NEVER_HAPPENED_TICKS: i64 = i64::MIN; + +/// Get milliseconds since unix epoch. +#[inline] +pub fn ms_since_epoch() -> i64 { + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as i64 +} + +/// Get milliseconds since an arbitrary time in the past, guaranteed to monotonically increase. +#[inline] +pub fn ms_monotonic() -> i64 { + static STARTUP_INSTANT: std::sync::RwLock> = std::sync::RwLock::new(None); + let si = *STARTUP_INSTANT.read().unwrap(); + if let Some(si) = si { + si.elapsed().as_millis() as i64 + } else { + STARTUP_INSTANT + .write() + .unwrap() + .get_or_insert(std::time::Instant::now()) + .elapsed() + .as_millis() as i64 + } +} + +/// Wait for a kill signal (e.g. SIGINT or OS-equivalent) sent to this process and return when received. +#[cfg(unix)] +pub fn wait_for_process_abort() { + if let Ok(mut signals) = signal_hook::iterator::Signals::new([libc::SIGINT, libc::SIGTERM, libc::SIGQUIT]) { + 'wait_for_exit: loop { + for signal in signals.wait() { + match signal as libc::c_int { + libc::SIGINT | libc::SIGTERM | libc::SIGQUIT => { + break 'wait_for_exit; + } + _ => {} + } + } + std::thread::sleep(std::time::Duration::from_millis(100)); + } + } else { + panic!("unable to listen for OS signals"); + } +} + +#[cold] +#[inline(never)] +pub extern "C" fn unlikely_branch() {} + +#[cfg(unix)] +pub fn rand() -> u32 { + unsafe { (libc::rand() as u32) ^ (libc::rand() as u32).wrapping_shr(8) } +} + +#[cfg(test)] +mod tests { + use super::ms_monotonic; + use std::time::Duration; + + #[test] + fn monotonic_clock_sanity_check() { + let start = ms_monotonic(); + std::thread::sleep(Duration::from_millis(500)); + let end = ms_monotonic(); + // per docs: + // + // The thread may sleep longer than the duration specified due to scheduling specifics or + // platform-dependent functionality. It will never sleep less. + // + assert!((end - start).abs() >= 500); + assert!((end - start).abs() < 750); + } +} diff --git a/src/utils/src/marshalable.rs b/src/utils/src/marshalable.rs new file mode 100644 index 0000000..24a1bde --- /dev/null +++ b/src/utils/src/marshalable.rs @@ -0,0 +1,169 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::error::Error; +use std::fmt::{Debug, Display}; + +use crate::buffer::{Buffer, OutOfBoundsError}; + +/// A super-lightweight zero-allocation serialization interface. +pub trait Marshalable: Sized { + const MAX_MARSHAL_SIZE: usize; + + /// Write this object into a buffer. + fn marshal(&self, buf: &mut Buffer) -> Result<(), OutOfBoundsError>; + + /// Read this object from a buffer. + /// + /// The supplied cursor is advanced by the number of bytes read. If an Err is returned + /// the value of the cursor is undefined but likely points to about where the error + /// occurred. It may also point beyond the buffer, which would indicate an overrun error. + fn unmarshal(buf: &Buffer, cursor: &mut usize) -> Result; + + /// Write this marshalable entity into a buffer of the given size. + /// + /// This will return an Err if the buffer is too small or some other error occurs. It's just + /// a shortcut to creating a buffer and marshaling into it. + #[inline] + fn to_buffer(&self) -> Result, OutOfBoundsError> { + let mut tmp = Buffer::new(); + self.marshal(&mut tmp)?; + Ok(tmp) + } + + /* + /// Write this marshalable entity into a buffer of the given size. + /// + /// This will return an Err if the buffer is too small or some other error occurs. It's just + /// a shortcut to creating a buffer and marshaling into it. + #[inline] + fn to_buffer(&self) -> Result, UnmarshalError> { + let mut tmp = Buffer::new(); + self.marshal(&mut tmp)?; + Ok(tmp) + } + + /// Unmarshal this object from a buffer. + /// + /// This is just a shortcut to calling unmarshal() with a zero cursor and then discarding the cursor. + #[inline] + fn from_buffer(buf: &Buffer) -> Result { + let mut tmp = 0; + Self::unmarshal(buf, &mut tmp) + } + + /// Marshal and convert to a Rust vector. + #[inline] + fn to_bytes(&self) -> Vec { + assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE); + let mut tmp = Buffer::::new(); + assert!(self.marshal(&mut tmp).is_ok()); // panics if TEMP_BUF_SIZE is too small + tmp.as_bytes().to_vec() + } + + /// Unmarshal from a raw slice. + #[inline] + fn from_bytes(b: &[u8]) -> Result { + if b.len() <= TEMP_BUF_SIZE { + let mut tmp = Buffer::::new_boxed(); + assert!(tmp.append_bytes(b).is_ok()); + let mut cursor = 0; + Self::unmarshal(&tmp, &mut cursor) + } else { + Err(UnmarshalError::OutOfBounds) + } + } + + /// Marshal a slice of marshalable objects to a concatenated byte vector. + #[inline] + fn marshal_multiple_to_bytes(objects: &[Self]) -> Result, UnmarshalError> { + assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE); + let mut tmp: Buffer<{ TEMP_BUF_SIZE }> = Buffer::new(); + let mut v: Vec = Vec::with_capacity(objects.len() * Self::MAX_MARSHAL_SIZE); + for i in objects.iter() { + i.marshal(&mut tmp)?; + let _ = v.write_all(tmp.as_bytes()); + tmp.clear(); + } + Ok(v) + } + + /// Unmarshal a concatenated byte slice of marshalable objects. + #[inline] + fn unmarshal_multiple_from_bytes(mut bytes: &[u8]) -> Result, UnmarshalError> { + assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE); + let mut tmp: Buffer<{ TEMP_BUF_SIZE }> = Buffer::new(); + let mut v: Vec = Vec::new(); + while bytes.len() > 0 { + let chunk_size = bytes.len().min(Self::MAX_MARSHAL_SIZE); + if tmp.append_bytes(&bytes[..chunk_size]).is_err() { + return Err(UnmarshalError::OutOfBounds); + } + let mut cursor = 0; + v.push(Self::unmarshal(&mut tmp, &mut cursor)?); + if cursor == 0 { + return Err(UnmarshalError::InvalidData); + } + let _ = tmp.erase_first_n(cursor); + bytes = &bytes[chunk_size..]; + } + Ok(v) + } + + /// Unmarshal a buffer with a byte slice of marshalable objects. + #[inline] + fn unmarshal_multiple(buf: &Buffer, cursor: &mut usize, eof: usize) -> Result, UnmarshalError> { + let mut v: Vec = Vec::new(); + while *cursor < eof { + v.push(Self::unmarshal(buf, cursor)?); + } + Ok(v) + } + */ +} + +pub enum UnmarshalError { + OutOfBounds, + InvalidData, + UnsupportedVersion, + IoError(std::io::Error), +} + +impl Display for UnmarshalError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::OutOfBounds => f.write_str("out of bounds"), + Self::InvalidData => f.write_str("invalid data"), + Self::UnsupportedVersion => f.write_str("unsupported version"), + Self::IoError(e) => f.write_str(e.to_string().as_str()), + } + } +} + +impl Debug for UnmarshalError { + #[inline(always)] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(self, f) + } +} + +impl Error for UnmarshalError {} + +impl From for UnmarshalError { + #[inline(always)] + fn from(_: crate::buffer::OutOfBoundsError) -> Self { + Self::OutOfBounds + } +} + +impl From for UnmarshalError { + #[inline(always)] + fn from(e: std::io::Error) -> Self { + Self::IoError(e) + } +} diff --git a/src/utils/src/memory.rs b/src/utils/src/memory.rs new file mode 100644 index 0000000..841b2b5 --- /dev/null +++ b/src/utils/src/memory.rs @@ -0,0 +1,121 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +// This is a collection of functions that use "unsafe" to do things with memory that should in fact +// be safe. Some of these may eventually get stable standard library replacements. + +#[allow(unused_imports)] +use std::mem::{needs_drop, size_of, MaybeUninit}; + +#[allow(unused_imports)] +use std::ptr::copy_nonoverlapping; + +/// Implement this trait to mark a struct as safe to cast from a byte array. +pub unsafe trait FlatBuffer: Sized {} + +/// Store a raw object to a byte array (for architectures known not to care about unaligned access). +/// This will panic if the slice is too small or the object requires drop. +#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64"))] +#[inline(always)] +pub fn store_raw(o: T, dest: &mut [u8]) { + assert!(!std::mem::needs_drop::()); + assert!(dest.len() >= size_of::()); + unsafe { *dest.as_mut_ptr().cast() = o }; +} + +/// Store a raw object to a byte array (portable). +/// This will panic if the slice is too small or the object requires drop. +#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))] +#[inline(always)] +pub fn store_raw(o: T, dest: &mut [u8]) { + assert!(!std::mem::needs_drop::()); + assert!(dest.len() >= size_of::()); + unsafe { copy_nonoverlapping((&o as *const T).cast(), dest.as_mut_ptr(), size_of::()) }; +} + +/// Load a raw object from a byte array (for architectures known not to care about unaligned access). +/// This will panic if the slice is too small or the object requires drop. +#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64"))] +#[inline(always)] +pub fn load_raw(src: &[u8]) -> T { + assert!(!std::mem::needs_drop::()); + assert!(src.len() >= size_of::()); + unsafe { *src.as_ptr().cast() } +} + +/// Load a raw object from a byte array (portable). +/// This will panic if the slice is too small or the object requires drop. +#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))] +#[inline(always)] +pub fn load_raw(src: &[u8]) -> T { + assert!(!std::mem::needs_drop::()); + assert!(src.len() >= size_of::()); + unsafe { + let mut tmp: T = MaybeUninit::uninit().assume_init(); + copy_nonoverlapping(src.as_ptr(), (&mut tmp as *mut T).cast(), size_of::()); + tmp + } +} + +/// Our version of the not-yet-stable array_chunks method in slice. +#[inline(always)] +pub fn array_chunks_exact(a: &[T]) -> impl Iterator { + let mut i = 0; + let l = a.len(); + std::iter::from_fn(move || { + let j = i + S; + if j <= l { + let next = unsafe { &*a.as_ptr().add(i).cast() }; + i = j; + Some(next) + } else { + None + } + }) +} + +/// Obtain a view into an array cast as another array. +/// This will panic if the template parameters would result in out of bounds access. +#[inline(always)] +pub fn array_range(a: &[T; S]) -> &[T; LEN] { + assert!((START + LEN) <= S); + unsafe { &*a.as_ptr().add(START).cast::<[T; LEN]>() } +} + +/// Get a reference to a raw object as a byte array. +/// The template parameter S must equal the size of the object in bytes or this will panic. +#[inline(always)] +pub fn as_byte_array(o: &T) -> &[u8; S] { + assert_eq!(S, size_of::()); + unsafe { &*(o as *const T).cast() } +} + +/// Get a reference to a raw object as a byte array. +/// The template parameter S must equal the size of the object in bytes or this will panic. +#[inline(always)] +pub fn as_byte_array_mut(o: &mut T) -> &mut [u8; S] { + assert_eq!(S, size_of::()); + unsafe { &mut *(o as *mut T).cast() } +} + +/// Transmute an object to a byte array. +/// The template parameter S must equal the size of the object in bytes or this will panic. +#[inline(always)] +pub fn to_byte_array(o: T) -> [u8; S] { + assert_eq!(S, size_of::()); + assert!(!std::mem::needs_drop::()); + unsafe { *(&o as *const T).cast() } +} + +/// Cast a byte slice into a flat struct. +/// This will panic if the slice is too small or the struct requires drop. +pub fn cast_to_struct(b: &[u8]) -> &T { + assert!(b.len() >= size_of::()); + assert!(!std::mem::needs_drop::()); + unsafe { &*b.as_ptr().cast() } +} diff --git a/src/utils/src/pool.rs b/src/utils/src/pool.rs new file mode 100644 index 0000000..d8a1a5a --- /dev/null +++ b/src/utils/src/pool.rs @@ -0,0 +1,251 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::ops::{Deref, DerefMut}; +use std::ptr::NonNull; +use std::sync::{Arc, Mutex, Weak}; + +/// Each pool requires a factory that creates and resets (for re-use) pooled objects. +pub trait PoolFactory { + fn create(&self) -> O; + fn reset(&self, obj: &mut O); +} + +/// Container for pooled objects that have been checked out of the pool. +/// +/// Objects are automagically returned to the pool when Pooled<> is dropped if the pool still exists. +/// If the pool itself is gone objects are freed. Two methods for conversion to/from raw pointers are +/// available for interoperation with foreign APIs. +#[repr(transparent)] +pub struct Pooled>(NonNull>); + +#[repr(C)] +struct PoolEntry> { + obj: O, // must be first + return_pool: Weak>, +} + +impl> Pooled { + /// Create a pooled object wrapper around an object but with no pool to return it to. + /// The object will be freed when this pooled container is dropped. + #[inline] + pub fn naked(o: O) -> Self { + unsafe { + Self(NonNull::new_unchecked(Box::into_raw(Box::new(PoolEntry:: { + obj: o, + return_pool: Weak::new(), + })))) + } + } + + /// Get a raw pointer to the object wrapped by this pooled object container. + /// + /// The returned pointer MUST be returned to the pooling system with from_raw() or memory + /// will leak. + #[inline] + pub unsafe fn into_raw(self) -> *mut O { + // Verify that the structure is not padded before 'obj'. + assert_eq!( + (&self.0.as_ref().obj as *const O).cast::(), + (self.0.as_ref() as *const PoolEntry).cast::() + ); + + let ptr = self.0.as_ptr().cast::(); + std::mem::forget(self); + ptr + } + + /// Restore a raw pointer from into_raw() into a Pooled object. + /// + /// The supplied pointer MUST have been obtained from a Pooled object. None is returned + /// if the pointer is null. + #[inline] + pub unsafe fn from_raw(raw: *mut O) -> Option { + if !raw.is_null() { + Some(Self(NonNull::new_unchecked(raw.cast()))) + } else { + None + } + } +} + +impl> Clone for Pooled +where + O: Clone, +{ + #[inline] + fn clone(&self) -> Self { + let internal = unsafe { &mut *self.0.as_ptr() }; + if let Some(p) = internal.return_pool.upgrade() { + if let Some(o) = p.pool.lock().unwrap().pop() { + let mut o = Self(o); + *o.as_mut() = self.as_ref().clone(); + o + } else { + Pooled::(unsafe { + NonNull::new_unchecked(Box::into_raw(Box::new(PoolEntry:: { + obj: self.as_ref().clone(), + return_pool: Arc::downgrade(&p), + }))) + }) + } + } else { + Self::naked(self.as_ref().clone()) + } + } +} + +unsafe impl> Send for Pooled where O: Send {} +unsafe impl> Sync for Pooled where O: Sync {} + +impl> Deref for Pooled { + type Target = O; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + unsafe { &self.0.as_ref().obj } + } +} + +impl> DerefMut for Pooled { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut self.0.as_mut().obj } + } +} + +impl> AsRef for Pooled { + #[inline(always)] + fn as_ref(&self) -> &O { + unsafe { &self.0.as_ref().obj } + } +} + +impl> AsMut for Pooled { + #[inline(always)] + fn as_mut(&mut self) -> &mut O { + unsafe { &mut self.0.as_mut().obj } + } +} + +impl> Drop for Pooled { + #[inline] + fn drop(&mut self) { + let internal = unsafe { &mut *self.0.as_ptr() }; + if let Some(p) = internal.return_pool.upgrade() { + p.factory.reset(&mut internal.obj); + p.pool.lock().unwrap().push(self.0); + } else { + drop(unsafe { Box::from_raw(self.0.as_ptr()) }); + } + } +} + +/// An object pool for Reusable objects. +/// Checked out objects are held by a guard object that returns them when dropped if +/// the pool still exists or drops them if the pool has itself been dropped. +pub struct Pool>(Arc>); + +struct PoolInner> { + factory: F, + pool: Mutex>>>, +} + +impl> Pool { + #[inline] + pub fn new(initial_stack_capacity: usize, factory: F) -> Self { + Self(Arc::new(PoolInner:: { + factory, + pool: Mutex::new(Vec::with_capacity(initial_stack_capacity)), + })) + } + + /// Get a pooled object, or allocate one if the pool is empty. + #[inline] + pub fn get(&self) -> Pooled { + if let Some(o) = self.0.pool.lock().unwrap().pop() { + return Pooled::(o); + } + Pooled::(unsafe { + NonNull::new_unchecked(Box::into_raw(Box::new(PoolEntry:: { + obj: self.0.factory.create(), + return_pool: Arc::downgrade(&self.0), + }))) + }) + } + + /// Dispose of all pooled objects, freeing any memory they use. + /// + /// If get() is called after this new objects will be allocated, and any outstanding + /// objects will still be returned on drop unless the pool itself is dropped. This can + /// be done to free some memory if there has been a spike in memory use. + #[inline] + pub fn purge(&self) { + for o in self.0.pool.lock().unwrap().drain(..) { + drop(unsafe { Box::from_raw(o.as_ptr()) }) + } + } +} + +impl> Drop for Pool { + #[inline(always)] + fn drop(&mut self) { + self.purge(); + } +} + +unsafe impl> Send for Pool {} +unsafe impl> Sync for Pool {} + +#[cfg(test)] +mod tests { + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::Arc; + use std::time::Duration; + + use super::*; + + struct TestPoolFactory; + + impl PoolFactory for TestPoolFactory { + fn create(&self) -> String { + String::new() + } + + fn reset(&self, obj: &mut String) { + obj.clear(); + } + } + + #[test] + fn threaded_pool_use() { + let p: Arc> = Arc::new(Pool::new(2, TestPoolFactory {})); + let ctr = Arc::new(AtomicUsize::new(0)); + for _ in 0..64 { + let p2 = p.clone(); + let ctr2 = ctr.clone(); + let _ = std::thread::spawn(move || { + for _ in 0..16384 { + let mut o1 = p2.get(); + o1.push('a'); + let o2 = p2.get(); + drop(o1); + let mut o2 = unsafe { Pooled::::from_raw(o2.into_raw()).unwrap() }; + o2.push('b'); + ctr2.fetch_add(1, Ordering::Relaxed); + } + }); + } + loop { + std::thread::sleep(Duration::from_millis(100)); + if ctr.load(Ordering::Relaxed) >= 16384 * 64 { + break; + } + } + } +} diff --git a/src/utils/src/reaper.rs b/src/utils/src/reaper.rs new file mode 100644 index 0000000..60624c2 --- /dev/null +++ b/src/utils/src/reaper.rs @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::collections::VecDeque; +use std::sync::Arc; + +use tokio::sync::Notify; +use tokio::task::JoinHandle; +use tokio::time::Instant; + +/// Watches tokio jobs and times them out if they run past a deadline or aborts them all if the reaper is dropped. +pub struct Reaper { + q: Arc<(std::sync::Mutex, Instant)>>, Notify)>, + finisher: JoinHandle<()>, +} + +impl Reaper { + pub fn new(runtime: &tokio::runtime::Handle) -> Self { + let q = Arc::new((std::sync::Mutex::new(VecDeque::with_capacity(16)), Notify::new())); + Self { + q: q.clone(), + finisher: runtime.spawn(async move { + loop { + q.1.notified().await; + loop { + let j = q.0.lock().unwrap().pop_front(); + if let Some(j) = j { + let _ = tokio::time::timeout_at(j.1, j.0).await; + } else { + break; + } + } + } + }), + } + } + + /// Add a job to be executed with timeout at a given instant. + #[inline] + pub fn add(&self, job: JoinHandle<()>, deadline: Instant) { + self.q.0.lock().unwrap().push_back((job, deadline)); + self.q.1.notify_waiters(); + } +} + +impl Drop for Reaper { + #[inline] + fn drop(&mut self) { + self.finisher.abort(); + self.q.0.lock().unwrap().drain(..).for_each(|j| j.0.abort()); + } +} diff --git a/src/utils/src/ringbuffer.rs b/src/utils/src/ringbuffer.rs new file mode 100644 index 0000000..225fbf5 --- /dev/null +++ b/src/utils/src/ringbuffer.rs @@ -0,0 +1,120 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::mem::MaybeUninit; + +/// A FIFO ring buffer. +pub struct RingBuffer { + a: [MaybeUninit; C], + p: usize, +} + +impl RingBuffer { + #[inline] + pub fn new() -> Self { + #[allow(invalid_value)] + let mut tmp: Self = unsafe { MaybeUninit::uninit().assume_init() }; + tmp.p = 0; + tmp + } + + /// Add an element to the buffer, replacing old elements if full. + #[inline] + pub fn add(&mut self, o: T) { + let p = self.p; + if p < C { + unsafe { self.a.get_unchecked_mut(p).write(o) }; + } else { + unsafe { *self.a.get_unchecked_mut(p % C).assume_init_mut() = o }; + } + self.p = p.wrapping_add(1); + } + + /// Clear the buffer and drop all elements. + #[inline] + pub fn clear(&mut self) { + for i in 0..C.min(self.p) { + unsafe { self.a.get_unchecked_mut(i).assume_init_drop() }; + } + self.p = 0; + } + + /// Gets an iterator that dumps the contents of the buffer in FIFO order. + #[inline] + pub fn iter(&self) -> RingBufferIterator<'_, T, C> { + let s = C.min(self.p); + RingBufferIterator { b: self, s, i: self.p.wrapping_sub(s) } + } +} + +impl Default for RingBuffer { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + +impl Drop for RingBuffer { + #[inline(always)] + fn drop(&mut self) { + self.clear(); + } +} + +pub struct RingBufferIterator<'a, T, const C: usize> { + b: &'a RingBuffer, + s: usize, + i: usize, +} + +impl<'a, T, const C: usize> Iterator for RingBufferIterator<'a, T, C> { + type Item = &'a T; + + #[inline] + fn next(&mut self) -> Option { + let s = self.s; + if s > 0 { + let i = self.i; + self.s = s.wrapping_sub(1); + self.i = i.wrapping_add(1); + Some(unsafe { self.b.a.get_unchecked(i % C).assume_init_ref() }) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fifo() { + let mut tmp: RingBuffer = RingBuffer::new(); + let mut tmp2 = Vec::new(); + for i in 0..4 { + tmp.add(i); + tmp2.push(i); + } + for (i, j) in tmp.iter().zip(tmp2.iter()) { + assert_eq!(*i, *j); + } + tmp.clear(); + tmp2.clear(); + for i in 0..23 { + tmp.add(i); + tmp2.push(i); + } + while tmp2.len() > 8 { + tmp2.remove(0); + } + for (i, j) in tmp.iter().zip(tmp2.iter()) { + assert_eq!(*i, *j); + } + } +} diff --git a/src/utils/src/rwu_lock.rs b/src/utils/src/rwu_lock.rs new file mode 100644 index 0000000..17d9488 --- /dev/null +++ b/src/utils/src/rwu_lock.rs @@ -0,0 +1,63 @@ +use std::{ + ops::{Deref, DerefMut}, + sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}, +}; + +/// A wrapper around a `std::sync::RwLock` that allows for atomic upgrades of read locks to write locks. +/// This wrapped struct does not check for lock poisoning. It is assumed that the user will never allow a lock to become poisoned. +/// +/// See the documentation of `std::sync::RwLock` for more details. +pub struct RwuLock(RwLock<(usize, T)>); + +impl RwuLock { + pub fn new(t: T) -> Self { + Self(RwLock::new((0, t))) + } + pub fn read<'a>(&'a self) -> RwuLockReadGuard<'a, T> { + RwuLockReadGuard(self.0.read().unwrap()) + } + pub fn write<'a>(&'a self) -> RwuLockWriteGuard<'a, T> { + let mut w = self.0.write().unwrap(); + w.0 = w.0.wrapping_add(1); + RwuLockWriteGuard(w) + } + + pub fn upgrade<'a, 'b>(&'a self, r: RwuLockReadGuard<'b, T>) -> Option> { + let write_id = r.0 .0; + drop(r); + let mut w = self.0.write().unwrap(); + if w.0 == write_id { + w.0 = w.0.wrapping_add(1); + Some(RwuLockWriteGuard(w)) + } else { + None + } + } +} + +/// RAII structure used to release the shared read access of a lock when dropped. +/// Can be atomically upgraded to a `RwuLockWriteGuard` with `RwuLock::upgrade`. +pub struct RwuLockReadGuard<'a, T>(RwLockReadGuard<'a, (usize, T)>); + +impl<'a, T> Deref for RwuLockReadGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 .1 + } +} +/// RAII structure used to release the exclusive write access of a lock when dropped. +pub struct RwuLockWriteGuard<'a, T>(RwLockWriteGuard<'a, (usize, T)>); + +impl<'a, T> Deref for RwuLockWriteGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 .1 + } +} +impl<'a, T> DerefMut for RwuLockWriteGuard<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 .1 + } +} diff --git a/src/utils/src/str.rs b/src/utils/src/str.rs new file mode 100644 index 0000000..adcc3f8 --- /dev/null +++ b/src/utils/src/str.rs @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use crate::hex::HEX_CHARS; + +/// Escape non-ASCII-printable characters in a string. +/// This also escapes quotes and other sensitive characters that cause issues on terminals. +pub fn escape(b: &[u8]) -> String { + let mut s = String::with_capacity(b.len() * 2); + for b in b.iter() { + let b = *b; + if (43..=126).contains(&b) && b != 92 && b != 96 { + s.push(b as char); + } else { + s.push('\\'); + s.push(HEX_CHARS[(b.wrapping_shr(4) & 0xf) as usize] as char); + s.push(HEX_CHARS[(b & 0xf) as usize] as char); + } + } + s +} + +/// Unescape a string with \XX hexadecimal escapes. +pub fn unescape(s: &str) -> Vec { + let mut b = Vec::with_capacity(s.len()); + let mut s = s.as_bytes(); + while let Some(c) = s.first() { + let c = *c; + if c == b'\\' { + if s.len() < 3 { + break; + } + let mut cc = 0u8; + for c in [s[1], s[2]] { + if (48..=57).contains(&c) { + cc = cc.wrapping_shl(4) | (c - 48); + } else if (65..=70).contains(&c) { + cc = cc.wrapping_shl(4) | (c - 55); + } else if (97..=102).contains(&c) { + cc = cc.wrapping_shl(4) | (c - 87); + } + } + b.push(cc); + s = &s[3..]; + } else { + b.push(c); + s = &s[1..]; + } + } + b +} diff --git a/src/utils/src/sync.rs b/src/utils/src/sync.rs new file mode 100644 index 0000000..58da56e --- /dev/null +++ b/src/utils/src/sync.rs @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + +/// Variant version of lock for RwLock with automatic conversion to a write lock as needed. +pub enum RMaybeWLockGuard<'a, T> { + R(Option>), + W(RwLockWriteGuard<'a, T>), +} + +impl<'a, T> RMaybeWLockGuard<'a, T> { + #[inline(always)] + pub fn new_read(l: &'a RwLock) -> Self { + Self::R(Some(l.read().unwrap())) + } + + /// Get a readable reference to the object. + #[inline] + pub fn read(&self) -> &T { + match self { + Self::R(r) => r.as_ref().unwrap(), + Self::W(w) => w, + } + } + + /// Get a writable reference to the object, converting this to a write lock if needed. + #[inline] + pub fn write(&mut self, l: &'a RwLock) -> &mut T { + match self { + Self::R(r) => { + let _ = r.take(); + *self = Self::W(l.write().unwrap()); + match self { + Self::W(w) => &mut *w, + _ => panic!(), + } + } + Self::W(w) => &mut *w, + } + } +} diff --git a/src/utils/src/varint.rs b/src/utils/src/varint.rs new file mode 100644 index 0000000..d868aeb --- /dev/null +++ b/src/utils/src/varint.rs @@ -0,0 +1,118 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::io::{Read, Write}; + +pub const VARINT_MAX_SIZE_BYTES: usize = 10; + +/// Encode an integer as a varint. +/// +/// WARNING: if the supplied byte slice does not have at least 10 bytes available this may panic. +/// This is checked in debug mode by an assertion. +pub fn encode(b: &mut [u8], mut v: u64) -> usize { + debug_assert!(b.len() >= VARINT_MAX_SIZE_BYTES); + let mut i = 0; + loop { + if v > 0x7f { + b[i] = (v as u8) & 0x7f; + i += 1; + v = v.wrapping_shr(7); + } else { + b[i] = (v as u8) | 0x80; + i += 1; + break; + } + } + i +} + +/// Write a variable length integer, which can consume up to 10 bytes. +#[inline(always)] +pub fn write(w: &mut W, v: u64) -> std::io::Result<()> { + let mut b = [0_u8; VARINT_MAX_SIZE_BYTES]; + let i = encode(&mut b, v); + w.write_all(&b[0..i]) +} + +/// Dencode up to 10 bytes as a varint. +/// +/// if the supplied byte slice does not contain a valid varint encoding this will return None. +/// if the supplied byte slice is shorter than expected this will return None. +pub fn decode(b: &[u8]) -> Option<(u64, usize)> { + let mut v = 0_u64; + let mut pos = 0; + let mut i = 0_usize; + while i < b.len() && i < VARINT_MAX_SIZE_BYTES { + let b = b[i]; + i += 1; + if b <= 0x7f { + v |= (b as u64).wrapping_shl(pos); + pos += 7; + } else { + v |= ((b & 0x7f) as u64).wrapping_shl(pos); + return Some((v, i)); + } + } + None +} + +/// Read a variable length integer, returning the value and the number of bytes written. +pub fn read(r: &mut R) -> std::io::Result<(u64, usize)> { + let mut v = 0_u64; + let mut buf = [0_u8; 1]; + let mut pos = 0; + let mut i = 0_usize; + loop { + r.read_exact(&mut buf)?; + let b = buf[0]; + i += 1; + if b <= 0x7f { + v |= (b as u64).wrapping_shl(pos); + pos += 7; + } else { + v |= ((b & 0x7f) as u64).wrapping_shl(pos); + return Ok((v, i)); + } + } +} + +/// A container for an encoded varint. Use as_ref() to get bytes. +pub struct Encoded([u8; VARINT_MAX_SIZE_BYTES], u8); + +impl Encoded { + #[inline(always)] + pub fn from(v: u64) -> Encoded { + let mut e = Encoded([0_u8; VARINT_MAX_SIZE_BYTES], 0); + e.1 = encode(&mut e.0, v) as u8; + e + } +} + +impl AsRef<[u8]> for Encoded { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + &self.0[0..(self.1 as usize)] + } +} + +#[cfg(test)] +mod tests { + use crate::varint::*; + + #[test] + fn varint() { + let mut t: Vec = Vec::new(); + for i in 0..131072 { + t.clear(); + let ii = (u64::MAX / 131072) * i; + assert!(write(&mut t, ii).is_ok()); + let mut t2 = t.as_slice(); + assert_eq!(read(&mut t2).unwrap().0, ii); + } + } +} diff --git a/src/zssp.rs b/src/zssp.rs new file mode 100644 index 0000000..62f4ef0 --- /dev/null +++ b/src/zssp.rs @@ -0,0 +1,2603 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +* +* (c) ZeroTier, Inc. +* https://www.zerotier.com/ +*/ +// ZSSP: ZeroTier Secure Session Protocol +// FIPS compliant Noise_XK with Jedi powers (Kyber1024) and built-in attack-resistant large payload (fragmentation) support. + +use std::cmp::Reverse; +use std::collections::HashMap; +use std::hash::Hash; +use std::num::NonZeroU32; +use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; +use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; + +use zerotier_crypto::aes::{Aes, AesGcm}; +use zerotier_crypto::constant::{AES_256_KEY_SIZE, AES_GCM_NONCE_SIZE, AES_GCM_TAG_SIZE}; +use zerotier_crypto::hash::SHA512; +use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; +use zerotier_crypto::secret::Secret; +use zerotier_crypto::{random, secure_eq}; + +use pqc_kyber::KYBER_SECRETKEYBYTES; +use zerotier_utils::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; + +use crate::applicationlayer::*; +use crate::error::{FaultType, OpenError, ReceiveError, SendError}; +use crate::frag_cache::UnassociatedFragCache; +use crate::fragged::{Assembled, Fragged}; +use crate::handshake_cache::UnassociatedHandshakeCache; +use crate::log_event::LogEvent; +use crate::proto::*; +use crate::symmetric_state::SymmetricState; + +/// Session context for local application. +/// +/// Each application using ZSSP must create an instance of this to own sessions and +/// defragment incoming packets that are not yet associated with a session. +/// +/// Internally this is just a clonable Arc, so it can be safely shared with multiple threads. +pub struct Context(pub Arc>); +impl Clone for Context { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} +pub struct ContextInner { + unassociated_defrag_cache: Mutex>, + unassociated_handshake_states: UnassociatedHandshakeCache, + /// `session_queue -> state_machine_lock -> state -> session_map` + session_queue: Mutex>, Reverse>>, + session_map: RwLock>, bool)>>, + challenge_counter: AtomicU64, + challenge_antireplay_window: [AtomicU64; CHALLENGE_COUNTER_WINDOW_MAX_OOO], + challenge_salt: [u8; CHALLENGE_SALT_SIZE], +} + +/// Result generated by the context packet receive function, with possible payloads. +pub enum ReceiveResult<'b, Application: ApplicationLayer> { + /// Packet superficially appeared valid but is not associated with a session yet. + /// This can occur because the packet was only a fragment of a larger packet, + /// or if it was a control packet that does not go through full Noise authentication. + Unassociated, + /// Packet was authentic and belongs to this specific session. + Session(Arc>, SessionEvent<'b>), + /// Packet was a part of a handshake, and while it superficially appeared valid the application + /// explicitly rejected it. + /// Relates to callbacks `check_allow_incoming_session` and `check_accept_session`. + Rejected, +} +#[derive(Debug, PartialEq, Eq)] +pub enum SessionEvent<'b> { + /// The received packet was valid, and it contained the necessary keys to fully establish a new + /// session with Alice, the handshake initiator. + /// Contains the current ratchet number for metric purposes. + /// + /// If the session Arc returned is dropped, the session with this peer will be immediately + /// terminated. Save the session Arc to some long lived datastructure to keep it alive. + NewSession(u64), + /// When Alice calls `Context::open`, a session will be created, but Bob will not yet have + /// received this session. They will have to successfully complete a handshake first. + /// Contains the current ratchet number for metric purposes. + /// + /// Alice will receive this return value when the received packet confirms both parties + /// have completed the initial handshake and now have a shared session with each other. + /// If according to the upper protocol, Bob is the first party to send data, it is possible for + /// Alice to start receiving data from Bob before this value is returned. + /// + /// This return value can only occur once per session, only for session objects that were + /// created with `Context::open`. + Established(u64), + /// Bob explicitly refused to establish a session with Alice, and sent us an error code. + /// The application should immediately drop this session as Bob will not allow us to connect. + /// + /// This return value cannot occur after a session is fully established. + Rejected, + /// The received packet was valid and a data payload was decoded and authenticated. + Data(&'b mut [u8]), + /// The received packet completed a rekey event and a new ratchet key was derived. + /// Contains the current ratchet number for metric purposes. + Ratchet(u64), + /// The received packet was some authentic protocol control packet. No action needs to be taken. + Control, +} +#[derive(Debug, PartialEq, Eq)] +pub enum IncomingSessionAction { + Allow, + Challenge, + Drop, +} +pub enum AcceptSessionAction { + Accept(Application::Data), + SendReject, + SilentlyReject, +} +/// ZeroTier Secure Session Protocol (ZSSP) Session +/// +/// A FIPS/NIST compliant variant of Noise_XK with hybrid Kyber1024 PQ data forward secrecy. +pub struct Session { + /// An arbitrary application defined object associated with each session. + pub application_data: Application::Data, + /// The receive context associated with this session, + /// only this context can receive messages from the remote peer. + context: Weak>, + /// Handle into the session queue for changing the update timer. + queue_idx: BinaryHeapIndex, + + remote_s_public_key: P384PublicKey, + send_counter: AtomicU64, + /// This bool signals to all threads to stop incrementing the counter and instead error out. + session_has_expired: AtomicBool, + /// The following is a ring buffer of previously seen counter values, where we use the counter's + /// value as the index of the head of the ring buffer. + counter_antireplay_window: [AtomicU64; COUNTER_WINDOW_MAX_OOO], + /// Enforces atomicity of state machine transitions. + /// There is a standard locking sequence, + /// it goes `session_queue -> state_machine_lock -> state -> session_map`. + /// Any lock can be skipped but they must be locked in that order. + state_machine_lock: Mutex<()>, + state: RwLock>, + defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], + header_send_cipher: Aes, + header_receive_cipher: Aes, + kex_send_cipher: Mutex>>, + kex_receive_cipher: Mutex>>, + /// Pre-computed rekeying values. + noise_kk_ss: Secret, + noise_kk_local_init_h: [u8; NOISE_HASHLEN], + noise_kk_remote_init_h: [u8; NOISE_HASHLEN], + was_bob: bool, +} +/// `AesGcm` is not threadsafe, but it is threadsafe when inside a `Mutex`. +unsafe impl Send for Session {} +unsafe impl Sync for Session {} + +/// Session state may only be mutated during atomic transitions of the offer state machine. +struct SessionMutableState { + ratchet_number: u64, + ratchet_fingerprint: Secret, + ratchet_key: Secret, + /// For OOO rekeying reliability we allow our version of Noise CipherState to hold the last two + /// session keys, instead of just the most recent one. + cipher_states: [Option; 2], + /// This is the index of `noise_cipher_state` that contains the most recent key. + /// It will be attached to fragment headers to help with OOO transport. + current_key: usize, + /// This defines the exact state of the offer state machine we are in. + outgoing_offer: OfferStateMachine, +} +/// These offer enums form a state machine. +/// Documented below are the only legal transitions for this state machine. +/// A session is initialized with an `outgoing_offer` of either NoiseXKPattern1 or Normal. +enum OfferStateMachine { + Normal { + timeout: i64, + }, // -> NoiseKKPattern1, NoiseKKPattern2 + /// This state uses a lot of memory so we put it on the heap. + NoiseXKPattern1or3(Box>), // -> Normal + NoiseKKPattern1 { + next_retry_time: AtomicI64, + timeout: i64, + new_key_id: NonZeroU32, + noise_e_secret: P384KeyPair, + noise_message: [u8; NoiseKKPattern1or2::SIZE], + noise_ck: SymmetricState, + noise_h_pskep: [u8; NOISE_HASHLEN], + }, // -> NoiseKKPattern2, KeyConfirm + NoiseKKPattern2 { + next_retry_time: AtomicI64, + timeout: i64, + noise_message: [u8; NoiseKKPattern1or2::SIZE], + kex_send_key: Secret, + new_ratchet_number: u64, + new_ratchet_fingerprint: Secret, + new_ratchet_key: Secret, + }, // -> Normal + KeyConfirm { + next_retry_time: AtomicI64, + timeout: i64, + }, // -> Normal +} +pub(crate) struct NoiseXKBobHandshakeState { + remote_key_id: NonZeroU32, + local_key_id: NonZeroU32, + header_receive_key: Secret, + header_send_key: Secret, + ratchet_number: u64, + ratchet_fingerprint: Option<[u8; RATCHET_FINGERPRINT_SIZE]>, + noise_h_ee1peekem1pskp: [u8; NOISE_HASHLEN], + noise_e_secret: P384KeyPair, + noise_ck_eseeekem1psk: SymmetricState, + noise_k_eseeekem1psk: Secret, + noise_pattern3_defrag: Mutex>, +} +struct NoiseXKAliceHandshake { + next_retry_time: AtomicI64, + timeout: i64, + /// A secure random number put in the header of Alice's fragments to identify them. + /// If a DDOS attacker could guess this they could block Alice starting the handshake. + local_key_id: NonZeroU32, + alice_identity_blob: Application::LocalIdentityBlob, + offer: NoiseXKAliceHandshakeState, +} +enum NoiseXKAliceHandshakeState { + NoiseXKPattern1 { + noise_h_ee1p: [u8; NOISE_HASHLEN], + noise_e_secret: P384KeyPair, + noise_e1_secret: Secret, + noise_ck_es: SymmetricState, + /// ZSSP assumes an unreliable, out-of-order physical transport environment, so for that + /// reason we have to resend key offers. + noise_message: [u8; NoiseXKPattern1::SIZE], + message_id: u64, + }, + NoiseXKPattern3 { + noise_message: [u8; NoiseXKPattern3::MAX_SIZE], + noise_message_len: usize, + new_ratchet_number: u64, + new_ratchet_fingerprint: Secret, + new_ratchet_key: Secret, + }, +} + +struct SessionKey { + remote_key_id: NonZeroU32, + local_key_id: NonZeroU32, + /// Pool of reusable sending ciphers. + receive_cipher_pool: [Mutex>; 8], + /// Pool of reusable receiving ciphers. + send_cipher_pool: [Mutex>; 8], + /// Rekey at or after this counter. + rekey_at_counter: u64, + /// Hard error when this counter value is reached or exceeded. + expire_at_counter: u64, +} + +macro_rules! byzantine_fault { + ($name:expr, $is_natural:ident) => { + ReceiveError::ByzantineFault { + file: file!(), + line: line!(), + error: $name, + is_naturally_occurring: $is_natural, + } + }; +} + +impl Context { + /// Create a new session context. + pub fn new() -> Self { + debug_assert!(Application::REKEY_AFTER_TIME_MAX_JITTER_MS > 0, "Invalid protocol constant"); + Self(Arc::new(ContextInner { + unassociated_defrag_cache: Mutex::new(UnassociatedFragCache::new()), + unassociated_handshake_states: UnassociatedHandshakeCache::new(), + session_map: RwLock::new(HashMap::new()), + session_queue: Mutex::new(IndexedBinaryHeap::new()), + challenge_counter: AtomicU64::new(INIT_COUNTER), + challenge_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), + challenge_salt: random::get_bytes_secure(), + })) + } + + /// Perform periodic background service and cleanup tasks. + /// + /// This returns the number of milliseconds until it should be called again. The caller should + /// try to satisfy this but small variations in timing of up to +/- a second or two are not + /// a problem. + /// + /// * `send_to` - Function to get a sender and an MTU to send something over an active session + /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced + /// with remote peers (although both of these properties would help reliability slightly). + /// Used to determine if any current handshakes should be resent or timed-out, or if a session + /// should rekey. + #[inline] + pub fn service bool>( + &self, + app: &Application, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + current_time: i64, + ) -> i64 { + let retry_next = current_time.saturating_add(Application::RETRY_INTERVAL_MS); + let mut next_service_time = 2 * Application::RETRY_INTERVAL_MS; + + let mut session_queue = self.0.session_queue.lock().unwrap(); + // This update system takes heavy advantage of the fact that sessions only need to be updated + // either roughly every second or roughly every hour. That big gap allows for minor optimizations. + // If the gap changes (unlikely) this code may need to be rewritten. + while let Some((session, timer, queue_idx)) = session_queue.peek() { + if timer.0 >= current_time { + next_service_time = next_service_time.min(timer.0 - current_time); + break; + } + let session = match session.upgrade() { + Some(s) => s, + _ => { + session_queue.remove(queue_idx); + continue; + } + }; + let state = session.state.read().unwrap(); + use OfferStateMachine::*; + let next_timer = match &state.outgoing_offer { + Normal { timeout, .. } => { + if *timeout <= current_time { + drop(state); + if let Some((send, _)) = send_to(&session) { + let result = initiate_rekey(&self.0, &session, send, current_time); + if result.is_ok() { + app.event_log(LogEvent::ServiceKKStart(&session), current_time); + } + result.unwrap_or(retry_next) + } else { + retry_next + } + } else { + *timeout + } + } + // If there's an outstanding attempt to open a session, retransmit this + // periodically in case the initial packet doesn't make it. + NoiseXKPattern1or3(handshake_state) => { + if let Some(ts) = process_timer(&handshake_state.next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { + ts + } else { + // We have to eventually time out NoiseXKPattern3 because of unreliable network conditions. + if handshake_state.timeout <= current_time { + drop(state); + let _kex_lock = session.state_machine_lock.lock().unwrap(); + let mut state = session.state.write().unwrap(); + let ratchet_fingerprint = state.ratchet_fingerprint.clone(); + // Since we dropped the lock we must re-check if we are in the correct state. + if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { + if handshake_state.timeout <= current_time { + app.event_log(LogEvent::ServiceXKTimeout(&session), current_time); + if !handshake_state.reinitialize( + &session, + &ratchet_fingerprint, + &mut self.0.session_map.write().unwrap(), + current_time, + ) { + session.expire_inner(&self.0, &mut session_queue); + } + } + } + } else if let Some((mut send, mut mtu)) = send_to(&session) { + mtu = mtu.max(MIN_TRANSPORT_MTU); + match &handshake_state.offer { + NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, message_id, .. } => { + app.event_log(LogEvent::ServiceXK1Resend(&session), current_time); + // We are in state NoiseXKPattern1 so resend noise_pattern1. + send_with_fragmentation( + &mut send, + mtu, + &mut noise_message.clone(), + PACKET_TYPE_NOISE_XK_PATTERN_1, + None, + *message_id, + None, + ); + } + NoiseXKAliceHandshakeState::NoiseXKPattern3 { noise_message, noise_message_len, .. } => { + app.event_log(LogEvent::ServiceXK3Resend(&session), current_time); + send_with_fragmentation( + &mut send, + mtu, + &mut noise_message.clone()[..*noise_message_len], + PACKET_TYPE_NOISE_XK_PATTERN_3, + state.cipher_states[0].as_ref().map(|k| k.remote_key_id), + 0, + Some(&session.header_send_cipher), + ); + } + } + } + retry_next + } + } + NoiseKKPattern1 { next_retry_time, timeout, noise_message, .. } | NoiseKKPattern2 { next_retry_time, timeout, noise_message, .. } => { + if let Some(ts) = process_timer(&next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { + ts + } else { + if *timeout <= current_time { + app.event_log(LogEvent::ServiceKKTimeout(&session), current_time); + next_retry_time.store(i64::MAX, Ordering::Relaxed); + session.expire_inner(&self.0, &mut session_queue); + } else { + let packet_type = if let NoiseKKPattern1 { .. } = &state.outgoing_offer { + app.event_log(LogEvent::ServiceKK1Resend(&session), current_time); + PACKET_TYPE_NOISE_KK_PATTERN_1 + } else { + app.event_log(LogEvent::ServiceKK2Resend(&session), current_time); + PACKET_TYPE_NOISE_KK_PATTERN_2 + }; + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&state, send, packet_type, noise_message); + } + } + retry_next + } + } + KeyConfirm { next_retry_time, timeout, .. } => { + if let Some(ts) = process_timer(&next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { + ts + } else { + if *timeout <= current_time { + app.event_log(LogEvent::ServiceKeyConfirmTimeout(&session), current_time); + next_retry_time.store(i64::MAX, Ordering::Relaxed); + session.expire_inner(&self.0, &mut session_queue); + } else { + app.event_log(LogEvent::ServiceKeyConfirmResend(&session), current_time); + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&state, send, PACKET_TYPE_KEY_CONFIRM, &[]); + } + } + retry_next + } + } + }; + session_queue.change_priority(queue_idx, Reverse(next_timer)); + } + drop(session_queue); + + self.0 + .unassociated_defrag_cache + .lock() + .unwrap() + .check_for_expiry(Application::INITIAL_OFFER_TIMEOUT_MS, current_time); + self.0.unassociated_handshake_states.service(current_time); + + next_service_time + } + + /// Create a new session and send initial packet(s) to other side. + /// + /// This will return SendError::DataTooLarge if the combined size of the metadata and the local + /// static public blob (as retrieved from the application layer) exceed MAX_INIT_PAYLOAD_SIZE. + /// + /// * `app` - Application layer instance + /// * `send` - Function to be called to send one or more initial packets to the remote being + /// contacted + /// * `mtu` - MTU for initial packets + /// * `remote_s_public_key` - Remote side's static public NIST P-384 key + /// * `application_data` - Arbitrary data meaningful to the application to include with session + /// object + /// * `ratchet_state` - The last saved and confirmed ratchet state associated with this remote + /// peer, or None if we do not have one. + /// * `local_identity_blob` - Payload to be sent to Bob that contains the information necessary + /// for the upper protocol to authenticate and approve of Alice's identity. + /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced + /// with the remote peer. Used to determine when this offer should be resent. + #[inline] + pub fn open( + &self, + app: &Application, + mut send: impl FnMut(&mut [u8]) -> bool, + mut mtu: usize, + remote_s_public_key: P384PublicKey, + application_data: Application::Data, + ratchet_state: Option<(u64, [u8; RATCHET_FINGERPRINT_SIZE], [u8; RATCHET_KEY_SIZE])>, + local_identity_blob: Application::LocalIdentityBlob, + current_time: i64, + ) -> Result>, OpenError> { + mtu = mtu.max(MIN_TRANSPORT_MTU); + if local_identity_blob.as_ref().len() > MAX_IDENTITY_BLOB_SIZE { + return Err(OpenError::DataTooLarge); + } + let (ratchet_number, mut ratchet_fingerprint, mut ratchet_key) = + ratchet_state.unwrap_or((0, [0; RATCHET_FINGERPRINT_SIZE], [0; RATCHET_KEY_SIZE])); + let sha512 = &mut SHA512::new(); + + let alice_s_keypair = app.local_s_keypair(); + let noise_kk_ss = alice_s_keypair.agree(&remote_s_public_key).ok_or(OpenError::InvalidPublicKey)?; + let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, alice_s_keypair.public_key_bytes()); + let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, alice_s_keypair.public_key_bytes()); + + let mut session_queue = self.0.session_queue.lock().unwrap(); + let mut session_map = self.0.session_map.write().unwrap(); + let local_key_id = generate_key_id(&session_map); + // Begin Noise XKhfs+psk2. + let (offer, a2b_header_key, b2a_header_key) = + NoiseXKAliceHandshake::::initialize(local_key_id, &remote_s_public_key, &ratchet_fingerprint)?; + let handshake_state = Box::new(NoiseXKAliceHandshake { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), + local_key_id, + alice_identity_blob: local_identity_blob, + offer, + }); + if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, message_id, .. } = &handshake_state.offer { + send_with_fragmentation( + &mut send, + mtu, + &mut noise_message.clone(), + PACKET_TYPE_NOISE_XK_PATTERN_1, + None, + *message_id, + None, + ); + } + + let queue_idx = session_queue.reserve_index(); + let session = Arc::new(Session { + context: Arc::downgrade(&self.0), + queue_idx, + application_data, + remote_s_public_key, + send_counter: AtomicU64::new(INIT_COUNTER), + session_has_expired: AtomicBool::new(false), + counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), + state_machine_lock: Mutex::new(()), + state: RwLock::new(SessionMutableState { + ratchet_number, + ratchet_fingerprint: Secret::from_bytes_then_nuke(&mut ratchet_fingerprint), + ratchet_key: Secret::from_bytes_then_nuke(&mut ratchet_key), + cipher_states: [None, None], + // Points at 1 until the first key is confirmed. + current_key: 1, + outgoing_offer: OfferStateMachine::NoiseXKPattern1or3(handshake_state), + }), + header_receive_cipher: Aes::new(b2a_header_key.as_bytes()), + header_send_cipher: Aes::new(a2b_header_key.as_bytes()), + kex_receive_cipher: Mutex::new(None), + kex_send_cipher: Mutex::new(None), + noise_kk_ss, + noise_kk_local_init_h, + noise_kk_remote_init_h, + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + was_bob: false, + }); + session_map.insert(local_key_id, (Arc::downgrade(&session), false)); + session_queue.push_reserved( + queue_idx, + Arc::downgrade(&session), + Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + ); + + return Ok(session); + } + + /// Receive, authenticate, decrypt, and process a physical wire packet. + /// + /// The check_allow_incoming_session function is called when an initial Noise_XK init message is + /// received. This is before anything is known about the caller. A return value of true proceeds + /// with negotiation. False drops the packet and ignores the inbound attempt. + /// + /// The check_accept_session function is called at the end of negotiation for an incoming + /// session with the caller's static public blob. It must return the P-384 static public key + /// extracted from the supplied blob and application data. A return of Some() accepts the + /// session and will always result in a new session ReceiveResult being returned. + /// + /// * `app` - Interface to application using ZSSP + /// * `check_allow_incoming_session` - Function to call to check whether an unidentified new + /// session should be accepted + /// * `check_accept_session` - Function to accept sessions after final negotiation. + /// The second argument is the identity blob that the remote peer sent us. The application + /// must verify this identity is associated with the remote peer's static key. + /// If the third argument is `Some`, it is a ratchet fingerprint. The application must verify + /// that it is associated with the remote peer's static key and identity. + /// If the third argument is `None` it means the remote peer connected to us with the zero + /// ratchet. The application should decide whether or not this remote peer is allowed to + /// connect with the zero ratchet. + /// * `send_unassociated_reply` - Function to send reply packets directly when no session exists + /// * `send_unassociated_mtu` - MTU for unassociated replies + /// * `send_to` - Function to get senders for existing sessions, permitting MTU and path lookup + /// * `remote_address` - Whatever the remote address is, as long as you can Hash it + /// * `data_buf` - Buffer to receive decrypted and authenticated object data (an error is + /// returned if too small) + /// * `incoming_physical_packet_buf` - Buffer containing incoming wire packet + /// (receive() takes ownership) + /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced + /// with the remote peer. Used to check the state of local offers we may currently have or want + /// to put in-flight. + #[inline] + pub fn receive<'a, SendFn: FnMut(&mut [u8]) -> bool>( + &self, + app: &Application, + check_allow_incoming_session: impl FnOnce() -> IncomingSessionAction, + check_accept_session: impl FnOnce(&P384PublicKey, &[u8], Option<&[u8; RATCHET_FINGERPRINT_SIZE]>) -> AcceptSessionAction, + mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, + mut send_unassociated_mtu: usize, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + remote_address: &impl Hash, + data_buf: &'a mut [u8], + mut incoming_physical_packet_buf: Application::IncomingPacketBuffer, + current_time: i64, + ) -> Result, ReceiveError> { + send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); + let incoming_physical_packet: &mut [u8] = incoming_physical_packet_buf.as_mut(); + let incoming_physical_packet_len = incoming_physical_packet.len(); + if incoming_physical_packet_len < MIN_PACKET_SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + + // The first section parses the header and looks up relevant state information. If it's a DATA + // or NOP packet it gets handled right here, otherwise we pull out a set of variables and + // continue to the logic that handles KEX and session control packets. + + let mut assembled_packet = Assembled::new(); // needs to outlive the block below + let mut incoming = None; + let (session, packet_type, fragments) = { + let mut local_key_id = [0u8; SESSION_ID_SIZE]; + local_key_id.copy_from_slice(&incoming_physical_packet[0..SESSION_ID_SIZE]); + // `from_ne_bytes` because this id was generated locally. + if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(local_key_id)) { + let session_map = self.0.session_map.read().unwrap(); + if let Some((Some(session), key_index)) = session_map.get(&local_key_id).map(|r| (r.0.upgrade(), r.1 as usize)) { + drop(session_map); + session + .header_receive_cipher + .crypt_block_in_place(&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]); + let (fragment_count, fragment_no, packet_type, incoming_counter, header_nonce) = parse_packet_header(&incoming_physical_packet); + // Handle replay protection. + if PACKET_TYPE_RANGE_TRANSPORT.contains(&packet_type) { + // For DOS resistant reply-protection we need to check that the given counter is + // in the window of valid counters immediately. + // But for packets larger than 1 fragment we can't actually record the + // counter as received until we've authenticated the packet. + // So we check the counter window twice, and only update it the second time + // after the packet has been authenticated. + if !session.check_receive_window(incoming_counter) { + // This can occur naturally if packets arrive way out of order, or + // if they are duplicates. + // This can also be naturally triggered if Bob has just successfully + // received the first session key and is reject all of Alice's resends. + // This can also occur if a session was manually expired, but not + // dropped, and the remote party is still sending us data. + return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); + } + if packet_type != PACKET_TYPE_DATA { + // This is a control packet. + if fragment_count != 1 || fragment_no > 0 { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + return receive_control_fragment( + self, + session, + app, + send_to, + packet_type, + incoming_counter, + incoming_physical_packet_buf.as_mut(), + current_time, + ); + } + } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_2 { + // We need to reject fragments marked with this type if they are sent out + // of sequence, since an attacker is able to replay them. + match &session.state.read().unwrap().outgoing_offer { + OfferStateMachine::NoiseXKPattern1or3(handshake_state) => match &handshake_state.offer { + NoiseXKAliceHandshakeState::NoiseXKPattern1 { .. } => { + if incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + } + // This error can occur naturally if Bob's initial reply to Alice had a + // resend that was delayed massively and arrived out of order. + NoiseXKAliceHandshakeState::NoiseXKPattern3 { .. } => return Err(byzantine_fault!(FaultType::OutOfSequence, true)), + }, + _ => return Err(byzantine_fault!(FaultType::OutOfSequence, false)), + }; + } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_3 { + // This can be triggered if Bob successfully received a session key and + // needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3. + return Err(byzantine_fault!(FaultType::OutOfSequence, true)); + } else { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + // Handle defragmentation. + let fragments = if fragment_count > 1 { + let idx = incoming_counter as usize % session.defrag.len(); + session.defrag[idx].lock().unwrap().assemble( + header_nonce, + incoming_physical_packet_buf, + fragment_no, + fragment_count, + &mut assembled_packet, + ); + if assembled_packet.is_empty() { + // We have not yet authenticated the sender so we do not report + // receiving a packet from them. + return Ok(ReceiveResult::Unassociated); + } else { + assembled_packet.as_ref() + } + } else { + std::array::from_ref(&incoming_physical_packet_buf) + }; + // Handle DATA in the fastest path when we have a session. + if packet_type == PACKET_TYPE_DATA { + let state = session.state.read().unwrap(); + // The error here can occur because the other party is using a brand new + // session key that we have not received yet. + let key = state.cipher_states[key_index] + .as_ref() + .ok_or(byzantine_fault!(FaultType::OutOfSequence, true))?; + let mut c = key.get_receive_cipher(incoming_counter); + c.reset_init_gcm(&create_message_nonce(packet_type, incoming_counter)); + + let mut data_len = 0; + + // Decrypt fragments 0..N-1 where N is the number of fragments. + for f in fragments[..(fragments.len() - 1)].iter() { + let f: &[u8] = f.as_ref(); + debug_assert!(f.len() >= HEADER_SIZE); + let current_frag_data_start = data_len; + data_len += f.len() - HEADER_SIZE; + if data_len > data_buf.len() { + return Err(ReceiveError::DataBufferTooSmall); + } + c.crypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); + } + + // Decrypt final fragment (or only fragment if not fragmented) + let current_frag_data_start = data_len; + let last_fragment = fragments.last().unwrap().as_ref(); + if last_fragment.len() < (HEADER_SIZE + AES_GCM_TAG_SIZE) { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE); + if data_len > data_buf.len() { + return Err(ReceiveError::DataBufferTooSmall); + } + let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE; + c.crypt(&last_fragment[HEADER_SIZE..payload_end], &mut data_buf[current_frag_data_start..data_len]); + + let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..]); + drop(c); + drop(state); + + if !aead_authentication_ok { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + if !session.update_receive_window(incoming_counter) { + // This can be naturally triggered because Bob has just + // successfully received a session key and needs to reject + // all of Alice's resends. + // This can also occur naturally if some part of the outer + // system is duplicating the packets being sent to us. + // We are safely deduplicating them here. + return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); + } + // Packet fully authenticated + return Ok(ReceiveResult::Session(session, SessionEvent::Data(&mut data_buf[..data_len]))); + } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_2 { + (Some(session), packet_type, fragments) + } else { + unreachable!() + } + } else { + drop(session_map); + // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 + incoming = self.0.unassociated_handshake_states.get(local_key_id); + if let Some(incoming) = incoming.as_ref() { + Aes::::new(incoming.header_receive_key.as_bytes()) + .crypt_block_in_place(&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]); + let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(&incoming_physical_packet); + app.event_log( + LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), + current_time, + ); + if packet_type != PACKET_TYPE_NOISE_XK_PATTERN_3 { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + let fragments = if fragment_count > 1 { + incoming.noise_pattern3_defrag.lock().unwrap().assemble( + header_nonce, + incoming_physical_packet_buf, + fragment_no, + fragment_count, + &mut assembled_packet, + ); + if !assembled_packet.is_empty() { + assembled_packet.as_ref() + } else { + return Ok(ReceiveResult::Unassociated); + } + } else { + std::array::from_ref(&incoming_physical_packet_buf) + }; + // We must guarantee that this incoming handshake is processed once and only + // once. This prevents catastrophic nonce reuse caused by multithreading. + if self.0.unassociated_handshake_states.remove(local_key_id) { + (None, PACKET_TYPE_NOISE_XK_PATTERN_3, fragments) + } else { + return Ok(ReceiveResult::Unassociated); + } + } else { + // This can occur naturally because either Bob's incoming_sessions cache got + // full so Alice's incoming session was dropped, or the session this packet + // was for was dropped by the application. + return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); + } + } + } else { + let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(&incoming_physical_packet); + app.event_log( + LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), + current_time, + ); + if packet_type != PACKET_TYPE_NOISE_XK_PATTERN_1 && packet_type != PACKET_TYPE_BOB_DOS_CHALLENGE { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + let fragments = if fragment_count > 1 { + self.0.unassociated_defrag_cache.lock().unwrap().assemble( + header_nonce, + remote_address, + incoming_physical_packet_len - HEADER_SIZE, + incoming_physical_packet_buf, + fragment_no, + fragment_count, + Application::RETRY_INTERVAL_MS, + current_time, + &mut assembled_packet, + ); + if !assembled_packet.is_empty() { + assembled_packet.as_ref() + } else { + return Ok(ReceiveResult::Unassociated); + } + } else { + std::array::from_ref(&incoming_physical_packet_buf) + }; + (None, packet_type, fragments) + } + }; + + debug_assert!(fragments.len() >= 1); + debug_assert!(incoming.is_none() || session.is_none()); + + let mut pkt_assembly_buffer = [0u8; MAX_NOISE_HANDSHAKE_SIZE]; + let message_size = assemble_fragments_into::(fragments, &mut pkt_assembly_buffer)?; + if message_size < MIN_PACKET_SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + let message = &mut pkt_assembly_buffer[..message_size]; + + use OfferStateMachine::*; + match packet_type { + PACKET_TYPE_NOISE_XK_PATTERN_1 => { + app.event_log(LogEvent::ReceiveUncheckedXK1, current_time); + // Alice (remote) --> Bob (local) + // -> e, es, e1 + if session.is_some() || incoming.is_some() { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + if message.len() != NoiseXKPattern1::SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); + // The message id must be the first 8 bytes of the gcm tag. + // This forces the message id to be authenticated along with the entire message. + if noise_pattern1.header[8..] != noise_pattern1.p_gcm_tag[8..] { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + if let Some(remote_key_id) = NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.alice_key_id)) { + let sha512 = &mut SHA512::new(); + // Let application filter incoming connection attempts by whatever criteria it wants. + // This should ideally prevent ZSSP from wasting time on DDOS attacks. + match check_allow_incoming_session() { + IncomingSessionAction::Allow => {} + IncomingSessionAction::Challenge => { + let mut counter = 0u64.to_ne_bytes(); + counter.copy_from_slice(&noise_pattern1.challenge_counter); + let counter = u64::from_be_bytes(counter); + + sha512.reset(); + let mut hasher = SHAHasher(sha512); + hasher.0.update(&noise_pattern1.challenge_counter); + remote_address.hash(&mut hasher); + hasher.0.update(&self.0.challenge_salt); + let is_valid = self.check_challenge_window(counter) + && secure_eq(&hasher.0.finish()[..CHALLENGE_MAC_SIZE], &noise_pattern1.challenge_mac) + && verify_pow::(&mut hasher.0, &message) + && self.update_challenge_window(counter); + app.event_log(LogEvent::ReceiveCheckXK1Challenge(is_valid), current_time); + if !is_valid { + // Alice failed the challenge so issue them a new challenge. + let mut challenge_buffer = [0u8; BobDOSChallenge::SIZE]; + let challenge: &mut BobDOSChallenge = byte_array_as_proto_buffer_mut(&mut challenge_buffer); + challenge.alice_key_id = remote_key_id.get().to_ne_bytes(); + // We attach a monotonically increasing counter value to the challenge + // so it cannot be replayed. + let counter = self.0.challenge_counter.fetch_add(1, Ordering::Relaxed); + challenge.challenge_counter = counter.to_be_bytes(); + + hasher.0.reset(); + hasher.0.update(&counter.to_be_bytes()); + remote_address.hash(&mut hasher); + hasher.0.update(&self.0.challenge_salt); + challenge.challenge_mac.copy_from_slice(&hasher.0.finish()[..CHALLENGE_MAC_SIZE]); + challenge.prior_challenge_pow = noise_pattern1.challenge_pow; + // We haven't decrypted any of Alice's packet so we don't know the + // header protection cipher. + // For DOS resistance Alice will not accept unencrypted headers directly + // into their session defrag buffer, so we have to send them this reply + // through their incoming sessions cache. + send_with_fragmentation( + &mut send_unassociated_reply, + send_unassociated_mtu, + &mut challenge_buffer, + PACKET_TYPE_BOB_DOS_CHALLENGE, + None, + random::next_u64_secure(), + None, + ); + return Ok(ReceiveResult::Unassociated); + } + // Alice succeeded at the challenge so continue to decryption. + } + IncomingSessionAction::Drop => return Ok(ReceiveResult::Rejected), + } + + let local_s_keypair = app.local_s_keypair(); + // Noise process handshake prologue. + let noise_h = mix_hash( + sha512, + &INITIAL_H, + &message[NoiseXKPattern1::PROLOGUE_START..NoiseXKPattern1::PROLOGUE_END], + ); + let noise_h = mix_hash(sha512, &noise_h, local_s_keypair.public_key_bytes()); + // Noise process pattern1 e token. + let mut noise_ck = SymmetricState::new(INITIAL_H); + let (noise_e_pattern1, noise_es) = from_bytes_agreement(&noise_pattern1.noise_e, &local_s_keypair) + .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; + let noise_h_e = mix_hash(sha512, &noise_h, &noise_pattern1.noise_e); + noise_ck.mix_key(&noise_pattern1.noise_e); + // Noise process pattern1 es token. + let noise_k_es = noise_ck.mix_key_initialize_key(noise_es.as_bytes()); + drop(noise_es); + // Noise process pattern1 e1 token. + let (is_auth, noise_h_ee1) = decrypt_and_hash( + sha512, + &noise_k_es, + &noise_h_e, + packet_type, + 0, + &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], + ); + if !is_auth { + // This could occur naturally if Alice's ApplicationLayer is dynamically + // changing their mtu, which in bad network conditions could clobber their + // resent KEX packet. + // Or maybe Alice randomly generated the same temporary id twice in a row. + // Since these situations are super unlikely to occur we still mark this error + // as unnatural. + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + // Noise process pattern1 payload. + let (is_auth, noise_h_ee1p) = decrypt_and_hash( + sha512, + &noise_k_es, + &noise_h_ee1, + packet_type, + 1, + &mut message[NoiseXKPattern1::P_ENC_START..NoiseXKPattern1::P_AUTH_END], + ); + drop(noise_k_es); + if !is_auth { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(LABEL_HEADER_KEY, &noise_h_ee1p); + // Get ratchet key. + let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(message); + use crate::GetRatchetAction::*; + let (sent_zero, ratchet_number, ratchet_key) = if noise_pattern1.ratchet_fingerprint == [0u8; RATCHET_FINGERPRINT_SIZE] { + if app.allow_zero_ratchet(current_time) { + (true, 0, [0u8; RATCHET_KEY_SIZE]) + } else { + return Ok(ReceiveResult::Rejected); + } + } else { + match app.lookup_ratchet(&noise_pattern1.ratchet_fingerprint, current_time) { + Ok(Found(ratchet_number, ratchet_key)) => (false, ratchet_number, ratchet_key), + Ok(Downgrade) => (false, 0, [0u8; RATCHET_KEY_SIZE]), + Ok(Ignore) => return Ok(ReceiveResult::Rejected), + Err(()) => return Err(ReceiveError::RatchetIoError), + } + }; + + // Start of Noise XKhfs+psk2 pattern2. + let mut message2 = [0u8; NoiseXKPattern2::SIZE]; + let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); + // Noise process pattern2 e token. + let noise_e_pattern2_secret = P384KeyPair::generate(); + noise_pattern2.noise_e = noise_e_pattern2_secret.public_key_bytes().clone(); + let noise_h_ee1pe = mix_hash(sha512, &noise_h_ee1p, &noise_pattern2.noise_e); + noise_ck.mix_key(&noise_pattern2.noise_e); + // Noise process pattern2 ee token. + let noise_ee = noise_e_pattern2_secret + .agree(&noise_e_pattern1) + .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; + let noise_k_esee = noise_ck.mix_key_initialize_key(noise_ee.as_bytes()); + drop(noise_ee); + // Noise process pattern2 ekem1 token. + let (noise_ekem1, noise_ekem1_secret) = pqc_kyber::encapsulate(&noise_pattern1.noise_e1, &mut random::SecureRandom::default()) + .map_err(|_| byzantine_fault!(FaultType::FailedAuthentication, false)) + .map(|(ct, ekem1)| (ct, Secret(ekem1)))?; + // Alice fully authenticated. + noise_pattern2.noise_ekem1 = noise_ekem1; + let noise_h_ee1peekem1 = encrypt_and_hash( + sha512, + &noise_k_esee, + &noise_h_ee1pe, + PACKET_TYPE_NOISE_XK_PATTERN_2, + 0, + &mut message2[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], + ); + drop(noise_k_esee); + noise_ck.mix_key(noise_ekem1_secret.as_bytes()); + drop(noise_ekem1_secret); + // Noise process pattern2 psk token. + let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(&ratchet_key); + let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); + // Noise process pattern2 payload. + // We try to prevent the id we generate from colliding with another session but + // because we might have handshakes in flight it's impossible to 100% prevent. + // In those exceedingly rare cases we have to drop Alice's session and start over. + let local_key_id = generate_key_id(&self.0.session_map.read().unwrap()); + let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); + noise_pattern2.bob_key_id = local_key_id.get().to_ne_bytes(); + + let noise_h_ee1peekem1pskp = encrypt_and_hash( + sha512, + &noise_k_eseeekem1psk, + &noise_h_ee1peekem1psk, + PACKET_TYPE_NOISE_XK_PATTERN_2, + 0, + &mut message2[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END], + ); + + app.event_log(LogEvent::ReceiveValidXK1, current_time); + let handshake = Arc::new(NoiseXKBobHandshakeState { + local_key_id, + remote_key_id, + ratchet_number, + ratchet_fingerprint: (!sent_zero).then(|| noise_pattern1.ratchet_fingerprint), + noise_h_ee1peekem1pskp, + noise_ck_eseeekem1psk: noise_ck.clone(), + noise_k_eseeekem1psk: noise_k_eseeekem1psk.clone(), + noise_e_secret: noise_e_pattern2_secret, + header_receive_key: header_a2b_key.clone(), + header_send_key: header_b2a_key.clone(), + noise_pattern3_defrag: Mutex::new(Fragged::new()), + }); + self.0.unassociated_handshake_states.insert(local_key_id, handshake, current_time); + + // We put a copy of the gcm tag in the header so Alice can tell this packet apart + // from any other pattern 1 packet we send, without having to make Bob maintain state. + let mut pattern2_id = 0u64.to_ne_bytes(); + pattern2_id[5] = message2[NoiseXKPattern2::P_AUTH_END - 3]; + pattern2_id[6] = message2[NoiseXKPattern2::P_AUTH_END - 2]; + pattern2_id[7] = message2[NoiseXKPattern2::P_AUTH_END - 1]; + send_with_fragmentation( + &mut send_unassociated_reply, + send_unassociated_mtu, + &mut message2, + PACKET_TYPE_NOISE_XK_PATTERN_2, + Some(remote_key_id), + u64::from_be_bytes(pattern2_id), + Some(&Aes::new(header_b2a_key.first_n::())), + ); + + return Ok(ReceiveResult::Unassociated); + } else { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + } + PACKET_TYPE_BOB_DOS_CHALLENGE => { + app.event_log(LogEvent::ReceiveUncheckedDOSChallenge, current_time); + // We expect Bob to only send this to us through our unassociated defrag cache. + if incoming.is_some() || session.is_some() { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + if message.len() != BobDOSChallenge::SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + let challenge: &BobDOSChallenge = byte_array_as_proto_buffer(message); + + if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(challenge.alice_key_id)) { + if let Some(session) = self.0.session_map.read().unwrap().get(&local_key_id).and_then(|s| s.0.upgrade()) { + // We don't need to hold the kex lock because we are not transitioning state. + let mut state = session.state.write().unwrap(); + if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { + if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, .. } = &mut handshake_state.offer { + let pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(noise_message); + // Only people who know what Alice's prior pow was can convince us to + // compute a new pow. + if challenge.prior_challenge_pow != pattern1.challenge_pow { + // This can occur if Bob sends us multiple challenges and they + // arrive OOO. + return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); + } + pattern1.challenge_counter.copy_from_slice(&challenge.challenge_counter); + pattern1.challenge_mac.copy_from_slice(&challenge.challenge_mac); + let mut pow = random::next_u64_secure(); + let sha512 = &mut SHA512::new(); + loop { + let pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(noise_message); + pattern1.challenge_pow.copy_from_slice(&pow.to_be_bytes()); + if verify_pow::(sha512, noise_message) { + break; + } + pow = pow.wrapping_add(1); + } + + app.event_log(LogEvent::ReceiveValidDOSChallenge(&session), current_time); + return Ok(ReceiveResult::Unassociated); + } else { + // This could happen if Bob challenges Alice, but their challenge packet + // gets massively delayed. + return Err(byzantine_fault!(FaultType::OutOfSequence, true)); + } + } else { + // This could happen if Bob challenges Alice, but their challenge packet + // gets massively delayed. + return Err(byzantine_fault!(FaultType::OutOfSequence, true)); + } + } else { + // This can occur naturally if Alice's session was dropped. + return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); + } + } else { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + } + PACKET_TYPE_NOISE_XK_PATTERN_2 => { + app.event_log(LogEvent::ReceiveUncheckedXK2, current_time); + // Bob (remote) --> Alice (local) + // <- e, ee, ekem1, psk + if incoming.is_some() { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + if message.len() != NoiseXKPattern2::SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + + let session = session.ok_or(byzantine_fault!(FaultType::UnknownLocalKeyId, false))?; + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + + if let NoiseXKPattern1or3(handshake_state) = &state.outgoing_offer { + if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { + noise_h_ee1p, noise_e_secret, noise_e1_secret, noise_ck_es, .. + } = &handshake_state.offer + { + let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); + // Authenticate header counter. + if noise_pattern2.header[13..] != noise_pattern2.p_gcm_tag[13..] { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + + // Noise process pattern2 e token. + if let Some((noise_e_pattern2, noise_ee)) = from_bytes_agreement(&noise_pattern2.noise_e, &noise_e_secret) { + let sha512 = &mut SHA512::new(); + let mut noise_ck = noise_ck_es.clone(); + let noise_h_ee1pe = mix_hash(sha512, noise_h_ee1p, noise_e_pattern2.as_bytes()); + noise_ck.mix_key(noise_e_pattern2.as_bytes()); + // Noise process pattern2 ee token. + let noise_k_esee = noise_ck.mix_key_initialize_key(noise_ee.as_bytes()); + drop(noise_ee); + // Noise process pattern2 ekem1 token. + let (is_auth, noise_h_ee1peekem1) = decrypt_and_hash( + sha512, + &noise_k_esee, + &noise_h_ee1pe, + packet_type, + 0, + &mut message[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], + ); + let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); + let noise_ekem1_secret = + pqc_kyber::decapsulate(&noise_pattern2.noise_ekem1, noise_e1_secret.as_bytes()).map(|k| Secret(k)); + if let Some(Ok(noise_ekem1_secret)) = is_auth.then_some(noise_ekem1_secret) { + noise_ck.mix_key(noise_ekem1_secret.as_bytes()); + drop(noise_ekem1_secret); + // We attempt to decrypt the payload at most twice. First time with + // the ratchet key Alice last remembers, and second time with a ratchet + // key of zero if Alice allows ratchet downgrades. + let mut ratchet_number = state.ratchet_number; + let mut ratchet_key = state.ratchet_key.as_bytes(); + let mut ratchet_result = None; + for i in 0..2 { + // Constant time ratchet key downgrade check. + let mut noise_ck_ratchet = noise_ck.clone(); + let mut payload = [0u8; NoiseXKPattern2::P_AUTH_END - NoiseXKPattern2::P_ENC_START]; + payload.copy_from_slice(&message[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END]); + // Noise process pattern2 psk token. + let (temp_h, noise_k_ratchet) = noise_ck_ratchet.mix_key_and_hash_initialize_key(ratchet_key); + let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); + // Noise process pattern2 payload. + let (is_auth, noise_h_ratchet) = + decrypt_and_hash(sha512, &noise_k_ratchet, &noise_h_ee1peekem1psk, packet_type, 0, &mut payload); + let mut key_id = 0u32.to_ne_bytes(); + key_id.copy_from_slice(&payload[..NoiseXKPattern2::P_AUTH_START - NoiseXKPattern2::P_ENC_START]); + + if is_auth { + if i > 0 { + ratchet_result = + NonZeroU32::new(u32::from_ne_bytes(key_id)).map(|id| (id, noise_k_ratchet, noise_h_ratchet)); + noise_ck = noise_ck_ratchet.clone(); + break; + } + } else { + if i > 0 || !app.allow_downgrade(&session, current_time) { + break; + } + // If auth failed maybe Bob wants to downgrade the ratchet, + // retry decryption with no ratchet if we have not already. + ratchet_number = 0; + ratchet_key = &[0u8; RATCHET_KEY_SIZE]; + } + } + + if let Some((remote_key_id, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) = ratchet_result { + // Start of Noise XKhfs+psk2 pattern3. + let mut message3 = [0u8; NoiseXKPattern3::MAX_SIZE]; + // Noise process pattern3 s token. + let alice_s_keypair = app.local_s_keypair(); + if let Some(noise_se) = alice_s_keypair.agree(&noise_e_pattern2) { + let payload = handshake_state.alice_identity_blob.as_ref(); + // Packet fully authenticated. + let s_enc_start = HEADER_SIZE; + let s_auth_start = s_enc_start + P384_PUBLIC_KEY_SIZE; + let p_enc_start = s_auth_start + AES_GCM_TAG_SIZE; + let p_auth_start = p_enc_start + payload.len(); + let p_auth_end = p_auth_start + AES_GCM_TAG_SIZE; + let message3_len = p_auth_end; + + message3[s_enc_start..s_auth_start].copy_from_slice(alice_s_keypair.public_key_bytes()); + let noise_h_ee1peekem1pskps = encrypt_and_hash( + sha512, + &noise_k_eseeekem1psk, + &noise_h_ee1peekem1pskp, + PACKET_TYPE_NOISE_XK_PATTERN_3, + 1, + &mut message3[s_enc_start..p_enc_start], + ); + drop(noise_k_eseeekem1psk); + // Noise process pattern3 se token. + let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(noise_se.as_bytes()); + drop(noise_se); + // Noise process pattern3 payload token. + message3[p_enc_start..p_auth_start].copy_from_slice(payload); + let noise_h_ee1peekem1pskpsp = encrypt_and_hash( + sha512, + &noise_k_eseeekem1pskse, + &noise_h_ee1peekem1pskps, + PACKET_TYPE_NOISE_XK_PATTERN_3, + 0, + &mut message3[p_enc_start..p_auth_end], + ); + drop(noise_k_eseeekem1pskse); + // Alice finished Noise XKhfs+psk2 handshake. + // Transition offer state machine to the NoiseXKPattern3 state. + let new_ratchet_number = ratchet_number + 1; + let (new_ratchet_key, new_ratchet_fingerprint) = + noise_ck.get_ask2(LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); + let result = app.save_ratchet_state( + &session.remote_s_public_key, + &session.application_data, + SaveRatchetAction::SaveAsUnconfirmed, + new_ratchet_number, + new_ratchet_fingerprint.as_bytes(), + new_ratchet_key.as_bytes(), + current_time, + ); + if result.is_err() { + return Err(ReceiveError::RatchetIoError); + } + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); + + let local_key_id = handshake_state.local_key_id; + drop(state); + let mut state = session.state.write().unwrap(); + session.kex_receive_cipher.lock().unwrap().replace(AesGcm::new(kex_key_b2a.as_bytes())); + session.kex_send_cipher.lock().unwrap().replace(AesGcm::new(kex_key_a2b.as_bytes())); + + state.cipher_states[0].replace(SessionKey::new::( + noise_ck, + local_key_id, + remote_key_id, + INIT_COUNTER, + false, + )); + debug_assert!(state.cipher_states[1].is_none()); + if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { + handshake_state.next_retry_time = + AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); + handshake_state.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); + handshake_state.offer = NoiseXKAliceHandshakeState::NoiseXKPattern3 { + noise_message: message3, + noise_message_len: p_auth_end, + new_ratchet_number, + new_ratchet_fingerprint: new_ratchet_fingerprint.clone(), + new_ratchet_key: new_ratchet_key.clone(), + }; + } + drop(state); + drop(kex_lock); + + if let Some((mut send, mut mtu)) = send_to(&session) { + mtu = mtu.max(MIN_TRANSPORT_MTU); + send_with_fragmentation( + &mut send, + mtu, + &mut message3[..message3_len], + PACKET_TYPE_NOISE_XK_PATTERN_3, + Some(remote_key_id), + 0, + Some(&session.header_send_cipher), + ); + } + app.event_log(LogEvent::ReceiveValidXK2(&session), current_time); + return Ok(ReceiveResult::Session(session, SessionEvent::Control)); + } + } + } + } + // Bob failed authentication so we must restart our offer according to Noise. + // We restart the offer instead of dropping the session to defend against DOS. + drop(state); + let mut state = session.state.write().unwrap(); + let ratchet_fingerprint = state.ratchet_fingerprint.clone(); + if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { + if !handshake_state.reinitialize(&session, &ratchet_fingerprint, &mut self.0.session_map.write().unwrap(), current_time) { + session.expire() + } + } + drop(state); + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } else { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + } else { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + } + PACKET_TYPE_NOISE_XK_PATTERN_3 => { + app.event_log(LogEvent::ReceiveUncheckedXK3, current_time); + // Alice (remote) --> Bob (local) + // -> s, se + if session.is_some() { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + if message.len() < NoiseXKPattern3::MIN_SIZE || message.len() > NoiseXKPattern3::MAX_SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + // The code above guarantees to us that each `incoming` handshake state that reaches + // this point will be strictly unique, even for the same remote peer. + // This property is strictly necessary to prevent catastrophic nonce reuse due to + // two session being created with the same set of keys. + let handshake_state = incoming.ok_or(byzantine_fault!(FaultType::UnknownLocalKeyId, false))?; + let s_enc_start = HEADER_SIZE; + + let s_auth_start = s_enc_start + P384_PUBLIC_KEY_SIZE; + let p_enc_start = s_auth_start + AES_GCM_TAG_SIZE; + let p_auth_end = message.len(); + let p_auth_start = p_auth_end - AES_GCM_TAG_SIZE; + + if !(p_enc_start <= p_auth_start) { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + // Do not read from the message before this point, otherwise an array out of bounds + // error is possible. + // Noise process pattern3 s token. + let sha512 = &mut SHA512::new(); + let (is_auth, noise_h_ee1peekem1pskps) = decrypt_and_hash( + sha512, + &handshake_state.noise_k_eseeekem1psk, + &handshake_state.noise_h_ee1peekem1pskp, + packet_type, + 1, + &mut message[s_enc_start..p_enc_start], + ); + if !is_auth { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + // Noise process pattern3 se token. + if let Some((remote_s_public_key, noise_se)) = + from_bytes_agreement(&message[s_enc_start..s_auth_start], &handshake_state.noise_e_secret) + { + let mut noise_ck = handshake_state.noise_ck_eseeekem1psk.clone(); + let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(noise_se.as_bytes()); + drop(noise_se); + // Noise process pattern3 payload. + let (is_auth, noise_h_ee1peekem1pskpsp) = decrypt_and_hash( + sha512, + &noise_k_eseeekem1pskse, + &noise_h_ee1peekem1pskps, + packet_type, + 0, + &mut message[p_enc_start..p_auth_end], + ); + drop(noise_k_eseeekem1pskse); + if !is_auth { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + // Bob finished Noise XKhfs+psk2 handshake. + let header_send_cipher = Aes::new(handshake_state.header_send_key.as_bytes()); + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); + match check_accept_session( + &remote_s_public_key, + &message[p_enc_start..p_auth_start], + handshake_state.ratchet_fingerprint.as_ref(), + ) { + AcceptSessionAction::Accept(application_data) => { + let bob_s_keypair = app.local_s_keypair(); + let noise_kk_ss = bob_s_keypair + .agree(&remote_s_public_key) + .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; + let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, bob_s_keypair.public_key_bytes()); + let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, bob_s_keypair.public_key_bytes()); + + let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); + // We must make sure the ratchet key is saved before we transition. + let new_ratchet_number = handshake_state.ratchet_number + 1; + let result = app.save_ratchet_state( + &remote_s_public_key, + &application_data, + SaveRatchetAction::SaveAsConfirmed, + new_ratchet_number, + new_ratchet_fingerprint.as_bytes(), + new_ratchet_key.as_bytes(), + current_time, + ); + if result.is_err() { + return Err(ReceiveError::RatchetIoError); + } + + let mut session_queue = self.0.session_queue.lock().unwrap(); + let queue_idx = session_queue.reserve_index(); + let session = Arc::new(Session { + context: Arc::downgrade(&self.0), + queue_idx, + application_data, + remote_s_public_key, + send_counter: AtomicU64::new(INIT_COUNTER), + session_has_expired: AtomicBool::new(false), + counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), + state_machine_lock: Mutex::new(()), + state: RwLock::new(SessionMutableState { + ratchet_number: new_ratchet_number, + ratchet_fingerprint: new_ratchet_fingerprint.clone(), + ratchet_key: new_ratchet_key.clone(), + cipher_states: [ + Some(SessionKey::new::( + noise_ck, + handshake_state.local_key_id, + handshake_state.remote_key_id, + INIT_COUNTER, + true, + )), + None, + ], + current_key: 0, + outgoing_offer: KeyConfirm { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), + }, + }), + header_receive_cipher: Aes::new(handshake_state.header_receive_key.as_bytes()), + header_send_cipher, + kex_receive_cipher: Mutex::new(Some(AesGcm::new(kex_key_a2b.as_bytes()))), + kex_send_cipher: Mutex::new(Some(AesGcm::new(kex_key_b2a.as_bytes()))), + noise_kk_ss, + noise_kk_local_init_h, + noise_kk_remote_init_h, + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + was_bob: true, + }); + let timer = Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); + session_queue.push_reserved(queue_idx, Arc::downgrade(&session), timer); + drop(session_queue); + // There is the miniscule possibility this key id is already + // in use, in which case we have to drop this session like + // nothing ever happened. + let mut session_map = self.0.session_map.write().unwrap(); + if !session_map.contains_key(&handshake_state.local_key_id) { + session_map.insert(handshake_state.local_key_id, (Arc::downgrade(&session), false)); + drop(session_map); + let _ = session.send_control(&session.state.read().unwrap(), send_unassociated_reply, PACKET_TYPE_KEY_CONFIRM, &[]); + + app.event_log(LogEvent::ReceiveValidXK3(&session.application_data), current_time); + return Ok(ReceiveResult::Session(session, SessionEvent::NewSession(new_ratchet_number))); + } else { + // This can occur if we accidentally generate a key id collision. + // There is an extremely short amount of time during which + // another session can steal this session's id, we'll have to + // restart the handshake in this case. + return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); + } + } + AcceptSessionAction::SendReject => { + // We just used a counter with this key, but we are not storing + // the fact we used it in memory. This is currently ok because the + // handshake is being dropped, so nonce reuse can't happen. + let (mut fragment, len) = encrypt_control( + &mut AesGcm::new(kex_key_b2a.as_bytes()), + &header_send_cipher, + PACKET_TYPE_SESSION_REJECTED, + INIT_COUNTER, + handshake_state.remote_key_id.get(), + &[], + ); + send_unassociated_reply(&mut fragment[..len]); + return Ok(ReceiveResult::Rejected); + } + AcceptSessionAction::SilentlyReject => return Ok(ReceiveResult::Rejected), + } + } else { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + } + _ => return Err(byzantine_fault!(FaultType::InvalidPacket, false)), + } + } + /// Helper function for sending the empty string over the session. Useful for keep-alives. + /// + /// * `session` - The session to send to + /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a + /// slice of `data` + /// * `current_time` - Current time in milliseconds + #[inline] + pub fn send_empty(&self, session: &Arc>, send: impl FnMut(&mut [u8]) -> bool, current_time: i64) -> Result<(), SendError> { + self.send(session, send, &mut [0u8; MIN_TRANSPORT_MTU], &[], current_time) + } + /// Send data over the session. + /// + /// * `session` - The session to send to + /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a + /// slice of `data` + /// * `mtu_sized_buffer` - A writable work buffer whose size equals the MTU + /// * `data` - Data to send + /// * `current_time` - Current time in milliseconds + #[inline] + pub fn send( + &self, + session: &Arc>, + mut send: impl FnMut(&mut [u8]) -> bool, + mtu_sized_buffer: &mut [u8], + mut data: &[u8], + current_time: i64, + ) -> Result<(), SendError> { + if mtu_sized_buffer.len() < MIN_TRANSPORT_MTU { + return Err(SendError::InvalidParameter); + } + let state = session.state.read().unwrap(); + let key = state.cipher_states[state.current_key].as_ref().ok_or(SendError::SessionNotEstablished)?; + let counter = session.get_next_outgoing_counter()?; + + let mut c = key.get_send_cipher(counter)?; + c.reset_init_gcm(&create_message_nonce(PACKET_TYPE_DATA, counter)); + + let fragment_max_chunk_size = mtu_sized_buffer.len() - HEADER_SIZE; + let fragment_count = (data.len() + AES_GCM_TAG_SIZE + (fragment_max_chunk_size - 1)) / fragment_max_chunk_size; + if fragment_count > MAX_FRAGMENTS { + return Err(SendError::DataTooLarge); + } + let last_fragment_no = fragment_count - 1; + + for fragment_no in 0..fragment_count { + let chunk_size = fragment_max_chunk_size.min(data.len()); + let mut fragment_size = chunk_size + HEADER_SIZE; + + set_packet_header( + mtu_sized_buffer, + fragment_count as u8, + fragment_no as u8, + PACKET_TYPE_DATA, + key.remote_key_id.get(), + counter, + ); + + c.crypt(&data[..chunk_size], &mut mtu_sized_buffer[HEADER_SIZE..fragment_size]); + data = &data[chunk_size..]; + + if fragment_no == last_fragment_no { + debug_assert!(data.is_empty()); + let tagged_fragment_size = fragment_size + AES_GCM_TAG_SIZE; + mtu_sized_buffer[fragment_size..tagged_fragment_size].copy_from_slice(&c.finish_encrypt()); + fragment_size = tagged_fragment_size; + } + + session + .header_send_cipher + .crypt_block_in_place(&mut mtu_sized_buffer[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]); + if !send(&mut mtu_sized_buffer[..fragment_size]) { + break; + } + } + drop(c); + if counter >= key.rekey_at_counter { + if let OfferStateMachine::Normal { .. } = &state.outgoing_offer { + drop(state); + if let Ok(timer) = initiate_rekey(&self.0, session, send, current_time) { + self.0.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timer)); + } + } + } + return Ok(()); + } + /// Update the challenge window, returning true if the challenge is still valid. + #[inline(always)] + fn check_challenge_window(&self, counter: u64) -> bool { + let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.load(Ordering::Relaxed); + prev_counter < counter + } + /// Update the challenge window, returning true if the challenge is still valid. + #[inline(always)] + fn update_challenge_window(&self, counter: u64) -> bool { + let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); + prev_counter < counter + } +} +/// Initiate the rekeying protocol. This session will now begin attempting to rekey this session +/// with its peer, if it was not already. +fn initiate_rekey( + context: &Arc>, + session: &Arc>, + send: impl FnOnce(&mut [u8]) -> bool, + current_time: i64, +) -> Result { + let mut message = [0u8; NoiseKKPattern1or2::SIZE]; + + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + // We may only attempt to rekey if we are not already doing so. + match &state.outgoing_offer { + OfferStateMachine::Normal { .. } => (), + _ => return Err(()), + } + let sha512 = &mut SHA512::new(); + // Start of Noise KKpsk0 pattern1. + // Noise process pattern1 psk0 token. + let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); + let noise_temp_h = noise_ck.mix_key_and_hash(state.ratchet_key.as_bytes()); + let noise_h_psk = mix_hash(sha512, &session.noise_kk_local_init_h, &noise_temp_h); + // Noise process pattern1 e token. + let noise_e_secret = P384KeyPair::generate(); + let noise_h_pske = mix_hash(sha512, &noise_h_psk, noise_e_secret.public_key_bytes()); + noise_ck.mix_key(noise_e_secret.public_key_bytes()); + + let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); + noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); + // Noise process pattern1 es token. + let noise_es = noise_e_secret.agree(&session.remote_s_public_key).ok_or(())?; + noise_ck.mix_key(noise_es.as_bytes()); + drop(noise_es); + // Noise process pattern1 ss token. + let noise_k_pskesss = noise_ck.mix_key_initialize_key(session.noise_kk_ss.as_bytes()); + // Noise process pattern1 payload token. + let mut session_map = context.session_map.write().unwrap(); + let new_key_id = generate_key_id(&session_map); + let next_key_index = state.current_key ^ 1; + session_map.insert(new_key_id, (Arc::downgrade(session), next_key_index > 0)); + drop(session_map); + + let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); + noise_pattern1.key_id = new_key_id.get().to_ne_bytes(); + let noise_h_pskep = encrypt_and_hash( + sha512, + &noise_k_pskesss, + &noise_h_pske, + PACKET_TYPE_NOISE_KK_PATTERN_1, + 0, + &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], + ); + drop(noise_k_pskesss); + + drop(state); + let mut state = session.state.write().unwrap(); + state.outgoing_offer = OfferStateMachine::NoiseKKPattern1 { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), + new_key_id, + noise_e_secret, + noise_message: message.clone(), + noise_h_pskep, + noise_ck: noise_ck.clone(), + }; + drop(state); + drop(kex_lock); + let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_NOISE_KK_PATTERN_1, &message); + return Ok(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); +} +fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mut [u8]) -> bool>( + context: &Context, + session: Arc>, + app: &Application, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + packet_type: u8, + counter: u64, + fragment: &mut [u8], + current_time: i64, +) -> Result, ReceiveError> { + let state = session.state.read().unwrap(); + let mut c = session.kex_receive_cipher.lock().unwrap(); + let message = decrypt_control( + c.as_mut().ok_or(byzantine_fault!(FaultType::OutOfSequence, false))?, + packet_type, + counter, + fragment, + )?; + drop(c); + session.update_receive_window(counter); + use OfferStateMachine::*; + return match packet_type { + PACKET_TYPE_SESSION_REJECTED => match &state.outgoing_offer { + NoiseXKPattern1or3(_) => { + drop(state); + Ok(ReceiveResult::Session(session, SessionEvent::Rejected)) + } + _ => Err(byzantine_fault!(FaultType::OutOfSequence, false)), + }, + PACKET_TYPE_KEY_CONFIRM => { + drop(state); + app.event_log(LogEvent::ReceiveValidKeyConfirm(&session), current_time); + let kex_lock = session.state_machine_lock.lock().unwrap(); + let mut state = session.state.write().unwrap(); + // We only want to stop sending NoiseKKPattern2 offers when the latest derived + // key is confirmed. And we only want to do that once. + let (used_latest_key, key_confirmed, ret) = match &state.outgoing_offer { + NoiseKKPattern2 { + new_ratchet_number, new_ratchet_fingerprint, new_ratchet_key, .. + } => ( + true, + Some((*new_ratchet_number, new_ratchet_fingerprint.clone(), new_ratchet_key.clone())), + SessionEvent::Ratchet(*new_ratchet_number), + ), + NoiseXKPattern1or3(handshake_state) => { + if let NoiseXKAliceHandshakeState::NoiseXKPattern3 { + new_ratchet_number, new_ratchet_fingerprint, new_ratchet_key, .. + } = &handshake_state.offer + { + ( + true, + Some((*new_ratchet_number, new_ratchet_fingerprint.clone(), new_ratchet_key.clone())), + SessionEvent::Established(*new_ratchet_number), + ) + } else { + (false, None, SessionEvent::Control) + } + } + _ => (true, None, SessionEvent::Control), + }; + if let Some(ratchet) = key_confirmed { + let result = app.save_ratchet_state( + &session.remote_s_public_key, + &session.application_data, + SaveRatchetAction::ConfirmLatestAndDeletePrevious, + ratchet.0, + ratchet.1.as_bytes(), + ratchet.2.as_bytes(), + current_time, + ); + if result.is_ok() { + if let NoiseKKPattern2 { kex_send_key, .. } = &state.outgoing_offer { + session.kex_send_cipher.lock().unwrap().replace(AesGcm::new(kex_send_key.as_bytes())); + } + state.ratchet_number = ratchet.0; + state.ratchet_fingerprint.overwrite(&ratchet.1); + state.ratchet_key.overwrite(&ratchet.2); + state.current_key ^= 1; + state.outgoing_offer = new_normal_state(current_time); + } else { + return Err(ReceiveError::RatchetIoError); + } + } + drop(state); + drop(kex_lock); + if used_latest_key { + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_KEY_DELETE, &[]); + } + } + Ok(ReceiveResult::Session(session, ret)) + } + PACKET_TYPE_KEY_DELETE => { + drop(state); + app.event_log(LogEvent::ReceiveValidKeyDelete(&session), current_time); + let kex_lock = session.state_machine_lock.lock().unwrap(); + let mut state = session.state.write().unwrap(); + // Check if we should end any current offers and transition + // back to the None state + match &state.outgoing_offer { + KeyConfirm { .. } => { + let result = app.save_ratchet_state( + &session.remote_s_public_key, + &session.application_data, + SaveRatchetAction::DeletePrevious, + state.ratchet_number, + state.ratchet_fingerprint.as_bytes(), + state.ratchet_key.as_bytes(), + current_time, + ); + if result.is_ok() { + state.outgoing_offer = new_normal_state(current_time); + } else { + return Err(ReceiveError::RatchetIoError); + } + } + _ => (), + } + drop(state); + drop(kex_lock); + Ok(ReceiveResult::Session(session, SessionEvent::Control)) + } + PACKET_TYPE_NOISE_KK_PATTERN_1 => { + app.event_log(LogEvent::ReceiveUncheckedKK1, current_time); + let message = &mut message[..NoiseKKPattern1or2::SIZE]; + let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); + + drop(state); + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + // We need the following operation to be atomic with the change of offer type + let (should_rekey_as_bob, chosen_id) = match &state.outgoing_offer { + // Check rekey rate limits. + Normal { .. } => (true, None), + // In the following situation, both parties are in state NoiseKKPattern1, + // we need to deterministically allow only one of them to transition to + // NoiseKKPattern2. + NoiseKKPattern1 { new_key_id, .. } => (session.was_bob, Some(*new_key_id)), + _ => (false, None), + }; + if !should_rekey_as_bob { + // This can be triggered if both parties attempt rekeying simultaneously, or if the + // remote party sent us a duplicate rekey request. + // The code above handles this case and only lets one party through to rekeying. + drop(state); + drop(kex_lock); + return Ok(ReceiveResult::Session(session, SessionEvent::Control)); + } + // Noise process pattern1 psk0 token. + let sha512 = &mut SHA512::new(); + let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); + let noise_temp_h = noise_ck.mix_key_and_hash(state.ratchet_key.as_bytes()); + let noise_h_psk = mix_hash(sha512, &session.noise_kk_remote_init_h, &noise_temp_h); + // Noise process pattern1 e token. + if let Some((alice_e, noise_es)) = from_bytes_agreement(&noise_pattern1.noise_e, &app.local_s_keypair()) { + let bob_e_secret = P384KeyPair::generate(); + if let (Some(noise_ee), Some(noise_se)) = (bob_e_secret.agree(&alice_e), bob_e_secret.agree(&session.remote_s_public_key)) { + let noise_h_pske = mix_hash(sha512, &noise_h_psk, alice_e.as_bytes()); + noise_ck.mix_key(alice_e.as_bytes()); + // Noise process pattern1 es token. + noise_ck.mix_key(noise_es.as_bytes()); + drop(noise_es); + // Noise process pattern1 ss token. + let noise_k_pskesss = noise_ck.mix_key_initialize_key(session.noise_kk_ss.as_bytes()); + + // Noise process pattern1 payload. + let (is_auth, noise_h_pskep) = decrypt_and_hash( + sha512, + &noise_k_pskesss, + &noise_h_pske, + packet_type, + 0, + &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], + ); + let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); + if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.key_id))) { + // Alice fully authenticated. + // Start of Noise KKpsk0 pattern2. + // Noise process pattern2 e token. + let noise_h_pskepe = mix_hash(sha512, &noise_h_pskep, bob_e_secret.public_key_bytes()); + noise_ck.mix_key(bob_e_secret.public_key_bytes()); + // Noise process pattern2 ee token. + noise_ck.mix_key(noise_ee.as_bytes()); + drop(noise_ee); + // Noise process pattern2 se token. + let noise_k_pskessseese = noise_ck.mix_key_initialize_key(noise_se.as_bytes()); + drop(noise_se); + // Noise process pattern2 payload. + let mut message2 = [0u8; NoiseKKPattern1or2::SIZE]; + let noise_pattern2: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message2); + noise_pattern2.noise_e = *bob_e_secret.public_key_bytes(); + let mut session_map = context.0.session_map.write().unwrap(); + // If we already generated a new key id mapping reuse it. + let new_key_id = chosen_id.unwrap_or_else(|| generate_key_id(&session_map)); + noise_pattern2.key_id = new_key_id.get().to_ne_bytes(); + + let noise_h_pskepep = encrypt_and_hash( + sha512, + &noise_k_pskessseese, + &noise_h_pskepe, + PACKET_TYPE_NOISE_KK_PATTERN_2, + 0, + &mut message2[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], + ); + drop(noise_k_pskessseese); + // Bob finished Noise KKpsk0 handshake. + let new_ratchet_number = state.ratchet_number + 1; + let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(LABEL_RATCHET_STATE, &noise_h_pskepep); + let result = app.save_ratchet_state( + &session.remote_s_public_key, + &session.application_data, + SaveRatchetAction::SaveAsUnconfirmed, + new_ratchet_number, + new_ratchet_fingerprint.as_bytes(), + new_ratchet_key.as_bytes(), + current_time, + ); + if result.is_err() { + drop(state); + drop(kex_lock); + return Err(ReceiveError::RatchetIoError); + } + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(LABEL_KEX_KEY, &noise_h_pskepep); + // The new "Bob" doesn't know yet if Alice has received the new key, so the + // new key is recorded as the "alt" (key_index ^ 1) but the current key is + // not advanced yet. + let next_key_index = state.current_key ^ 1; + session_map.insert(new_key_id, (Arc::downgrade(&session), next_key_index > 0)); + if let Some(pre_id) = state.cipher_states[next_key_index].as_ref().map(|k| k.local_key_id) { + session_map.remove(&pre_id); + } + drop(session_map); + drop(state); + let mut state = session.state.write().unwrap(); + let current_counter = session.send_counter.load(Ordering::Relaxed); + session.kex_receive_cipher.lock().unwrap().replace(AesGcm::new(kex_key_a2b.as_bytes())); + + state.cipher_states[next_key_index].replace(SessionKey::new::( + noise_ck, + new_key_id, + remote_key_id, + current_counter, + true, + )); + let timer = current_time.saturating_add(Application::RETRY_INTERVAL_MS); + state.outgoing_offer = NoiseKKPattern2 { + next_retry_time: AtomicI64::new(timer), + timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), + noise_message: message2, + kex_send_key: kex_key_b2a.clone(), + new_ratchet_number, + new_ratchet_fingerprint: new_ratchet_fingerprint.clone(), + new_ratchet_key: new_ratchet_key.clone(), + }; + drop(state); + drop(kex_lock); + context.0.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timer)); + + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_NOISE_KK_PATTERN_2, &message2); + } + app.event_log(LogEvent::ReceiveValidKK1(&session), current_time); + return Ok(ReceiveResult::Session(session, SessionEvent::Control)); + } + } + } + Err(byzantine_fault!(FaultType::FailedAuthentication, false)) + } + PACKET_TYPE_NOISE_KK_PATTERN_2 => { + app.event_log(LogEvent::ReceiveUncheckedKK2, current_time); + let message = &mut message[..NoiseKKPattern1or2::SIZE]; + let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); + + drop(state); + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + if let NoiseKKPattern1 { new_key_id, noise_e_secret, noise_ck, noise_h_pskep, .. } = &state.outgoing_offer { + // Noise process pattern2 e token. + if let Some((bob_e, noise_ee)) = from_bytes_agreement(&noise_pattern2.noise_e, &noise_e_secret) { + if let Some(noise_se) = app.local_s_keypair().agree(&bob_e) { + let sha512 = &mut SHA512::new(); + let mut noise_ck = noise_ck.clone(); + let noise_h_pskepe = mix_hash(sha512, noise_h_pskep, bob_e.as_bytes()); + noise_ck.mix_key(bob_e.as_bytes()); + // Noise process pattern2 ee token. + noise_ck.mix_key(noise_ee.as_bytes()); + drop(noise_ee); + // Noise process pattern2 se token. + let noise_k_pskessseese = noise_ck.mix_key_initialize_key(noise_se.as_bytes()); + drop(noise_se); + // Noise process pattern2 payload. + let (is_auth, noise_h_pskepep) = decrypt_and_hash( + sha512, + &noise_k_pskessseese, + &noise_h_pskepe, + packet_type, + 0, + &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], + ); + let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); + if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern2.key_id))) { + // Bob fully authenticated. + // Alice finished Noise KKpsk0 handshake. + let new_ratchet_number = state.ratchet_number + 1; + let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(LABEL_RATCHET_STATE, &noise_h_pskepep); + + let result = app.save_ratchet_state( + &session.remote_s_public_key, + &session.application_data, + SaveRatchetAction::SaveAsConfirmed, + new_ratchet_number, + new_ratchet_fingerprint.as_bytes(), + new_ratchet_key.as_bytes(), + current_time, + ); + if result.is_err() { + drop(state); + drop(kex_lock); + return Err(ReceiveError::RatchetIoError); + } + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(LABEL_KEX_KEY, &noise_h_pskepep); + + let new_key_id = *new_key_id; + drop(state); + let mut state = session.state.write().unwrap(); + let next_key_index = state.current_key ^ 1; + state.current_key = next_key_index; + if let Some(key) = state.cipher_states[next_key_index].as_ref() { + context.0.session_map.write().unwrap().remove(&key.local_key_id); + } + session.kex_receive_cipher.lock().unwrap().replace(AesGcm::new(kex_key_b2a.as_bytes())); + session.kex_send_cipher.lock().unwrap().replace(AesGcm::new(kex_key_a2b.as_bytes())); + + state.ratchet_number = new_ratchet_number; + state.ratchet_fingerprint.overwrite_first_n(&new_ratchet_fingerprint); + state.ratchet_key.overwrite(&new_ratchet_key); + + state.cipher_states[next_key_index].replace(SessionKey::new::( + noise_ck, + new_key_id, + remote_key_id, + session.send_counter.load(Ordering::Relaxed), + false, + )); + state.outgoing_offer = KeyConfirm { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), + }; + drop(state); + drop(kex_lock); + // Let Bob know we got the key. + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_KEY_CONFIRM, &[]); + } + app.event_log(LogEvent::ReceiveValidKK2(&session), current_time); + return Ok(ReceiveResult::Session(session, SessionEvent::Ratchet(new_ratchet_number))); + } + } + } + // Bob failed authentication so according to Noise we must terminate this + // handshake. + // This should not happen in practice since this packet will have already passed + // authentication under the current key. + session.expire(); + Err(byzantine_fault!(FaultType::FailedAuthentication, false)) + } else { + drop(state); + drop(kex_lock); + Ok(ReceiveResult::Session(session, SessionEvent::Control)) + } + } + _ => Err(byzantine_fault!(FaultType::InvalidPacket, false)), + }; +} + +impl Session { + /// This can only fail with `MaxKeyLifetimeExceeded` or `SessionNotEstablished`. + #[inline] + fn send_control( + &self, + state: &SessionMutableState, + send: impl FnOnce(&mut [u8]) -> bool, + packet_type: u8, + packet: &[u8], + ) -> Result<(), SendError> { + let key = state.cipher_states[state.current_key].as_ref().ok_or(SendError::SessionNotEstablished)?; + let counter = self.get_next_outgoing_counter()?; + let mut c = self.kex_send_cipher.lock().unwrap(); + let (mut fragment, len) = encrypt_control( + c.as_mut().ok_or(SendError::SessionNotEstablished)?, + &self.header_send_cipher, + packet_type, + counter, + key.remote_key_id.get(), + packet, + ); + send(&mut fragment[..len]); + return Ok(()); + } + /// Check whether this session is established. + #[inline] + pub fn established(&self) -> bool { + let state = self.state.read().unwrap(); + return match &state.outgoing_offer { + OfferStateMachine::NoiseXKPattern1or3(_) => false, + _ => true, + }; + } + /// The static public key of the remote peer. + #[inline] + pub fn remote_s_public_key(&self) -> &P384PublicKey { + &self.remote_s_public_key + } + /// The most recent confirmed ratchet state of this session. + /// The returned values are sensitive and should be securely erased before being dropped. + #[inline] + pub fn ratchet_state(&self) -> (u64, [u8; RATCHET_FINGERPRINT_SIZE], [u8; RATCHET_KEY_SIZE]) { + let state = self.state.read().unwrap(); + (state.ratchet_number, *state.ratchet_fingerprint.as_bytes(), *state.ratchet_key.as_bytes()) + } + /// The most recent confirmed ratchet number of this session. + #[inline] + pub fn ratchet_number(&self) -> u64 { + self.state.read().unwrap().ratchet_number + } + /// Mark a session as expired. This will make it impossible for this session to successfully + /// receive or send data. It is recommended to simply `drop` the session instead, but this can + /// provide some reassurance in complex shared ownership situations. + pub fn expire(&self) { + if let Some(context) = self.context.upgrade() { + self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); + } + } + fn expire_inner( + &self, + context: &Arc>, + session_queue: &mut IndexedBinaryHeap>, Reverse>, + ) { + // Prevent this session from being updated. + session_queue.remove(self.queue_idx); + self.session_has_expired.store(true, Ordering::Relaxed); + let _kex_lock = self.state_machine_lock.lock().unwrap(); + let state = self.state.read().unwrap(); + let mut session_map = context.session_map.write().unwrap(); + for key in &state.cipher_states { + if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { + session_map.remove(&pre_id); + } + } + use OfferStateMachine::*; + let id = match &state.outgoing_offer { + NoiseXKPattern1or3(handshake_state) => handshake_state.local_key_id, + NoiseKKPattern1 { new_key_id, .. } => *new_key_id, + _ => return, + }; + session_map.remove(&id); + } + + /// Get the next outgoing counter value. + #[inline(always)] + fn get_next_outgoing_counter(&self) -> Result { + if self.session_has_expired.load(Ordering::Relaxed) { + Err(SendError::SessionExpired) + } else { + let counter = self.send_counter.fetch_add(1, Ordering::Relaxed); + if counter > THREAD_SAFE_COUNTER_HARD_EXPIRE { + // Because this thread sets the flag itself it will never be able to increment the + // counter again. + // For that reason the other atomic orderings can be `Relaxed`. + self.session_has_expired.store(true, Ordering::SeqCst) + } + Ok(counter) + } + } + /// Check the receive window without mutating state. + #[inline(always)] + fn check_receive_window(&self, counter: u64) -> bool { + let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.load(Ordering::Relaxed); + prev_counter < counter && counter.wrapping_sub(prev_counter) <= COUNTER_WINDOW_MAX_SKIP_AHEAD + } + /// Update the receive window, returning true if the packet is still valid. + /// This should only be called after the packet is authenticated. + #[inline(always)] + fn update_receive_window(&self, counter: u64) -> bool { + let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); + prev_counter < counter && counter.wrapping_sub(prev_counter) <= COUNTER_WINDOW_MAX_SKIP_AHEAD + } +} +impl Drop for Session { + fn drop(&mut self) { + if let Some(context) = self.context.upgrade() { + self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); + } + } +} + +impl NoiseXKAliceHandshake { + /// Can only fail with `OpenError::InvalidPublicKey` because of remote_s_public_key. + /// Corresponds to Noise `Initialize`. + #[inline] + fn initialize( + local_key_id: NonZeroU32, + remote_s_public_key: &P384PublicKey, + ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], + ) -> Result<(NoiseXKAliceHandshakeState, Secret, Secret), OpenError> { + let mut message = [0u8; NoiseXKPattern1::SIZE]; + let sha512 = &mut SHA512::new(); + // Start of Noise XKhfs+psk2 pattern1. + let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); + let noise_e_secret = P384KeyPair::generate(); + let noise_e1_secret = pqc_kyber::keypair(&mut random::SecureRandom::default()); + noise_pattern1.alice_key_id = local_key_id.get().to_ne_bytes(); + noise_pattern1.noise_e = noise_e_secret.public_key_bytes().clone(); + noise_pattern1.noise_e1 = noise_e1_secret.public; + noise_pattern1.ratchet_fingerprint = *ratchet_fingerprint; + // Noise process prologue. + let noise_h = mix_hash( + sha512, + &INITIAL_H, + &message[NoiseXKPattern1::PROLOGUE_START..NoiseXKPattern1::PROLOGUE_END], + ); + let noise_h = mix_hash(sha512, &noise_h, remote_s_public_key.as_bytes()); + // Noise process pattern1 e token. + let mut noise_ck = SymmetricState::new(INITIAL_H); + let noise_h_e = mix_hash(sha512, &noise_h, noise_e_secret.public_key_bytes()); + noise_ck.mix_key(noise_e_secret.public_key_bytes()); + // Noise process pattern1 es token. + let noise_es = noise_e_secret.agree(remote_s_public_key).ok_or(OpenError::InvalidPublicKey)?; + let noise_k_es = noise_ck.mix_key_initialize_key(noise_es.as_bytes()); + drop(noise_es); + // Noise process pattern1 e1 token. + let noise_h_ee1 = encrypt_and_hash( + sha512, + &noise_k_es, + &noise_h_e, + PACKET_TYPE_NOISE_XK_PATTERN_1, + 0, + &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], + ); + // Noise process pattern1 payload. + let noise_h_ee1p = encrypt_and_hash( + sha512, + &noise_k_es, + &noise_h_ee1, + PACKET_TYPE_NOISE_XK_PATTERN_1, + 1, + &mut message[NoiseXKPattern1::P_ENC_START..NoiseXKPattern1::P_AUTH_END], + ); + drop(noise_k_es); + let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(LABEL_HEADER_KEY, &noise_h_ee1p); + let mut pattern1_id = 0u64.to_ne_bytes(); + pattern1_id.copy_from_slice(&message[NoiseXKPattern1::P_AUTH_START + 8..NoiseXKPattern1::P_AUTH_END]); + Ok(( + NoiseXKAliceHandshakeState::NoiseXKPattern1 { + noise_h_ee1p, + noise_e_secret, + noise_e1_secret: Secret(noise_e1_secret.secret), + noise_ck_es: noise_ck, + noise_message: message, + message_id: u64::from_be_bytes(pattern1_id), + }, + header_a2b_key, + header_b2a_key, + )) + } + /// Should not fail unless Bob's public key is adversarial. + fn reinitialize( + &mut self, + session: &Arc>, + ratchet_fingerprint: &Secret, + session_map: &mut HashMap>, bool)>, + current_time: i64, + ) -> bool { + let local_key_id = generate_key_id(session_map); + if let Ok((offer, a2b_header_key, b2a_header_key)) = + Self::initialize(local_key_id, &session.remote_s_public_key, ratchet_fingerprint.as_bytes()) + { + self.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); + session_map.remove(&self.local_key_id); + session_map.insert(local_key_id, (Arc::downgrade(session), false)); + self.local_key_id = local_key_id; + self.offer = offer; + session.header_send_cipher.reset(a2b_header_key.as_bytes()); + session.header_receive_cipher.reset(b2a_header_key.as_bytes()); + true + } else { + false + } + } +} + +/// Create the normal state of the offer state machine, with the correct timestamps. +fn new_normal_state(current_time: i64) -> OfferStateMachine { + OfferStateMachine::Normal { + timeout: current_time + .saturating_add(Application::REKEY_AFTER_TIME_MS) + .saturating_sub(random::next_u32_secure() as i64 % Application::REKEY_AFTER_TIME_MAX_JITTER_MS), + } +} +/// Get a timestamp of when this timer should trigger next, or None if it should trigger now. +fn process_timer(timer: &AtomicI64, wait_time: i64, current_time: i64) -> Option { + let ts = timer.load(Ordering::Relaxed); + if ts <= current_time && timer.fetch_max(ts.saturating_add(wait_time), Ordering::Relaxed) == ts { + None + } else { + Some(ts) + } +} + +/// Corresponds to Noise `EncryptAndHash`. +#[inline] +fn encrypt_and_hash( + sha512: &mut SHA512, + noise_k: &Secret, + noise_h: &[u8; NOISE_HASHLEN], + packet_type: u8, + noise_k_uses: u64, + message: &mut [u8], +) -> [u8; NOISE_HASHLEN] { + let auth_start = message.len() - AES_GCM_TAG_SIZE; + let mut gcm = AesGcm::new(noise_k.as_bytes()); + // Encrypt and add authentication tag. + gcm.reset_init_gcm(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); + gcm.aad(noise_h); + if auth_start > 0 { + gcm.crypt_in_place(&mut message[..auth_start]); + } + message[auth_start..].copy_from_slice(&gcm.finish_encrypt()); + mix_hash(sha512, noise_h, message) +} +/// Corresponds to Noise `DecryptAndHash`. +#[inline] +fn decrypt_and_hash( + sha512: &mut SHA512, + noise_k: &Secret, + noise_h: &[u8; NOISE_HASHLEN], + packet_type: u8, + noise_k_uses: u64, + message: &mut [u8], +) -> (bool, [u8; NOISE_HASHLEN]) { + let auth_start = message.len() - AES_GCM_TAG_SIZE; + let noise_h_c = mix_hash(sha512, noise_h, message); + let mut gcm = AesGcm::new(noise_k.as_bytes()); + gcm.reset_init_gcm(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); + gcm.aad(noise_h); + if auth_start > 0 { + gcm.crypt_in_place(&mut message[..auth_start]); + } + (gcm.finish_decrypt(&message[auth_start..]), noise_h_c) +} +/// Encrypt a standardized control packet. +#[inline] +fn encrypt_control( + c: &mut AesGcm, + header_cipher: &Aes, + packet_type: u8, + counter: u64, + remote_key_id: u32, + packet: &[u8], +) -> ([u8; CONTROL_PACKET_MAX_SIZE], usize) { + let mut fragment = [0u8; CONTROL_PACKET_MAX_SIZE]; + let fragment_len = packet.len() + HEADER_SIZE + AES_GCM_TAG_SIZE; + + c.reset_init_gcm(&create_message_nonce(packet_type, counter)); + if packet.len() > 0 { + fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE].copy_from_slice(packet); + c.crypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); + } + fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len].copy_from_slice(&c.finish_encrypt()); + drop(c); + set_packet_header(&mut fragment, 1, 0, packet_type, remote_key_id, counter); + header_cipher.crypt_block_in_place(&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]); + (fragment, fragment_len) +} +#[inline] +fn decrypt_control<'a>(c: &mut AesGcm, packet_type: u8, counter: u64, fragment: &'a mut [u8]) -> Result<&'a mut [u8], ReceiveError> { + let fragment_len = fragment.len(); + if fragment_len < CONTROL_PACKET_MIN_SIZE || fragment_len > CONTROL_PACKET_MAX_SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + c.reset_init_gcm(&create_message_nonce(packet_type, counter)); + c.crypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); + if !c.finish_decrypt(&fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]) { + // This can occur naturally if one of the remote peers resent a + // control packet that got delayed and arrived out of order. + return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); + } + Ok(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]) +} + +#[inline(always)] +fn set_packet_header(packet: &mut [u8], fragment_count: u8, fragment_no: u8, packet_type: u8, remote_key_id: u32, counter_or_id: u64) { + debug_assert!(packet.len() >= MIN_PACKET_SIZE); + debug_assert!(fragment_count > 0); + debug_assert!(fragment_count <= MAX_FRAGMENTS as u8); + debug_assert!(fragment_no < MAX_FRAGMENTS as u8); + debug_assert_eq!((packet_type << 1) >> 1, packet_type); + // [0..4] recipient key id + // -- start AES(ck_es * h_e_e1_p) encrypted block -- + // [4] fragment count (1..255) + // [5] fragment number (0..254) + // [6] reserved zero + // -- start of AES-GCM Nonce -- + // [7] packet type + // [8..16] 64-bit counter or packet id (big endian) + packet[4..16].copy_from_slice(&create_message_nonce(packet_type, counter_or_id)); + packet[0..4].copy_from_slice(&remote_key_id.to_ne_bytes()); + packet[4] = fragment_count; + packet[5] = fragment_no; + packet[6] = 0; +} +/// Create a 96-bit AES-GCM nonce. +/// +/// The primary information that we want to be contained here is the counter and the +/// packet type. The former makes this unique and the latter's inclusion authenticates +/// it as effectively AAD. Other elements of the header are either not authenticated, +/// like fragmentation info, or their authentication is implied via key exchange like +/// the key id. +#[inline(always)] +fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_NONCE_SIZE] { + let mut ret = [0u8; AES_GCM_NONCE_SIZE]; + ret[3] = packet_type; + // Noise requires a big endian counter at the end of the Nonce + ret[4..].copy_from_slice(&counter.to_be_bytes()); + ret +} +/// returns `(fragment_count, fragment_no, packet_type, counter, header_nonce)`. +#[inline(always)] +fn parse_packet_header(packet: &[u8]) -> (u8, u8, u8, u64, [u8; 10]) { + let mut header_nonce = [0; 10]; + let mut counter = 0u64.to_ne_bytes(); + header_nonce.copy_from_slice(&packet[6..16]); + counter.copy_from_slice(&packet[8..16]); + // We intentionally ignore the version number for future revisions. + (packet[4], packet[5], packet[7], u64::from_be_bytes(counter), header_nonce) +} + +/// Break a packet into fragments and send them all. +/// +/// The contents of packet[] are mangled during this operation, so it should be discarded after. +/// This is only used for key exchange and control packets. For data packets this is done inline +/// for better performance with encryption and fragmentation happening at the same time. +fn send_with_fragmentation( + send: &mut impl FnMut(&mut [u8]) -> bool, + mtu: usize, + packet: &mut [u8], + packet_type: u8, + remote_key_id: Option, + counter_or_id: u64, + header_cipher: Option<&Aes>, +) -> bool { + let packet_len = packet.len(); + let fragment_count = (packet_len.saturating_add(mtu - 1)) / mtu; // integer ceiling divide + debug_assert!(fragment_count <= MAX_FRAGMENTS); + let mut fragment_start = 0; + let mut fragment_end = packet_len.min(mtu); + let mut fragment_no = 0; + loop { + let fragment = &mut packet[fragment_start..fragment_end]; + set_packet_header( + fragment, + fragment_count as u8, + fragment_no as u8, + packet_type, + remote_key_id.map(|n| n.get()).unwrap_or(0), + counter_or_id, + ); + if let Some(hcc) = header_cipher { + hcc.crypt_block_in_place(&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]); + } + if !send(fragment) { + return false; + } + fragment_no += 1; + if fragment_no < fragment_count { + fragment_start = fragment_end - HEADER_SIZE; + fragment_end = (fragment_start.saturating_add(mtu)).min(packet_len); + } else { + break; + } + } + return true; +} + +/// Assemble a series of fragments into a buffer and return the length of the assembled packet in +/// bytes. +/// +/// This is also only used for key exchange and control packets. For data packets decryption and +/// assembly happen in one pass for better performance. +fn assemble_fragments_into(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result { + let mut l = 0; + for i in 0..fragments.len() { + let mut ff = fragments[i].as_ref(); + if i > 0 { + ff = &ff[HEADER_SIZE..]; + } + let j = l + ff.len(); + if j > d.len() { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + d[l..j].copy_from_slice(ff); + l = j; + } + return Ok(l); +} +/// Generate a random local key id that is currently unused. +fn generate_key_id(session_map: &HashMap>, bool)>) -> NonZeroU32 { + loop { + if let Some(local_key_id) = NonZeroU32::new(random::next_u32_secure()) { + if !session_map.contains_key(&local_key_id) { + return local_key_id; + } + } + } +} + +impl SessionKey { + #[inline(always)] + fn new( + ck: SymmetricState, + local_key_id: NonZeroU32, + remote_key_id: NonZeroU32, + current_counter: u64, + is_bob: bool, + ) -> Self { + let (b2a, a2b) = ck.split(); + let (receive_key, send_key) = if is_bob { + (&a2b, &b2a) + } else { + (&b2a, &a2b) + }; + let receive_cipher_pool = std::array::from_fn(|_| Mutex::new(AesGcm::new(receive_key.as_bytes()))); + let send_cipher_pool = std::array::from_fn(|_| Mutex::new(AesGcm::new(send_key.as_bytes()))); + Self { + local_key_id, + remote_key_id, + receive_cipher_pool, + send_cipher_pool, + rekey_at_counter: current_counter.saturating_add(Application::REKEY_AFTER_USES), + expire_at_counter: current_counter.saturating_add(Application::EXPIRE_AFTER_USES), + } + } + + #[inline(always)] + fn get_send_cipher<'a>(&'a self, counter: u64) -> Result>, SendError> { + if counter < self.expire_at_counter { + Ok(self.send_cipher_pool[(counter as usize) % self.send_cipher_pool.len()].lock().unwrap()) + } else { + Err(SendError::SessionExpired) + } + } + + #[inline(always)] + fn get_receive_cipher<'a>(&'a self, counter: u64) -> MutexGuard<'a, AesGcm> { + let idx = (counter as usize) % self.receive_cipher_pool.len(); + self.receive_cipher_pool[idx].lock().unwrap() + } +} + +/// MixHash to update 'h' during negotiation. +#[inline] +fn mix_hash(hasher: &mut SHA512, h: &[u8; NOISE_HASHLEN], m: &[u8]) -> [u8; NOISE_HASHLEN] { + hasher.reset(); + hasher.update(h); + hasher.update(m); + hasher.finish() +} +/// Check if the proof of work attached to the first message contains the correct number of leading +/// zeros. +#[inline] +fn verify_pow(hasher: &mut SHA512, message: &[u8]) -> bool { + if Application::PROOF_OF_WORK_BIT_DIFFICULTY == 0 { + return true; + } + hasher.reset(); + hasher.update(&message[NoiseXKPattern1::P_AUTH_END..NoiseXKPattern1::SIZE]); + let mut n = 0u32.to_ne_bytes(); + n.copy_from_slice(&hasher.finish()[..4]); + let n = u32::from_be_bytes(n); + n.leading_zeros() >= Application::PROOF_OF_WORK_BIT_DIFFICULTY +} +fn from_bytes_agreement(public: &[u8], private: &P384KeyPair) -> Option<(P384PublicKey, Secret<48>)> { + P384PublicKey::from_bytes(public).and_then(|e| private.agree(&e).map(|ee| (e, ee))) +} From 5a813c72099691fe56400f06bd58688522e1767c Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 12 Jul 2023 14:44:30 -0400 Subject: [PATCH 02/91] removed all errors --- Cargo.toml | 4 +- src/applicationlayer.rs | 26 +- src/crypto/Cargo.toml | 30 - src/crypto/LICENSE | 15 - src/crypto/README.md | 7 - src/crypto/aes.rs | 20 + src/crypto/aes_gcm.rs | 37 + src/crypto/build.rs | 106 - src/crypto/mod.rs | 22 + src/crypto/p384.rs | 24 + src/crypto/{src => }/secret.rs | 27 +- src/crypto/sha512.rs | 23 + src/crypto/src/aes_fruity.rs | 257 -- src/crypto/src/aes_gmac_siv_fruity.rs | 472 --- src/crypto/src/aes_gmac_siv_openssl.rs | 246 -- src/crypto/src/aes_openssl.rs | 122 - src/crypto/src/aes_tests.rs | 3677 ------------------------ src/crypto/src/cipher_ctx.rs | 172 -- src/crypto/src/constant.rs | 4 - src/crypto/src/error.rs | 348 --- src/crypto/src/hash.rs | 294 -- src/crypto/src/lib.rs | 59 - src/crypto/src/mimcvdf.rs | 141 - src/crypto/src/p384.rs | 406 --- src/crypto/src/p384_builtin.rs | 1098 ------- src/crypto/src/poly1305.rs | 48 - src/crypto/src/random.rs | 177 -- src/crypto/src/salsa.rs | 267 -- src/crypto/src/typestate.rs | 223 -- src/crypto/src/x25519.rs | 171 -- src/frag_cache.rs | 4 +- src/{utils/src => }/indexed_heap.rs | 0 src/lib.rs | 4 + src/main.rs | 333 --- src/proto.rs | 10 +- src/symmetric_state.rs | 44 +- src/zssp.rs | 499 ++-- 37 files changed, 454 insertions(+), 8963 deletions(-) delete mode 100644 src/crypto/Cargo.toml delete mode 100644 src/crypto/LICENSE delete mode 100644 src/crypto/README.md create mode 100644 src/crypto/aes.rs create mode 100644 src/crypto/aes_gcm.rs delete mode 100644 src/crypto/build.rs create mode 100644 src/crypto/mod.rs create mode 100644 src/crypto/p384.rs rename src/crypto/{src => }/secret.rs (79%) create mode 100644 src/crypto/sha512.rs delete mode 100644 src/crypto/src/aes_fruity.rs delete mode 100644 src/crypto/src/aes_gmac_siv_fruity.rs delete mode 100644 src/crypto/src/aes_gmac_siv_openssl.rs delete mode 100644 src/crypto/src/aes_openssl.rs delete mode 100644 src/crypto/src/aes_tests.rs delete mode 100644 src/crypto/src/cipher_ctx.rs delete mode 100644 src/crypto/src/constant.rs delete mode 100644 src/crypto/src/error.rs delete mode 100644 src/crypto/src/hash.rs delete mode 100644 src/crypto/src/lib.rs delete mode 100644 src/crypto/src/mimcvdf.rs delete mode 100644 src/crypto/src/p384.rs delete mode 100644 src/crypto/src/p384_builtin.rs delete mode 100644 src/crypto/src/poly1305.rs delete mode 100644 src/crypto/src/random.rs delete mode 100644 src/crypto/src/salsa.rs delete mode 100644 src/crypto/src/typestate.rs delete mode 100644 src/crypto/src/x25519.rs rename src/{utils/src => }/indexed_heap.rs (100%) delete mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 742fa91..2f37c25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,5 +17,5 @@ path = "src/main.rs" doc = false [dependencies] -pqc_kyber = { version = "0.4.0", default-features = false, features = ["kyber1024", "std"] } -hex-literal = "0.3.4" +pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber1024", "std"] } +hex-literal = "0.4.1" diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 3858423..782d002 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -8,9 +8,11 @@ use std::sync::Arc; -use zerotier_crypto::p384::{P384KeyPair, P384PublicKey}; - +use crate::crypto::aes::{AesEnc, AesDec}; +use crate::crypto::aes_gcm::{AesGcmEnc, AesGcmDec}; +use crate::crypto::sha512::{Sha512, HmacSha512}; use crate::{log_event::LogEvent, Session, RATCHET_FINGERPRINT_SIZE, RATCHET_KEY_SIZE}; +use crate::crypto::p384::{P384PublicKey, P384KeyPair}; /// Trait to implement to integrate the session into an application. /// @@ -81,6 +83,21 @@ pub trait ApplicationLayer: Sized { /// computational work as Bob will when they process Alice's initiation packet. const PROOF_OF_WORK_BIT_DIFFICULTY: u32 = 13; + + type BlockCipherEnc: AesEnc; + type BlockCipherDec: AesDec; + + type AeadEnc: AesGcmEnc; + type AeadDec: AesGcmDec; + + type Hash: Sha512; + type HmacHash: HmacSha512; + + type KeyPair: P384KeyPair; + type PublicKey: P384PublicKey; + + type Rng: CryptoRng + RngCore; + /// Type for arbitrary opaque object for use by the application that is attached to /// each session. type Data; @@ -97,8 +114,6 @@ pub trait ApplicationLayer: Sized { /// It will be dropped as soon as the session is established. type LocalIdentityBlob: AsRef<[u8]>; - /// Get this node's static key in serialized form and the P-384 static key it contains. - fn local_s_keypair(&self) -> &P384KeyPair; /// Save the given ratchet state to persistent storage. /// A ratchet state consists of a ratchet number, a ratchet fingerprint, and a ratchet key. /// @@ -129,7 +144,7 @@ pub trait ApplicationLayer: Sized { #[allow(unused)] fn save_ratchet_state( &self, - alice_s_public: &P384PublicKey, + alice_s_public: &Self::PublicKey, application_data: &Self::Data, ratchet_action: SaveRatchetAction, latest_ratchet_number: u64, @@ -224,6 +239,7 @@ pub enum SaveRatchetAction { DeletePrevious, } use SaveRatchetAction::*; +use pqc_kyber::{RngCore, CryptoRng}; impl SaveRatchetAction { /// If this is true then this is the first time the latest ratchet state has ever been seen, /// so it ought to be immediately saved. diff --git a/src/crypto/Cargo.toml b/src/crypto/Cargo.toml deleted file mode 100644 index 9a7e18a..0000000 --- a/src/crypto/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "zerotier-crypto" -authors = ["Steven Fackler "] -license = "Apache-2.0" -description = "OpenSSL bindings" -readme = "README.md" -keywords = ["crypto", "tls", "ssl", "dtls"] -categories = ["cryptography", "api-bindings"] -edition = "2021" -version = "0.1.0" - - -[dependencies] -ed25519-dalek = { version = "1.0.1", features = ["std", "u64_backend"], default-features = false } -poly1305 = { version = "0.8.0", features = [], default-features = false } -x25519-dalek = { version = "1.2.0", features = ["std", "u64_backend"], default-features = false } -cfg-if = "1.0" -foreign-types = "0.5.0" -libc = "0.2" -lazy_static = "^1" -rand_core = "0.6.4" -ctor = "^0" -#ed25519-dalek still uses rand_core 0.5.1, and that version is incompatible with 0.6.4, so we need to import and implement both. -rand_core_051 = { package = "rand_core", version = "0.5.1" } - -ffi = { package = "openssl-sys", version = "0.9.80", path = "../openssl-sys" } - -[dev-dependencies] -hex = "0.4.3" -hex-literal = "0.3.4" diff --git a/src/crypto/LICENSE b/src/crypto/LICENSE deleted file mode 100644 index f259067..0000000 --- a/src/crypto/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -Copyright 2011-2017 Google Inc. - 2013 Jack Lloyd - 2013-2014 Steven Fackler - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/src/crypto/README.md b/src/crypto/README.md deleted file mode 100644 index 3006f04..0000000 --- a/src/crypto/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# ZeroTier Cryptography Library - ------- - -Most of this library is just glue to provide a simple safe API around things like OpenSSL or OS-specific crypto APIs. - -It is very important that this library is only linked to OpenSSL versions greater than 1.1.0. 1.1.0 introduced no-hassle threadsafety which we take advantage of. If we want a version prior to 1.1.0 we will have to add conditional threadsafety code. diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs new file mode 100644 index 0000000..842e1ed --- /dev/null +++ b/src/crypto/aes.rs @@ -0,0 +1,20 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. + +pub const AES_256_BLOCK_SIZE: usize = 16; +pub const AES_256_KEY_SIZE: usize = 32; + +pub trait AesEnc: Send + Sync { + fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self; + + fn reset(&self, key: &[u8; AES_256_KEY_SIZE]); + + fn encrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]); +} + +pub trait AesDec: Send + Sync { + fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self; + + fn reset(&self, key: &[u8; AES_256_KEY_SIZE]); + + fn decrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]); +} diff --git a/src/crypto/aes_gcm.rs b/src/crypto/aes_gcm.rs new file mode 100644 index 0000000..0f97e1a --- /dev/null +++ b/src/crypto/aes_gcm.rs @@ -0,0 +1,37 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. + +pub const AES_GCM_TAG_SIZE: usize = 16; +pub const AES_GCM_IV_SIZE: usize = 12; +pub const AES_GCM_KEY_SIZE: usize = 32; + +/// Implementations of this trait does not have to be Send + Sync, +/// but if it is wrapped in a `Mutex` it must satisfy the requirements of Send + Sync. +pub trait AesGcmEnc { + fn new(key: &[u8; AES_GCM_KEY_SIZE]) -> Self; + + fn set_iv(&mut self, iv: &[u8; AES_GCM_IV_SIZE]); + + fn set_aad(&mut self, aad: &[u8]); + + fn encrypt(&mut self, input: &[u8], output: &mut [u8]); + + fn encrypt_in_place(&mut self, data: &mut [u8]); + + fn finish_encrypt(&mut self, output: &mut [u8; AES_GCM_TAG_SIZE]); +} + +/// Implementations of this trait does not have to be Send + Sync, +/// but if it is wrapped in a `Mutex` it must satisfy the requirements of Send + Sync. +pub trait AesGcmDec { + fn new(key: &[u8; AES_GCM_KEY_SIZE]) -> Self; + + fn set_iv(&mut self, iv: &[u8; AES_GCM_IV_SIZE]); + + fn set_aad(&mut self, aad: &[u8]); + + fn decrypt(&mut self, input: &[u8], output: &mut [u8]); + + fn decrypt_in_place(&mut self, data: &mut [u8]); + + fn finish_decrypt(&mut self, expected_tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; +} diff --git a/src/crypto/build.rs b/src/crypto/build.rs deleted file mode 100644 index 444b5e4..0000000 --- a/src/crypto/build.rs +++ /dev/null @@ -1,106 +0,0 @@ -#![allow(clippy::inconsistent_digit_grouping, clippy::uninlined_format_args, clippy::unusual_byte_groupings)] - -use std::env; - -fn main() { - if env::var("DEP_OPENSSL_LIBRESSL").is_ok() { - println!("cargo:rustc-cfg=libressl"); - } - - if env::var("CARGO_FEATURE_UNSTABLE_BORINGSSL").is_ok() { - println!("cargo:rustc-cfg=boringssl"); - return; - } - - if let Ok(v) = env::var("DEP_OPENSSL_LIBRESSL_VERSION") { - println!("cargo:rustc-cfg=libressl{}", v); - } - - if let Ok(vars) = env::var("DEP_OPENSSL_CONF") { - for var in vars.split(',') { - println!("cargo:rustc-cfg=osslconf=\"{}\"", var); - } - } - - if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") { - let version = u64::from_str_radix(&version, 16).unwrap(); - - if version >= 0x1_00_01_00_0 { - println!("cargo:rustc-cfg=ossl101"); - } - if version >= 0x1_00_02_00_0 { - println!("cargo:rustc-cfg=ossl102"); - } - if version >= 0x1_01_00_00_0 { - println!("cargo:rustc-cfg=ossl110"); - } - if version >= 0x1_01_00_07_0 { - println!("cargo:rustc-cfg=ossl110g"); - } - if version >= 0x1_01_00_08_0 { - println!("cargo:rustc-cfg=ossl110h"); - } - if version >= 0x1_01_01_00_0 { - println!("cargo:rustc-cfg=ossl111"); - } - if version >= 0x3_00_00_00_0 { - println!("cargo:rustc-cfg=ossl300"); - } - } - - if let Ok(version) = env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER") { - let version = u64::from_str_radix(&version, 16).unwrap(); - - if version >= 0x2_05_01_00_0 { - println!("cargo:rustc-cfg=libressl251"); - } - - if version >= 0x2_06_01_00_0 { - println!("cargo:rustc-cfg=libressl261"); - } - - if version >= 0x2_07_00_00_0 { - println!("cargo:rustc-cfg=libressl270"); - } - - if version >= 0x2_07_01_00_0 { - println!("cargo:rustc-cfg=libressl271"); - } - - if version >= 0x2_07_03_00_0 { - println!("cargo:rustc-cfg=libressl273"); - } - - if version >= 0x2_08_00_00_0 { - println!("cargo:rustc-cfg=libressl280"); - } - - if version >= 0x2_09_01_00_0 { - println!("cargo:rustc-cfg=libressl291"); - } - - if version >= 0x3_02_01_00_0 { - println!("cargo:rustc-cfg=libressl321"); - } - - if version >= 0x3_03_02_00_0 { - println!("cargo:rustc-cfg=libressl332"); - } - - if version >= 0x3_04_00_00_0 { - println!("cargo:rustc-cfg=libressl340"); - } - - if version >= 0x3_05_00_00_0 { - println!("cargo:rustc-cfg=libressl350"); - } - - if version >= 0x3_06_00_00_0 { - println!("cargo:rustc-cfg=libressl360"); - } - - if version >= 0x3_06_01_00_0 { - println!("cargo:rustc-cfg=libressl361"); - } - } -} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs new file mode 100644 index 0000000..71af726 --- /dev/null +++ b/src/crypto/mod.rs @@ -0,0 +1,22 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. + +pub mod aes; +pub mod aes_gcm; +pub mod sha512; +pub mod p384; +pub mod secret; + +/// Constant time byte slice equality. +#[inline] +pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { + let (a, b) = (a.as_ref(), b.as_ref()); + if a.len() == b.len() { + let mut x = 0u8; + for (aa, bb) in a.iter().zip(b.iter()) { + x |= *aa ^ *bb; + } + x == 0 + } else { + false + } +} diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs new file mode 100644 index 0000000..c7ca236 --- /dev/null +++ b/src/crypto/p384.rs @@ -0,0 +1,24 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. + +pub const P384_PUBLIC_KEY_SIZE: usize = 49; +pub const P384_ECDH_SHARED_SECRET_SIZE: usize = 48; + +/// A NIST P-384 ECDH/ECDSA public key. +pub trait P384PublicKey: Sized + Send + Sync { + /// Create a p384 public key from raw bytes. + fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option; + + fn as_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE]; +} + +/// A NIST P-384 ECDH/ECDSA public/private key pair. +pub trait P384KeyPair: Send + Sync { + /// Randomly generate a new p384 keypair. + fn generate() -> Self; + + /// Get the raw bytes that uniquely define the public key. + fn public_key_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE]; + + /// Perform ECDH key agreement, returning the raw (un-hashed!) ECDH secret. + fn agree(&self, other_public: &impl P384PublicKey, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool; +} diff --git a/src/crypto/src/secret.rs b/src/crypto/secret.rs similarity index 79% rename from src/crypto/src/secret.rs rename to src/crypto/secret.rs index f985356..fc03a40 100644 --- a/src/crypto/src/secret.rs +++ b/src/crypto/secret.rs @@ -1,10 +1,6 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. -use std::{convert::TryInto, ffi::c_void}; - -extern "C" { - fn OPENSSL_cleanse(ptr: *mut c_void, len: usize); -} +use std::convert::TryInto; /// Container for secrets that clears them on drop. /// @@ -36,7 +32,7 @@ impl Secret { #[inline(always)] pub fn from_bytes_then_nuke(b: &mut [u8]) -> Self { let ret = Self(b.try_into().unwrap()); - unsafe { OPENSSL_cleanse(b.as_mut_ptr().cast(), L) }; + b.fill(0); ret } #[inline(always)] @@ -44,20 +40,11 @@ impl Secret { Self(b.try_into().unwrap()) } - #[inline(always)] - pub fn as_bytes(&self) -> &[u8; L] { - &self.0 - } #[inline(always)] pub fn as_ptr(&self) -> *const u8 { self.0.as_ptr() } - #[inline(always)] - pub fn as_bytes_mut(&mut self) -> &mut [u8; L] { - &mut self.0 - } - /// Get the first N bytes of this secret as a fixed length array. #[inline(always)] pub fn first_n(&self) -> &[u8; N] { @@ -78,20 +65,12 @@ impl Secret { let amount = N.min(L); self.0[..amount].copy_from_slice(&src.0[..amount]); } - - /// Destroy the contents of this secret, ignoring normal Rust mutability constraints. - /// - /// This can be used to force a secret to be forgotten under e.g. key lifetime exceeded or error conditions. - #[inline(always)] - pub fn nuke(&self) { - unsafe { OPENSSL_cleanse(self.0.as_ptr().cast_mut().cast(), L) }; - } } impl Drop for Secret { #[inline(always)] fn drop(&mut self) { - unsafe { OPENSSL_cleanse(self.0.as_mut_ptr().cast(), L) }; + self.0.fill(0); } } diff --git a/src/crypto/sha512.rs b/src/crypto/sha512.rs new file mode 100644 index 0000000..d477762 --- /dev/null +++ b/src/crypto/sha512.rs @@ -0,0 +1,23 @@ +// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. + +pub const SHA512_HASH_SIZE: usize = 64; + +pub trait Sha512 { + fn new() -> Self; + + fn reset(&mut self); + + fn update(&mut self, input: &[u8]); + + fn finish(&mut self, output: &mut [u8; SHA512_HASH_SIZE]); +} + +pub trait HmacSha512 { + fn new(key: &[u8]) -> Self; + + fn reset(&mut self, key: &[u8]); + + fn update(&mut self, input: &[u8]); + + fn finish(&mut self, output: &mut [u8; SHA512_HASH_SIZE]); +} diff --git a/src/crypto/src/aes_fruity.rs b/src/crypto/src/aes_fruity.rs deleted file mode 100644 index 3ac9885..0000000 --- a/src/crypto/src/aes_fruity.rs +++ /dev/null @@ -1,257 +0,0 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. - -// MacOS implementation of AES primitives since CommonCrypto seems to be faster than OpenSSL, especially on ARM64. -use std::os::raw::{c_int, c_void}; -use std::ptr::{null, null_mut}; -use std::sync::Mutex; - -use crate::constant::*; -use crate::secure_eq; - -#[allow(non_upper_case_globals, unused)] -const kCCModeECB: i32 = 1; -#[allow(non_upper_case_globals, unused)] -const kCCModeCTR: i32 = 4; -#[allow(non_upper_case_globals, unused)] -const kCCModeGCM: i32 = 11; -#[allow(non_upper_case_globals, unused)] -const kCCEncrypt: i32 = 0; -#[allow(non_upper_case_globals, unused)] -const kCCDecrypt: i32 = 1; -#[allow(non_upper_case_globals, unused)] -const kCCAlgorithmAES: i32 = 0; -#[allow(non_upper_case_globals, unused)] -const kCCOptionECBMode: i32 = 2; - -extern "C" { - fn CCCryptorCreateWithMode( - op: i32, - mode: i32, - alg: i32, - padding: i32, - iv: *const c_void, - key: *const c_void, - key_len: usize, - tweak: *const c_void, - tweak_len: usize, - num_rounds: c_int, - options: i32, - cryyptor_ref: *mut *mut c_void, - ) -> i32; - fn CCCryptorUpdate( - cryptor_ref: *mut c_void, - data_in: *const c_void, - data_in_len: usize, - data_out: *mut c_void, - data_out_len: usize, - data_out_written: *mut usize, - ) -> i32; - //fn CCCryptorReset(cryptor_ref: *mut c_void, iv: *const c_void) -> i32; - fn CCCryptorRelease(cryptor_ref: *mut c_void) -> i32; - fn CCCryptorGCMSetIV(cryptor_ref: *mut c_void, iv: *const c_void, iv_len: usize) -> i32; - fn CCCryptorGCMAddAAD(cryptor_ref: *mut c_void, aad: *const c_void, len: usize) -> i32; - fn CCCryptorGCMEncrypt(cryptor_ref: *mut c_void, data_in: *const c_void, data_in_len: usize, data_out: *mut c_void) -> i32; - fn CCCryptorGCMDecrypt(cryptor_ref: *mut c_void, data_in: *const c_void, data_in_len: usize, data_out: *mut c_void) -> i32; - fn CCCryptorGCMFinal(cryptor_ref: *mut c_void, tag: *mut c_void, tag_len: *mut usize) -> i32; - fn CCCryptorGCMReset(cryptor_ref: *mut c_void) -> i32; -} - -pub struct AesGcm(*mut c_void); - -impl Drop for AesGcm { - #[inline(always)] - fn drop(&mut self) { - unsafe { CCCryptorRelease(self.0) }; - } -} - -impl AesGcm { - pub fn new(k: &[u8; AES_256_KEY_SIZE]) -> Self { - unsafe { - let mut ptr: *mut c_void = null_mut(); - assert_eq!( - CCCryptorCreateWithMode( - if ENCRYPT { - kCCEncrypt - } else { - kCCDecrypt - }, - kCCModeGCM, - kCCAlgorithmAES, - 0, - null(), - k.as_ptr().cast(), - AES_256_KEY_SIZE, - null(), - 0, - 0, - 0, - &mut ptr, - ), - 0 - ); - AesGcm(ptr) - } - } - - #[inline(always)] - pub fn reset_init_gcm(&mut self, iv: &[u8]) { - assert_eq!(iv.len(), AES_GCM_NONCE_SIZE); - unsafe { - assert_eq!(CCCryptorGCMReset(self.0), 0); - assert_eq!(CCCryptorGCMSetIV(self.0, iv.as_ptr().cast(), AES_GCM_NONCE_SIZE), 0); - } - } - - #[inline(always)] - pub fn aad(&mut self, aad: &[u8]) { - unsafe { - assert_eq!(CCCryptorGCMAddAAD(self.0, aad.as_ptr().cast(), aad.len()), 0); - } - } - - #[inline(always)] - pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) { - unsafe { - assert_eq!(input.len(), output.len()); - if ENCRYPT { - assert_eq!( - CCCryptorGCMEncrypt(self.0, input.as_ptr().cast(), input.len(), output.as_mut_ptr().cast()), - 0 - ); - } else { - assert_eq!( - CCCryptorGCMDecrypt(self.0, input.as_ptr().cast(), input.len(), output.as_mut_ptr().cast()), - 0 - ); - } - } - } - - #[inline(always)] - pub fn crypt_in_place(&mut self, data: &mut [u8]) { - unsafe { - if ENCRYPT { - assert_eq!(CCCryptorGCMEncrypt(self.0, data.as_ptr().cast(), data.len(), data.as_mut_ptr().cast()), 0); - } else { - assert_eq!(CCCryptorGCMDecrypt(self.0, data.as_ptr().cast(), data.len(), data.as_mut_ptr().cast()), 0); - } - } - } - - #[inline(always)] - fn finish(&mut self) -> [u8; AES_GCM_TAG_SIZE] { - let mut tag = 0_u128.to_ne_bytes(); - unsafe { - let mut tag_len = AES_GCM_TAG_SIZE; - if CCCryptorGCMFinal(self.0, tag.as_mut_ptr().cast(), &mut tag_len) != 0 { - debug_assert!(false); - tag.fill(0); - } - } - tag - } -} - -impl AesGcm { - /// Produce the gcm authentication tag. - #[inline(always)] - pub fn finish_encrypt(&mut self) -> [u8; AES_GCM_TAG_SIZE] { - self.finish() - } -} -impl AesGcm { - /// Check the gcm authentication tag. Outputs true if it matches the just decrypted message, outputs false otherwise. - #[inline(always)] - pub fn finish_decrypt(&mut self, expected_tag: &[u8]) -> bool { - secure_eq(&self.finish(), expected_tag) - } -} - -pub struct Aes(Mutex<*mut c_void>); -unsafe impl Send for Aes {} -unsafe impl Sync for Aes {} - -impl Drop for Aes { - #[inline(always)] - fn drop(&mut self) { - let p = self.0.lock().unwrap(); - unsafe { - CCCryptorRelease(*p); - } - } -} - -impl Aes { - pub fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self { - unsafe { - let mut p = null_mut(); - assert_eq!( - CCCryptorCreateWithMode( - if ENCRYPT { - kCCEncrypt - } else { - kCCDecrypt - }, - kCCModeECB, - kCCAlgorithmAES, - 0, - null(), - key.as_ptr().cast(), - AES_256_KEY_SIZE, - null(), - 0, - 0, - kCCOptionECBMode, - &mut p, - ), - 0 - ); - Self(Mutex::new(p)) - } - } - pub fn reset(&self, key: &[u8; AES_256_KEY_SIZE]) { - let mut p = self.0.lock().unwrap(); - unsafe { - CCCryptorRelease(*p); - assert_eq!( - CCCryptorCreateWithMode( - if ENCRYPT { - kCCEncrypt - } else { - kCCDecrypt - }, - kCCModeECB, - kCCAlgorithmAES, - 0, - null(), - key.as_ptr().cast(), - AES_256_KEY_SIZE, - null(), - 0, - 0, - kCCOptionECBMode, - &mut *p, - ), - 0 - ); - } - } - - #[inline(always)] - pub fn crypt_block_in_place(&self, data: &mut [u8]) { - assert_eq!(data.len(), AES_BLOCK_SIZE); - unsafe { - let mut data_out_written = 0; - let p = self.0.lock().unwrap(); - CCCryptorUpdate( - *p, - data.as_ptr().cast(), - AES_BLOCK_SIZE, - data.as_mut_ptr().cast(), - AES_BLOCK_SIZE, - &mut data_out_written, - ); - } - } -} diff --git a/src/crypto/src/aes_gmac_siv_fruity.rs b/src/crypto/src/aes_gmac_siv_fruity.rs deleted file mode 100644 index 3a09f5d..0000000 --- a/src/crypto/src/aes_gmac_siv_fruity.rs +++ /dev/null @@ -1,472 +0,0 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. - -// AES-GMAC-SIV implemented using MacOS/iOS CommonCrypto (MacOS 10.13 or newer required). - -use std::os::raw::{c_int, c_void}; -use std::ptr::{null, null_mut}; - -#[allow(non_upper_case_globals)] -const kCCModeECB: i32 = 1; -#[allow(non_upper_case_globals)] -const kCCModeCTR: i32 = 4; -#[allow(non_upper_case_globals)] -const kCCModeGCM: i32 = 11; -#[allow(non_upper_case_globals)] -const kCCEncrypt: i32 = 0; -#[allow(non_upper_case_globals)] -const kCCDecrypt: i32 = 1; -#[allow(non_upper_case_globals)] -const kCCAlgorithmAES: i32 = 0; -#[allow(non_upper_case_globals)] -const kCCOptionECBMode: i32 = 2; - -extern "C" { - fn CCCryptorCreateWithMode( - op: i32, - mode: i32, - alg: i32, - padding: i32, - iv: *const c_void, - key: *const c_void, - key_len: usize, - tweak: *const c_void, - tweak_len: usize, - num_rounds: c_int, - options: i32, - cryyptor_ref: *mut *mut c_void, - ) -> i32; - fn CCCryptorUpdate( - cryptor_ref: *mut c_void, - data_in: *const c_void, - data_in_len: usize, - data_out: *mut c_void, - data_out_len: usize, - data_out_written: *mut usize, - ) -> i32; - fn CCCryptorReset(cryptor_ref: *mut c_void, iv: *const c_void) -> i32; - fn CCCryptorRelease(cryptor_ref: *mut c_void) -> i32; - fn CCCryptorGCMSetIV(cryptor_ref: *mut c_void, iv: *const c_void, iv_len: usize) -> i32; - fn CCCryptorGCMAddAAD(cryptor_ref: *mut c_void, aad: *const c_void, len: usize) -> i32; - fn CCCryptorGCMFinalize(cryptor_ref: *mut c_void, tag: *mut c_void, tag_len: usize) -> i32; - fn CCCryptorGCMReset(cryptor_ref: *mut c_void) -> i32; -} - -pub struct AesCtr(*mut c_void); - -impl Drop for AesCtr { - fn drop(&mut self) { - if !self.0.is_null() { - unsafe { - CCCryptorRelease(self.0); - } - } - } -} - -impl AesCtr { - /// Construct a new AES-CTR cipher. - /// Key must be 16, 24, or 32 bytes in length or a panic will occur. - pub fn new(k: &[u8]) -> Self { - if k.len() != 32 && k.len() != 24 && k.len() != 16 { - panic!("AES supports 128, 192, or 256 bits keys"); - } - unsafe { - let mut ptr: *mut c_void = null_mut(); - let result = CCCryptorCreateWithMode( - kCCEncrypt, - kCCModeCTR, - kCCAlgorithmAES, - 0, - crate::ZEROES.as_ptr().cast(), - k.as_ptr().cast(), - k.len(), - null(), - 0, - 0, - 0, - &mut ptr, - ); - if result != 0 { - panic!("CCCryptorCreateWithMode for CTR mode returned {}", result); - } - AesCtr(ptr) - } - } - - /// Initialize AES-CTR for encryption or decryption with the given IV. - /// If it's already been used, this also resets the cipher. There is no separate reset. - pub fn init(&mut self, iv: &[u8]) { - unsafe { - if iv.len() == 16 { - if CCCryptorReset(self.0, iv.as_ptr().cast()) != 0 { - panic!("CCCryptorReset for CTR mode failed (old MacOS bug)"); - } - } else if iv.len() < 16 { - let mut iv2 = [0_u8; 16]; - iv2[0..iv.len()].copy_from_slice(iv); - if CCCryptorReset(self.0, iv2.as_ptr().cast()) != 0 { - panic!("CCCryptorReset for CTR mode failed (old MacOS bug)"); - } - } else { - panic!("CTR IV must be less than or equal to 16 bytes in length"); - } - } - } - - /// Encrypt or decrypt (same operation with CTR mode) - #[inline(always)] - pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) { - unsafe { - assert!(output.len() >= input.len()); - let mut data_out_written: usize = 0; - CCCryptorUpdate( - self.0, - input.as_ptr().cast(), - input.len(), - output.as_mut_ptr().cast(), - output.len(), - &mut data_out_written, - ); - } - } - - /// Encrypt or decrypt in place (same operation with CTR mode) - #[inline(always)] - pub fn crypt_in_place(&mut self, data: &mut [u8]) { - unsafe { - let mut data_out_written: usize = 0; - CCCryptorUpdate( - self.0, - data.as_ptr().cast(), - data.len(), - data.as_mut_ptr().cast(), - data.len(), - &mut data_out_written, - ); - } - } -} - -unsafe impl Send for AesCtr {} - -#[repr(align(8))] -pub struct AesGmacSiv { - tag: [u8; 16], - tmp: [u8; 16], - ctr: *mut c_void, - ecb_enc: *mut c_void, - ecb_dec: *mut c_void, - gmac: *mut c_void, -} - -impl Drop for AesGmacSiv { - fn drop(&mut self) { - unsafe { - if !self.ctr.is_null() { - CCCryptorRelease(self.ctr); - } - if !self.ecb_enc.is_null() { - CCCryptorRelease(self.ecb_enc); - } - if !self.ecb_dec.is_null() { - CCCryptorRelease(self.ecb_dec); - } - if !self.gmac.is_null() { - CCCryptorRelease(self.gmac); - } - } - } -} - -impl AesGmacSiv { - /// Create a new keyed instance of AES-GMAC-SIV - /// The key may be of size 16, 24, or 32 bytes (128, 192, or 256 bits). Any other size will panic. - /// Two keys are required: one for GMAC and one for AES-CTR. - pub fn new(k0: &[u8], k1: &[u8]) -> Self { - if k0.len() != 32 && k0.len() != 24 && k0.len() != 16 { - panic!("AES supports 128, 192, or 256 bits keys"); - } - if k1.len() != k0.len() { - panic!("k0 and k1 must be of the same size"); - } - let mut c: AesGmacSiv = AesGmacSiv { - tag: [0_u8; 16], - tmp: [0_u8; 16], - ctr: null_mut(), - ecb_enc: null_mut(), - ecb_dec: null_mut(), - gmac: null_mut(), - }; - unsafe { - let result = CCCryptorCreateWithMode( - kCCEncrypt, - kCCModeCTR, - kCCAlgorithmAES, - 0, - crate::ZEROES.as_ptr().cast(), - k1.as_ptr().cast(), - k1.len(), - null(), - 0, - 0, - 0, - &mut c.ctr, - ); - if result != 0 { - panic!("CCCryptorCreateWithMode for CTR mode returned {}", result); - } - let result = CCCryptorCreateWithMode( - kCCEncrypt, - kCCModeECB, - kCCAlgorithmAES, - 0, - crate::ZEROES.as_ptr().cast(), - k1.as_ptr().cast(), - k1.len(), - null(), - 0, - 0, - kCCOptionECBMode, - &mut c.ecb_enc, - ); - if result != 0 { - panic!("CCCryptorCreateWithMode for ECB encrypt mode returned {}", result); - } - let result = CCCryptorCreateWithMode( - kCCDecrypt, - kCCModeECB, - kCCAlgorithmAES, - 0, - crate::ZEROES.as_ptr().cast(), - k1.as_ptr().cast(), - k1.len(), - null(), - 0, - 0, - kCCOptionECBMode, - &mut c.ecb_dec, - ); - if result != 0 { - panic!("CCCryptorCreateWithMode for ECB decrypt mode returned {}", result); - } - let result = CCCryptorCreateWithMode( - kCCEncrypt, - kCCModeGCM, - kCCAlgorithmAES, - 0, - crate::ZEROES.as_ptr().cast(), - k0.as_ptr().cast(), - k0.len(), - null(), - 0, - 0, - 0, - &mut c.gmac, - ); - if result != 0 { - panic!("CCCryptorCreateWithMode for GCM (GMAC) mode returned {}", result); - } - } - c - } - - /// Reset to prepare for another encrypt or decrypt operation. - #[inline(always)] - pub fn reset(&mut self) { - unsafe { - CCCryptorGCMReset(self.gmac); - } - } - - /// Initialize for encryption. - #[inline(always)] - pub fn encrypt_init(&mut self, iv: &[u8]) { - self.tag[0..8].copy_from_slice(iv); - self.tag[8..12].fill(0); - unsafe { - CCCryptorGCMSetIV(self.gmac, self.tag.as_ptr().cast(), 12); - } - } - - /// Set additional authenticated data (data to be authenticated but not encrypted). - /// This can currently only be called once. Multiple calls will result in corrupt data. - #[inline(always)] - pub fn encrypt_set_aad(&mut self, data: &[u8]) { - unsafe { - CCCryptorGCMAddAAD(self.gmac, data.as_ptr().cast(), data.len()); - } - let pad = data.len() & 0xf; - if pad != 0 { - unsafe { - CCCryptorGCMAddAAD(self.gmac, crate::ZEROES.as_ptr().cast(), 16 - pad); - } - } - } - - /// Feed plaintext in for the first encryption pass. - /// This may be called more than once. - #[inline(always)] - pub fn encrypt_first_pass(&mut self, plaintext: &[u8]) { - unsafe { - CCCryptorGCMAddAAD(self.gmac, plaintext.as_ptr().cast(), plaintext.len()); - } - } - - /// Finish first pass and begin second pass. - #[inline(always)] - pub fn encrypt_first_pass_finish(&mut self) { - unsafe { - CCCryptorGCMFinalize(self.gmac, self.tmp.as_mut_ptr().cast(), 16); - let tmp = self.tmp.as_mut_ptr().cast::(); - *self.tag.as_mut_ptr().cast::().offset(1) = *tmp ^ *tmp.offset(1); - let mut data_out_written: usize = 0; - CCCryptorUpdate( - self.ecb_enc, - self.tag.as_ptr().cast(), - 16, - self.tag.as_mut_ptr().cast(), - 16, - &mut data_out_written, - ); - } - self.tmp.copy_from_slice(&self.tag); - self.tmp[12] &= 0x7f; - unsafe { - if CCCryptorReset(self.ctr, self.tmp.as_ptr().cast()) != 0 { - panic!("CCCryptorReset for CTR mode failed (old MacOS bug)"); - } - } - } - - /// Feed plaintext for second pass and write ciphertext to supplied buffer. - /// This may be called more than once. - #[inline(always)] - pub fn encrypt_second_pass(&mut self, plaintext: &[u8], ciphertext: &mut [u8]) { - unsafe { - assert!(ciphertext.len() >= plaintext.len()); - let mut data_out_written: usize = 0; - CCCryptorUpdate( - self.ctr, - plaintext.as_ptr().cast(), - plaintext.len(), - ciphertext.as_mut_ptr().cast(), - ciphertext.len(), - &mut data_out_written, - ); - } - } - - /// Encrypt plaintext in place. - /// This may be called more than once. - #[inline(always)] - pub fn encrypt_second_pass_in_place(&mut self, plaintext_to_ciphertext: &mut [u8]) { - unsafe { - let mut data_out_written: usize = 0; - CCCryptorUpdate( - self.ctr, - plaintext_to_ciphertext.as_ptr().cast(), - plaintext_to_ciphertext.len(), - plaintext_to_ciphertext.as_mut_ptr().cast(), - plaintext_to_ciphertext.len(), - &mut data_out_written, - ); - } - } - - /// Finish second pass and return a reference to the tag for this message. - /// The tag returned remains valid until reset() is called. - #[inline(always)] - pub fn encrypt_second_pass_finish(&mut self) -> &[u8; 16] { - return &self.tag; - } - - #[inline(always)] - fn decrypt_init_internal(&mut self) { - self.tmp[12] &= 0x7f; - unsafe { - if CCCryptorReset(self.ctr, self.tmp.as_ptr().cast()) != 0 { - panic!("CCCryptorReset for CTR mode failed (old MacOS bug)"); - } - let mut data_out_written = 0; - CCCryptorUpdate( - self.ecb_dec, - self.tag.as_ptr().cast(), - 16, - self.tag.as_mut_ptr().cast(), - 16, - &mut data_out_written, - ); - let tmp = self.tmp.as_mut_ptr().cast::(); - *tmp = *self.tag.as_mut_ptr().cast::(); - *tmp.add(1) = 0; - CCCryptorGCMSetIV(self.gmac, self.tmp.as_ptr().cast(), 12); - } - } - - /// Initialize this cipher for decryption. - /// The supplied tag must be 16 bytes in length. Any other length will panic. - #[inline(always)] - pub fn decrypt_init(&mut self, tag: &[u8]) { - self.tmp.copy_from_slice(tag); - self.tag.copy_from_slice(tag); - self.decrypt_init_internal(); - } - - /// Set additional authenticated data to be checked. - #[inline(always)] - pub fn decrypt_set_aad(&mut self, data: &[u8]) { - self.encrypt_set_aad(data); - } - - /// Decrypt ciphertext and write to plaintext. - /// This may be called more than once. - #[inline(always)] - pub fn decrypt(&mut self, ciphertext: &[u8], plaintext: &mut [u8]) { - unsafe { - let mut data_out_written = 0; - CCCryptorUpdate( - self.ctr, - ciphertext.as_ptr().cast(), - ciphertext.len(), - plaintext.as_mut_ptr().cast(), - plaintext.len(), - &mut data_out_written, - ); - CCCryptorGCMAddAAD(self.gmac, plaintext.as_ptr().cast(), plaintext.len()); - } - } - - /// Decrypt ciphertext in place. - /// This may be called more than once. - #[inline(always)] - pub fn decrypt_in_place(&mut self, ciphertext_to_plaintext: &mut [u8]) { - unsafe { - let mut data_out_written = 0; - CCCryptorUpdate( - self.ctr, - ciphertext_to_plaintext.as_ptr().cast(), - ciphertext_to_plaintext.len(), - ciphertext_to_plaintext.as_mut_ptr().cast(), - ciphertext_to_plaintext.len(), - &mut data_out_written, - ); - CCCryptorGCMAddAAD(self.gmac, ciphertext_to_plaintext.as_ptr().cast(), ciphertext_to_plaintext.len()); - } - } - - /// Finish decryption and returns the decrypted tag if the message appears valid. - #[inline(always)] - pub fn decrypt_finish(&mut self) -> Option<&[u8; 16]> { - unsafe { - CCCryptorGCMFinalize(self.gmac, self.tmp.as_mut_ptr().cast(), 16); - let tmp = self.tmp.as_mut_ptr().cast::(); - if *self.tag.as_mut_ptr().cast::().offset(1) == *tmp ^ *tmp.offset(1) { - Some(&self.tag) - } else { - None - } - } - } -} - -unsafe impl Send for AesGmacSiv {} diff --git a/src/crypto/src/aes_gmac_siv_openssl.rs b/src/crypto/src/aes_gmac_siv_openssl.rs deleted file mode 100644 index 2771ecc..0000000 --- a/src/crypto/src/aes_gmac_siv_openssl.rs +++ /dev/null @@ -1,246 +0,0 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. -use std::ptr; - -use crate::{cipher_ctx::CipherCtx, ZEROES}; - -/// AES-GMAC-SIV encryptor/decryptor. -pub struct AesGmacSiv { - tag: [u8; 16], - tmp: [u8; 16], - ecb_enc: CipherCtx, - ecb_dec: CipherCtx, - ctr: CipherCtx, - gmac: CipherCtx, -} - -impl AesGmacSiv { - /// Create a new keyed instance of AES-GMAC-SIV - /// The key may be of size 16, 24, or 32 bytes (128, 192, or 256 bits). Any other size will panic. - pub fn new(k0: &[u8], k1: &[u8]) -> Self { - let gmac = CipherCtx::new().unwrap(); - unsafe { - let t = match k0.len() { - 16 => ffi::EVP_aes_128_gcm(), - 24 => ffi::EVP_aes_192_gcm(), - 32 => ffi::EVP_aes_256_gcm(), - _ => panic!("Aes KEY_SIZE must be 16, 24 or 32"), - }; - gmac.cipher_init::(t, k0.as_ptr(), ptr::null_mut()).unwrap(); - } - let ctr = CipherCtx::new().unwrap(); - unsafe { - let t = match k1.len() { - 16 => ffi::EVP_aes_128_ctr(), - 24 => ffi::EVP_aes_192_ctr(), - 32 => ffi::EVP_aes_256_ctr(), - _ => panic!("Aes KEY_SIZE must be 16, 24 or 32"), - }; - ctr.cipher_init::(t, k1.as_ptr(), ptr::null_mut()).unwrap(); - } - let ecb_enc = CipherCtx::new().unwrap(); - unsafe { - let t = match k1.len() { - 16 => ffi::EVP_aes_128_ecb(), - 24 => ffi::EVP_aes_192_ecb(), - 32 => ffi::EVP_aes_256_ecb(), - _ => panic!("Aes KEY_SIZE must be 16, 24 or 32"), - }; - ecb_enc.cipher_init::(t, k1.as_ptr(), ptr::null_mut()).unwrap(); - ffi::EVP_CIPHER_CTX_set_padding(ecb_enc.as_ptr(), 0); - } - let ecb_dec = CipherCtx::new().unwrap(); - unsafe { - let t = match k1.len() { - 16 => ffi::EVP_aes_128_ecb(), - 24 => ffi::EVP_aes_192_ecb(), - 32 => ffi::EVP_aes_256_ecb(), - _ => panic!("Aes KEY_SIZE must be 16, 24 or 32"), - }; - ecb_dec.cipher_init::(t, k1.as_ptr(), ptr::null_mut()).unwrap(); - ffi::EVP_CIPHER_CTX_set_padding(ecb_dec.as_ptr(), 0); - } - - AesGmacSiv { - tag: [0_u8; 16], - tmp: [0_u8; 16], - ecb_dec, - ecb_enc, - ctr, - gmac, - } - } - - /// Reset to prepare for another encrypt or decrypt operation. - #[inline(always)] - pub fn reset(&mut self) {} - - /// Initialize for encryption. - #[inline(always)] - pub fn encrypt_init(&mut self, iv: &[u8]) { - self.tag[0..8].copy_from_slice(iv); - self.tag[8..12].fill(0); - unsafe { - self.gmac - .cipher_init::(ptr::null_mut(), ptr::null_mut(), self.tag[0..12].as_ptr()) - .unwrap(); - } - } - - /// Set additional authenticated data (data to be authenticated but not encrypted). - /// This can currently only be called once. Multiple calls will result in corrupt data. - #[inline(always)] - pub fn encrypt_set_aad(&mut self, data: &[u8]) { - unsafe { - self.gmac.update::(data, ptr::null_mut()).unwrap(); - let mut pad = data.len() & 0xf; - if pad != 0 { - pad = 16 - pad; - self.gmac.update::(&ZEROES[0..pad], ptr::null_mut()).unwrap(); - } - } - } - - /// Feed plaintext in for the first encryption pass. - /// This may be called more than once. - #[inline(always)] - pub fn encrypt_first_pass(&mut self, plaintext: &[u8]) { - unsafe { - self.gmac.update::(plaintext, ptr::null_mut()).unwrap(); - } - } - - /// Finish first pass and begin second pass. - #[inline(always)] - pub fn encrypt_first_pass_finish(&mut self) { - unsafe { - self.gmac.finalize::(ptr::null_mut()).unwrap(); - self.gmac.tag(&mut self.tmp).unwrap(); - } - - self.tag[8] = self.tmp[0] ^ self.tmp[8]; - self.tag[9] = self.tmp[1] ^ self.tmp[9]; - self.tag[10] = self.tmp[2] ^ self.tmp[10]; - self.tag[11] = self.tmp[3] ^ self.tmp[11]; - self.tag[12] = self.tmp[4] ^ self.tmp[12]; - self.tag[13] = self.tmp[5] ^ self.tmp[13]; - self.tag[14] = self.tmp[6] ^ self.tmp[14]; - self.tag[15] = self.tmp[7] ^ self.tmp[15]; - - let mut tag_tmp = [0_u8; 16]; - - unsafe { - self.ecb_enc.update::(&self.tag, tag_tmp.as_mut_ptr()).unwrap(); - } - self.tag.copy_from_slice(&tag_tmp); - self.tmp.copy_from_slice(&tag_tmp); - - self.tmp[12] &= 0x7f; - - unsafe { - self.ctr.cipher_init::(ptr::null_mut(), ptr::null_mut(), self.tmp.as_ptr()).unwrap(); - } - } - - /// Feed plaintext for second pass and write ciphertext to supplied buffer. - /// This may be called more than once. - #[inline(always)] - pub fn encrypt_second_pass(&mut self, plaintext: &[u8], ciphertext: &mut [u8]) { - unsafe { - self.ctr.update::(plaintext, ciphertext.as_mut_ptr()).unwrap(); - } - } - - /// Encrypt plaintext in place. - /// This may be called more than once. - #[inline(always)] - pub fn encrypt_second_pass_in_place(&mut self, plaintext_to_ciphertext: &mut [u8]) { - unsafe { - let out = plaintext_to_ciphertext.as_mut_ptr(); - self.ctr.update::(plaintext_to_ciphertext, out).unwrap(); - } - } - - /// Finish second pass and return a reference to the tag for this message. - /// The tag returned remains valid until reset() is called. - #[inline(always)] - pub fn encrypt_second_pass_finish(&mut self) -> &[u8; 16] { - &self.tag - } - - /// Initialize this cipher for decryption. - /// The supplied tag must be 16 bytes in length. Any other length will panic. - #[inline(always)] - pub fn decrypt_init(&mut self, tag: &[u8]) { - self.tmp.copy_from_slice(tag); - self.tmp[12] &= 0x7f; - - unsafe { - self.ctr - .cipher_init::(ptr::null_mut(), ptr::null_mut(), self.tmp.as_ptr()) - .unwrap(); - } - - let mut tag_tmp = [0_u8; 16]; - - unsafe { - self.ecb_dec.update::(tag, tag_tmp.as_mut_ptr()).unwrap(); - } - self.tag.copy_from_slice(&tag_tmp); - tag_tmp[8..12].fill(0); - - unsafe { - self.gmac.cipher_init::(ptr::null_mut(), ptr::null_mut(), tag_tmp.as_ptr()).unwrap(); - } - } - - /// Set additional authenticated data to be checked. - #[inline(always)] - pub fn decrypt_set_aad(&mut self, data: &[u8]) { - self.encrypt_set_aad(data); - } - - /// Decrypt ciphertext and write to plaintext. - /// This may be called more than once. - #[inline(always)] - pub fn decrypt(&mut self, ciphertext: &[u8], plaintext: &mut [u8]) { - unsafe { - self.ctr.update::(ciphertext, plaintext.as_mut_ptr()).unwrap(); - self.gmac.update::(plaintext, ptr::null_mut()).unwrap(); - } - } - - /// Decrypt ciphertext in place. - /// This may be called more than once. - #[inline(always)] - pub fn decrypt_in_place(&mut self, ciphertext_to_plaintext: &mut [u8]) { - self.decrypt( - unsafe { std::slice::from_raw_parts(ciphertext_to_plaintext.as_ptr(), ciphertext_to_plaintext.len()) }, - ciphertext_to_plaintext, - ); - } - - /// Finish decryption and return true if authentication appears valid. - /// If this returns false the message should be dropped. - #[inline(always)] - pub fn decrypt_finish(&mut self) -> Option<&[u8; 16]> { - unsafe { - self.gmac.finalize::(self.tmp.as_mut_ptr()).unwrap(); - self.gmac.tag(&mut self.tmp).unwrap(); - } - if (self.tag[8] == self.tmp[0] ^ self.tmp[8]) - && (self.tag[9] == self.tmp[1] ^ self.tmp[9]) - && (self.tag[10] == self.tmp[2] ^ self.tmp[10]) - && (self.tag[11] == self.tmp[3] ^ self.tmp[11]) - && (self.tag[12] == self.tmp[4] ^ self.tmp[12]) - && (self.tag[13] == self.tmp[5] ^ self.tmp[13]) - && (self.tag[14] == self.tmp[6] ^ self.tmp[14]) - && (self.tag[15] == self.tmp[7] ^ self.tmp[15]) - { - Some(&self.tag) - } else { - None - } - } -} - -unsafe impl Send for AesGmacSiv {} diff --git a/src/crypto/src/aes_openssl.rs b/src/crypto/src/aes_openssl.rs deleted file mode 100644 index 840a47d..0000000 --- a/src/crypto/src/aes_openssl.rs +++ /dev/null @@ -1,122 +0,0 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. - -use std::{mem::MaybeUninit, ptr, sync::Mutex}; - -use crate::{cipher_ctx::CipherCtx, constant::*}; - -/// An OpenSSL AES_GCM context. Automatically frees itself on drop. -/// The current interface is custom made for ZeroTier, but could easily be adapted for other uses. -/// Whether `ENCRYPT` is true or false decides respectively whether this context encrypts or decrypts. -/// Even though OpenSSL lets you set this dynamically almost no operations work when you do this -/// without resetting the context. -/// -/// This object cannot be mutated by multiple threads at the same time so wrap it in a Mutex if -/// you need to do this. As far as I have read a Mutex can safely implement Send and Sync. -pub struct AesGcm(CipherCtx); - -impl AesGcm { - /// Create an AesGcm context with the given key. - /// OpenSSL internally processes and caches this key, so it is recommended to reuse this context whenever encrypting under the same key. Call `reset_init_gcm` to change the IV for each reuse. - pub fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self { - let ctx = CipherCtx::new().unwrap(); - unsafe { - let t = ffi::EVP_aes_256_gcm(); - ctx.cipher_init::(t, key.as_ptr(), ptr::null()).unwrap(); - ffi::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); - } - - AesGcm(ctx) - } - - /// Set the IV of this AesGcm context. This call resets the IV but leaves the key and encryption algorithm alone. - /// This method must be called before any other method on AesGcm. - /// `iv` must be exactly 12 bytes in length, because that is what Aes supports. - pub fn reset_init_gcm(&mut self, iv: &[u8]) { - debug_assert_eq!(iv.len(), AES_GCM_NONCE_SIZE, "Aes IV must be 12 bytes long"); - unsafe { - self.0.cipher_init::(ptr::null(), ptr::null(), iv.as_ptr()).unwrap(); - } - } - - /// Add additional authentication data to AesGcm (same operation with CTR mode). - #[inline(always)] - pub fn aad(&mut self, aad: &[u8]) { - unsafe { self.0.update::(aad, ptr::null_mut()).unwrap() }; - } - - /// Encrypt or decrypt (same operation with CTR mode) - #[inline(always)] - pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) { - debug_assert!(output.len() >= input.len(), "output buffer must fit the size of the input buffer"); - unsafe { self.0.update::(input, output.as_mut_ptr()).unwrap() }; - } - - /// Encrypt or decrypt in place (same operation with CTR mode). - #[inline(always)] - pub fn crypt_in_place(&mut self, data: &mut [u8]) { - let ptr = data.as_mut_ptr(); - unsafe { self.0.update::(data, ptr).unwrap() } - } -} -impl AesGcm { - /// Produce the gcm authentication tag. - #[inline(always)] - pub fn finish_encrypt(&mut self) -> [u8; AES_GCM_TAG_SIZE] { - unsafe { - let mut tag = MaybeUninit::<[u8; AES_GCM_TAG_SIZE]>::uninit(); - self.0.finalize::(tag.as_mut_ptr().cast()).unwrap(); - self.0.tag(&mut *tag.as_mut_ptr()).unwrap(); - tag.assume_init() - } - } -} -impl AesGcm { - /// Check the gcm authentication tag. Outputs true if it matches the just decrypted message, outputs false otherwise. - #[inline(always)] - pub fn finish_decrypt(&mut self, expected_tag: &[u8]) -> bool { - debug_assert_eq!(expected_tag.len(), AES_GCM_TAG_SIZE); - if self.0.set_tag(expected_tag).is_ok() { - unsafe { self.0.finalize::(ptr::null_mut()).is_ok() } - } else { - false - } - } -} - -/// An OpenSSL AES_ECB context. Automatically frees itself on drop. -/// AES_ECB is very insecure if used incorrectly so its public interface supports only exactly what -/// ZeroTier uses it for. -pub struct Aes(Mutex); -unsafe impl Send for Aes {} -unsafe impl Sync for Aes {} - -impl Aes { - /// Create an AesEcb context with the given key. - /// OpenSSL internally processes and caches this key, so it is recommended to reuse this context - /// whenever encrypting under the same key. - pub fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self { - let ctx = CipherCtx::new().unwrap(); - unsafe { - let t = ffi::EVP_aes_256_ecb(); - ctx.cipher_init::(t, key.as_ptr(), ptr::null()).unwrap(); - ffi::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); - } - - Aes(Mutex::new(ctx)) - } - pub fn reset(&self, key: &[u8; AES_256_KEY_SIZE]) { - let ctx = self.0.lock().unwrap(); - unsafe { - ctx.cipher_init::(ptr::null(), key.as_ptr(), ptr::null()).unwrap(); - } - } - - /// Do not ever encrypt the same plaintext twice. Make sure data is always different between calls. - #[inline(always)] - pub fn crypt_block_in_place(&self, data: &mut [u8]) { - debug_assert_eq!(data.len(), AES_BLOCK_SIZE, "Incorrect Aes block size"); - let ptr = data.as_mut_ptr(); - let ctx = self.0.lock().unwrap(); - unsafe { ctx.update::(data, ptr).unwrap() } - } -} diff --git a/src/crypto/src/aes_tests.rs b/src/crypto/src/aes_tests.rs deleted file mode 100644 index 9a7a188..0000000 --- a/src/crypto/src/aes_tests.rs +++ /dev/null @@ -1,3677 +0,0 @@ -#[cfg(test)] -mod test { - use crate::aes::AesGcm; - use crate::aes_gmac_siv::AesGmacSiv; - use hex_literal::hex; - use std::time::SystemTime; - - fn to_hex(b: &[u8]) -> String { - let mut s = String::new(); - for c in b.iter() { - s = format!("{}{:0>2x}", s, *c); - } - s - } - - #[test] - fn aes_256_gcm() { - let key = [1u8; 32]; - let mut enc = AesGcm::::new(&key); - let mut dec = AesGcm::::new(&key); - - let plain = [2u8; 127]; - let iv0 = [3u8; 12]; - let iv1 = [4u8; 12]; - let mut tag_out; - let mut cipher_out = [0u8; 127]; - let mut plain_out = [0u8; 127]; - - enc.reset_init_gcm(&iv0); - enc.crypt(&plain, &mut cipher_out); - tag_out = enc.finish_encrypt(); - - dec.reset_init_gcm(&iv0); - dec.crypt(&cipher_out, &mut plain_out); - assert!(dec.finish_decrypt(&tag_out)); - - assert_eq!(plain, plain_out); - - enc.reset_init_gcm(&iv1); - enc.crypt(&plain, &mut cipher_out); - tag_out = enc.finish_encrypt(); - - dec.reset_init_gcm(&iv1); - dec.crypt(&cipher_out, &mut plain_out); - assert!(dec.finish_decrypt(&tag_out)); - - assert_eq!(plain, plain_out); - - enc.reset_init_gcm(&iv0); - enc.crypt(&plain, &mut cipher_out); - tag_out = enc.finish_encrypt(); - - dec.reset_init_gcm(&iv1); - dec.crypt(&cipher_out, &mut plain_out); - assert!(!dec.finish_decrypt(&tag_out)); - } - - #[test] - fn aes_256_gcm_quick_benchmark() { - let mut buf = [0_u8; 12345]; - for i in 1..12345 { - buf[i] = i as u8; - } - let iv = [1_u8; 12]; - - let mut c = AesGcm::::new(&[1_u8; 32]); - - let benchmark_iterations: usize = 80000; - let start = SystemTime::now(); - for _ in 0..benchmark_iterations { - c.reset_init_gcm(&iv); - c.crypt_in_place(&mut buf); - } - let duration = SystemTime::now().duration_since(start).unwrap(); - println!( - " AES-256-GCM encrypt benchmark: {} MiB/sec", - (((benchmark_iterations * buf.len()) as f64) / 1048576.0) / duration.as_secs_f64() - ); - - let mut c = AesGcm::::new(&[1_u8; 32]); - - let start = SystemTime::now(); - for _ in 0..benchmark_iterations { - c.reset_init_gcm(&iv); - c.crypt_in_place(&mut buf); - } - let duration = SystemTime::now().duration_since(start).unwrap(); - println!( - " AES-256-GCM decrypt benchmark: {} MiB/sec", - (((benchmark_iterations * buf.len()) as f64) / 1048576.0) / duration.as_secs_f64() - ); - } - - #[test] - fn aes_gcm_test_vectors() { - // Even though we are just wrapping other implementations, it's still good to test thoroughly! - for tv in NIST_AES_GCM_TEST_VECTORS.iter() { - let mut gcm = AesGcm::new(&tv.key); - gcm.reset_init_gcm(tv.nonce); - gcm.aad(tv.aad); - let mut ciphertext = Vec::new(); - ciphertext.resize(tv.plaintext.len(), 0); - gcm.crypt(tv.plaintext, ciphertext.as_mut()); - let mut tag = gcm.finish_encrypt(); - assert!(tag.eq(tv.tag)); - assert!(ciphertext.as_slice().eq(tv.ciphertext)); - - let mut gcm = AesGcm::new(&tv.key); - gcm.reset_init_gcm(tv.nonce); - gcm.aad(tv.aad); - let mut ct_copy = ciphertext.clone(); - gcm.crypt_in_place(ct_copy.as_mut()); - assert!(gcm.finish_decrypt(&tag)); - - gcm.reset_init_gcm(tv.nonce); - gcm.aad(tv.aad); - gcm.crypt_in_place(ciphertext.as_mut()); - tag[0] ^= 1; - assert!(!gcm.finish_decrypt(&tag)); - } - } - - #[test] - fn aes_gmac_siv_test_vectors() { - let mut test_pt = [0_u8; 65536]; - let mut test_ct = [0_u8; 65536]; - let mut test_aad = [0_u8; 65536]; - for i in 0..65536 { - test_pt[i] = i as u8; - test_aad[i] = i as u8; - } - let mut c = AesGmacSiv::new(TV0_KEYS[0], TV0_KEYS[1]); - for (test_length, expected_ct_sha384, expected_tag) in TEST_VECTORS.iter() { - test_ct.fill(0); - c.reset(); - c.encrypt_init(&(*test_length as u64).to_le_bytes()); - c.encrypt_set_aad(&test_aad[0..*test_length]); - c.encrypt_first_pass(&test_pt[0..*test_length]); - c.encrypt_first_pass_finish(); - c.encrypt_second_pass(&test_pt[0..*test_length], &mut test_ct[0..*test_length]); - let tag = c.encrypt_second_pass_finish(); - let ct_hash = crate::hash::SHA384::hash(&test_ct[0..*test_length]).to_vec(); - //println!("{} {} {}", *test_length, to_hex(ct_hash.as_slice()), to_hex(tag)); - if !to_hex(ct_hash.as_slice()).eq(*expected_ct_sha384) { - panic!("test vector failed (ciphertest)"); - } - if !to_hex(tag).eq(*expected_tag) { - panic!("test vector failed (tag)"); - } - } - } - - #[test] - fn aes_gmac_siv_encrypt_decrypt() { - let aes_key_0: [u8; 32] = [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, - ]; - let aes_key_1: [u8; 32] = [ - 2, 3, 4, 5, 6, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, - ]; - let iv: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; - - let mut buf = [0_u8; 12345]; - for i in 1..12345 { - buf[i] = i as u8; - } - - let mut c = AesGmacSiv::new(&aes_key_0, &aes_key_1); - - for _ in 0..256 { - c.reset(); - c.encrypt_init(&iv); - c.encrypt_first_pass(&buf); - c.encrypt_first_pass_finish(); - c.encrypt_second_pass_in_place(&mut buf); - let tag = *c.encrypt_second_pass_finish(); - let sha = crate::hash::SHA384::hash(&buf).to_vec(); - let sha = to_hex(sha.as_slice()); - if sha != "4dc97c10abb6112a3907e5eb588ea5123719442b715da994d9756b003677719824326973960268823d924f66491a16e6" { - panic!("encrypt result hash check failed! {}", sha); - } - //println!("Encrypt OK, tag: {}, hash: {}", to_hex(&tag), sha); - - c.reset(); - c.decrypt_init(&tag); - c.decrypt_in_place(&mut buf); - let _ = c.decrypt_finish().expect("decrypt_finish() failed!"); - for i in 1..12345 { - if buf[i] != (i & 0xff) as u8 { - panic!("decrypt data check failed!"); - } - } - //println!("Decrypt OK"); - } - //println!("Encrypt/decrypt test OK"); - - let benchmark_iterations: usize = 80000; - let start = SystemTime::now(); - for _ in 0..benchmark_iterations { - c.reset(); - c.encrypt_init(&iv); - c.encrypt_first_pass(&buf); - c.encrypt_first_pass_finish(); - c.encrypt_second_pass_in_place(&mut buf); - let _ = c.encrypt_second_pass_finish(); - } - let duration = SystemTime::now().duration_since(start).unwrap(); - println!( - " AES-GMAC-SIV (legacy) encrypt benchmark: {} MiB/sec", - (((benchmark_iterations * buf.len()) as f64) / 1048576.0) / duration.as_secs_f64() - ); - let start = SystemTime::now(); - for _ in 0..benchmark_iterations { - c.reset(); - c.decrypt_init(&buf[0..16]); // we don't care if decryption is successful to benchmark, so anything will do - c.decrypt_in_place(&mut buf); - c.decrypt_finish(); - } - let duration = SystemTime::now().duration_since(start).unwrap(); - println!( - " AES-GMAC-SIV (legacy) decrypt benchmark: {} MiB/sec", - (((benchmark_iterations * buf.len()) as f64) / 1048576.0) / duration.as_secs_f64() - ); - } - - struct GcmTV { - pub key: &'static K, - pub nonce: &'static [u8; 12], - pub aad: &'static [u8], - pub plaintext: &'static [u8], - pub ciphertext: &'static [u8], - pub tag: &'static [u8; 16], - } - - /// AES-GMAC-SIV test keys. - const TV0_KEYS: [&[u8]; 2] = [ - "00000000000000000000000000000000".as_bytes(), - "11111111111111111111111111111111".as_bytes(), - ]; - - /// AES-GMAC-SIV test vectors. - /// Test vectors consist of a series of input sizes, a SHA384 hash of a resulting ciphertext, and an expected tag. - /// Input is a standard byte array consisting of bytes 0, 1, 2, 3, ..., 255 and then cycling back to 0 over and over - /// and is provided both as ciphertext and associated data (AAD). - #[allow(unused)] - const TEST_VECTORS: [(usize, &str, &str); 85] = [ - ( - 0, - "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", - "43847e644239134deccf5538162c861e", - ), - ( - 777, - "aabf892f18a620b9c3bae91bb03a74c84193e4a7b64916c6bc88b885b9ebed4134495e5f22f12e3046fbb3f26fa111a7", - "b8c318b5dcc1d672114a6f7be54ef289", - ), - ( - 1554, - "648f551df29217f0e634b72ba6973c0eb95c7d4be8b135e550d8bcdf65b75980881bc0e03cf22589e04bedc7da1804cd", - "535b8ddd51ec82a1e850906fe321b21a", - ), - ( - 2331, - "bfbfdffea40062e23bbdf0835e1d38d1623bebca7407908bbc6d5b3f2bfd062a2d237f091affda7348094fafda0bd1a7", - "4f521876fbb2c563051196b33c20c822", - ), - ( - 3108, - "cc6035cab70f3a3298a5c4956ff07f179acf3771bb915c590a8a19fe5133d6d8a81c118148394dfb364af5c2fbdaadeb", - "d3adfa578c8bcd738c55ffc527358cef", - ), - ( - 3885, - "15ec2760a21c25f9870a84ee757f3da2c261a950c2f692d75ff9e99b2d50c826c21e27e49c4cd3450fedc7e60371589f", - "a4c22d6c3d773634c2dc057e1f7c6738", - ), - ( - 4662, - "c2afad6f034704300c34f143dcdcb86c9b954cec1ebf22e7071f288c58a2ae430d3e3748d214d1021472793d3f337dc6", - "c0601cb6cd4883102f70570c2cdc0ab6", - ), - ( - 5439, - "8fee067f5a7a475a630f9db8b2eb80c1edc40eb4246a0f1c078e535df7d06451c6a9bde1a23ba70285690dd7100a8626", - "7352239f2302b08844309d28b13fa867", - ), - ( - 6216, - "60095b4172438aee61e65f5379f4ef276c3632d4ac74eea7723a2201823432614aba7b4670d9bf7a5b9126ca38f3b88a", - "c0f0b0aa651965f8514b473c5406285e", - ), - ( - 6993, - "10e754dd08b4d2a6c109fb01fce2b57d54743947e14a7e67d7efd0608baf91f7fc42a53328fe8c18d234abad8ebcdff0", - "58444988a62a99060728a7637c8499eb", - ), - ( - 7770, - "1abc4a5dcd2696336bd0e8af20fe7fc261aa424b52cfb5ad80ee7c7c793ac44f11db3506cdbbbaed0f80000925d08d52", - "e8065c563bc6018cdcbf9aaafef767e6", - ), - ( - 8547, - "26aaf74ae8bfc6aaf45ceee0476ea0a484304f5c36050d3e2265cb194a2f7c308213314232270608b6d3f1c11b834e33", - "ec50e4b3f6e4b3de24b3476623d08157", - ), - ( - 9324, - "863206305d466aa9c0d0ec674572069f61fe5009767f99ec8832912725c28c49d6a106ad3f55372c922e4e169fc382ce", - "0cfac64f49e0f128d0a18d293878f222", - ), - ( - 10101, - "bd0c0950b947a6c34f1fa6e877433b42c039a8ea7b37634c40fb47efae4958ba74ef0991cfedf3c82a0b87ef59635071", - "e0220a02b74259eeebbebede847d50f9", - ), - ( - 10878, - "d7b9901af1dacf6a8c369b993ba1c607f9b7f073d02311c72d8449d3494d477ffc8344a1d8b488020ccfc7c80fbd27e1", - "ebe3933146734a6ade2b434f2bcd78ae", - ), - ( - 11655, - "0ba265e3ef0bebf01a4f3490da462c7730aad6aa6c70bb9ce64a36d26d24fe213660e60e4d3301329170471f11ff8ca2", - "ec3dd4bf4cb7d527a86dd559c773a87b", - ), - ( - 12432, - "c3b6755a1be922ec71c1e187ead36c4e6fc307c72969c64ca1e9b7339d61e1a93a74a315fd73bed8fa5797b78b19dbe5", - "5b58dcf392749bcef91056ba9475d0ef", - ), - ( - 13209, - "2fb1a67151183daa2f0d7f0064534497357f173161349dd008499a8c1a123cc942662ecc426e2ad7743fe0ab9f5d7be1", - "c011260d328d310e2ab606aa1ef8afd4", - ), - ( - 13986, - "6afae2a07ce9bfe30fbbfb7dcf32d755bcf357334dc5c309e58cab38ebe559f25b313a0b3ca32ff1dc41f7b99718f653", - "011bf43cfbbb7ae5986f8e0fc87771a9", - ), - ( - 14763, - "cc6215c115eb6411f4712c2289f5bf0ccb5151635f9f9ceac7c1b62d8d2f4d26498079d0289f83aeb26e97b5b924ffc4", - "a015034a8d5bc83cc76c6983a5ba19ab", - ), - ( - 15540, - "3cebce794e947341c4ceec444ca43c6ac57c6f58de462bfec7566cbd59a1b6f2eae774120e29521e76120a604d1a12d9", - "d373cd2bd9000655141ac632880eca40", - ), - ( - 16317, - "899147b98d78bb5d137dc7c4f03be7eca82bcca19cc3a701261332923707aed2e6719d35d2f2bf067cd1d193a53529cf", - "ed223b64529299c787f49d631ce181c1", - ), - ( - 17094, - "aecd1830958b994b2c331b90e7d8ff79f27c83a71f5797a65ade3a30b4fa5928e79140bcd03f375591d53df96fea1a4d", - "948a7c253d54bb6b65d78530c0eb7aab", - ), - ( - 17871, - "e677ffd4ecaba5899659fefe5fe8e643004392be3be6dc5a801409870ac1e3398f47cc1d83f7a4c41925b6337e01f7fd", - "156a600c336f3ac034ca90034aa22635", - ), - ( - 18648, - "4ee50f4a98d0bbd160add6acf76765ccdac0c1cd0bb2adbbcb22dd012a1121620b739a120df7dc4091e684ddf28eb726", - "75873467b416a7b025f9f1b015bf653a", - ), - ( - 19425, - "aa025f32c0575af7209828fc7fc4591b41fa7cfb485e26c5401e63ca1fa05776f8b8af1769a15e81f2c663bca9b02ab3", - "5679efa7a4404e1e5c9b372782a41bf2", - ), - ( - 20202, - "6e77ab62d2affeb27f4ef326191b3df3863c338a629f64a785505f4a5968ff59bc011c7a27951cb00e2e7d9b9bd32fec", - "36a9c4515d34f9bb962d8876ab3b5c86", - ), - ( - 20979, - "1625b4f0e65fc66f11ba3ee6b3e20c732535654c447df6b517ced113107a1057a64477faa2af4a5ede4034bf3cff98ea", - "9058044e0f71c28d4f8d3281a3aec024", - ), - ( - 21756, - "94efe6aa55bd77bfa58c185dec313a41003f9bef02568e72c337be4de1b46c6e5bb9a9329b4f108686489b8bc9d5f4f0", - "8d6d2c90590268a26f5e7d76351f48c1", - ), - ( - 22533, - "7327a05fdb0ac92433dfc2c85c5e96e6ddcbdb01e079f8dafbee79c14cb4d5fd46047acd6bb0e09a98f6dd03dced2a0a", - "4e0f0a394f85bca35c68ef667aa9c244", - ), - ( - 23310, - "93da9e356efbc8b5ae366256f4c6fc11c11fc347aaa879d591b7c1262d90adf98925f571914696054f1d09c74783561e", - "8c83c157be439280afc790ee3fd667eb", - ), - ( - 24087, - "99b91be5ffca51b1cbc7410798b1540b5b1a3356f801ed4dc54812919c08ca5a9adc218bc51e594d97b46445a1515506", - "9436ff05729a77f673e815e464aeaa75", - ), - ( - 24864, - "074253ad5d5a5d2b072e7aeaffa04a06119ec812a88ca43481fe5e2dce02cf6736952095cd342ec70b833c12fc1777f4", - "69d8951b96866a08efbb65f2bc31cfbc", - ), - ( - 25641, - "c0a301f90597c05cf19e60c35378676764086b7156e455f4800347f8a6e733d644e4cc709fb9d95a9211f3e1e10c762a", - "3561c9802143c306ecc5e07e3b976d9e", - ), - ( - 26418, - "3c839e59d945b841acb604e1b9ae3df36a291444ce0bcae336ee875beaf208bf10af7342b375429ecb92ec54d11a5907", - "3032ffdb8daee11b2e739132c6175615", - ), - ( - 27195, - "3dc59b16603950dfc26a90bf036712eb088412e8de4d1b27c3fa6be6502ac12d89d194764fb53c3dc7d90fa696ba5a16", - "49436717edff7cd67c9a1be16d524f07", - ), - ( - 27972, - "4fbc0d40ff13376b8ed5382890cdea337b4a0c9c31b477c4008d2ef8299bd5ab771ba70b1b4b743f8f7caa1f0164d1a1", - "64a9856a3bb81dc81ff1bc1025192dc9", - ), - ( - 28749, - "6ab191aa6327f229cc94e8c7b1b7ee30bc723e6aeaf3050eb7d14cb491c3513254e9b19894c2b4f071d298401fd31945", - "101f2ffea60f246a3b57c4a530d67cf1", - ), - ( - 29526, - "d06dece58e6c7345986aae4b7f15b3317653f5387d6262f389b5cbbe804568124a876eabb89204e96b3c0f7b552df3c4", - "5c0e873adba65a9f4cb24cce4f194b18", - ), - ( - 30303, - "7a33c1268eafdc1f89ad460fa4ded8d3df9a3cabe4339706877878c64a2c8080cf3fa5ea7f2f24744e3341476b1eb5a5", - "b7dc708fc46ce5cde24a31ad549fec83", - ), - ( - 31080, - "37bf1f9fca6d705b989b2d63259ca924dc860fc6027e07d9aad79b94841227739774f5d324590df45d8f41249ef742ea", - "8ead50308c281e699b79b69dad7ecb91", - ), - ( - 31857, - "91b120c73be86f9d53326fa707cfa1411e5ac76ab998a2d7ebd73a75e3b1a04c9f0855d102184b8a3fd5d99818b0b134", - "6056d09595bd16bfa317c6f87ce64bb7", - ), - ( - 32634, - "42cc255c06184ead57b27efd0cefb0f2c788c8962a6fd15db3f25533a7f49700bca85af916f9e985f1941a6e66943b38", - "3b15e332d2f53bb97e1a9d03e6113b97", - ), - ( - 33411, - "737f8bb8f3fd03a9d13e50abba3a42f4491c36eda3eb215085abda733227ec490cb863ffbd68f915c8fb2926a899fbc3", - "b2c647d25c46aab4d4a5ede4a3b4576d", - ), - ( - 34188, - "e9caa36505e19628175d1ce8b933267380099753a41e503fa2f894cea17b7692f0b27079ed33cdd1293db9a35722d561", - "a2882adfd00f22823250215b12b3a1fd", - ), - ( - 34965, - "81ddc348ebbdfb963daa5d0c1b51bbb73cacd883d4fc4316db6bd3388779beff7be0655bbac73951f89dc53832199c11", - "f33106eb8104f3780350c6d4f82333ad", - ), - ( - 35742, - "308ce31daf40dab707e2cb4c4a5307bc403e24c971ae1e30e998449f804a167fe5f2cf617d585851b6fe9f2b4209f09c", - "44070ac90cbf350ab92289cc063e978c", - ), - ( - 36519, - "71f51b4bddbe8a52f18be75f9bdb3fca0773901b794de845450fb308c34775ede1a6da9a82b61e9682a29a3ef71274e2", - "0e387704298c444bf3afba0edc0c1c1c", - ), - ( - 37296, - "478ac94eee8c5f96210003fcb478392b91f2ef6fc3a729774e5fe82a2d8d0abc54ae1d25b3eaefb061e2bd43b70ca4ea", - "fb65ebeda52cd5848d303c0677cecb7f", - ), - ( - 38073, - "bc3a9390618da7d644be932627353e2c92024df939d2d8497fba61fae3dd822cdd3e130c1707f4a9d5d4a0cbb4b3e0b3", - "d790d529a837ec79f7cc3f66ed9a399f", - ), - ( - 38850, - "ef0e63a53a10e56477c47e13320b8a7d330aee3a4363c850edc56c0707a2686478e5a5193f54ceb33467ab7e8a22aa21", - "6f2c18742f106f16fc290767342fb62b", - ), - ( - 39627, - "c16f63533c099d872d9a01c326db7756e7eb488c756b9a6ebf575993d8ea2eb45c572b2e162f061e145710e0e21e8e18", - "a57afde7938b223ae5e109a03db4ee4c", - ), - ( - 40404, - "ade484ae8c13465a73589ef14789bb6891c933453e198df84edd34b4ac5c83aa90f2cf61fa072fa4d8f5b5c4cd68fa9e", - "a01d13009db86ac442f7afd39d83309f", - ), - ( - 41181, - "6c5c7eed0e043a0bd60bcac9b5b546e150028d70c1efefc9ff69037ef4dc1a36878b171b9f2a639df822d11054a0e405", - "6321c8622ca5866c875d340206d06a28", - ), - ( - 41958, - "dd311c54222fb0d92858719cf5b1c51bb5e3ca2539ffd68f1dd6c7e38969495be935804855ccdcc4b4cf221fcdbda886", - "cf401eb819b5dc5cd8c909aae9b3b34b", - ), - ( - 42735, - "31cda9d663199b32eff042dd16c0b909ba999641e77ba751c91752bfc4d595e17ec6467119e74a600b72da72ba287d0a", - "12fd6298ab5d744eb6ade3106565afad", - ), - ( - 43512, - "11b014057d51a8384d549d5d083c4406b575df6a9295853dd8f2f84f078cc241bb90495a119126b10b9510efcb68c0d3", - "a48a49eea5dc90359ef21f32132f8604", - ), - ( - 44289, - "b44f5dbeecd76ee7efe3fb4dfe10ba8135d7a5e4d104149f4a91c5c6ee9446d9be19fb4c9ba668b074466d3892e22228", - "07e1cbb7a19174d9b1e4d5a2c741cc14", - ), - ( - 45066, - "d87bbba3a3c739cab622386c89aeb685a70009fab1a606bd34622adfa3a75a05b58d56ee6b9874d414db38a6a32927b3", - "a27cd252712cd2a1a2d95dea39f888d4", - ), - ( - 45843, - "abb90e60ea13c6cb3b401b8e271637416b87fbede165dde7be1d34abe4427dae4b39b499352cacac909bc43fb94028c8", - "df3ae762b9257936feda435a61a9c3a1", - ), - ( - 46620, - "56d1132ee6e0f85543950d2d9667244b66b0ce6414eacd1859b128ed0b9026b31a25bfdcce3d1a0ce7c39d99f609c89c", - "cfe7c3c3f1cb615e2d210cc8136443e6", - ), - ( - 47397, - "ecb023ec4c23cf95d1848a38b359f1f590f172dee9d8fb1be6bc9c4fb2ce96f612d60d7b111de539ab8313a87b821176", - "501d24752bf55cb12239863981898a07", - ), - ( - 48174, - "34236ab60f05bb510aa0880fec358fb2002903efa14c912cab8a399e09418f97223ca2f7b8d6798c11d39e79032eaaa8", - "4ecaba4eae886aa429927188abab9623", - ), - ( - 48951, - "55e8b40fad90a3d8c85a0f4d5bcf5975b8a6e2fb78377109f5b607a5e367187fbbc9a1e978aab3228fbf43ad23d0ad13", - "84c43bc30eb4a67230b6c634fe3c7782", - ), - ( - 49728, - "14b1f896d0d01ecff4e456c3c392b1ca2bad9f1ef07713f84cdd89e663aa27ca77d80213ed57a89431eb992b11d98749", - "7f58c2f9a249f70fe1c6f9b4f65e5a1d", - ), - ( - 50505, - "1335b1fb56196e0b371fa53ab7445845fdefcea3eb2833478deb3526e2ec888945e95ee8239b52caae5b9920ba4f43bb", - "5fd729126b236ce3e0686fc706dce20f", - ), - ( - 51282, - "0d1983a6cab870c5e78f89a11dd30e7d2c71a3882f8bba3e71dc1b96a2d9fc6cc6d91d683b74456b886de34df792cfda", - "7731ae6e6c54dfde12f6116357e812ea", - ), - ( - 52059, - "9d619fb4aa8441baaefed7b778693c291f2c1441b206ec135930fac3529d26587ac36f4472949e0b198b51c0c5a9d0f8", - "39db2c996aea28996e03d576c118630f", - ), - ( - 52836, - "31dca4fa285878ba3efc3b66a248a078b69a11c3c73f81077377c4ffcb7002627aad5faa955e3141c1d8508aad68c8f6", - "32ac1e5a09e7e629ff95f30aa9b69c00", - ), - ( - 53613, - "931a9969cf2bb02302c32b1eecd4933805e2da403d85aaf98c82c68129fb95f089eb85c65a6fcbc7d81bedb39de0cabb", - "1a6f54b87c12868da530eac94d99eb31", - ), - ( - 54390, - "2f0742565801a37810ecb3f50a6f782e73a369a790d1a6a85135e7ffa12fc063db8909ab9eca7cf7308832887a6149d1", - "1b18ed6a8f901b7947626216839f0643", - ), - ( - 55167, - "901defbd308b54deef89acd0d94e4387b370f9d2e6f870d72da2e447ed3ebe69c5f9f144488bd6207a732102160bff47", - "1e0e6a05fcc0794121f617e28cfac1a0", - ), - ( - 55944, - "df984a5f7475250155dc4733a746e98446dc93a56a3f3bff691ddfef7deefb32b1da1b0e7e15cce443831ebfb3e30ada", - "876121af882d0ebeae38f111f3d4b6e8", - ), - ( - 56721, - "acb693ed837b33561408cb1eed636e0082ac404f3fd72d277fa146ae5cd81a1fde3645f4cdc7babd8ba044b78075cb67", - "5b90ed6c7943fc6da623c536e2ff1352", - ), - ( - 57498, - "dffb54bf5938e812076cfbf15cd524d72a189566c7980363a49dd89fb49e230d9742ef0b0e1ac543dca14366d735d152", - "22aee072457306e32747fbbbc3ae127c", - ), - ( - 58275, - "92dbc245a980fc78974f7a27e62c22b12a00be9d3ef8d3718ff85f6d5fbcbf1d9d1e0f0a3daeb8c2628d090550a0ff6b", - "5fa348117faba4ac8c9d9317ff44cd2d", - ), - ( - 59052, - "57721475cb719691850696d9a8ad4c28ca8ef9a7d45874ca21df4df250cb87ea60c464f4e3252e2d6161ed36c4b56d75", - "24d92ae7cac56d9c0276b06f7428d5df", - ), - ( - 59829, - "d0936026440b5276747cb9fb7dc96de5d4e7846c233ca5f6f9354b2b39f760333483cbe99ffa905facb347242f58a7ef", - "05c57068e183f9d835e7f461202f923c", - ), - ( - 60606, - "7b3bb3527b73a8692f076f6a503b2e09b427119543c7812db73c7c7fb2d43af9ecbd2a8a1452ac8ada96ad0bad7bb185", - "f958635a193fec0bfb958e97961381df", - ), - ( - 61383, - "ff0d00255a36747eced86acfccd0cf9ef09faa9f44c8cf382efec462e7ead66e562a971060c3f32798ba142d9e1640a2", - "838159b222e56aadde8229ed56a14095", - ), - ( - 62160, - "15806e088ed1428cd73ede3fecf5b60e2a616f1925004dadd2cab8e847059f795659659e82a4554f270baf88bf60af63", - "fed2aa0c9c0a73d499cc970aef21c52f", - ), - ( - 62937, - "cfad71b23b6da51256bd1ddbd1ac77977fe10b2ad0a830a23a794cef914bf71a9519d78a5f83fc411e8d8db996a45d4e", - "e1ea412fd3e1bd91c24b6b6445e8ff43", - ), - ( - 63714, - "7d03a3698a79b1af1663e3e485c2efdc306ecd87b2644f2e01d83a35999d6cdf12241b6114d60d107c10c0d0c9cc0d23", - "e6a3c3f3fd2d9cfcdc06cca2f59e9a83", - ), - ( - 64491, - "e12b168cce0e82ed1db88df549f39b3ff40b5884a09fceae69c4c3db13c1c37ea79531c47b2700d1c27774a1ab7e8b35", - "4cbb14d789f5cd8eca49ce9e1d442ea1", - ), - ( - 65268, - "056c9d1172cfa76ce7f19c605e5969c284b82dca155dc9c1ed58062ab4d5a7704e27fe69f3aa745b73f45f1cd0ee57df", - "8195187f092d52c2a8695b680568b934", - ), - ]; - - /// - const NIST_AES_GCM_TEST_VECTORS: &[GcmTV<[u8; 32]>] = &[ - GcmTV { - key: &hex!("b52c505a37d78eda5dd34f20c22540ea1b58963cf8e5bf8ffa85f9f2492505b4"), - nonce: &hex!("516c33929df5a3284ff463d7"), - plaintext: b"", - aad: b"", - ciphertext: b"", - tag: &hex!("bdc1ac884d332457a1d2664f168c76f0"), - }, - GcmTV { - key: &hex!("5fe0861cdc2690ce69b3658c7f26f8458eec1c9243c5ba0845305d897e96ca0f"), - nonce: &hex!("770ac1a5a3d476d5d96944a1"), - plaintext: b"", - aad: b"", - ciphertext: b"", - tag: &hex!("196d691e1047093ca4b3d2ef4baba216"), - }, - GcmTV { - key: &hex!("7620b79b17b21b06d97019aa70e1ca105e1c03d2a0cf8b20b5a0ce5c3903e548"), - nonce: &hex!("60f56eb7a4b38d4f03395511"), - plaintext: b"", - aad: b"", - ciphertext: b"", - tag: &hex!("f570c38202d94564bab39f75617bc87a"), - }, - GcmTV { - key: &hex!("7e2db00321189476d144c5f27e787087302a48b5f7786cd91e93641628c2328b"), - nonce: &hex!("ea9d525bf01de7b2234b606a"), - plaintext: b"", - aad: b"", - ciphertext: b"", - tag: &hex!("db9df5f14f6c9f2ae81fd421412ddbbb"), - }, - GcmTV { - key: &hex!("a23dfb84b5976b46b1830d93bcf61941cae5e409e4f5551dc684bdcef9876480"), - nonce: &hex!("5aa345908048de10a2bd3d32"), - plaintext: b"", - aad: b"", - ciphertext: b"", - tag: &hex!("f28217649230bd7a40a9a4ddabc67c43"), - }, - GcmTV { - key: &hex!("dfe928f86430b78add7bb7696023e6153d76977e56103b180253490affb9431c"), - nonce: &hex!("1dd0785af9f58979a10bd62d"), - plaintext: b"", - aad: b"", - ciphertext: b"", - tag: &hex!("a55eb09e9edef58d9f671d72207f8b3c"), - }, - GcmTV { - key: &hex!("34048db81591ee68224956bd6989e1630fcf068d7ff726ae81e5b29f548cfcfb"), - nonce: &hex!("1621d34cff2a5b250c7b76fc"), - plaintext: b"", - aad: b"", - ciphertext: b"", - tag: &hex!("4992ec3d57cccfa58fd8916c59b70b11"), - }, - GcmTV { - key: &hex!("a1114f8749c72b8cef62e7503f1ad921d33eeede32b0b5b8e0d6807aa233d0ad"), - nonce: &hex!("a190ed3ff2e238be56f90bd6"), - plaintext: b"", - aad: b"", - ciphertext: b"", - tag: &hex!("c8464d95d540fb191156fbbc1608842a"), - }, - GcmTV { - key: &hex!("ddbb99dc3102d31102c0e14b238518605766c5b23d9bea52c7c5a771042c85a0"), - nonce: &hex!("95d15ed75c6a109aac1b1d86"), - plaintext: b"", - aad: b"", - ciphertext: b"", - tag: &hex!("813d1da3775cacd78e96d86f036cff96"), - }, - GcmTV { - key: &hex!("1faa506b8f13a2e6660af78d92915adf333658f748f4e48fa20135a29e9abe5f"), - nonce: &hex!("e50f278d3662c99d750f60d3"), - plaintext: b"", - aad: b"", - ciphertext: b"", - tag: &hex!("aec7ece66b7344afd6f6cc7419cf6027"), - }, - GcmTV { - key: &hex!("f30b5942faf57d4c13e7a82495aedf1b4e603539b2e1599317cc6e53225a2493"), - nonce: &hex!("336c388e18e6abf92bb739a9"), - plaintext: b"", - aad: b"", - ciphertext: b"", - tag: &hex!("ddaf8ef4cb2f8a6d401f3be5ff0baf6a"), - }, - GcmTV { - key: &hex!("daf4d9c12c5d29fc3fa936532c96196e56ae842e47063a4b29bfff2a35ed9280"), - nonce: &hex!("5381f21197e093b96cdac4fa"), - plaintext: b"", - aad: b"", - ciphertext: b"", - tag: &hex!("7f1832c7f7cd7812a004b79c3d399473"), - }, - GcmTV { - key: &hex!("6b524754149c81401d29a4b8a6f4a47833372806b2d4083ff17f2db3bfc17bca"), - nonce: &hex!("ac7d3d618ab690555ec24408"), - plaintext: b"", - aad: b"", - ciphertext: b"", - tag: &hex!("db07a885e2bd39da74116d06c316a5c9"), - }, - GcmTV { - key: &hex!("cff083303ff40a1f66c4aed1ac7f50628fe7e9311f5d037ebf49f4a4b9f0223f"), - nonce: &hex!("45d46e1baadcfbc8f0e922ff"), - plaintext: b"", - aad: b"", - ciphertext: b"", - tag: &hex!("1687c6d459ea481bf88e4b2263227906"), - }, - GcmTV { - key: &hex!("3954f60cddbb39d2d8b058adf545d5b82490c8ae9283afa5278689041d415a3a"), - nonce: &hex!("8fb3d98ef24fba03746ac84f"), - plaintext: b"", - aad: b"", - ciphertext: b"", - tag: &hex!("7fb130855dfe7a373313361f33f55237"), - }, - GcmTV { - key: &hex!("78dc4e0aaf52d935c3c01eea57428f00ca1fd475f5da86a49c8dd73d68c8e223"), - nonce: &hex!("d79cf22d504cc793c3fb6c8a"), - plaintext: b"", - aad: &hex!("b96baa8c1c75a671bfb2d08d06be5f36"), - ciphertext: b"", - tag: &hex!("3e5d486aa2e30b22e040b85723a06e76"), - }, - GcmTV { - key: &hex!("4457ff33683cca6ca493878bdc00373893a9763412eef8cddb54f91318e0da88"), - nonce: &hex!("699d1f29d7b8c55300bb1fd2"), - plaintext: b"", - aad: &hex!("6749daeea367d0e9809e2dc2f309e6e3"), - ciphertext: b"", - tag: &hex!("d60c74d2517fde4a74e0cd4709ed43a9"), - }, - GcmTV { - key: &hex!("4d01c96ef9d98d4fb4e9b61be5efa772c9788545b3eac39eb1cacb997a5f0792"), - nonce: &hex!("32124a4d9e576aea2589f238"), - plaintext: b"", - aad: &hex!("d72bad0c38495eda50d55811945ee205"), - ciphertext: b"", - tag: &hex!("6d6397c9e2030f5b8053bfe510f3f2cf"), - }, - GcmTV { - key: &hex!("8378193a4ce64180814bd60591d1054a04dbc4da02afde453799cd6888ee0c6c"), - nonce: &hex!("bd8b4e352c7f69878a475435"), - plaintext: b"", - aad: &hex!("1c6b343c4d045cbba562bae3e5ff1b18"), - ciphertext: b"", - tag: &hex!("0833967a6a53ba24e75c0372a6a17bda"), - }, - GcmTV { - key: &hex!("22fc82db5b606998ad45099b7978b5b4f9dd4ea6017e57370ac56141caaabd12"), - nonce: &hex!("880d05c5ee599e5f151e302f"), - plaintext: b"", - aad: &hex!("3e3eb5747e390f7bc80e748233484ffc"), - ciphertext: b"", - tag: &hex!("2e122a478e64463286f8b489dcdd09c8"), - }, - GcmTV { - key: &hex!("fc00960ddd698d35728c5ac607596b51b3f89741d14c25b8badac91976120d99"), - nonce: &hex!("a424a32a237f0df530f05e30"), - plaintext: b"", - aad: &hex!("cfb7e05e3157f0c90549d5c786506311"), - ciphertext: b"", - tag: &hex!("dcdcb9e4004b852a0da12bdf255b4ddd"), - }, - GcmTV { - key: &hex!("69749943092f5605bf971e185c191c618261b2c7cc1693cda1080ca2fd8d5111"), - nonce: &hex!("bd0d62c02ee682069bd1e128"), - plaintext: b"", - aad: &hex!("6967dce878f03b643bf5cdba596a7af3"), - ciphertext: b"", - tag: &hex!("378f796ae543e1b29115cc18acd193f4"), - }, - GcmTV { - key: &hex!("fc4875db84819834b1cb43828d2f0ae3473aa380111c2737e82a9ab11fea1f19"), - nonce: &hex!("da6a684d3ff63a2d109decd6"), - plaintext: b"", - aad: &hex!("91b6fa2ab4de44282ffc86c8cde6e7f5"), - ciphertext: b"", - tag: &hex!("504e81d2e7877e4dad6f31cdeb07bdbd"), - }, - GcmTV { - key: &hex!("9f9fe7d2a26dcf59d684f1c0945b5ffafe0a4746845ed317d35f3ed76c93044d"), - nonce: &hex!("13b59971cd4dd36b19ac7104"), - plaintext: b"", - aad: &hex!("190a6934f45f89c90067c2f62e04c53b"), - ciphertext: b"", - tag: &hex!("4f636a294bfbf51fc0e131d694d5c222"), - }, - GcmTV { - key: &hex!("ab9155d7d81ba6f33193695cf4566a9b6e97a3e409f57159ae6ca49655cca071"), - nonce: &hex!("26a9f8d665d163ddb92d035d"), - plaintext: b"", - aad: &hex!("4a203ac26b951a1f673c6605653ec02d"), - ciphertext: b"", - tag: &hex!("437ea77a3879f010691e288d6269a996"), - }, - GcmTV { - key: &hex!("0f1c62dd80b4a6d09ee9d787b1b04327aa361529ffa3407560414ac47b7ef7bc"), - nonce: &hex!("c87613a3b70d2a048f32cb9a"), - plaintext: b"", - aad: &hex!("8f23d404be2d9e888d219f1b40aa29e8"), - ciphertext: b"", - tag: &hex!("36d8a309acbb8716c9c08c7f5de4911e"), - }, - GcmTV { - key: &hex!("f3e954a38956df890255f01709e457b33f4bfe7ecb36d0ee50f2500471eebcde"), - nonce: &hex!("9799abd3c52110c704b0f36a"), - plaintext: b"", - aad: &hex!("ddb70173f44157755b6c9b7058f40cb7"), - ciphertext: b"", - tag: &hex!("b323ae3abcb415c7f420876c980f4858"), - }, - GcmTV { - key: &hex!("0625316534fbd82fe8fdea50fa573c462022c42f79e8b21360e5a6dce66dde28"), - nonce: &hex!("da64a674907cd6cf248f5fbb"), - plaintext: b"", - aad: &hex!("f24d48e04f5a0d987ba7c745b73b0364"), - ciphertext: b"", - tag: &hex!("df360b810f27e794673a8bb2dc0d68b0"), - }, - GcmTV { - key: &hex!("28f045ac7c4fe5d4b01a9dcd5f1ad3efff1c4f170fc8ab8758d97292868d5828"), - nonce: &hex!("5d85de95b0bdc44514143919"), - plaintext: b"", - aad: &hex!("601d2158f17ab3c7b4dcb6950fbdcdde"), - ciphertext: b"", - tag: &hex!("42c3f527418cf2c3f5d5010ccba8f271"), - }, - GcmTV { - key: &hex!("19310eed5f5f44eb47075c105eb31e36bbfd1310f741b9baa66a81138d357242"), - nonce: &hex!("a1247120138fa4f0e96c992c"), - plaintext: b"", - aad: &hex!("29d746414333e0f72b4c3f44ec6bfe42"), - ciphertext: b"", - tag: &hex!("d5997e2f956df3fa2c2388e20f30c480"), - }, - GcmTV { - key: &hex!("886cff5f3e6b8d0e1ad0a38fcdb26de97e8acbe79f6bed66959a598fa5047d65"), - nonce: &hex!("3a8efa1cd74bbab5448f9945"), - plaintext: b"", - aad: &hex!("519fee519d25c7a304d6c6aa1897ee1eb8c59655"), - ciphertext: b"", - tag: &hex!("f6d47505ec96c98a42dc3ae719877b87"), - }, - GcmTV { - key: &hex!("6937a57d35fe6dc3fc420b123bccdce874bd4c18f2e7c01ce2faf33d3944fd9d"), - nonce: &hex!("a87247797b758467b96310f3"), - plaintext: b"", - aad: &hex!("ead961939a33dd578f8e93db8b28a1c85362905f"), - ciphertext: b"", - tag: &hex!("599de3ecf22cb867f03f7f6d9fd7428a"), - }, - GcmTV { - key: &hex!("e65a331776c9dcdf5eba6c59e05ec079d97473bcdce84daf836be323456263a0"), - nonce: &hex!("ca731f768da01d02eb8e727e"), - plaintext: b"", - aad: &hex!("d7274586517bf1d8da866f4a47ad0bcf2948a862"), - ciphertext: b"", - tag: &hex!("a8abe7a8085f25130a7206d37a8aaf6d"), - }, - GcmTV { - key: &hex!("77bb1b6ef898683c981b2fc899319ffbb6000edca22566b634db3a3c804059e5"), - nonce: &hex!("354a19283769b3b991b05a4c"), - plaintext: b"", - aad: &hex!("b5566251a8a8bec212dc08113229ff8590168800"), - ciphertext: b"", - tag: &hex!("e5c2dccf8fc7f296cac95d7071cb8d7d"), - }, - GcmTV { - key: &hex!("2a43308d520a59ed51e47a3a915e1dbf20a91f0886506e481ad3de65d50975b4"), - nonce: &hex!("bcbf99733d8ec90cb23e6ce6"), - plaintext: b"", - aad: &hex!("eb88288729289d26fe0e757a99ad8eec96106053"), - ciphertext: b"", - tag: &hex!("01b0196933aa49123eab4e1571250383"), - }, - GcmTV { - key: &hex!("2379b35f85102db4e7aecc52b705bc695d4768d412e2d7bebe999236783972ff"), - nonce: &hex!("918998c4801037b1cd102faa"), - plaintext: b"", - aad: &hex!("b3722309e0f066225e8d1659084ebb07a93b435d"), - ciphertext: b"", - tag: &hex!("dfb18aee99d1f67f5748d4b4843cb649"), - }, - GcmTV { - key: &hex!("98b3cb7537167e6d14a2a8b2310fe94b715c729fdf85216568150b556d0797ba"), - nonce: &hex!("bca5e2e5a6b30f18d263c6b2"), - plaintext: b"", - aad: &hex!("260d3d72db70d677a4e3e1f3e11431217a2e4713"), - ciphertext: b"", - tag: &hex!("d6b7560f8ac2f0a90bad42a6a07204bc"), - }, - GcmTV { - key: &hex!("30341ae0f199b10a15175d00913d5029526ab7f761c0b936a7dd5f1b1583429d"), - nonce: &hex!("dbe109a8ce5f7b241e99f7af"), - plaintext: b"", - aad: &hex!("fe4bdee5ca9c4806fa024715fbf66ab845285fa7"), - ciphertext: b"", - tag: &hex!("ae91daed658e26c0d126575147af9899"), - }, - GcmTV { - key: &hex!("8232b6a1d2e367e9ce1ea8d42fcfc83a4bc8bdec465c6ba326e353ad9255f207"), - nonce: &hex!("cd2fb5ff9cf0f39868ad8685"), - plaintext: b"", - aad: &hex!("02418b3dde54924a9628de06004c0882ae4ec3bb"), - ciphertext: b"", - tag: &hex!("d5308f63708675ced19b2710afd2db49"), - }, - GcmTV { - key: &hex!("f9a132a50a508145ffd8294e68944ea436ce0f9a97e181f5e0d6c5d272311fc1"), - nonce: &hex!("892991b54e94b9d57442ccaf"), - plaintext: b"", - aad: &hex!("4e0fbd3799da250fa27911b7e68d7623bfe60a53"), - ciphertext: b"", - tag: &hex!("89881d5f786e6d53e0d19c3b4e6887d8"), - }, - GcmTV { - key: &hex!("0e3746e5064633ea9311b2b8427c536af92717de20eeb6260db1333c3d8a8114"), - nonce: &hex!("f84c3a1c94533f7f25cec0ac"), - plaintext: b"", - aad: &hex!("8c0d41e6135338c8d3e63e2a5fa0a9667ec9a580"), - ciphertext: b"", - tag: &hex!("479ccfe9241de2c474f2edebbb385c09"), - }, - GcmTV { - key: &hex!("b997e9b0746abaaed6e64b63bdf64882526ad92e24a2f5649df055c9ec0f1daa"), - nonce: &hex!("f141d8d71b033755022f0a7d"), - plaintext: b"", - aad: &hex!("681d6583f527b1a92f66caae9b1d4d028e2e631e"), - ciphertext: b"", - tag: &hex!("b30442a6395ec13246c48b21ffc65509"), - }, - GcmTV { - key: &hex!("87660ec1700d4e9f88a323a49f0b871e6aaf434a2d8448d04d4a22f6561028e0"), - nonce: &hex!("2a07b42593cd24f0a6fe406c"), - plaintext: b"", - aad: &hex!("1dd239b57185b7e457ced73ebba043057f049edd"), - ciphertext: b"", - tag: &hex!("df7a501049b37a534098cb45cb9c21b7"), - }, - GcmTV { - key: &hex!("ea4792e1f1717b77a00de4d109e627549b165c82af35f33ca7e1a6b8ed62f14f"), - nonce: &hex!("7453cc8b46fe4b93bcc48381"), - plaintext: b"", - aad: &hex!("46d98970a636e7cd7b76fc362ae88298436f834f"), - ciphertext: b"", - tag: &hex!("518dbacd36be6fba5c12871678a55516"), - }, - GcmTV { - key: &hex!("34892cdd1d48ca166f7ba73182cb97336c2c754ac160a3e37183d6fb5078cec3"), - nonce: &hex!("ed3198c5861b78c71a6a4eec"), - plaintext: b"", - aad: &hex!("a6fa6d0dd1e0b95b4609951bbbe714de0ae0ccfa"), - ciphertext: b"", - tag: &hex!("c6387795096b348ecf1d1f6caaa3c813"), - }, - GcmTV { - key: &hex!("f4069bb739d07d0cafdcbc609ca01597f985c43db63bbaaa0debbb04d384e49c"), - nonce: &hex!("d25ff30fdc3d464fe173e805"), - plaintext: b"", - aad: &hex!("3e1449c4837f0892f9d55127c75c4b25d69be334baf5f19394d2d8bb460cbf2120e14736d0f634aa792feca20e455f11"), - ciphertext: b"", - tag: &hex!("805ec2931c2181e5bfb74fa0a975f0cf"), - }, - GcmTV { - key: &hex!("62189dcc4beb97462d6c0927d8a270d39a1b07d72d0ad28840badd4f68cf9c8b"), - nonce: &hex!("859fda5247c888823a4b8032"), - plaintext: b"", - aad: &hex!("b28d1621ee110f4c9d709fad764bba2dd6d291bc003748faac6d901937120d41c1b7ce67633763e99e05c71363fceca8"), - ciphertext: b"", - tag: &hex!("27330907d0002880bbb4c1a1d23c0be2"), - }, - GcmTV { - key: &hex!("59012d85a1b90aeb0359e6384c9991e7be219319f5b891c92c384ade2f371816"), - nonce: &hex!("3c9cde00c23912cff9689c7c"), - plaintext: b"", - aad: &hex!("e5daf473a470860b55210a483c0d1a978d8add843c2c097f73a3cda49ac4a614c8e887d94e6692309d2ed97ebe1eaf5d"), - ciphertext: b"", - tag: &hex!("048239e4e5c2c8b33890a7c950cda852"), - }, - GcmTV { - key: &hex!("4be09b408ad68b890f94be5efa7fe9c917362712a3480c57cd3844935f35acb7"), - nonce: &hex!("8f350bd3b8eea173fc7370bc"), - plaintext: b"", - aad: &hex!("2819d65aec942198ca97d4435efd9dd4d4393b96cf5ba44f09bce4ba135fc8636e8275dcb515414b8befd32f91fc4822"), - ciphertext: b"", - tag: &hex!("a133cb7a7d0471dbac61fb41589a2efe"), - }, - GcmTV { - key: &hex!("13cb965a4d9d1a36efad9f6ca1ba76386a5bb160d80b0917277102357ac7afc8"), - nonce: &hex!("f313adec42a66d13c3958180"), - plaintext: b"", - aad: &hex!("717b48358898e5ccfea4289049adcc1bb0db3b3ebd1767ac24fb2b7d37dc80ea2316c17f14fb51b5e18cd5bb09afe414"), - ciphertext: b"", - tag: &hex!("81b4ef7a84dc4a0b1fddbefe37f53852"), - }, - GcmTV { - key: &hex!("d27f1bebbbdef0edca393a6261b0338abbc491262eab0737f55246458f6668cc"), - nonce: &hex!("fc062f857886e278f3a567d2"), - plaintext: b"", - aad: &hex!("2bae92dea64aa99189de8ea4c046745306002e02cfb46a41444ce8bfcc329bd4205963d9ab5357b026a4a34b1a861771"), - ciphertext: b"", - tag: &hex!("5c5a6c4613f1e522596330d45f243fdd"), - }, - GcmTV { - key: &hex!("7b4d19cd3569f74c7b5df61ab78379ee6bfa15105d21b10bf6096699539006d0"), - nonce: &hex!("fbed5695c4a739eded97b1e3"), - plaintext: b"", - aad: &hex!("c6f2e5d663bfaf668d014550ef2e66bf89978799a785f1f2c79a2cb3eb3f2fd4076207d5f7e1c284b4af5cffc4e46198"), - ciphertext: b"", - tag: &hex!("7101b434fb90c7f95b9b7a0deeeb5c81"), - }, - GcmTV { - key: &hex!("d3431488d8f048590bd76ec66e71421ef09f655d7cf8043bf32f75b4b2e7efcc"), - nonce: &hex!("cc766e98b40a81519fa46392"), - plaintext: b"", - aad: &hex!("93320179fdb40cbc1ccf00b872a3b4a5f6c70b56e43a84fcac5eb454a0a19a747d452042611bf3bbaafd925e806ffe8e"), - ciphertext: b"", - tag: &hex!("3afcc336ce8b7191eab04ad679163c2a"), - }, - GcmTV { - key: &hex!("a440948c0378561c3956813c031f81573208c7ffa815114ef2eee1eb642e74c6"), - nonce: &hex!("c1f4ffe54b8680832eed8819"), - plaintext: b"", - aad: &hex!("253438f132b18e8483074561898c5652b43a82cc941e8b4ae37e792a8ed6ec5ce2bcec9f1ffcf4216e46696307bb774a"), - ciphertext: b"", - tag: &hex!("129445f0a3c979a112a3afb10a24e245"), - }, - GcmTV { - key: &hex!("798706b651033d9e9bf2ce064fb12be7df7308cf45df44776588cd391c49ff85"), - nonce: &hex!("5a43368a39e7ffb775edfaf4"), - plaintext: b"", - aad: &hex!("926b74fe6381ebd35757e42e8e557601f2287bfc133a13fd86d61c01aa84f39713bf99a8dc07b812f0274c9d3280a138"), - ciphertext: b"", - tag: &hex!("89fe481a3d95c03a0a9d4ee3e3f0ed4a"), - }, - GcmTV { - key: &hex!("c3aa2a39a9fef4a466618d1288bb62f8da7b1cb760ccc8f1be3e99e076f08eff"), - nonce: &hex!("9965ba5e23d9453d7267ca5b"), - plaintext: b"", - aad: &hex!("93efb6a2affc304cb25dfd49aa3e3ccdb25ceac3d3cea90dd99e38976978217ad5f2b990d10b91725c7fd2035ecc6a30"), - ciphertext: b"", - tag: &hex!("00a94c18a4572dcf4f9e2226a03d4c07"), - }, - GcmTV { - key: &hex!("14e06858008f7e77186a2b3a7928a0c7fcee22136bc36f53553f20fa5c37edcd"), - nonce: &hex!("32ebe0dc9ada849b5eda7b48"), - plaintext: b"", - aad: &hex!("6c0152abfa485b8cd67c154a5f0411f22121379774d745f40ee577b028fd0e188297581561ae972223d75a24b488aed7"), - ciphertext: b"", - tag: &hex!("2625b0ba6ee02b58bc529e43e2eb471b"), - }, - GcmTV { - key: &hex!("fbb56b11c51a093ce169a6990399c4d741f62b3cc61f9e8a609a1b6ae8e7e965"), - nonce: &hex!("9c5a953247e91aceceb9defb"), - plaintext: b"", - aad: &hex!("46cb5c4f617916a9b1b2e03272cb0590ce716498533047d73c81e4cbe9278a3686116f5632753ea2df52efb3551aea2d"), - ciphertext: b"", - tag: &hex!("4f3b82e6be4f08756071f2c46c31fedf"), - }, - GcmTV { - key: &hex!("b303bf02f6a8dbb5bc4baccab0800db5ee06de648e2fae299b95f135c9b107cc"), - nonce: &hex!("906495b67ef4ce00b44422fa"), - plaintext: b"", - aad: &hex!("872c6c370926535c3fa1baec031e31e7c6c82808c8a060742dbef114961c314f1986b2131a9d91f30f53067ec012c6b7"), - ciphertext: b"", - tag: &hex!("64dde37169082d181a69107f60c5c6bb"), - }, - GcmTV { - key: &hex!("29f5f8075903063cb6d7050669b1f74e08a3f79ef566292dfdef1c06a408e1ab"), - nonce: &hex!("35f25c48b4b5355e78b9fb3a"), - plaintext: b"", - aad: &hex!("107e2e23159fc5c0748ca7a077e5cc053fa5c682ff5269d350ee817f8b5de4d3972041d107b1e2f2e54ca93b72cd0408"), - ciphertext: b"", - tag: &hex!("fee5a9baebb5be0165deaa867e967a9e"), - }, - GcmTV { - key: &hex!("03ccb7dbc7b8425465c2c3fc39ed0593929ffd02a45ff583bd89b79c6f646fe9"), - nonce: &hex!("fd119985533bd5520b301d12"), - plaintext: b"", - aad: &hex!("98e68c10bf4b5ae62d434928fc6405147c6301417303ef3a703dcfd2c0c339a4d0a89bd29fe61fecf1066ab06d7a5c31a48ffbfed22f749b17e9bd0dc1c6f8fbd6fd4587184db964d5456132106d782338c3f117ec05229b0899"), - ciphertext: b"", - tag: &hex!("cf54e7141349b66f248154427810c87a"), - }, - GcmTV { - key: &hex!("57e112cd45f2c57ddb819ea651c206763163ef016ceead5c4eae40f2bbe0e4b4"), - nonce: &hex!("188022c2125d2b1fcf9e4769"), - plaintext: b"", - aad: &hex!("09c8f445ce5b71465695f838c4bb2b00624a1c9185a3d552546d9d2ee4870007aaf3007008f8ae9affb7588b88d09a90e58b457f88f1e3752e3fb949ce378670b67a95f8cf7f5c7ceb650efd735dbc652cae06e546a5dbd861bd"), - ciphertext: b"", - tag: &hex!("9efcddfa0be21582a05749f4050d29fe"), - }, - GcmTV { - key: &hex!("a4ddf3cab7453aaefad616fd65d63d13005e9459c17d3173cd6ed7f2a86c921f"), - nonce: &hex!("06177b24c58f3be4f3dd4920"), - plaintext: b"", - aad: &hex!("f95b046d80485e411c56b834209d3abd5a8a9ddf72b1b916679adfdde893044315a5f4967fd0405ec297aa332f676ff0fa5bd795eb609b2e4f088db1cdf37ccff0735a5e53c4c12173a0026aea42388a7d7153a8830b8a901cf9"), - ciphertext: b"", - tag: &hex!("9d1bd8ecb3276906138d0b03fcb8c1bb"), - }, - GcmTV { - key: &hex!("24a92b24e85903cd4aaabfe07c310df5a4f8f459e03a63cbd1b47855b09c0be8"), - nonce: &hex!("22e756dc898d4cf122080612"), - plaintext: b"", - aad: &hex!("2e01b2536dbe376be144296f5c38fb099e008f962b9f0e896334b6408393bff1020a0e442477abfdb1727213b6ccc577f5e16cb057c8945a07e307264b65979aed96b5995f40250ffbaaa1a1f0eccf394015f6290f5e64dfe5ca"), - ciphertext: b"", - tag: &hex!("0d7f1aed4708a03b0c80b2a18785c96d"), - }, - GcmTV { - key: &hex!("15276fc64438578e0ec53366b90a0e23d93910fec10dc3003d9b3f3fa72db702"), - nonce: &hex!("c5e931946d5caebc227656d2"), - plaintext: b"", - aad: &hex!("3f967c83ba02e77c14e9d41185eb87f172250e93edb0f82b6742c124298ab69418358eddefa39fedc3cade9d80f036d864a59ead37c87727c56c701a8cd9634469ff31c704f5ee39354157e6558467b92824da36b1c071bedfe9"), - ciphertext: b"", - tag: &hex!("a0ffa19adcf31d061cd0dd46d24015ef"), - }, - GcmTV { - key: &hex!("ec09804a048bb854c71618b5a3a1c590910fc8a68455139b719486d2280ea59a"), - nonce: &hex!("d0b1247e7121a9276ac18ca3"), - plaintext: b"", - aad: &hex!("66b1d39d414596308e866b04476e053b71acd1cd07ce80939577ebbeace0430f7e4c0c185fe1d97ac7569950c83db40bbed0f1d173e1aa0dc28b4773705032d97551f7fcef7f55e4b69f88df650032dfc5232c156641104b5397"), - ciphertext: b"", - tag: &hex!("8440e6d864ab778f9be478f203162d86"), - }, - GcmTV { - key: &hex!("4adf86bfa547725e4b80365a5a327c107040facfff007dc35102066bd6a995c4"), - nonce: &hex!("b1018cc331911255a55a0795"), - plaintext: b"", - aad: &hex!("053ca4428c990b4456d3c1895d5d52deff675896de9faa53d8cf241255f4a31dc3399f15d83be380256616e5af043abfb37552655adf4f2e68dda24bc3736951134f359d9c0e288bb798b6c3ea46239231a3cb280066db9862e7"), - ciphertext: b"", - tag: &hex!("c7424f38084930bfc5edc1fcf1e7608d"), - }, - GcmTV { - key: &hex!("3c92e0d1e39a3c766573c4646c768c402ccff48a56682a93433512abf0456e00"), - nonce: &hex!("d57f319e590191841d2b98bd"), - plaintext: b"", - aad: &hex!("840d9394aa240e52ba152151c12acd1cd44881e8549dc832b71a45da7efcc74fb7e844d9fec25e5d497b8fb8f47f328c8d99045a19e366e6ce5e19dc26f67a81a94fa6c97c314d886e7b56eff144c09f6fa519db6308bc73422e"), - ciphertext: b"", - tag: &hex!("cb4ef72dbda4914d7434f9686f823e2f"), - }, - GcmTV { - key: &hex!("b66ba39733888a9e0a2e30452844161dc33cb383c02ce16c4efad5452509b5b5"), - nonce: &hex!("937cb665e37059b2e40359f2"), - plaintext: b"", - aad: &hex!("dbcd9694a8834860034e8ede3a5bd419fcf91c005ad99f488aa623f581622093f9d41e6a68e20fd202f302bcfc4417ca89090bfcd4d5224e8ff4eb5bbae4ecb27baa239f59c2f99cd47c0a269c497906b41a8f320a3dd2dc2de2"), - ciphertext: b"", - tag: &hex!("bdc8249302d9d666cf7168317c118743"), - }, - GcmTV { - key: &hex!("2f9fcd1043455695638c991a1b1d35ad57c18ef0727322747b7991abc3d787f3"), - nonce: &hex!("d06cf548f62869f4bed7a318"), - plaintext: b"", - aad: &hex!("432023c12cf1f614e1005112a17dbe6c5d54022a95cf6335a5bc55004c75f09a5699739ecf928e1c78d03dad5096a17a084afe1cc22041bbdfb5985bd08b0dcc59d2b08cd86b7aad597c4cd7b4ba6d6a7370b83995a6511a1f9e"), - ciphertext: b"", - tag: &hex!("322eb84fb6884f10cfb766c2e3ec779e"), - }, - GcmTV { - key: &hex!("21c5839a63e1230c06b086341c96ab74585e69bced94332caeb1fa77d510c24f"), - nonce: &hex!("5ab6e5ed6ee733be7250858c"), - plaintext: b"", - aad: &hex!("c92f08e30f67d42516133c48e97b65cc9e124365e110aba5e7b2cbe83debcc99edf4eb0007af052bda22d85900271b1897af4fd9ace6a2d09d984ac3de79d05de0b105a81b12542b2c48e27d409fd6992dd062d6055d6fc66842"), - ciphertext: b"", - tag: &hex!("53b0e450309d146459f2a1e46c9d9e23"), - }, - GcmTV { - key: &hex!("25a144f0fdba184125d81a87e7ed82fad33c701a094a67a81fe4692dc69afa31"), - nonce: &hex!("8bf575c5c2b45b4efc6746e4"), - plaintext: b"", - aad: &hex!("2a367cb0d3b7c5b8320b3cf95e82b6ba0bba1d09a2055885dedd9ef5641623682212103238b8f775cce42ddfd4f66382f2c3a5e8d6dff9163ced83580a75705574026b55db90f75f8abb3014c9a707021dedc075da38bebbf0a0"), - ciphertext: b"", - tag: &hex!("0e2ce9cac8dfcedb0572ec6cab621efd"), - }, - GcmTV { - key: &hex!("42bc841b3b03a807cd366a35ecec8a6aebef7c4cba0ec8cb8da0da41df8ccef1"), - nonce: &hex!("1bd46f85df5f4b3a126ee315"), - plaintext: b"", - aad: &hex!("ede3dcddbdc7d8e5d034c01661332ec349cb4e7a9fbaaf7abe2c647587db86cd427ce66908e070bc49ef838747e06b45ac486dfbea6f8698b4625e21e69db8327ec05cfd74accbe67ab644948cdb554af179a1e264e08fe16641"), - ciphertext: b"", - tag: &hex!("633ab6aaf5b32b53a794f6be6262fc5f"), - }, - GcmTV { - key: &hex!("c25b8500be73210596fc4a9fb4d84d1a3379a91e3f0a6cc4177d996046627679"), - nonce: &hex!("b56c48c0c4cd318b20437002"), - plaintext: b"", - aad: &hex!("bcd14dd043fdc8c327957e1c1428698543ec8602521a7c74788d296d37d4828f10f90656883d2531c702ebda2dc0a68dab00154577454455fad986ff8e0973098dbf370ff703ed98222b945726ed9be7909210ddbc672e99fdd9"), - ciphertext: b"", - tag: &hex!("8171d4ff60fe7ef6de0288326aa73223"), - }, - GcmTV { - key: &hex!("dd95259bc8eefa3e493cb1a6ba1d8ee2b341d5230d50363094a2cc3433b3d9b9"), - nonce: &hex!("a1a6ced084f4f13990750a9e"), - plaintext: b"", - aad: &hex!("d46db90e13684b26149cb3b7f776e228a0538fa1892c418aaad07aa08d3076f4a52bee8f130ff560db2b8d1009e9260fa6233fc22733e050c9e4f7cc699062765e261dffff1159e9060b26c8065dfab04055b58c82c340d987c9"), - ciphertext: b"", - tag: &hex!("9e120b01899fe2cb3e3a0b0c05045940"), - }, - GcmTV { - key: &hex!("31bdadd96698c204aa9ce1448ea94ae1fb4a9a0b3c9d773b51bb1822666b8f22"), - nonce: &hex!("0d18e06c7c725ac9e362e1ce"), - plaintext: &hex!("2db5168e932556f8089a0622981d017d"), - aad: b"", - ciphertext: &hex!("fa4362189661d163fcd6a56d8bf0405a"), - tag: &hex!("d636ac1bbedd5cc3ee727dc2ab4a9489"), - }, - GcmTV { - key: &hex!("460fc864972261c2560e1eb88761ff1c992b982497bd2ac36c04071cbb8e5d99"), - nonce: &hex!("8a4a16b9e210eb68bcb6f58d"), - plaintext: &hex!("99e4e926ffe927f691893fb79a96b067"), - aad: b"", - ciphertext: &hex!("133fc15751621b5f325c7ff71ce08324"), - tag: &hex!("ec4e87e0cf74a13618d0b68636ba9fa7"), - }, - GcmTV { - key: &hex!("f78a2ba3c5bd164de134a030ca09e99463ea7e967b92c4b0a0870796480297e5"), - nonce: &hex!("2bb92fcb726c278a2fa35a88"), - plaintext: &hex!("f562509ed139a6bbe7ab545ac616250c"), - aad: b"", - ciphertext: &hex!("e2f787996e37d3b47294bf7ebba5ee25"), - tag: &hex!("00f613eee9bdad6c9ee7765db1cb45c0"), - }, - GcmTV { - key: &hex!("48e6af212da1386500454c94a201640c2151b28079240e40d72d2a5fd7d54234"), - nonce: &hex!("ef0ff062220eb817dc2ece94"), - plaintext: &hex!("c7afeecec1408ad155b177c2dc7138b0"), - aad: b"", - ciphertext: &hex!("9432a620e6a22307e06a321d66846fd4"), - tag: &hex!("e3ea499192f2cd8d3ab3edfc55897415"), - }, - GcmTV { - key: &hex!("79cd8d750fc8ea62a2714edcd9b32867c7c4da906c56e23a644552f5b812e75a"), - nonce: &hex!("9bbfdb81015d2b57dead2de5"), - plaintext: &hex!("f980ad8c55ebd31ee6f98f44e92bff55"), - aad: b"", - ciphertext: &hex!("41a34d1e759c859e91b8cf5d3ded1970"), - tag: &hex!("68cd98406d5b322571e750c30aa49834"), - }, - GcmTV { - key: &hex!("130ae450c18efb851057aaa79575a0a090194be8b2c95469a0e8e380a8f48f42"), - nonce: &hex!("b269115396f81b39e0c38f47"), - plaintext: &hex!("036cf36280dee8355c82abc4c1fdb778"), - aad: b"", - ciphertext: &hex!("09f7568fd8181652e556f0dda5a49ed5"), - tag: &hex!("d10b61947cae275b7034f5259ba6fc28"), - }, - GcmTV { - key: &hex!("9c7121289aefc67090cabed53ad11658be72a5372761b9d735e81d2bfc0e3267"), - nonce: &hex!("ade1702d2051b8dd203b5419"), - plaintext: &hex!("b95bcaa2b31403d76859a4c301c50b56"), - aad: b"", - ciphertext: &hex!("628285e6489090dde1b9a60674785003"), - tag: &hex!("9f516af3f3b93d610edbc5ba6e2d115f"), - }, - GcmTV { - key: &hex!("0400b42897011fc20fd2280a52ef905d6ebf1b055b48c97067bd786d678ec4ea"), - nonce: &hex!("0abfb0a41496b453358409d9"), - plaintext: &hex!("20c8230191e35f4e9b269d59cf5521f6"), - aad: b"", - ciphertext: &hex!("dd8c38087daffbbb3ebb57ebf5ee5f78"), - tag: &hex!("bfb07aa5049ee350ec6fb1397f37087b"), - }, - GcmTV { - key: &hex!("56690798978c154ff250ba78e463765f2f0ce69709a4551bd8cb3addeda087b6"), - nonce: &hex!("cf37c286c18ad4ea3d0ba6a0"), - plaintext: &hex!("2d328124a8d58d56d0775eed93de1a88"), - aad: b"", - ciphertext: &hex!("3b0a0267f6ecde3a78b30903ebd4ca6e"), - tag: &hex!("1fd2006409fc636379f3d4067eca0988"), - }, - GcmTV { - key: &hex!("8a02a33bdf87e7845d7a8ae3c8727e704f4fd08c1f2083282d8cb3a5d3cedee9"), - nonce: &hex!("599f5896851c968ed808323b"), - plaintext: &hex!("4ade8b32d56723fb8f65ce40825e27c9"), - aad: b"", - ciphertext: &hex!("cb9133796b9075657840421a46022b63"), - tag: &hex!("a79e453c6fad8a5a4c2a8e87821c7f88"), - }, - GcmTV { - key: &hex!("23aaa78a5915b14f00cf285f38ee275a2db97cb4ab14d1aac8b9a73ff1e66467"), - nonce: &hex!("4a675ec9be1aab9632dd9f59"), - plaintext: &hex!("56659c06a00a2e8ed1ac60572eee3ef7"), - aad: b"", - ciphertext: &hex!("e6c01723bfbfa398d9c9aac8c683bb12"), - tag: &hex!("4a2f78a9975d4a1b5f503a4a2cb71553"), - }, - GcmTV { - key: &hex!("fe647f72e95c469027f4d7778429a2e8e90d090268d4fa7df44f65c0af84190a"), - nonce: &hex!("4f40ae2a83a9b480e4686c90"), - plaintext: &hex!("31fd6cce3f0d2b0d18e0af01c4b5609e"), - aad: b"", - ciphertext: &hex!("54c769fd542f0d3022f1335a7c410b61"), - tag: &hex!("106cb7cbcd967da6cad646039c753474"), - }, - GcmTV { - key: &hex!("fce205515f0551b1797128a2132d8e002ea5ab1beb99c5e7e8329398cf478e10"), - nonce: &hex!("20209a0d4a3b9bfddeef39a0"), - plaintext: &hex!("7d663e31a2f6ffef17e536684dae2e87"), - aad: b"", - ciphertext: &hex!("6529712030fb659dc11ab719f6a4c402"), - tag: &hex!("58699464d062aba505508c576c4e07dd"), - }, - GcmTV { - key: &hex!("cd33003ff18f6f3369dd9a35381261ba660ce0a769864475152e677066540337"), - nonce: &hex!("20bffe9064ce76d275204138"), - plaintext: &hex!("acaf53d4dd2fe12cd44450b0d9adcc92"), - aad: b"", - ciphertext: &hex!("a669fda0444b180165f90815dc992b33"), - tag: &hex!("6e31f5a56c4790cedcc2368c51d0639b"), - }, - GcmTV { - key: &hex!("381873b5f9579d8241f0c61f0d9e327bb9f678691714aaa48ea7d92678d43fe7"), - nonce: &hex!("3fc8bec23603158e012d65e5"), - plaintext: &hex!("7b622e9b408fe91f6fa800ecef838d36"), - aad: b"", - ciphertext: &hex!("8ca4de5b4e2ab22431a009f3ddd01bae"), - tag: &hex!("b3a7f80e3edf322622731550164cd747"), - }, - GcmTV { - key: &hex!("92e11dcdaa866f5ce790fd24501f92509aacf4cb8b1339d50c9c1240935dd08b"), - nonce: &hex!("ac93a1a6145299bde902f21a"), - plaintext: &hex!("2d71bcfa914e4ac045b2aa60955fad24"), - aad: &hex!("1e0889016f67601c8ebea4943bc23ad6"), - ciphertext: &hex!("8995ae2e6df3dbf96fac7b7137bae67f"), - tag: &hex!("eca5aa77d51d4a0a14d9c51e1da474ab"), - }, - GcmTV { - key: &hex!("7da3bccaffb3464178ca7c722379836db50ce0bfb47640b9572163865332e486"), - nonce: &hex!("c04fd2e701c3dc62b68738b3"), - plaintext: &hex!("fd671cab1ee21f0df6bb610bf94f0e69"), - aad: &hex!("fec0311013202e4ffdc4204926ae0ddf"), - ciphertext: &hex!("6be61b17b7f7d494a7cdf270562f37ba"), - tag: &hex!("5e702a38323fe1160b780d17adad3e96"), - }, - GcmTV { - key: &hex!("a359b9584beec189527f8842dda6b6d4c6a5db2f889635715fa3bcd7967c0a71"), - nonce: &hex!("8616c4cde11b34a944caba32"), - plaintext: &hex!("33a46b7539d64c6e1bdb91ba221e3007"), - aad: &hex!("e1796fca20cb3d3ab0ade69b2a18891e"), - ciphertext: &hex!("b0d316e95f3f3390ba10d0274965c62b"), - tag: &hex!("aeaedcf8a012cc32ef25a62790e9334c"), - }, - GcmTV { - key: &hex!("8c83238e7b3b58278200b54940d779d0a0750673aab0bf2f5808dd15dc1a8c49"), - nonce: &hex!("70f8f4ebe408f61a35077956"), - plaintext: &hex!("6e57f8572dd5b2247410f0d4c7424186"), - aad: &hex!("e1cbf83924f1b8d1014b97db56c25a15"), - ciphertext: &hex!("4a11acb9611251df01f79f16f8201ffb"), - tag: &hex!("9732be4ad0569586753d90fabb06f62c"), - }, - GcmTV { - key: &hex!("fe21919bb320af8744c9e862b5b7cf8b81ad3ad1fb0e7d7d710a688d3eed154b"), - nonce: &hex!("38bc3917aa1925f40850c082"), - plaintext: &hex!("aea53b1ea79a71c3a4b83c92a0c979f1"), - aad: &hex!("f24102fa7e6b819bb3ff47f90844db9c"), - ciphertext: &hex!("2fb8b697bf8f7a2eea25fe702a3ae0a9"), - tag: &hex!("5be77e827737ad7c4f79e0e343fe010d"), - }, - GcmTV { - key: &hex!("499e8a3f39ac4abc62dd4e1a6133042e74785972b6b501bfaffefc8bb29fd312"), - nonce: &hex!("5c728dbbef9dcc0ff483e891"), - plaintext: &hex!("b44014c7fc6b3f15d126a881fbe2bd2b"), - aad: &hex!("82300dab592f840ae991efa3623a6203"), - ciphertext: &hex!("578fe5e1aef7619f392c027c838a239e"), - tag: &hex!("49fdc724f05eb56ea9e3fd14b61ad567"), - }, - GcmTV { - key: &hex!("2775d3e7a8fc665bb9a59edc22eb136add194824ed8f2adb449177404c739716"), - nonce: &hex!("73f16c054e166696df679a2e"), - plaintext: &hex!("c9f3bce40310b6c0a3fd62742e4f3617"), - aad: &hex!("23199a1c9b7244913952ca4f7e7444f4"), - ciphertext: &hex!("72c85c10756266d00a9a4340b2cb3137"), - tag: &hex!("5881e4565b42394e62d5daf0d1ebc593"), - }, - GcmTV { - key: &hex!("425a341c67e6d873870f54e2cc5a2984c734e81729c0dbaaeee050309f1ce674"), - nonce: &hex!("0c09b7b4e9e097317b791433"), - plaintext: &hex!("76dda644b3faca509b37def0319f30cc"), - aad: &hex!("4300a721547846761e4bf8df2b6ec1d6"), - ciphertext: &hex!("1dd80daa0fc9e47e43897c64a6663f5e"), - tag: &hex!("5d69b34d8c3b12f783faaea7e93685db"), - }, - GcmTV { - key: &hex!("dd5c48988a6e9f9f60be801ba5c090f224a1b53d6601ec5858eab7b7784a8d5e"), - nonce: &hex!("43562d48cd4110a66d9ca64e"), - plaintext: &hex!("2cda2761fd0be2b03f9714fce8d0e303"), - aad: &hex!("55e568309fc6cb0fb0e0e7d2511d4116"), - ciphertext: &hex!("f2cfb6f5446e7aa172adfcd66b92a98d"), - tag: &hex!("e099c64d2966e780ce7d2eaae97f47d8"), - }, - GcmTV { - key: &hex!("2bdad9c3e5de6e4e101b7f16e727c690db95eacf4b0ccbdec7aab6fb9fc80486"), - nonce: &hex!("a5cf3967d244074d2153c576"), - plaintext: &hex!("84c867ec36cc6fe3487f5192fdfd390b"), - aad: &hex!("6bdae72b5ed0e4d1f10064ebd02cf85c"), - ciphertext: &hex!("53c8fa437c1b5fa91abbd6508b3878ce"), - tag: &hex!("7859593d127324be8b9cf1d43ead4d82"), - }, - GcmTV { - key: &hex!("01e92afdb5d956be12d38b09252966c5728d26f3c72e54bb62bbc55ae590e716"), - nonce: &hex!("886e55364eeb90e87ac79bbe"), - plaintext: &hex!("6c6570385f3d6d937e54a3a2e95bc9eb"), - aad: &hex!("c76aabb7f44b942a81feb50249d2131a"), - ciphertext: &hex!("423b749a507f437b431114962180d352"), - tag: &hex!("54d859320a49281368297da7d4e37326"), - }, - GcmTV { - key: &hex!("46921319217598cb64256fe49abca1f18a9d1dbca360f8630afb5c6137cb42b5"), - nonce: &hex!("290827cf981415760ec3b37a"), - plaintext: &hex!("480d32b191c2e201aed03680f93ea2da"), - aad: &hex!("535ee80b12f581baaf8027e6e3900e31"), - ciphertext: &hex!("89ace4f73583fb1ac260dea99b54055e"), - tag: &hex!("7b8b8358363c175a66e6fb48d1bc2222"), - }, - GcmTV { - key: &hex!("e18cd9b01b59bc0de1502efb74c3642997fe7dfb8d80c8a73caffe7726807d33"), - nonce: &hex!("bd087b384c40841b3839ba02"), - plaintext: &hex!("62f7f3a12b8c5f6747fcfe192d850b19"), - aad: &hex!("fe69f837961b1d83f27fbf68e6791a1c"), - ciphertext: &hex!("bacfccf6397424e96caf761e71dd3e3a"), - tag: &hex!("9c9a5b65420f83e766c7c051680e8e58"), - }, - GcmTV { - key: &hex!("68ee463b3153d9a042e5e3685def6f90f7659a203441de337fb94831cbeae9b2"), - nonce: &hex!("9c4a9254c485236cf838de7e"), - plaintext: &hex!("73731054514f3fb0102c7a1df809f212"), - aad: &hex!("d55820e7acbb27d23c7df32938cf7d42"), - ciphertext: &hex!("13b7823cac37f40eb811e3c966d16a67"), - tag: &hex!("76288c33a66ff6451e2cec6c4ba4935e"), - }, - GcmTV { - key: &hex!("64bd594daf279e3172f9aa713b35b7fce8f43083792bc7d1f10919131f400a7b"), - nonce: &hex!("339a2c40e9d9507c34228649"), - plaintext: &hex!("2b794cb4c98450463a3e225ab33f3f30"), - aad: &hex!("2b9544807b362ebfd88146e2b02c9270"), - ciphertext: &hex!("434d703b8d1069ad8036288b7c2d1ae6"), - tag: &hex!("7d31e397c0c943cbb16cfb9539a6a17d"), - }, - GcmTV { - key: &hex!("83688deb4af8007f9b713b47cfa6c73e35ea7a3aa4ecdb414dded03bf7a0fd3a"), - nonce: &hex!("0b459724904e010a46901cf3"), - plaintext: &hex!("33d893a2114ce06fc15d55e454cf90c3"), - aad: &hex!("794a14ccd178c8ebfd1379dc704c5e208f9d8424"), - ciphertext: &hex!("cc66bee423e3fcd4c0865715e9586696"), - tag: &hex!("0fb291bd3dba94a1dfd8b286cfb97ac5"), - }, - GcmTV { - key: &hex!("013f549af9ecc2ee0259d5fc2311059cb6f10f6cd6ced3b543babe7438a88251"), - nonce: &hex!("e45e759a3bfe4b652dc66d5b"), - plaintext: &hex!("79490d4d233ba594ece1142e310a9857"), - aad: &hex!("b5fe530a5bafce7ae79b3c15471fa68334ab378e"), - ciphertext: &hex!("619443034e4437b893a45a4c89fad851"), - tag: &hex!("6da8a991b690ff6a442087a356f8e9e3"), - }, - GcmTV { - key: &hex!("4b2815c531d2fceab303ec8bca739a97abca9373b7d415ad9d6c6fa9782518cc"), - nonce: &hex!("47d647a72b3b5fe19f5d80f7"), - plaintext: &hex!("d3f6a645779e07517bd0688872e0a49b"), - aad: &hex!("20fd79bd0ee538f42b7264a5d098af9a30959bf5"), - ciphertext: &hex!("00be3b295899c455110a0ae833140c4d"), - tag: &hex!("d054e3997c0085e87055b79829ec3629"), - }, - GcmTV { - key: &hex!("2503b909a569f618f7eb186e4c4b81dbfe974c553e2a16a29aea6846293e1a51"), - nonce: &hex!("e4fa3dc131a910c75f61a38b"), - plaintext: &hex!("188d542f8a815695c48c3a882158958c"), - aad: &hex!("f80edf9b51f8fd66f57ce9af5967ec028245eb6e"), - ciphertext: &hex!("4d39b5494ca12b770099a8eb0c178aca"), - tag: &hex!("adda54ad0c7f848c1c72758406b49355"), - }, - GcmTV { - key: &hex!("6c8f34f14569f625aad7b232f59fa8b187ab24fadcdbaf7d8eb45da8f914e673"), - nonce: &hex!("6e2f886dd97be0e4c5bd488b"), - plaintext: &hex!("ac8aa71cfbf1e968ef5515531576e314"), - aad: &hex!("772ec23e49dbe1d923b1018fc2bef4b579e46241"), - ciphertext: &hex!("cb0ce70345e950b429e710c47d9c8d9b"), - tag: &hex!("9dceea98c438b1d9c154e5386180966d"), - }, - GcmTV { - key: &hex!("182fe560614e1c6adfd1566ac44856df723dcb7e171a7c5796b6d3f83ef3d233"), - nonce: &hex!("8484abca6877a8622bfd2e3c"), - plaintext: &hex!("92ca46b40f2c75755a28943a68a8d81c"), - aad: &hex!("2618c0f7fe97772a0c97638cca238a967987c5e5"), - ciphertext: &hex!("ed1941b330f4275d05899f8677d73637"), - tag: &hex!("3fe93f1f5ffa4844963de1dc964d1996"), - }, - GcmTV { - key: &hex!("65a290b2fabe7cd5fb2f6d627e9f1f79c2c714bffb4fb86e9df3e5eab28320ed"), - nonce: &hex!("5a5ed4d5592a189f0737cf47"), - plaintext: &hex!("662dda0f9c8f92bc906e90288100501c"), - aad: &hex!("ad1c7f7a7fb7f8fef4819c1dd1a67e007c99a87b"), - ciphertext: &hex!("8eb7cb5f0418da43f7e051c588776186"), - tag: &hex!("2b15399ee23690bbf5252fb26a01ae34"), - }, - GcmTV { - key: &hex!("7b720d31cd62966dd4d002c9ea41bcfc419e6d285dfab0023ba21b34e754cb2f"), - nonce: &hex!("e1fb1f9229b451b72f89c333"), - plaintext: &hex!("1aa2948ed804f24e5d783b1bc959e086"), - aad: &hex!("7fdae42d0cf6a13873d3092c41dd3a19a9ea90f9"), - ciphertext: &hex!("8631d3c6b6647866b868421b6a3a548a"), - tag: &hex!("a31febbe169d8d6f391a5e60ef6243a0"), - }, - GcmTV { - key: &hex!("a2aec8f3438ab4d6d9ae566a2cf9101ad3a3cc20f83674c2e208e8ca5abac2bb"), - nonce: &hex!("815c020686c52ae5ddc81680"), - plaintext: &hex!("a5ccf8b4eac22f0e1aac10b8d62cdc69"), - aad: &hex!("86120ce3aa81445a86d971fdb7b3b33c07b25bd6"), - ciphertext: &hex!("364c9ade7097e75f99187e5571ec2e52"), - tag: &hex!("64c322ae7a8dbf3d2407b12601e50942"), - }, - GcmTV { - key: &hex!("e5104cfcbfa30e56915d9cf79efcf064a1d4ce1919b8c20de47eab0c106d67c1"), - nonce: &hex!("d1a5ec793597745c7a31b605"), - plaintext: &hex!("7b6b303381441f3fdf9a0cf79ee2e9e0"), - aad: &hex!("9931678430ff3aa765b871b703dfcc43fb1b8594"), - ciphertext: &hex!("425d48a76001bed9da270636be1f770b"), - tag: &hex!("76ff43a157a6748250a3fdee7446ed22"), - }, - GcmTV { - key: &hex!("f461d1b75a72d942aa096384dc20cf8514a9ad9a9720660add3f318284ca3014"), - nonce: &hex!("d0495f25874e5714a1149e94"), - plaintext: &hex!("d9e4b967fdca8c8bae838a5da95d7cce"), - aad: &hex!("1133f372e3db22456e7ea92f29dff7f1d92864d3"), - ciphertext: &hex!("1df711e6fbcba22b0564c6e36051a3f7"), - tag: &hex!("f0563b7494d5159289b644afc4e8e397"), - }, - GcmTV { - key: &hex!("a9a98ef5076ceb45c4b60a93aeba102507f977bc9b70ded1ad7d422108cdaa65"), - nonce: &hex!("54a1bc67e3a8a3e44deec232"), - plaintext: &hex!("ede93dd1eaa7c9859a0f709f86a48776"), - aad: &hex!("10cfef05e2cd1edd30db5c028bd936a03df03bdc"), - ciphertext: &hex!("3d3b61f553ab59a9f093cac45afa5ac0"), - tag: &hex!("7814cfc873b3398d997d8bb38ead58ef"), - }, - GcmTV { - key: &hex!("d9e17c9882600dd4d2edbeae9a224d8588ff5aa210bd902d1080a6911010c5c5"), - nonce: &hex!("817f3501e977a45a9e110fd4"), - plaintext: &hex!("d74d968ea80121aea0d7a2a45cd5388c"), - aad: &hex!("d216284811321b7591528f0af5a3f2768429e4e8"), - ciphertext: &hex!("1587c8b00e2c197f32a21019feeee99a"), - tag: &hex!("63ea43c03d00f8ae5724589cb6f64480"), - }, - GcmTV { - key: &hex!("ec251b45cb70259846db530aff11b63be00a951827020e9d746659bef2b1fd6f"), - nonce: &hex!("e41652e57b624abd84fe173a"), - plaintext: &hex!("75023f51ba81b680b44ea352c43f700c"), - aad: &hex!("92dd2b00b9dc6c613011e5dee477e10a6e52389c"), - ciphertext: &hex!("29274599a95d63f054ae0c9b9df3e68d"), - tag: &hex!("eb19983b9f90a0e9f556213d7c4df0f9"), - }, - GcmTV { - key: &hex!("61f71fdbe29f56bb0fdf8a9da80cef695c969a2776a88e62cb3d39fca47b18e3"), - nonce: &hex!("77f1d75ab0e3a0ed9bf2b981"), - plaintext: &hex!("110a5c09703482ef1343396d0c3852d3"), - aad: &hex!("c882691811d3de6c927d1c9f2a0f15f782d55c21"), - ciphertext: &hex!("7e9daa4983283facd29a93037eb70bb0"), - tag: &hex!("244930965913ebe0fa7a0eb547b159fb"), - }, - GcmTV { - key: &hex!("e4fed339c7b0cd267305d11ab0d5c3273632e8872d35bdc367a1363438239a35"), - nonce: &hex!("0365882cf75432cfd23cbd42"), - plaintext: &hex!("fff39a087de39a03919fbd2f2fa5f513"), - aad: &hex!("8a97d2af5d41160ac2ff7dd8ba098e7aa4d618f0f455957d6a6d0801796747ba57c32dfbaaaf15176528fe3a0e4550c9"), - ciphertext: &hex!("8d9e68f03f7e5f4a0ffaa7650d026d08"), - tag: &hex!("3554542c478c0635285a61d1b51f6afa"), - }, - GcmTV { - key: &hex!("bd93c7bfc850b33c86484e04859ed374beaee9d613bdca6f072d1d182aeebd04"), - nonce: &hex!("6414c7749effb9af7e5c4762"), - plaintext: &hex!("b6de1699931f2252efc98d491d22ee12"), - aad: &hex!("76f43d5664c7ac1b4de43f2e2c4bc71f6918e0762f40e5dd5597ef4ff215855a4fd26d3ea6ccbd4e10789948fa692433"), - ciphertext: &hex!("a6c7e52f2018b823506e48064ffe6ee4"), - tag: &hex!("175e653c9036f66835f10cf1c82d1741"), - }, - GcmTV { - key: &hex!("df0125a826c7fe49243d89cbdd7562aafd2103fa2783cf901976b5f5d481cdcb"), - nonce: &hex!("f63c1461b2964929d035d9bf"), - plaintext: &hex!("cc27ff68f981e4d6fb1918427c3d6b9e"), - aad: &hex!("0bf602ec47593e44ac1b88244455fa04359e338057b0a0ba057cb506d546d4d6d8538640fe7dd3d5864bd33b5a33d768"), - ciphertext: &hex!("b8fa150af93078574ac7c4615f88647d"), - tag: &hex!("4584553ac3ccdf8b0efae517652d3a18"), - }, - GcmTV { - key: &hex!("d33ea320cec0e43dfc1e3d1d8ccca2dd7e30ad3ea18ad7141cc83645d18771ae"), - nonce: &hex!("540009f321f41d00202e473b"), - plaintext: &hex!("e56cdd522d526d8d0cd18131a19ee4fd"), - aad: &hex!("a41162e1fe875a81fbb5667f73c5d4cbbb9c3956002f7867047edec15bdcac1206e519ee9c238c371a38a485c710da60"), - ciphertext: &hex!("8b624b6f5483f42f36c85dc7cf3e9609"), - tag: &hex!("2651e978d9eaa6c5f4db52391ac9bc7c"), - }, - GcmTV { - key: &hex!("7f35f5979b23321e6449f0f5ef99f2e7b796d52d560cc77aabfb621dbf3a6530"), - nonce: &hex!("cf0f6f3eed4cf374da714c77"), - plaintext: &hex!("4e9f53affdb5b1e91bf423d29c54401a"), - aad: &hex!("a676d35d93e12bfe0603f6aef2c3dd892a9b1ad22d476c3509d313256d4e98e4dda4e46e93b54cf59c2b90608a8fb3ad"), - ciphertext: &hex!("1714d55ef83df2927ee95ff22f1d90e6"), - tag: &hex!("4962a91d1071dd2c05934968d21eb43c"), - }, - GcmTV { - key: &hex!("06ecc134993506cf539b1e797a519fe1d9f34321fe6a0b05f1936285c35c93a4"), - nonce: &hex!("f2190861d1140bd080d79906"), - plaintext: &hex!("519c1fc45a628ec16c515427796711f7"), - aad: &hex!("a04f2723c2521181437ad63f7910481d5de98f3e2561cec3a177bdbcb5048619738852e0fb212a3caa741a353e4e89a8"), - ciphertext: &hex!("b36c793224ce3bb1b54144398fbdedb6"), - tag: &hex!("0030e6e84f6f8eb474ce8e071c2953dd"), - }, - GcmTV { - key: &hex!("734fa8b423b91e0ecccc7f554480eef57a82423a9f92b28d464320fba405a71c"), - nonce: &hex!("a6b5c78bb5791f4d121390ce"), - plaintext: &hex!("b496a99b39e0e94bb5829cfc3d7b3856"), - aad: &hex!("9ce25ff9b55dfa04e4271999a47cba8af8e83a390b090d1c4306b40ce8882624b662ff5867896396789295c19ec80d07"), - ciphertext: &hex!("904081a40484bb6454fc52cb6674e737"), - tag: &hex!("6a0787cf3921a71c35b5054954527823"), - }, - GcmTV { - key: &hex!("d106280b84f25b294f71c261f66a65c2efd9680e19f50316d237975052796392"), - nonce: &hex!("cfc6aa2aeba468c66bf4553f"), - plaintext: &hex!("57e937f8b9b814e965bb569fcf63aaac"), - aad: &hex!("012a43f9903a3808bf34fd6f77d831d9154205ded589964cae60d2e49c856b7a4100a55c8cd02f5e476f62e988dcbd2b"), - ciphertext: &hex!("c835f5d4fd30fe9b2edb4aff24803c60"), - tag: &hex!("e88426bb4619807f18a9cc9839754777"), - }, - GcmTV { - key: &hex!("81eb63bc47aba313d964a5335cfb039051520b3112fa54cab368e5243947d450"), - nonce: &hex!("18cc5dd875753ff51cc6f441"), - plaintext: &hex!("45f51399dff6a0dcd43f35256616d6be"), - aad: &hex!("24f766c56777312494245a4e6c7dbebbae4026e0907eadbc20a488982678161de7b924473c0a81ee59a0fa6905952b33"), - ciphertext: &hex!("a2fc7b0784ec4233142f9cde12ab9e98"), - tag: &hex!("4e60b8561cacfe7133740cd2bddefaa0"), - }, - GcmTV { - key: &hex!("0a997863786a4e97332224ed484ffca508b166f0603687200d99fd6accd45d83"), - nonce: &hex!("7a9acabd4b8d3e1036293a07"), - plaintext: &hex!("9d2c9ff39f57c96ecce287c68c5cd6eb"), - aad: &hex!("525fc5ac7fe93c183a3ef7c75e3fbd52dce956855aff385966f4d79966bdb3ec2019c466584d21bfee74511a77d82adb"), - ciphertext: &hex!("238441c65b2a1c41b302da0f52d40770"), - tag: &hex!("c351d93ab9491cdfb7fa15e7a251de22"), - }, - GcmTV { - key: &hex!("acbfeb7c595b704960c1097e93d3906534c23444c8acc1f8e969ce6c3fe8a46b"), - nonce: &hex!("28922ecac3013806c11660e6"), - plaintext: &hex!("e0d8c52d60c6ed6980abd4348f3f96f1"), - aad: &hex!("b1fe886107013ebdeb19315a9d096ed81803951a508f56f68202a7df00bebae0742dd1128c200952a049ef0cd7cfe4e6"), - ciphertext: &hex!("56fe1cf2c1d193b9b33badbf846f52cc"), - tag: &hex!("1cb4c14f50a54a64813ffc810f31f9f8"), - }, - GcmTV { - key: &hex!("f6e768475c33269596da1f5a5a38547a885006bebb9134e21274d8456e9f5529"), - nonce: &hex!("3579e5ac51d1f1b82ea352ca"), - plaintext: &hex!("0aa481f856f8b96547672e5ae5370f9e"), - aad: &hex!("6929b6053ba148304366164f79b1b9f592c9cb9bce65094cec5cb8b0fc63e20d86b17c8bf5a7b089a63c5eac1824ee93"), - ciphertext: &hex!("b2f4edf5f0b0bfc590fead6239b0f2fb"), - tag: &hex!("2540ceb5ef247c95d63df84c46468533"), - }, - GcmTV { - key: &hex!("2ca76112300bed65b87ba6ec887cd514f4633c1c96565fec8e3e69ae2ba88401"), - nonce: &hex!("964864510a8c957dcfb97d2f"), - plaintext: &hex!("0aff24b4c5aa45b81ce08ec2439be446"), - aad: &hex!("5aebdfd153a18763f36ecc9e8e9a01cb7b3f21e435b35b0da937c67e87c9ec058d08060a95e1eda0a5ab6546cca45094"), - ciphertext: &hex!("03da1f5a1403dbdd9f75a26113608ec0"), - tag: &hex!("a1c215d0c552a6061aa2b60afc3667a6"), - }, - GcmTV { - key: &hex!("c0ff018b6c337dde685c8279cf6de59d7ce4b288032b819e074b671e72abbc91"), - nonce: &hex!("f12e6b1e85f87ef4c9ccbb7b"), - plaintext: &hex!("f7512bbfa2d40d14be71b70f70701c99"), - aad: &hex!("0577e8d28c0e9e5cde3c8b2a1a2aa8e2fc3ec8e96768405fcfbd623be7fc4e2e395c59b5b3a8ea117ef211320bc1f857"), - ciphertext: &hex!("0187b4c2d52486b4417e5a013d553e5e"), - tag: &hex!("dba451e7339be8ebed3ea9683d1b4552"), - }, - GcmTV { - key: &hex!("d90c6948ac2353867e943069196a2c4d0c4d51e34e2505661b1d76f3e5f17ac5"), - nonce: &hex!("07e5623f474e2f0fe9f4c7d2"), - plaintext: &hex!("8a9fb1b384c0d1728099a4f7cb002f07"), - aad: &hex!("0de97574ae1bc6d3ef06c6ce03513ca47dff4728803e0aacc50564ee32b775fd535f5c8c30186550d99bff6f384af2dd"), - ciphertext: &hex!("4234a3a9fb199c3b293357983e8ac30b"), - tag: &hex!("d51e6f071dbab126f5fc9732967108ef"), - }, - GcmTV { - key: &hex!("80d755e24d129e68a5259ec2cf618e39317074a83c8961d3768ceb2ed8d5c3d7"), - nonce: &hex!("7598c07ba7b16cd12cf50813"), - plaintext: &hex!("5e7fd1298c4f15aa0f1c1e47217aa7a9"), - aad: &hex!("0e94f4c48fd0c9690c853ad2a5e197c5de262137b69ed0cdfa28d8d12413e4ffff15374e1cccb0423e8ed829a954a335ed705a272ad7f9abd1057c849bb0d54b768e9d79879ec552461cc04adb6ca0040c5dd5bc733d21a93702"), - ciphertext: &hex!("5762a38cf3f2fdf3645d2f6696a7eead"), - tag: &hex!("8a6708e69468915c5367573924fe1ae3"), - }, - GcmTV { - key: &hex!("dda7977efa1be95a0e41ed8bcd2aa648621945c95a9e28b63919e1d92d269fc3"), - nonce: &hex!("053f6e1be42af8894a6e86a0"), - plaintext: &hex!("6fa9b08176e9963927afba1e5f969a42"), - aad: &hex!("cb5114a001989339657427eb88329d6ce9c69694dc91a69b7557d62184e57832ec76d162fc9c47490bb3d78e5899445cecf85d36cb1f07fed5a3d82aaf7e9590f3ed74ad13b13c8adbfc7f29d7b151448d6f29d11d0bd3d03b76"), - ciphertext: &hex!("d4adbff3ec8edade29b9a1b748c31b54"), - tag: &hex!("3b331733c753858c22d309ceb0f9488c"), - }, - GcmTV { - key: &hex!("d7da934ad057dc06bd1ec234fcc4efdc5119037a440b5827de25915f22dd47e5"), - nonce: &hex!("1b54c4ea37d2395ef70dcc72"), - plaintext: &hex!("86d5567658361198348207ede7a46da6"), - aad: &hex!("735de4596a80e64e38a12ab24ef73881d6ed3b533cb2c101025c3615acd2114150feeca84ade4e563bc4a300eb4a0cd97a184a293f0ac063e4f3c61e7fcdb331bcc6459fafaf0e2dda881f34eb717f4ee8c4b6890d3ef59721f3"), - ciphertext: &hex!("70a1c1d7c200ba5ae1b6f29917bb19f2"), - tag: &hex!("a25d51cccb198bed33de0b98df249c2d"), - }, - GcmTV { - key: &hex!("930ebb4b9b9c35094be374cc0b700c437b3c46b45d489a716c30f93cd5f986c9"), - nonce: &hex!("7a21e5febd82ec9b97bfbe83"), - plaintext: &hex!("980086665d08a365f6bbe20ae51116f7"), - aad: &hex!("9f2ed5f6cf9e2d6505d3c99a8f81a7dfc5658dd085eba966c8b3206230973a086ec36fe948573baee108fca941bce53dad73180877cd497976209c1adf8a9861f0215560df064caf0ef2f99445c11816f5b8deeafedd682b5fb2"), - ciphertext: &hex!("05baaefdeb0c33674a8064a2e9951aaf"), - tag: &hex!("2ec7efd2564d4e09a6ab852f3af49939"), - }, - GcmTV { - key: &hex!("70213d8949a65f463d13206071fab1b4c6b614fd3cee0d340d2d806de6714a93"), - nonce: &hex!("f8529d3e4f155cbb1ffb3d0a"), - plaintext: &hex!("47d47a5fd32a2a416f921cc7f00c0f81"), - aad: &hex!("112360db39b867dabaaa1d777bd881df2104b69fba15a4f37a832f5da38ad8a8c7c46db93e5b4eadf8b9a5a75508ad1457994c133c5ac85509eedfb13b90a2cf6c56a3c778582939362008608b08f9c4866a0e38744572114598"), - ciphertext: &hex!("b220b69bd851a17fbc5b725fb912f11e"), - tag: &hex!("4c3436943d58501c0826ae5827bc063e"), - }, - GcmTV { - key: &hex!("7a5834230ebbbf616630f2edb3ad4320182433c0546ac1e34bc9fd046e4a0ed9"), - nonce: &hex!("d27dd6212b6defdcbbc701bb"), - plaintext: &hex!("b4def1251427ade064a9614e353dda3f"), - aad: &hex!("3bc12f3bb88ea4f8a2184959bb9cd68911a78458b27e9b528ccecafe7f13f303dc714722875f26b136d18a3acfe82b53ad5e13c71f3f6db4b0fd59fffd9cd4422c73f2c31ac97010e5edf5950dc908e8df3d7e1cbf7c34a8521e"), - ciphertext: &hex!("88f94965b4350750e11a2dc139ccaef1"), - tag: &hex!("8a61f0166e70c9bfdd198403e53a68a5"), - }, - GcmTV { - key: &hex!("c3f10586f246aacadcce3701441770c03cfec940afe1908c4c537df4e01c50a0"), - nonce: &hex!("4f52faa1fa67a0e5f4196452"), - plaintext: &hex!("79d97ea3a2edd65045821ea745a44742"), - aad: &hex!("46f9a22b4e52e1526513a952dbee3b91f69595501e0177d50ff364638588c08d92fab8c58a969bdcc84c468d8498c4f06392b99ed5e0c484507fc48dc18d87c40e2ed848b43150be9d36f14cf2cef1310ba4a745adcc7bdc41f6"), - ciphertext: &hex!("560cf716e56190e9397c2f103629eb1f"), - tag: &hex!("ff7c9124879644e80555687d273c55d8"), - }, - GcmTV { - key: &hex!("ad70ebcf889e88b867ded0e4838ca66d6991499046a5671d99e91ed463ae78b1"), - nonce: &hex!("561e13b335718fcbee364100"), - plaintext: &hex!("82d5568872a4cef12238c0feb14f0fb4"), - aad: &hex!("e037bd7306eec185b9cb4e3bf295232da19005957086d62e6fb342284f05feaa0e81d6c95071e7e4d7b6aad7b00f7e7863dd0fc16303a8304bb8855305f28067f4be71eed95ff90e046382116229f0fd3d2c3ef2e87e0d0e7950"), - ciphertext: &hex!("771c6d091f8190ddbdb8886d9ce2ebd5"), - tag: &hex!("5009abd1ebeb26dab852346ea6d8aee3"), - }, - GcmTV { - key: &hex!("a452fa24b381e7165ee90f3371c2b0db2176f848a0354c78e92f2f1f89bbc511"), - nonce: &hex!("4bd904dfe18241eb5455d912"), - plaintext: &hex!("3f43df23ea940f3680a4b679b56db579"), - aad: &hex!("64f1a9d21deb183cff84f1aef5be83dbfc72e275f229eb5d59ace143605e8901dfa8f4724be24c86b5429bc84b629971fe1f9663b7537427b45dfb67d5f04506df4ee2c33d7f15af9f6e86058b131b7e6042b43a55bf6915f048"), - ciphertext: &hex!("c054974c4562f8536aef2734f10e09fc"), - tag: &hex!("2c5cafaf7b1f7581c5ec13080994e33c"), - }, - GcmTV { - key: &hex!("209ea3c4dd0420a4d63dbb72099a0202c9b0709f3b1221565f890511eef8005b"), - nonce: &hex!("43775083e4008816129f5d40"), - plaintext: &hex!("b4967f8c4fb1b34b6ff43a22d34fae5c"), - aad: &hex!("9abc653a2347fc6e5a8cb9bdc251dff7c56109797c387494c0ed55570330961eb5b11087603e08ad293d0dd55571008e62d1163f67cf829e28d27beba65553bd11d8838f8a7a5f1fe05500befbaf97839801e99ecf998882c707"), - ciphertext: &hex!("a8d22a6e25232938d3f8600a66be80da"), - tag: &hex!("2ef93cc03c17bbfb6626144697fd2422"), - }, - GcmTV { - key: &hex!("dabd63ac5274b26842c2695c9850d7accc1693ee2aeee1e2e1338bbbc5b80f87"), - nonce: &hex!("fd6790d620f12870b1d99b31"), - plaintext: &hex!("4a28048f5683679a557630a661f030e2"), - aad: &hex!("e4a06b9b205a7faadb21dc7fea8a0de0e013d717b61b24ec42f81afc8cdbc055573e971375da2fa5103a091317eab13b6a110ea211af257feabf52abafec23fd5b114b013d5c052199020573f8b7b7ae6958f733e87efa0426c2"), - ciphertext: &hex!("196d0345df259b47665bc233b798ebba"), - tag: &hex!("b0729d8b427ad048a7396cedf2257338"), - }, - GcmTV { - key: &hex!("b238df5e52e649d4b0a05e53020ac59e7d5bf49b8d04f8c30c356ed62dba9ed1"), - nonce: &hex!("f153f093c9a3479f999eda04"), - plaintext: &hex!("d48e779766afa73d7e04fc6fc3fa825e"), - aad: &hex!("45b5df0c15140e5ce7a19f4e02834e6027971e3e0e719626c29081a6301e95c71214345afac1908bb75ff2d3281261e6c5f41dc4e4796f054174a64f8e177f3f33321edfbd263e204135699428a09f34eb344211bfb9fac9afba"), - ciphertext: &hex!("b1989eb510843d8f35205dc3f949522f"), - tag: &hex!("616089990729228f673099514824d9b4"), - }, - GcmTV { - key: &hex!("f3dc2456d3b8947591a2d82b7319226b0f346cd4361bcc13b56da43e072a2774"), - nonce: &hex!("7a8acb5a84d7d01e3c00499e"), - plaintext: &hex!("ad075da908231ff9aae30daa6b847143"), - aad: &hex!("5e6be069effee27d34a8087c0d193f9f13e6440dc9fabfe24f6c867f831d06789d0dce92b2e3ff3ab9fe14202a8b42f384c25e3f3753dd503ec907a9b877f1707d64e4ac42909a7dee00c87c4a09d04de331515460ed101f5187"), - ciphertext: &hex!("9f224f2a1a1fbaade8b87b748971c0ac"), - tag: &hex!("cb5089d9dfaebf98e4b36ebc5f9a1a50"), - }, - GcmTV { - key: &hex!("f5a56b69a1562c77e8edebc327a20295c2eba7d406d899a622c53539626c9d72"), - nonce: &hex!("a395b8aca4508a6a5f3cb4d8"), - plaintext: &hex!("7de4638701bd2b600d7f8d26da7a75bc"), - aad: &hex!("2e4fca2b163e4403971716015386cd81bdd1e57f00f2936da408098341011f2644a38ddad799f70eaa54f6e430d4853ff2b9c44a35123670879a83120bd555c76b95b70de0c8054f9d08539a5795e70a2446d7b9fab3f7887c6b"), - ciphertext: &hex!("6508be2698ba9889b4e445b99190a5c5"), - tag: &hex!("3394106f257c2e15c815430f60bc24ba"), - }, - GcmTV { - key: &hex!("376371a780947256c52f07d80bb25a4d7e919ca8bd693b1a0ccbca748d2ce620"), - nonce: &hex!("27d7170f6f70f2fc40dfca78"), - plaintext: &hex!("7a279f9f8568b7c307490549b259226c"), - aad: &hex!("272c3559398ad774fa4b6895afc92870b2b92d310fa0debf0b7960e1fe38bfda64acd2fef26d6b177d8ab11d8afceee77374c6c18ad405d5ae323ad65fb6b04f0c809319133712f47636c5e042f15ed02f37ee7a10c643d7b178"), - ciphertext: &hex!("32284379d8c40ec18ee5774085d7d870"), - tag: &hex!("dcdee1a757f9758c944d296b1dabe7b2"), - }, - GcmTV { - key: &hex!("82c4f12eeec3b2d3d157b0f992d292b237478d2cecc1d5f161389b97f999057a"), - nonce: &hex!("7b40b20f5f397177990ef2d1"), - plaintext: &hex!("982a296ee1cd7086afad976945"), - aad: b"", - ciphertext: &hex!("ec8e05a0471d6b43a59ca5335f"), - tag: &hex!("113ddeafc62373cac2f5951bb9165249"), - }, - GcmTV { - key: &hex!("db4340af2f835a6c6d7ea0ca9d83ca81ba02c29b7410f221cb6071114e393240"), - nonce: &hex!("40e438357dd80a85cac3349e"), - plaintext: &hex!("8ddb3397bd42853193cb0f80c9"), - aad: b"", - ciphertext: &hex!("b694118c85c41abf69e229cb0f"), - tag: &hex!("c07f1b8aafbd152f697eb67f2a85fe45"), - }, - GcmTV { - key: &hex!("acad4a3588a7c5ec67832baee242b007c8f42ed7425d5a7e57b1070b7be2677e"), - nonce: &hex!("b11704ba368abadf8b0c2b98"), - plaintext: &hex!("2656b5fbec8a3666cad5f460b7"), - aad: b"", - ciphertext: &hex!("35c7114cabe39203df19413a99"), - tag: &hex!("16f4c7e5becf00db1223476a14c43ebc"), - }, - GcmTV { - key: &hex!("e5a0eb92cc2b064e1bc80891faf1fab5e9a17a9c3a984e25416720e30e6c2b21"), - nonce: &hex!("4742357c335913153ff0eb0f"), - plaintext: &hex!("8499893e16b0ba8b007d54665a"), - aad: b"", - ciphertext: &hex!("eb8e6175f1fe38eb1acf95fd51"), - tag: &hex!("88a8b74bb74fda553e91020a23deed45"), - }, - GcmTV { - key: &hex!("e78c477053f5dae5c02941061d397bc38dda5de3c9c8660a19de66c56c57fd22"), - nonce: &hex!("4f52c67c2bb748d192a5a4e2"), - plaintext: &hex!("91593e21e1f883af5c32d9be07"), - aad: b"", - ciphertext: &hex!("e37fbc56b0af200a7aa1bbe34e"), - tag: &hex!("29fe54eaaccf5e382601a15603c9f28c"), - }, - GcmTV { - key: &hex!("d0b13482037639aa797471a52b60f353b42e0ed271daa4f38a9293191cb78b72"), - nonce: &hex!("40fb7cae46adf3771bf3756a"), - plaintext: &hex!("938f40ac8e0e3b956aac5e9184"), - aad: b"", - ciphertext: &hex!("7dca05a1abe81928ccfb2164dd"), - tag: &hex!("5ea53ee170d9ab5f6cc047854e47cf60"), - }, - GcmTV { - key: &hex!("46da5ec688feead76a1ddcd60befb45074a2ef2254d7be26abdfd84629dbbc32"), - nonce: &hex!("9fb3b2b03925f476fc9a35f3"), - plaintext: &hex!("a41adc9fb4e25a8adef1180ec8"), - aad: b"", - ciphertext: &hex!("f55d4cbe9b14cea051fe7a2477"), - tag: &hex!("824753da0113d21186699dbb366c0589"), - }, - GcmTV { - key: &hex!("de3adf89f2fe246c07b0ce035f4af73cf2f65e5034dcfecfe9d7690ae1bdbd96"), - nonce: &hex!("a94aa4df0d8451644a5056c0"), - plaintext: &hex!("96825f6d6301db14a8d78fc2f4"), - aad: b"", - ciphertext: &hex!("784c6c3c24a022637cbc907c48"), - tag: &hex!("1eeaeddcdb4c72c4e8966950a319a4ef"), - }, - GcmTV { - key: &hex!("03c362288883327f6289bc1824e1c329ce485e0ce0e8d3405245283cf0f2eae2"), - nonce: &hex!("5de9f882c915c72729b2245c"), - plaintext: &hex!("f5c1c8d41de01d9c08d9f47ece"), - aad: b"", - ciphertext: &hex!("61af621953a126a2d1de559e92"), - tag: &hex!("fbdeb761238f2b70c5fb3dde0a7978f3"), - }, - GcmTV { - key: &hex!("e9ead7c59100b768aa6367d80c04a49bcd19fa8cc2e158dc8edeec3ea39b657d"), - nonce: &hex!("e81854665d2e0a97150fbab3"), - plaintext: &hex!("f8ccf69c52a873695367a42940"), - aad: b"", - ciphertext: &hex!("af2a7199602ee9ed2020c7b4cd"), - tag: &hex!("29715945ab1c034ecfcd91a466fc822e"), - }, - GcmTV { - key: &hex!("bc3e5b0fe423205904c32f870b9adec9d736a1616624043e819533fa97ed9b79"), - nonce: &hex!("335fe5180135673ce1a75144"), - plaintext: &hex!("295df9665eef999204f92acf24"), - aad: b"", - ciphertext: &hex!("3ac2a8a1b505a84677adfdb396"), - tag: &hex!("21f20aa0bb77d46d7290bc9c97a7a7bd"), - }, - GcmTV { - key: &hex!("ce889c73e0d64e272aba4bf9777afc7ee6457ddc9626ad931708ed7530d71b99"), - nonce: &hex!("fe61a6cda62fecd4e3b0c562"), - plaintext: &hex!("e2ae40ba5b4103b1a3066c1b57"), - aad: b"", - ciphertext: &hex!("185aa3508a37e6712b28191ec2"), - tag: &hex!("9ec1d567585aa467730cce92e536728e"), - }, - GcmTV { - key: &hex!("41e0cb1aed2fe53e0b688acb042a0c710a3c3ae3205b07c0af5191073abdfba9"), - nonce: &hex!("2f56e35216d88d34d08f6872"), - plaintext: &hex!("6482df0e4150e73dac51dc3220"), - aad: b"", - ciphertext: &hex!("9cb09b9927dfbe0f228e0a4307"), - tag: &hex!("fe7e87a596d63e2ab2aae46b64d466e8"), - }, - GcmTV { - key: &hex!("52a7662954d525cb00602b1ff5e937d41065ac4b921e284ffac73c04cfd462a0"), - nonce: &hex!("baffe73856ab1a47fb1feebf"), - plaintext: &hex!("9d0b5ca712f97caa1875d3ad87"), - aad: b"", - ciphertext: &hex!("fd01165380aedd6be226a66af3"), - tag: &hex!("35a492e39952c26456850b0172d723d1"), - }, - GcmTV { - key: &hex!("c4badb9766986faeb888b1db33060a9cd1f02e1afe7aaaea072d905750cb7352"), - nonce: &hex!("cc6966e9d81a298a561416d4"), - plaintext: &hex!("de68fb51731b45e7c2c5063923"), - aad: b"", - ciphertext: &hex!("f5be41f2c8c32e01098d433057"), - tag: &hex!("c82b1b012916ab6ed851d59829dad8ab"), - }, - GcmTV { - key: &hex!("dad89d9be9bba138cdcf8752c45b579d7e27c3dbb40f53e771dd8cfd500aa2d5"), - nonce: &hex!("cfb2aec82cfa6c7d89ee72ff"), - plaintext: &hex!("b526ba1050177d05b0f72f8d67"), - aad: &hex!("6e43784a91851a77667a02198e28dc32"), - ciphertext: &hex!("8b29e66e924ecae84f6d8f7d68"), - tag: &hex!("1e365805c8f28b2ed8a5cadfd9079158"), - }, - GcmTV { - key: &hex!("0d35d3dbd99cd5e088caf686b1cead9defe0c6001463e92e6d9fcdc2b0dcbaf6"), - nonce: &hex!("f9139eb9368d69ac48479d1f"), - plaintext: &hex!("5e2103eb3e739298c9f5c6ba0e"), - aad: &hex!("825cc713bb41c789c1ace0f2d0dd3377"), - ciphertext: &hex!("8ff3870eec0176d9f0c6c1b1a2"), - tag: &hex!("344234475538dc78c01f249f673e0862"), - }, - GcmTV { - key: &hex!("d35d64f1872bdcb422228f0d63f8e48977ed68d143f648ae2cd852f944b0e6dd"), - nonce: &hex!("0b2184aadbe8b515924dda5e"), - plaintext: &hex!("c8f999aa1a08871d74db490cf3"), - aad: &hex!("888f328d9e9eebbb9cb2704b5b880d66"), - ciphertext: &hex!("ad0d5e7c1065a34b27a256d144"), - tag: &hex!("8c8e7076950f7f2aeba62e1e761650d5"), - }, - GcmTV { - key: &hex!("9484b7ce3c118a8a2d556c2f7ba41fca34f60c9ea1070171459c9e7487c9537e"), - nonce: &hex!("87bc033522ae84d2abe863c5"), - plaintext: &hex!("14d8004793190563825e273dda"), - aad: &hex!("07ee18737b9bf8223979a01c59a90eb4"), - ciphertext: &hex!("43034a2c57ccacc367796d766a"), - tag: &hex!("4c981ca8b6e9e52092f5435e7ef55fbb"), - }, - GcmTV { - key: &hex!("4f4539e4a80ec01a14d6bb1bae0010f8a8b3f2cd0ac01adf239a9b2b755f0614"), - nonce: &hex!("2b6f00ce1570432bf52fdcac"), - plaintext: &hex!("820cc9389e7e74ca1cbb5a5fe6"), - aad: &hex!("0d72a13effe40544c57cc18005b998cb"), - ciphertext: &hex!("99553fdf3e777e2a4b3b6a5538"), - tag: &hex!("3cbf51640a3a93c3662c738e98fb36a2"), - }, - GcmTV { - key: &hex!("2f5e93ee24a8cd2fc6d3765f12d2179ddb8397783e136af9e0ac75f16fca451e"), - nonce: &hex!("0dc3c70a191f3722641fd701"), - plaintext: &hex!("4e96463793cdeda403668c4aee"), - aad: &hex!("ebab30cbcc99905354e4ee6f07c7db87"), - ciphertext: &hex!("ab03f8ca7b1b150bdc26d4e691"), - tag: &hex!("020546afff4290c4c8ef7fc38035ebfd"), - }, - GcmTV { - key: &hex!("a902e15d06ef5ad334d0ec6502e936ee53ef3f3608f7708848b11cefa92983d1"), - nonce: &hex!("b9f3e966efa43ab4aca1f2d8"), - plaintext: &hex!("393ff3dfe51cd43543e4e29fcc"), - aad: &hex!("2eaa35c00bf1cf8a81919bd04b43fd97"), - ciphertext: &hex!("7e8928b450c622ac8efe29d5a0"), - tag: &hex!("5a285de95990aef171629350bbcaf46e"), - }, - GcmTV { - key: &hex!("96657976da7692004e271b594e8304f77db9c9e77859246bb30a16239ba76a53"), - nonce: &hex!("79226100afea30644876e79a"), - plaintext: &hex!("2b0833a065c3853ee27c8968d0"), - aad: &hex!("ede7a9072a0086b9a1e55d900747cf76"), - ciphertext: &hex!("19373168f1a4052a57c6b8146f"), - tag: &hex!("debbf044325384b90a0c442d95455fb9"), - }, - GcmTV { - key: &hex!("630ea13eb5f52378b976ba2662f824dc622920759a15d2e341c446b03ea7bd5c"), - nonce: &hex!("0f9ebe47682f93d44c4db314"), - plaintext: &hex!("5c734964878a4250a3bf61fdd6"), - aad: &hex!("5ad8e9cffe622e9f35bdb185473868e5"), - ciphertext: &hex!("67cb6d943340d002d3323fcc4e"), - tag: &hex!("f5dc0f88f236560c4e2a6d6c15d3c0de"), - }, - GcmTV { - key: &hex!("c64f8a3ac230dce61b53d7b584f2309384274d4b32d404bc0c491f129781e52d"), - nonce: &hex!("7f4b3bcf763f9e2d08516a6d"), - plaintext: &hex!("fe581128ae9832d27ec58bd7ac"), - aad: &hex!("89ed6945547ee5998de1bb2d2f0bef1e"), - ciphertext: &hex!("81d7a8fdaf42b5716b892199c9"), - tag: &hex!("8183aaff4c0973fe56c02c2e0c7e4457"), - }, - GcmTV { - key: &hex!("dd73670fb221f7ee185f5818065e22dda3780fc900fc02ef00232c661d7bffce"), - nonce: &hex!("c33de65344cfbf228e1652bd"), - plaintext: &hex!("ada4d98147b30e5a901229952a"), - aad: &hex!("e1a5e52427f1c5b887575a6f2c445429"), - ciphertext: &hex!("6ed4e4bd1f953d47c5288c48f4"), - tag: &hex!("404e3a9b9f5ddab9ee169a7c7c2cf7af"), - }, - GcmTV { - key: &hex!("f6c5d9562b7dbdd0bf628ddc9d660c27841b06a638f56601f408f23aa2f66f4e"), - nonce: &hex!("67280bcb945ba6eda1c6c80a"), - plaintext: &hex!("f4caead242d180fbd2e6d32d0c"), - aad: &hex!("5b33716567b6c67b78ea5cd9349bcaaf"), - ciphertext: &hex!("fdfa39517d89ea47e6ccb0f831"), - tag: &hex!("91f9b540ca90e310a1f5c12c03d8c25e"), - }, - GcmTV { - key: &hex!("ce1d242f13de7638b870e0aa85843ea43a9255a4fa4d32057347f38e0267daeb"), - nonce: &hex!("86562be4621b4d5eb1983075"), - plaintext: &hex!("d20e59a8ef1a7de9096c3e6746"), - aad: &hex!("d48a9490a0b7deb023460608b7db79ce"), - ciphertext: &hex!("35ce69fb15d01159c52266537c"), - tag: &hex!("dc48f7b8d3feeeb26fcf63c0d2a889ec"), - }, - GcmTV { - key: &hex!("512753cea7c8a6165f2ebbd3768cc7b951029bd527b126233cf0841aff7568c7"), - nonce: &hex!("b79221802d8d97978041fe84"), - plaintext: &hex!("c63d6c1006b615275c085730b1"), - aad: &hex!("22fa0605b955a33468f3e60160b907f2"), - ciphertext: &hex!("bdb5d7f24732bdba1d2a429108"), - tag: &hex!("fca923d2941a6fd9d596b86c3afb0ad9"), - }, - GcmTV { - key: &hex!("e7b18429e3edded2d992ca27afab99e438b8aff25fc8460201fabe08e7d48ec2"), - nonce: &hex!("9db9b7320aaac68538e37bf7"), - plaintext: &hex!("c4713bc67a59928eee50039901"), - aad: &hex!("283e12a26e1646087b5b9d8c123dde1f"), - ciphertext: &hex!("a5932f92bda107d28f2a8aaa74"), - tag: &hex!("9a1357fd8ed21fe14d1ca2e597c3ef17"), - }, - GcmTV { - key: &hex!("69b458f2644af9020463b40ee503cdf083d693815e2659051ae0d039e606a970"), - nonce: &hex!("8d1da8ab5f91ccd09205944b"), - plaintext: &hex!("f3e0e09224256bf21a83a5de8d"), - aad: &hex!("036ad5e5494ef817a8af2f5828784a4bfedd1653"), - ciphertext: &hex!("c0a62d77e6031bfdc6b13ae217"), - tag: &hex!("a794a9aaee48cd92e47761bf1baff0af"), - }, - GcmTV { - key: &hex!("97431e565e8370a4879de962746a2fd67eca868b1c8e51eece2c1f94f74af407"), - nonce: &hex!("17fb63066e2726d282ecc610"), - plaintext: &hex!("e21629cc973fbe40176e621d9d"), - aad: &hex!("78e7374da7c77be5938de8dd76cf0308618306a9"), - ciphertext: &hex!("80dbd469de480389ba6c2fca52"), - tag: &hex!("4e284abb8b4f9f13c7497ae56df05fa5"), - }, - GcmTV { - key: &hex!("2b14ad68f442f7f92a72c7ba909bcf995c827b439d39a02f77c9bf8f84ab04dc"), - nonce: &hex!("4c847ea59f83d82b0ac0bc37"), - plaintext: &hex!("b3c4b26ebbfc717f51e874587d"), - aad: &hex!("8eb650f662be23191e88f1cd0422e57453090e21"), - ciphertext: &hex!("3e288478688e60178920090814"), - tag: &hex!("a928dc026986823062f37ec825c67b95"), - }, - GcmTV { - key: &hex!("11f41bf7d4b9ac7b0035ce54481ed1502ff05cfae02ffba9e502f61bfe785351"), - nonce: &hex!("06f5cf8c12c236e094c32014"), - plaintext: &hex!("bee374a32293cad5e1b28419b3"), - aad: &hex!("d15cbde6290b7723625c99ffa82a9c4c03ed214d"), - ciphertext: &hex!("3f8122deb6dbe0ff596441203d"), - tag: &hex!("60ef7f3723710b9ab744f8eea00267f7"), - }, - GcmTV { - key: &hex!("18ca572da055a2ebb479be6d6d7164e78f592b159cdea76e9fe208062d7b3fa1"), - nonce: &hex!("1b041e534ae20748262f3929"), - plaintext: &hex!("cda2fa0015361ecf684c6ba7d1"), - aad: &hex!("e8a925d7ce18dd456b071cb4c46655940efbe991"), - ciphertext: &hex!("740d8d578e2e7522c31019f471"), - tag: &hex!("f2eeb5af1bfedd10570a137fe2566c3f"), - }, - GcmTV { - key: &hex!("0de2ac5bfec9e8a859c3b6b86dde0537029cdca2d0844bf3e1d98f370e199be1"), - nonce: &hex!("1778e308e0221288f1eb4c5a"), - plaintext: &hex!("575d93a3416763cbd371b5a671"), - aad: &hex!("1362264f5655f71986aa788efd48f6fc13bb6ab4"), - ciphertext: &hex!("8f8df7ca83bf876b63c78e2c9a"), - tag: &hex!("16c74e315aab97efafbe95c9dcaa2d0c"), - }, - GcmTV { - key: &hex!("b381535a085bc4808fa7a139c7204e8a87c7145dfc8f3900df1fa9a9844fab35"), - nonce: &hex!("21ddc54d3c633f4a344a0e42"), - plaintext: &hex!("e4d958cee583010bbfd3a53021"), - aad: &hex!("7ac3ba600e08363ddb57c45a8670bb4abb869db0"), - ciphertext: &hex!("c42c81a312759cdb032aafe852"), - tag: &hex!("0c472591db3df8a7c67164591542dcc9"), - }, - GcmTV { - key: &hex!("29f21e5029ea4964b96dc6f4c34b2df4cce02f2fcf0f168ffd470e7858e0a0ad"), - nonce: &hex!("63a1c1ccc328280a90ff96fe"), - plaintext: &hex!("dc12113764c13c21432ca1ba33"), - aad: &hex!("454f447433f0948581956c4be1b19d932e89b492"), - ciphertext: &hex!("1cb45aac5def93daef806b781e"), - tag: &hex!("f4b0723c89607b66c392049ba042db63"), - }, - GcmTV { - key: &hex!("2733d3aa52a9d70a9fbd6ce2364bb5f9004902aa5eeb17446e08f2bdcc41db15"), - nonce: &hex!("196c4addb84a58beb3674a7a"), - plaintext: &hex!("cbc50cafda2544bcd291e8a025"), - aad: &hex!("c9826fe31f29b55b9d0f9da9795869a1a98befe5"), - ciphertext: &hex!("7a89cc58ccb97ad3e54ca4a9c8"), - tag: &hex!("3990d9aba210182996fdbd91c2ae4801"), - }, - GcmTV { - key: &hex!("0c4b9005b407415c19672bcd0ebe169f66fe404f22529baf55568e0901e94922"), - nonce: &hex!("e51381e959a1f5688c938576"), - plaintext: &hex!("c6179bd3451d9299b727e8bd0a"), - aad: &hex!("0b512faeb4da740dcc1e30d3c7ea61035e8570b7"), - ciphertext: &hex!("4d3fe086c990f16020b4c5eed6"), - tag: &hex!("9ff2297845814719f851ab0943117efb"), - }, - GcmTV { - key: &hex!("fee442ba37c351ec094a48794216a51d208c6a5ba0e5bdb8f3c0f0dfc1e4ed63"), - nonce: &hex!("a666f2f0d42214dbaa6a2658"), - plaintext: &hex!("a2cf3ea0e43e435261cb663a3b"), - aad: &hex!("7198c12810345403862c5374092cc79b669baecc"), - ciphertext: &hex!("713d4050f8c7fd63c0c1bf2ad9"), - tag: &hex!("250a35e2b45ba6b0fe24512f8213d8cb"), - }, - GcmTV { - key: &hex!("77f754d0cf7dbdaf75cfe965ab131e8cd39087ee6d986dec4ad2ff08ebd7f14b"), - nonce: &hex!("e28a14f3107ca190d824ed5f"), - plaintext: &hex!("54a97a74889e55d8043451c796"), - aad: &hex!("1decf0cbc50a9da6dad4a785a941e4b95ce5aaa8"), - ciphertext: &hex!("eedbf8dd81eb19184589dcb157"), - tag: &hex!("7749edd752fab7e50dbc3b0b47678bf6"), - }, - GcmTV { - key: &hex!("0523f232001e68bd65a79837bbaf70ec2e20851301d8e12fddb5926acb2100cb"), - nonce: &hex!("2bb8d5cb3ceb15107582e1fa"), - plaintext: &hex!("6b4cdc9f9c5082d86a1d2e68fe"), - aad: &hex!("1f55bba71cb63df431ef8832c77499ee3c502067"), - ciphertext: &hex!("079fe90ef517ed2f614a3cd8ce"), - tag: &hex!("539c30590a2527f1d52dfae92920794c"), - }, - GcmTV { - key: &hex!("54c56ee869ebb112a408717eb40af6937fe51eb061b42277a10537e7db346b6a"), - nonce: &hex!("5bfb63e2f3e5b2e1b4343480"), - plaintext: &hex!("75f9496b8d0ca96ed3af02dcab"), - aad: &hex!("740ab07b9c5de2afa37f0788ae5230535c18203d"), - ciphertext: &hex!("827902e58c4c8b7af976f61842"), - tag: &hex!("036ee6473c2138f2a2c2841438cb0edc"), - }, - GcmTV { - key: &hex!("d968ffdbed6ffc259b4310e2e97e42d877ef5d86d2169928c51031983779a485"), - nonce: &hex!("633d0d8d3613c83b40df99dd"), - plaintext: &hex!("08cfc65fea9b07f0c01d29dfdf"), - aad: &hex!("9aadc8d8975ec0a3f5c960ce72aaec8ef0b42034"), - ciphertext: &hex!("7b450f162bdedc301b96a3ac36"), - tag: &hex!("970d97344b1451f3f969aeb972d352e6"), - }, - GcmTV { - key: &hex!("5f671466378f470ba5f5160e2209f3d95a48b7e560625d5a08654414de23aee2"), - nonce: &hex!("6b3c08a663d04132243dd96c"), - plaintext: &hex!("c428592d9f8a7f107ec4d0df05"), - aad: &hex!("12965559c31d538f937bda6eee9c93b0387318dc5d9496fb1c3a0b9b978dbfebff2a5823974ee9d679834dbe59f7ec51"), - ciphertext: &hex!("1d8d7fe4357080c817303ce19c"), - tag: &hex!("e88d6b566fdc7b4fd62106bd2eb806ec"), - }, - GcmTV { - key: &hex!("fbcc2e7faa4295080e40b141bef829ba9d34e0691231ad6c62b5109009d74b5e"), - nonce: &hex!("7f35d9ec651c5b0966573e2f"), - plaintext: &hex!("cdd251d449551fec080425d565"), - aad: &hex!("6330d16002a8fd51762043f2df06ecc9c535c96ebe33526d8faf767c2c2af3cd01f4e02fa102f15ce0236d9c9cef26de"), - ciphertext: &hex!("514c5523024dd4c7d59bd73b15"), - tag: &hex!("d3a399843e5776aa348e3e5e56482fff"), - }, - GcmTV { - key: &hex!("04ef660ec041f5c0c24209f959ccf1a2a7cdb0dba22b134ea9f75e6f1efdae4a"), - nonce: &hex!("0f5f6fbca29358217c8a6b67"), - plaintext: &hex!("0835b312191f30f931e65aa05f"), - aad: &hex!("505e205d13ec945391c7d6516af86255e82f38433f40404d4f1e42d23b33eb9e6dea5820dad60622d3a825fc8f01a5d2"), - ciphertext: &hex!("5ddc0f5963f0290c1a0fb65be7"), - tag: &hex!("106d1f8d26abe4b4b1e590cd5d85e737"), - }, - GcmTV { - key: &hex!("42d3ff74284395fb9db9b8c7a444fa400f7fc6b985a7fec2478667c7f17cf3ba"), - nonce: &hex!("89230fbed59d1226a093ad28"), - plaintext: &hex!("d8339e3618ba57a243a27c85d6"), - aad: &hex!("60342f97310446266b2e47b18e008979d07fc181151ac0939b495e7f31de1d0e74042532840ab91686efd7a402d27a94"), - ciphertext: &hex!("9bb6fa36fa167016109d521ac0"), - tag: &hex!("600909ef32ca62951ecbdc811caa7778"), - }, - GcmTV { - key: &hex!("e115c6468606a5f9b8e9a7c220d7d7684d686c9210a669770b6e4bf24447cd17"), - nonce: &hex!("029c7c9ee2d3ab26843e8b41"), - plaintext: &hex!("7abf84842f9867cfc5eabc7032"), - aad: &hex!("1befd9f97f99fc096deafde5e158ac86716c0ba32454988fe48ba4737684361849a221c03fc0948cb25b5f29d6a0cb2a"), - ciphertext: &hex!("851c7047fb09646fbddb824531"), - tag: &hex!("d0ac4110c8d768f0a804ecda387cfa30"), - }, - GcmTV { - key: &hex!("56552f0cef34673a4c958ff55ad0b32c6ababa06cb3ae90178ab1c9a1f29c0e5"), - nonce: &hex!("b34d24935407e8592247ffff"), - plaintext: &hex!("dbd6cc358b28ab66a69f5238d4"), - aad: &hex!("b199437da189486a8fd1c2fa1fe3ebbb116f0ef41415bb7c8065272fb0b2fe8edca9cd0d4255d467e77f2834be557474"), - ciphertext: &hex!("76dc8d035e5ca4001e4e3fcb18"), - tag: &hex!("49c01f735da1131cd42b01b746fd38de"), - }, - GcmTV { - key: &hex!("d4f405ba556e6fe74b7e6dbdd7a8eae36376d1ca7a98d567d108729aeae5c326"), - nonce: &hex!("df6637c98a6592843e0b81ef"), - plaintext: &hex!("abe87641e9a5169f90179d3099"), - aad: &hex!("a5328cbabdfe6c3c1d4f5152189072dade71e2bacd857d3ce37ee9e3161eb0f20de5a29b7999fd9c7c60cdc03751bd1b"), - ciphertext: &hex!("06f9cf9677745e78c6c02bf06b"), - tag: &hex!("5a3a76da0703c24a9588afb2ac1a9e13"), - }, - GcmTV { - key: &hex!("4f667f65ea4569264456e25de498579036d6a604c18baf770bb626d8a1c68e4f"), - nonce: &hex!("43e27d275abefdd45137c8ff"), - plaintext: &hex!("eaa2498ce27e5658489381b6ec"), - aad: &hex!("264b807b4631d7c87ee9f1507082f5af9218f531b4630141f3c94939aa7cf81c71ea540783995560bf7e6e02d196227f"), - ciphertext: &hex!("bac018bf2e7090e7f217ab3365"), - tag: &hex!("13e5a16a9ce7a88cda640de2c4fdc07e"), - }, - GcmTV { - key: &hex!("f5624a166759ef0b8168af6565649f7797fa92476e008c407458101e75831312"), - nonce: &hex!("521ca79ffc8930349abfc052"), - plaintext: &hex!("1fab3def2ea13e815f8746093b"), - aad: &hex!("6e2771ecd637361cb6b947148910f7d9206d6af176c510bb5dd5bc9b97ac015fb05537affbc1756625715374172fb456"), - ciphertext: &hex!("ca72ff15a7eb62a2839bcf0c43"), - tag: &hex!("475fff6d9e2382583c9614020844b92a"), - }, - GcmTV { - key: &hex!("ac1383a3c783d3d0667e944cbe1a6159647b96afa922557eb1cb6407546b98ca"), - nonce: &hex!("70366112dbe1bd905b900e3a"), - plaintext: &hex!("b8dd871f9d866867efbe551c3b"), - aad: &hex!("b7c1865927737bee802415277cf1a25b7380774a9d27b6a3253f077d36e9c4142df2bbbf3c03414ac09161626ce9367c"), - ciphertext: &hex!("ba181874380841791f64881534"), - tag: &hex!("c5641edf42c446873372bbbde1146642"), - }, - GcmTV { - key: &hex!("f37499d9b6ad2e7618e30a23082673008f3ae1938b9397c02a4da2453fb7e403"), - nonce: &hex!("18e112ea6a998d6f9705f7e0"), - plaintext: &hex!("31560b2114a248ffe0696fa130"), - aad: &hex!("736f1a71fb259f46c6519bb87451f238f47d80c74a016604499b02568f1c7bedf70f9597d7b62c1698c4f2631f4e9706"), - ciphertext: &hex!("0163f558be0142ebabde29a7bc"), - tag: &hex!("45579ce07ee64cdac3a7a42109ff44e7"), - }, - GcmTV { - key: &hex!("50b7f5118ef7ee22b107d93ceab9881ef9658931e80385d1ae92501b95e47d62"), - nonce: &hex!("d5113665039169978b7dc4db"), - plaintext: &hex!("9ba4cd5e600277f4c786ce827e"), - aad: &hex!("68ff6c63e94cb7dd2b8413662a56c88dc130b79b8b2e2388c1089b61fa51ea37819109b5ef64da1250f5d6b5d74cc392"), - ciphertext: &hex!("67842199482b28be56f7570d11"), - tag: &hex!("79e03841843fe32337b7c7409a2153bc"), - }, - GcmTV { - key: &hex!("d396941c9c59e6a7bc7d71bd56daf6eabe4bfb943151cdb9895103384b8f38b4"), - nonce: &hex!("f408f8c21f3825d7a87643ed"), - plaintext: &hex!("dc8ad6a50812b25f1b0af70bee"), - aad: &hex!("947bd9a904e03fdd2c91d038d26d48ac6e32afcad908eacd42a25f6240964656d5a493242d3f8a19119a4cd9957d9c42"), - ciphertext: &hex!("57e6d821079bb8a79027f30e25"), - tag: &hex!("de8c26d5a3da6be24b3f6ea1e2a0f0c6"), - }, - GcmTV { - key: &hex!("eca22b3a29761fd40031b5c27d60adbcfac3a8e87feb9380c429cfbcda27bd06"), - nonce: &hex!("4e6fe3d1f989d2efb8293168"), - plaintext: &hex!("44d6a6af7d90be17aac02049a4"), - aad: &hex!("29beb1f0bb6b568268b9c7383991a09fd03da7e1639488169e4f58ec6451cad6d4c62086eee59df64e52a36527733d8c"), - ciphertext: &hex!("9aaa295bb3db7f6335a4c8cf2f"), - tag: &hex!("55f7577163a130c0dbcde243ef216885"), - }, - GcmTV { - key: &hex!("fa3ce8b099f3a392624bc433b5265235b65c0952cfc54817be2a8003d057903c"), - nonce: &hex!("3168b4e50efe96b3d3aed600"), - plaintext: &hex!("84ed3ccd428d3783ecea180b3b"), - aad: &hex!("d451fa64d73b7d7eee8f8143c40bab8e3f7a58ee018acda23224974f64ac7e1e389f5058ec08664bf56492b932d15f42"), - ciphertext: &hex!("ee2bd527568a4e7537c8f939b6"), - tag: &hex!("f4615f7dfdffec8a2d52c992456210ad"), - }, - GcmTV { - key: &hex!("ff9506b4d46ba54128876fadfcc673a4c927c618ea7d95cfcaa508cbc8f7fc66"), - nonce: &hex!("3742ad2208a0484345eee1be"), - plaintext: &hex!("7fd0d6cadc92cad27bb2d7d8c8"), - aad: &hex!("f1360a27fdc244be8739d85af6491c762a693aafe668c449515fdeeedb6a90aeee3891bbc8b69adc6a6426cb12fcdebc32c9f58c5259d128b91efa28620a3a9a0168b0ff5e76951cb41647ba4aa1f87fac0d97ac580e42cffc7e"), - ciphertext: &hex!("bdb8346b28eb4d7226493611a6"), - tag: &hex!("7484d827b767647f44c7f94a39f8175c"), - }, - GcmTV { - key: &hex!("b65b7e27d552395f5f444f031d5118fb4fb226deb0ac4e82784b901accd43c51"), - nonce: &hex!("2493026855dd1c1da3af7b7e"), - plaintext: &hex!("8adb36d2c2358e505b5d214ad0"), - aad: &hex!("b78e31b1793c2b758494e9c8ae7d3cee6e3697d40ffba04d3c6cbe25e12eeea365d5a2e7b46c4245771b7b2eb2062a640e6090d9f81caf63207865bb4f2c4cf6af81898560e3aeaa521dcd2c336e0ec57faffef58683a72710b9"), - ciphertext: &hex!("e9f19548d66ef3c16b711b89e2"), - tag: &hex!("e7efc91bbf2026c3519010d65628e85f"), - }, - GcmTV { - key: &hex!("8e4f8859bc838f6a2e7deb1849c27b78878285e00caad67507d5e79105669674"), - nonce: &hex!("e71d0ebb691a4c31fdd9879c"), - plaintext: &hex!("bd1713d8d276df4367bf3cbb81"), - aad: &hex!("47ca6cef3ca77997ef1b04e3721469be440ad6812aa3674ae92ca016b391d202e29932edfa83029eccae90bd8dbe4b434e7304b28fe249b380b2c3c49324fd5b3e469e3e135abc1c9fd77828b409c7482e6a63461c0597b14e5c"), - ciphertext: &hex!("eecbfb74e314628b0e3f827881"), - tag: &hex!("c9ea890294d7e10f38b88e7c7493c5f8"), - }, - GcmTV { - key: &hex!("2530cdcb2a789000822588a31bdc87c09234838da2d6ae1259c7049186525f11"), - nonce: &hex!("0c509faa257dbb0e743a53ac"), - plaintext: &hex!("a8edc524930ce4c20897c66f75"), - aad: &hex!("92a92cb8c1984ede806028cc45ac95574167ee83f03a707cc4b0fb8ad70907e0016e38b650f4a75bc83a625e3c670701d43bfb0326d1c4fe7c68410733c0c874c920389d164bf67a9032e2e837f5e9e324b97932d1f917ba7dca"), - ciphertext: &hex!("1f658c7a1f41152b22999ed1b7"), - tag: &hex!("cf3e4fef775d9c6ff3695be2602a90d8"), - }, - GcmTV { - key: &hex!("54c31fb2fb4aab6a82ce188e6afa71a3354811099d1203fe1f991746f7342f90"), - nonce: &hex!("f0fe974bdbe1694dc3b06cc6"), - plaintext: &hex!("fbb7b3730f0cd7b1052a5298ee"), - aad: &hex!("2879e05e0f8dd4402425eabb0dc184dcd07d46d54d775d7c2b76b0f76b3eed5f7ca93c6ae71bf509c270490269ea869ed6603fdf7113aa625648ab8ed88210f8b30ec9c94bca5757ca3d77491f64109101165636b068e3095cb4"), - ciphertext: &hex!("3a5a2a8aa93c462cfb80f1f728"), - tag: &hex!("59ef9d54ee01fb6cd54bd0e08f74096f"), - }, - GcmTV { - key: &hex!("8084061d0f7858a65c3a3557215ed46f1590278ca97a45dcb095d2a0979f2e3f"), - nonce: &hex!("6973898b1a8f72856415675b"), - plaintext: &hex!("200d0445cb09eb52f54d2f74c6"), - aad: &hex!("8b543e294546848c3308ccea302f0238b7dffc1706d03657c190ea745cc75bcd5a437993e787828ea7fe42fea1d5c6f7229a72ea65f0d0c190989a590ab49c54726633282c689eef8cf852af263b5edf63e449fd5440730003ca"), - ciphertext: &hex!("ec242c358193ca6187c89aa7a5"), - tag: &hex!("967428ac6956525ba81d5901ed259407"), - }, - GcmTV { - key: &hex!("2aad7db82df4a0d2ec85218da9d61ade98f65feeb8532d8eb728ef8aac220da6"), - nonce: &hex!("029ac2e9f5dc3d76b0d1f9df"), - plaintext: &hex!("ba363912f6207c54aecd26b627"), - aad: &hex!("d6f4b6232d17b1bc307912a15f39ccd185a465ee860279e98eb9551498d7b078271ebabdda7211e6b4ab187043171bc5e4bf9ffcf89a778430e735df29410a45ca354b0003433c6bc8593ee82e7c096a32eac76d11daa7d64150"), - ciphertext: &hex!("bfcad32611da275a0f0821517c"), - tag: &hex!("9ea37bdcaafad69caf06d67fb18dd001"), - }, - GcmTV { - key: &hex!("f70bb950ab56f12f1efc2376d32a59d16ef3ef5969e0106ab40cc314c9b0c7e8"), - nonce: &hex!("3b3b29ba422c2bacafeeb8b3"), - plaintext: &hex!("029929277043dc0379f152a484"), - aad: &hex!("464ac0c84b9ff17a0e7c39a65f89682a89b8787553a6275f0d55effaabef2114072c739f9831a5d5a5133ae4de14eb51346b318b255a1bff57e50c433e1e69a00fe1a8b6f6b621d515d670d89e148f6b65d6eb4c54878cb819ce"), - ciphertext: &hex!("c0b97d6d1a95d708d6dc7d2b95"), - tag: &hex!("322eb4395bf4d4dd070b8f9f6195f8ee"), - }, - GcmTV { - key: &hex!("f4950f01cb11fdd9afb297f7aa852facfac354ff96557befa5f657678de6cefb"), - nonce: &hex!("aba7d864f29cbc449cd93e33"), - plaintext: &hex!("e6daf59ef54ac7405984fc4c4e"), - aad: &hex!("852f624cea7a8c20e189e0c79f578c0d770c4bf7c4e691649eba992f6de89d7bf2078aff94803a3dc62628e02a80a01957722e2a931fc56283d84ab68ce11ae867835c2d9700df130048ea8eaaca41f1a9059be2acaea6e0f7f2"), - ciphertext: &hex!("d01d36ff8009b4082279abb906"), - tag: &hex!("d9a36c8008493bd95c09049299cbd075"), - }, - GcmTV { - key: &hex!("714261ef4f02fb4efb0e6b5aed96d7b3ceac6551a57cf679da179c01aac5ee0e"), - nonce: &hex!("3b7d15c7fd877461a789255a"), - plaintext: &hex!("815de8b0382fe60cb0d3782ee9"), - aad: &hex!("7621e58152336ee415f037f2e11581fe4da545c18d6e80177d5ab5dda89a25e8057d6fccec3757759a6e86e631080c0b17baa8be0b8fe579d3bfa97937ee242b6faacfc09425853df4dc26bc263ed1083a73ffc978c9265f8069"), - ciphertext: &hex!("29c566ea47752a31a380fd0e7c"), - tag: &hex!("b279340a384dbbae721c54e9183b3966"), - }, - GcmTV { - key: &hex!("53459ba5a2e49d1a7c2fb6ad9e6961b4dbe5158cb9266eff425d6dcccaaf8073"), - nonce: &hex!("3c97dc635a75fbe2c33c9a41"), - plaintext: &hex!("03fbfe5842ed781990ca8be728"), - aad: &hex!("7fe308afe58a927680bee3368301f4dc7c47811fc09f1b9922a092a497b9c6b67c857fdcc32da1011acb110b3c1475bef303f1a609479485cc400ee8f38381c45d078708ad49f226f95dd9c81478d1ee2b53c3b906d96f8ddd76"), - ciphertext: &hex!("5865e5a1ec711732a4ee871bff"), - tag: &hex!("856a653ec214178096bed423e30a36e9"), - }, - GcmTV { - key: &hex!("f0501583c226d2519ed23fcc6f2cffd2f013eb91aa07b3a5a2073d6e2bd10cef"), - nonce: &hex!("29a922ad9bdeddc2e298b99f"), - plaintext: &hex!("035eb6922345c02a81435d9e77"), - aad: &hex!("d84f54bac09ea92afe0a7335cb0bb5f68425490fd2fb6c3b99218f49856ed427ec902e510b899d54951fe84cdbfd112608d1e999f64ecc9cd4be3a0114c1c34875dbf35a1b0be421659f99d69b32e968cebfca6f95837e3edeb4"), - ciphertext: &hex!("095971f99af467805a62bfb882"), - tag: &hex!("d5ff2b7beac260e517ea3eca13ff1e77"), - }, - GcmTV { - key: &hex!("78e6789b596c71cb3becc833cf823d2ebb18ca2e26c27e26a55ef95df7353971"), - nonce: &hex!("65da9c7a9f17b11246bcf8db"), - plaintext: &hex!("003e82a147df3c953400f87ab5"), - aad: &hex!("d49aee7ffd31e7c8d831d97ae894a00473adbc5071f6099d567caaef85c295d5143a1316ff82753cc35d3efc60f7e5101ddd811336b404d598f6c439cce6b47fcbebb15d1c342e4151b355025a03b4397260b4a7e6444fa57b5b"), - ciphertext: &hex!("abcceced40209fc30a5590fee8"), - tag: &hex!("0a203973b81375949ebd932597efd495"), - }, - GcmTV { - key: &hex!("816b3e6ca31d59688c20bcd1fa4285197735d8734289ca19a4730e56f1631ccf"), - nonce: &hex!("4c191ac994f86985c180ccd4"), - plaintext: &hex!("b2060dd86bc307133b7d365830"), - aad: &hex!("b3dcd643c68ccce186570c63288c8722b8a13dfaf9e71f44f1eeb454a44dddf5f955540cd46c9f3b6f820588f71936d7a8c54c7b7bc43f58bb48e6416149feae7a3f8d8198a970811627489266a871e8cb87878cdb3a48be65f5"), - ciphertext: &hex!("53e65880ad0012a75f1188996f"), - tag: &hex!("9ca8a71a45eb4402a6b03106bae330d1"), - }, - GcmTV { - key: &hex!("a07ba57478061bd7abddd762971cf2e47141891f76c3d1c150b53eee5704557d"), - nonce: &hex!("5adfb85b2d9e239c5146501d"), - plaintext: &hex!("67c8824c1837cfdec6edcd719c"), - aad: &hex!("937b3ed73e67ca0b02f9eb736a668362d4d0447c15f6083099a7f90c7c49318dd72f6baa74da22ff53b56c24fb9a1b1d6c4e29f4ac4d917220ebe3c8d760999da7be9e1e8f6a171133640c9196f9ee3cdb76a5a342a95a05c8c4"), - ciphertext: &hex!("1eb85c6682850e849eb37927e5"), - tag: &hex!("8079f705cf551a5484132cd0f0c5297c"), - }, - GcmTV { - key: &hex!("268ed1b5d7c9c7304f9cae5fc437b4cd3aebe2ec65f0d85c3918d3d3b5bba89b"), - nonce: &hex!("9ed9d8180564e0e945f5e5d4"), - plaintext: &hex!("fe29a40d8ebf57262bdb87191d01843f4ca4b2de97d88273154a0b7d9e2fdb80"), - aad: b"", - ciphertext: &hex!("791a4a026f16f3a5ea06274bf02baab469860abde5e645f3dd473a5acddeecfc"), - tag: &hex!("05b2b74db0662550435ef1900e136b15"), - }, - GcmTV { - key: &hex!("c772a8d5e9f3384f16be2c34bf9afd9ebf86b69e6f610cd195a9db169e9be17e"), - nonce: &hex!("9b8e079f9971d7352e6810a3"), - plaintext: &hex!("7f13fcaf0db79d792823a9271b1213a98d116eff7e8e3c86ddeb6a0a03f13afa"), - aad: b"", - ciphertext: &hex!("d29e2bf3518668a14f17a3e4e76e1b43685734b801118d33a23238f34d18aa40"), - tag: &hex!("8e02b0b7d172cf5e2578f5b30fac2e7a"), - }, - GcmTV { - key: &hex!("d5924b31676e2354fe7dafffaf529749598ea1bf5e4c44f5b60240e09d8036aa"), - nonce: &hex!("5d847784f0bcd79cb84fcf1d"), - plaintext: &hex!("6fd80c8f0d4de081a93c16b84dec697a1e4f9d80a6af497c561572645eac0d63"), - aad: b"", - ciphertext: &hex!("282cc9d2308a443019cfdc4d79854accc7731ee36902bafe3ffaca6484327b82"), - tag: &hex!("4dc5e0f2ab91bdfd31f2bdcf06af9667"), - }, - GcmTV { - key: &hex!("b328c6d7946221a08c4f0509b52992a139890cdd8eae1956851f110c49602cb5"), - nonce: &hex!("1a433c33ca12ce26cf3dffff"), - plaintext: &hex!("217bdc314a4d335c72b5267b424fc8e31f4bb118e6cfaeacf5548f4ba8f51980"), - aad: b"", - ciphertext: &hex!("a322944e07bf84ab424ffa75fd0309e8691c9036b08f344ba76ce0774f43b351"), - tag: &hex!("14dd6b1c2b224533ccc9fee8d2881358"), - }, - GcmTV { - key: &hex!("c2080965d21d229c0d0d6c56cbce83880120c21a48172a64560b90dc4ce1ffbe"), - nonce: &hex!("928d6c0195f5f0974f38730b"), - plaintext: &hex!("864397271e1b242aa1dff38e78aa89353e1554ba907318a0aaad44f26fcd567d"), - aad: b"", - ciphertext: &hex!("7de4f941f44bd0f268b2a47b9c4927cc10537bbed739d52ab099fde4033041d1"), - tag: &hex!("b51a59931817257619e7be1091128c49"), - }, - GcmTV { - key: &hex!("dd6b7e2584edf1f1e6c2c0dd1f72161a92d2cba99856554f820de1256d48c099"), - nonce: &hex!("fe9d553c75067e8dbae1ab67"), - plaintext: &hex!("f9f86f7762859f11d6e7ef56178657ddcded532843446f86a23eac35aa2dd3c0"), - aad: b"", - ciphertext: &hex!("f7aaa1711c8092783b05b4e5e6c9c6944e991bd59c94b9d0356df00a66e2db5b"), - tag: &hex!("c61edd176c8322a01d8c5f3df09252e9"), - }, - GcmTV { - key: &hex!("37f39137416bafde6f75022a7a527cc593b6000a83ff51ec04871a0ff5360e4e"), - nonce: &hex!("a291484c3de8bec6b47f525f"), - plaintext: &hex!("fafd94cede8b5a0730394bec68a8e77dba288d6ccaa8e1563a81d6e7ccc7fc97"), - aad: b"", - ciphertext: &hex!("44dc868006b21d49284016565ffb3979cc4271d967628bf7cdaf86db888e92e5"), - tag: &hex!("01a2b578aa2f41ec6379a44a31cc019c"), - }, - GcmTV { - key: &hex!("a2ef619054164073c06a191b6431c4c0bc2690508dcb6e88a8396a1391291483"), - nonce: &hex!("16c6d20224b556a8ad7e6007"), - plaintext: &hex!("949a9f85966f4a317cf592e70c5fb59c4cacbd08140c8169ba10b2e8791ae57b"), - aad: b"", - ciphertext: &hex!("b5054a392e5f0672e7922ac243b93b432e8c58274ff4a6d3aa8cb654e494e2f2"), - tag: &hex!("cf2bbdb740369c140e93e251e6f5c875"), - }, - GcmTV { - key: &hex!("76f386bc8b93831903901b5eda1f7795af8adcecffa8aef004b754a353c62d8e"), - nonce: &hex!("96618b357c41f41a2c48343b"), - plaintext: &hex!("36108edad5de3bfb0258df7709fbbb1a157c36321f8de72eb8320e9aa1794933"), - aad: b"", - ciphertext: &hex!("b2093a4fc8ff0daefc1c786b6b04324a80d77941a88e0a7a6ef0a62beb8ed283"), - tag: &hex!("e55ea0456af9cdff2cad4eebbf00da1b"), - }, - GcmTV { - key: &hex!("6fb2d130bbad1924cab37d071553b12169e978a805bf74cb4c23d5ccd393d7bb"), - nonce: &hex!("76826741225a391fdce4d3b6"), - plaintext: &hex!("c49b80080e2efeb5724b9e5b53ba0c302e97bd16f1a6bbec01e1ca6c35a42a3c"), - aad: b"", - ciphertext: &hex!("62fbe5466a7ff83ff719f4927e00e9319e1bb7e835c5d6b4e9d4bc5a8d6e2beb"), - tag: &hex!("df72da7a66cb5257836f3c19ecadcd55"), - }, - GcmTV { - key: &hex!("402e8113970257d9437807620098370243536a105cca4fbc81a1ff2d48874f48"), - nonce: &hex!("c924c19c4d14905a2bdf63bf"), - plaintext: &hex!("917b9585f65e59bf4d242bb0802966045dd29fbc66911277baecdfcc818c3c35"), - aad: b"", - ciphertext: &hex!("5b6594edcddbb338f4e813687f4f23a75a64c21e3cf5d2e7c9af0f7e3ee3e616"), - tag: &hex!("f1cccd93a4411247c8b6830addd72c6f"), - }, - GcmTV { - key: &hex!("2aac499cb0eb72b4598acff4330df6cd764978997d5ace51da88e0c18671bde9"), - nonce: &hex!("fd16cdc39d7f0b92e1f95c97"), - plaintext: &hex!("e7b75bfa35c9a004d0b68265623a9b06b6d4493ea0ad4f6c777ba5add8c7bbbb"), - aad: b"", - ciphertext: &hex!("c3d0a0f7ce9720c95aac86151aad634884ddfa62df58f18394537f6504d9a8aa"), - tag: &hex!("76749a1ec70236b267fc340d5fbb6da3"), - }, - GcmTV { - key: &hex!("a2a502d6bb19089351e228d5cbff203e54fc31f2772253df08557875d964c231"), - nonce: &hex!("0ebb5af4a462a1e6ded7164a"), - plaintext: &hex!("bbecc89450c07b8de631155e5d7cc7a9d26376bb57d7458d49b4c36e140490f3"), - aad: b"", - ciphertext: &hex!("fd09c950890441fcaaa8809a8998079abb88741c6672abae12383ffd724f8299"), - tag: &hex!("22fac246058bf142c5f26812a635b480"), - }, - GcmTV { - key: &hex!("ce2d289e20c76f75c135c8118d5cbf5f2828026f0b639588a3eb4ad752cea548"), - nonce: &hex!("bb08526dd8bd1c3bb58d0999"), - plaintext: &hex!("56f5db1e796a0c4633a8d570182c39e3c8451e7ba485b98d38a2c926a1b92a46"), - aad: b"", - ciphertext: &hex!("a41005df18734d4f3f99f19ef8fc43b16ef431207cb0466341bf164b58e23533"), - tag: &hex!("a45c2a1ef6aec75cc22d71807dab3c27"), - }, - GcmTV { - key: &hex!("66e418d0ec97b420b1b5365d1b6d5cd7c5ac1a5653739120d4aec3c94c93c287"), - nonce: &hex!("989f94480266e3652488184e"), - plaintext: &hex!("e5052b19d7f827fd60f45c8925809fd2217ec4d16aa89bbf95c86a1c1e42bd36"), - aad: b"", - ciphertext: &hex!("f341630574ee92942cf4c5ecd3721ae74b32c557379dfe8351bd1c6661a240da"), - tag: &hex!("e85fb655ef432e19580e0426dd405a3e"), - }, - GcmTV { - key: &hex!("37ccdba1d929d6436c16bba5b5ff34deec88ed7df3d15d0f4ddf80c0c731ee1f"), - nonce: &hex!("5c1b21c8998ed6299006d3f9"), - plaintext: &hex!("ad4260e3cdc76bcc10c7b2c06b80b3be948258e5ef20c508a81f51e96a518388"), - aad: &hex!("22ed235946235a85a45bc5fad7140bfa"), - ciphertext: &hex!("3b335f8b08d33ccdcad228a74700f1007542a4d1e7fc1ebe3f447fe71af29816"), - tag: &hex!("1fbf49cc46f458bf6e88f6370975e6d4"), - }, - GcmTV { - key: &hex!("2c11470e6f136bec73351619288f819fb2bbba451857aadfb78384074612778a"), - nonce: &hex!("4e6cc2bcc15a46d51e88958d"), - plaintext: &hex!("3b3186a02475f536d80d8bd326ecc8b33dd04f66f8ba1d20917952410b05c2ed"), - aad: &hex!("05d29369922fdac1a7b37f07953fe175"), - ciphertext: &hex!("6380945a08977e87b294b9e412a26aebeeb8960c512439bac36636763cd91c0c"), - tag: &hex!("1029a3c4be1d90123c1b404513efde53"), - }, - GcmTV { - key: &hex!("df25ea377c784d743846555a10cfaa044936535649e94da21811bad9cea957b5"), - nonce: &hex!("35f5f8e950c1f57ad3dfb1fa"), - plaintext: &hex!("98941a807ac8f16eef0b3d3c7bbdfd55d01736c5b3360d92b4358a5a8919380b"), - aad: &hex!("28eb4677110ccb6edc8d2013dc8f46ec"), - ciphertext: &hex!("24a07532e981aaf3106eab8dfbb2d2078342e2eaee027e148f06aca68f6a1c50"), - tag: &hex!("131373ed4a0e3f584ae978d42daa6f3a"), - }, - GcmTV { - key: &hex!("106168ea651f22c54196a06f1a10bcf4e620d93e4dc0824d798f44f9219c6177"), - nonce: &hex!("4064dcbd631cf20b05ae22de"), - plaintext: &hex!("b0d3da2b96b8889c92e445abbea4c6d0d5d44d7fbcc7dade4c92f6bcddbf06e1"), - aad: &hex!("a36e2fb9cd96a8ca9ae2b193aa498efd"), - ciphertext: &hex!("f55a6d8a6965ea451637bec7548cfb1ffe59fc0ce6ea6a937cb5dd32b3d45d5f"), - tag: &hex!("8d1bf2715041f817f11631fc9910c629"), - }, - GcmTV { - key: &hex!("272d1649a3dd804de0962d3e07064a7054c00a6234ab1b0cdcf685ab394837e5"), - nonce: &hex!("955b5897f6b9806bbec5c33e"), - plaintext: &hex!("36e57c29c08c51ad7fa91c0416f976cfd011780eb44cc5abd34c7b431b093b8d"), - aad: &hex!("33e618ecbbe5eb0566df21c3c34b7e25"), - ciphertext: &hex!("cd6aeb345081dc0bb2c8b4d19b280658fb87c0f2bd0f4c9da694dc1feeb32f4e"), - tag: &hex!("dd37eac6bd6a4d3618241738779735d7"), - }, - GcmTV { - key: &hex!("3dab6a51bb7af334dd4b79a7d139550c88f0778d43c21fc4ad33f983a13515cb"), - nonce: &hex!("362eaa67cab3d1ed48e9f388"), - plaintext: &hex!("3eb7f5f0a4ca9aa7000497602c6124433a60a8fcd91b20175b4ee87e6b10a2d7"), - aad: &hex!("52852150786e6547a2618e15c77110b6"), - ciphertext: &hex!("cc3316041b88733839249b756ffa00bbec6211942f604f26c4a35ed32e6eeaff"), - tag: &hex!("5936c5500240d50c0da0fcdc248f176e"), - }, - GcmTV { - key: &hex!("0ea606521b935d5b4b66df89fb372d35c4d6d2c03767367e38de0d4c27761d56"), - nonce: &hex!("0d3168318a4f76392699640b"), - plaintext: &hex!("f450b36d6c49411897bce39001d73ff01b5e8566179e36dacac7064cab5c6270"), - aad: &hex!("3bd8849070cf034c4298f40f33b0b839"), - ciphertext: &hex!("3b15fad18726c4eaa70502b3f3b32c5092d1d92835e6460665fc50dda953a191"), - tag: &hex!("11fd3fddf61e010c17fbedd4bd5fb012"), - }, - GcmTV { - key: &hex!("c8c4f9e0bd289ef1bd16104a8074fb073dd9035ab937ab076fb5801e2295aa2f"), - nonce: &hex!("be699d9d98ec1f724da8bd0f"), - plaintext: &hex!("49fe9407a719d41e658587809cfed7a5b49941c2d6378f3c0afe612f54f058a1"), - aad: &hex!("a985c7489732038c3190cb52be23737c"), - ciphertext: &hex!("17a9aaa6a3c68ba1f6cb26fdd6536c207e3c9ce58f43e4ecfd38d3387a798a0f"), - tag: &hex!("d832cb4814142562fedfe45b36126cb8"), - }, - GcmTV { - key: &hex!("52d0f20b0ca7a6f9e5c5b8549d5910f1b5b344fc6852392f983558e3c593be24"), - nonce: &hex!("d5c618a940a5a5d9cc813f27"), - plaintext: &hex!("a9fed8a29355685321f978e59c40135309306cd41b25349fe671dc7990951c68"), - aad: &hex!("61823f7e39ed76143ca7249d149bdf57"), - ciphertext: &hex!("509c540e558d0bf0a3b776cddfbfddc15486748a7f9952b17c1cbd6869c263f4"), - tag: &hex!("42e35ee3f7119f87fb52b5d75b8ab8ec"), - }, - GcmTV { - key: &hex!("5d291a8f1a6433a41076702d9d8a8c196e464550ed900ce8c2a36f4d10483954"), - nonce: &hex!("c4ba743ee692e5d00b5ae2c6"), - plaintext: &hex!("605d519b26182458fea68dddd86033390fc545f843ae817850a2a4574add015d"), - aad: &hex!("878fa6720ab30e0287f6903acd2dca19"), - ciphertext: &hex!("1c2f153f2374d3945cca9757dc18d9a15a93276526285a6e316ee32a72092c34"), - tag: &hex!("e7905e856c88c6ece4bb47781becf923"), - }, - GcmTV { - key: &hex!("09e2724d4017cd57e967000e4da2cd5c5c18ccfb06c33b7ce62a7641e4bb0b73"), - nonce: &hex!("9ea18b420a10177289ab370b"), - plaintext: &hex!("6f5dfa86d5df4febd752265c56390049e7cda60c2644c84ab413932faad15b15"), - aad: &hex!("a8e77939423d5894d307fd60278d162a"), - ciphertext: &hex!("35e37a9b913eb58b72262e92d7584d44bf9a8442f1b2f3da3a5d05ec6a2a31e2"), - tag: &hex!("1a95023b1a4a3e885520ec79e1a3aef9"), - }, - GcmTV { - key: &hex!("8544a9f4f6c0efdff3da90cfa3ee53fbe1f8de159d29537c803e1651da153718"), - nonce: &hex!("be406029a1d0c25d09af94cf"), - plaintext: &hex!("7e88a65646ed138b7c749366d16e41dbafd9987ad2373bb9d0b6ce0c1a4d6661"), - aad: &hex!("599dbb73897d045a1bd87385e60323a2"), - ciphertext: &hex!("38ffbf9ffff8d6a92090584e6dace1c6a47d3d5709a25e470557d5c8f5dd1851"), - tag: &hex!("d5b2e83c47df404de9a7cd95d3cbe7ab"), - }, - GcmTV { - key: &hex!("35b9d2a5db3b06e7720cec794dae615029a491c417f235498e0496cd8183d1bf"), - nonce: &hex!("b382987916e19752dd9ecc0c"), - plaintext: &hex!("76b290496901c5824ad167433dbb6d6b5856d41913ee97ec81e70cf6a170e35c"), - aad: &hex!("e0aa3a1f1df601366c59a390f4f06c3b"), - ciphertext: &hex!("78347400d6799e77e11e76c0ecfd311becf31f74f14b3a71e6d526ce57015c8b"), - tag: &hex!("bf8dec2feac7cfe9f330bdfc92737b33"), - }, - GcmTV { - key: &hex!("d707eab3c167b73efeb08c50e12b1569a275487ea136f52736c0f3ce66b69fa3"), - nonce: &hex!("11116f34182e52428642e747"), - plaintext: &hex!("a0c4818362035b16b50de445d558ea5cf8844bf5c84b96232999a2279806cc45"), - aad: &hex!("ae9f90331800c358716c92667f79f748"), - ciphertext: &hex!("91c77404b20028ef0fd4dd7f8b65b6594af94a1e7fc79cfbdb108265354fc71b"), - tag: &hex!("6c3410d4b915dbad745715202c04e9a4"), - }, - GcmTV { - key: &hex!("405d13ee48d3b9fc26bcfca776b2af6c745d8fc34171622f8c6c4be5a54b8b65"), - nonce: &hex!("add1524abb1b846f0f6577da"), - plaintext: &hex!("e06475990d6e3990266de1bd025c3b1910c0736c81050885f2bfc13ec78e9d96"), - aad: &hex!("0b1c4c3ba877bca5846b2c1f2b0e2105"), - ciphertext: &hex!("6399f7e6d6c680fc41bac8bee3836b9a4241403d5a19e4919f396ce37b238d38"), - tag: &hex!("e754f400d76c76e03c63ea88cf64ccba"), - }, - GcmTV { - key: &hex!("5853c020946b35f2c58ec427152b840420c40029636adcbb027471378cfdde0f"), - nonce: &hex!("eec313dd07cc1b3e6b068a47"), - plaintext: &hex!("ce7458e56aef9061cb0c42ec2315565e6168f5a6249ffd31610b6d17ab64935e"), - aad: &hex!("1389b522c24a774181700553f0246bbabdd38d6f"), - ciphertext: &hex!("eadc3b8766a77ded1a58cb727eca2a9790496c298654cda78febf0da16b6903b"), - tag: &hex!("3d49a5b32fde7eafcce90079217ffb57"), - }, - GcmTV { - key: &hex!("5019ac0617fea10517a2a2714e6cd369c681be340c2a24611306edcd9d5c3928"), - nonce: &hex!("fd1fa6b5cab9aa8d56418abb"), - plaintext: &hex!("4349221f6647a906a47e64b5a7a1deb2f7caf5c3fef16f0b968d625bca363dca"), - aad: &hex!("953bcbd731a139c5de3a2b75e9ffa4f48018266a"), - ciphertext: &hex!("dbce650508dab5f499767651ee734692f7b157341977692d2ca879799e8f54aa"), - tag: &hex!("20239e97e2db4985f07e271ba545bbbf"), - }, - GcmTV { - key: &hex!("c8cee90a8b9ad6094d469e5d1edc30d667608e89b26200cac77efd7e52af36fd"), - nonce: &hex!("5a1aa9c8e635281ee1fb9df7"), - plaintext: &hex!("728d9221891bd75c8e60b7dd6f53edcfd1ab1cebc63a6ce54be220b5b362233b"), - aad: &hex!("0538b3b64da72aac591bc59991a140eff206b3f7"), - ciphertext: &hex!("b753eb6b87f0c8778c3ea3a74fba3b31ced6d2da94d43d482ab0431806a80d75"), - tag: &hex!("b21d29cf6fd04571ffcaf317d384df11"), - }, - GcmTV { - key: &hex!("b4b77710f86ffd463fc14bb9eaa4424b2b3a581778e5511a094a08fb204cab59"), - nonce: &hex!("3e4b12bf55633bf48d104620"), - plaintext: &hex!("6f44a8df11dce27df075ea10ddeb7566ca6c988a334cf56e8540f71166d7c0d1"), - aad: &hex!("3e3b4c9369266266098326217b5677a40297cb87"), - ciphertext: &hex!("31f82f5cb1cd5c4b4819b61aa9377abebe8fca76978b1199178462c7c1c4e2b2"), - tag: &hex!("1b3a535768e8480d75ec91b2e7b55efd"), - }, - GcmTV { - key: &hex!("0a8fb75498a139223c763d52bbe3d42f813de370fa36b81edc4553d4219d2d5d"), - nonce: &hex!("7d6cb675fded3efef908a11a"), - plaintext: &hex!("81b69ca354de3b04d76ee62334cb981e55f0210f1174d391655d0f6712921a0e"), - aad: &hex!("2314ad86b248f1ed2878e7c562b533bf2dda5a29"), - ciphertext: &hex!("6a23d30737f4a72b1e07ba23d17fde43a4498e2e60d3e1b0c8e6ea26a2bb331a"), - tag: &hex!("7fcac442fb657910c62a74b1d0638902"), - }, - GcmTV { - key: &hex!("a84315058849690c2b88062aef81134d338526baa7090e865fcaad94bbf51ca5"), - nonce: &hex!("a487cfa701447b495aab41e0"), - plaintext: &hex!("18074e14dc0a14d4439f1d710927ed8c200154c8492f77f10f653e0bf6070ca6"), - aad: &hex!("7c4416b0cf13ac76bec6687a6840dc703e91bb86"), - ciphertext: &hex!("80f40b7e335d40fc5859e87f385e14798a253818e8ad73b1799c1419638246a4"), - tag: &hex!("b4c7c76d8863e784eb6029cd160ef6de"), - }, - GcmTV { - key: &hex!("82833bcaaec56f6abbb3378f7d65daf6e6f6f2a0d1e858c7219f53a7840f4e00"), - nonce: &hex!("4bc9b028a00be8feb5232978"), - plaintext: &hex!("d9b2383123a27a93bce85add8392b938093b40e82f182e484bf4f84fa3bfb3f0"), - aad: &hex!("76fc8ed57154cd8a9b3d02c87061edd2a8157811"), - ciphertext: &hex!("383efe971438cd2b2cbb399d74a3fb3eedd394f1862addc58e9fdd4c421402d2"), - tag: &hex!("fd803c4fa917f7ff649a6aac013a96b1"), - }, - GcmTV { - key: &hex!("ee4634c49c5672c660968a42862698f6c1b2c7b79efd1605c24af8ff9ff8366c"), - nonce: &hex!("877912b2f35888d2810612cc"), - plaintext: &hex!("9512a5268a0cb3fbd916ddb820dce77f1e0dbb52c8ffc7a74be077119e9245e4"), - aad: &hex!("93bd669db4f1354ef6c8addb0cf729e46d5c3846"), - ciphertext: &hex!("69af0ac954e0d69043851d89f1538ebcb42769857eba27dbe4ad4fd60fd75537"), - tag: &hex!("3ee443873e2f7f7ea601fe3d7e5211e2"), - }, - GcmTV { - key: &hex!("442f4bbc468433411e49486a15c5eed577f5007380ff126d9974f3bd3fe4e3c4"), - nonce: &hex!("1e7133aaa8af826dc646ec62"), - plaintext: &hex!("7f8069e5c356ece135d98bb563c8b411ea90ea3b673dfd92e1ba9c459efae61f"), - aad: &hex!("577662f611446b5b31814930029edb949a30dcb9"), - ciphertext: &hex!("b962952750eb2bce313e1a85a72e3c9cc2ea7e58c353ea37df2c9f0723995ca7"), - tag: &hex!("e633fe9f10cedf0f0d02aa2ddcf47d86"), - }, - GcmTV { - key: &hex!("3a29aec009f44fdd2b1bc07cb7836f29d8589774bd0d74089a68d9e67827d6d8"), - nonce: &hex!("a42c5fb61573c72688ac31d8"), - plaintext: &hex!("d36eb81506c0a0e4ebcac9b4b1acebb38b94b8f2ce3d6f85a8f705fa40cb987a"), - aad: &hex!("2ee2582d544e1663f1d7a0b5033bcb0fce13b3e5"), - ciphertext: &hex!("179ef449daaacb961f88c39b4457d6638f304762bd695924ca9ebd01a3e99b9f"), - tag: &hex!("1fee176c7a5d214748e1d47b77f4bcc8"), - }, - GcmTV { - key: &hex!("ed47660054294f3c913c97b869317cbddc395d757bef7d29b8ccbdd2c54e99d3"), - nonce: &hex!("770a00642c67eff93c9f1f56"), - plaintext: &hex!("034193397cbd0eb414459273a88808db2d0711e46f80d7883212c443d9e31b54"), - aad: &hex!("06210fca2018d2357256c09197730e9777caea96"), - ciphertext: &hex!("6a250ebd3390229d46b691142743dba1c432c0feaa0f0dd19d0ce4e6a8918d80"), - tag: &hex!("a5f6e975592b472907c34b93bfc69dde"), - }, - GcmTV { - key: &hex!("9539844493362dc3f913308f7e12a2a0e02afdbd8869877b30ce0397fb0349dc"), - nonce: &hex!("eadda3132079195a54fde2c1"), - plaintext: &hex!("62349a0b1e40a9f31eadf27073682da15f0a05cf4566ee718b28325f7d8eaba0"), - aad: &hex!("0ae4a90cb292c4e519b525755af6c720b3145a1e"), - ciphertext: &hex!("ad6c9521bf78d1d95673edd150f2b8dd28f10625d67fa25f1fb42d132ba7fcfa"), - tag: &hex!("916242a9cb80dffcb6d3ae05c278819a"), - }, - GcmTV { - key: &hex!("3b4eb08d27ae0b77605ae628a1b54a5402026550679fab0a20752bee510d3d92"), - nonce: &hex!("28a20c40f49a00493da3488a"), - plaintext: &hex!("c8a47edcf84872f53f96ef41ce05ca37cbc3854b556d6e606f0a8a32d0861907"), - aad: &hex!("0591390e2d14ebe62aeb1741c26448ce55b28cab"), - ciphertext: &hex!("a3e8cbf84df8529838f79315c7f1a0b7bb3ad4c4d036ec317b1810b274ee3080"), - tag: &hex!("0a8f66daeb7f0a88756909c4e93fcd36"), - }, - GcmTV { - key: &hex!("0cccea8f1f6ce141690e246cf4cb9f35b66baf6e6986b8e0b4cfdd13fcdbc8c3"), - nonce: &hex!("929f07be5aa7bae7607bae3c"), - plaintext: &hex!("9fa5214c599523c695d37937b02f78837f6406960b2a03bf9a6db34bd35e3dc7"), - aad: &hex!("b851e610be70a994808b34ca73f45f1ea973de65"), - ciphertext: &hex!("917ecc8b00b53f7fb0732d66848a106e91f60acf2dcf180832a74d5993c658da"), - tag: &hex!("2959e20746bbb6ab66dfd29b9477799a"), - }, - GcmTV { - key: &hex!("ecbfaef2345b34f31fbf6d68efb385e5833df8b6e6ae621ede02baf9735d2dba"), - nonce: &hex!("50c3527b1a35ccb318b446de"), - plaintext: &hex!("634f6dd60783d1f952353fd1d359b9ee4f4afa53cc13e81c5adfe24b46baf08f"), - aad: &hex!("f8981548bde6ee6c1745f947de191bf29997fadf"), - ciphertext: &hex!("705e5f67ab889ba238118e3fd9b90b68be801995ae307378d93b50977cf90588"), - tag: &hex!("12d14468ac18cc9936bd565f8ad42d0d"), - }, - GcmTV { - key: &hex!("dc776f0156c15d032623854b625c61868e5db84b7b6f9fbd3672f12f0025e0f6"), - nonce: &hex!("67130951c4a57f6ae7f13241"), - plaintext: &hex!("9378a727a5119595ad631b12a5a6bc8a91756ef09c8d6eaa2b718fe86876da20"), - aad: &hex!("fd0920faeb7b212932280a009bac969145e5c316cf3922622c3705c3457c4e9f124b2076994323fbcfb523f8ed16d241"), - ciphertext: &hex!("6d958c20870d401a3c1f7a0ac092c97774d451c09f7aae992a8841ff0ab9d60d"), - tag: &hex!("b876831b4ecd7242963b040aa45c4114"), - }, - GcmTV { - key: &hex!("07b3b8735d67a05632c557076ac41293f52540bac0521573e8c0414ec36f7220"), - nonce: &hex!("0046420eee8d56de35e2f7d5"), - plaintext: &hex!("4835d489828325a0cb38a59fc29cfeedccae25f2e9c399281d9b7641fb609765"), - aad: &hex!("d51cedf9a30e476de37c90b2f60882193630c7497a921ab01590a26bce8cb247e3b5590e7b07b955956ca89c7a041988"), - ciphertext: &hex!("46eb31cd98b6cc3ecafe1cd1fc2d45fa693667cbd3a7d2c5f8c10296827ea83c"), - tag: &hex!("36cd4e76dd0679887477bfb96cf1c5f6"), - }, - GcmTV { - key: &hex!("0219f14b9ca6506c1388177c4ae6ee64ad2ac0256ebbf8c219b40df6e8571d70"), - nonce: &hex!("3420a87c4b9b23ba81eb221e"), - plaintext: &hex!("348f7a4ca944f252e4562c66dacf01fb10d70a3c8f5b280a2829567a2a94e47e"), - aad: &hex!("54dc2277b8d1aae660ffcc326e2c5d9e16b8ca17288601aacd02b3eea8bc5cc60718639aa189506b7b333b87da86e940"), - ciphertext: &hex!("58c92119bfb6ad53e387cac6728ce73b82e18f6e5bfbfca5f5acc370cd8c76a4"), - tag: &hex!("e7f9e3e3dae6d0a3470d8f597291180c"), - }, - GcmTV { - key: &hex!("87440ee7f6febf3e14ef0a917a87c5d61260fefc979eeaeac0a64662c98cb4f7"), - nonce: &hex!("7c48bc75e58f21cc9989d691"), - plaintext: &hex!("f8e40a6a985f424898a7996307a077c487406c5312eefe055ea5b17a4b22087b"), - aad: &hex!("e0c66e5db1c7665a015ba7e21e08ff3de5b4a5fcd5d35e41db7e97ccd0c3df657ae803c3529d375420ad75ac9621cea0"), - ciphertext: &hex!("5a118fc3dbdaf6bc9490d372b7623af76da7841bf9820a9c6624a15eff6a69c2"), - tag: &hex!("0ddc2ae087d9b8ca2249ea5aa3dbd4c7"), - }, - GcmTV { - key: &hex!("b12425796f63bf5435740f9039fa66367fc7702d675c61b2dec4435feeea07f8"), - nonce: &hex!("f26727053e6d67c2d2bf1e69"), - plaintext: &hex!("9df079d98a6e4dbe277a8545f4f6c19fe130f4a84bdd6b760a049fba21d4e99a"), - aad: &hex!("e50fca2e5a81ae56ca07f34c4b5da140d368cceab08494f5e28f746cbfefdc285b79b33cf4969fe618b77ab7baafe271"), - ciphertext: &hex!("845f00202e2e894516d8f4a4021430e531967098c9a94024c7113c9a1b91c8cd"), - tag: &hex!("3566c75967ae00198e39ebe9f0ac697f"), - }, - GcmTV { - key: &hex!("674dfb625b8b0ce1dadbbbcbf7e151c5b2cecf0a1bc4e07f4734f3a6792350cd"), - nonce: &hex!("99e7b76e6686449616ad36c7"), - plaintext: &hex!("0a744a72e536a0484db47091609228d803bcfa9a8daf579e3039e3645f7688e2"), - aad: &hex!("2ab1573e5a94ca2997590840bd9c62e6add55e4d3eac12c895d2ec637791caa41d46ed91e6064db627e1fbef71d31d01"), - ciphertext: &hex!("e550ee77069709f5199be3c618f2a4178e4d719ab73df41cbfe32c52777138ff"), - tag: &hex!("134ac3fa8bd4af7ee836f4a3421d9e99"), - }, - GcmTV { - key: &hex!("10c1de5f741560dae5be23e15649f0114db52949560bb6cdf2d4883247392ee1"), - nonce: &hex!("7cf73c1472cd60d8d35fde51"), - plaintext: &hex!("05becd366aebaa2e609f507dd2dd4433b2aba0634b0eb9a5bf7ded4cc8fbed72"), - aad: &hex!("d3fa8b6f607a20a18dd7eac85eabef69d4fb5a074d8e7d1bf15d07732ed80e020163b475f209c4b0cbfa00d65d1e82ef"), - ciphertext: &hex!("280f0c306e1a3aab8ff9ab3e4a9adc2e9ae4e4e1a06f190d11b3b4dc4280e4f3"), - tag: &hex!("3bc8be845bf5ff844c07337c2cfd5f80"), - }, - GcmTV { - key: &hex!("e8d6ab5e514645dd7e051b028f5bfe624c72f44f30279577365aea65d4a8a819"), - nonce: &hex!("30b0d654ee5b79c2cfb24100"), - plaintext: &hex!("19be7e0feedd402bf4b05995a38e5f423c033de016e3ae83ea8c3c1cba658e1e"), - aad: &hex!("082e534bf860d0061ec2dad34d6b0db8cba1c651f2c705356ff271e47365b0b18f8ddb3a3c2269b437fb0703c9ad367a"), - ciphertext: &hex!("8573800c737d2480b2885ce714ac6a15f23287b1d12949a3d76effbe82b593bd"), - tag: &hex!("50110884292151f51213ccb2fe934d88"), - }, - GcmTV { - key: &hex!("2d1eaf5e62ca80fd1515a811c0e4c045aba8c769df03d57f7493eb623ed8b941"), - nonce: &hex!("abf190b05df2e6556cb34b47"), - plaintext: &hex!("9c7cd522ed5c0af3e57da08d2653ef77eb973734f360572bbcb15a2a6cbd60b9"), - aad: &hex!("75ab9bd39c24e498a54d85a8b76a4126dc1879f2a30270a42609763e045a4021785b6134f283fd81c195c3188e78752d"), - ciphertext: &hex!("5fdfdaccb105e5408c375af8ca63a67afaba7ccbcd591acca9a86d92f92fd0f7"), - tag: &hex!("49940b7610618b3a5cb3912339e06b3c"), - }, - GcmTV { - key: &hex!("b6020677e098c59e19eacf26732473d843aafd6bf999c707bb08ab896406918d"), - nonce: &hex!("807167ef2b84b32d1df4a94c"), - plaintext: &hex!("3199d6b95d133ba5b7eadc420080a0b249c84f4960bd369d6bf9e313627cf670"), - aad: &hex!("06225d410ada3e04157da7e5481d7d9f2285845824aac0c0e033244ed4c1b19615354c224ba8b7093c5651d10ef952fe"), - ciphertext: &hex!("4618adbfa5ea4ee260e310140b385232b7c3ad46887aa2107f7dafffd85cda22"), - tag: &hex!("2d76307bf55826dfeb58a171b6fa80e4"), - }, - GcmTV { - key: &hex!("f75456c4918d0bea72f546a9a1e2db0b6ab9bcd9782b5eb1c2700e729921d666"), - nonce: &hex!("c75b83134e7b9188e5800ffe"), - plaintext: &hex!("f9a23abbd0f2b367ce16c2a0613cd293ac7e66cbe020eaeb5deb09d5031fd992"), - aad: &hex!("5ef46c9eb5865cab2c8a35f9c4c434614a6c9f1b5c479739f7434d3326cff1e70b0d2877c084a71c7a9d33d258d304bb"), - ciphertext: &hex!("56e4efe6c0944153b65ed4909845219842b9b88f54d8d8394051132afb95d391"), - tag: &hex!("255e2c8c43f8979c440c3581bff6cf65"), - }, - GcmTV { - key: &hex!("9831c5c12e53e8a961642e93ddb2e13a38506acd0cf422e6ad9fbaeabce7b3f2"), - nonce: &hex!("bff29de3d6869e5fa75b96f9"), - plaintext: &hex!("b1edbed58ed34e99f718db0608e54dd31883baec1c8a0799c4ff8a5dad468de4"), - aad: &hex!("67ebeecb74cc81fdfee8065f8b1c1f5012bf788953bec9525e896611b827084a8e6baa0ce40ee70bc699b152bc6ed903"), - ciphertext: &hex!("13845db7e33bab1f5766a7fadfb942748e779753d97f143e645ccfcbd7c23b23"), - tag: &hex!("10dbe8a3e1901c8b88b0ab1441664d32"), - }, - GcmTV { - key: &hex!("a02c2d4a43f0f7f1db57c07f13f07f588edfe069a9d83c9b76e9511946c4fc48"), - nonce: &hex!("84677438592dcaf683d08a67"), - plaintext: &hex!("ad5a884dad20ffa88794c4fca39f2ca01c6f67657ab38e5cf86ac5597318ef07"), - aad: &hex!("d5dea0cd6080af49a1c6b4d69ace674a622f84f9f190b2db8a22e084a66500b52ff20a8d04f62a7aeaedb67e2258598c"), - ciphertext: &hex!("83da16ae07ee0e885484c1330a6255a6e7ac22915c63cbefaabc6f9f059dd69d"), - tag: &hex!("42c4a270705493d85ad7bbcfda86dffb"), - }, - GcmTV { - key: &hex!("feba412b641bc762bfa79ef17c3ea16e5630605470db096e36ffd33813641ace"), - nonce: &hex!("e3633f21e7c63a459d5d1670"), - plaintext: &hex!("9326572bd33551322ca42fcfb7cef8be41d78725f392c34907ecd1fe5572bff1"), - aad: &hex!("b7ee0233863b0e185b2f46181eb5fc0718832e1e76e7d4115a4c1f7e998c41319ccef44f5db89e8c5f077bd553d7bf42"), - ciphertext: &hex!("5019ea98cc9dc9368432c6d58f9e144f55446e763c0a8b4d8a6ce26f3dd95260"), - tag: &hex!("1010beb9cd6e9b611280a5395f08bca9"), - }, - GcmTV { - key: &hex!("21bd5691f7af1ce765f099e3c5c09786936982834efd81dd5527c7c322f90e83"), - nonce: &hex!("36a59e523df04bc7feb74944"), - plaintext: &hex!("77e539dfdab4cfb9309a75c2ee9f9e9aa1b4651568b05390d73da19f12ccbe78"), - aad: &hex!("48aef5872f67f524b54598781c3b28f9cbcf353066c3670370fca44e132761203100b5e6c7352a930f7e9cbf28a8e1ce"), - ciphertext: &hex!("c21483731f7fe1b8a17d6e133eda16db7d73ddd7e34b47eec2f99b3bbc9669aa"), - tag: &hex!("15f9265bc523298cefb20337f878b283"), - }, - GcmTV { - key: &hex!("26bf255bee60ef0f653769e7034db95b8c791752754e575c761059e9ee8dcf78"), - nonce: &hex!("cecd97ab07ce57c1612744f5"), - plaintext: &hex!("96983917a036650763aca2b4e927d95ffc74339519ed40c4336dba91edfbf9ad"), - aad: &hex!("afebbe9f260f8c118e52b84d8880a34622675faef334cdb41be9385b7d059b79c0f8a432d25f8b71e781b177fce4d4c57ac5734543e85d7513f96382ff4b2d4b95b2f1fdbaf9e78bbd1db13a7dd26e8a4ac83a3e8ab42d1d545f"), - ciphertext: &hex!("e34b1540a769f7913331d66796e00bdc3ee0f258cf244eb7663375cc5ad6c658"), - tag: &hex!("3841f02beb7a7fca7e578922d0a2f80c"), - }, - GcmTV { - key: &hex!("74ce3121c18bbff4756ad10d0f293bb1ea3f93490daad0249cd3b05e223c9747"), - nonce: &hex!("81107afb4c264f65ae0002b1"), - plaintext: &hex!("7a133385ead593c3907806bec12240943f00a8c3c1b0ac73b8b81af2d3192c6f"), - aad: &hex!("f00847f848d758494afd90b6c49375e0e76e26dcba284e9a608eae33b87ad2deac28ccf40d2db154bbe10dc0fd69b09c9b8920f0f74ea62dd68df275074e288e76a290336b3bf6b485c0159525c362092408f51167c8e59e218f"), - ciphertext: &hex!("64bd17f3e8f71a4844b970d4ebc119961812efb9015b818e8d88b906d5efbd76"), - tag: &hex!("46d0e42aa046237efee17eab6d9cfb75"), - }, - GcmTV { - key: &hex!("4c669a1969c97d56da30a46236c15407e06aada686205eed3bd7796b02c97a4b"), - nonce: &hex!("0a07758d5ad44766e051da6c"), - plaintext: &hex!("cd59bb307be76f11304f69ac8b151e1628ac61dec81086e7f24fd5bd83df8856"), - aad: &hex!("0b8277114cbf7ee16c9bbda1ab40419a02e469ebb295883f0a833c3cb755ded44a3c410034a201f7d91b43519fbabb55b974834be5d5afc7aea7c84b44a14e8e16dd68a3e8cc79ad2bf76d0ceb33d58ddb6378b45681ceaa0f2f"), - ciphertext: &hex!("bc62ce0b23cf4aa8e16b4450c8ab8c629a53949f01e68b875ecc5c45ff6d3ab0"), - tag: &hex!("5ffeda728914031006f271c3d9986f2d"), - }, - GcmTV { - key: &hex!("a23296632913051e438114deb782fb955b75acc35e86e7e9fdaf4e9025b87f12"), - nonce: &hex!("ad50db40f80f15214e43ffd7"), - plaintext: &hex!("b71116cc27b5a5844d9b51a4a720cb3f06d55d6aaeaeaf921236424db8617204"), - aad: &hex!("a6f96f5a89bfd8c8f34cd07045270d80e58ea62f1f0b10f2506a954f272af0bc71df96ad3fa8eed52c45e0b868091dc4f75d9e0eaf15a0a858a71bf7036c5607110cbfe47ad9b6d02e942fcfae88d4c792a1f824e60e3cf98a37"), - ciphertext: &hex!("8e9e4b0ac93ab8e73688d6b4723d8c5ef399ead72246c7aa7a0783a8bfe29936"), - tag: &hex!("b7dea91e4b357ce805edeea3f91392d2"), - }, - GcmTV { - key: &hex!("4036a07bdd4e10eb545f3d9124c9f766d2d0c8c59fc0d5835ac55dcfaebfc3a1"), - nonce: &hex!("815828fbb964497cdadccaad"), - plaintext: &hex!("717f22faff8066182e46d32dbac7831ec24272871c45c7c12ca779f868e7739a"), - aad: &hex!("0bc0e3931388bcb091463bae2989a93bde103bc14fc5d39f9448ca90367e86336b188f73218b2b0ab72a9a564ad5ff32544c5afeacecadfa55d2fb66925a88299dbf58f425cf49e31f42ac4edace743fdf9680d20ec845afc278"), - ciphertext: &hex!("e8c3b0342964c7a71f084d44ba2f93742bccd9821b30087d11b53bbe8b085808"), - tag: &hex!("86ddd9c469849cb6b100c339ca62717d"), - }, - GcmTV { - key: &hex!("714bc3ba3839ac6707863a40aa3db5a2eebcb38dc6ec6d22b083cef244fb09f7"), - nonce: &hex!("2cfe1c51d894e5ef2f5a2c3c"), - plaintext: &hex!("0cc4a18bbfea87de0ac3446c777be38ca843d16f93be2c12c790fda4de94c9bf"), - aad: &hex!("84e3d46af2ecb717a39024d62bbc24d119f5aff57569dfef94e7db71ad5aff864abacdc5f8554e18ed5129cfb3366d349c52b3d1a111b867e8772140749e7f33e2e64259968486e32f047d21120da73c77757c4595ccac1b5713"), - ciphertext: &hex!("0857c8fb93412fde69bad287b43deea36506d7ee061d6844d00a7e77418f702f"), - tag: &hex!("24a9e5290957074807d55ad705adaa89"), - }, - GcmTV { - key: &hex!("2f93b5a37be1a43853bf1fd578061d0744e6bd89337cde20177d1e95a2b642c4"), - nonce: &hex!("52b6d91557ae15aa792ce4b7"), - plaintext: &hex!("0fcaa316a135d81052509dd85f688aed2e5fd4261e174f435cf1c4115aa6f354"), - aad: &hex!("992ba9efa287a5c3e5177bd4931af498982a1728b56b3d7c4b28476905e29f83326c4f3223a28844fc9b9d84d4f6cd859074aff647a35dde28e1ee889faab3bb9c09a4c3fbf2a16460d48a40dc53378d4673f4325e6aa3992a71"), - ciphertext: &hex!("f99774cef3c15af33cda3cb449cd335ffe4f27435edf83aff4a4f4c2d2df6647"), - tag: &hex!("c5e09b83b1c2cc81e48a1f7c62b7bb35"), - }, - GcmTV { - key: &hex!("531ca845af7bf731c49c3136407322b1c0f6b32b8eaebf03744b2edc1202d096"), - nonce: &hex!("baf13b85202bbfc899fc73f7"), - plaintext: &hex!("d4e9783f537c738200e7ba7526605f359a98c9f10cafaa2f433c40f3e5081a36"), - aad: &hex!("e2ba9cf548b4f6fb206f224250d85af327fde8d08916686ae770203dc29c694f8902b02222fd287f28ce6091006368c3949bea2937ff0bdedb7dbbd013ccf0a15ee0af8c56fe211b7c311e182f27707f59e09492b3604e80c6c5"), - ciphertext: &hex!("642f544929202128a783b985d36f60964c7d78e1d41f5d1bfe27de3ae0180df3"), - tag: &hex!("e333528c59ee1909750ed72fd1309ee1"), - }, - GcmTV { - key: &hex!("3add17568daa9d441aa7a89bf88fa4e6998a921d57e494a254080445bc9b6f35"), - nonce: &hex!("b290f4a52496380218c3dcf5"), - plaintext: &hex!("2c6908cb34215f89a3f3a3c892e8887f2efa496a15ab913fc7d34cc70c0dff79"), - aad: &hex!("0bc9cc13eb2890aa60515c2297a99f092f6e516236c0dec9f986ea98b8a180680f2c6c20bd4354c33433a4c6f6a25e632f90ebef3a383c3592268b483eebf5f5db006929e7987edbcac4755d3afd1cdf9b02954ebd4fef53d5f6"), - ciphertext: &hex!("2cf3beae94fd5e6a4126a8ec8a7166b0aacb8b8bbce45d6106b78d3456d05149"), - tag: &hex!("ce1509b1bd5c47a593702618b0d79f6c"), - }, - GcmTV { - key: &hex!("1c1dcfd4c4cc4beb71d6e368f739d8e681dfe48fbae39728386c9dfc08825743"), - nonce: &hex!("0deceb69ce0dc776a3a71b4c"), - plaintext: &hex!("b12700258ace7b16e40f4e86886892837168b256a170937a3b89063a9a0d68f7"), - aad: &hex!("a3af2db672292431fa8ee1fa5b197593b13e58a68c4129401d0942474d5f4cbe62093aaa5453f6d355d2f4b6dc8abde58ce863d1be5f9ecf39730a49565b3b6882a0a641c0b5d156a4107309dd150fd1f1634ea4e5100b3d4f88"), - ciphertext: &hex!("3ea7f1c0d613323e095558ddde53247420fa0eef17997a1e9c5ba93d5f24c46f"), - tag: &hex!("70534a87c258905d35806f4439f6906e"), - }, - GcmTV { - key: &hex!("f2724153aac9d50f350878d3c498bc3dd782d90cce5cce4ae14126c0e1fbb3cf"), - nonce: &hex!("1c07b61c5316659bad65cca9"), - plaintext: &hex!("067ccbd0206f1f05d2872210dc5717a0585e8195d72afd0c77da11b9b3710e44"), - aad: &hex!("e69db7fcd3b590a6d32052612034036d5c8bffa5e5e9b742ffe75a9fbba89dd576dec08154cf4e6d36f0fdd4419bdf50adc1974a80ea313421c926dffa87565b4bd0c1e84f2ff305af91877f830f145bb13dfa7efa5e3aa682e6"), - ciphertext: &hex!("9aba433eef383466a1291bd486c3ce5e0ed126010e0a77bf037c5eaed2c72460"), - tag: &hex!("f30a155e35400bb0540883e8e09b4afd"), - }, - GcmTV { - key: &hex!("a2544eb2047c97cfcaf0ec1427c5df395472285233a93ffccda8fee660aced56"), - nonce: &hex!("a751bea3c769bb5db25ab109"), - plaintext: &hex!("b9514cc01a357605918f9cc19123dcc8db328c605ca0eb9d69d871afeea1dcfb"), - aad: &hex!("eb9e09884de1454d6aeb0d6c82375f2428992031ea6cabf6a29aa6a4de49a353e4ffae043dad18ae651b20b7bca13f5c327ca9f132014bfa86e716d4724e05a1ef675521a6607a536756e6a8c16bb885b64815f1eb5ec282ce8e"), - ciphertext: &hex!("cb442b17088f6ac5f24c7a04f0050559386f3a57131b92a54142c7a556fdb935"), - tag: &hex!("5f80c5c0cdf0c7890bfd1fbd58c33081"), - }, - GcmTV { - key: &hex!("ceb057782efb1e85d805448af946a9b4d4128bf09a12473cce1e8ef8bfd2869d"), - nonce: &hex!("406f9730e9b1e421e428439b"), - plaintext: &hex!("0815723d5367b1328cac632fa26e23f2b814a1d59a2971d94d02ebd7ecf5c14a"), - aad: &hex!("0772ae00e1ca05d096cf533fd3de2818ac783edfca0eee7686a6290f3357481e883fb2f895b9a4f4004c56b8a1265242cfdf1fb4af7edc41ed78c5f4ffe9c4080d4a17318f9c56ecdb3a06f3c748535387d56a096943a76d46f6"), - ciphertext: &hex!("9d82355d8e460896201be15fd95fed48a8524666d987ab078550883034d0253c"), - tag: &hex!("a0bee8ac0e636d64d3b1eb33fd6f21d4"), - }, - GcmTV { - key: &hex!("7dbdbdfe36d4936940ad6d6f76c67c2851a0477f0aa7d6797bfdf2b7878ef7e0"), - nonce: &hex!("bc672b224b4b6b91fc3fd697"), - plaintext: &hex!("dfea463d35f0fa20487b606d6ccfd422a5b707f16527b422bf1d68a77db67e9c"), - aad: &hex!("faacb84ec7cfadd731de2f7c0892d7e38cbfb782b48412331af0b3eab602a722cad1069dea0052beb5ca70e2ee476c340c6193bcc60f939aabe446bf3ce958fe11a2ffc90241f0a7e4e274f0c1441def795893895bd848bf0f0e"), - ciphertext: &hex!("0ddc2281b1fcb904864a43657bc72357cf73fc1f16520caad7cddde10f846bd9"), - tag: &hex!("9d96699450aa9707695e5de56597101b"), - }, - GcmTV { - key: &hex!("187214df6e2d80ee8e9aae1fc569acd41589e952ddcbe8da018550d103767122"), - nonce: &hex!("56db334422b6c5e93460d013"), - plaintext: &hex!("53355283186719a9146c7305e3d1959a11ccf197570b855a43cbc7563a053c73"), - aad: &hex!("cbedb7ccfbf56dfd72e530bfe16b4f5aac48a90204bcb7a8cae1046010882cfc8b526e7562a7880914e61b60cbd605165242737d85eeed583c98cab3443874e5989ec9cde001adf7de9c9967de5178f75b8412b0c4d6fec5af72"), - ciphertext: &hex!("c2262585966bc9c23dc7cc1059d060211e86f3b3161d38b153635fbea4a28c05"), - tag: &hex!("a94297c584dfcd10ee5df19a2ee5c3d2"), - }, - GcmTV { - key: &hex!("1fded32d5999de4a76e0f8082108823aef60417e1896cf4218a2fa90f632ec8a"), - nonce: &hex!("1f3afa4711e9474f32e70462"), - plaintext: &hex!("06b2c75853df9aeb17befd33cea81c630b0fc53667ff45199c629c8e15dce41e530aa792f796b8138eeab2e86c7b7bee1d40b0"), - aad: b"", - ciphertext: &hex!("91fbd061ddc5a7fcc9513fcdfdc9c3a7c5d4d64cedf6a9c24ab8a77c36eefbf1c5dc00bc50121b96456c8cd8b6ff1f8b3e480f"), - tag: &hex!("30096d340f3d5c42d82a6f475def23eb"), - }, - GcmTV { - key: &hex!("b405ac89724f8b555bfee1eaa369cd854003e9fae415f28c5a199d4d6efc83d6"), - nonce: &hex!("cec71a13b14c4d9bd024ef29"), - plaintext: &hex!("ab4fd35bef66addfd2856b3881ff2c74fdc09c82abe339f49736d69b2bd0a71a6b4fe8fc53f50f8b7d6d6d6138ab442c7f653f"), - aad: b"", - ciphertext: &hex!("69a079bca9a6a26707bbfa7fd83d5d091edc88a7f7ff08bd8656d8f2c92144ff23400fcb5c370b596ad6711f386e18f2629e76"), - tag: &hex!("6d2b7861a3c59ba5a3e3a11c92bb2b14"), - }, - GcmTV { - key: &hex!("fad40c82264dc9b8d9a42c10a234138344b0133a708d8899da934bfee2bdd6b8"), - nonce: &hex!("0dade2c95a9b85a8d2bc13ef"), - plaintext: &hex!("664ea95d511b2cfdb9e5fb87efdd41cbfb88f3ff47a7d2b8830967e39071a89b948754ffb0ed34c357ed6d4b4b2f8a76615c03"), - aad: b"", - ciphertext: &hex!("ea94dcbf52b22226dda91d9bfc96fb382730b213b66e30960b0d20d2417036cbaa9e359984eea947232526e175f49739095e69"), - tag: &hex!("5ca8905d469fffec6fba7435ebdffdaf"), - }, - GcmTV { - key: &hex!("aa5fca688cc83283ecf39454679948f4d30aa8cb43db7cc4da4eff1669d6c52f"), - nonce: &hex!("4b2d7b699a5259f9b541fa49"), - plaintext: &hex!("c691f3b8f3917efb76825108c0e37dc33e7a8342764ce68a62a2dc1a5c940594961fcd5c0df05394a5c0fff66c254c6b26a549"), - aad: b"", - ciphertext: &hex!("2cd380ebd6b2cf1b80831cff3d6dc2b6770778ad0d0a91d03eb8553696800f84311d337302519d1036feaab8c8eb845882c5f0"), - tag: &hex!("5de4ef67bf8896fbe82c01dca041d590"), - }, - GcmTV { - key: &hex!("1c7690d5d845fceabba227b11ca221f4d6d302233641016d9cd3a158c3e36017"), - nonce: &hex!("93bca8de6b11a4830c5f5f64"), - plaintext: &hex!("3c79a39878a605f3ac63a256f68c8a66369cc3cd7af680d19692b485a7ba58ce1d536707c55eda5b256c8b29bbf0b4cbeb4fc4"), - aad: b"", - ciphertext: &hex!("c9e48684df13afccdb1d9ceaa483759022e59c3111188c1eceb02eaf308035b0428db826de862d925a3c55af0b61fd8f09a74d"), - tag: &hex!("8f577e8730c19858cad8e0124f311dd9"), - }, - GcmTV { - key: &hex!("dbdb5132f126e62ce5b74bf85a2ac33b276588a3fc91d1bb5c7405a1bf68418b"), - nonce: &hex!("64f9e16489995e1a99568118"), - plaintext: &hex!("b2740a3d5647aa5aaeb98a2e7bbf31edaea1ebacd63ad96b4e2688f1ff08af8ee4071bf26941c517d74523668ca1f9dfdbcaab"), - aad: b"", - ciphertext: &hex!("e5fec362d26a1286b7fd2ec0fa876017437c7bce242293ff03d72c2f321d9e39316a6aa7404a65ccd84890c2f527c1232b58d5"), - tag: &hex!("dfa591ee2372699758d2cc43bfcbd2ba"), - }, - GcmTV { - key: &hex!("8433a85f16c7c921476c83d042cb713eb11a83fc0cffe31dde97907f060b4ee9"), - nonce: &hex!("55ffc85ffd1cdea8b8c48382"), - plaintext: &hex!("23bc3983ba5b3be91c8a6aa148a99995241ee9e82ce44e1184beb742affbe48f545c9a980480cf1fab758a46e4711ea9267466"), - aad: b"", - ciphertext: &hex!("2f4bdc7b8b8cec1863e3145871554778c43963b527f8413bb9779935c138a34d86d7c76a9e6af689902f316191e12f34126a42"), - tag: &hex!("7dc63156b12c9868e6b9a5843df2d79e"), - }, - GcmTV { - key: &hex!("5d7bf55457929c65e4f2a97cbdcc9b432405b1352451ccc958bceebce557491d"), - nonce: &hex!("f45ae70c264ed6e1cc132978"), - plaintext: &hex!("ba5ac2a16d84b0df5a6e40f097d9d44bf21de1fcec06e4c7857463963e5c65c936d37d78867f253ce25690811bf39463e5702a"), - aad: b"", - ciphertext: &hex!("47c16f87ebf00ba3e50416b44b99976c2db579423c3a3420479c477cd5ef57621c9c0cee7520acb55e739cc5435bc8665a2a0c"), - tag: &hex!("456054ecb55cf7e75f9543def2c6e98c"), - }, - GcmTV { - key: &hex!("595f259c55abe00ae07535ca5d9b09d6efb9f7e9abb64605c337acbd6b14fc7e"), - nonce: &hex!("92f258071d79af3e63672285"), - plaintext: &hex!("a6fee33eb110a2d769bbc52b0f36969c287874f665681477a25fc4c48015c541fbe2394133ba490a34ee2dd67b898177849a91"), - aad: b"", - ciphertext: &hex!("bbca4a9e09ae9690c0f6f8d405e53dccd666aa9c5fa13c8758bc30abe1ddd1bcce0d36a1eaaaaffef20cd3c5970b9673f8a65c"), - tag: &hex!("26ccecb9976fd6ac9c2c0f372c52c821"), - }, - GcmTV { - key: &hex!("251227f72c481a7e064cbbaa5489bc85d740c1e6edea2282154507877ed56819"), - nonce: &hex!("db7193d9cd7aeced99062a1c"), - plaintext: &hex!("cccffd58fded7e589481da18beec51562481f4b28c2944819c37f7125d56dceca0ef0bb6f7d7eeb5b7a2bd6b551254e9edff3a"), - aad: b"", - ciphertext: &hex!("1cc08d75a03d32ee9a7ae88e0071406dbee1c306383cf41731f3c547f3377b92f7cc28b3c1066601f54753fbd689af5dbc5448"), - tag: &hex!("a0c7b7444229a8cfef24a31ee2de9961"), - }, - GcmTV { - key: &hex!("f256504fc78fff7139c42ed1510edf9ac5de27da706401aa9c67fd982d435911"), - nonce: &hex!("8adcf2d678abcef9dd45e8f9"), - plaintext: &hex!("d1b6db2b2c81751170d9e1a39997539e3e926ca4a43298cdd3eb6fe8678b508cdb90a8a94171abe2673894405eda5977694d7a"), - aad: b"", - ciphertext: &hex!("76205d63b9c5144e5daa8ac7e51f19fa96e71a3106ab779b67a8358ab5d60ef77197706266e2c214138334a3ed66ceccb5a6cd"), - tag: &hex!("c1fe53cf85fbcbff932c6e1d026ea1d5"), - }, - GcmTV { - key: &hex!("21d296335f58515a90537a6ca3a38536eba1f899a2927447a3be3f0add70bea5"), - nonce: &hex!("2be3ad164fcbcf8ee6708535"), - plaintext: &hex!("ad278650092883d348be63e991231ef857641e5efc0cab9bb28f360becc3c103d2794785024f187beaf9665b986380c92946a7"), - aad: b"", - ciphertext: &hex!("b852aeba704e9d89448ba180a0bfde9e975a21cc073d0c02701215872ed7469f00fe349294ba2d72bf3c7780b72c76101ba148"), - tag: &hex!("bdd6d708b45ae54cd8482e4c5480a3c1"), - }, - GcmTV { - key: &hex!("d42380580e3491ddfbc0ec32424e3a281cbe71aa7505ff5ab8d24e64fbe47518"), - nonce: &hex!("fbed88de61d605a7137ffeb2"), - plaintext: &hex!("4887a6ef947888bf80e4c40d9769650506eb4f4a5fd241b42c9046e3a2cf119db002f89a9eba1d11b7a378be6b27d6f8fc86c9"), - aad: b"", - ciphertext: &hex!("87aa27f96187ce27e26caf71ba5ba4e37705fd86ca9291ea68d6c6f9030291cdbff58bff1e6741590b268367e1f1b8c4b94cd4"), - tag: &hex!("d1690a6fe403c4754fd3773d89395ecd"), - }, - GcmTV { - key: &hex!("5511727ecd92acec510d5d8c0c49b3caacd2140431cf51e09437ebd8ca82e2ce"), - nonce: &hex!("ae80d03696e23464c881ccff"), - plaintext: &hex!("184b086646ef95111ccb3d319f3124f4d4d241f9d731ce26662ea39e43457e30b0bd739b5d5dbceb353ce0c3647a3a4c87e3b0"), - aad: b"", - ciphertext: &hex!("aa28cb257698963dfc3e3fe86368d881ac066eb8ee215a7c0ed72e4d081db0b940071e2e64ff6204960da8e3464daf4cb7f37b"), - tag: &hex!("c1578aa6e3325ee4b5e9fb9ee62a7028"), - }, - GcmTV { - key: &hex!("d48f3072bbd535a2df0a2864feb33b488596cd523ad1623b1cefe7b8cbefcf4a"), - nonce: &hex!("bbf2a537d285444d94f5e944"), - plaintext: &hex!("060c585bd51539afdd8ff871440db36bfdce33b7f039321b0a63273a318bd25375a2d9615b236cfe63d627c6c561535ddfb6bd"), - aad: b"", - ciphertext: &hex!("993d5d692c218570d294ab90d5f7aa683dc0e470efac279a776040f3b49386813f68b0db6a7aef59025cc38520fb318a1eac55"), - tag: &hex!("8cd808438a8f5b6a69ff3ae255bf2cb2"), - }, - GcmTV { - key: &hex!("5fe01c4baf01cbe07796d5aaef6ec1f45193a98a223594ae4f0ef4952e82e330"), - nonce: &hex!("bd587321566c7f1a5dd8652d"), - plaintext: &hex!("881dc6c7a5d4509f3c4bd2daab08f165ddc204489aa8134562a4eac3d0bcad7965847b102733bb63d1e5c598ece0c3e5dadddd"), - aad: &hex!("9013617817dda947e135ee6dd3653382"), - ciphertext: &hex!("16e375b4973b339d3f746c1c5a568bc7526e909ddff1e19c95c94a6ccff210c9a4a40679de5760c396ac0e2ceb1234f9f5fe26"), - tag: &hex!("abd3d26d65a6275f7a4f56b422acab49"), - }, - GcmTV { - key: &hex!("885a9b124137e40bd0f697771317e401ce36327e61a8f9d0b80f4798f30a731d"), - nonce: &hex!("beebc2f5a26fd2cab1e9c395"), - plaintext: &hex!("427ec568ad8367c202f5d9999240f9994cc113500154f7f49e9ca27cc8154143b855238bca5c7bd6d9852b4eebd41e4eb98f16"), - aad: &hex!("2e8bdde32258a5fcd8cd21037d0545eb"), - ciphertext: &hex!("a1d83aab6864db463d9d7c22419462bde0740355c1147c62b4c4f23ceeaf65b16b873b1cc7e698dff6e3d19cf9da33e8cbcba7"), - tag: &hex!("4fdbfd5210afa3556ec0fdc48b98e1eb"), - }, - GcmTV { - key: &hex!("21c190e2b52e27b107f7a24b913a34bd5b7022060c5a4dec9ab289ff8ae67e2d"), - nonce: &hex!("b28a61e6c1dfa7f76d086063"), - plaintext: &hex!("4e1b9528cf46b1dd889858d3904d41d3174dcb225923f923d80adbfe6eec144b1d4eb3690d0b8519c99beaee25bb50fd2d148f"), - aad: &hex!("d80657377ddbbed1f9b8d824b3c4d876"), - ciphertext: &hex!("7126fa807aa6b61a60958fe4cc8682bb256e5bbdc499d04a6caa81b23f9e67d3da4cf1994b5a8ecc7bce641864d0519a6509cd"), - tag: &hex!("d3e96568f2cd1a48771ee4f67ad042c1"), - }, - GcmTV { - key: &hex!("11c33ae37680130c51ed11bfaf0fcb6ed4fc7d903ff432b811763d2c7ef83a33"), - nonce: &hex!("0f224d26dbf632cebdce3b8b"), - plaintext: &hex!("f8a2affe5a7e67f2c62622e4a56804b48e529d1faf9096f94409224129921ce46aed898dd5391746e8170e05f91e0524166625"), - aad: &hex!("dee803732ff662cba9f861227f8b67cf"), - ciphertext: &hex!("3856558375c363b25e8f9e9e2eb63cf0e76a1c6e228893c7b22da4a69b682528b4a4ca2b99e7a537390e2d1e05a68f3e39c4e9"), - tag: &hex!("9b12691b2002ca9227035c68ea941ef3"), - }, - GcmTV { - key: &hex!("3b291794fbb9152c3e4f4de4608a9137d277bd651f97e738afaa548d97b4ec60"), - nonce: &hex!("4d1c69c6da96c085d31422ba"), - plaintext: &hex!("21b3ca1f47a0c7f6ebd097eda69d9e5b5fbf5c24d781658003cfd443ae7096be19e1cd3c14fe9738efb00847697fccb466ae1b"), - aad: &hex!("f3a5fa61a4e987413a8fab4aa51d895d"), - ciphertext: &hex!("6c1439cd2cb564e7944fd52f316e84aeffc3fd8024df5a7d95a87c4d31a0f8ea17f21442c709a83b326d067d5f8e3005ebe22a"), - tag: &hex!("e58048f2c1f806e09552c2e5cdf1b9d9"), - }, - GcmTV { - key: &hex!("8e7a8e7b129326e5410c8ae67fbd318de1909caba1d2b79210793c6b2c6e61c7"), - nonce: &hex!("8e48513fdd971861ef7b5dc3"), - plaintext: &hex!("ef6b4145910139293631db87a0d7782a1d95db568e857598128582e8914b4fa7c03c1b83e5624a2eb4c340c8ad7e6736a3e700"), - aad: &hex!("80bb66a4727095b6c201fb3d82b0fcf5"), - ciphertext: &hex!("e302687c0548973897a27c31911fc87ee93d8758c4ded68d6bd6415eaaf86bcc45fa6a1ef8a6ae068820549b170405b3fc0925"), - tag: &hex!("ff5c193952558e5a120e672f566be411"), - }, - GcmTV { - key: &hex!("d687e0262f7af2768570df90b698094e03b668ce6183b6c6b6ca385dcd622729"), - nonce: &hex!("50f6904f2d8466daa33c2461"), - plaintext: &hex!("79e3067d94464e019a7c8af10b53adf5b09426d35f2257c3cbaffe1ff720565c07e77aeef06f9d03a2353053992073a4ed1fc8"), - aad: &hex!("e8fa99432929d66f10205ad3e9592151"), - ciphertext: &hex!("18f6e6aeecc8dc5a3d0b63a2a8b7bfaf695bd9c49a7392dbfa8ed44771eebe27f94589d8a430da4cf03a8693bc7525e1fcac82"), - tag: &hex!("3c864eaa1b0ae44a7f0ad9ba287ba800"), - }, - GcmTV { - key: &hex!("26dc5ce74b4d64d1dc2221cdd6a63d7a9226134708299cd719a68f636b6b5ebd"), - nonce: &hex!("0294c54ff4ed30782222c834"), - plaintext: &hex!("ae4c7f040d3a5ff108e29381e7a0830221d5378b13b87ef0703c327686d30af004902d4ddb59d5787fecea4731eaa8042443d5"), - aad: &hex!("2a9fb326f98bbe2d2cf57bae9ecbeff7"), - ciphertext: &hex!("9601aec6bc6e8a09d054a01e500a4e4cdcc7c2cf83122656be7c26fc7dc1a773a40be7e8a049a6cdf059e93a23ca441ef1ca96"), - tag: &hex!("b620a8a0c8fe6117f22735c0ca29434c"), - }, - GcmTV { - key: &hex!("7fa0644efc7f2e8df4b311f54ba8b8c975b2c2aa97962f8ca8a322541bedaa9d"), - nonce: &hex!("5e774e45a07eeb9721734412"), - plaintext: &hex!("84d1c75455e4c57419a9d78a90efc232c179517fe94aff53a4b8f7575db5af627f3d008006f216ecfc49ab8da8927ff5dc3959"), - aad: &hex!("6ad673daa8c412bf280ea39ba0d9b6d4"), - ciphertext: &hex!("e2f00b5a86b3dec2b77e54db328c8d954d4b716f9735e5798b05d65c512674d56e88bda0d486685a45d5c249719884329e3297"), - tag: &hex!("0ce8eb54d5ad35dd2cb3fa75e7b70e33"), - }, - GcmTV { - key: &hex!("91d0429f2c45cf8ab01d50b9f04daaaccbe0503c9f115f9457c83a043dc83b23"), - nonce: &hex!("34401d8d922eebac1829f22e"), - plaintext: &hex!("d600d82a3c20c94792362959de440c93119a718ac749fa88aa606fc99cb02b4ca9ba958d28dc85f0523c99d82f43f58c5f979b"), - aad: &hex!("1b29de9321aebc3ff9d1c2507aee80e9"), - ciphertext: &hex!("84cbc9936eb7270080bb7024780113d064eccb63d3da0bd6bce4f8737d28304bfb6102f3ae9c394cc6452633fc551582bbfe1d"), - tag: &hex!("e132dc8a31d21f24ea0e69dfb6b26557"), - }, - GcmTV { - key: &hex!("44e6411b9fbfcef387d0ca07b719181c7567e27dba59e8e1c3cc1763cfeaca04"), - nonce: &hex!("25a1cfd97bd8e63de5d65974"), - plaintext: &hex!("db28a592b1f3603c287991a69cc64eacdd62046445a8ba4067575f12553de155d06a9b40ddf58fec56c8171687b9cb54b1f346"), - aad: &hex!("4b1751b074ab649d27fd3f2c4d7ee33a"), - ciphertext: &hex!("36bf6bb761b2248fe71a620e34e9d18e12a74ca42c9a9a21d30345995a83eb44bcae3c67c020730cd8d5e51a741694cc396469"), - tag: &hex!("e69ebf80a88d6eca41ae87cdcab4e1f2"), - }, - GcmTV { - key: &hex!("a94bfcefae90f9078860db80ccc50819eadf7cce29df3279f94f5eea97009ef2"), - nonce: &hex!("f481bcb7f5da296e9454ff78"), - plaintext: &hex!("97d0c7dfcab32a386f51d92e89333ec84eecd552e68d14cf48b75067bf0e1946ad03a5d063b852ca053c929088af45d0884a88"), - aad: &hex!("9f80d845577818df9ba984ee552ae203"), - ciphertext: &hex!("18a1c9bfe1b1dfdd06e465df347c1e942b37b3e48cb0c905841a593b5b0d0330feb3b8970dbc9429252a897f0f8e12860ea39a"), - tag: &hex!("10cf4d335b8d8e7e8bbaf49222a1cd66"), - }, - GcmTV { - key: &hex!("a50a60e568ff35a610ef9479c08bbc7bb64c373fc853f37fa6b350250a26f232"), - nonce: &hex!("5ada1d4aca883d7bd6fa869f"), - plaintext: &hex!("9ea44e72a1d21395cd81d20db05816441010efd8f811b75bb143ab47f55eefce4eec5f606fa5d98b260d7e5df4a7474cbd8599"), - aad: &hex!("cc7a7a541be7a6d1b846354cb6a571e6"), - ciphertext: &hex!("4165b135187faeb395d4531c062738e0d47df8bed91982eb32e391a6b3711f117b6fae0afde791de3e72fcf96d2b53ff1a621a"), - tag: &hex!("e2cbfea2100585b2cbe5107da17ff77a"), - }, - GcmTV { - key: &hex!("5ff3311461d247ceb1eaf591292fcba54308dd3484fd1851e09a12b8f6663fc1"), - nonce: &hex!("61af2e6aec183129cf053c2b"), - plaintext: &hex!("920df8b2888a74022ede6919ed0bf48ccf51e395fe5bfa69a6209ff9a46674024eaa4f43ae2c933730b9fdc8ad216130447cc8"), - aad: &hex!("5eafed6674f2ae83397df923e059db49"), - ciphertext: &hex!("0e35e1208168b639e012df398bc8bf2b19b08d46af0353cd78f6d1b7ae14e6224c1da6fdc9433b171f1cd2b512d5f1acd84f03"), - tag: &hex!("5bc77eb02e4d51e2019446b468498d0e"), - }, - GcmTV { - key: &hex!("42e93547eee7e18ec9620dd3dc0e2b1cf3e5d448198a902ded3f935da9d35b33"), - nonce: &hex!("e02e12ba92a6046af11adf0e"), - plaintext: &hex!("6c3704b32527ace3d5236687c4a98a1ad5a4f83c04af2f62c9e87e7f3d0469327919d810bb6c44fd3c9b146852583a44ed2f3c"), - aad: &hex!("ac3d536981e3cabc81211646e14f2f92"), - ciphertext: &hex!("8b6506af703ae3158eb61e2f9c2b63de403b2ebc6b1e6759ceb99c08aa66cb07d1d913ac4acd7af9b9e03b3af602bcaf2bb65e"), - tag: &hex!("a6ce2ccb236fc99e87b76cc412a79031"), - }, - GcmTV { - key: &hex!("24501ad384e473963d476edcfe08205237acfd49b5b8f33857f8114e863fec7f"), - nonce: &hex!("9ff18563b978ec281b3f2794"), - plaintext: &hex!("27f348f9cdc0c5bd5e66b1ccb63ad920ff2219d14e8d631b3872265cf117ee86757accb158bd9abb3868fdc0d0b074b5f01b2c"), - aad: &hex!("adb5ec720ccf9898500028bf34afccbcaca126ef"), - ciphertext: &hex!("eb7cb754c824e8d96f7c6d9b76c7d26fb874ffbf1d65c6f64a698d839b0b06145dae82057ad55994cf59ad7f67c0fa5e85fab8"), - tag: &hex!("bc95c532fecc594c36d1550286a7a3f0"), - }, - GcmTV { - key: &hex!("fb43f5ab4a1738a30c1e053d484a94254125d55dccee1ad67c368bc1a985d235"), - nonce: &hex!("9fbb5f8252db0bca21f1c230"), - plaintext: &hex!("34b797bb82250e23c5e796db2c37e488b3b99d1b981cea5e5b0c61a0b39adb6bd6ef1f50722e2e4f81115cfcf53f842e2a6c08"), - aad: &hex!("98f8ae1735c39f732e2cbee1156dabeb854ec7a2"), - ciphertext: &hex!("871cd53d95a8b806bd4821e6c4456204d27fd704ba3d07ce25872dc604ea5c5ea13322186b7489db4fa060c1fd4159692612c8"), - tag: &hex!("07b48e4a32fac47e115d7ac7445d8330"), - }, - GcmTV { - key: &hex!("9f953b9f2f3bb4103a4b34d8ca2ec3720df7fedf8c69cac900bd75338beababe"), - nonce: &hex!("eb731ae04e39f3eb88cc77fa"), - plaintext: &hex!("3b80d5ac12ba9dad9d9ff30a73732674e11c9edf9bb057fd1c6adc97cf6c5fa3ee8690ad4c51b10b3bd5da9a28e6275cbe28cb"), - aad: &hex!("d44a07d869ac0d89b15262a1e8e1aa74f09bcb82"), - ciphertext: &hex!("1533ce8e2fc6ab485aef6fcfb08ded83ae549a7111fce2a1d8a3f691f35182ce46fce6204d7dafb8d3206c4e4b645bc3f5afd1"), - tag: &hex!("f09265c21f90ef79b309a93db73d9290"), - }, - GcmTV { - key: &hex!("2426e2d1cd9545ec2fb7ab9137ad852734333925bfc5674763d6ee906e81c091"), - nonce: &hex!("49a094a71d393b36daa4a591"), - plaintext: &hex!("7cbe7982d365a55d147c954583f9760a09948ab73ebbe1b2c1d69ed58e092a347392192cfe8bce18ca43ee19af7652331bd92c"), - aad: &hex!("177309cfc913e3f5c093e8b1319ba81826d43ce5"), - ciphertext: &hex!("cab992e17cf6ec69fd3c67ea0424bcd67475a7f1f16e6733c4419d1b5a755f78d6eda8e368360d403800a08f0d52b4bc0aa0ab"), - tag: &hex!("b125f8caee9e54b9f9414b1c09021ed8"), - }, - GcmTV { - key: &hex!("8dc1b24bcbbee3cb8e14b344166d461d00c7490041edc9fa07e19cc82a3ed9c4"), - nonce: &hex!("31768ad18c971b188d947019"), - plaintext: &hex!("84e4f79dbb7209cbaf70e4fefe137c494786c899602783e9c034296978d7f0c571f7ea9d80ed0cc4723124872d7326890300c1"), - aad: &hex!("eb3673b64560cca7bda76a1de7ae1014ee1acaee"), - ciphertext: &hex!("2402acd865d4b731bc9395eae0e57d38fdf5ce847ac7aef75791a52c7573ea9b3a296e62cb1ed97c4bd34be50ee7f3d75747cf"), - tag: &hex!("665abb725498ede2b0df655fc1765a2b"), - }, - GcmTV { - key: &hex!("bc898f643a5f2cd864c10b507b4b803b4ff4ace61fadcc7bcd98af394731b791"), - nonce: &hex!("cc447d83c0a6734a79778c64"), - plaintext: &hex!("124eb963cdb56fa49c70a9b1aa682445c55065f26859f1d16eef7cfe491587533eedd7e23deabddfc5550c2fa6a08b17822699"), - aad: &hex!("e932bd2e0e6c550d136f725e14c53d27ffb20f6a"), - ciphertext: &hex!("45d8908ef9eef369e78b7ea0b7d023a92c63648271927efe9b0220eb09ed96f3b635c6ec8bfc68b4c228b712494bb37f4c7f1a"), - tag: &hex!("47899857494bac28d2176a9c923026b2"), - }, - GcmTV { - key: &hex!("8e82a85466ee024eb1ae10c4982d6a95e6dbe5582299ab37fe89a9db80ab51a6"), - nonce: &hex!("04cfd489e18eeb7a4a8ab36b"), - plaintext: &hex!("3aa2e4eaed18c4602715ae77379e9083708af9f9b49031324d41abca61440319c8c8e6dbcc20006a825b12ced00b2286848a94"), - aad: &hex!("7bb54b1a6ed0ca387268a146430c0bfa2602a8fd"), - ciphertext: &hex!("674b1391937074642408eeae9b748ca629da9fd00281824f5a108f6078ee78f98749392bb6e29b53e53e4b11739ac53a8e653b"), - tag: &hex!("e320a873a9c2e8ef455698c37ea59a6d"), - }, - GcmTV { - key: &hex!("f1f2c5503ebf35ac1373c29e2305e963f89f6ed015a181b70fb549429805d5d9"), - nonce: &hex!("2fb5c6a24f406872755db05c"), - plaintext: &hex!("b4a2809198035c277637bb1c2927fb5c60b49ef9087c800012d8663d997983fcb78d51a054114a24e1e1b5214b58e7dee47195"), - aad: &hex!("92c1f3489aed90aedafb55562a34b3f4be29e101"), - ciphertext: &hex!("f051a3a968278a46630b2894a0d386c18fa034960d8ddd14e88e1071afbbca5baf02967c2270117b4fb2bd4cfd032174505f99"), - tag: &hex!("6f1db5293660b6904f7f008e409bdc06"), - }, - GcmTV { - key: &hex!("f0338d26d74bd1768da5bb79c59fab2b4abe1966324048790c44bc98a6b34b6c"), - nonce: &hex!("c8269e4406fa0be1cf057b2f"), - plaintext: &hex!("323c373e4d85a1fd21f387fdd8c7e6aeebd5aae893d7af286cb214600cba8b9eb06df085a2dc5aed870259f7f3cc81d3eb53bd"), - aad: &hex!("13fb0edcba095cef9c4343a0629fd5020f03729d"), - ciphertext: &hex!("08572b9cf9bcfd21d4403a1218d94476b9ee8c3b94c56625c21ccaf4c0efa34cf22a532389210793699c9de1ab14f8c4c52928"), - tag: &hex!("29968c9fb610940cee9fd5b2f7c8ba21"), - }, - GcmTV { - key: &hex!("a67648285b65b9196060aaa02af279170164353e38fb77c3968c403cfa9acdc8"), - nonce: &hex!("0822d6b3e91eccb7e14245fd"), - plaintext: &hex!("b5d271768c12ccabf89eb2d58cbde840c26d1c9b3692581f90c8b0d7b2cff31ae9192d284f5448de7d924a7b08f115edae75aa"), - aad: &hex!("0d9a5af7ac27438d92534d97ff4378274790e59f"), - ciphertext: &hex!("b59041eed7abc2ff507d1932b5c55ac52728e5ac6648dcc74b38870db6181b1989f95a0144f0db368ec50414cfda0b977141e3"), - tag: &hex!("1d12ce89e1261d73470f3ae36ab87288"), - }, - GcmTV { - key: &hex!("51162b2435f3cf43471f4cc0ffac98b438501ee9b887843a66e9951ca35b8767"), - nonce: &hex!("dcb902eaa837ed22bf5fa636"), - plaintext: &hex!("3edf43358f5109a4dfb4a02987170a67cdd170f6028f7708bdd7726f476b882b9640270f2270f7babfa384181c8e58c15d04c4"), - aad: &hex!("4d459905ff89aed07dcda43a3d191a3da9309faa"), - ciphertext: &hex!("046a2313d36cbc43b6d0787e5ef37d153090a31d0f6656004034be72b9b07ace3a8abe8614362282d87da40c29c60a1a9f5c40"), - tag: &hex!("c7410b5cb94d2877c189983791cee82e"), - }, - GcmTV { - key: &hex!("2fa2beb1cde2226f28fb42a5fb0af3fc58fbb76bf14aa436e6535d466456a0f4"), - nonce: &hex!("50190514a3740b3c0b1df576"), - plaintext: &hex!("a5e0b4837dfca263ba286abf7940b6e70fabb55d8dee5028617c1190fbd327f79b79d2f34db6076ab07cecff7114b15ca02a33"), - aad: &hex!("25142928c1ae9c7b850309e07df359389db539fc"), - ciphertext: &hex!("850fd22bd0897b98ce40bc6c1345a9d59abf796b1b8c34ee8b377e54ee7d59dec05c022ecae96ffdfa1311bdd4e7a9d35aac47"), - tag: &hex!("4b5ab89b4f627ca32d12a1791c286870"), - }, - GcmTV { - key: &hex!("a92a797ce2b2f382030b77a1abe94c8076eee88de2dc4929350b244dbdaddd30"), - nonce: &hex!("716f577401a7893c42c91710"), - plaintext: &hex!("9d26ff79a89720fab6e4cda85887e3c0c3f86a4670d065c8ea68042b6f9f16dd2c5b31acb36331f5b1e50f08c492dc12eebd9e"), - aad: &hex!("8642681f1839b88990c2a939f00c9b90766dadac"), - ciphertext: &hex!("3080bcf3604cf81f5f2c6edc80dfe5d877168a9903598a700a0bbae188fadc7a8b76a04b40400f9252d7f9437fa8f024a3bdeb"), - tag: &hex!("8fc56f6bf48efb00476886b2a03ecb89"), - }, - GcmTV { - key: &hex!("89d0723e5a087456b7b709b8b21be380b463ba3dc9b79170e9947526798fe91c"), - nonce: &hex!("68e2f307b7d49d4d9c041755"), - plaintext: &hex!("7fe2afb710e8fd49cca1c2ba8fd0814594fba4d667017630e170a8a379fa5837bf370ca1cd4c98bd8c4f13eb7068ffa71ab07c"), - aad: &hex!("b34805b30703a62b6d37c93f2443e1a33154b5fb"), - ciphertext: &hex!("b841012752bbf1dfa7b59366dbf353bf98b61ff2e6e7a13d64d9dcb58b771003c8842ac002aac1fa8ca00a21eaf101ab44f380"), - tag: &hex!("73a93e2722db63c2bbf470d5193b2230"), - }, - GcmTV { - key: &hex!("329a6e94b1cce693e445694650d62b8c2c9ab03a09e6d4eca05c48291e576b89"), - nonce: &hex!("78f471bc32f8637a213e87ac"), - plaintext: &hex!("65264d75e1a176a7e966e59109cd074ac5d54740eb0c58084af023e5599eb611846199579d95ba94b6d25ee4d9074b9714f231"), - aad: &hex!("c00c465524e2e2f8a55c0793ed9af851be45a70e"), - ciphertext: &hex!("964d665d1e3c1018dfd883e217cfe4c856cc844f7644b53bb68fbe66f8541fa43ac54e92a2b194d6d8929fe031e94b3e70eca0"), - tag: &hex!("fd511385711236f2e99e6da5042007b7"), - }, - GcmTV { - key: &hex!("463b412911767d57a0b33969e674ffe7845d313b88c6fe312f3d724be68e1fca"), - nonce: &hex!("611ce6f9a6880750de7da6cb"), - plaintext: &hex!("e7d1dcf668e2876861940e012fe52a98dacbd78ab63c08842cc9801ea581682ad54af0c34d0d7f6f59e8ee0bf4900e0fd85042"), - aad: &hex!("0a682fbc6192e1b47a5e0868787ffdafe5a50cead3575849990cdd2ea9b3597749403efb4a56684f0c6bde352d4aeec5"), - ciphertext: &hex!("8886e196010cb3849d9c1a182abe1eeab0a5f3ca423c3669a4a8703c0f146e8e956fb122e0d721b869d2b6fcd4216d7d4d3758"), - tag: &hex!("2469cecd70fd98fec9264f71df1aee9a"), - }, - GcmTV { - key: &hex!("55f9171a03c21e09e3a5fd771e56bffb775ebb190319f3dc214c4b19f72e5482"), - nonce: &hex!("14f3bf95a08e8f52eb46fbf9"), - plaintext: &hex!("af6b17fd67bc1173b063fc6f0941483cee9cbbbbed3a4dcff55a74b0c9535b977efa640e5b1a30faa859fd3daa8dd780cc94a0"), - aad: &hex!("bac1ddefd111d471e75f0efb0f8127b4da923ecc788a5c91e3e2f65e2943e4caf42f54896604af19ed0b4d8697d45ab9"), - ciphertext: &hex!("3ae8678089522371fe4bd4da99ffd83a32988e0728aa3a4970ded1fe73bc30c2eb1fe24c0ff5ab549ac7e567d7036628fd718d"), - tag: &hex!("cf59603e05f4ed1d2da04e19399b8512"), - }, - GcmTV { - key: &hex!("54601d1538e5f04dc3fe95e483e40dec0aaa58375dc868da167c9a599ed345d9"), - nonce: &hex!("c5150872e45c341c2b99c69a"), - plaintext: &hex!("ae87c08c7610a125e7aa6f93fac0f80472530b2ce4d7194f5f4cb8ac025323c6c43a806788ef50c5028764ec32f2839005c813"), - aad: &hex!("93cd7ee8648a64c59d54cdac455b05ffdfc2effe8b19b50babd8c1a8c21f5dc8dc6050e2347f4cd28701594b9f8d4de5"), - ciphertext: &hex!("d5f005dc67bdc9738407ce2401977f59c9c83520e262d0c8db7fe47ae0eada30d674694f008e222f9733a6e63d81499e247567"), - tag: &hex!("3470155144c74929980134db6995dd88"), - }, - GcmTV { - key: &hex!("e966c470cbecc819260640d5404c84382e6e649da96d29cad2d4412e671ed802"), - nonce: &hex!("b3a92d6f49fe2cb9c144d339"), - plaintext: &hex!("7adf6fcb41d59b8d2b663010c3d4cf5f5f0b95cf754f76f8626c4428467e5c6684e77e7857b1cc755762e9ea9117e3bb077040"), - aad: &hex!("dfa62a3a4b5b3af6770cfd3cef3bbb4cce3f64925782a9a8a6e15fe3744d8f9310400dd04e8d7966c03850539e440aa5"), - ciphertext: &hex!("5f5b09486e6cd2a854e5622b4988e2408fddaca42c21d946c5cd789fe5a1306ef33c8cd44467ad7aa4c8152bce656a20367284"), - tag: &hex!("2b388109afdada6473435230d747b4eb"), - }, - GcmTV { - key: &hex!("4a8a12c0575ec65ae1c5784d2829bc7b04818eb00bd4c90a0d032ea281076e27"), - nonce: &hex!("959f113b705397fb738018b0"), - plaintext: &hex!("0c5571195586e4fc7096fb86cfcd6684081446f3d7adc33a897f03ac4ff6c3cc2019b67bd3184c86070764f6deaa8a10d0d81f"), - aad: &hex!("adb8bc96142a1025122dc22f826957197af33dcdcf6b7ab56bc1a5e17e8534e48b8daf685faf9543bb343614bdf6737f"), - ciphertext: &hex!("84212d5991231d35c4e8621163e5b370a0105a05856866e74df72c0808c062981570d32d274ea732fa4d29f9cfa7839cadbe6a"), - tag: &hex!("39cee3b8fa0bf92605666ccd9eb19840"), - }, - GcmTV { - key: &hex!("6197a4fa7cfcedeff223f69ea68b4ddf54b683350c20875be353077e9bbce346"), - nonce: &hex!("1a69ecabd42c53c0ec64fcd0"), - plaintext: &hex!("40a487b4daf866c20f3c4911a0586709c3344aa988dc9c464bcf36cc4e3d92701e611e60cf69f3edbf76cd27ff6ba935026d7f"), - aad: &hex!("b20a7ca5b5b603f661587e01f7ef171823ef463c187ded77a3d616400cc1d2b0b688ac9e927498341560cbc8eb9a4198"), - ciphertext: &hex!("06420fa038ee62db30cc05bfe34c8d2c39a9d439653907c512ed606511921fe76110913a5bfb6b6c7b23d7f8883f5ab65f4b14"), - tag: &hex!("4d3097c9919002cd1da83f29820312ed"), - }, - GcmTV { - key: &hex!("c9dbe185023ecaa78be9bfac1b91b9da6bd7c11349feb69e6b0be83a838e77b2"), - nonce: &hex!("8940fa7c6afd3f7a09ec93b6"), - plaintext: &hex!("075be0d61273e6975978d0b88b3fa38fc398d4d0f22a342a8afa5562af0e7c8fa548f0d8faec898a20c97e851754992c1ed4a3"), - aad: &hex!("f17bd357608365e66b98e49191cdc2a3813bba5a1b7988aa8aaaaad4b86d0ef4e2698cad799d63fcd2a5e87c0e3e929a"), - ciphertext: &hex!("615c1097d577363a77bfc7dd57179acb68166e78021b3397d7029ce33cbc848f036b9c07989eeb9f42aeaeebe8542f103b1d32"), - tag: &hex!("a22ab25fd8a6127469e8ce9ff686d575"), - }, - GcmTV { - key: &hex!("e6cdcf497a6e119009bf43ac183d2dd4d4e967964ef92811f69eb18d92923305"), - nonce: &hex!("3e88459a76e1dcc890788297"), - plaintext: &hex!("72a3dfb555ba0029fc3d1c85b836f76135bd1858189efdde2db29045f2c26e6a65627d81a0b85ca42e8269d432a41154e929ac"), - aad: &hex!("a359f86ec918537d80a84da7b66bca700c1ff9ec7f8695a30808d484da218d15ae89c5f943e71778445130191f779001"), - ciphertext: &hex!("9ae3f8ccae0bb5789b1105118760c406e41175a76612435cb0c8be225ea6b368c9d08c9d9a24b512d1458e94af79e3060ab69e"), - tag: &hex!("ac3bbc8fd6a7097df6f298411c23e385"), - }, - GcmTV { - key: &hex!("de5531b50888b61d63af2210ee23f46d91a5e60312bd578584af586bf22ea756"), - nonce: &hex!("0fde8689b0348bbcfaa89fec"), - plaintext: &hex!("80621e54eef1c92afb1f64ed860e39311eea7e2cca6f5624008c1d2e581d7112b7ee0b559fc3db575b7b7c42ee4f2a20442dc0"), - aad: &hex!("22db97cd5f359f12aec66c51c7da79ba629db4c8c7e5501be2ec1e4cc3f3944b6e3057d093bc68b735b5156950f91804"), - ciphertext: &hex!("933018419a32b7bf65f9777c44889a44b32d61ceddbb46839366ce2ca2ffeb1833f46559e59c93bb07f622d9633f13932cf7f1"), - tag: &hex!("25023a4ee9bdbf525cfef888e2480f86"), - }, - GcmTV { - key: &hex!("bc0c6368a9bb2622f6d5ba12de581f003336c298adac34499bf26b11e630f891"), - nonce: &hex!("2aa8f30b567cf1edd818e42d"), - plaintext: &hex!("1dcc1a3167fba55c00d3383e26d386eaa0449154599992da7f7f6598f41b3eb8e4d0a9143dfcab963f5c390a6ae2010fbcf6ec"), - aad: &hex!("0e28ebf87eb757e83031fb836f7b049a46bd740b0a39c9b798d2407e1150da86dfe84121c7c98449559453ad7558e779"), - ciphertext: &hex!("78d00a6e3302369817b9cf1f24ea13c41751382e3fea74403d094737e32fb507184cfebce48d10b4ce8db12ef961e4df2c8e95"), - tag: &hex!("c0aff3594f86b58e229c7ad05c2b84f0"), - }, - GcmTV { - key: &hex!("5d98a0c7ad6f9c0b116613ca5082250356a6a9bca55fe1a4a2962b733214dac4"), - nonce: &hex!("8b2d8e8d83bdd6a3125dd997"), - plaintext: &hex!("4f3685c2cfbc856379d1fd00f9611fe4c0a4b9c4013fe1bee144449709a6a7e31ff6fb0da74ed464b066b03b50f19cd7f5f9bc"), - aad: &hex!("2f20636d46ce37e9bb0ca0c41d819e3eabcedacbd1ca3ced112d3ad620bbd3b2effe80d3ec8760706e8f14db83139a70"), - ciphertext: &hex!("8e178c0e3e5d22b3be897e0b8879b0d53fef2efb9946ccff6d717b001e3033f2cc22d01d9551e9c0749de704fbe3189328cbb0"), - tag: &hex!("541b7db823e37b5ed323626b9c6748f6"), - }, - GcmTV { - key: &hex!("d80a2703e982de1a2fe706ffe6e389f351ab356ccf056df045e2941b42ef21a4"), - nonce: &hex!("1521ab8f7242cba05427f429"), - plaintext: &hex!("6f9fde28e85776a49cfbad1459d94611757a3cd996aa6e2d702d0483a4d88d532131ebd405b351226b16d19d30d32807a1d511"), - aad: &hex!("5395de90d6bec7c159ab9d6cfa663bdc6295d025e1fcc8b760b9ba42d785eda218dabc6fa7c0f733ad77f61682bff2db"), - ciphertext: &hex!("1e72a8495ceadaf0d31b28ba7cb7c37ccb117761d38fe7dd98eb230ff4ea0b400401e9b5311a7be9b2a533523ad469e2fdb233"), - tag: &hex!("bb174b7624c935ff75b3b77ff7068a98"), - }, - GcmTV { - key: &hex!("6d5c69d7135c0b5b7fef512c127fa788092f1a908358ab658b8f23e463409aa5"), - nonce: &hex!("b36cccad38cd6148a384a026"), - plaintext: &hex!("b4e74f5c56f2ea056d9ff931525944dfad207e063ba226c354e0320a50449967e964580d9b57028c14005aba6865f8bc6a3ef8"), - aad: &hex!("b19f4616bb1452251a2a7dbf78f920194f139e0424d27683621d1ee1e865737c2466e058439c8e122e582a7b63607ce9"), - ciphertext: &hex!("1ce12cd5502efa9ea259584ae9b3c7dbd9444380d4b77a2c787f9b2257019b23ee183dffebb3106a26b18d8a23445626a578e2"), - tag: &hex!("62945e31bae3181855b69c37898ac5bf"), - }, - GcmTV { - key: &hex!("e6afe3c4db2c1d13edb1c5931b2b4b515ec0fd6201139ee1ea55cec92263830e"), - nonce: &hex!("358bd9ea64177d1e23a41726"), - plaintext: &hex!("710bb3394b094ee7d053bc6599b26dafd337e8a61c580d0446c3bf195e77ca5132c8ec3a47a61579dce38360bba7c65e4d5634"), - aad: &hex!("7e0f841cddd7eeebd1ec7b7b8d0e2f71656e5e9ff3cfa739c0b9d0ec4941a0b3f3b396690dbe5f5082d6fb6dd701c68d"), - ciphertext: &hex!("4574a8db515b41c14c2a962dff34e2161a7195c491b11b79889aff93c5b79a6455df9fe8ef5c5b9edb5da1aa9fe66058b9065f"), - tag: &hex!("7c928d7f5cbac9bb4b5928fe727899eb"), - }, - GcmTV { - key: &hex!("5cb962278d79417b7795499e8b92befe4228f3ba5f31992201aa356a6d139a67"), - nonce: &hex!("76f7e7608f09a05f336994cf"), - plaintext: &hex!("2e12cbd468086aa70e2ecd1ddef561e85c225dd083e5956f5c67503344b0ea982bb5044dafbcc02a5b9be1e9b988902d80172b"), - aad: &hex!("032de3fdec273fc8446c2bf767e201f2c7c190acf9d6d321a24a0462cbc3356e798fe23d6c1b4fe83be9c95d71c05504"), - ciphertext: &hex!("c959344a46aa5216d2b37c832436eb72a4a363a6df5642cfbbfd640dea1d64c80bd97eabc1aab192969ee0b799e592a13d2351"), - tag: &hex!("51b227eaf7228a4419f2f3b79b53463a"), - }, - GcmTV { - key: &hex!("148579a3cbca86d5520d66c0ec71ca5f7e41ba78e56dc6eebd566fed547fe691"), - nonce: &hex!("b08a5ea1927499c6ecbfd4e0"), - plaintext: &hex!("9d0b15fdf1bd595f91f8b3abc0f7dec927dfd4799935a1795d9ce00c9b879434420fe42c275a7cd7b39d638fb81ca52b49dc41"), - aad: &hex!("e4f963f015ffbb99ee3349bbaf7e8e8e6c2a71c230a48f9d59860a29091d2747e01a5ca572347e247d25f56ba7ae8e05cde2be3c97931292c02370208ecd097ef692687fecf2f419d3200162a6480a57dad408a0dfeb492e2c5d"), - ciphertext: &hex!("2097e372950a5e9383c675e89eea1c314f999159f5611344b298cda45e62843716f215f82ee663919c64002a5c198d7878fd3f"), - tag: &hex!("adbecdb0d5c2224d804d2886ff9a5760"), - }, - GcmTV { - key: &hex!("e49af19182faef0ebeeba9f2d3be044e77b1212358366e4ef59e008aebcd9788"), - nonce: &hex!("e7f37d79a6a487a5a703edbb"), - plaintext: &hex!("461cd0caf7427a3d44408d825ed719237272ecd503b9094d1f62c97d63ed83a0b50bdc804ffdd7991da7a5b6dcf48d4bcd2cbc"), - aad: &hex!("19a9a1cfc647346781bef51ed9070d05f99a0e0192a223c5cd2522dbdf97d9739dd39fb178ade3339e68774b058aa03e9a20a9a205bc05f32381df4d63396ef691fefd5a71b49a2ad82d5ea428778ca47ee1398792762413cff4"), - ciphertext: &hex!("32ca3588e3e56eb4c8301b009d8b84b8a900b2b88ca3c21944205e9dd7311757b51394ae90d8bb3807b471677614f4198af909"), - tag: &hex!("3e403d035c71d88f1be1a256c89ba6ad"), - }, - GcmTV { - key: &hex!("c277df045d0a1a3956958f271055c229d2634427b1d73e99d54920da69f72e01"), - nonce: &hex!("79e24f84bc77a21a6cb14ee2"), - plaintext: &hex!("5ca68d858cc30b1cb0514c4e9de98e1a1a835df401f69e9ec6f1bcb1158f09114dff551683b3827457f77e17a7097b1ea69eac"), - aad: &hex!("ca09282238d492029afbd30ea9b4aa9d448d77b4b41a791c35ebe3f8e5034ac71210117a843fae647cea020712c27e5c8f85acf933d5e28430c7770862d8dbb197cbbcfe49dd63f6aa05fbd13e32c459342698dfee5935c7c321"), - ciphertext: &hex!("5c5223c8eda59a8dc28b08e6c21482a46e5d84d32c7050bf144fc57f4e8094de133198da7b4b8398b167204aff837da15d9ab2"), - tag: &hex!("378885950a4491bee3cd681d3c957b9a"), - }, - GcmTV { - key: &hex!("4d07f78d19e6d8bb32bf209f138307890f0f1ae39362779ff2bf1f9b734fe653"), - nonce: &hex!("d983a5d5af78a3b1cd5fbd58"), - plaintext: &hex!("94f0bbc4340d97d854e25cc7ce85ea1e781e68bf6f639e0a981bb03e3c209cbf5127171cb0fff65bc3ecac92774d10146d1ac5"), - aad: &hex!("a3dc9ff9210bc4b3276909883db2c2aa0762cd22b46901a248c0372d073e7778b9c1d8469b26bb42406e484ef7747f71dea785fc0020a2eac17e0ac3fbe0453629efd68d5678fbecc10af8ffbe7828f826defb638763f4ecfe82"), - ciphertext: &hex!("6543b4d97fccd273b36436fef719ac31bf0e5c4c058ea71aea2a0e5b60e329be6ea81ce386e6e9fe4480e58363c3b2036865ac"), - tag: &hex!("924cf7c0770f228a4b92e9b2a11fc70b"), - }, - GcmTV { - key: &hex!("9572b9c57abdf1caae3bebc0e4bbf9e556b5cbacca2c4756050fefd10a666155"), - nonce: &hex!("de292a9858caaccdcab6a433"), - plaintext: &hex!("6f420a32708ccd4df0d3149e8c1d88dceba66ee4546f38db07046ebf30f47627f7fdda1dd79783adabe5f6b6853857b99b864c"), - aad: &hex!("a042d97a9b8f6caf51c5f24522d7ed83e2c5d8ec6b37ef2598134a30e57319300c3fdf92fb1d9797f5ef00971f662aae768f69f9ca0455bd6d1059d5f85b8ecb977006b833f90ac2d5bbf4498c83f4d1a42584c0dfc4a2e2453c"), - ciphertext: &hex!("a9af961d61ab578cc1348eb6f729603f481c5d9bf9bee3a13eda022bd09c03a4f207c21c45c0232a9742ae8f0c54b4278a3a63"), - tag: &hex!("eff9bb26156ec76f0060cd93a959e055"), - }, - GcmTV { - key: &hex!("3cc8671c4d25c3cbc887f4dcbd64e531e91cf6252f6ee9c29d9988d20ab6747f"), - nonce: &hex!("f960a09c0b5067280926a9c3"), - plaintext: &hex!("5b58717b0b32076566b58bf37c6133e61468b2be67715fb0007fe390c4b5578decf55502a4e3c12e7bdf0ba98784d126e4753a"), - aad: &hex!("79d73a7ff86698e6114a0f465373fbee029e042424c439b22e3ad37b36b9e02bab82e16844114e99e39c169f462fe61b87c4627c394384acc9531680706e4e56491a304c6075cca37c64db24468c1fb9519605c83f0ee3e0316a"), - ciphertext: &hex!("1d0be097470c1ac30619f63c3961152ab27db88ce694b7bba4db185cb31803cc7bab890e931c90766621bfe5d887eb0cd6995d"), - tag: &hex!("dbd57ea091ff16fc7dbc5435030cc74e"), - }, - GcmTV { - key: &hex!("882068be4552d7ad224fc8fa2af00d6abf76ccf1a7689d75f6f0e9bd82c1215e"), - nonce: &hex!("890a5315992f12674d1c8018"), - plaintext: &hex!("8464c03e0280cb1f63c054a24a050e980f60cc7313f09f2092c45d77bbe9ad2a8c1f6cdca2acd8c57c87e887edadb66bcb66c4"), - aad: &hex!("916721df816b1cad531dee8e4a8e634d43ed87db99609bcc986d16bfac2cff577d536d749a5c3625de53c5351825c228911f0a64be1fc9738a26394efe5332c0762bf59b65d3f1c5aafa9ca2e63eccd59568e6c0269950911a71"), - ciphertext: &hex!("020e297d907177dba12dde4bfe1b0ff9b6a9d9db0695193e4181449e157137b59b488616ba151b06d889f8498ce373d2396ab9"), - tag: &hex!("e48537ecb27460b477a6e7c3463dbcb0"), - }, - GcmTV { - key: &hex!("4deadcf0f7e19231f8afcb6fb902b105bef23f2fa9323a51833ff8368ccb4f91"), - nonce: &hex!("6d4d01abd587ed110e512ed2"), - plaintext: &hex!("75686e0fdd3fd96f3e6dfafd7a2a907f9f375d93943cb2229bd72b032bf624af4fc72071289386e3dccc45959e47ab42b261a2"), - aad: &hex!("31a2797318104b2dc9977e599435b041c56bafe5e7d901a58614c2d3fb9d220e3fd3e2828cef69e0604ed73340cb1e21967294dcd874893942442200b2a5b860ee8cf91e1d8eb3d364d0e43e84f6379f434a1ae17c236b216842"), - ciphertext: &hex!("8feaf9a089599812117a67aed2f4bf3431ff1f6cfd64ea5ff475287abb4ff1ab6b3e4f8a55d1c6b3f08594f403e771ec7e9956"), - tag: &hex!("5040407621712e053591179e1689698e"), - }, - GcmTV { - key: &hex!("80f1c515f10d79cdbee275213aa9ac0845e2cf42874f7e695081cb103abf1a27"), - nonce: &hex!("399d5f9b218b62ff60c267bd"), - plaintext: &hex!("9e95221873f65282dd1ec75494d2500e62a2b6edda5a6f33b3d4dd7516ef25cf4154472e61c6aed2749c5a7d86637052b00f54"), - aad: &hex!("d2a8fff8ae24a6a5efc75764549a765222df317e323a798cbb8a23d1af8fdf8a3b767f55703b1c0feba3912d4234441978191262f1999c69caa4e9a3e0454c143af0022cd6e44cec14149f9e9964a1f2c5e5a6e3e768bd870060"), - ciphertext: &hex!("4f996562e23ebbfd4fe26523aee9525b13d6e134e72d21bdc7f195c6403501fd8300b6e597b668f199f93591ba742a91b54454"), - tag: &hex!("2da1c7325f58575d275abf96c7fa9e51"), - }, - GcmTV { - key: &hex!("c2c6e9be5a480a4a56bfcd0e268faa2276093bd1f7e8ce61e746d003decc761e"), - nonce: &hex!("c1541eb25721d4856df8f928"), - plaintext: &hex!("87d22e0318fbbb420b86b0585bd12c14645ff2c742e5639b3a114cc96c5f738edfbe2055116f259e3d6c14cb6d8fca45708289"), - aad: &hex!("f34e79e5fe437eda03ccfef2f1d6319df51a71c9891863e4b98a7298bd64490460354db5a28b0fadcb815024ea17f3b84810e27954afb1fdf44f0defb930b1793684a781310b9af95b4bcf0a727a2cb0ac529b805811b3721d98"), - ciphertext: &hex!("b5d6e57c7aa0240e0b6e332d3b3323b525a3d8a553ad041ba599e909188da537c3293d1687fb967882d16a5615b84e95f9dd77"), - tag: &hex!("1cce334cec4b51216cac0fc620cdadf9"), - }, - GcmTV { - key: &hex!("ea0d6184a71456e27f9ac82dfc7f6694c898f7c0d19d1cb0db4e575dd0094bb6"), - nonce: &hex!("5018fb816d515511bfb939d5"), - plaintext: &hex!("083147d0c80f134f7393855c8a95bf6e6abd6f9a7b1fca584e8bfc6b5dc13a8edbfd473e232c041d9be9ee7709dc86b3aa320a"), - aad: &hex!("8bc6bd0a263212bd7281fd1a45e512fca104f859358eae9293a297c529a0abaffd8a77507b9069040f2b3141a7620691e110a8b593b956d8e3e71694506b89018a03861c1ba6082687adce15a874c73477430cef075eba077a93"), - ciphertext: &hex!("f0a5c4941782e2f2941dd05acee29b65341773f2e8d51935a3f4fa6f268ff030c880976cf1ee858f6571abd8411b695a2fadf0"), - tag: &hex!("067d8cc2d38c30697272daa00c7f70cf"), - }, - GcmTV { - key: &hex!("c624feb6cb0d78d634b627134c692f0bf5debf84d8639e22ff27ce2ace49d438"), - nonce: &hex!("a54f4f1204255f6b312222cd"), - plaintext: &hex!("ec34f45c1b70fd56518cc5c404cc13330ab7d51c10f4d2cfeb26b097ae76897191ec1b3953b0086e425c7da221d29f65d5ccf3"), - aad: &hex!("d9099ba6be50dca77e0b9803766ad993132479fbab43b8f4126a7f9ef673ac0caf2de235e1e84ad9fe505c43d1ac779f5072c025c14ea0d930ce39db8c5930baada23b3e4654470e559fcb6eb1c133a77318b87cc7913e12d404"), - ciphertext: &hex!("713d28a5123d65e82cca6e7fd919e1e5e3bdaab12ae715cf8b7c974eb5f62be8c3b42637074c6b891f6c6033eb4b7e61db9f0b"), - tag: &hex!("01ededff6e4d1dce4ac790218e208ebe"), - }, - GcmTV { - key: &hex!("1afc68b32596198ae0f3a8612751c2413322e8054ff2ac6bede3d4a1ee20ee62"), - nonce: &hex!("356860e76e794492de6a68f3"), - plaintext: &hex!("293041038f9e8edee23d2f18bce87b522380f1fa18b3021830a54ab891da8548095228ed9860176152e27945d66254f0db8590"), - aad: &hex!("205e44009e0ef963838aff615b35c9f1271d487cf719677d956718bce8ab676cceb636ad381432c5c790c26b07051b661a2fec4e607f9644f84993c8335db21ae36b6008bab2883ad7541809bf5f49272295c1c1f1cf8c678553"), - ciphertext: &hex!("e06109680d5fefd345665ec9a5b2e7bf3ece3af1b62841a95c453e7753b5a1d6d8a10b3c6c42df1f23832b74e74871821f1c0b"), - tag: &hex!("953d8d04f70e2af055ac902a455235b2"), - }, - GcmTV { - key: &hex!("f61b723359e798fefecc26b10b168dc331c639079598f1f651166cc58c671ee1"), - nonce: &hex!("b07e9407b592d4fd95509343"), - plaintext: &hex!("2724f1ad6b5b409a59c7f2ff649eb24b4a33a03d7a0426e29a6ea3aa91b4f00699fbed75bb7189964303e2e9fe3a7e5f74b7a1"), - aad: &hex!("1429c6f27828cb94ad5e62451da10fd574660cec2b8f279a19bbb8a167a630d3ac60db04e8faa02204792e49aed4501844a419d3ecdff0d03799866fee81a91187b08a44d5bb617ff3b2cef79cd48750ea20903e1d3627a17730"), - ciphertext: &hex!("362bad8de943dce8f53edf682d02e1d893c23c5272b13fd35b492f8477083a8c34027db32b6131931f03555ac5fbc6dbb13801"), - tag: &hex!("a51775606343755691f125019b44fdfc"), - }, - GcmTV { - key: &hex!("6be7f4d18ff0fbdd9b3b3cacaba4629a0c617387079add62f6ce1584b33faad1"), - nonce: &hex!("fda568c9cb13d9c176bcef03"), - plaintext: &hex!("4df668e99d5068604a48bcca5baa8245435928558a83d68d7b0b081861224e9bd39ea8f2d55a635949e66c6f6a7ff5cc34dd94"), - aad: &hex!("11ebeb97dd4a9925c1fbe2b9af77392058d2d971e42db15da39f090d7bc132573c34bf7d92a2d72dc66ee6840c3ff07985b8976ee8d8f36bf47ae330b899fdc60652dd5a23c45f3680f11951f019e0697c8acfcaa95f01b9c7dd"), - ciphertext: &hex!("488b40ad594e1845ccdd9e9467fc5e1afbbfde34e57d45bfcd30b61cc326d57fe8e3f31a39cdebf00f60bbd2c3cdf69f756eff"), - tag: &hex!("3bf3fbab9b48486fd08a5552604df639"), - }, - ]; -} diff --git a/src/crypto/src/cipher_ctx.rs b/src/crypto/src/cipher_ctx.rs deleted file mode 100644 index 0cfcb90..0000000 --- a/src/crypto/src/cipher_ctx.rs +++ /dev/null @@ -1,172 +0,0 @@ -use std::ptr::{self, NonNull}; - -use crate::error::{cvt, cvt_p, ErrorStack}; -use libc::{c_int, c_void}; - -extern "C" { - fn EVP_CIPHER_CTX_free(ctx: *mut ffi::EVP_CIPHER_CTX); - fn EVP_CIPHER_CTX_new() -> *mut ffi::EVP_CIPHER_CTX; - - fn EVP_EncryptInit_ex(ctx: *mut ffi::EVP_CIPHER_CTX, cipher: *const ffi::EVP_CIPHER, engine: *mut c_void, key: *const u8, iv: *const u8) - -> c_int; - fn EVP_DecryptInit_ex(ctx: *mut ffi::EVP_CIPHER_CTX, cipher: *const ffi::EVP_CIPHER, engine: *mut c_void, key: *const u8, iv: *const u8) - -> c_int; - fn EVP_EncryptUpdate(ctx: *mut ffi::EVP_CIPHER_CTX, out: *mut u8, outl: *mut c_int, in_: *const u8, inl: c_int) -> c_int; - fn EVP_DecryptUpdate(ctx: *mut ffi::EVP_CIPHER_CTX, out: *mut u8, outl: *mut c_int, in_: *const u8, inl: c_int) -> c_int; - fn EVP_EncryptFinal_ex(ctx: *mut ffi::EVP_CIPHER_CTX, out: *mut u8, outl: *mut c_int) -> c_int; - fn EVP_DecryptFinal_ex(ctx: *mut ffi::EVP_CIPHER_CTX, out: *mut u8, outl: *mut c_int) -> c_int; - fn EVP_CIPHER_CTX_ctrl(ctx: *mut ffi::EVP_CIPHER_CTX, type_: c_int, arg: c_int, ptr: *mut c_void) -> c_int; - -} - -pub struct CipherCtx(NonNull); -impl Drop for CipherCtx { - fn drop(&mut self) { - unsafe { - EVP_CIPHER_CTX_free(self.0.as_ptr()); - } - } -} - -impl CipherCtx { - /// Creates a new context. - pub fn new() -> Result { - unsafe { - let ptr = cvt_p(EVP_CIPHER_CTX_new())?; - Ok(CipherCtx(NonNull::new_unchecked(ptr))) - } - } -} -impl CipherCtx { - /// Initializes the context for encryption or decryption. - /// All pointer fields can be null, in which case the corresponding field in the context is not updated. - pub unsafe fn cipher_init(&self, t: *const ffi::EVP_CIPHER, key: *const u8, iv: *const u8) -> Result<(), ErrorStack> { - let evp_f = if ENCRYPT { - EVP_EncryptInit_ex - } else { - EVP_DecryptInit_ex - }; - - // OpenSSL will usually leak a static amount of memory per cipher given here. - cvt(evp_f(self.0.as_ptr(), t, ptr::null_mut(), key, iv))?; - Ok(()) - } - - /// Writes data into the context. - /// - /// Providing no output buffer will cause the input to be considered additional authenticated data (AAD). - /// - /// Returns the number of bytes written to `output`. - /// - /// This function is the same as [`Self::cipher_update`] but with the - /// output size check removed. It can be used when the exact - /// buffer size control is maintained by the caller. - /// - /// SAFETY: The caller is expected to provide `output` buffer - /// large enough to contain correct number of bytes. For streaming - /// ciphers the output buffer size should be at least as big as - /// the input buffer. For block ciphers the size of the output - /// buffer depends on the state of partially updated blocks. - pub unsafe fn update(&self, input: &[u8], output: *mut u8) -> Result<(), ErrorStack> { - let evp_f = if ENCRYPT { - EVP_EncryptUpdate - } else { - EVP_DecryptUpdate - }; - - let mut outlen = 0; - - cvt(evp_f(self.0.as_ptr(), output, &mut outlen, input.as_ptr(), input.len() as c_int))?; - - Ok(()) - } - - /// Finalizes the encryption or decryption process. - /// - /// Any remaining data will be written to the output buffer. - /// - /// Returns the number of bytes written to `output`. - /// - /// This function is the same as [`Self::cipher_final`] but with - /// the output buffer size check removed. - /// - /// SAFETY: The caller is expected to provide `output` buffer - /// large enough to contain correct number of bytes. For streaming - /// ciphers the output buffer can be empty, for block ciphers the - /// output buffer should be at least as big as the block. - pub unsafe fn finalize(&self, output: *mut u8) -> Result<(), ErrorStack> { - let evp_f = if ENCRYPT { - EVP_EncryptFinal_ex - } else { - EVP_DecryptFinal_ex - }; - let mut outl = 0; - - cvt(evp_f(self.0.as_ptr(), output, &mut outl))?; - - Ok(()) - } - - /// Retrieves the calculated authentication tag from the context. - /// - /// This should be called after [`Self::cipher_final`], and is only supported by authenticated ciphers. - /// - /// The size of the buffer indicates the size of the tag. While some ciphers support a range of tag sizes, it is - /// recommended to pick the maximum size. - pub fn tag(&self, tag: &mut [u8]) -> Result<(), ErrorStack> { - unsafe { - cvt(EVP_CIPHER_CTX_ctrl( - self.0.as_ptr(), - ffi::EVP_CTRL_GCM_GET_TAG, - tag.len() as c_int, - tag.as_mut_ptr() as *mut _, - ))?; - } - - Ok(()) - } - - /// Sets the authentication tag for verification during decryption. - #[allow(unused)] - pub fn set_tag(&self, tag: &[u8]) -> Result<(), ErrorStack> { - unsafe { - cvt(EVP_CIPHER_CTX_ctrl( - self.0.as_ptr(), - ffi::EVP_CTRL_GCM_SET_TAG, - tag.len() as c_int, - tag.as_ptr() as *mut _, - ))?; - } - - Ok(()) - } - pub fn as_ptr(&self) -> *mut ffi::EVP_CIPHER_CTX { - self.0.as_ptr() - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn aes_128_ecb() { - let key = [1u8; 16]; - let ctx = CipherCtx::new().unwrap(); - unsafe { - ctx.cipher_init::(ffi::EVP_aes_128_ecb(), key.as_ptr(), ptr::null()).unwrap(); - ffi::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); - assert_eq!(ffi::EVP_CIPHER_CTX_get_block_size(ctx.as_ptr()) as usize, 16); - - let origin = [2u8; 16]; - let mut val = origin; - let p = val.as_mut_ptr(); - - ctx.update::(&val, p).unwrap(); - ctx.cipher_init::(ptr::null(), key.as_ptr(), ptr::null()).unwrap(); - ctx.update::(&val, p).unwrap(); - - assert_eq!(val, origin); - } - } -} diff --git a/src/crypto/src/constant.rs b/src/crypto/src/constant.rs deleted file mode 100644 index e9f0467..0000000 --- a/src/crypto/src/constant.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub const AES_256_KEY_SIZE: usize = 32; -pub const AES_BLOCK_SIZE: usize = 16; -pub const AES_GCM_TAG_SIZE: usize = 16; -pub const AES_GCM_NONCE_SIZE: usize = 12; diff --git a/src/crypto/src/error.rs b/src/crypto/src/error.rs deleted file mode 100644 index 74135e6..0000000 --- a/src/crypto/src/error.rs +++ /dev/null @@ -1,348 +0,0 @@ -use cfg_if::cfg_if; -use libc::{c_char, c_int}; -use std::borrow::Cow; -use std::error; -use std::ffi::CStr; -use std::fmt; -use std::io; -use std::ptr; -use std::str; - -type ErrType = libc::c_ulong; - -/// Collection of [`Error`]s from OpenSSL. -/// -/// [`Error`]: struct.Error.html -#[derive(Debug, Clone)] -pub struct ErrorStack(Vec); - -impl ErrorStack { - /// Returns the contents of the OpenSSL error stack. - #[cold] - #[inline(never)] - pub fn get() -> ErrorStack { - let mut vec = vec![]; - while let Some(err) = Error::get() { - vec.push(err); - } - ErrorStack(vec) - } - - /// Pushes the errors back onto the OpenSSL error stack. - pub fn put(&self) { - for error in self.errors() { - error.put(); - } - } -} - -impl ErrorStack { - /// Returns the errors in the stack. - pub fn errors(&self) -> &[Error] { - &self.0 - } -} - -impl fmt::Display for ErrorStack { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.0.is_empty() { - return fmt.write_str("OpenSSL error"); - } - - let mut first = true; - for err in &self.0 { - if !first { - fmt.write_str(", ")?; - } - write!(fmt, "{}", err)?; - first = false; - } - Ok(()) - } -} - -impl error::Error for ErrorStack {} - -impl From for io::Error { - fn from(e: ErrorStack) -> io::Error { - io::Error::new(io::ErrorKind::Other, e) - } -} - -impl From for fmt::Error { - fn from(_: ErrorStack) -> fmt::Error { - fmt::Error - } -} - -/// An error reported from OpenSSL. -#[derive(Clone)] -pub struct Error { - code: ErrType, - file: ShimStr, - line: c_int, - func: Option, - data: Option>, -} - -unsafe impl Sync for Error {} -unsafe impl Send for Error {} - -impl Error { - /// Returns the first error on the OpenSSL error stack. - pub fn get() -> Option { - unsafe { - let mut file = ptr::null(); - let mut line = 0; - let mut func = ptr::null(); - let mut data = ptr::null(); - let mut flags = 0; - match ERR_get_error_all(&mut file, &mut line, &mut func, &mut data, &mut flags) { - 0 => None, - code => { - // The memory referenced by data is only valid until that slot is overwritten - // in the error stack, so we'll need to copy it off if it's dynamic - let data = if flags & ffi::ERR_TXT_STRING != 0 { - let bytes = CStr::from_ptr(data as *const _).to_bytes(); - let data = str::from_utf8(bytes).unwrap(); - #[cfg(not(boringssl))] - let data = if flags & ffi::ERR_TXT_MALLOCED != 0 { - Cow::Owned(data.to_string()) - } else { - Cow::Borrowed(data) - }; - #[cfg(boringssl)] - let data = Cow::Borrowed(data); - Some(data) - } else { - None - }; - - let file = ShimStr::new(file); - - let func = if func.is_null() { - None - } else { - Some(ShimStr::new(func)) - }; - - Some(Error { code, file, line, func, data }) - } - } - } - } - - /// Pushes the error back onto the OpenSSL error stack. - pub fn put(&self) { - self.put_error(); - - unsafe { - let data = match self.data { - Some(Cow::Borrowed(data)) => Some((data.as_ptr() as *mut c_char, 0)), - Some(Cow::Owned(ref data)) => { - let ptr = ffi::CRYPTO_malloc((data.len() + 1) as _, concat!(file!(), "\0").as_ptr() as _, line!() as _) as *mut c_char; - if ptr.is_null() { - None - } else { - ptr::copy_nonoverlapping(data.as_ptr(), ptr as *mut u8, data.len()); - *ptr.add(data.len()) = 0; - Some((ptr, ffi::ERR_TXT_MALLOCED)) - } - } - None => None, - }; - if let Some((ptr, flags)) = data { - ffi::ERR_set_error_data(ptr, flags | ffi::ERR_TXT_STRING); - } - } - } - - #[cfg(ossl300)] - fn put_error(&self) { - unsafe { - ffi::ERR_new(); - ffi::ERR_set_debug(self.file.as_ptr(), self.line, self.func.as_ref().map_or(ptr::null(), |s| s.as_ptr())); - ffi::ERR_set_error(ffi::ERR_GET_LIB(self.code), ffi::ERR_GET_REASON(self.code), ptr::null()); - } - } - - /// Returns the raw OpenSSL error code for this error. - pub fn code(&self) -> ErrType { - self.code - } - - /// Returns the name of the library reporting the error, if available. - pub fn library(&self) -> Option<&'static str> { - unsafe { - let cstr = ffi::ERR_lib_error_string(self.code); - if cstr.is_null() { - return None; - } - let bytes = CStr::from_ptr(cstr as *const _).to_bytes(); - Some(str::from_utf8(bytes).unwrap()) - } - } - - /// Returns the name of the function reporting the error. - pub fn function(&self) -> Option> { - self.func.as_ref().map(|s| s.as_str()) - } - - /// Returns the reason for the error. - pub fn reason(&self) -> Option<&'static str> { - unsafe { - let cstr = ffi::ERR_reason_error_string(self.code); - if cstr.is_null() { - return None; - } - let bytes = CStr::from_ptr(cstr as *const _).to_bytes(); - Some(str::from_utf8(bytes).unwrap()) - } - } - - /// Returns the name of the source file which encountered the error. - pub fn file(&self) -> RetStr<'_> { - self.file.as_str() - } - - /// Returns the line in the source file which encountered the error. - pub fn line(&self) -> u32 { - self.line as u32 - } - - /// Returns additional data describing the error. - #[allow(clippy::option_as_ref_deref)] - pub fn data(&self) -> Option<&str> { - self.data.as_ref().map(|s| &**s) - } -} - -impl fmt::Debug for Error { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut builder = fmt.debug_struct("Error"); - builder.field("code", &self.code()); - if let Some(library) = self.library() { - builder.field("library", &library); - } - if let Some(function) = self.function() { - builder.field("function", &function); - } - if let Some(reason) = self.reason() { - builder.field("reason", &reason); - } - builder.field("file", &self.file()); - builder.field("line", &self.line()); - if let Some(data) = self.data() { - builder.field("data", &data); - } - builder.finish() - } -} - -impl fmt::Display for Error { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(fmt, "error:{:08X}", self.code())?; - match self.library() { - Some(l) => write!(fmt, ":{}", l)?, - None => write!(fmt, ":lib({})", ffi::ERR_GET_LIB(self.code()))?, - } - match self.function() { - Some(f) => write!(fmt, ":{}", f)?, - None => write!(fmt, ":func({})", ffi::ERR_GET_FUNC(self.code()))?, - } - match self.reason() { - Some(r) => write!(fmt, ":{}", r)?, - None => write!(fmt, ":reason({})", ffi::ERR_GET_REASON(self.code()))?, - } - write!(fmt, ":{}:{}:{}", self.file(), self.line(), self.data().unwrap_or("")) - } -} - -impl error::Error for Error {} - -cfg_if! { - if #[cfg(ossl300)] { - use std::ffi::{CString}; - use ffi::ERR_get_error_all; - - type RetStr<'a> = &'a str; - - #[derive(Clone)] - struct ShimStr(CString); - - impl ShimStr { - unsafe fn new(s: *const c_char) -> Self { - ShimStr(CStr::from_ptr(s).to_owned()) - } - - fn as_ptr(&self) -> *const c_char { - self.0.as_ptr() - } - - fn as_str(&self) -> &str { - self.0.to_str().unwrap() - } - } - } else { - #[allow(bad_style)] - unsafe extern "C" fn ERR_get_error_all( - file: *mut *const c_char, - line: *mut c_int, - func: *mut *const c_char, - data: *mut *const c_char, - flags: *mut c_int, - ) -> ErrType { - let code = ffi::ERR_get_error_line_data(file, line, data, flags); - *func = ffi::ERR_func_error_string(code); - code - } - - type RetStr<'a> = &'static str; - - #[derive(Clone)] - struct ShimStr(*const c_char); - - impl ShimStr { - unsafe fn new(s: *const c_char) -> Self { - ShimStr(s) - } - - fn as_ptr(&self) -> *const c_char { - self.0 - } - - fn as_str(&self) -> &'static str { - unsafe { - CStr::from_ptr(self.0).to_str().unwrap() - } - } - } - } -} - -#[inline] -pub fn cvt_p(r: *mut T) -> Result<*mut T, ErrorStack> { - if r.is_null() { - Err(ErrorStack::get()) - } else { - Ok(r) - } -} - -#[inline] -pub fn cvt(r: c_int) -> Result { - if r <= 0 { - Err(ErrorStack::get()) - } else { - Ok(r) - } -} - -#[inline] -pub fn cvt_n(r: c_int) -> Result { - if r < 0 { - Err(ErrorStack::get()) - } else { - Ok(r) - } -} diff --git a/src/crypto/src/hash.rs b/src/crypto/src/hash.rs deleted file mode 100644 index edc29e6..0000000 --- a/src/crypto/src/hash.rs +++ /dev/null @@ -1,294 +0,0 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. - -use std::ffi::c_void; -use std::io::Write; -use std::mem::MaybeUninit; -use std::os::raw::{c_int, c_uint}; -use std::ptr::null; - -use crate::secret::Secret; - -pub const SHA512_HASH_SIZE: usize = 64; -pub const SHA384_HASH_SIZE: usize = 48; -pub const HMAC_SHA512_SIZE: usize = 64; -pub const HMAC_SHA384_SIZE: usize = 48; - -pub struct SHA512(ffi::SHA512_CTX); - -impl SHA512 { - #[inline(always)] - pub fn hash(data: &[u8]) -> [u8; SHA512_HASH_SIZE] { - unsafe { - let mut hash = MaybeUninit::<[u8; SHA512_HASH_SIZE]>::uninit(); - ffi::SHA512(data.as_ptr(), data.len(), hash.as_mut_ptr() as *mut _); - hash.assume_init() - } - } - - /// Creates a new hasher. - #[inline(always)] - pub fn new() -> Self { - unsafe { - let mut ctx = MaybeUninit::uninit(); - ffi::SHA512_Init(ctx.as_mut_ptr()); - SHA512(ctx.assume_init()) - } - } - - #[inline(always)] - pub fn reset(&mut self) { - unsafe { ffi::SHA512_Init(&mut self.0) }; - } - - /// Feeds some data into the hasher. - /// - /// This can be called multiple times. - #[inline(always)] - pub fn update(&mut self, buf: &[u8]) { - unsafe { - ffi::SHA512_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); - } - } - - /// Returns the hash of the data. - #[inline(always)] - pub fn finish(&mut self) -> [u8; SHA512_HASH_SIZE] { - unsafe { - let mut hash = MaybeUninit::<[u8; SHA512_HASH_SIZE]>::uninit(); - ffi::SHA512_Final(hash.as_mut_ptr() as *mut _, &mut self.0); - hash.assume_init() - } - } -} - -impl Write for SHA512 { - #[inline(always)] - fn write(&mut self, b: &[u8]) -> std::io::Result { - self.update(b); - Ok(b.len()) - } - - #[inline(always)] - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} - -unsafe impl Send for SHA512 {} - -pub struct SHA384(ffi::SHA512_CTX); - -impl SHA384 { - #[inline(always)] - pub fn hash(data: &[u8]) -> [u8; SHA384_HASH_SIZE] { - unsafe { - let mut hash = MaybeUninit::<[u8; SHA384_HASH_SIZE]>::uninit(); - ffi::SHA384(data.as_ptr(), data.len(), hash.as_mut_ptr() as *mut _); - hash.assume_init() - } - } - - #[inline(always)] - pub fn new() -> Self { - unsafe { - let mut ctx = MaybeUninit::uninit(); - ffi::SHA384_Init(ctx.as_mut_ptr()); - SHA384(ctx.assume_init()) - } - } - - #[inline(always)] - pub fn reset(&mut self) { - unsafe { - ffi::SHA384_Init(&mut self.0); - } - } - - #[inline(always)] - pub fn update(&mut self, buf: &[u8]) { - unsafe { - ffi::SHA384_Update(&mut self.0, buf.as_ptr() as *const c_void, buf.len()); - } - } - - #[inline(always)] - pub fn finish(&mut self) -> [u8; SHA384_HASH_SIZE] { - unsafe { - let mut hash = MaybeUninit::<[u8; SHA384_HASH_SIZE]>::uninit(); - ffi::SHA384_Final(hash.as_mut_ptr() as *mut _, &mut self.0); - hash.assume_init() - } - } -} - -impl Write for SHA384 { - #[inline(always)] - fn write(&mut self, b: &[u8]) -> std::io::Result { - self.update(b); - Ok(b.len()) - } - - #[inline(always)] - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} - -unsafe impl Send for SHA384 {} - -//#[link(name="crypto")] -extern "C" { - fn HMAC_CTX_new() -> *mut c_void; - fn HMAC_CTX_reset(ctx: *mut c_void) -> c_int; - fn HMAC_Init_ex(ctx: *mut c_void, key: *const c_void, key_len: c_int, evp_md: *const c_void, _impl: *const c_void) -> c_int; - fn HMAC_Update(ctx: *mut c_void, data: *const c_void, len: usize) -> c_int; - fn HMAC_Final(ctx: *mut c_void, output: *mut c_void, output_len: *mut c_uint) -> c_int; - fn HMAC_CTX_free(ctx: *mut c_void); - fn EVP_sha384() -> *const c_void; - fn EVP_sha512() -> *const c_void; -} - -pub struct HMACSHA512 { - ctx: *mut c_void, - evp_md: *const c_void, -} - -impl HMACSHA512 { - #[inline(always)] - pub fn new(key: &[u8]) -> Self { - unsafe { - let hm = Self { ctx: HMAC_CTX_new(), evp_md: EVP_sha512() }; - assert!(!hm.ctx.is_null()); - assert_ne!(HMAC_Init_ex(hm.ctx, key.as_ptr().cast(), key.len() as c_int, hm.evp_md, null()), 0); - hm - } - } - - #[inline(always)] - pub fn reset(&mut self, key: &[u8]) { - unsafe { - assert_ne!(HMAC_CTX_reset(self.ctx), 0); - assert_ne!(HMAC_Init_ex(self.ctx, key.as_ptr().cast(), key.len() as c_int, self.evp_md, null()), 0); - } - } - - #[inline(always)] - pub fn update(&mut self, b: &[u8]) { - unsafe { - assert_ne!(HMAC_Update(self.ctx, b.as_ptr().cast(), b.len()), 0); - } - } - - #[inline(always)] - pub fn finish_into(&mut self, md: &mut [u8]) { - unsafe { - debug_assert_eq!(md.len(), HMAC_SHA512_SIZE); - let mut mdlen = HMAC_SHA512_SIZE as c_uint; - assert_ne!(HMAC_Final(self.ctx, md.as_mut_ptr().cast(), &mut mdlen), 0); - debug_assert_eq!(mdlen, HMAC_SHA512_SIZE as c_uint); - } - } - - #[inline(always)] - pub fn finish(&mut self) -> [u8; HMAC_SHA512_SIZE] { - let mut tmp = [0u8; HMAC_SHA512_SIZE]; - self.finish_into(&mut tmp); - tmp - } -} - -impl Drop for HMACSHA512 { - #[inline(always)] - fn drop(&mut self) { - unsafe { HMAC_CTX_free(self.ctx) }; - } -} - -unsafe impl Send for HMACSHA512 {} - -pub struct HMACSHA384 { - ctx: *mut c_void, - evp_md: *const c_void, -} - -impl HMACSHA384 { - #[inline(always)] - pub fn new(key: &[u8]) -> Self { - unsafe { - let hm = Self { ctx: HMAC_CTX_new(), evp_md: EVP_sha384() }; - assert!(!hm.ctx.is_null()); - assert_ne!(HMAC_Init_ex(hm.ctx, key.as_ptr().cast(), key.len() as c_int, hm.evp_md, null()), 0); - hm - } - } - - #[inline(always)] - pub fn reset(&mut self, key: &[u8]) { - unsafe { - assert_ne!(HMAC_CTX_reset(self.ctx), 0); - assert_ne!(HMAC_Init_ex(self.ctx, key.as_ptr().cast(), key.len() as c_int, self.evp_md, null()), 0); - } - } - - #[inline(always)] - pub fn update(&mut self, b: &[u8]) { - unsafe { - assert_ne!(HMAC_Update(self.ctx, b.as_ptr().cast(), b.len()), 0); - } - } - - #[inline(always)] - pub fn finish_into(&mut self, md: &mut [u8]) { - unsafe { - assert_eq!(md.len(), HMAC_SHA384_SIZE); - let mut mdlen = HMAC_SHA384_SIZE as c_uint; - assert_ne!(HMAC_Final(self.ctx, md.as_mut_ptr().cast(), &mut mdlen), 0); - assert_eq!(mdlen, HMAC_SHA384_SIZE as c_uint); - } - } - - #[inline(always)] - pub fn finish(&mut self) -> [u8; HMAC_SHA384_SIZE] { - let mut tmp = [0u8; HMAC_SHA384_SIZE]; - self.finish_into(&mut tmp); - tmp - } -} - -impl Drop for HMACSHA384 { - #[inline(always)] - fn drop(&mut self) { - unsafe { HMAC_CTX_free(self.ctx) }; - } -} - -unsafe impl Send for HMACSHA384 {} - -#[inline(always)] -pub fn hmac_sha512(key: &[u8], msg: &[u8]) -> [u8; HMAC_SHA512_SIZE] { - let mut hm = HMACSHA512::new(key); - hm.update(msg); - hm.finish() -} - -pub fn hmac_sha512_secret256(key: &[u8], msg: &[u8]) -> Secret<32> { - let mut hm = HMACSHA512::new(key); - hm.update(msg); - let mut md = [0u8; HMAC_SHA512_SIZE]; - hm.finish_into(&mut md); - // With such a simple procedure hopefully the compiler implements the following line as a move from md and not a copy - // If not we ought to change this code so we don't leak a secret value on the stack - unsafe { Secret::from_bytes(&md[0..32]) } -} -pub fn hmac_sha512_secret(key: &[u8], msg: &[u8]) -> Secret { - let mut hm = HMACSHA512::new(key); - hm.update(msg); - Secret::move_bytes(hm.finish()) -} - -#[inline(always)] -pub fn hmac_sha384(key: &[u8], msg: &[u8]) -> [u8; HMAC_SHA384_SIZE] { - let mut hm = HMACSHA384::new(key); - hm.update(msg); - hm.finish() -} diff --git a/src/crypto/src/lib.rs b/src/crypto/src/lib.rs deleted file mode 100644 index c83b8e2..0000000 --- a/src/crypto/src/lib.rs +++ /dev/null @@ -1,59 +0,0 @@ -mod cipher_ctx; -mod error; - -pub mod hash; -pub mod mimcvdf; -pub mod p384; -pub mod random; -pub mod secret; - -pub mod constant; -pub mod poly1305; -pub mod salsa; -pub mod typestate; -pub mod x25519; - -#[cfg(target_os = "macos")] -pub mod aes_fruity; -#[cfg(target_os = "macos")] -pub use aes_fruity as aes; - -#[cfg(not(target_os = "macos"))] -pub mod aes_openssl; -#[cfg(not(target_os = "macos"))] -pub use aes_openssl as aes; - -mod aes_tests; - -#[cfg(target_os = "macos")] -pub mod aes_gmac_siv_fruity; -#[cfg(target_os = "macos")] -pub use aes_gmac_siv_fruity as aes_gmac_siv; - -#[cfg(not(target_os = "macos"))] -pub mod aes_gmac_siv_openssl; -#[cfg(not(target_os = "macos"))] -pub use aes_gmac_siv_openssl as aes_gmac_siv; -use ctor::ctor; - -#[ctor] -fn openssl_init() { - ffi::init(); -} - -/// Constant time byte slice equality. -#[inline] -pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { - let (a, b) = (a.as_ref(), b.as_ref()); - if a.len() == b.len() { - let mut x = 0u8; - for (aa, bb) in a.iter().zip(b.iter()) { - x |= *aa ^ *bb; - } - x == 0 - } else { - false - } -} - -pub const ZEROES: [u8; 64] = [0_u8; 64]; diff --git a/src/crypto/src/mimcvdf.rs b/src/crypto/src/mimcvdf.rs deleted file mode 100644 index bfb4992..0000000 --- a/src/crypto/src/mimcvdf.rs +++ /dev/null @@ -1,141 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -/* - * MIMC is a hash function originally designed for use with STARK and SNARK proofs. It's based - * on modular multiplication and exponentiation instead of the usual bit twiddling or ARX - * operations that underpin more common hash algorithms. - * - * It's useful as a verifiable delay function because it can be computed in both directions with - * one direction taking orders of magnitude longer than the other. The "backward" direction is - * used as the delay function as it requires modular exponentiation which is inherently more - * compute intensive. The "forward" direction simply requires modular cubing which is two modular - * multiplications and is much faster. - * - * It's also nice because it's incredibly simple with a tiny code footprint. - * - * This is used for anti-DOS and anti-spamming delay functions. It's not used for anything - * really "cryptographically hard," and if it were broken cryptographically it would still be - * useful as a VDF as long as the break didn't yield a significantly faster way of computing a - * delay proof than the straightforward iterative way implemented here. - * - * Here are two references on MIMC with the first being the original paper and the second being - * a blog post describing its use as a VDF. - * - * https://eprint.iacr.org/2016/492.pdf - * https://vitalik.ca/general/2018/07/21/starks_part_3.html - */ - -// p = 2^127 - 39, the largest 127-bit prime of the form 6k + 5 -const PRIME: u128 = 170141183460469231731687303715884105689; - -// (2p - 1) / 3 -const PRIME_2P_MINUS_1_DIV_3: u128 = 113427455640312821154458202477256070459; - -// Randomly generated round constants, each modulo PRIME. -const K_COUNT_MASK: usize = 31; -const K: [u128; 32] = [ - 0x1fdd07a761b611bb1ab9419a70599a7c, - 0x23056b05d5c6b925e333d7418047650a, - 0x77a638f9b437a307f8866fbd2672c705, - 0x60213dab83bab91d1c310bd87e9da332, - 0xf56bc883301ab373179e46b098b7a7, - 0x7914a0dbd2f971344173b350c28a838, - 0x44bb64af5e446e6ebdc068d10d318f26, - 0x1bca1921fd328bb725ae0cbcbc20a263, - 0xafa963242f5216a7da1cd5328b23659, - 0x7fe17c43782b883a63ee0a790e0b2b77, - 0x23bb62abf728bf453200ee528f902c33, - 0x75ec0c055be14955db6878567e3c0465, - 0x7902bb57876e0b08b4de02a66755e5d7, - 0xe5d7094f37b615f5a1e1594b0390de8, - 0x12d4ddee90653a26f5de63ff4651f2d, - 0xce4a15bc35633b5ed8bcae2c93d739c, - 0x23f25b935e52df87255db8c608ef9ab4, - 0x611a08d7464fb984c98104d77f1609a7, - 0x7aa825876a7f6acde5efa57992da9c43, - 0x2be9686f630fa28a0a0e1081a59755b4, - 0x50060dac9ac4656ba3f8ee7592f4e28a, - 0x4113abff6f5bb303eac2ca809d4d529d, - 0x2af9d01d4e753feb5834c14ca0543397, - 0x73c2d764691ced2b823dda887e22ae85, - 0x5b53dcd4750ff888dca2497cec4dacb7, - 0x5d8984a52c2d8f3cc9bcf61ef29f8a1, - 0x588d8cc99533d649aabb5f0f552140e, - 0x4dae04985fde8c8464ba08aaa7d8761e, - 0x53f0c4740b8c3bda3fc05109b9a2b71, - 0x3e918c88a6795e3bf840e0b74d91b9d7, - 0x1dbcb30d724f11200aebb1dff87def91, - 0x6086b0af0e1e68558170239d23be9780, -]; - -fn mulmod(mut a: u128, mut b: u128) -> u128 { - let mut res: u128 = 0; - a %= M; - loop { - if (b & 1) != 0 { - res = res.wrapping_add(a) % M; - } - b = b.wrapping_shr(1); - if b != 0 { - a = a.wrapping_shl(1) % M; - } else { - return res; - } - } -} - -#[inline(always)] -fn powmod(mut base: u128, mut exp: u128) -> u128 { - let mut res: u128 = 1; - loop { - if (exp & 1) != 0 { - res = mulmod::(base, res); - } - exp = exp.wrapping_shr(1); - if exp != 0 { - base = mulmod::(base, base); - } else { - return res; - } - } -} - -/// Compute MIMC for the given number of iterations and return a proof that can be checked much more quickly. -pub fn delay(mut input: u128, rounds: usize) -> u128 { - debug_assert!(rounds > 0); - input %= PRIME; - for r in 1..(rounds + 1) { - input = powmod::(input ^ K[(rounds - r) & K_COUNT_MASK], PRIME_2P_MINUS_1_DIV_3); - } - input -} - -/// Quickly verify the result of delay() given the returned proof, original input, and original number of rounds. -pub fn verify(mut proof: u128, original_input: u128, rounds: usize) -> bool { - debug_assert!(rounds > 0); - for r in 0..rounds { - proof = mulmod::(proof, mulmod::(proof, proof)) ^ K[r & K_COUNT_MASK]; - } - proof == (original_input % PRIME) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn delay_and_verify() { - for i in 1..5 { - let input = (crate::random::xorshift64_random() as u128).wrapping_mul(crate::random::xorshift64_random() as u128); - let proof = delay(input, i * 3); - //println!("{}", proof); - assert!(verify(proof, input, i * 3)); - } - } -} diff --git a/src/crypto/src/p384.rs b/src/crypto/src/p384.rs deleted file mode 100644 index 5cd4be5..0000000 --- a/src/crypto/src/p384.rs +++ /dev/null @@ -1,406 +0,0 @@ -// Version using OpenSSL's ECC -use std::os::raw::{c_int, c_ulong, c_void}; -use std::sync::Mutex; -use std::{mem, ptr}; - -use lazy_static::lazy_static; - -use crate::error::{cvt, cvt_n, cvt_p, ErrorStack}; -use crate::hash::SHA384; -use crate::secret::Secret; -use crate::secure_eq; - -pub const P384_PUBLIC_KEY_SIZE: usize = 49; -pub const P384_SECRET_KEY_SIZE: usize = 48; -pub const P384_ECDSA_SIGNATURE_SIZE: usize = 96; -pub const P384_ECDH_SHARED_SECRET_SIZE: usize = 48; - -extern "C" { - fn ECDH_compute_key(out: *mut u8, outlen: c_ulong, pub_key: *const ffi::EC_POINT, ecdh: *mut ffi::EC_KEY, kdf: *const c_void) -> c_int; -} -/// A NIST P-384 ECDH/ECDSA public key. -pub struct P384PublicKey { - /// OpenSSL does not guarantee threadsafety for this object (even though it could) so we have - /// to wrap this in a mutex. - key: Mutex, - bytes: [u8; P384_PUBLIC_KEY_SIZE], -} - -unsafe impl Send for P384PublicKey {} -unsafe impl Sync for P384PublicKey {} - -impl P384PublicKey { - /// Create a p384 public key from raw bytes. - /// `buffer` must have length `P384_PUBLIC_KEY_SIZE`. - pub fn from_bytes(buffer: &[u8]) -> Option { - if buffer.len() == P384_PUBLIC_KEY_SIZE { - unsafe { - // Write the buffer into OpenSSL. - let key = OSSLKey::pub_from_slice(buffer).ok()?; - // Get OpenSSL to double check if this final key makes sense. - // It will be read-only after this point. - if ffi::EC_KEY_check_key(key.0) == 1 { - let mut bytes = [0u8; P384_PUBLIC_KEY_SIZE]; - bytes.clone_from_slice(buffer); - return Some(Self { key: Mutex::new(key), bytes }); - } - } - } - None - } - - /// Verify the ECDSA/SHA384 signature. - pub fn verify(&self, msg: &[u8], signature: &[u8]) -> bool { - if signature.len() == P384_ECDSA_SIGNATURE_SIZE { - const CAP: usize = P384_ECDSA_SIGNATURE_SIZE / 2; - unsafe { - // Write the raw bytes into OpenSSL. - let r = OSSLBN::from_slice(&signature[0..CAP]); - let s = OSSLBN::from_slice(&signature[CAP..]); - if let (Ok(r), Ok(s)) = (r, s) { - // Create the OpenSSL object that actually supports verification. - if let Ok(sig) = cvt_p(ffi::ECDSA_SIG_new()) { - let is_valid = if ffi::ECDSA_SIG_set0(sig, r.0, s.0) == 1 { - // For some reason this one random function, `ECDSA_SIG_set0`, takes - // ownership of its parameters. I've double checked and it is the only one - // we call that does that. We `forget` the memory so we don't double free. - mem::forget(r); - mem::forget(s); - // Digest the message. - let data = &SHA384::hash(msg); - - let key = self.key.lock().unwrap(); - // Actually perform the verification. - ffi::ECDSA_do_verify(data.as_ptr(), data.len() as c_int, sig, key.0) == 1 - } else { - false - }; - // Guarantee signature free. - ffi::ECDSA_SIG_free(sig); - return is_valid; - } - } - } - } - false - } - - pub fn as_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE] { - &self.bytes - } -} -impl Clone for P384PublicKey { - fn clone(&self) -> Self { - Self { - key: Mutex::new(self.key.lock().unwrap().clone_public().unwrap()), - bytes: self.bytes, - } - } -} -impl PartialEq for P384PublicKey { - fn eq(&self, other: &Self) -> bool { - secure_eq(&self.bytes, &other.bytes) - } -} - -/// A NIST P-384 ECDH/ECDSA public/private key pair. -pub struct P384KeyPair { - /// OpenSSL does not guarantee threadsafety for this object (even though it could) so we have - /// to wrap this in a mutex. - pair: Mutex, - pub_bytes: [u8; P384_PUBLIC_KEY_SIZE], -} - -unsafe impl Send for P384KeyPair {} -unsafe impl Sync for P384KeyPair {} - -impl P384KeyPair { - /// Randomly generate a new p384 keypair. - pub fn generate() -> P384KeyPair { - unsafe { - let pair = OSSLKey::new().unwrap(); - // Ask OpenSSL to securely generate the keypair. - cvt(ffi::EC_KEY_generate_key(pair.0)).unwrap(); - // Read out the raw public key into a buffer. - let public_key = ffi::EC_KEY_get0_public_key(pair.0); - let mut buffer = [0_u8; P384_PUBLIC_KEY_SIZE]; - let bnc = OSSLBNC::new().unwrap(); - let len = ffi::EC_POINT_point2oct( - GROUP_P384.0, - public_key, - ffi::point_conversion_form_t::POINT_CONVERSION_COMPRESSED, - buffer.as_mut_ptr(), - P384_PUBLIC_KEY_SIZE, - bnc.0, - ); - if len <= 0 { - Err::<(), _>(ErrorStack::get()).unwrap(); - } - Self { pair: Mutex::new(pair), pub_bytes: buffer } - } - } - - /// Create a p384 keypair from raw bytes. - /// `public_bytes` should have length `P384_PUBLIC_KEY_SIZE` and `secret_bytes` should have length - /// `P384_SECRET_KEY_SIZE`. - pub fn from_bytes(public_bytes: &[u8], secret_bytes: &[u8]) -> Option { - if public_bytes.len() == P384_PUBLIC_KEY_SIZE && secret_bytes.len() == P384_SECRET_KEY_SIZE { - unsafe { - // Write the raw bytes into OpenSSL. - let pair = OSSLKey::pub_from_slice(public_bytes).ok()?; - let private = OSSLBN::from_slice(secret_bytes).ok()?; - // Tell OpenSSL to assign the private key to the public key. - // This makes the public key into a proper keypair. - if cvt(ffi::EC_KEY_set_private_key(pair.0, private.0)).is_ok() { - // Get OpenSSL to double check if this final key makes sense. - // It will be read-only after this point. - if ffi::EC_KEY_check_key(pair.0) == 1 { - let mut pub_bytes = [0u8; P384_PUBLIC_KEY_SIZE]; - pub_bytes.clone_from_slice(public_bytes); - return Some(Self { pair: Mutex::new(pair), pub_bytes }); - } - } - } - } - None - } - /// Create a new `P384PublicKey` object that only contains the public key from - /// this keypair. This object can be safely sent to a different thread. - pub fn to_public_key(&self) -> P384PublicKey { - let key = self.pair.lock().unwrap().clone_public().unwrap(); - P384PublicKey { key: Mutex::new(key), bytes: self.pub_bytes } - } - /// Get the raw bytes that uniquely define the public key. - pub fn public_key_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE] { - &self.pub_bytes - } - - /// Clone the raw bytes that uniquely define the secret key. - /// They are wrapped in a container which will erase them on drop. - /// - /// **Only write these to 100% trusted storage mediums. Avoid calling this function in general.** - pub fn secret_key_bytes(&self) -> Secret { - unsafe { - let mut tmp: Secret = Secret::default(); - let keypair = self.pair.lock().unwrap(); - // Get a temporary handle to the private key. - let ptr = ffi::EC_KEY_get0_private_key(keypair.0); - // Read the key's raw bytes out of OpenSSL. - let size = cvt_n(ffi::BN_bn2bin(ptr, tmp.as_bytes_mut().as_mut_ptr())).unwrap() as usize; - drop(keypair); - - // Double check big-endian-ness. - tmp.0.copy_within(..size, P384_SECRET_KEY_SIZE - size); - tmp - } - } - - /// Sign a message with ECDSA/SHA384. - pub fn sign(&self, msg: &[u8]) -> [u8; P384_ECDSA_SIGNATURE_SIZE] { - // Digest the message. - let data = &SHA384::hash(msg); - unsafe { - let keypair = self.pair.lock().unwrap(); - // Actually create the signature with ECDSA. - let sig = cvt_p(ffi::ECDSA_do_sign(data.as_ptr(), data.len() as c_int, keypair.0)); - drop(keypair); - let sig = sig.unwrap(); - - // Get handles to the OpenSSL objects that actually support reading out into bytes. - let mut r = ptr::null(); - let mut s = ptr::null(); - ffi::ECDSA_SIG_get0(sig, &mut r, &mut s); - if r.is_null() || s.is_null() { - ffi::ECDSA_SIG_free(sig); - Err::<(), _>(ErrorStack::get()).unwrap(); - } - // Determine the size of the buffers to guarantee sanity and big-endian-ness. - let r_len = ((ffi::BN_num_bits(r) + 7) / 8) as usize; - let s_len = ((ffi::BN_num_bits(s) + 7) / 8) as usize; - const CAP: usize = P384_ECDSA_SIGNATURE_SIZE / 2; - if !(r_len > 0 && s_len > 0 && r_len <= CAP && s_len <= CAP) { - ffi::ECDSA_SIG_free(sig); - Err::<(), _>(ErrorStack::get()).unwrap(); - } - - let mut b = [0_u8; P384_ECDSA_SIGNATURE_SIZE]; - // Read the signature's raw bytes out of OpenSSL. - ffi::BN_bn2bin(r, b[(CAP - r_len)..CAP].as_mut_ptr()); - ffi::BN_bn2bin(s, b[(P384_ECDSA_SIGNATURE_SIZE - s_len)..P384_ECDSA_SIGNATURE_SIZE].as_mut_ptr()); - ffi::ECDSA_SIG_free(sig); - b - } - } - - /// Perform ECDH key agreement, returning the raw (un-hashed!) ECDH secret. - /// - /// This secret should not be used directly. It should be hashed and perhaps used in a KDF. - pub fn agree(&self, other_public: &P384PublicKey) -> Option> { - let keypair = self.pair.lock().unwrap(); - let other_key = other_public.key.lock().unwrap(); - unsafe { - let mut s: Secret = Secret::default(); - // Ask OpenSSL to perform DH between the keypair and the other key's public key object. - if ECDH_compute_key( - s.as_bytes_mut().as_mut_ptr(), - P384_ECDH_SHARED_SECRET_SIZE as c_ulong, - ffi::EC_KEY_get0_public_key(other_key.0), - keypair.0, - ptr::null(), - ) == P384_ECDH_SHARED_SECRET_SIZE as c_int - { - Some(s) - } else { - None - } - } - } -} - -/// OpenSSL wrapper for a BN_CTX handle that guarantees free will be called. -struct OSSLBNC(*mut ffi::BN_CTX); -impl OSSLBNC { - unsafe fn new() -> Result { - cvt_p(ffi::BN_CTX_new()).map(Self) - } -} -impl Drop for OSSLBNC { - fn drop(&mut self) { - unsafe { - ffi::BN_CTX_free(self.0); - } - } -} -/// OpenSSL wrapper for a BIGNUM handle that guarantees free will be called. -struct OSSLBN(*mut ffi::BIGNUM); -impl OSSLBN { - /// We would use OpenSSL's newer API for p384 if it actually supported raw byte encodings of keys. - /// Until then we are stuck with the old API. - unsafe fn from_slice(n: &[u8]) -> Result { - cvt_p(ffi::BN_bin2bn(n.as_ptr(), n.len() as c_int, ptr::null_mut())).map(Self) - } -} -impl Drop for OSSLBN { - fn drop(&mut self) { - unsafe { - ffi::BN_free(self.0); - } - } -} -/// OpenSSL wrapper for a EC_KEY handle that guarantees free will be called. -struct OSSLKey(*mut ffi::EC_KEY); -impl OSSLKey { - /// Create an empty key, guaranteeing to the caller it has the correct group and will be freed. - unsafe fn new() -> Result { - let key = cvt_p(ffi::EC_KEY_new())?; - cvt(ffi::EC_KEY_set_group(key, GROUP_P384.0))?; - Ok(Self(key)) - } - /// Create a key, guaranteeing to the caller it has the correct group, has a public key and will be freed. - /// - /// We would use OpenSSL's newer API for p384 if it actually supported raw byte encodings of keys. - /// Until then we are stuck with the old API. - unsafe fn pub_from_slice(buffer: &[u8]) -> Result> { - /// The public key is an ec_point, we need to be sure we free its memory - struct Point(*mut ffi::EC_POINT); - impl Point { - unsafe fn new() -> Result { - cvt_p(ffi::EC_POINT_new(GROUP_P384.0)).map(Self) - } - } - impl Drop for Point { - fn drop(&mut self) { - unsafe { - ffi::EC_POINT_free(self.0); - } - } - } - let bnc = OSSLBNC::new()?; - let point = Point::new()?; - // Ask OpenSSL to read the raw bytes into the OpenSSL object. - cvt(ffi::EC_POINT_oct2point(GROUP_P384.0, point.0, buffer.as_ptr(), buffer.len(), bnc.0))?; - // Check if the object is valid. - if cvt_n(ffi::EC_POINT_is_on_curve(GROUP_P384.0, point.0, bnc.0))? == 1 { - // Create an OpenSSL key and guarantee to the caller that the key was initialized with a - // public key. - let ec_key = OSSLKey::new()?; - cvt(ffi::EC_KEY_set_public_key(ec_key.0, point.0))?; - Ok(ec_key) - } else { - Err(None) - } - } - /// Create a `Send`-able clone of the public key. We don't reference count for this reason. - fn clone_public(&self) -> Result { - unsafe { - let point = ffi::EC_KEY_get0_public_key(self.0); - // Create an OpenSSL key and guarantee to the caller that the key was initialized with a - // public key. - let key = OSSLKey::new()?; - cvt(ffi::EC_KEY_set_public_key(key.0, point))?; - Ok(key) - } - } -} -impl Drop for OSSLKey { - fn drop(&mut self) { - unsafe { - ffi::EC_KEY_free(self.0); - } - } -} -/// OpenSSL wrapper for a EC_GROUP that is used to tell rust that an OpenSSL EC_GROUP is threadsafe. -/// We only ever instantiate one of these with lazy_static. It is never freed. -struct OSSLGroup(*mut ffi::EC_GROUP); -impl OSSLGroup { - unsafe fn p384() -> Self { - Self(cvt_p(ffi::EC_GROUP_new_by_curve_name(ffi::NID_secp384r1)).unwrap()) - } -} -unsafe impl Send for OSSLGroup {} -unsafe impl Sync for OSSLGroup {} -lazy_static! { - static ref GROUP_P384: OSSLGroup = unsafe { OSSLGroup::p384() }; -} - -#[cfg(test)] -mod tests { - use crate::{p384::P384KeyPair, secure_eq}; - - #[test] - fn generate_sign_verify_agree() { - let kp = P384KeyPair::generate(); - let kp2 = P384KeyPair::generate(); - let kp_pub = kp.to_public_key(); - let kp2_pub = kp2.to_public_key(); - - let sig = kp.sign(&[0_u8; 16]); - if !kp_pub.verify(&[0_u8; 16], &sig) { - panic!("ECDSA verify failed"); - } - if kp_pub.verify(&[1_u8; 16], &sig) { - panic!("ECDSA verify succeeded for incorrect message"); - } - - let sec0 = kp.agree(&kp2_pub).unwrap(); - let sec1 = kp2.agree(&kp_pub).unwrap(); - if !secure_eq(&sec0, &sec1) { - panic!("ECDH secrets do not match"); - } - - let pkb = kp.public_key_bytes(); - let skb = kp.secret_key_bytes(); - let kp3 = P384KeyPair::from_bytes(pkb, skb.as_ref()).unwrap(); - - let pkb3 = kp3.public_key_bytes(); - let skb3 = kp3.secret_key_bytes(); - - assert_eq!(pkb, pkb3); - assert_eq!(skb.as_bytes(), skb3.as_bytes()); - - let sig = kp3.sign(&[3_u8; 16]); - if !kp_pub.verify(&[3_u8; 16], &sig) { - panic!("ECDSA verify failed (from key reconstructed from bytes)"); - } - } -} diff --git a/src/crypto/src/p384_builtin.rs b/src/crypto/src/p384_builtin.rs deleted file mode 100644 index 24e2362..0000000 --- a/src/crypto/src/p384_builtin.rs +++ /dev/null @@ -1,1098 +0,0 @@ -// This is small and relatively fast but may not be constant time and hasn't been well audited, so we don't -// use it by default. It's left here though in case it proves useful in the future on embedded systems. -#[cfg(target_feature = "builtin_nist_ecc")] -mod builtin { - use crate::hash::SHA384; - use crate::secret::Secret; - - // EASY-ECC by Kenneth MacKay - // https://github.com/esxgx/easy-ecc (no longer there, but search GitHub for forks) - // - // Translated directly from C to Rust using https://c2rust.com and then hacked a bit - // to eliminate some dependencies. The translated code still has a lot of gratuitous - // "as"es but they're not consequential. - // - // It inherits its original BSD 2-Clause license, not ZeroTier's license. - - pub mod libc { - pub type c_uchar = u8; - pub type c_ulong = u64; - pub type c_long = i64; - pub type c_uint = u32; - pub type c_int = i32; - pub type c_ulonglong = u64; - pub type c_longlong = i64; - } - - pub type uint8_t = libc::c_uchar; - pub type uint64_t = libc::c_ulong; - pub type uint = libc::c_uint; - pub type uint128_t = u128; - pub struct EccPoint { - pub x: [u64; 6], - pub y: [u64; 6], - } - static mut curve_p: [uint64_t; 6] = [ - 0xffffffff as libc::c_uint as uint64_t, - 0xffffffff00000000 as libc::c_ulong, - 0xfffffffffffffffe as libc::c_ulong, - 0xffffffffffffffff as libc::c_ulong, - 0xffffffffffffffff as libc::c_ulong, - 0xffffffffffffffff as libc::c_ulong, - ]; - static mut curve_b: [uint64_t; 6] = [ - 0x2a85c8edd3ec2aef as libc::c_long as uint64_t, - 0xc656398d8a2ed19d as libc::c_ulong, - 0x314088f5013875a as libc::c_long as uint64_t, - 0x181d9c6efe814112 as libc::c_long as uint64_t, - 0x988e056be3f82d19 as libc::c_ulong, - 0xb3312fa7e23ee7e4 as libc::c_ulong, - ]; - static mut curve_G: EccPoint = { - let mut init = EccPoint { - x: [ - 0x3a545e3872760ab7 as libc::c_long as uint64_t, - 0x5502f25dbf55296c as libc::c_long as uint64_t, - 0x59f741e082542a38 as libc::c_long as uint64_t, - 0x6e1d3b628ba79b98 as libc::c_long as uint64_t, - 0x8eb1c71ef320ad74 as libc::c_ulong, - 0xaa87ca22be8b0537 as libc::c_ulong, - ], - y: [ - 0x7a431d7c90ea0e5f as libc::c_long as uint64_t, - 0xa60b1ce1d7e819d as libc::c_long as uint64_t, - 0xe9da3113b5f0b8c0 as libc::c_ulong, - 0xf8f41dbd289a147c as libc::c_ulong, - 0x5d9e98bf9292dc29 as libc::c_long as uint64_t, - 0x3617de4a96262c6f as libc::c_long as uint64_t, - ], - }; - init - }; - static mut curve_n: [uint64_t; 6] = [ - 0xecec196accc52973 as libc::c_ulong, - 0x581a0db248b0a77a as libc::c_long as uint64_t, - 0xc7634d81f4372ddf as libc::c_ulong, - 0xffffffffffffffff as libc::c_ulong, - 0xffffffffffffffff as libc::c_ulong, - 0xffffffffffffffff as libc::c_ulong, - ]; - - unsafe fn getRandomNumber(mut p_vli: *mut uint64_t) -> libc::c_int { - crate::random::fill_bytes_secure(&mut *std::ptr::slice_from_raw_parts_mut(p_vli.cast(), 48)); - return 1 as libc::c_int; - } - - unsafe fn vli_clear(mut p_vli: *mut uint64_t) { - let mut i: uint = 0; - i = 0 as libc::c_int as uint; - while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { - *p_vli.offset(i as isize) = 0 as libc::c_int as uint64_t; - i = i.wrapping_add(1) - } - } - /* Returns 1 if p_vli == 0, 0 otherwise. */ - - unsafe fn vli_isZero(mut p_vli: *mut uint64_t) -> libc::c_int { - let mut i: uint = 0; - i = 0 as libc::c_int as uint; - while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { - if *p_vli.offset(i as isize) != 0 { - return 0 as libc::c_int; - } - i = i.wrapping_add(1) - } - return 1 as libc::c_int; - } - /* Returns nonzero if bit p_bit of p_vli is set. */ - - unsafe fn vli_testBit(mut p_vli: *mut uint64_t, mut p_bit: uint) -> uint64_t { - return *p_vli.offset(p_bit.wrapping_div(64 as libc::c_int as libc::c_uint) as isize) - & (1 as libc::c_int as uint64_t) << p_bit.wrapping_rem(64 as libc::c_int as libc::c_uint); - } - /* Counts the number of 64-bit "digits" in p_vli. */ - - unsafe fn vli_numDigits(mut p_vli: *mut uint64_t) -> uint { - let mut i: libc::c_int = 0; - /* Search from the end until we find a non-zero digit. - We do it in reverse because we expect that most digits will be nonzero. */ - i = 48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int; - while i >= 0 as libc::c_int && *p_vli.offset(i as isize) == 0 as libc::c_int as libc::c_ulong { - i -= 1 - } - return (i + 1 as libc::c_int) as uint; - } - /* Counts the number of bits required for p_vli. */ - - unsafe fn vli_numBits(mut p_vli: *mut uint64_t) -> uint { - let mut i: uint = 0; - let mut l_digit: uint64_t = 0; - let mut l_numDigits: uint = vli_numDigits(p_vli); - if l_numDigits == 0 as libc::c_int as libc::c_uint { - return 0 as libc::c_int as uint; - } - l_digit = *p_vli.offset(l_numDigits.wrapping_sub(1 as libc::c_int as libc::c_uint) as isize); - i = 0 as libc::c_int as uint; - while l_digit != 0 { - l_digit >>= 1 as libc::c_int; - i = i.wrapping_add(1) - } - return l_numDigits - .wrapping_sub(1 as libc::c_int as libc::c_uint) - .wrapping_mul(64 as libc::c_int as libc::c_uint) - .wrapping_add(i); - } - /* Sets p_dest = p_src. */ - - unsafe fn vli_set(mut p_dest: *mut uint64_t, mut p_src: *mut uint64_t) { - let mut i: uint = 0; - i = 0 as libc::c_int as uint; - while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { - *p_dest.offset(i as isize) = *p_src.offset(i as isize); - i = i.wrapping_add(1) - } - } - /* Returns sign of p_left - p_right. */ - - unsafe fn vli_cmp(mut p_left: *mut uint64_t, mut p_right: *mut uint64_t) -> libc::c_int { - let mut i: libc::c_int = 0; - i = 48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int; - while i >= 0 as libc::c_int { - if *p_left.offset(i as isize) > *p_right.offset(i as isize) { - return 1 as libc::c_int; - } else { - if *p_left.offset(i as isize) < *p_right.offset(i as isize) { - return -(1 as libc::c_int); - } - } - i -= 1 - } - return 0 as libc::c_int; - } - /* Computes p_result = p_in << c, returning carry. Can modify in place (if p_result == p_in). 0 < p_shift < 64. */ - - unsafe fn vli_lshift(mut p_result: *mut uint64_t, mut p_in: *mut uint64_t, mut p_shift: uint) -> uint64_t { - let mut l_carry: uint64_t = 0 as libc::c_int as uint64_t; - let mut i: uint = 0; - i = 0 as libc::c_int as uint; - while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { - let mut l_temp: uint64_t = *p_in.offset(i as isize); - *p_result.offset(i as isize) = l_temp << p_shift | l_carry; - l_carry = l_temp >> (64 as libc::c_int as libc::c_uint).wrapping_sub(p_shift); - i = i.wrapping_add(1) - } - return l_carry; - } - /* Computes p_vli = p_vli >> 1. */ - - unsafe fn vli_rshift1(mut p_vli: *mut uint64_t) { - let mut l_end: *mut uint64_t = p_vli; - let mut l_carry: uint64_t = 0 as libc::c_int as uint64_t; - p_vli = p_vli.offset((48 as libc::c_int / 8 as libc::c_int) as isize); - loop { - let fresh0 = p_vli; - p_vli = p_vli.offset(-1); - if !(fresh0 > l_end) { - break; - } - let mut l_temp: uint64_t = *p_vli; - *p_vli = l_temp >> 1 as libc::c_int | l_carry; - l_carry = l_temp << 63 as libc::c_int - } - } - /* Computes p_result = p_left + p_right, returning carry. Can modify in place. */ - - unsafe fn vli_add(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t, mut p_right: *mut uint64_t) -> uint64_t { - let mut l_carry: uint64_t = 0 as libc::c_int as uint64_t; - let mut i: uint = 0; - i = 0 as libc::c_int as uint; - while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { - let mut l_sum: uint64_t = (*p_left.offset(i as isize)) - .wrapping_add(*p_right.offset(i as isize)) - .wrapping_add(l_carry); - if l_sum != *p_left.offset(i as isize) { - l_carry = (l_sum < *p_left.offset(i as isize)) as libc::c_int as uint64_t - } - *p_result.offset(i as isize) = l_sum; - i = i.wrapping_add(1) - } - return l_carry; - } - /* Computes p_result = p_left - p_right, returning borrow. Can modify in place. */ - - unsafe fn vli_sub(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t, mut p_right: *mut uint64_t) -> uint64_t { - let mut l_borrow: uint64_t = 0 as libc::c_int as uint64_t; - let mut i: uint = 0; - i = 0 as libc::c_int as uint; - while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { - let mut l_diff: uint64_t = (*p_left.offset(i as isize)) - .wrapping_sub(*p_right.offset(i as isize)) - .wrapping_sub(l_borrow); - if l_diff != *p_left.offset(i as isize) { - l_borrow = (l_diff > *p_left.offset(i as isize)) as libc::c_int as uint64_t - } - *p_result.offset(i as isize) = l_diff; - i = i.wrapping_add(1) - } - return l_borrow; - } - /* Computes p_result = p_left * p_right. */ - - unsafe fn vli_mult(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t, mut p_right: *mut uint64_t) { - let mut r01: uint128_t = 0 as libc::c_int as uint128_t; - let mut r2: uint64_t = 0 as libc::c_int as uint64_t; - let mut i: uint = 0; - let mut k: uint = 0; - /* Compute each digit of p_result in sequence, maintaining the carries. */ - k = 0 as libc::c_int as uint; - while k < (48 as libc::c_int / 8 as libc::c_int * 2 as libc::c_int - 1 as libc::c_int) as libc::c_uint { - let mut l_min: uint = if k < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { - 0 as libc::c_int as libc::c_uint - } else { - k.wrapping_add(1 as libc::c_int as libc::c_uint) - .wrapping_sub((48 as libc::c_int / 8 as libc::c_int) as libc::c_uint) - }; - i = l_min; - while i <= k && i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { - let mut l_product: uint128_t = - (*p_left.offset(i as isize) as uint128_t).wrapping_mul(*p_right.offset(k.wrapping_sub(i) as isize) as u128); - r01 = (r01 as u128).wrapping_add(l_product) as uint128_t as uint128_t; - r2 = (r2 as libc::c_ulong).wrapping_add((r01 < l_product) as libc::c_int as libc::c_ulong) as uint64_t as uint64_t; - i = i.wrapping_add(1) - } - *p_result.offset(k as isize) = r01 as uint64_t; - r01 = r01 >> 64 as libc::c_int | (r2 as uint128_t) << 64 as libc::c_int; - r2 = 0 as libc::c_int as uint64_t; - k = k.wrapping_add(1) - } - *p_result.offset((48 as libc::c_int / 8 as libc::c_int * 2 as libc::c_int - 1 as libc::c_int) as isize) = r01 as uint64_t; - } - /* Computes p_result = p_left^2. */ - - unsafe fn vli_square(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t) { - let mut r01: uint128_t = 0 as libc::c_int as uint128_t; - let mut r2: uint64_t = 0 as libc::c_int as uint64_t; - let mut i: uint = 0; - let mut k: uint = 0; - k = 0 as libc::c_int as uint; - while k < (48 as libc::c_int / 8 as libc::c_int * 2 as libc::c_int - 1 as libc::c_int) as libc::c_uint { - let mut l_min: uint = if k < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { - 0 as libc::c_int as libc::c_uint - } else { - k.wrapping_add(1 as libc::c_int as libc::c_uint) - .wrapping_sub((48 as libc::c_int / 8 as libc::c_int) as libc::c_uint) - }; - i = l_min; - while i <= k && i <= k.wrapping_sub(i) { - let mut l_product: uint128_t = - (*p_left.offset(i as isize) as uint128_t).wrapping_mul(*p_left.offset(k.wrapping_sub(i) as isize) as u128); - if i < k.wrapping_sub(i) { - r2 = (r2 as u128).wrapping_add(l_product >> 127 as libc::c_int) as uint64_t as uint64_t; - l_product = (l_product as u128).wrapping_mul(2 as libc::c_int as u128) as uint128_t as uint128_t - } - r01 = (r01 as u128).wrapping_add(l_product) as uint128_t as uint128_t; - r2 = (r2 as libc::c_ulong).wrapping_add((r01 < l_product) as libc::c_int as libc::c_ulong) as uint64_t as uint64_t; - i = i.wrapping_add(1) - } - *p_result.offset(k as isize) = r01 as uint64_t; - r01 = r01 >> 64 as libc::c_int | (r2 as uint128_t) << 64 as libc::c_int; - r2 = 0 as libc::c_int as uint64_t; - k = k.wrapping_add(1) - } - *p_result.offset((48 as libc::c_int / 8 as libc::c_int * 2 as libc::c_int - 1 as libc::c_int) as isize) = r01 as uint64_t; - } - /* #if SUPPORTS_INT128 */ - /* SUPPORTS_INT128 */ - /* Computes p_result = (p_left + p_right) % p_mod. - Assumes that p_left < p_mod and p_right < p_mod, p_result != p_mod. */ - - unsafe fn vli_modAdd(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t, mut p_right: *mut uint64_t, mut p_mod: *mut uint64_t) { - let mut l_carry: uint64_t = vli_add(p_result, p_left, p_right); - if l_carry != 0 || vli_cmp(p_result, p_mod) >= 0 as libc::c_int { - /* p_result > p_mod (p_result = p_mod + remainder), so subtract p_mod to get remainder. */ - vli_sub(p_result, p_result, p_mod); - }; - } - /* Computes p_result = (p_left - p_right) % p_mod. - Assumes that p_left < p_mod and p_right < p_mod, p_result != p_mod. */ - - unsafe fn vli_modSub(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t, mut p_right: *mut uint64_t, mut p_mod: *mut uint64_t) { - let mut l_borrow: uint64_t = vli_sub(p_result, p_left, p_right); - if l_borrow != 0 { - /* In this case, p_result == -diff == (max int) - diff. - Since -x % d == d - x, we can get the correct result from p_result + p_mod (with overflow). */ - vli_add(p_result, p_result, p_mod); - }; - } - //#elif ECC_CURVE == secp384r1 - - unsafe fn omega_mult(mut p_result: *mut uint64_t, mut p_right: *mut uint64_t) { - let mut l_tmp: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut l_carry: uint64_t = 0; - let mut l_diff: uint64_t = 0; - /* Multiply by (2^128 + 2^96 - 2^32 + 1). */ - vli_set(p_result, p_right); /* 1 */ - l_carry = vli_lshift(l_tmp.as_mut_ptr(), p_right, 32 as libc::c_int as uint); /* 2^96 + 1 */ - *p_result.offset((1 as libc::c_int + 48 as libc::c_int / 8 as libc::c_int) as isize) = l_carry.wrapping_add(vli_add( - p_result.offset(1 as libc::c_int as isize), - p_result.offset(1 as libc::c_int as isize), - l_tmp.as_mut_ptr(), - )); /* 2^128 + 2^96 + 1 */ - *p_result.offset((2 as libc::c_int + 48 as libc::c_int / 8 as libc::c_int) as isize) = vli_add( - p_result.offset(2 as libc::c_int as isize), - p_result.offset(2 as libc::c_int as isize), - p_right, - ); /* 2^128 + 2^96 - 2^32 + 1 */ - l_carry = (l_carry as libc::c_ulong).wrapping_add(vli_sub(p_result, p_result, l_tmp.as_mut_ptr())) as uint64_t as uint64_t; - l_diff = (*p_result.offset((48 as libc::c_int / 8 as libc::c_int) as isize)).wrapping_sub(l_carry); - if l_diff > *p_result.offset((48 as libc::c_int / 8 as libc::c_int) as isize) { - /* Propagate borrow if necessary. */ - let mut i: uint = 0; - i = (1 as libc::c_int + 48 as libc::c_int / 8 as libc::c_int) as uint; - loop { - let ref mut fresh1 = *p_result.offset(i as isize); - *fresh1 = (*fresh1).wrapping_sub(1); - if *p_result.offset(i as isize) != -(1 as libc::c_int) as uint64_t { - break; - } - i = i.wrapping_add(1) - } - } - *p_result.offset((48 as libc::c_int / 8 as libc::c_int) as isize) = l_diff; - } - /* Computes p_result = p_product % curve_p - see PDF "Comparing Elliptic Curve Cryptography and RSA on 8-bit CPUs" - section "Curve-Specific Optimizations" */ - - unsafe fn vli_mmod_fast(mut p_result: *mut uint64_t, mut p_product: *mut uint64_t) { - let mut l_tmp: [uint64_t; 12] = std::mem::MaybeUninit::uninit().assume_init(); - while vli_isZero(p_product.offset((48 as libc::c_int / 8 as libc::c_int) as isize)) == 0 { - /* While c1 != 0 */ - let mut l_carry: uint64_t = 0 as libc::c_int as uint64_t; /* tmp = w * c1 */ - let mut i: uint = 0; /* p = c0 */ - vli_clear(l_tmp.as_mut_ptr()); - vli_clear(l_tmp.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize)); - omega_mult(l_tmp.as_mut_ptr(), p_product.offset((48 as libc::c_int / 8 as libc::c_int) as isize)); - vli_clear(p_product.offset((48 as libc::c_int / 8 as libc::c_int) as isize)); - /* (c1, c0) = c0 + w * c1 */ - i = 0 as libc::c_int as uint; - while i < (48 as libc::c_int / 8 as libc::c_int + 3 as libc::c_int) as libc::c_uint { - let mut l_sum: uint64_t = (*p_product.offset(i as isize)).wrapping_add(l_tmp[i as usize]).wrapping_add(l_carry); - if l_sum != *p_product.offset(i as isize) { - l_carry = (l_sum < *p_product.offset(i as isize)) as libc::c_int as uint64_t - } - *p_product.offset(i as isize) = l_sum; - i = i.wrapping_add(1) - } - } - while vli_cmp(p_product, curve_p.as_mut_ptr()) > 0 as libc::c_int { - vli_sub(p_product, p_product, curve_p.as_mut_ptr()); - } - vli_set(p_result, p_product); - } - //#endif - /* Computes p_result = (p_left * p_right) % curve_p. */ - - unsafe fn vli_modMult_fast(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t, mut p_right: *mut uint64_t) { - let mut l_product: [uint64_t; 12] = std::mem::MaybeUninit::uninit().assume_init(); - vli_mult(l_product.as_mut_ptr(), p_left, p_right); - vli_mmod_fast(p_result, l_product.as_mut_ptr()); - } - /* Computes p_result = p_left^2 % curve_p. */ - - unsafe fn vli_modSquare_fast(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t) { - let mut l_product: [uint64_t; 12] = std::mem::MaybeUninit::uninit().assume_init(); - vli_square(l_product.as_mut_ptr(), p_left); - vli_mmod_fast(p_result, l_product.as_mut_ptr()); - } - /* Computes p_result = (1 / p_input) % p_mod. All VLIs are the same size. - See "From Euclid's GCD to Montgomery Multiplication to the Great Divide" - https://labs.oracle.com/techrep/2001/smli_tr-2001-95.pdf */ - - unsafe fn vli_modInv(mut p_result: *mut uint64_t, mut p_input: *mut uint64_t, mut p_mod: *mut uint64_t) { - let mut a: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut b: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut u: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut v: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut l_carry: uint64_t = 0; - let mut l_cmpResult: libc::c_int = 0; - if vli_isZero(p_input) != 0 { - vli_clear(p_result); - return; - } - vli_set(a.as_mut_ptr(), p_input); - vli_set(b.as_mut_ptr(), p_mod); - vli_clear(u.as_mut_ptr()); - u[0 as libc::c_int as usize] = 1 as libc::c_int as uint64_t; - vli_clear(v.as_mut_ptr()); - loop { - l_cmpResult = vli_cmp(a.as_mut_ptr(), b.as_mut_ptr()); - if !(l_cmpResult != 0 as libc::c_int) { - break; - } - l_carry = 0 as libc::c_int as uint64_t; - if a[0 as libc::c_int as usize] & 1 as libc::c_int as libc::c_ulong == 0 { - vli_rshift1(a.as_mut_ptr()); - if u[0 as libc::c_int as usize] & 1 as libc::c_int as libc::c_ulong != 0 { - l_carry = vli_add(u.as_mut_ptr(), u.as_mut_ptr(), p_mod) - } - vli_rshift1(u.as_mut_ptr()); - if l_carry != 0 { - u[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] = - (u[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] as libc::c_ulonglong - | 0x8000000000000000 as libc::c_ulonglong) as uint64_t - } - } else if b[0 as libc::c_int as usize] & 1 as libc::c_int as libc::c_ulong == 0 { - vli_rshift1(b.as_mut_ptr()); - if v[0 as libc::c_int as usize] & 1 as libc::c_int as libc::c_ulong != 0 { - l_carry = vli_add(v.as_mut_ptr(), v.as_mut_ptr(), p_mod) - } - vli_rshift1(v.as_mut_ptr()); - if l_carry != 0 { - v[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] = - (v[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] as libc::c_ulonglong - | 0x8000000000000000 as libc::c_ulonglong) as uint64_t - } - } else if l_cmpResult > 0 as libc::c_int { - vli_sub(a.as_mut_ptr(), a.as_mut_ptr(), b.as_mut_ptr()); - vli_rshift1(a.as_mut_ptr()); - if vli_cmp(u.as_mut_ptr(), v.as_mut_ptr()) < 0 as libc::c_int { - vli_add(u.as_mut_ptr(), u.as_mut_ptr(), p_mod); - } - vli_sub(u.as_mut_ptr(), u.as_mut_ptr(), v.as_mut_ptr()); - if u[0 as libc::c_int as usize] & 1 as libc::c_int as libc::c_ulong != 0 { - l_carry = vli_add(u.as_mut_ptr(), u.as_mut_ptr(), p_mod) - } - vli_rshift1(u.as_mut_ptr()); - if l_carry != 0 { - u[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] = - (u[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] as libc::c_ulonglong - | 0x8000000000000000 as libc::c_ulonglong) as uint64_t - } - } else { - vli_sub(b.as_mut_ptr(), b.as_mut_ptr(), a.as_mut_ptr()); - vli_rshift1(b.as_mut_ptr()); - if vli_cmp(v.as_mut_ptr(), u.as_mut_ptr()) < 0 as libc::c_int { - vli_add(v.as_mut_ptr(), v.as_mut_ptr(), p_mod); - } - vli_sub(v.as_mut_ptr(), v.as_mut_ptr(), u.as_mut_ptr()); - if v[0 as libc::c_int as usize] & 1 as libc::c_int as libc::c_ulong != 0 { - l_carry = vli_add(v.as_mut_ptr(), v.as_mut_ptr(), p_mod) - } - vli_rshift1(v.as_mut_ptr()); - if l_carry != 0 { - v[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] = - (v[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] as libc::c_ulonglong - | 0x8000000000000000 as libc::c_ulonglong) as uint64_t - } - } - } - vli_set(p_result, u.as_mut_ptr()); - } - /* ------ Point operations ------ */ - /* Returns 1 if p_point is the point at infinity, 0 otherwise. */ - - unsafe fn EccPoint_isZero(mut p_point: *mut EccPoint) -> libc::c_int { - return (vli_isZero((*p_point).x.as_mut_ptr()) != 0 && vli_isZero((*p_point).y.as_mut_ptr()) != 0) as libc::c_int; - } - /* Point multiplication algorithm using Montgomery's ladder with co-Z coordinates. - From http://eprint.iacr.org/2011/338.pdf - */ - /* Double in place */ - - unsafe fn EccPoint_double_jacobian(mut X1: *mut uint64_t, mut Y1: *mut uint64_t, mut Z1: *mut uint64_t) { - /* t1 = X, t2 = Y, t3 = Z */ - let mut t4: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); /* t4 = y1^2 */ - let mut t5: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); /* t5 = x1*y1^2 = A */ - if vli_isZero(Z1) != 0 { - return; - } /* t4 = y1^4 */ - vli_modSquare_fast(t4.as_mut_ptr(), Y1); /* t2 = y1*z1 = z3 */ - vli_modMult_fast(t5.as_mut_ptr(), X1, t4.as_mut_ptr()); /* t3 = z1^2 */ - vli_modSquare_fast(t4.as_mut_ptr(), t4.as_mut_ptr()); /* t1 = x1 + z1^2 */ - vli_modMult_fast(Y1, Y1, Z1); /* t3 = 2*z1^2 */ - vli_modSquare_fast(Z1, Z1); /* t3 = x1 - z1^2 */ - vli_modAdd(X1, X1, Z1, curve_p.as_mut_ptr()); /* t1 = x1^2 - z1^4 */ - vli_modAdd(Z1, Z1, Z1, curve_p.as_mut_ptr()); /* t3 = 2*(x1^2 - z1^4) */ - vli_modSub(Z1, X1, Z1, curve_p.as_mut_ptr()); /* t1 = 3*(x1^2 - z1^4) */ - vli_modMult_fast(X1, X1, Z1); - vli_modAdd(Z1, X1, X1, curve_p.as_mut_ptr()); - vli_modAdd(X1, X1, Z1, curve_p.as_mut_ptr()); - if vli_testBit(X1, 0 as libc::c_int as uint) != 0 { - let mut l_carry: uint64_t = vli_add(X1, X1, curve_p.as_mut_ptr()); - vli_rshift1(X1); - let ref mut fresh2 = *X1.offset((48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as isize); - *fresh2 |= l_carry << 63 as libc::c_int - } else { - vli_rshift1(X1); - } - /* t1 = 3/2*(x1^2 - z1^4) = B */ - vli_modSquare_fast(Z1, X1); /* t3 = B^2 */ - vli_modSub(Z1, Z1, t5.as_mut_ptr(), curve_p.as_mut_ptr()); /* t3 = B^2 - A */ - vli_modSub(Z1, Z1, t5.as_mut_ptr(), curve_p.as_mut_ptr()); /* t3 = B^2 - 2A = x3 */ - vli_modSub(t5.as_mut_ptr(), t5.as_mut_ptr(), Z1, curve_p.as_mut_ptr()); /* t5 = A - x3 */ - vli_modMult_fast(X1, X1, t5.as_mut_ptr()); /* t1 = B * (A - x3) */ - vli_modSub(t4.as_mut_ptr(), X1, t4.as_mut_ptr(), curve_p.as_mut_ptr()); /* t4 = B * (A - x3) - y1^4 = y3 */ - vli_set(X1, Z1); - vli_set(Z1, Y1); - vli_set(Y1, t4.as_mut_ptr()); - } - /* Modify (x1, y1) => (x1 * z^2, y1 * z^3) */ - - unsafe fn apply_z(mut X1: *mut uint64_t, mut Y1: *mut uint64_t, mut Z: *mut uint64_t) { - let mut t1: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); /* z^2 */ - vli_modSquare_fast(t1.as_mut_ptr(), Z); /* x1 * z^2 */ - vli_modMult_fast(X1, X1, t1.as_mut_ptr()); /* z^3 */ - vli_modMult_fast(t1.as_mut_ptr(), t1.as_mut_ptr(), Z); - vli_modMult_fast(Y1, Y1, t1.as_mut_ptr()); - /* y1 * z^3 */ - } - /* P = (x1, y1) => 2P, (x2, y2) => P' */ - - unsafe fn XYcZ_initial_double( - mut X1: *mut uint64_t, - mut Y1: *mut uint64_t, - mut X2: *mut uint64_t, - mut Y2: *mut uint64_t, - mut p_initialZ: *mut uint64_t, - ) { - let mut z: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - vli_set(X2, X1); - vli_set(Y2, Y1); - vli_clear(z.as_mut_ptr()); - z[0 as libc::c_int as usize] = 1 as libc::c_int as uint64_t; - if !p_initialZ.is_null() { - vli_set(z.as_mut_ptr(), p_initialZ); - } - apply_z(X1, Y1, z.as_mut_ptr()); - EccPoint_double_jacobian(X1, Y1, z.as_mut_ptr()); - apply_z(X2, Y2, z.as_mut_ptr()); - } - /* Input P = (x1, y1, Z), Q = (x2, y2, Z) - Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3) - or P => P', Q => P + Q - */ - - unsafe fn XYcZ_add(mut X1: *mut uint64_t, mut Y1: *mut uint64_t, mut X2: *mut uint64_t, mut Y2: *mut uint64_t) { - /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ - let mut t5: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); /* t5 = x2 - x1 */ - vli_modSub(t5.as_mut_ptr(), X2, X1, curve_p.as_mut_ptr()); /* t5 = (x2 - x1)^2 = A */ - vli_modSquare_fast(t5.as_mut_ptr(), t5.as_mut_ptr()); /* t1 = x1*A = B */ - vli_modMult_fast(X1, X1, t5.as_mut_ptr()); /* t3 = x2*A = C */ - vli_modMult_fast(X2, X2, t5.as_mut_ptr()); /* t4 = y2 - y1 */ - vli_modSub(Y2, Y2, Y1, curve_p.as_mut_ptr()); /* t5 = (y2 - y1)^2 = D */ - vli_modSquare_fast(t5.as_mut_ptr(), Y2); /* t5 = D - B */ - vli_modSub(t5.as_mut_ptr(), t5.as_mut_ptr(), X1, curve_p.as_mut_ptr()); /* t5 = D - B - C = x3 */ - vli_modSub(t5.as_mut_ptr(), t5.as_mut_ptr(), X2, curve_p.as_mut_ptr()); /* t3 = C - B */ - vli_modSub(X2, X2, X1, curve_p.as_mut_ptr()); /* t2 = y1*(C - B) */ - vli_modMult_fast(Y1, Y1, X2); /* t3 = B - x3 */ - vli_modSub(X2, X1, t5.as_mut_ptr(), curve_p.as_mut_ptr()); /* t4 = (y2 - y1)*(B - x3) */ - vli_modMult_fast(Y2, Y2, X2); /* t4 = y3 */ - vli_modSub(Y2, Y2, Y1, curve_p.as_mut_ptr()); - vli_set(X2, t5.as_mut_ptr()); - } - /* Input P = (x1, y1, Z), Q = (x2, y2, Z) - Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3) - or P => P - Q, Q => P + Q - */ - - unsafe fn XYcZ_addC(mut X1: *mut uint64_t, mut Y1: *mut uint64_t, mut X2: *mut uint64_t, mut Y2: *mut uint64_t) { - /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ - let mut t5: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); /* t5 = x2 - x1 */ - let mut t6: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); /* t5 = (x2 - x1)^2 = A */ - let mut t7: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); /* t1 = x1*A = B */ - vli_modSub(t5.as_mut_ptr(), X2, X1, curve_p.as_mut_ptr()); /* t3 = x2*A = C */ - vli_modSquare_fast(t5.as_mut_ptr(), t5.as_mut_ptr()); /* t4 = y2 + y1 */ - vli_modMult_fast(X1, X1, t5.as_mut_ptr()); /* t4 = y2 - y1 */ - vli_modMult_fast(X2, X2, t5.as_mut_ptr()); /* t6 = C - B */ - vli_modAdd(t5.as_mut_ptr(), Y2, Y1, curve_p.as_mut_ptr()); /* t2 = y1 * (C - B) */ - vli_modSub(Y2, Y2, Y1, curve_p.as_mut_ptr()); /* t6 = B + C */ - vli_modSub(t6.as_mut_ptr(), X2, X1, curve_p.as_mut_ptr()); /* t3 = (y2 - y1)^2 */ - vli_modMult_fast(Y1, Y1, t6.as_mut_ptr()); /* t3 = x3 */ - vli_modAdd(t6.as_mut_ptr(), X1, X2, curve_p.as_mut_ptr()); /* t7 = B - x3 */ - vli_modSquare_fast(X2, Y2); /* t4 = (y2 - y1)*(B - x3) */ - vli_modSub(X2, X2, t6.as_mut_ptr(), curve_p.as_mut_ptr()); /* t4 = y3 */ - vli_modSub(t7.as_mut_ptr(), X1, X2, curve_p.as_mut_ptr()); /* t7 = (y2 + y1)^2 = F */ - vli_modMult_fast(Y2, Y2, t7.as_mut_ptr()); /* t7 = x3' */ - vli_modSub(Y2, Y2, Y1, curve_p.as_mut_ptr()); /* t6 = x3' - B */ - vli_modSquare_fast(t7.as_mut_ptr(), t5.as_mut_ptr()); /* t6 = (y2 + y1)*(x3' - B) */ - vli_modSub(t7.as_mut_ptr(), t7.as_mut_ptr(), t6.as_mut_ptr(), curve_p.as_mut_ptr()); /* t2 = y3' */ - vli_modSub(t6.as_mut_ptr(), t7.as_mut_ptr(), X1, curve_p.as_mut_ptr()); - vli_modMult_fast(t6.as_mut_ptr(), t6.as_mut_ptr(), t5.as_mut_ptr()); - vli_modSub(Y1, t6.as_mut_ptr(), Y1, curve_p.as_mut_ptr()); - vli_set(X1, t7.as_mut_ptr()); - } - - unsafe fn EccPoint_mult(mut p_result: *mut EccPoint, mut p_point: *mut EccPoint, mut p_scalar: *mut uint64_t, mut p_initialZ: *mut uint64_t) { - /* R0 and R1 */ - let mut Rx: [[uint64_t; 6]; 2] = std::mem::MaybeUninit::uninit().assume_init(); - let mut Ry: [[uint64_t; 6]; 2] = std::mem::MaybeUninit::uninit().assume_init(); - let mut z: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut i: libc::c_int = 0; - let mut nb: libc::c_int = 0; - vli_set(Rx[1 as libc::c_int as usize].as_mut_ptr(), (*p_point).x.as_mut_ptr()); - vli_set(Ry[1 as libc::c_int as usize].as_mut_ptr(), (*p_point).y.as_mut_ptr()); - XYcZ_initial_double( - Rx[1 as libc::c_int as usize].as_mut_ptr(), - Ry[1 as libc::c_int as usize].as_mut_ptr(), - Rx[0 as libc::c_int as usize].as_mut_ptr(), - Ry[0 as libc::c_int as usize].as_mut_ptr(), - p_initialZ, - ); - i = vli_numBits(p_scalar).wrapping_sub(2 as libc::c_int as libc::c_uint) as libc::c_int; - while i > 0 as libc::c_int { - nb = (vli_testBit(p_scalar, i as uint) == 0) as libc::c_int; - XYcZ_addC( - Rx[(1 as libc::c_int - nb) as usize].as_mut_ptr(), - Ry[(1 as libc::c_int - nb) as usize].as_mut_ptr(), - Rx[nb as usize].as_mut_ptr(), - Ry[nb as usize].as_mut_ptr(), - ); - XYcZ_add( - Rx[nb as usize].as_mut_ptr(), - Ry[nb as usize].as_mut_ptr(), - Rx[(1 as libc::c_int - nb) as usize].as_mut_ptr(), - Ry[(1 as libc::c_int - nb) as usize].as_mut_ptr(), - ); - i -= 1 - } - nb = (vli_testBit(p_scalar, 0 as libc::c_int as uint) == 0) as libc::c_int; - XYcZ_addC( - Rx[(1 as libc::c_int - nb) as usize].as_mut_ptr(), - Ry[(1 as libc::c_int - nb) as usize].as_mut_ptr(), - Rx[nb as usize].as_mut_ptr(), - Ry[nb as usize].as_mut_ptr(), - ); - /* Find final 1/Z value. */ - vli_modSub( - z.as_mut_ptr(), - Rx[1 as libc::c_int as usize].as_mut_ptr(), - Rx[0 as libc::c_int as usize].as_mut_ptr(), - curve_p.as_mut_ptr(), - ); /* X1 - X0 */ - vli_modMult_fast(z.as_mut_ptr(), z.as_mut_ptr(), Ry[(1 as libc::c_int - nb) as usize].as_mut_ptr()); /* Yb * (X1 - X0) */ - vli_modMult_fast(z.as_mut_ptr(), z.as_mut_ptr(), (*p_point).x.as_mut_ptr()); /* xP * Yb * (X1 - X0) */ - vli_modInv(z.as_mut_ptr(), z.as_mut_ptr(), curve_p.as_mut_ptr()); /* 1 / (xP * Yb * (X1 - X0)) */ - vli_modMult_fast(z.as_mut_ptr(), z.as_mut_ptr(), (*p_point).y.as_mut_ptr()); /* yP / (xP * Yb * (X1 - X0)) */ - vli_modMult_fast(z.as_mut_ptr(), z.as_mut_ptr(), Rx[(1 as libc::c_int - nb) as usize].as_mut_ptr()); /* Xb * yP / (xP * Yb * (X1 - X0)) */ - /* End 1/Z calculation */ - XYcZ_add( - Rx[nb as usize].as_mut_ptr(), - Ry[nb as usize].as_mut_ptr(), - Rx[(1 as libc::c_int - nb) as usize].as_mut_ptr(), - Ry[(1 as libc::c_int - nb) as usize].as_mut_ptr(), - ); - apply_z( - Rx[0 as libc::c_int as usize].as_mut_ptr(), - Ry[0 as libc::c_int as usize].as_mut_ptr(), - z.as_mut_ptr(), - ); - vli_set((*p_result).x.as_mut_ptr(), Rx[0 as libc::c_int as usize].as_mut_ptr()); - vli_set((*p_result).y.as_mut_ptr(), Ry[0 as libc::c_int as usize].as_mut_ptr()); - } - - unsafe fn ecc_bytes2native(mut p_native: *mut uint64_t, mut p_bytes: *const uint8_t) { - let mut i: libc::c_uint = 0; - i = 0 as libc::c_int as libc::c_uint; - while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { - let mut p_digit: *const uint8_t = p_bytes.offset( - (8 as libc::c_int as libc::c_uint) - .wrapping_mul(((48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as libc::c_uint).wrapping_sub(i)) - as isize, - ); - *p_native.offset(i as isize) = (*p_digit.offset(0 as libc::c_int as isize) as uint64_t) << 56 as libc::c_int - | (*p_digit.offset(1 as libc::c_int as isize) as uint64_t) << 48 as libc::c_int - | (*p_digit.offset(2 as libc::c_int as isize) as uint64_t) << 40 as libc::c_int - | (*p_digit.offset(3 as libc::c_int as isize) as uint64_t) << 32 as libc::c_int - | (*p_digit.offset(4 as libc::c_int as isize) as uint64_t) << 24 as libc::c_int - | (*p_digit.offset(5 as libc::c_int as isize) as uint64_t) << 16 as libc::c_int - | (*p_digit.offset(6 as libc::c_int as isize) as uint64_t) << 8 as libc::c_int - | *p_digit.offset(7 as libc::c_int as isize) as uint64_t; - i = i.wrapping_add(1) - } - } - - unsafe fn ecc_native2bytes(mut p_bytes: *mut uint8_t, mut p_native: *const uint64_t) { - let mut i: libc::c_uint = 0; - i = 0 as libc::c_int as libc::c_uint; - while i < (48 as libc::c_int / 8 as libc::c_int) as libc::c_uint { - let mut p_digit: *mut uint8_t = p_bytes.offset( - (8 as libc::c_int as libc::c_uint) - .wrapping_mul(((48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as libc::c_uint).wrapping_sub(i)) - as isize, - ); - *p_digit.offset(0 as libc::c_int as isize) = (*p_native.offset(i as isize) >> 56 as libc::c_int) as uint8_t; - *p_digit.offset(1 as libc::c_int as isize) = (*p_native.offset(i as isize) >> 48 as libc::c_int) as uint8_t; - *p_digit.offset(2 as libc::c_int as isize) = (*p_native.offset(i as isize) >> 40 as libc::c_int) as uint8_t; - *p_digit.offset(3 as libc::c_int as isize) = (*p_native.offset(i as isize) >> 32 as libc::c_int) as uint8_t; - *p_digit.offset(4 as libc::c_int as isize) = (*p_native.offset(i as isize) >> 24 as libc::c_int) as uint8_t; - *p_digit.offset(5 as libc::c_int as isize) = (*p_native.offset(i as isize) >> 16 as libc::c_int) as uint8_t; - *p_digit.offset(6 as libc::c_int as isize) = (*p_native.offset(i as isize) >> 8 as libc::c_int) as uint8_t; - *p_digit.offset(7 as libc::c_int as isize) = *p_native.offset(i as isize) as uint8_t; - i = i.wrapping_add(1) - } - } - /* Compute a = sqrt(a) (mod curve_p). */ - - unsafe fn mod_sqrt(mut a: *mut uint64_t) { - let mut i: libc::c_uint = 0; - let mut p1: [uint64_t; 6] = [1 as libc::c_int as uint64_t, 0, 0, 0, 0, 0]; - let mut l_result: [uint64_t; 6] = [1 as libc::c_int as uint64_t, 0, 0, 0, 0, 0]; - /* Since curve_p == 3 (mod 4) for all supported curves, we can - compute sqrt(a) = a^((curve_p + 1) / 4) (mod curve_p). */ - vli_add(p1.as_mut_ptr(), curve_p.as_mut_ptr(), p1.as_mut_ptr()); /* p1 = curve_p + 1 */ - i = vli_numBits(p1.as_mut_ptr()).wrapping_sub(1 as libc::c_int as libc::c_uint); /* -a = 3 */ - while i > 1 as libc::c_int as libc::c_uint { - vli_modSquare_fast(l_result.as_mut_ptr(), l_result.as_mut_ptr()); /* y = x^2 */ - if vli_testBit(p1.as_mut_ptr(), i) != 0 { - vli_modMult_fast(l_result.as_mut_ptr(), l_result.as_mut_ptr(), a); - /* y = x^2 - 3 */ - } /* y = x^3 - 3x */ - i = i.wrapping_sub(1) - } /* y = x^3 - 3x + b */ - vli_set(a, l_result.as_mut_ptr()); - } - - unsafe fn ecc_point_decompress(mut p_point: *mut EccPoint, mut p_compressed: *const uint8_t) { - let mut _3: [uint64_t; 6] = [3 as libc::c_int as uint64_t, 0, 0, 0, 0, 0]; - ecc_bytes2native((*p_point).x.as_mut_ptr(), p_compressed.offset(1 as libc::c_int as isize)); - vli_modSquare_fast((*p_point).y.as_mut_ptr(), (*p_point).x.as_mut_ptr()); - vli_modSub( - (*p_point).y.as_mut_ptr(), - (*p_point).y.as_mut_ptr(), - _3.as_mut_ptr(), - curve_p.as_mut_ptr(), - ); - vli_modMult_fast((*p_point).y.as_mut_ptr(), (*p_point).y.as_mut_ptr(), (*p_point).x.as_mut_ptr()); - vli_modAdd( - (*p_point).y.as_mut_ptr(), - (*p_point).y.as_mut_ptr(), - curve_b.as_mut_ptr(), - curve_p.as_mut_ptr(), - ); - mod_sqrt((*p_point).y.as_mut_ptr()); - if (*p_point).y[0 as libc::c_int as usize] & 0x1 as libc::c_int as libc::c_ulong - != (*p_compressed.offset(0 as libc::c_int as isize) as libc::c_int & 0x1 as libc::c_int) as libc::c_ulong - { - vli_sub((*p_point).y.as_mut_ptr(), curve_p.as_mut_ptr(), (*p_point).y.as_mut_ptr()); - }; - } - pub unsafe fn ecc_make_key(mut p_publicKey: *mut uint8_t, mut p_privateKey: *mut uint8_t) -> libc::c_int { - let mut l_private: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut l_public: EccPoint = std::mem::MaybeUninit::uninit().assume_init(); - let mut l_tries: libc::c_uint = 0 as libc::c_int as libc::c_uint; - loop { - if getRandomNumber(l_private.as_mut_ptr()) == 0 || { - let fresh3 = l_tries; - l_tries = l_tries.wrapping_add(1); - (fresh3) >= 1024 as libc::c_int as libc::c_uint - } { - return 0 as libc::c_int; - } - if !(vli_isZero(l_private.as_mut_ptr()) != 0) { - /* Make sure the private key is in the range [1, n-1]. - For the supported curves, n is always large enough that we only need to subtract once at most. */ - if vli_cmp(curve_n.as_mut_ptr(), l_private.as_mut_ptr()) != 1 as libc::c_int { - vli_sub(l_private.as_mut_ptr(), l_private.as_mut_ptr(), curve_n.as_mut_ptr()); - } - EccPoint_mult(&mut l_public, &mut curve_G, l_private.as_mut_ptr(), 0 as *mut uint64_t); - } - if !(EccPoint_isZero(&mut l_public) != 0) { - break; - } - } - ecc_native2bytes(p_privateKey, l_private.as_mut_ptr() as *const uint64_t); - ecc_native2bytes(p_publicKey.offset(1 as libc::c_int as isize), l_public.x.as_mut_ptr() as *const uint64_t); - *p_publicKey.offset(0 as libc::c_int as isize) = - (2 as libc::c_int as libc::c_ulong).wrapping_add(l_public.y[0 as libc::c_int as usize] & 0x1 as libc::c_int as libc::c_ulong) as uint8_t; - return 1 as libc::c_int; - } - pub unsafe fn ecdh_shared_secret(mut p_publicKey: *const uint8_t, mut p_privateKey: *const uint8_t, mut p_secret: *mut uint8_t) -> libc::c_int { - let mut l_public: EccPoint = std::mem::MaybeUninit::uninit().assume_init(); - let mut l_private: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut l_random: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - if getRandomNumber(l_random.as_mut_ptr()) == 0 { - return 0 as libc::c_int; - } - ecc_point_decompress(&mut l_public, p_publicKey); - ecc_bytes2native(l_private.as_mut_ptr(), p_privateKey); - let mut l_product: EccPoint = EccPoint { x: [0; 6], y: [0; 6] }; - EccPoint_mult(&mut l_product, &mut l_public, l_private.as_mut_ptr(), l_random.as_mut_ptr()); - ecc_native2bytes(p_secret, l_product.x.as_mut_ptr() as *const uint64_t); - return (EccPoint_isZero(&mut l_product) == 0) as libc::c_int; - } - /* -------- ECDSA code -------- */ - /* Computes p_result = (p_left * p_right) % p_mod. */ - - unsafe fn vli_modMult(mut p_result: *mut uint64_t, mut p_left: *mut uint64_t, mut p_right: *mut uint64_t, mut p_mod: *mut uint64_t) { - let mut l_product: [uint64_t; 12] = std::mem::MaybeUninit::uninit().assume_init(); - let mut l_modMultiple: [uint64_t; 12] = std::mem::MaybeUninit::uninit().assume_init(); - let mut l_digitShift: uint = 0; - let mut l_bitShift: uint = 0; - let mut l_productBits: uint = 0; - let mut l_modBits: uint = vli_numBits(p_mod); - vli_mult(l_product.as_mut_ptr(), p_left, p_right); - l_productBits = vli_numBits(l_product.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize)); - if l_productBits != 0 { - l_productBits = (l_productBits as libc::c_uint).wrapping_add((48 as libc::c_int / 8 as libc::c_int * 64 as libc::c_int) as libc::c_uint) - as uint as uint - } else { - l_productBits = vli_numBits(l_product.as_mut_ptr()) - } - if l_productBits < l_modBits { - /* l_product < p_mod. */ - vli_set(p_result, l_product.as_mut_ptr()); - return; - } - /* Shift p_mod by (l_leftBits - l_modBits). This multiplies p_mod by the largest - power of two possible while still resulting in a number less than p_left. */ - vli_clear(l_modMultiple.as_mut_ptr()); - vli_clear(l_modMultiple.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize)); - l_digitShift = l_productBits.wrapping_sub(l_modBits).wrapping_div(64 as libc::c_int as libc::c_uint); - l_bitShift = l_productBits.wrapping_sub(l_modBits).wrapping_rem(64 as libc::c_int as libc::c_uint); - if l_bitShift != 0 { - l_modMultiple[l_digitShift.wrapping_add((48 as libc::c_int / 8 as libc::c_int) as libc::c_uint) as usize] = - vli_lshift(l_modMultiple.as_mut_ptr().offset(l_digitShift as isize), p_mod, l_bitShift) - } else { - vli_set(l_modMultiple.as_mut_ptr().offset(l_digitShift as isize), p_mod); - } - /* Subtract all multiples of p_mod to get the remainder. */ - vli_clear(p_result); /* Use p_result as a temp var to store 1 (for subtraction) */ - *p_result.offset(0 as libc::c_int as isize) = 1 as libc::c_int as uint64_t; - while l_productBits > (48 as libc::c_int / 8 as libc::c_int * 64 as libc::c_int) as libc::c_uint - || vli_cmp(l_modMultiple.as_mut_ptr(), p_mod) >= 0 as libc::c_int - { - let mut l_cmp: libc::c_int = vli_cmp( - l_modMultiple.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize), - l_product.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize), - ); - if l_cmp < 0 as libc::c_int - || l_cmp == 0 as libc::c_int && vli_cmp(l_modMultiple.as_mut_ptr(), l_product.as_mut_ptr()) <= 0 as libc::c_int - { - if vli_sub(l_product.as_mut_ptr(), l_product.as_mut_ptr(), l_modMultiple.as_mut_ptr()) != 0 { - /* borrow */ - vli_sub( - l_product.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize), - l_product.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize), - p_result, - ); - } - vli_sub( - l_product.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize), - l_product.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize), - l_modMultiple.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize), - ); - } - let mut l_carry: uint64_t = - (l_modMultiple[(48 as libc::c_int / 8 as libc::c_int) as usize] & 0x1 as libc::c_int as libc::c_ulong) << 63 as libc::c_int; - vli_rshift1(l_modMultiple.as_mut_ptr().offset((48 as libc::c_int / 8 as libc::c_int) as isize)); - vli_rshift1(l_modMultiple.as_mut_ptr()); - l_modMultiple[(48 as libc::c_int / 8 as libc::c_int - 1 as libc::c_int) as usize] |= l_carry; - l_productBits = l_productBits.wrapping_sub(1) - } - vli_set(p_result, l_product.as_mut_ptr()); - } - - unsafe fn umax(mut a: uint, mut b: uint) -> uint { - a.max(b) - } - pub unsafe fn ecdsa_sign(mut p_privateKey: *const uint8_t, mut p_hash: *const uint8_t, mut p_signature: *mut uint8_t) -> libc::c_int { - let mut k: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut l_tmp: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut l_s: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut p: EccPoint = std::mem::MaybeUninit::uninit().assume_init(); - let mut l_tries: libc::c_uint = 0 as libc::c_int as libc::c_uint; - loop { - if getRandomNumber(k.as_mut_ptr()) == 0 || { - let fresh4 = l_tries; - l_tries = l_tries.wrapping_add(1); - (fresh4) >= 1024 as libc::c_int as libc::c_uint - } { - return 0 as libc::c_int; - } - if !(vli_isZero(k.as_mut_ptr()) != 0) { - if vli_cmp(curve_n.as_mut_ptr(), k.as_mut_ptr()) != 1 as libc::c_int { - vli_sub(k.as_mut_ptr(), k.as_mut_ptr(), curve_n.as_mut_ptr()); - } - /* tmp = k * G */ - EccPoint_mult(&mut p, &mut curve_G, k.as_mut_ptr(), 0 as *mut uint64_t); - /* r = x1 (mod n) */ - if vli_cmp(curve_n.as_mut_ptr(), p.x.as_mut_ptr()) != 1 as libc::c_int { - vli_sub(p.x.as_mut_ptr(), p.x.as_mut_ptr(), curve_n.as_mut_ptr()); - /* s = r*d */ - } - } /* s = e + r*d */ - if !(vli_isZero(p.x.as_mut_ptr()) != 0) { - break; /* k = 1 / k */ - } - } /* s = (e + r*d) / k */ - ecc_native2bytes(p_signature, p.x.as_mut_ptr() as *const uint64_t); - ecc_bytes2native(l_tmp.as_mut_ptr(), p_privateKey); - vli_modMult(l_s.as_mut_ptr(), p.x.as_mut_ptr(), l_tmp.as_mut_ptr(), curve_n.as_mut_ptr()); - ecc_bytes2native(l_tmp.as_mut_ptr(), p_hash); - vli_modAdd(l_s.as_mut_ptr(), l_tmp.as_mut_ptr(), l_s.as_mut_ptr(), curve_n.as_mut_ptr()); - vli_modInv(k.as_mut_ptr(), k.as_mut_ptr(), curve_n.as_mut_ptr()); - vli_modMult(l_s.as_mut_ptr(), l_s.as_mut_ptr(), k.as_mut_ptr(), curve_n.as_mut_ptr()); - ecc_native2bytes(p_signature.offset(48 as libc::c_int as isize), l_s.as_mut_ptr() as *const uint64_t); - return 1 as libc::c_int; - } - pub unsafe fn ecdsa_verify(mut p_publicKey: *const uint8_t, mut p_hash: *const uint8_t, mut p_signature: *const uint8_t) -> libc::c_int { - let mut u1: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut u2: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut z: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut l_public: EccPoint = std::mem::MaybeUninit::uninit().assume_init(); - let mut l_sum: EccPoint = std::mem::MaybeUninit::uninit().assume_init(); - let mut rx: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut ry: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut tx: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut ty: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut tz: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut l_r: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - let mut l_s: [uint64_t; 6] = std::mem::MaybeUninit::uninit().assume_init(); - ecc_point_decompress(&mut l_public, p_publicKey); - ecc_bytes2native(l_r.as_mut_ptr(), p_signature); - ecc_bytes2native(l_s.as_mut_ptr(), p_signature.offset(48 as libc::c_int as isize)); - if vli_isZero(l_r.as_mut_ptr()) != 0 || vli_isZero(l_s.as_mut_ptr()) != 0 { - /* r, s must not be 0. */ - return 0 as libc::c_int; - } - if vli_cmp(curve_n.as_mut_ptr(), l_r.as_mut_ptr()) != 1 as libc::c_int || vli_cmp(curve_n.as_mut_ptr(), l_s.as_mut_ptr()) != 1 as libc::c_int - { - /* r, s must be < n. */ - return 0 as libc::c_int; - } - /* Calculate u1 and u2. */ - vli_modInv(z.as_mut_ptr(), l_s.as_mut_ptr(), curve_n.as_mut_ptr()); /* Z = s^-1 */ - ecc_bytes2native(u1.as_mut_ptr(), p_hash); /* u1 = e/s */ - vli_modMult(u1.as_mut_ptr(), u1.as_mut_ptr(), z.as_mut_ptr(), curve_n.as_mut_ptr()); /* u2 = r/s */ - vli_modMult(u2.as_mut_ptr(), l_r.as_mut_ptr(), z.as_mut_ptr(), curve_n.as_mut_ptr()); - /* Calculate l_sum = G + Q. */ - vli_set(l_sum.x.as_mut_ptr(), l_public.x.as_mut_ptr()); /* Z = x2 - x1 */ - vli_set(l_sum.y.as_mut_ptr(), l_public.y.as_mut_ptr()); /* Z = 1/Z */ - vli_set(tx.as_mut_ptr(), curve_G.x.as_mut_ptr()); - vli_set(ty.as_mut_ptr(), curve_G.y.as_mut_ptr()); - vli_modSub(z.as_mut_ptr(), l_sum.x.as_mut_ptr(), tx.as_mut_ptr(), curve_p.as_mut_ptr()); - XYcZ_add(tx.as_mut_ptr(), ty.as_mut_ptr(), l_sum.x.as_mut_ptr(), l_sum.y.as_mut_ptr()); - vli_modInv(z.as_mut_ptr(), z.as_mut_ptr(), curve_p.as_mut_ptr()); - apply_z(l_sum.x.as_mut_ptr(), l_sum.y.as_mut_ptr(), z.as_mut_ptr()); - /* Use Shamir's trick to calculate u1*G + u2*Q */ - let mut l_points: [*mut EccPoint; 4] = [0 as *mut EccPoint, &mut curve_G, &mut l_public, &mut l_sum]; /* Z = x2 - x1 */ - let mut l_numBits: uint = umax(vli_numBits(u1.as_mut_ptr()), vli_numBits(u2.as_mut_ptr())); /* Z = 1/Z */ - let mut l_point: *mut EccPoint = l_points[((vli_testBit(u1.as_mut_ptr(), l_numBits.wrapping_sub(1 as libc::c_int as libc::c_uint)) != 0) - as libc::c_int - | ((vli_testBit(u2.as_mut_ptr(), l_numBits.wrapping_sub(1 as libc::c_int as libc::c_uint)) != 0) as libc::c_int) << 1 as libc::c_int) - as usize]; - vli_set(rx.as_mut_ptr(), (*l_point).x.as_mut_ptr()); - vli_set(ry.as_mut_ptr(), (*l_point).y.as_mut_ptr()); - vli_clear(z.as_mut_ptr()); - z[0 as libc::c_int as usize] = 1 as libc::c_int as uint64_t; - let mut i: libc::c_int = 0; - i = l_numBits.wrapping_sub(2 as libc::c_int as libc::c_uint) as libc::c_int; - while i >= 0 as libc::c_int { - EccPoint_double_jacobian(rx.as_mut_ptr(), ry.as_mut_ptr(), z.as_mut_ptr()); - let mut l_index: libc::c_int = (vli_testBit(u1.as_mut_ptr(), i as uint) != 0) as libc::c_int - | ((vli_testBit(u2.as_mut_ptr(), i as uint) != 0) as libc::c_int) << 1 as libc::c_int; - let mut l_point_0: *mut EccPoint = l_points[l_index as usize]; - if !l_point_0.is_null() { - vli_set(tx.as_mut_ptr(), (*l_point_0).x.as_mut_ptr()); - vli_set(ty.as_mut_ptr(), (*l_point_0).y.as_mut_ptr()); - apply_z(tx.as_mut_ptr(), ty.as_mut_ptr(), z.as_mut_ptr()); - vli_modSub(tz.as_mut_ptr(), rx.as_mut_ptr(), tx.as_mut_ptr(), curve_p.as_mut_ptr()); - XYcZ_add(tx.as_mut_ptr(), ty.as_mut_ptr(), rx.as_mut_ptr(), ry.as_mut_ptr()); - vli_modMult_fast(z.as_mut_ptr(), z.as_mut_ptr(), tz.as_mut_ptr()); - } - i -= 1 - } - vli_modInv(z.as_mut_ptr(), z.as_mut_ptr(), curve_p.as_mut_ptr()); - apply_z(rx.as_mut_ptr(), ry.as_mut_ptr(), z.as_mut_ptr()); - /* v = x1 (mod n) */ - if vli_cmp(curve_n.as_mut_ptr(), rx.as_mut_ptr()) != 1 as libc::c_int { - vli_sub(rx.as_mut_ptr(), rx.as_mut_ptr(), curve_n.as_mut_ptr()); - } - /* Accept only if v == r. */ - return (vli_cmp(rx.as_mut_ptr(), l_r.as_mut_ptr()) == 0 as libc::c_int) as libc::c_int; - } - - #[derive(Clone, PartialEq, Eq)] - pub struct P384PublicKey([u8; 49]); - - impl P384PublicKey { - pub fn from_bytes(b: &[u8]) -> Option { - if b.len() == 49 { - Some(Self(b.try_into().unwrap())) - } else { - None - } - } - - pub fn verify(&self, msg: &[u8], signature: &[u8]) -> bool { - if signature.len() == 96 { - unsafe { - return ecdsa_verify(self.0.as_ptr().cast(), SHA384::hash(msg).as_ptr().cast(), signature.as_ptr().cast()) != 0; - } - } - return false; - } - - pub fn as_bytes(&self) -> &[u8; 49] { - &self.0 - } - } - - #[derive(Clone, PartialEq, Eq)] - pub struct P384KeyPair(P384PublicKey, Secret<48>); - - impl P384KeyPair { - pub fn generate() -> P384KeyPair { - let mut kp = Self(P384PublicKey([0_u8; 49]), Secret::new()); - unsafe { ecc_make_key(kp.0 .0.as_mut_ptr().cast(), kp.1 .0.as_mut_ptr().cast()) }; - kp - } - - pub fn from_bytes(public_bytes: &[u8], secret_bytes: &[u8]) -> Option { - if public_bytes.len() == 49 && secret_bytes.len() == 48 { - Some(Self( - P384PublicKey(public_bytes.try_into().unwrap()), - Secret(secret_bytes.try_into().unwrap()), - )) - } else { - None - } - } - - pub fn public_key(&self) -> &P384PublicKey { - &self.0 - } - - pub fn public_key_bytes(&self) -> &[u8; 49] { - &self.0 .0 - } - - pub fn secret_key_bytes(&self) -> Secret<48> { - self.1.clone() - } - - pub fn sign(&self, msg: &[u8]) -> [u8; 96] { - let msg = SHA384::hash(msg); - let mut sig = [0_u8; 96]; - unsafe { - ecdsa_sign(self.1 .0.as_ptr().cast(), msg.as_ptr().cast(), sig.as_mut_ptr().cast()); - } - sig - } - - pub fn agree(&self, other_public: &P384PublicKey) -> Option> { - let mut k = Secret::new(); - unsafe { - ecdh_shared_secret(other_public.0.as_ptr().cast(), self.1 .0.as_ptr().cast(), k.0.as_mut_ptr().cast()); - } - Some(k) - } - } - - impl P384KeyPair {} -} - -#[cfg(target_feature = "builtin_nist_ecc")] -pub use builtin::*; - diff --git a/src/crypto/src/poly1305.rs b/src/crypto/src/poly1305.rs deleted file mode 100644 index b49183a..0000000 --- a/src/crypto/src/poly1305.rs +++ /dev/null @@ -1,48 +0,0 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. - -use poly1305::universal_hash::KeyInit; - -/// The poly1305 message authentication function. -pub struct Poly1305(poly1305::Poly1305, [u8; 16], usize); - -pub const POLY1305_ONE_TIME_KEY_SIZE: usize = 32; -pub const POLY1305_MAC_SIZE: usize = 16; - -#[inline(always)] -pub fn compute(one_time_key: &[u8], message: &[u8]) -> [u8; POLY1305_MAC_SIZE] { - poly1305::Poly1305::new(poly1305::Key::from_slice(one_time_key)) - .compute_unpadded(message) - .into() -} - -#[cfg(test)] -mod tests { - use crate::poly1305::*; - - const TV0_INPUT: [u8; 32] = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]; - const TV0_KEY: [u8; 32] = [ - 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f, 0x72, - 0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35, - ]; - const TV0_TAG: [u8; 16] = [ - 0x49, 0xec, 0x78, 0x09, 0x0e, 0x48, 0x1e, 0xc6, 0xc2, 0x6b, 0x33, 0xb9, 0x1c, 0xcc, 0x03, 0x07, - ]; - - const TV1_INPUT: [u8; 12] = [0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21]; - const TV1_KEY: [u8; 32] = [ - 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f, 0x72, - 0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35, - ]; - const TV1_TAG: [u8; 16] = [ - 0xa6, 0xf7, 0x45, 0x00, 0x8f, 0x81, 0xc9, 0x16, 0xa2, 0x0d, 0xcc, 0x74, 0xee, 0xf2, 0xb2, 0xf0, - ]; - - #[test] - fn poly1305() { - assert_eq!(TV0_TAG, compute(&TV0_KEY, &TV0_INPUT)); - assert_eq!(TV1_TAG, compute(&TV1_KEY, &TV1_INPUT)); - } -} diff --git a/src/crypto/src/random.rs b/src/crypto/src/random.rs deleted file mode 100644 index 34d20d3..0000000 --- a/src/crypto/src/random.rs +++ /dev/null @@ -1,177 +0,0 @@ -use std::sync::Mutex; - -use libc::c_int; - -use crate::error::{cvt, ErrorStack}; - -/// Fill buffer with cryptographically strong pseudo-random bytes. -fn rand_bytes(buf: &mut [u8]) -> Result<(), ErrorStack> { - unsafe { - assert!(buf.len() <= c_int::max_value() as usize); - cvt(ffi::RAND_bytes(buf.as_mut_ptr(), buf.len() as c_int)).map(|_| ()) - } -} - -pub fn next_u32_secure() -> u32 { - unsafe { - let mut tmp = [0u32; 1]; - rand_bytes(&mut *(tmp.as_mut_ptr().cast::<[u8; 4]>())).unwrap(); - tmp[0] - } -} - -pub fn next_u64_secure() -> u64 { - unsafe { - let mut tmp = [0u64; 1]; - rand_bytes(&mut *(tmp.as_mut_ptr().cast::<[u8; 8]>())).unwrap(); - tmp[0] - } -} - -pub fn next_u128_secure() -> u128 { - unsafe { - let mut tmp = [0u128; 1]; - rand_bytes(&mut *(tmp.as_mut_ptr().cast::<[u8; 16]>())).unwrap(); - tmp[0] - } -} - -#[inline(always)] -pub fn fill_bytes_secure(dest: &mut [u8]) { - rand_bytes(dest).unwrap(); -} - -#[inline(always)] -pub fn get_bytes_secure() -> [u8; COUNT] { - let mut tmp = [0u8; COUNT]; - rand_bytes(&mut tmp).unwrap(); - tmp -} - -pub struct SecureRandom; - -impl Default for SecureRandom { - #[inline(always)] - fn default() -> Self { - Self - } -} - -impl SecureRandom { - #[inline(always)] - pub fn get() -> Self { - Self - } -} - -impl rand_core::RngCore for SecureRandom { - #[inline(always)] - fn next_u32(&mut self) -> u32 { - next_u32_secure() - } - - #[inline(always)] - fn next_u64(&mut self) -> u64 { - next_u64_secure() - } - - #[inline(always)] - fn fill_bytes(&mut self, dest: &mut [u8]) { - fill_bytes_secure(dest); - } - - #[inline(always)] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { - fill_bytes_secure(dest); - Ok(()) - } -} - -/// ed25519-dalek still uses rand_core 0.5.1, and that version is incompatible with 0.6.4, so we need to import and implement both. -impl rand_core_051::RngCore for SecureRandom { - #[inline(always)] - fn next_u32(&mut self) -> u32 { - next_u32_secure() - } - - #[inline(always)] - fn next_u64(&mut self) -> u64 { - next_u64_secure() - } - - #[inline(always)] - fn fill_bytes(&mut self, dest: &mut [u8]) { - fill_bytes_secure(dest); - } - - #[inline(always)] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_051::Error> { - fill_bytes_secure(dest); - Ok(()) - } -} - -impl rand_core::CryptoRng for SecureRandom {} -impl rand_core_051::CryptoRng for SecureRandom {} - -unsafe impl Sync for SecureRandom {} -unsafe impl Send for SecureRandom {} - -/// xorshift* by Marsaglia. -/// Simple and deterministic which makes it good for testing. -pub struct Xorshift64Star(pub u64); -impl Xorshift64Star { - #[inline(always)] - pub fn new(seed: u64) -> Self { - Self(seed) - } -} -impl rand_core::RngCore for Xorshift64Star { - #[inline(always)] - fn next_u32(&mut self) -> u32 { - self.next_u64() as u32 - } - #[inline(always)] - fn next_u64(&mut self) -> u64 { - self.0 ^= self.0.wrapping_shr(12); - self.0 ^= self.0.wrapping_shl(25); - self.0 ^= self.0.wrapping_shr(27); - self.0.wrapping_mul(0x2545F4914F6CDD1Du64) - } - - #[inline(always)] - fn fill_bytes(&mut self, dest: &mut [u8]) { - // This could be faster with manual unrolling - let mut r = self.next_u64().to_ne_bytes(); - let mut n = 0; - for byte in dest { - *byte = r[n]; - n += 1; - if n >= 8 { - r = self.next_u64().to_ne_bytes(); - n = 0 - } - } - } - - #[inline(always)] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { - self.fill_bytes(dest); - Ok(()) - } -} - -/// Get a non-cryptographic random number. -pub fn xorshift64_random() -> u64 { - static XORSHIFT64_STATE: Mutex = Mutex::new(0); - let mut x = XORSHIFT64_STATE.lock().unwrap(); - while *x == 0 { - *x = next_u64_secure(); - } - *x ^= x.wrapping_shr(12); - *x ^= x.wrapping_shl(25); - *x ^= x.wrapping_shr(27); - let r = *x; - drop(x); - r.wrapping_mul(0x2545F4914F6CDD1Du64) -} diff --git a/src/crypto/src/salsa.rs b/src/crypto/src/salsa.rs deleted file mode 100644 index 7c1f79b..0000000 --- a/src/crypto/src/salsa.rs +++ /dev/null @@ -1,267 +0,0 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. - -use std::convert::TryInto; -use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut}; - -const CONSTANTS: [u32; 4] = [ - u32::from_le_bytes(*b"expa"), - u32::from_le_bytes(*b"nd 3"), - u32::from_le_bytes(*b"2-by"), - u32::from_le_bytes(*b"te k"), -]; - -/// Salsa stream cipher implementation supporting 8, 12, or 20 rounds. -/// -/// WARNING: this has a major limitation/caveat. If you call crypt() with plaintext whose -/// size is not a multiple of 64, subsequent calls to crypt() will not be properly aligned. -/// This is okay for uses in ZeroTier but might break other cases. Salsa is deprecated as -/// transport encryption in ZeroTier anyway, but is still used to derive addresses from -/// identity public keys. -pub struct Salsa { - state: [u32; 16], -} - -impl Salsa { - /// Create a new Salsa cipher given a 256-bit key and a 64-bit IV. - pub fn new(key: &[u8], iv: &[u8]) -> Self { - assert!(ROUNDS == 8 || ROUNDS == 12 || ROUNDS == 20); - assert!(key.len() >= 32); - assert!(iv.len() >= 8); - Self { - state: [ - CONSTANTS[0], - u32::from_le_bytes((&key[0..4]).try_into().unwrap()), - u32::from_le_bytes((&key[4..8]).try_into().unwrap()), - u32::from_le_bytes((&key[8..12]).try_into().unwrap()), - u32::from_le_bytes((&key[12..16]).try_into().unwrap()), - CONSTANTS[1], - u32::from_le_bytes((&iv[0..4]).try_into().unwrap()), - u32::from_le_bytes((&iv[4..8]).try_into().unwrap()), - 0, - 0, - CONSTANTS[2], - u32::from_le_bytes((&key[16..20]).try_into().unwrap()), - u32::from_le_bytes((&key[20..24]).try_into().unwrap()), - u32::from_le_bytes((&key[24..28]).try_into().unwrap()), - u32::from_le_bytes((&key[28..32]).try_into().unwrap()), - CONSTANTS[3], - ], - } - } - - #[inline] - pub fn crypt(&mut self, mut plaintext: &[u8], mut ciphertext: &mut [u8]) { - let (j0, j1, j2, j3, j4, j5, j6, j7, mut j8, mut j9, j10, j11, j12, j13, j14, j15) = ( - self.state[0], - self.state[1], - self.state[2], - self.state[3], - self.state[4], - self.state[5], - self.state[6], - self.state[7], - self.state[8], - self.state[9], - self.state[10], - self.state[11], - self.state[12], - self.state[13], - self.state[14], - self.state[15], - ); - - while !plaintext.is_empty() { - let ( - mut x0, - mut x1, - mut x2, - mut x3, - mut x4, - mut x5, - mut x6, - mut x7, - mut x8, - mut x9, - mut x10, - mut x11, - mut x12, - mut x13, - mut x14, - mut x15, - ) = (j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15); - - for _ in 0..(ROUNDS / 2) { - x4 ^= x0.wrapping_add(x12).rotate_left(7); - x8 ^= x4.wrapping_add(x0).rotate_left(9); - x12 ^= x8.wrapping_add(x4).rotate_left(13); - x0 ^= x12.wrapping_add(x8).rotate_left(18); - x9 ^= x5.wrapping_add(x1).rotate_left(7); - x13 ^= x9.wrapping_add(x5).rotate_left(9); - x1 ^= x13.wrapping_add(x9).rotate_left(13); - x5 ^= x1.wrapping_add(x13).rotate_left(18); - x14 ^= x10.wrapping_add(x6).rotate_left(7); - x2 ^= x14.wrapping_add(x10).rotate_left(9); - x6 ^= x2.wrapping_add(x14).rotate_left(13); - x10 ^= x6.wrapping_add(x2).rotate_left(18); - x3 ^= x15.wrapping_add(x11).rotate_left(7); - x7 ^= x3.wrapping_add(x15).rotate_left(9); - x11 ^= x7.wrapping_add(x3).rotate_left(13); - x15 ^= x11.wrapping_add(x7).rotate_left(18); - x1 ^= x0.wrapping_add(x3).rotate_left(7); - x2 ^= x1.wrapping_add(x0).rotate_left(9); - x3 ^= x2.wrapping_add(x1).rotate_left(13); - x0 ^= x3.wrapping_add(x2).rotate_left(18); - x6 ^= x5.wrapping_add(x4).rotate_left(7); - x7 ^= x6.wrapping_add(x5).rotate_left(9); - x4 ^= x7.wrapping_add(x6).rotate_left(13); - x5 ^= x4.wrapping_add(x7).rotate_left(18); - x11 ^= x10.wrapping_add(x9).rotate_left(7); - x8 ^= x11.wrapping_add(x10).rotate_left(9); - x9 ^= x8.wrapping_add(x11).rotate_left(13); - x10 ^= x9.wrapping_add(x8).rotate_left(18); - x12 ^= x15.wrapping_add(x14).rotate_left(7); - x13 ^= x12.wrapping_add(x15).rotate_left(9); - x14 ^= x13.wrapping_add(x12).rotate_left(13); - x15 ^= x14.wrapping_add(x13).rotate_left(18); - } - - x0 = x0.wrapping_add(j0); - x1 = x1.wrapping_add(j1); - x2 = x2.wrapping_add(j2); - x3 = x3.wrapping_add(j3); - x4 = x4.wrapping_add(j4); - x5 = x5.wrapping_add(j5); - x6 = x6.wrapping_add(j6); - x7 = x7.wrapping_add(j7); - x8 = x8.wrapping_add(j8); - x9 = x9.wrapping_add(j9); - x10 = x10.wrapping_add(j10); - x11 = x11.wrapping_add(j11); - x12 = x12.wrapping_add(j12); - x13 = x13.wrapping_add(j13); - x14 = x14.wrapping_add(j14); - x15 = x15.wrapping_add(j15); - - j8 = j8.wrapping_add(1); - j9 = j9.wrapping_add((j8 == 0) as u32); - - if plaintext.len() >= 64 { - #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] - { - // Slightly faster keystream XOR for little-endian platforms with unaligned load/store. - unsafe { - *ciphertext.as_mut_ptr().cast::() = *plaintext.as_ptr().cast::() ^ x0; - *ciphertext.as_mut_ptr().cast::().add(1) = *plaintext.as_ptr().cast::().add(1) ^ x1; - *ciphertext.as_mut_ptr().cast::().add(2) = *plaintext.as_ptr().cast::().add(2) ^ x2; - *ciphertext.as_mut_ptr().cast::().add(3) = *plaintext.as_ptr().cast::().add(3) ^ x3; - *ciphertext.as_mut_ptr().cast::().add(4) = *plaintext.as_ptr().cast::().add(4) ^ x4; - *ciphertext.as_mut_ptr().cast::().add(5) = *plaintext.as_ptr().cast::().add(5) ^ x5; - *ciphertext.as_mut_ptr().cast::().add(6) = *plaintext.as_ptr().cast::().add(6) ^ x6; - *ciphertext.as_mut_ptr().cast::().add(7) = *plaintext.as_ptr().cast::().add(7) ^ x7; - *ciphertext.as_mut_ptr().cast::().add(8) = *plaintext.as_ptr().cast::().add(8) ^ x8; - *ciphertext.as_mut_ptr().cast::().add(9) = *plaintext.as_ptr().cast::().add(9) ^ x9; - *ciphertext.as_mut_ptr().cast::().add(10) = *plaintext.as_ptr().cast::().add(10) ^ x10; - *ciphertext.as_mut_ptr().cast::().add(11) = *plaintext.as_ptr().cast::().add(11) ^ x11; - *ciphertext.as_mut_ptr().cast::().add(12) = *plaintext.as_ptr().cast::().add(12) ^ x12; - *ciphertext.as_mut_ptr().cast::().add(13) = *plaintext.as_ptr().cast::().add(13) ^ x13; - *ciphertext.as_mut_ptr().cast::().add(14) = *plaintext.as_ptr().cast::().add(14) ^ x14; - *ciphertext.as_mut_ptr().cast::().add(15) = *plaintext.as_ptr().cast::().add(15) ^ x15; - } - } - #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))] - { - // Portable keystream XOR with alignment-safe access and native to little-endian conversion. - let keystream = [ - x0.to_le(), - x1.to_le(), - x2.to_le(), - x3.to_le(), - x4.to_le(), - x5.to_le(), - x6.to_le(), - x7.to_le(), - x8.to_le(), - x9.to_le(), - x10.to_le(), - x11.to_le(), - x12.to_le(), - x13.to_le(), - x14.to_le(), - x15.to_le(), - ]; - for i in 0..64 { - ciphertext[i] = plaintext[i] ^ unsafe { *keystream.as_ptr().cast::().add(i) }; - } - } - - plaintext = &plaintext[64..]; - ciphertext = &mut ciphertext[64..]; - } else { - let keystream = [ - x0.to_le(), - x1.to_le(), - x2.to_le(), - x3.to_le(), - x4.to_le(), - x5.to_le(), - x6.to_le(), - x7.to_le(), - x8.to_le(), - x9.to_le(), - x10.to_le(), - x11.to_le(), - x12.to_le(), - x13.to_le(), - x14.to_le(), - x15.to_le(), - ]; - for i in 0..plaintext.len() { - ciphertext[i] = plaintext[i] ^ unsafe { *keystream.as_ptr().cast::().add(i) }; - } - break; - } - } - - self.state[8] = j8; - self.state[9] = j9; - } - - #[inline(always)] - pub fn crypt_in_place(&mut self, data: &mut [u8]) { - unsafe { - self.crypt( - &*slice_from_raw_parts(data.as_ptr(), data.len()), - &mut *slice_from_raw_parts_mut(data.as_mut_ptr(), data.len()), - ) - } - } -} - -#[cfg(test)] -mod tests { - use crate::salsa::*; - - const SALSA_20_TV0_KEY: [u8; 32] = [ - 0x0f, 0x62, 0xb5, 0x08, 0x5b, 0xae, 0x01, 0x54, 0xa7, 0xfa, 0x4d, 0xa0, 0xf3, 0x46, 0x99, 0xec, 0x3f, 0x92, 0xe5, 0x38, 0x8b, 0xde, 0x31, - 0x84, 0xd7, 0x2a, 0x7d, 0xd0, 0x23, 0x76, 0xc9, 0x1c, - ]; - const SALSA_20_TV0_IV: [u8; 8] = [0x28, 0x8f, 0xf6, 0x5d, 0xc4, 0x2b, 0x92, 0xf9]; - const SALSA_20_TV0_KS: [u8; 64] = [ - 0x5e, 0x5e, 0x71, 0xf9, 0x01, 0x99, 0x34, 0x03, 0x04, 0xab, 0xb2, 0x2a, 0x37, 0xb6, 0x62, 0x5b, 0xf8, 0x83, 0xfb, 0x89, 0xce, 0x3b, 0x21, - 0xf5, 0x4a, 0x10, 0xb8, 0x10, 0x66, 0xef, 0x87, 0xda, 0x30, 0xb7, 0x76, 0x99, 0xaa, 0x73, 0x79, 0xda, 0x59, 0x5c, 0x77, 0xdd, 0x59, 0x54, - 0x2d, 0xa2, 0x08, 0xe5, 0x95, 0x4f, 0x89, 0xe4, 0x0e, 0xb7, 0xaa, 0x80, 0xa8, 0x4a, 0x61, 0x76, 0x66, 0x3f, - ]; - - #[test] - fn salsa20() { - let mut s20 = Salsa::<20>::new(&SALSA_20_TV0_KEY, &SALSA_20_TV0_IV); - let mut ks = [0_u8; 64]; - s20.crypt_in_place(&mut ks); - assert_eq!(ks, SALSA_20_TV0_KS); - - let mut s20 = Salsa::<20>::new(&SALSA_20_TV0_KEY, &SALSA_20_TV0_IV); - let mut ks = [0_u8; 32]; - s20.crypt_in_place(&mut ks); - assert_eq!(ks, &SALSA_20_TV0_KS[..32]); - } -} diff --git a/src/crypto/src/typestate.rs b/src/crypto/src/typestate.rs deleted file mode 100644 index 0e7ca04..0000000 --- a/src/crypto/src/typestate.rs +++ /dev/null @@ -1,223 +0,0 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. - -use std::fmt::Debug; -use std::hash::Hash; -use std::ops::{Deref, DerefMut}; - -/// Typestate indicating that a credential or other object has been internally validated. -#[repr(transparent)] -pub struct Valid(T); - -impl AsRef for Valid { - #[inline(always)] - fn as_ref(&self) -> &T { - &self.0 - } -} - -impl AsMut for Valid { - #[inline(always)] - fn as_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl Deref for Valid { - type Target = T; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Valid { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Clone for Valid -where - T: Clone, -{ - #[inline(always)] - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl PartialEq for Valid -where - T: PartialEq, -{ - #[inline(always)] - fn eq(&self, other: &Self) -> bool { - self.0.eq(&other.0) - } -} - -impl Eq for Valid where T: Eq {} - -impl Ord for Valid -where - T: Ord, -{ - #[inline(always)] - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl PartialOrd for Valid -where - T: PartialOrd, -{ - #[inline(always)] - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Hash for Valid -where - T: Hash, -{ - #[inline(always)] - fn hash(&self, state: &mut H) { - self.0.hash(state); - } -} - -impl Debug for Valid -where - T: Debug, -{ - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("Valid").field(&self.0).finish() - } -} - -impl Valid { - #[inline(always)] - pub fn remove_typestate(self) -> T { - self.0 - } - - #[inline(always)] - pub fn mark_valid(o: T) -> Self { - Self(o) - } -} - -/// Typestate indicating that a credential or other object has been externally validated. -/// -/// This is more appropriate for certificates signed by an external authority. -#[repr(transparent)] -pub struct Verified(T); - -impl AsRef for Verified { - #[inline(always)] - fn as_ref(&self) -> &T { - &self.0 - } -} - -impl AsMut for Verified { - #[inline(always)] - fn as_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl Deref for Verified { - type Target = T; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Verified { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Clone for Verified -where - T: Clone, -{ - #[inline(always)] - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl PartialEq for Verified -where - T: PartialEq, -{ - #[inline(always)] - fn eq(&self, other: &Self) -> bool { - self.0.eq(&other.0) - } -} - -impl Eq for Verified where T: Eq {} - -impl Ord for Verified -where - T: Ord, -{ - #[inline(always)] - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl PartialOrd for Verified -where - T: PartialOrd, -{ - #[inline(always)] - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Hash for Verified -where - T: Hash, -{ - #[inline(always)] - fn hash(&self, state: &mut H) { - self.0.hash(state); - } -} - -impl Debug for Verified -where - T: Debug, -{ - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("Valid").field(&self.0).finish() - } -} - -impl Verified { - #[inline(always)] - pub fn remove_typestate(self) -> T { - self.0 - } - - #[inline(always)] - pub fn mark_verified(o: T) -> Self { - Self(o) - } -} diff --git a/src/crypto/src/x25519.rs b/src/crypto/src/x25519.rs deleted file mode 100644 index 47041ff..0000000 --- a/src/crypto/src/x25519.rs +++ /dev/null @@ -1,171 +0,0 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. - -use std::convert::TryInto; -use std::io::Write; - -use ed25519_dalek::Digest; - -use crate::random::SecureRandom; -use crate::secret::Secret; - -pub const C25519_PUBLIC_KEY_SIZE: usize = 32; -pub const C25519_SECRET_KEY_SIZE: usize = 32; -pub const C25519_SHARED_SECRET_SIZE: usize = 32; -pub const ED25519_PUBLIC_KEY_SIZE: usize = 32; -pub const ED25519_SECRET_KEY_SIZE: usize = 32; -pub const ED25519_SIGNATURE_SIZE: usize = 64; - -/// Curve25519 key pair for ECDH key agreement. -pub struct X25519KeyPair(x25519_dalek::StaticSecret, Secret<32>, x25519_dalek::PublicKey); - -impl X25519KeyPair { - pub fn generate() -> X25519KeyPair { - let sk = x25519_dalek::StaticSecret::new(SecureRandom::get()); - let sk2 = Secret(sk.to_bytes()); - let pk = x25519_dalek::PublicKey::from(&sk); - X25519KeyPair(sk, sk2, pk) - } - - pub fn from_bytes(public_key: &[u8], secret_key: &[u8]) -> Option { - if public_key.len() == 32 && secret_key.len() == 32 { - /* NOTE: we keep the original secret separately from x25519_dalek's StaticSecret - * due to how "clamping" is done in the old C++ code vs x25519_dalek. Clamping - * is explained here: - * - * https://www.jcraige.com/an-explainer-on-ed25519-clamping - * - * The old code does clamping at the time of use. In other words the code that - * performs things like key agreement or signing clamps the secret before doing - * the operation. The x25519_dalek code does clamping at generation or when - * from() is used to get a key from a raw byte array. - * - * Unfortunately this introduces issues when interoperating with old code. The - * old system generates secrets that are not clamped (since they're clamped at - * use!) and assumes that these exact binary keys will be preserved in e.g. - * identities. So to preserve this behavior we store the secret separately - * so secret_bytes() will return it as-is. - * - * The new code will still clamp at generation resulting in secrets that are - * pre-clamped, but the old code won't care about this. It's only a problem when - * going the other way. - * - * This has no cryptographic implication since regardless of where, the clamping - * is done. It's just an API thing. - */ - let pk: [u8; 32] = public_key.try_into().unwrap(); - let sk_orig: Secret<32> = Secret(secret_key.try_into().unwrap()); - let pk = x25519_dalek::PublicKey::from(pk); - let sk = x25519_dalek::StaticSecret::from(sk_orig.0); - Some(X25519KeyPair(sk, sk_orig, pk)) - } else { - None - } - } - - #[inline(always)] - pub fn public_bytes(&self) -> [u8; C25519_PUBLIC_KEY_SIZE] { - self.2.to_bytes() - } - - #[inline(always)] - pub fn secret_bytes(&self) -> &Secret<32> { - &self.1 - } - - /// Execute ECDH agreement and return a raw (un-hashed) shared secret key. - pub fn agree(&self, their_public: &[u8]) -> Secret<{ C25519_SHARED_SECRET_SIZE }> { - let pk: [u8; 32] = their_public.try_into().unwrap(); - let pk = x25519_dalek::PublicKey::from(pk); - let sec = self.0.diffie_hellman(&pk); - Secret(sec.to_bytes()) - } -} - -impl Clone for X25519KeyPair { - fn clone(&self) -> Self { - Self( - x25519_dalek::StaticSecret::from(self.0.to_bytes()), - self.1.clone(), - x25519_dalek::PublicKey::from(self.1 .0), - ) - } -} - -/// Ed25519 key pair for EDDSA signatures. -pub struct Ed25519KeyPair(ed25519_dalek::Keypair, Secret<32>); - -impl Ed25519KeyPair { - pub fn generate() -> Ed25519KeyPair { - let mut rng = SecureRandom::get(); - let kp = ed25519_dalek::Keypair::generate(&mut rng); - let sk2 = Secret(kp.secret.to_bytes()); - Ed25519KeyPair(kp, sk2) - } - - pub fn from_bytes(public_bytes: &[u8], secret_bytes: &[u8]) -> Option { - if public_bytes.len() == ED25519_PUBLIC_KEY_SIZE && secret_bytes.len() == ED25519_SECRET_KEY_SIZE { - let pk = ed25519_dalek::PublicKey::from_bytes(public_bytes); - let sk = ed25519_dalek::SecretKey::from_bytes(secret_bytes); - if pk.is_ok() && sk.is_ok() { - // See comment in from_bytes() in C25519KeyPair for an explanation of the copy of the secret here. - let pk = pk.unwrap(); - let sk = sk.unwrap(); - let sk2 = Secret(sk.to_bytes()); - Some(Ed25519KeyPair(ed25519_dalek::Keypair { public: pk, secret: sk }, sk2)) - } else { - None - } - } else { - None - } - } - - #[inline(always)] - pub fn public_bytes(&self) -> [u8; ED25519_PUBLIC_KEY_SIZE] { - self.0.public.to_bytes() - } - - #[inline(always)] - pub fn secret_bytes(&self) -> &Secret<32> { - &self.1 - } - - pub fn sign(&self, msg: &[u8]) -> [u8; ED25519_SIGNATURE_SIZE] { - let mut h = ed25519_dalek::Sha512::new(); - let _ = h.write_all(msg); - self.0.sign_prehashed(h.clone(), None).unwrap().to_bytes() - } - - /// Create a signature with the first 32 bytes of the SHA512 hash appended. - /// ZeroTier does this for legacy reasons, but it's ignored in newer versions. - pub fn sign_zt(&self, msg: &[u8]) -> [u8; 96] { - let mut h = ed25519_dalek::Sha512::new(); - let _ = h.write_all(msg); - let sig = self.0.sign_prehashed(h.clone(), None).unwrap(); - let s = sig.as_ref(); - let mut s2 = [0_u8; 96]; - s2[0..64].copy_from_slice(s); - let h = h.finalize(); - s2[64..96].copy_from_slice(&h.as_slice()[0..32]); - s2 - } -} - -impl Clone for Ed25519KeyPair { - fn clone(&self) -> Self { - Self(ed25519_dalek::Keypair::from_bytes(&self.0.to_bytes()).unwrap(), self.1.clone()) - } -} - -pub fn ed25519_verify(public_key: &[u8], signature: &[u8], msg: &[u8]) -> bool { - if public_key.len() == 32 && signature.len() >= 64 { - ed25519_dalek::PublicKey::from_bytes(public_key).map_or(false, |pk| { - let mut h = ed25519_dalek::Sha512::new(); - let _ = h.write_all(msg); - let sig: [u8; 64] = signature[0..64].try_into().unwrap(); - pk.verify_prehashed(h, None, &ed25519_dalek::Signature::from(sig)).is_ok() - }) - } else { - false - } -} diff --git a/src/frag_cache.rs b/src/frag_cache.rs index d77a7f2..50d97f2 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -233,10 +233,9 @@ impl Drop for UnassociatedFragCache { } } +/* #[test] fn test_cache() { - use zerotier_crypto::random; - let mut cache = UnassociatedFragCache::new(); let mut assembled = Assembled::new(); @@ -304,3 +303,4 @@ fn test_cache() { } } } + */ diff --git a/src/utils/src/indexed_heap.rs b/src/indexed_heap.rs similarity index 100% rename from src/utils/src/indexed_heap.rs rename to src/indexed_heap.rs diff --git a/src/lib.rs b/src/lib.rs index 2bcbcc0..fcfb868 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,10 +5,14 @@ * (c) ZeroTier, Inc. * https://www.zerotier.com/ */ +pub const AES_BLOCK_SIZE: usize = 16; + +pub mod crypto; mod applicationlayer; mod frag_cache; mod fragged; +mod indexed_heap; mod handshake_cache; mod log_event; mod proto; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 975276d..0000000 --- a/src/main.rs +++ /dev/null @@ -1,333 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ -use std::iter::ExactSizeIterator; -use std::str::FromStr; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{mpsc, Arc, Mutex}; -use std::thread; -use std::time::Duration; - -use zerotier_crypto::p384::{P384KeyPair, P384PublicKey}; -use zerotier_crypto::{random, secure_eq}; -use zssp::{ - AcceptSessionAction, GetRatchetAction, IncomingSessionAction, LogEvent, SaveRatchetAction, Session, RATCHET_FINGERPRINT_SIZE, RATCHET_KEY_SIZE, -}; - -const TEST_MTU: usize = 1500; - -struct TestApplication { - name: &'static str, - identity_key: P384KeyPair, - ratchets: Mutex<(u64, [(u64, [u8; RATCHET_FINGERPRINT_SIZE], [u8; RATCHET_KEY_SIZE]); 2])>, -} - -impl zssp::ApplicationLayer for TestApplication { - const REKEY_AFTER_TIME_MS: i64 = 4000; - const REKEY_AFTER_TIME_MAX_JITTER_MS: i64 = 2000; - - const RETRY_INTERVAL_MS: i64 = 250; - const INITIAL_OFFER_TIMEOUT_MS: i64 = 2000; - const EXPIRATION_TIMEOUT_MS: i64 = 60000; - - type Data = (); - type IncomingPacketBuffer = Vec; - type LocalIdentityBlob = [u8; 0]; - - fn local_s_keypair(&self) -> &P384KeyPair { - &self.identity_key - } - fn save_ratchet_state( - &self, - _: &P384PublicKey, - _: &Self::Data, - action: SaveRatchetAction, - ratchet_number: u64, - ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], - ratchet_key: &[u8; RATCHET_KEY_SIZE], - _: i64, - ) -> Result<(), ()> { - let latest_idx = ratchet_number as usize % 2; - let mut ratchets = self.ratchets.lock().unwrap(); - if action.save_latest() { - ratchets.1[latest_idx] = (ratchet_number, *ratchet_fingerprint, *ratchet_key); - } - if action.confirm_latest() { - ratchets.0 = ratchet_number; - } - if action.delete_previous() { - ratchets.1[latest_idx ^ 1] = (0, [0; RATCHET_FINGERPRINT_SIZE], [0; RATCHET_KEY_SIZE]); - } - Ok(()) - } - fn lookup_ratchet(&self, ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], _: i64) -> Result { - let r = self.ratchets.lock().unwrap(); - for state in &r.1 { - if secure_eq(&state.1, ratchet_fingerprint) { - return Ok(GetRatchetAction::Found(state.0, state.2)); - } - } - panic!() - } - fn allow_zero_ratchet(&self, _: i64) -> bool { - true - } - fn allow_downgrade(&self, _: &Arc>, _: i64) -> bool { - true - } - fn event_log(&self, event: LogEvent<'_, Self>, _: i64) { - println!("> [{}] {:?}", self.name, event); - match event { - LogEvent::ServiceKKTimeout(_) => panic!(), - _ => (), - } - } -} - -fn alice_main( - run: &AtomicBool, - packet_success_rate: u32, - alice_app: &TestApplication, - bob_app: &TestApplication, - alice_out: mpsc::SyncSender>, - alice_in: mpsc::Receiver>, -) { - let startup_time = std::time::Instant::now(); - let context = zssp::Context::::new(); - let mut data_buf = [0u8; 65536]; - let mut next_service = startup_time.elapsed().as_millis() as i64 + 500; - let test_data = [1u8; TEST_MTU * 10]; - let mut up = false; - let mut alice_session = None; - - while run.load(Ordering::Relaxed) { - if alice_session.is_none() { - up = false; - let ratchets = alice_app.ratchets.lock().unwrap(); - let ratchet_state = ratchets.1[ratchets.0 as usize % 2]; - alice_session = Some( - context - .open( - alice_app, - |b| alice_out.send(b.to_vec()).is_ok(), - TEST_MTU, - bob_app.identity_key.to_public_key(), - (), - Some(ratchet_state), - [], - startup_time.elapsed().as_millis() as i64, - ) - .unwrap(), - ); - println!("[alice] opening session"); - } - let current_time = startup_time.elapsed().as_millis() as i64; - loop { - let pkt = alice_in.try_recv(); - if let Ok(pkt) = pkt { - if (random::xorshift64_random() as u32) <= packet_success_rate { - use zssp::SessionEvent::*; - match context.receive( - alice_app, - || panic!(), - |_, _, _| panic!(), - |b| alice_out.send(b.to_vec()).is_ok(), - TEST_MTU, - |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)), - &0, - &mut data_buf, - pkt, - current_time, - ) { - Ok(zssp::ReceiveResult::Unassociated) => { - //println!("[alice] ok"); - } - Ok(zssp::ReceiveResult::Session(_, event)) => match event { - Established(ratchet_number) => { - up = true; - println!("[alice] new ratchet key #{}", ratchet_number); - } - Data(data) => { - assert!(!data.is_empty()); - //println!("[alice] received {}", data.len()); - } - NewSession(..) => panic!(), - Ratchet(ratchet_number) => { - println!("[alice] new ratchet key #{}", ratchet_number); - } - Rejected => panic!(), - Control => (), - }, - Ok(zssp::ReceiveResult::Rejected) => {} - Err(e) => { - println!("[alice] ERROR {:?}", e); - if let zssp::error::ReceiveError::ByzantineFault { is_naturally_occurring, .. } = e { - assert!(is_naturally_occurring) - } - } - } - } - } else { - break; - } - } - - if up { - context - .send( - alice_session.as_ref().unwrap(), - |b| alice_out.send(b.to_vec()).is_ok(), - &mut data_buf[..TEST_MTU], - &test_data[..1400 + ((random::xorshift64_random() as usize) % (test_data.len() - 1400))], - current_time, - ) - .unwrap(); - } else { - thread::sleep(Duration::from_millis(10)); - } - // TODO: we need to more comprehensively test if re-opening the session works - if (random::xorshift64_random() as u32) <= ((u32::MAX as f64) * 0.00000025) as u32 { - alice_session = None; - } - - if current_time >= next_service { - next_service = current_time - + context.service( - alice_app, - |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)), - current_time, - ); - } - } -} - -fn bob_main( - run: &AtomicBool, - packet_success_rate: u32, - _alice_app: &TestApplication, - bob_app: &TestApplication, - bob_out: mpsc::SyncSender>, - bob_in: mpsc::Receiver>, -) { - let startup_time = std::time::Instant::now(); - let context = zssp::Context::::new(); - let mut data_buf = [0u8; 65536]; - let mut data_buf_2 = [0u8; TEST_MTU]; - let mut last_speed_metric = startup_time.elapsed().as_millis() as i64; - let mut next_service = last_speed_metric + 500; - let mut transferred = 0u64; - - let mut bob_session = None; - - while run.load(Ordering::Relaxed) { - let pkt = bob_in.recv_timeout(Duration::from_millis(100)); - let current_time = startup_time.elapsed().as_millis() as i64; - - if let Ok(pkt) = pkt { - if (random::xorshift64_random() as u32) <= packet_success_rate { - use zssp::SessionEvent::*; - match context.receive( - bob_app, - || IncomingSessionAction::Allow, - |_, _, _| AcceptSessionAction::Accept(()), - |b| bob_out.send(b.to_vec()).is_ok(), - TEST_MTU, - |_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)), - &0, - &mut data_buf, - pkt, - current_time, - ) { - Ok(zssp::ReceiveResult::Unassociated) => {} - Ok(zssp::ReceiveResult::Session(s, event)) => match event { - NewSession(ratchet_number) => { - println!("[bob] new session, took {}s", current_time as f32 / 1000.0); - let _ = bob_session.replace(s); - println!("[bob] new ratchet key #{}", ratchet_number); - } - Data(data) => { - assert!(!data.is_empty()); - //println!("[bob] received {}", data.len()); - context - .send(&s, |b| bob_out.send(b.to_vec()).is_ok(), &mut data_buf_2, data.as_mut(), current_time) - .unwrap(); - transferred += data.len() as u64 * 2; // *2 because we are also sending this many bytes back - } - Established(_) => panic!(), - Rejected => panic!(), - Ratchet(ratchet_number) => { - println!("[bob] new ratchet key #{}", ratchet_number); - } - Control => (), - }, - Ok(zssp::ReceiveResult::Rejected) => {} - Err(e) => { - println!("[bob] ERROR {:?}", e); - if let zssp::error::ReceiveError::ByzantineFault { is_naturally_occurring, .. } = e { - assert!(is_naturally_occurring) - } - } - } - } - } - - let speed_metric_elapsed = current_time - last_speed_metric; - if speed_metric_elapsed >= 10000 { - last_speed_metric = current_time; - println!( - "[bob] throughput: {} MiB/sec (combined input and output)", - ((transferred as f64) / 1048576.0) / ((speed_metric_elapsed as f64) / 1000.0) - ); - transferred = 0; - } - - if current_time >= next_service { - next_service = current_time - + context.service( - bob_app, - |_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)), - current_time, - ); - } - } -} - -fn main() { - let run = AtomicBool::new(true); - - let alice_app = TestApplication { - name: "alice", - identity_key: P384KeyPair::generate(), - ratchets: Mutex::new((0, std::array::from_fn(|_| (0, [0u8; RATCHET_FINGERPRINT_SIZE], [0u8; RATCHET_KEY_SIZE])))), - }; - let bob_app = TestApplication { - name: "bob", - identity_key: P384KeyPair::generate(), - ratchets: Mutex::new((0, std::array::from_fn(|_| (0, [0u8; RATCHET_FINGERPRINT_SIZE], [0u8; RATCHET_KEY_SIZE])))), - }; - - let (alice_out, bob_in) = mpsc::sync_channel::>(256); - let (bob_out, alice_in) = mpsc::sync_channel::>(256); - - let args = std::env::args(); - let packet_success_rate = if args.len() <= 1 { - let default_success_rate = 1.0; - ((u32::MAX as f64) * default_success_rate) as u32 - } else { - ((u32::MAX as f64) * f64::from_str(args.last().unwrap().as_str()).unwrap()) as u32 - }; - - thread::scope(|ts| { - ts.spawn(|| alice_main(&run, packet_success_rate, &alice_app, &bob_app, alice_out, alice_in)); - ts.spawn(|| bob_main(&run, packet_success_rate, &alice_app, &bob_app, bob_out, bob_in)); - - thread::sleep(Duration::from_secs(60 * 60)); - - run.store(false, Ordering::SeqCst); - println!("finished"); - }); -} diff --git a/src/proto.rs b/src/proto.rs index 5fa7e68..121b132 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -11,9 +11,9 @@ use std::mem::size_of; use hex_literal::hex; use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; -use zerotier_crypto::constant::AES_GCM_TAG_SIZE; -use zerotier_crypto::hash::{SHA512, SHA512_HASH_SIZE}; -use zerotier_crypto::p384::P384_PUBLIC_KEY_SIZE; +use crate::crypto::aes_gcm::AES_GCM_TAG_SIZE; +use crate::crypto::sha512::{Sha512, SHA512_HASH_SIZE}; +use crate::crypto::p384::P384_PUBLIC_KEY_SIZE; /// Minimum size of a valid physical ZSSP packet of any type. Anything smaller is discarded. pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; @@ -282,8 +282,8 @@ pub(crate) fn byte_array_as_proto_buffer_mut(b: &mut [u8] unsafe { &mut *b.as_mut_ptr().cast() } } /// Trick rust into letting us use a hasher that returns more than 64 bits. -pub(crate) struct SHAHasher<'a>(pub &'a mut SHA512); -impl<'a> Hasher for SHAHasher<'a> { +pub(crate) struct ShaHasher<'a, ShaImpl: Sha512>(pub &'a mut ShaImpl); +impl<'a, ShaImpl: Sha512> Hasher for ShaHasher<'a, ShaImpl> { fn finish(&self) -> u64 { panic!() } diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 6d1697f..b5cb429 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -5,28 +5,34 @@ * (c) ZeroTier, Inc. * https://www.zerotier.com/ */ -use zerotier_crypto::constant::AES_256_KEY_SIZE; -use zerotier_crypto::hash::{HMACSHA512, HMAC_SHA512_SIZE}; -use zerotier_crypto::secret::Secret; +use std::marker::PhantomData; + +use crate::crypto::aes::AES_256_KEY_SIZE; +use crate::crypto::sha512::HmacSha512; +use crate::crypto::secret::Secret; use crate::proto::NOISE_HASHLEN; -#[derive(Clone)] -pub(crate) struct SymmetricState { +pub(crate) struct SymmetricState { chaining_key: Secret, token_counter: u8, + p: PhantomData +} +impl Clone for SymmetricState { + fn clone(&self) -> Self { + Self { chaining_key: self.chaining_key.clone(), token_counter: self.token_counter.clone(), p: PhantomData } + } } -impl SymmetricState { +impl SymmetricState { pub(crate) fn new(h: [u8; NOISE_HASHLEN]) -> Self { - debug_assert_eq!(NOISE_HASHLEN, HMAC_SHA512_SIZE); - Self { chaining_key: Secret(h), token_counter: b'P' } + Self { chaining_key: Secret(h), token_counter: b'P', p: PhantomData } } /// Corresponds to Noise `MixKey`. pub(crate) fn mix_key(&mut self, input_key_material: &[u8]) { let mut next_ck = Secret::new(); - self.kbkdf(input_key_material, self.label(), 2, next_ck.as_bytes_mut(), None, None); + self.kbkdf(input_key_material, self.label(), 2, next_ck.as_mut(), None, None); self.token_counter += 1; self.chaining_key.overwrite(&next_ck); @@ -39,7 +45,7 @@ impl SymmetricState { let mut next_ck = Secret::new(); let mut temp_k = [0u8; NOISE_HASHLEN]; - self.kbkdf(input_key_material, self.label(), 2, next_ck.as_bytes_mut(), Some(&mut temp_k), None); + self.kbkdf(input_key_material, self.label(), 2, next_ck.as_mut(), Some(&mut temp_k), None); self.token_counter += 1; self.chaining_key.overwrite(&next_ck); @@ -50,7 +56,7 @@ impl SymmetricState { let mut next_ck = Secret::new(); let mut temp_h = [0u8; NOISE_HASHLEN]; - self.kbkdf(input_key_material, self.label(), 3, next_ck.as_bytes_mut(), Some(&mut temp_h), None); + self.kbkdf(input_key_material, self.label(), 3, next_ck.as_mut(), Some(&mut temp_h), None); self.token_counter += 1; self.chaining_key.overwrite(&next_ck); @@ -66,7 +72,7 @@ impl SymmetricState { input_key_material, self.label(), 3, - next_ck.as_bytes_mut(), + next_ck.as_mut(), Some(&mut temp_h), Some(&mut temp_k), ); @@ -129,24 +135,24 @@ impl SymmetricState { ) { let l = &(num_outputs * 512u16).to_be_bytes(); - let mut hm = HMACSHA512::new(input_key_material); + let mut hm = Hmac::new(input_key_material); hm.update(&[1, label[0], label[1], label[2], label[3], 0x00]); - hm.update(self.chaining_key.as_bytes()); + hm.update(self.chaining_key.as_ref()); hm.update(l); - *output1 = hm.finish(); + hm.finish(output1); if let Some(output2) = output2 { hm.reset(input_key_material); hm.update(&[2, label[0], label[1], label[2], label[3], 0x00]); - hm.update(self.chaining_key.as_bytes()); + hm.update(self.chaining_key.as_ref()); hm.update(l); - *output2 = hm.finish(); + hm.finish(output2); } if let Some(output3) = output3 { hm.reset(input_key_material); hm.update(&[3, label[0], label[1], label[2], label[3], 0x00]); - hm.update(self.chaining_key.as_bytes()); + hm.update(self.chaining_key.as_ref()); hm.update(l); - *output3 = hm.finish(); + hm.finish(output3); } } } diff --git a/src/zssp.rs b/src/zssp.rs index 62f4ef0..6d3492c 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -9,21 +9,22 @@ // FIPS compliant Noise_XK with Jedi powers (Kyber1024) and built-in attack-resistant large payload (fragmentation) support. use std::cmp::Reverse; +use std::ops::DerefMut; use std::collections::HashMap; use std::hash::Hash; use std::num::NonZeroU32; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; -use zerotier_crypto::aes::{Aes, AesGcm}; -use zerotier_crypto::constant::{AES_256_KEY_SIZE, AES_GCM_NONCE_SIZE, AES_GCM_TAG_SIZE}; -use zerotier_crypto::hash::SHA512; -use zerotier_crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; -use zerotier_crypto::secret::Secret; -use zerotier_crypto::{random, secure_eq}; +use crate::crypto::aes::{AesEnc, AesDec}; +use crate::crypto::aes_gcm::{AES_GCM_KEY_SIZE, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE, AesGcmDec, AesGcmEnc}; +use crate::crypto::p384::{P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE, P384KeyPair, P384PublicKey}; +use crate::crypto::secret::Secret; +use crate::crypto::secure_eq; +use crate::crypto::sha512::Sha512; -use pqc_kyber::KYBER_SECRETKEYBYTES; -use zerotier_utils::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; +use pqc_kyber::{KYBER_SECRETKEYBYTES, RngCore}; +use crate::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; use crate::applicationlayer::*; use crate::error::{FaultType, OpenError, ReceiveError, SendError}; @@ -47,6 +48,7 @@ impl Clone for Context { } } pub struct ContextInner { + static_keypair: Application::KeyPair, unassociated_defrag_cache: Mutex>, unassociated_handshake_states: UnassociatedHandshakeCache, /// `session_queue -> state_machine_lock -> state -> session_map` @@ -55,6 +57,7 @@ pub struct ContextInner { challenge_counter: AtomicU64, challenge_antireplay_window: [AtomicU64; CHALLENGE_COUNTER_WINDOW_MAX_OOO], challenge_salt: [u8; CHALLENGE_SALT_SIZE], + rng: Mutex, } /// Result generated by the context packet receive function, with possible payloads. @@ -127,7 +130,7 @@ pub struct Session { /// Handle into the session queue for changing the update timer. queue_idx: BinaryHeapIndex, - remote_s_public_key: P384PublicKey, + remote_s_public_key: Application::PublicKey, send_counter: AtomicU64, /// This bool signals to all threads to stop incrementing the counter and instead error out. session_has_expired: AtomicBool, @@ -141,10 +144,10 @@ pub struct Session { state_machine_lock: Mutex<()>, state: RwLock>, defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], - header_send_cipher: Aes, - header_receive_cipher: Aes, - kex_send_cipher: Mutex>>, - kex_receive_cipher: Mutex>>, + header_send_cipher: Application::BlockCipherEnc, + header_receive_cipher: Application::BlockCipherDec, + kex_send_cipher: Mutex>, + kex_receive_cipher: Mutex>, /// Pre-computed rekeying values. noise_kk_ss: Secret, noise_kk_local_init_h: [u8; NOISE_HASHLEN], @@ -162,7 +165,7 @@ struct SessionMutableState { ratchet_key: Secret, /// For OOO rekeying reliability we allow our version of Noise CipherState to hold the last two /// session keys, instead of just the most recent one. - cipher_states: [Option; 2], + cipher_states: [Option>; 2], /// This is the index of `noise_cipher_state` that contains the most recent key. /// It will be attached to fragment headers to help with OOO transport. current_key: usize, @@ -182,16 +185,16 @@ enum OfferStateMachine { next_retry_time: AtomicI64, timeout: i64, new_key_id: NonZeroU32, - noise_e_secret: P384KeyPair, + noise_e_secret: Application::KeyPair, noise_message: [u8; NoiseKKPattern1or2::SIZE], - noise_ck: SymmetricState, + noise_ck: SymmetricState, noise_h_pskep: [u8; NOISE_HASHLEN], }, // -> NoiseKKPattern2, KeyConfirm NoiseKKPattern2 { next_retry_time: AtomicI64, timeout: i64, noise_message: [u8; NoiseKKPattern1or2::SIZE], - kex_send_key: Secret, + kex_send_key: Secret, new_ratchet_number: u64, new_ratchet_fingerprint: Secret, new_ratchet_key: Secret, @@ -204,14 +207,14 @@ enum OfferStateMachine { pub(crate) struct NoiseXKBobHandshakeState { remote_key_id: NonZeroU32, local_key_id: NonZeroU32, - header_receive_key: Secret, - header_send_key: Secret, + header_receive_key: Secret, + header_send_key: Secret, ratchet_number: u64, ratchet_fingerprint: Option<[u8; RATCHET_FINGERPRINT_SIZE]>, noise_h_ee1peekem1pskp: [u8; NOISE_HASHLEN], - noise_e_secret: P384KeyPair, - noise_ck_eseeekem1psk: SymmetricState, - noise_k_eseeekem1psk: Secret, + noise_e_secret: Application::KeyPair, + noise_ck_eseeekem1psk: SymmetricState, + noise_k_eseeekem1psk: Secret, noise_pattern3_defrag: Mutex>, } struct NoiseXKAliceHandshake { @@ -221,14 +224,14 @@ struct NoiseXKAliceHandshake { /// If a DDOS attacker could guess this they could block Alice starting the handshake. local_key_id: NonZeroU32, alice_identity_blob: Application::LocalIdentityBlob, - offer: NoiseXKAliceHandshakeState, + offer: NoiseXKAliceHandshakeState, } -enum NoiseXKAliceHandshakeState { +enum NoiseXKAliceHandshakeState { NoiseXKPattern1 { noise_h_ee1p: [u8; NOISE_HASHLEN], - noise_e_secret: P384KeyPair, + noise_e_secret: Application::KeyPair, noise_e1_secret: Secret, - noise_ck_es: SymmetricState, + noise_ck_es: SymmetricState, /// ZSSP assumes an unreliable, out-of-order physical transport environment, so for that /// reason we have to resend key offers. noise_message: [u8; NoiseXKPattern1::SIZE], @@ -243,13 +246,13 @@ enum NoiseXKAliceHandshakeState { }, } -struct SessionKey { +struct SessionKey { remote_key_id: NonZeroU32, local_key_id: NonZeroU32, /// Pool of reusable sending ciphers. - receive_cipher_pool: [Mutex>; 8], + receive_cipher_pool: [Mutex; 8], /// Pool of reusable receiving ciphers. - send_cipher_pool: [Mutex>; 8], + send_cipher_pool: [Mutex; 8], /// Rekey at or after this counter. rekey_at_counter: u64, /// Hard error when this counter value is reached or exceeded. @@ -269,16 +272,20 @@ macro_rules! byzantine_fault { impl Context { /// Create a new session context. - pub fn new() -> Self { + pub fn new(static_keypair: Application::KeyPair, mut rng: Application::Rng) -> Self { debug_assert!(Application::REKEY_AFTER_TIME_MAX_JITTER_MS > 0, "Invalid protocol constant"); + let mut challenge_salt = [0u8; CHALLENGE_SALT_SIZE]; + rng.fill_bytes(&mut challenge_salt); Self(Arc::new(ContextInner { + static_keypair, unassociated_defrag_cache: Mutex::new(UnassociatedFragCache::new()), unassociated_handshake_states: UnassociatedHandshakeCache::new(), session_map: RwLock::new(HashMap::new()), session_queue: Mutex::new(IndexedBinaryHeap::new()), challenge_counter: AtomicU64::new(INIT_COUNTER), challenge_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - challenge_salt: random::get_bytes_secure(), + challenge_salt, + rng: Mutex::new(rng), })) } @@ -357,7 +364,7 @@ impl Context { if !handshake_state.reinitialize( &session, &ratchet_fingerprint, - &mut self.0.session_map.write().unwrap(), + &mut self.0.session_map.write().unwrap(), &mut self.0.rng.lock().unwrap(), current_time, ) { session.expire_inner(&self.0, &mut session_queue); @@ -377,7 +384,7 @@ impl Context { PACKET_TYPE_NOISE_XK_PATTERN_1, None, *message_id, - None, + None::<&Application::BlockCipherEnc>, ); } NoiseXKAliceHandshakeState::NoiseXKPattern3 { noise_message, noise_message_len, .. } => { @@ -473,10 +480,9 @@ impl Context { #[inline] pub fn open( &self, - app: &Application, mut send: impl FnMut(&mut [u8]) -> bool, mut mtu: usize, - remote_s_public_key: P384PublicKey, + remote_s_public_key: Application::PublicKey, application_data: Application::Data, ratchet_state: Option<(u64, [u8; RATCHET_FINGERPRINT_SIZE], [u8; RATCHET_KEY_SIZE])>, local_identity_blob: Application::LocalIdentityBlob, @@ -488,21 +494,23 @@ impl Context { } let (ratchet_number, mut ratchet_fingerprint, mut ratchet_key) = ratchet_state.unwrap_or((0, [0; RATCHET_FINGERPRINT_SIZE], [0; RATCHET_KEY_SIZE])); - let sha512 = &mut SHA512::new(); + let sha512 = &mut Application::Hash::new(); - let alice_s_keypair = app.local_s_keypair(); - let noise_kk_ss = alice_s_keypair.agree(&remote_s_public_key).ok_or(OpenError::InvalidPublicKey)?; - let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, alice_s_keypair.public_key_bytes()); + let mut noise_kk_ss = Secret::new(); + if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { + return Err(OpenError::InvalidPublicKey) + } + let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, alice_s_keypair.public_key_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); let mut session_queue = self.0.session_queue.lock().unwrap(); let mut session_map = self.0.session_map.write().unwrap(); - let local_key_id = generate_key_id(&session_map); + let local_key_id = generate_key_id(&session_map, &mut self.0.rng.lock().unwrap()); // Begin Noise XKhfs+psk2. let (offer, a2b_header_key, b2a_header_key) = - NoiseXKAliceHandshake::::initialize(local_key_id, &remote_s_public_key, &ratchet_fingerprint)?; + NoiseXKAliceHandshake::::initialize(local_key_id, &remote_s_public_key, &ratchet_fingerprint, &mut self.0.rng.lock().unwrap())?; let handshake_state = Box::new(NoiseXKAliceHandshake { next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), @@ -518,7 +526,7 @@ impl Context { PACKET_TYPE_NOISE_XK_PATTERN_1, None, *message_id, - None, + None::<&Application::BlockCipherEnc>, ); } @@ -541,11 +549,11 @@ impl Context { current_key: 1, outgoing_offer: OfferStateMachine::NoiseXKPattern1or3(handshake_state), }), - header_receive_cipher: Aes::new(b2a_header_key.as_bytes()), - header_send_cipher: Aes::new(a2b_header_key.as_bytes()), + header_send_cipher: Application::BlockCipherEnc::new(a2b_header_key.as_ref()), + header_receive_cipher: Application::BlockCipherDec::new(b2a_header_key.as_ref()), kex_receive_cipher: Mutex::new(None), kex_send_cipher: Mutex::new(None), - noise_kk_ss, + noise_kk_ss: noise_kk_ss.clone(), noise_kk_local_init_h, noise_kk_remote_init_h, defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), @@ -599,7 +607,7 @@ impl Context { &self, app: &Application, check_allow_incoming_session: impl FnOnce() -> IncomingSessionAction, - check_accept_session: impl FnOnce(&P384PublicKey, &[u8], Option<&[u8; RATCHET_FINGERPRINT_SIZE]>) -> AcceptSessionAction, + check_accept_session: impl FnOnce(&Application::PublicKey, &[u8], Option<&[u8; RATCHET_FINGERPRINT_SIZE]>) -> AcceptSessionAction, mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, mut send_unassociated_mtu: usize, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, @@ -631,7 +639,7 @@ impl Context { drop(session_map); session .header_receive_cipher - .crypt_block_in_place(&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]); + .decrypt_in_place((&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); let (fragment_count, fragment_no, packet_type, incoming_counter, header_nonce) = parse_packet_header(&incoming_physical_packet); // Handle replay protection. if PACKET_TYPE_RANGE_TRANSPORT.contains(&packet_type) { @@ -718,7 +726,7 @@ impl Context { .as_ref() .ok_or(byzantine_fault!(FaultType::OutOfSequence, true))?; let mut c = key.get_receive_cipher(incoming_counter); - c.reset_init_gcm(&create_message_nonce(packet_type, incoming_counter)); + c.set_iv(&create_message_nonce(packet_type, incoming_counter)); let mut data_len = 0; @@ -731,7 +739,7 @@ impl Context { if data_len > data_buf.len() { return Err(ReceiveError::DataBufferTooSmall); } - c.crypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); + c.decrypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); } // Decrypt final fragment (or only fragment if not fragmented) @@ -745,9 +753,9 @@ impl Context { return Err(ReceiveError::DataBufferTooSmall); } let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE; - c.crypt(&last_fragment[HEADER_SIZE..payload_end], &mut data_buf[current_frag_data_start..data_len]); + c.decrypt(&last_fragment[HEADER_SIZE..payload_end], &mut data_buf[current_frag_data_start..data_len]); - let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..]); + let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..].try_into().unwrap()); drop(c); drop(state); @@ -775,8 +783,8 @@ impl Context { // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 incoming = self.0.unassociated_handshake_states.get(local_key_id); if let Some(incoming) = incoming.as_ref() { - Aes::::new(incoming.header_receive_key.as_bytes()) - .crypt_block_in_place(&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]); + Application::BlockCipherDec::new(incoming.header_receive_key.as_ref()) + .decrypt_in_place((&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(&incoming_physical_packet); app.event_log( LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), @@ -877,7 +885,7 @@ impl Context { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } if let Some(remote_key_id) = NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.alice_key_id)) { - let sha512 = &mut SHA512::new(); + let sha512 = &mut Application::Hash::new(); // Let application filter incoming connection attempts by whatever criteria it wants. // This should ideally prevent ZSSP from wasting time on DDOS attacks. match check_allow_incoming_session() { @@ -888,12 +896,14 @@ impl Context { let counter = u64::from_be_bytes(counter); sha512.reset(); - let mut hasher = SHAHasher(sha512); + let mut hasher = ShaHasher(sha512); + let mut output = [0u8; NOISE_HASHLEN]; hasher.0.update(&noise_pattern1.challenge_counter); remote_address.hash(&mut hasher); hasher.0.update(&self.0.challenge_salt); + hasher.0.finish(&mut output); let is_valid = self.check_challenge_window(counter) - && secure_eq(&hasher.0.finish()[..CHALLENGE_MAC_SIZE], &noise_pattern1.challenge_mac) + && secure_eq(&output[..CHALLENGE_MAC_SIZE], &noise_pattern1.challenge_mac) && verify_pow::(&mut hasher.0, &message) && self.update_challenge_window(counter); app.event_log(LogEvent::ReceiveCheckXK1Challenge(is_valid), current_time); @@ -911,7 +921,8 @@ impl Context { hasher.0.update(&counter.to_be_bytes()); remote_address.hash(&mut hasher); hasher.0.update(&self.0.challenge_salt); - challenge.challenge_mac.copy_from_slice(&hasher.0.finish()[..CHALLENGE_MAC_SIZE]); + hasher.0.finish(&mut output); + challenge.challenge_mac.copy_from_slice(&output[..CHALLENGE_MAC_SIZE]); challenge.prior_challenge_pow = noise_pattern1.challenge_pow; // We haven't decrypted any of Alice's packet so we don't know the // header protection cipher. @@ -924,8 +935,8 @@ impl Context { &mut challenge_buffer, PACKET_TYPE_BOB_DOS_CHALLENGE, None, - random::next_u64_secure(), - None, + self.0.rng.lock().unwrap().next_u64(), + None::<&Application::BlockCipherEnc>, ); return Ok(ReceiveResult::Unassociated); } @@ -934,25 +945,25 @@ impl Context { IncomingSessionAction::Drop => return Ok(ReceiveResult::Rejected), } - let local_s_keypair = app.local_s_keypair(); // Noise process handshake prologue. let noise_h = mix_hash( sha512, &INITIAL_H, &message[NoiseXKPattern1::PROLOGUE_START..NoiseXKPattern1::PROLOGUE_END], ); - let noise_h = mix_hash(sha512, &noise_h, local_s_keypair.public_key_bytes()); + let noise_h = mix_hash(sha512, &noise_h, self.0.static_keypair.public_key_bytes()); // Noise process pattern1 e token. let mut noise_ck = SymmetricState::new(INITIAL_H); - let (noise_e_pattern1, noise_es) = from_bytes_agreement(&noise_pattern1.noise_e, &local_s_keypair) + let mut noise_es = Secret::new(); + let noise_e_pattern1 = from_bytes_agreement::(&noise_pattern1.noise_e, &self.0.static_keypair, noise_es.as_mut()) .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; let noise_h_e = mix_hash(sha512, &noise_h, &noise_pattern1.noise_e); noise_ck.mix_key(&noise_pattern1.noise_e); // Noise process pattern1 es token. - let noise_k_es = noise_ck.mix_key_initialize_key(noise_es.as_bytes()); + let noise_k_es = noise_ck.mix_key_initialize_key(noise_es.as_ref()); drop(noise_es); // Noise process pattern1 e1 token. - let (is_auth, noise_h_ee1) = decrypt_and_hash( + let (is_auth, noise_h_ee1) = decrypt_and_hash::( sha512, &noise_k_es, &noise_h_e, @@ -970,7 +981,7 @@ impl Context { return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); } // Noise process pattern1 payload. - let (is_auth, noise_h_ee1p) = decrypt_and_hash( + let (is_auth, noise_h_ee1p) = decrypt_and_hash::( sha512, &noise_k_es, &noise_h_ee1, @@ -1005,23 +1016,25 @@ impl Context { let mut message2 = [0u8; NoiseXKPattern2::SIZE]; let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); // Noise process pattern2 e token. - let noise_e_pattern2_secret = P384KeyPair::generate(); + let noise_e_pattern2_secret = Application::KeyPair::generate(); noise_pattern2.noise_e = noise_e_pattern2_secret.public_key_bytes().clone(); let noise_h_ee1pe = mix_hash(sha512, &noise_h_ee1p, &noise_pattern2.noise_e); noise_ck.mix_key(&noise_pattern2.noise_e); // Noise process pattern2 ee token. - let noise_ee = noise_e_pattern2_secret - .agree(&noise_e_pattern1) - .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; - let noise_k_esee = noise_ck.mix_key_initialize_key(noise_ee.as_bytes()); + let mut noise_ee = Secret::new(); + if !noise_e_pattern2_secret + .agree(&noise_e_pattern1, noise_ee.as_mut()) { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + let noise_k_esee = noise_ck.mix_key_initialize_key(noise_ee.as_ref()); drop(noise_ee); // Noise process pattern2 ekem1 token. - let (noise_ekem1, noise_ekem1_secret) = pqc_kyber::encapsulate(&noise_pattern1.noise_e1, &mut random::SecureRandom::default()) + let (noise_ekem1, noise_ekem1_secret) = pqc_kyber::encapsulate(&noise_pattern1.noise_e1, self.0.rng.lock().unwrap().deref_mut()) .map_err(|_| byzantine_fault!(FaultType::FailedAuthentication, false)) - .map(|(ct, ekem1)| (ct, Secret(ekem1)))?; + .map(|(ct, ekem1)| (ct, Secret::move_bytes(ekem1)))?; // Alice fully authenticated. noise_pattern2.noise_ekem1 = noise_ekem1; - let noise_h_ee1peekem1 = encrypt_and_hash( + let noise_h_ee1peekem1 = encrypt_and_hash::( sha512, &noise_k_esee, &noise_h_ee1pe, @@ -1030,7 +1043,7 @@ impl Context { &mut message2[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], ); drop(noise_k_esee); - noise_ck.mix_key(noise_ekem1_secret.as_bytes()); + noise_ck.mix_key(noise_ekem1_secret.as_ref()); drop(noise_ekem1_secret); // Noise process pattern2 psk token. let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(&ratchet_key); @@ -1039,11 +1052,11 @@ impl Context { // We try to prevent the id we generate from colliding with another session but // because we might have handshakes in flight it's impossible to 100% prevent. // In those exceedingly rare cases we have to drop Alice's session and start over. - let local_key_id = generate_key_id(&self.0.session_map.read().unwrap()); + let local_key_id = generate_key_id(&self.0.session_map.read().unwrap(), &mut self.0.rng.lock().unwrap()); let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); noise_pattern2.bob_key_id = local_key_id.get().to_ne_bytes(); - let noise_h_ee1peekem1pskp = encrypt_and_hash( + let noise_h_ee1peekem1pskp = encrypt_and_hash::( sha512, &noise_k_eseeekem1psk, &noise_h_ee1peekem1psk, @@ -1081,7 +1094,7 @@ impl Context { PACKET_TYPE_NOISE_XK_PATTERN_2, Some(remote_key_id), u64::from_be_bytes(pattern2_id), - Some(&Aes::new(header_b2a_key.first_n::())), + Some(&Application::BlockCipherEnc::new(header_b2a_key.first_n::())), ); return Ok(ReceiveResult::Unassociated); @@ -1116,8 +1129,8 @@ impl Context { } pattern1.challenge_counter.copy_from_slice(&challenge.challenge_counter); pattern1.challenge_mac.copy_from_slice(&challenge.challenge_mac); - let mut pow = random::next_u64_secure(); - let sha512 = &mut SHA512::new(); + let mut pow = self.0.rng.lock().unwrap().next_u64(); + let sha512 = &mut Application::Hash::new(); loop { let pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(noise_message); pattern1.challenge_pow.copy_from_slice(&pow.to_be_bytes()); @@ -1174,16 +1187,17 @@ impl Context { } // Noise process pattern2 e token. - if let Some((noise_e_pattern2, noise_ee)) = from_bytes_agreement(&noise_pattern2.noise_e, &noise_e_secret) { - let sha512 = &mut SHA512::new(); + let mut noise_ee = Secret::new(); + if let Some(noise_e_pattern2) = from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) { + let sha512 = &mut Application::Hash::new(); let mut noise_ck = noise_ck_es.clone(); let noise_h_ee1pe = mix_hash(sha512, noise_h_ee1p, noise_e_pattern2.as_bytes()); noise_ck.mix_key(noise_e_pattern2.as_bytes()); // Noise process pattern2 ee token. - let noise_k_esee = noise_ck.mix_key_initialize_key(noise_ee.as_bytes()); + let noise_k_esee = noise_ck.mix_key_initialize_key(noise_ee.as_ref()); drop(noise_ee); // Noise process pattern2 ekem1 token. - let (is_auth, noise_h_ee1peekem1) = decrypt_and_hash( + let (is_auth, noise_h_ee1peekem1) = decrypt_and_hash::( sha512, &noise_k_esee, &noise_h_ee1pe, @@ -1193,15 +1207,15 @@ impl Context { ); let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); let noise_ekem1_secret = - pqc_kyber::decapsulate(&noise_pattern2.noise_ekem1, noise_e1_secret.as_bytes()).map(|k| Secret(k)); + pqc_kyber::decapsulate(&noise_pattern2.noise_ekem1, noise_e1_secret.as_ref()).map(|k| Secret(k)); if let Some(Ok(noise_ekem1_secret)) = is_auth.then_some(noise_ekem1_secret) { - noise_ck.mix_key(noise_ekem1_secret.as_bytes()); + noise_ck.mix_key(noise_ekem1_secret.as_ref()); drop(noise_ekem1_secret); // We attempt to decrypt the payload at most twice. First time with // the ratchet key Alice last remembers, and second time with a ratchet // key of zero if Alice allows ratchet downgrades. let mut ratchet_number = state.ratchet_number; - let mut ratchet_key = state.ratchet_key.as_bytes(); + let mut ratchet_key = state.ratchet_key.as_ref(); let mut ratchet_result = None; for i in 0..2 { // Constant time ratchet key downgrade check. @@ -1213,7 +1227,7 @@ impl Context { let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); // Noise process pattern2 payload. let (is_auth, noise_h_ratchet) = - decrypt_and_hash(sha512, &noise_k_ratchet, &noise_h_ee1peekem1psk, packet_type, 0, &mut payload); + decrypt_and_hash::(sha512, &noise_k_ratchet, &noise_h_ee1peekem1psk, packet_type, 0, &mut payload); let mut key_id = 0u32.to_ne_bytes(); key_id.copy_from_slice(&payload[..NoiseXKPattern2::P_AUTH_START - NoiseXKPattern2::P_ENC_START]); @@ -1239,8 +1253,8 @@ impl Context { // Start of Noise XKhfs+psk2 pattern3. let mut message3 = [0u8; NoiseXKPattern3::MAX_SIZE]; // Noise process pattern3 s token. - let alice_s_keypair = app.local_s_keypair(); - if let Some(noise_se) = alice_s_keypair.agree(&noise_e_pattern2) { + let mut noise_se = Secret::new(); + if self.0.static_keypair.agree(&noise_e_pattern2, noise_se.as_mut()) { let payload = handshake_state.alice_identity_blob.as_ref(); // Packet fully authenticated. let s_enc_start = HEADER_SIZE; @@ -1250,8 +1264,8 @@ impl Context { let p_auth_end = p_auth_start + AES_GCM_TAG_SIZE; let message3_len = p_auth_end; - message3[s_enc_start..s_auth_start].copy_from_slice(alice_s_keypair.public_key_bytes()); - let noise_h_ee1peekem1pskps = encrypt_and_hash( + message3[s_enc_start..s_auth_start].copy_from_slice(self.0.static_keypair.public_key_bytes()); + let noise_h_ee1peekem1pskps = encrypt_and_hash::( sha512, &noise_k_eseeekem1psk, &noise_h_ee1peekem1pskp, @@ -1261,11 +1275,11 @@ impl Context { ); drop(noise_k_eseeekem1psk); // Noise process pattern3 se token. - let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(noise_se.as_bytes()); + let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(noise_se.as_ref()); drop(noise_se); // Noise process pattern3 payload token. message3[p_enc_start..p_auth_start].copy_from_slice(payload); - let noise_h_ee1peekem1pskpsp = encrypt_and_hash( + let noise_h_ee1peekem1pskpsp = encrypt_and_hash::( sha512, &noise_k_eseeekem1pskse, &noise_h_ee1peekem1pskps, @@ -1284,8 +1298,8 @@ impl Context { &session.application_data, SaveRatchetAction::SaveAsUnconfirmed, new_ratchet_number, - new_ratchet_fingerprint.as_bytes(), - new_ratchet_key.as_bytes(), + new_ratchet_fingerprint.as_ref(), + new_ratchet_key.as_ref(), current_time, ); if result.is_err() { @@ -1296,10 +1310,10 @@ impl Context { let local_key_id = handshake_state.local_key_id; drop(state); let mut state = session.state.write().unwrap(); - session.kex_receive_cipher.lock().unwrap().replace(AesGcm::new(kex_key_b2a.as_bytes())); - session.kex_send_cipher.lock().unwrap().replace(AesGcm::new(kex_key_a2b.as_bytes())); + session.kex_send_cipher.lock().unwrap().replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); + session.kex_receive_cipher.lock().unwrap().replace(Application::AeadDec::new(kex_key_b2a.as_ref())); - state.cipher_states[0].replace(SessionKey::new::( + state.cipher_states[0].replace(SessionKey::new( noise_ck, local_key_id, remote_key_id, @@ -1346,7 +1360,7 @@ impl Context { let mut state = session.state.write().unwrap(); let ratchet_fingerprint = state.ratchet_fingerprint.clone(); if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if !handshake_state.reinitialize(&session, &ratchet_fingerprint, &mut self.0.session_map.write().unwrap(), current_time) { + if !handshake_state.reinitialize(&session, &ratchet_fingerprint, &mut self.0.session_map.write().unwrap(), &mut self.0.rng.lock().unwrap(), current_time) { session.expire() } } @@ -1387,8 +1401,8 @@ impl Context { // Do not read from the message before this point, otherwise an array out of bounds // error is possible. // Noise process pattern3 s token. - let sha512 = &mut SHA512::new(); - let (is_auth, noise_h_ee1peekem1pskps) = decrypt_and_hash( + let sha512 = &mut Application::Hash::new(); + let (is_auth, noise_h_ee1peekem1pskps) = decrypt_and_hash::( sha512, &handshake_state.noise_k_eseeekem1psk, &handshake_state.noise_h_ee1peekem1pskp, @@ -1400,14 +1414,15 @@ impl Context { return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); } // Noise process pattern3 se token. - if let Some((remote_s_public_key, noise_se)) = - from_bytes_agreement(&message[s_enc_start..s_auth_start], &handshake_state.noise_e_secret) + let mut noise_se = Secret::new(); + if let Some(remote_s_public_key) = + from_bytes_agreement(&message[s_enc_start..s_auth_start], &handshake_state.noise_e_secret, noise_se.as_mut()) { let mut noise_ck = handshake_state.noise_ck_eseeekem1psk.clone(); - let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(noise_se.as_bytes()); + let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(noise_se.as_ref()); drop(noise_se); // Noise process pattern3 payload. - let (is_auth, noise_h_ee1peekem1pskpsp) = decrypt_and_hash( + let (is_auth, noise_h_ee1peekem1pskpsp) = decrypt_and_hash::( sha512, &noise_k_eseeekem1pskse, &noise_h_ee1peekem1pskps, @@ -1420,7 +1435,7 @@ impl Context { return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); } // Bob finished Noise XKhfs+psk2 handshake. - let header_send_cipher = Aes::new(handshake_state.header_send_key.as_bytes()); + let header_send_cipher = Application::BlockCipherEnc::new(handshake_state.header_send_key.as_ref()); let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); match check_accept_session( &remote_s_public_key, @@ -1428,14 +1443,15 @@ impl Context { handshake_state.ratchet_fingerprint.as_ref(), ) { AcceptSessionAction::Accept(application_data) => { - let bob_s_keypair = app.local_s_keypair(); - let noise_kk_ss = bob_s_keypair - .agree(&remote_s_public_key) - .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; - let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, bob_s_keypair.public_key_bytes()); + let mut noise_kk_ss = Secret::new(); + if !self.0.static_keypair + .agree(&remote_s_public_key, noise_kk_ss.as_mut()) { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, bob_s_keypair.public_key_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); // We must make sure the ratchet key is saved before we transition. @@ -1445,8 +1461,8 @@ impl Context { &application_data, SaveRatchetAction::SaveAsConfirmed, new_ratchet_number, - new_ratchet_fingerprint.as_bytes(), - new_ratchet_key.as_bytes(), + new_ratchet_fingerprint.as_ref(), + new_ratchet_key.as_ref(), current_time, ); if result.is_err() { @@ -1469,7 +1485,7 @@ impl Context { ratchet_fingerprint: new_ratchet_fingerprint.clone(), ratchet_key: new_ratchet_key.clone(), cipher_states: [ - Some(SessionKey::new::( + Some(SessionKey::new( noise_ck, handshake_state.local_key_id, handshake_state.remote_key_id, @@ -1484,10 +1500,10 @@ impl Context { timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), }, }), - header_receive_cipher: Aes::new(handshake_state.header_receive_key.as_bytes()), + header_receive_cipher: Application::BlockCipherDec::new(handshake_state.header_receive_key.as_ref()), header_send_cipher, - kex_receive_cipher: Mutex::new(Some(AesGcm::new(kex_key_a2b.as_bytes()))), - kex_send_cipher: Mutex::new(Some(AesGcm::new(kex_key_b2a.as_bytes()))), + kex_send_cipher: Mutex::new(Some(Application::AeadEnc::new(kex_key_b2a.as_ref()))), + kex_receive_cipher: Mutex::new(Some(Application::AeadDec::new(kex_key_a2b.as_ref()))), noise_kk_ss, noise_kk_local_init_h, noise_kk_remote_init_h, @@ -1521,7 +1537,7 @@ impl Context { // the fact we used it in memory. This is currently ok because the // handshake is being dropped, so nonce reuse can't happen. let (mut fragment, len) = encrypt_control( - &mut AesGcm::new(kex_key_b2a.as_bytes()), + &mut Application::AeadEnc::new(kex_key_b2a.as_ref()), &header_send_cipher, PACKET_TYPE_SESSION_REJECTED, INIT_COUNTER, @@ -1575,7 +1591,7 @@ impl Context { let counter = session.get_next_outgoing_counter()?; let mut c = key.get_send_cipher(counter)?; - c.reset_init_gcm(&create_message_nonce(PACKET_TYPE_DATA, counter)); + c.set_iv(&create_message_nonce(PACKET_TYPE_DATA, counter)); let fragment_max_chunk_size = mtu_sized_buffer.len() - HEADER_SIZE; let fragment_count = (data.len() + AES_GCM_TAG_SIZE + (fragment_max_chunk_size - 1)) / fragment_max_chunk_size; @@ -1597,19 +1613,19 @@ impl Context { counter, ); - c.crypt(&data[..chunk_size], &mut mtu_sized_buffer[HEADER_SIZE..fragment_size]); + c.encrypt(&data[..chunk_size], &mut mtu_sized_buffer[HEADER_SIZE..fragment_size]); data = &data[chunk_size..]; if fragment_no == last_fragment_no { debug_assert!(data.is_empty()); let tagged_fragment_size = fragment_size + AES_GCM_TAG_SIZE; - mtu_sized_buffer[fragment_size..tagged_fragment_size].copy_from_slice(&c.finish_encrypt()); + c.finish_encrypt((&mut mtu_sized_buffer[fragment_size..tagged_fragment_size]).try_into().unwrap()); fragment_size = tagged_fragment_size; } session .header_send_cipher - .crypt_block_in_place(&mut mtu_sized_buffer[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]); + .encrypt_in_place((&mut mtu_sized_buffer[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); if !send(&mut mtu_sized_buffer[..fragment_size]) { break; } @@ -1659,35 +1675,38 @@ fn initiate_rekey( OfferStateMachine::Normal { .. } => (), _ => return Err(()), } - let sha512 = &mut SHA512::new(); + let sha512 = &mut Application::Hash::new(); // Start of Noise KKpsk0 pattern1. // Noise process pattern1 psk0 token. let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(state.ratchet_key.as_bytes()); + let noise_temp_h = noise_ck.mix_key_and_hash(state.ratchet_key.as_ref()); let noise_h_psk = mix_hash(sha512, &session.noise_kk_local_init_h, &noise_temp_h); // Noise process pattern1 e token. - let noise_e_secret = P384KeyPair::generate(); + let noise_e_secret = Application::KeyPair::generate(); let noise_h_pske = mix_hash(sha512, &noise_h_psk, noise_e_secret.public_key_bytes()); noise_ck.mix_key(noise_e_secret.public_key_bytes()); let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); // Noise process pattern1 es token. - let noise_es = noise_e_secret.agree(&session.remote_s_public_key).ok_or(())?; - noise_ck.mix_key(noise_es.as_bytes()); + let mut noise_es = Secret::new(); + if !noise_e_secret.agree(&session.remote_s_public_key, noise_es.as_mut()) { + return Err(()); + } + noise_ck.mix_key(noise_es.as_ref()); drop(noise_es); // Noise process pattern1 ss token. - let noise_k_pskesss = noise_ck.mix_key_initialize_key(session.noise_kk_ss.as_bytes()); + let noise_k_pskesss = noise_ck.mix_key_initialize_key(session.noise_kk_ss.as_ref()); // Noise process pattern1 payload token. let mut session_map = context.session_map.write().unwrap(); - let new_key_id = generate_key_id(&session_map); + let new_key_id = generate_key_id(&session_map, &mut context.rng.lock().unwrap()); let next_key_index = state.current_key ^ 1; session_map.insert(new_key_id, (Arc::downgrade(session), next_key_index > 0)); drop(session_map); let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); noise_pattern1.key_id = new_key_id.get().to_ne_bytes(); - let noise_h_pskep = encrypt_and_hash( + let noise_h_pskep = encrypt_and_hash::( sha512, &noise_k_pskesss, &noise_h_pske, @@ -1779,19 +1798,19 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu &session.application_data, SaveRatchetAction::ConfirmLatestAndDeletePrevious, ratchet.0, - ratchet.1.as_bytes(), - ratchet.2.as_bytes(), + ratchet.1.as_ref(), + ratchet.2.as_ref(), current_time, ); if result.is_ok() { if let NoiseKKPattern2 { kex_send_key, .. } = &state.outgoing_offer { - session.kex_send_cipher.lock().unwrap().replace(AesGcm::new(kex_send_key.as_bytes())); + session.kex_send_cipher.lock().unwrap().replace(Application::AeadEnc::new(kex_send_key.as_ref())); } state.ratchet_number = ratchet.0; state.ratchet_fingerprint.overwrite(&ratchet.1); state.ratchet_key.overwrite(&ratchet.2); state.current_key ^= 1; - state.outgoing_offer = new_normal_state(current_time); + state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); } else { return Err(ReceiveError::RatchetIoError); } @@ -1819,12 +1838,12 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu &session.application_data, SaveRatchetAction::DeletePrevious, state.ratchet_number, - state.ratchet_fingerprint.as_bytes(), - state.ratchet_key.as_bytes(), + state.ratchet_fingerprint.as_ref(), + state.ratchet_key.as_ref(), current_time, ); if result.is_ok() { - state.outgoing_offer = new_normal_state(current_time); + state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); } else { return Err(ReceiveError::RatchetIoError); } @@ -1862,24 +1881,28 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu return Ok(ReceiveResult::Session(session, SessionEvent::Control)); } // Noise process pattern1 psk0 token. - let sha512 = &mut SHA512::new(); + let sha512 = &mut Application::Hash::new(); let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(state.ratchet_key.as_bytes()); + let noise_temp_h = noise_ck.mix_key_and_hash(state.ratchet_key.as_ref()); let noise_h_psk = mix_hash(sha512, &session.noise_kk_remote_init_h, &noise_temp_h); // Noise process pattern1 e token. - if let Some((alice_e, noise_es)) = from_bytes_agreement(&noise_pattern1.noise_e, &app.local_s_keypair()) { - let bob_e_secret = P384KeyPair::generate(); - if let (Some(noise_ee), Some(noise_se)) = (bob_e_secret.agree(&alice_e), bob_e_secret.agree(&session.remote_s_public_key)) { + // Get public key validation out of the way early + let mut noise_es = Secret::new(); + let mut noise_ee = Secret::new(); + let mut noise_se = Secret::new(); + if let Some(alice_e) = from_bytes_agreement::(&noise_pattern1.noise_e, &context.0.static_keypair, noise_es.as_mut()) { + let bob_e_secret = Application::KeyPair::generate(); + if bob_e_secret.agree(&alice_e, noise_ee.as_mut()) && bob_e_secret.agree(&session.remote_s_public_key, noise_se.as_mut()) { let noise_h_pske = mix_hash(sha512, &noise_h_psk, alice_e.as_bytes()); noise_ck.mix_key(alice_e.as_bytes()); // Noise process pattern1 es token. - noise_ck.mix_key(noise_es.as_bytes()); + noise_ck.mix_key(noise_es.as_ref()); drop(noise_es); // Noise process pattern1 ss token. - let noise_k_pskesss = noise_ck.mix_key_initialize_key(session.noise_kk_ss.as_bytes()); + let noise_k_pskesss = noise_ck.mix_key_initialize_key(session.noise_kk_ss.as_ref()); // Noise process pattern1 payload. - let (is_auth, noise_h_pskep) = decrypt_and_hash( + let (is_auth, noise_h_pskep) = decrypt_and_hash::( sha512, &noise_k_pskesss, &noise_h_pske, @@ -1895,10 +1918,10 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let noise_h_pskepe = mix_hash(sha512, &noise_h_pskep, bob_e_secret.public_key_bytes()); noise_ck.mix_key(bob_e_secret.public_key_bytes()); // Noise process pattern2 ee token. - noise_ck.mix_key(noise_ee.as_bytes()); + noise_ck.mix_key(noise_ee.as_ref()); drop(noise_ee); // Noise process pattern2 se token. - let noise_k_pskessseese = noise_ck.mix_key_initialize_key(noise_se.as_bytes()); + let noise_k_pskessseese = noise_ck.mix_key_initialize_key(noise_se.as_ref()); drop(noise_se); // Noise process pattern2 payload. let mut message2 = [0u8; NoiseKKPattern1or2::SIZE]; @@ -1906,10 +1929,10 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu noise_pattern2.noise_e = *bob_e_secret.public_key_bytes(); let mut session_map = context.0.session_map.write().unwrap(); // If we already generated a new key id mapping reuse it. - let new_key_id = chosen_id.unwrap_or_else(|| generate_key_id(&session_map)); + let new_key_id = chosen_id.unwrap_or_else(|| generate_key_id(&session_map, &mut context.0.rng.lock().unwrap())); noise_pattern2.key_id = new_key_id.get().to_ne_bytes(); - let noise_h_pskepep = encrypt_and_hash( + let noise_h_pskepep = encrypt_and_hash::( sha512, &noise_k_pskessseese, &noise_h_pskepe, @@ -1926,8 +1949,8 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu &session.application_data, SaveRatchetAction::SaveAsUnconfirmed, new_ratchet_number, - new_ratchet_fingerprint.as_bytes(), - new_ratchet_key.as_bytes(), + new_ratchet_fingerprint.as_ref(), + new_ratchet_key.as_ref(), current_time, ); if result.is_err() { @@ -1948,9 +1971,9 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu drop(state); let mut state = session.state.write().unwrap(); let current_counter = session.send_counter.load(Ordering::Relaxed); - session.kex_receive_cipher.lock().unwrap().replace(AesGcm::new(kex_key_a2b.as_bytes())); + session.kex_receive_cipher.lock().unwrap().replace(Application::AeadDec::new(kex_key_a2b.as_ref())); - state.cipher_states[next_key_index].replace(SessionKey::new::( + state.cipher_states[next_key_index].replace(SessionKey::new( noise_ck, new_key_id, remote_key_id, @@ -1991,20 +2014,22 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let state = session.state.read().unwrap(); if let NoiseKKPattern1 { new_key_id, noise_e_secret, noise_ck, noise_h_pskep, .. } = &state.outgoing_offer { // Noise process pattern2 e token. - if let Some((bob_e, noise_ee)) = from_bytes_agreement(&noise_pattern2.noise_e, &noise_e_secret) { - if let Some(noise_se) = app.local_s_keypair().agree(&bob_e) { - let sha512 = &mut SHA512::new(); + let mut noise_ee = Secret::new(); + let mut noise_se = Secret::new(); + if let Some(bob_e) = from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) { + if context.0.static_keypair.agree(&bob_e, noise_se.as_mut()) { + let sha512 = &mut Application::Hash::new(); let mut noise_ck = noise_ck.clone(); let noise_h_pskepe = mix_hash(sha512, noise_h_pskep, bob_e.as_bytes()); noise_ck.mix_key(bob_e.as_bytes()); // Noise process pattern2 ee token. - noise_ck.mix_key(noise_ee.as_bytes()); + noise_ck.mix_key(noise_ee.as_ref()); drop(noise_ee); // Noise process pattern2 se token. - let noise_k_pskessseese = noise_ck.mix_key_initialize_key(noise_se.as_bytes()); + let noise_k_pskessseese = noise_ck.mix_key_initialize_key(noise_se.as_ref()); drop(noise_se); // Noise process pattern2 payload. - let (is_auth, noise_h_pskepep) = decrypt_and_hash( + let (is_auth, noise_h_pskepep) = decrypt_and_hash::( sha512, &noise_k_pskessseese, &noise_h_pskepe, @@ -2024,8 +2049,8 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu &session.application_data, SaveRatchetAction::SaveAsConfirmed, new_ratchet_number, - new_ratchet_fingerprint.as_bytes(), - new_ratchet_key.as_bytes(), + new_ratchet_fingerprint.as_ref(), + new_ratchet_key.as_ref(), current_time, ); if result.is_err() { @@ -2043,14 +2068,14 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu if let Some(key) = state.cipher_states[next_key_index].as_ref() { context.0.session_map.write().unwrap().remove(&key.local_key_id); } - session.kex_receive_cipher.lock().unwrap().replace(AesGcm::new(kex_key_b2a.as_bytes())); - session.kex_send_cipher.lock().unwrap().replace(AesGcm::new(kex_key_a2b.as_bytes())); + session.kex_receive_cipher.lock().unwrap().replace(Application::AeadDec::new(kex_key_b2a.as_ref())); + session.kex_send_cipher.lock().unwrap().replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); state.ratchet_number = new_ratchet_number; state.ratchet_fingerprint.overwrite_first_n(&new_ratchet_fingerprint); state.ratchet_key.overwrite(&new_ratchet_key); - state.cipher_states[next_key_index].replace(SessionKey::new::( + state.cipher_states[next_key_index].replace(SessionKey::new( noise_ck, new_key_id, remote_key_id, @@ -2123,7 +2148,7 @@ impl Session { } /// The static public key of the remote peer. #[inline] - pub fn remote_s_public_key(&self) -> &P384PublicKey { + pub fn remote_s_public_key(&self) -> &Application::PublicKey { &self.remote_s_public_key } /// The most recent confirmed ratchet state of this session. @@ -2131,7 +2156,7 @@ impl Session { #[inline] pub fn ratchet_state(&self) -> (u64, [u8; RATCHET_FINGERPRINT_SIZE], [u8; RATCHET_KEY_SIZE]) { let state = self.state.read().unwrap(); - (state.ratchet_number, *state.ratchet_fingerprint.as_bytes(), *state.ratchet_key.as_bytes()) + (state.ratchet_number, *state.ratchet_fingerprint.as_ref(), *state.ratchet_key.as_ref()) } /// The most recent confirmed ratchet number of this session. #[inline] @@ -2219,15 +2244,16 @@ impl NoiseXKAliceHandshake { #[inline] fn initialize( local_key_id: NonZeroU32, - remote_s_public_key: &P384PublicKey, + remote_s_public_key: &Application::PublicKey, ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], - ) -> Result<(NoiseXKAliceHandshakeState, Secret, Secret), OpenError> { + rng: &mut Application::Rng, + ) -> Result<(NoiseXKAliceHandshakeState, Secret, Secret), OpenError> { let mut message = [0u8; NoiseXKPattern1::SIZE]; - let sha512 = &mut SHA512::new(); + let sha512 = &mut Application::Hash::new(); // Start of Noise XKhfs+psk2 pattern1. let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); - let noise_e_secret = P384KeyPair::generate(); - let noise_e1_secret = pqc_kyber::keypair(&mut random::SecureRandom::default()); + let noise_e_secret = Application::KeyPair::generate(); + let noise_e1_secret = pqc_kyber::keypair(rng); noise_pattern1.alice_key_id = local_key_id.get().to_ne_bytes(); noise_pattern1.noise_e = noise_e_secret.public_key_bytes().clone(); noise_pattern1.noise_e1 = noise_e1_secret.public; @@ -2244,11 +2270,14 @@ impl NoiseXKAliceHandshake { let noise_h_e = mix_hash(sha512, &noise_h, noise_e_secret.public_key_bytes()); noise_ck.mix_key(noise_e_secret.public_key_bytes()); // Noise process pattern1 es token. - let noise_es = noise_e_secret.agree(remote_s_public_key).ok_or(OpenError::InvalidPublicKey)?; - let noise_k_es = noise_ck.mix_key_initialize_key(noise_es.as_bytes()); + let mut noise_es = Secret::new(); + if !noise_e_secret.agree(remote_s_public_key, noise_es.as_mut()) { + return Err(OpenError::InvalidPublicKey); + } + let noise_k_es = noise_ck.mix_key_initialize_key(noise_es.as_ref()); drop(noise_es); // Noise process pattern1 e1 token. - let noise_h_ee1 = encrypt_and_hash( + let noise_h_ee1 = encrypt_and_hash::( sha512, &noise_k_es, &noise_h_e, @@ -2257,7 +2286,7 @@ impl NoiseXKAliceHandshake { &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], ); // Noise process pattern1 payload. - let noise_h_ee1p = encrypt_and_hash( + let noise_h_ee1p = encrypt_and_hash::( sha512, &noise_k_es, &noise_h_ee1, @@ -2288,19 +2317,20 @@ impl NoiseXKAliceHandshake { session: &Arc>, ratchet_fingerprint: &Secret, session_map: &mut HashMap>, bool)>, + rng: &mut Application::Rng, current_time: i64, ) -> bool { - let local_key_id = generate_key_id(session_map); + let local_key_id = generate_key_id(session_map, rng); if let Ok((offer, a2b_header_key, b2a_header_key)) = - Self::initialize(local_key_id, &session.remote_s_public_key, ratchet_fingerprint.as_bytes()) + Self::initialize(local_key_id, &session.remote_s_public_key, ratchet_fingerprint.as_ref(), rng) { self.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); session_map.remove(&self.local_key_id); session_map.insert(local_key_id, (Arc::downgrade(session), false)); self.local_key_id = local_key_id; self.offer = offer; - session.header_send_cipher.reset(a2b_header_key.as_bytes()); - session.header_receive_cipher.reset(b2a_header_key.as_bytes()); + session.header_send_cipher.reset(a2b_header_key.as_ref()); + session.header_receive_cipher.reset(b2a_header_key.as_ref()); true } else { false @@ -2309,11 +2339,11 @@ impl NoiseXKAliceHandshake { } /// Create the normal state of the offer state machine, with the correct timestamps. -fn new_normal_state(current_time: i64) -> OfferStateMachine { +fn new_normal_state(rand: u64, current_time: i64) -> OfferStateMachine { OfferStateMachine::Normal { timeout: current_time .saturating_add(Application::REKEY_AFTER_TIME_MS) - .saturating_sub(random::next_u32_secure() as i64 % Application::REKEY_AFTER_TIME_MAX_JITTER_MS), + .saturating_sub(rand as i64 % Application::REKEY_AFTER_TIME_MAX_JITTER_MS), } } /// Get a timestamp of when this timer should trigger next, or None if it should trigger now. @@ -2328,30 +2358,30 @@ fn process_timer(timer: &AtomicI64, wait_time: i64, current_time: i64) -> Option /// Corresponds to Noise `EncryptAndHash`. #[inline] -fn encrypt_and_hash( - sha512: &mut SHA512, - noise_k: &Secret, +fn encrypt_and_hash( + sha512: &mut Application::Hash, + noise_k: &Secret, noise_h: &[u8; NOISE_HASHLEN], packet_type: u8, noise_k_uses: u64, message: &mut [u8], ) -> [u8; NOISE_HASHLEN] { let auth_start = message.len() - AES_GCM_TAG_SIZE; - let mut gcm = AesGcm::new(noise_k.as_bytes()); + let mut gcm = Application::AeadEnc::new(noise_k.as_ref()); // Encrypt and add authentication tag. - gcm.reset_init_gcm(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); - gcm.aad(noise_h); + gcm.set_iv(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); + gcm.set_aad(noise_h); if auth_start > 0 { - gcm.crypt_in_place(&mut message[..auth_start]); + gcm.encrypt_in_place(&mut message[..auth_start]); } - message[auth_start..].copy_from_slice(&gcm.finish_encrypt()); + gcm.finish_encrypt((&mut message[auth_start..]).try_into().unwrap()); mix_hash(sha512, noise_h, message) } /// Corresponds to Noise `DecryptAndHash`. #[inline] -fn decrypt_and_hash( - sha512: &mut SHA512, - noise_k: &Secret, +fn decrypt_and_hash( + sha512: &mut Application::Hash, + noise_k: &Secret, noise_h: &[u8; NOISE_HASHLEN], packet_type: u8, noise_k_uses: u64, @@ -2359,19 +2389,19 @@ fn decrypt_and_hash( ) -> (bool, [u8; NOISE_HASHLEN]) { let auth_start = message.len() - AES_GCM_TAG_SIZE; let noise_h_c = mix_hash(sha512, noise_h, message); - let mut gcm = AesGcm::new(noise_k.as_bytes()); - gcm.reset_init_gcm(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); - gcm.aad(noise_h); + let mut gcm = Application::AeadDec::new(noise_k.as_ref()); + gcm.set_iv(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); + gcm.set_aad(noise_h); if auth_start > 0 { - gcm.crypt_in_place(&mut message[..auth_start]); + gcm.decrypt_in_place(&mut message[..auth_start]); } - (gcm.finish_decrypt(&message[auth_start..]), noise_h_c) + (gcm.finish_decrypt((&message[auth_start..]).try_into().unwrap()), noise_h_c) } /// Encrypt a standardized control packet. #[inline] fn encrypt_control( - c: &mut AesGcm, - header_cipher: &Aes, + c: &mut impl AesGcmEnc, + header_cipher: &impl AesEnc, packet_type: u8, counter: u64, remote_key_id: u32, @@ -2380,26 +2410,26 @@ fn encrypt_control( let mut fragment = [0u8; CONTROL_PACKET_MAX_SIZE]; let fragment_len = packet.len() + HEADER_SIZE + AES_GCM_TAG_SIZE; - c.reset_init_gcm(&create_message_nonce(packet_type, counter)); + c.set_iv(&create_message_nonce(packet_type, counter)); if packet.len() > 0 { fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE].copy_from_slice(packet); - c.crypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); + c.encrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); } - fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len].copy_from_slice(&c.finish_encrypt()); + c.finish_encrypt((&mut fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]).try_into().unwrap()); drop(c); set_packet_header(&mut fragment, 1, 0, packet_type, remote_key_id, counter); - header_cipher.crypt_block_in_place(&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]); + header_cipher.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); (fragment, fragment_len) } #[inline] -fn decrypt_control<'a>(c: &mut AesGcm, packet_type: u8, counter: u64, fragment: &'a mut [u8]) -> Result<&'a mut [u8], ReceiveError> { +fn decrypt_control<'a>(c: &mut impl AesGcmDec, packet_type: u8, counter: u64, fragment: &'a mut [u8]) -> Result<&'a mut [u8], ReceiveError> { let fragment_len = fragment.len(); if fragment_len < CONTROL_PACKET_MIN_SIZE || fragment_len > CONTROL_PACKET_MAX_SIZE { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } - c.reset_init_gcm(&create_message_nonce(packet_type, counter)); - c.crypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); - if !c.finish_decrypt(&fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]) { + c.set_iv(&create_message_nonce(packet_type, counter)); + c.decrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); + if !c.finish_decrypt((&fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]).try_into().unwrap()) { // This can occur naturally if one of the remote peers resent a // control packet that got delayed and arrived out of order. return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); @@ -2436,8 +2466,8 @@ fn set_packet_header(packet: &mut [u8], fragment_count: u8, fragment_no: u8, pac /// like fragmentation info, or their authentication is implied via key exchange like /// the key id. #[inline(always)] -fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_NONCE_SIZE] { - let mut ret = [0u8; AES_GCM_NONCE_SIZE]; +fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] { + let mut ret = [0u8; AES_GCM_IV_SIZE]; ret[3] = packet_type; // Noise requires a big endian counter at the end of the Nonce ret[4..].copy_from_slice(&counter.to_be_bytes()); @@ -2466,7 +2496,7 @@ fn send_with_fragmentation( packet_type: u8, remote_key_id: Option, counter_or_id: u64, - header_cipher: Option<&Aes>, + header_cipher: Option<&impl AesEnc>, ) -> bool { let packet_len = packet.len(); let fragment_count = (packet_len.saturating_add(mtu - 1)) / mtu; // integer ceiling divide @@ -2485,7 +2515,7 @@ fn send_with_fragmentation( counter_or_id, ); if let Some(hcc) = header_cipher { - hcc.crypt_block_in_place(&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]); + hcc.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); } if !send(fragment) { return false; @@ -2523,9 +2553,9 @@ fn assemble_fragments_into(fragments: &[A::IncomingPacketBu return Ok(l); } /// Generate a random local key id that is currently unused. -fn generate_key_id(session_map: &HashMap>, bool)>) -> NonZeroU32 { +fn generate_key_id(session_map: &HashMap>, bool)>, rng: &mut Application::Rng) -> NonZeroU32 { loop { - if let Some(local_key_id) = NonZeroU32::new(random::next_u32_secure()) { + if let Some(local_key_id) = NonZeroU32::new(rng.next_u32()) { if !session_map.contains_key(&local_key_id) { return local_key_id; } @@ -2533,10 +2563,10 @@ fn generate_key_id(session_map: &HashMap SessionKey { #[inline(always)] - fn new( - ck: SymmetricState, + fn new( + ck: SymmetricState, local_key_id: NonZeroU32, remote_key_id: NonZeroU32, current_counter: u64, @@ -2548,20 +2578,20 @@ impl SessionKey { } else { (&b2a, &a2b) }; - let receive_cipher_pool = std::array::from_fn(|_| Mutex::new(AesGcm::new(receive_key.as_bytes()))); - let send_cipher_pool = std::array::from_fn(|_| Mutex::new(AesGcm::new(send_key.as_bytes()))); + let send_cipher_pool = std::array::from_fn(|_| Mutex::new(Application::AeadEnc::new(send_key.as_ref()))); + let receive_cipher_pool = std::array::from_fn(|_| Mutex::new(Application::AeadDec::new(receive_key.as_ref()))); Self { local_key_id, remote_key_id, - receive_cipher_pool, send_cipher_pool, + receive_cipher_pool, rekey_at_counter: current_counter.saturating_add(Application::REKEY_AFTER_USES), expire_at_counter: current_counter.saturating_add(Application::EXPIRE_AFTER_USES), } } #[inline(always)] - fn get_send_cipher<'a>(&'a self, counter: u64) -> Result>, SendError> { + fn get_send_cipher<'a>(&'a self, counter: u64) -> Result, SendError> { if counter < self.expire_at_counter { Ok(self.send_cipher_pool[(counter as usize) % self.send_cipher_pool.len()].lock().unwrap()) } else { @@ -2570,34 +2600,37 @@ impl SessionKey { } #[inline(always)] - fn get_receive_cipher<'a>(&'a self, counter: u64) -> MutexGuard<'a, AesGcm> { + fn get_receive_cipher<'a>(&'a self, counter: u64) -> MutexGuard<'a, Application::AeadDec> { let idx = (counter as usize) % self.receive_cipher_pool.len(); self.receive_cipher_pool[idx].lock().unwrap() } } /// MixHash to update 'h' during negotiation. -#[inline] -fn mix_hash(hasher: &mut SHA512, h: &[u8; NOISE_HASHLEN], m: &[u8]) -> [u8; NOISE_HASHLEN] { +#[inline(always)] +fn mix_hash(hasher: &mut impl Sha512, h: &[u8; NOISE_HASHLEN], m: &[u8]) -> [u8; NOISE_HASHLEN] { + let mut output = [0u8; NOISE_HASHLEN]; hasher.reset(); hasher.update(h); hasher.update(m); - hasher.finish() + hasher.finish(&mut output); + output } /// Check if the proof of work attached to the first message contains the correct number of leading /// zeros. -#[inline] -fn verify_pow(hasher: &mut SHA512, message: &[u8]) -> bool { +#[inline(always)] +fn verify_pow(hasher: &mut Application::Hash, message: &[u8]) -> bool { if Application::PROOF_OF_WORK_BIT_DIFFICULTY == 0 { return true; } hasher.reset(); hasher.update(&message[NoiseXKPattern1::P_AUTH_END..NoiseXKPattern1::SIZE]); - let mut n = 0u32.to_ne_bytes(); - n.copy_from_slice(&hasher.finish()[..4]); - let n = u32::from_be_bytes(n); + let mut output = [0u8; NOISE_HASHLEN]; + hasher.finish(&mut output); + let n = u32::from_be_bytes(output[..4].try_into().unwrap()); n.leading_zeros() >= Application::PROOF_OF_WORK_BIT_DIFFICULTY } -fn from_bytes_agreement(public: &[u8], private: &P384KeyPair) -> Option<(P384PublicKey, Secret<48>)> { - P384PublicKey::from_bytes(public).and_then(|e| private.agree(&e).map(|ee| (e, ee))) +#[inline(always)] +fn from_bytes_agreement(public: &[u8], private: &impl P384KeyPair, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> Option { + PublicKey::from_bytes(public.try_into().unwrap()).and_then(|e| private.agree(&e, output).then_some(e)) } From 22fc95b9bcb9984825e8b3e4ffd2c48196d8e0dc Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 12 Jul 2023 14:45:44 -0400 Subject: [PATCH 03/91] removed utils --- src/indexed_heap.rs | 1 + src/utils/Cargo.toml | 22 - src/utils/rustfmt.toml | 1 - src/utils/src/arc_pool.rs | 769 --------------------------------- src/utils/src/arrayvec.rs | 369 ---------------- src/utils/src/base24.rs | 131 ------ src/utils/src/base62.rs | 187 -------- src/utils/src/blob.rs | 150 ------- src/utils/src/buffer.rs | 752 -------------------------------- src/utils/src/canonicalarc.rs | 80 ---- src/utils/src/cast.rs | 36 -- src/utils/src/defer.rs | 25 -- src/utils/src/dictionary.rs | 209 --------- src/utils/src/error.rs | 61 --- src/utils/src/exitcode.rs | 22 - src/utils/src/flatsortedmap.rs | 86 ---- src/utils/src/gate.rs | 35 -- src/utils/src/hex.rs | 124 ------ src/utils/src/io.rs | 48 -- src/utils/src/json.rs | 202 --------- src/utils/src/lib.rs | 114 ----- src/utils/src/marshalable.rs | 169 -------- src/utils/src/memory.rs | 121 ------ src/utils/src/pool.rs | 251 ----------- src/utils/src/reaper.rs | 57 --- src/utils/src/ringbuffer.rs | 120 ----- src/utils/src/rwu_lock.rs | 63 --- src/utils/src/str.rs | 56 --- src/utils/src/sync.rs | 47 -- src/utils/src/varint.rs | 118 ----- 30 files changed, 1 insertion(+), 4425 deletions(-) delete mode 100644 src/utils/Cargo.toml delete mode 120000 src/utils/rustfmt.toml delete mode 100644 src/utils/src/arc_pool.rs delete mode 100644 src/utils/src/arrayvec.rs delete mode 100644 src/utils/src/base24.rs delete mode 100644 src/utils/src/base62.rs delete mode 100644 src/utils/src/blob.rs delete mode 100644 src/utils/src/buffer.rs delete mode 100644 src/utils/src/canonicalarc.rs delete mode 100644 src/utils/src/cast.rs delete mode 100644 src/utils/src/defer.rs delete mode 100644 src/utils/src/dictionary.rs delete mode 100644 src/utils/src/error.rs delete mode 100644 src/utils/src/exitcode.rs delete mode 100644 src/utils/src/flatsortedmap.rs delete mode 100644 src/utils/src/gate.rs delete mode 100644 src/utils/src/hex.rs delete mode 100644 src/utils/src/io.rs delete mode 100644 src/utils/src/json.rs delete mode 100644 src/utils/src/lib.rs delete mode 100644 src/utils/src/marshalable.rs delete mode 100644 src/utils/src/memory.rs delete mode 100644 src/utils/src/pool.rs delete mode 100644 src/utils/src/reaper.rs delete mode 100644 src/utils/src/ringbuffer.rs delete mode 100644 src/utils/src/rwu_lock.rs delete mode 100644 src/utils/src/str.rs delete mode 100644 src/utils/src/sync.rs delete mode 100644 src/utils/src/varint.rs diff --git a/src/indexed_heap.rs b/src/indexed_heap.rs index 2838c1d..721f287 100644 --- a/src/indexed_heap.rs +++ b/src/indexed_heap.rs @@ -14,6 +14,7 @@ pub struct IndexedBinaryHeap { map: Vec<(usize, u64)>, } +#[allow(unused)] impl IndexedBinaryHeap { pub fn new() -> Self { Self { diff --git a/src/utils/Cargo.toml b/src/utils/Cargo.toml deleted file mode 100644 index b548cf6..0000000 --- a/src/utils/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -authors = ["ZeroTier, Inc. "] -edition = "2021" -license = "MPL-2.0" -name = "zerotier-utils" -version = "0.1.0" - -[features] -default = [] -tokio = ["dep:tokio"] - -[dependencies] -serde = { version = "^1", features = ["derive"], default-features = false } -serde_json = { version = "^1", features = ["std"], default-features = false } -tokio = { version = "^1", default-features = false, features = ["fs", "io-util", "io-std", "net", "process", "rt", "rt-multi-thread", "signal", "sync", "time"], optional = true } - -[target."cfg(windows)".dependencies] -winapi = { version = "^0", features = ["handleapi", "ws2ipdef", "ws2tcpip"] } - -[target."cfg(not(windows))".dependencies] -libc = "^0" -signal-hook = "^0" diff --git a/src/utils/rustfmt.toml b/src/utils/rustfmt.toml deleted file mode 120000 index 39f97b0..0000000 --- a/src/utils/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -../rustfmt.toml \ No newline at end of file diff --git a/src/utils/src/arc_pool.rs b/src/utils/src/arc_pool.rs deleted file mode 100644 index 1b9131b..0000000 --- a/src/utils/src/arc_pool.rs +++ /dev/null @@ -1,769 +0,0 @@ -use std::fmt::{Debug, Display}; -use std::marker::PhantomData; -use std::mem::{self, ManuallyDrop, MaybeUninit}; -use std::num::NonZeroU64; -use std::ops::Deref; -use std::ptr::{self, NonNull}; -use std::sync::{ - atomic::{AtomicPtr, AtomicU32, Ordering}, - Mutex, RwLock, RwLockReadGuard, -}; - -const DEFAULT_L: usize = 64; - -union SlotState { - empty_next: *mut Slot, - full_obj: ManuallyDrop, -} -struct Slot { - obj: SlotState, - free_lock: RwLock<()>, - ref_count: AtomicU32, - uid: u64, -} - -struct PoolMem { - mem: [MaybeUninit>; L], - pre: *mut PoolMem, -} - -/// A generic, *thread-safe*, fixed-sized memory allocator for instances of `T`. -/// New instances of `T` are packed together into arrays of size `L`, and allocated in bulk as one memory arena from the global allocator. -/// Arenas from the global allocator are not deallocated until the pool is dropped, and are re-used as instances of `T` are allocated and freed. -/// -/// This specific datastructure also supports generational indexing, which means that an arbitrary number of non-owning references to allocated instances of `T` can be generated safely. These references can outlive the underlying `T` they reference, and will safely report upon dereference that the original underlying `T` is gone. -/// -/// Atomic reference counting is also implemented allowing for exceedingly complex models of shared ownership. Multiple copies of both strong and weak references to the underlying `T` can be generated that are all memory safe and borrow-checked. -/// -/// Allocating from a pool results in very little internal and external fragmentation in the global heap, thus saving significant amounts of memory from being used by one's program. Pools also allocate memory significantly faster on average than the global allocator. This specific pool implementation supports guaranteed constant time `alloc` and `free`. -pub struct Pool(Mutex<(*mut Slot, u64, *mut PoolMem, usize)>); -unsafe impl Send for Pool {} -unsafe impl Sync for Pool {} - -impl Pool { - pub const DEFAULT_L: usize = DEFAULT_L; - - /// Creates a new `Pool` with packing length `L`. Packing length determines the number of instances of `T` that will fit in a page before it becomes full. Once all pages in a `Pool` are full a new page is allocated from the Global allocator. Larger values of `L` are generally faster, but the returns are diminishing and vary by platform. - /// - /// A `Pool` cannot be interacted with directly, it requires a `impl StaticPool for Pool` implementation. See the `static_pool!` macro for automatically generated trait implementation. - #[inline] - pub const fn new() -> Self { - Pool(Mutex::new((ptr::null_mut(), 1, ptr::null_mut(), usize::MAX))) - } - - #[inline(always)] - fn create_arr() -> [MaybeUninit>; L] { - unsafe { MaybeUninit::<[MaybeUninit>; L]>::uninit().assume_init() } - } - - /// Allocates uninitialized memory for an instance `T`. The returned pointer points to this memory. It is undefined what will be contained in this memory, it must be initialized before being used. This pointer must be manually freed from the pool using `Pool::free_ptr` before being dropped, otherwise its memory will be leaked. If the pool is dropped before this pointer is freed, the destructor of `T` will not be run and this pointer will point to invalid memory. - unsafe fn alloc_ptr(&self, obj: T) -> NonNull> { - let mut mutex = self.0.lock().unwrap(); - let (mut first_free, uid, mut head_arena, mut head_size) = *mutex; - - let slot_ptr = if let Some(mut slot_ptr) = NonNull::new(first_free) { - let slot = slot_ptr.as_mut(); - let _announce_free = slot.free_lock.write().unwrap(); - debug_assert_eq!(slot.uid, 0); - first_free = slot.obj.empty_next; - slot.ref_count = AtomicU32::new(1); - slot.uid = uid; - slot.obj.full_obj = ManuallyDrop::new(obj); - slot_ptr - } else { - if head_size >= L { - let new = Box::leak(Box::new(PoolMem { pre: head_arena, mem: Self::create_arr() })); - head_arena = new; - head_size = 0; - } - let slot = Slot { - obj: SlotState { full_obj: ManuallyDrop::new(obj) }, - free_lock: RwLock::new(()), - ref_count: AtomicU32::new(1), - uid, - }; - let slot_ptr = &mut (*head_arena).mem[head_size]; - let slot_ptr = NonNull::new_unchecked(slot_ptr.write(slot)); - head_size += 1; - // We do not have to hold the free lock since we know this slot has never been touched before and nothing external references it - slot_ptr - }; - - *mutex = (first_free, uid.wrapping_add(1), head_arena, head_size); - slot_ptr - } - /// Frees memory allocated from the pool by `Pool::alloc_ptr`. This must be called only once on only pointers returned by `Pool::alloc_ptr` from the same pool. Once memory is freed the content of the memory is undefined, it should not be read or written. - /// - /// `drop` will be called on the `T` pointed to, be sure it has not been called already. - /// - /// The free lock must be held by the caller. - unsafe fn free_ptr(&self, mut slot_ptr: NonNull>) { - let slot = slot_ptr.as_mut(); - slot.uid = 0; - ManuallyDrop::::drop(&mut slot.obj.full_obj); - //linked-list insert - let mut mutex = self.0.lock().unwrap(); - - slot.obj.empty_next = mutex.0; - mutex.0 = slot_ptr.as_ptr(); - } -} -impl Drop for Pool { - fn drop(&mut self) { - let mutex = self.0.lock().unwrap(); - let (_, _, mut head_arena, _) = *mutex; - unsafe { - while !head_arena.is_null() { - let mem = Box::from_raw(head_arena); - head_arena = mem.pre; - drop(mem); - } - } - drop(mutex); - } -} - -pub trait StaticPool { - /// Must return a pointer to an instance of a `Pool` with a static lifetime. That pointer must be cast to a `*const ()` to make the borrow-checker happy. - /// - /// **Safety**: The returned pointer must have originally been a `&'static Pool` reference. So it must have had a matching `T` and `L` and it must have the static lifetime. - /// - /// In order to borrow-split allocations from a `Pool`, we need to force the borrow-checker to not associate the lifetime of an instance of `T` with the lifetime of the pool. Otherwise the borrow-checker would require every allocated `T` to have the `'static` lifetime, to match the pool's lifetime. - /// The simplest way I have found to do this is to return the pointer to the static pool as an anonymous, lifetimeless `*const ()`. This introduces unnecessary safety concerns surrounding pointer casting unfortunately. If there is a better way to borrow-split from a pool I will gladly implement it. - unsafe fn get_static_pool() -> *const (); - - /// Allocates memory for an instance `T` and puts its pointer behind a memory-safe Arc. This `PoolArc` automatically frees itself on drop, and will cause the borrow checker to complain if you attempt to drop the pool before you drop this box. - /// - /// This `PoolArc` supports the ability to generate weak, non-owning references to the allocated `T`. - #[inline(always)] - fn alloc(obj: T) -> PoolArc - where - Self: Sized, - { - unsafe { - PoolArc { - ptr: (*Self::get_static_pool().cast::>()).alloc_ptr(obj), - _p: PhantomData, - } - } - } -} - -/// A rust-style RAII wrapper that drops and frees memory allocated from a pool automatically, the same as an `Arc`. This will run the destructor of `T` in place within the pool before freeing it, correctly maintaining the invariants that the borrow checker and rust compiler expect of generic types. -pub struct PoolArc, const L: usize = DEFAULT_L> { - ptr: NonNull>, - _p: PhantomData<*const OriginPool>, -} - -impl, const L: usize> PoolArc { - /// Obtain a non-owning reference to the `T` contained in this `PoolArc`. This reference has the special property that the underlying `T` can be dropped from the pool while neither making this reference invalid or unsafe nor leaking the memory of `T`. Instead attempts to `grab` the reference will safely return `None`. - /// - /// `T` is guaranteed to be dropped when all `PoolArc` are dropped, regardless of how many `PoolWeakRef` still exist. - #[inline] - pub fn downgrade(&self) -> PoolWeakRef { - unsafe { - // Since this is a Arc we know for certain the object has not been freed, so we don't have to hold the free lock - PoolWeakRef { - ptr: self.ptr, - uid: NonZeroU64::new_unchecked(self.ptr.as_ref().uid), - _p: PhantomData, - } - } - } - /// Returns a number that uniquely identifies this allocated `T` within this pool. No other instance of `T` may have this uid. - pub fn uid(&self) -> NonZeroU64 { - unsafe { NonZeroU64::new_unchecked(self.ptr.as_ref().uid) } - } -} - -impl, const L: usize> Deref for PoolArc { - type Target = T; - #[inline] - fn deref(&self) -> &Self::Target { - unsafe { &self.ptr.as_ref().obj.full_obj } - } -} -impl, const L: usize> Clone for PoolArc { - fn clone(&self) -> Self { - unsafe { - self.ptr.as_ref().ref_count.fetch_add(1, Ordering::Relaxed); - } - Self { ptr: self.ptr, _p: PhantomData } - } -} -impl, const L: usize> Drop for PoolArc { - #[inline] - fn drop(&mut self) { - unsafe { - let slot = self.ptr.as_ref(); - if slot.ref_count.fetch_sub(1, Ordering::AcqRel) == 1 { - let _announce_free = slot.free_lock.write().unwrap(); - // We have to check twice in case a weakref was upgraded before the lock was acquired - if slot.ref_count.load(Ordering::Relaxed) == 0 { - (*OriginPool::get_static_pool().cast::>()).free_ptr(self.ptr); - } - } - } - } -} -unsafe impl, const L: usize> Send for PoolArc where T: Send {} -unsafe impl, const L: usize> Sync for PoolArc where T: Sync {} -impl, const L: usize> Debug for PoolArc -where - T: Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("PoolArc").field(self.deref()).finish() - } -} -impl, const L: usize> Display for PoolArc -where - T: Display, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.deref().fmt(f) - } -} - -/// A non-owning reference to a `T` allocated by a pool. This reference has the special property that the underlying `T` can be dropped from the pool while neither making this reference invalid nor leaking the memory of `T`. Instead attempts to `grab` this reference will safely return `None` if the underlying `T` has been freed by any thread. -/// -/// Due to their thread safety and low overhead a `PoolWeakRef` implements clone and copy. -/// -/// The lifetime of this reference is tied to the lifetime of the pool it came from, because if it were allowed to live longer than its origin pool, it would no longer be safe to dereference and would most likely segfault. Instead the borrow-checker will enforce that this reference has a shorter lifetime that its origin pool. -/// -/// For technical reasons a `RwLock>` will always be the fastest implementation of a `PoolWeakRefSwap`, which is why this library does not provide a `PoolWeakRefSwap` type. -pub struct PoolWeakRef, const L: usize = DEFAULT_L> { - /// A number that uniquely identifies this allocated `T` within this pool. No other instance of `T` may have this uid. This value is read-only. - pub uid: NonZeroU64, - ptr: NonNull>, - _p: PhantomData<*const OriginPool>, -} - -impl, const L: usize> PoolWeakRef { - /// Obtains a lock that allows the `T` contained in this `PoolWeakRef` to be dereferenced in a thread-safe manner. This lock does not prevent other threads from accessing `T` at the same time, so `T` ought to use interior mutability if it needs to be mutated in a thread-safe way. What this lock does guarantee is that `T` cannot be destructed and freed while it is being held. - /// - /// Do not attempt from within the same thread to drop the `PoolArc` that owns this `T` before dropping this lock, or else the thread will deadlock. Rust makes this quite hard to do accidentally but it's not strictly impossible. - #[inline] - pub fn grab<'b>(&self) -> Option> { - unsafe { - let slot = self.ptr.as_ref(); - let prevent_free_lock = slot.free_lock.read().unwrap(); - if slot.uid == self.uid.get() { - Some(PoolGuard(prevent_free_lock, &slot.obj.full_obj)) - } else { - None - } - } - } - /// Attempts to create an owning `PoolArc` from this `PoolWeakRef` of the underlying `T`. Will return `None` if the underlying `T` has already been dropped. - pub fn upgrade(&self) -> Option> { - unsafe { - let slot = self.ptr.as_ref(); - let _prevent_free_lock = slot.free_lock.read().unwrap(); - if slot.uid == self.uid.get() { - self.ptr.as_ref().ref_count.fetch_add(1, Ordering::Relaxed); - Some(PoolArc { ptr: self.ptr, _p: PhantomData }) - } else { - None - } - } - } -} -impl, const L: usize> Clone for PoolWeakRef { - fn clone(&self) -> Self { - Self { uid: self.uid, ptr: self.ptr, _p: PhantomData } - } -} -impl, const L: usize> Copy for PoolWeakRef {} -unsafe impl, const L: usize> Send for PoolWeakRef where T: Send {} -unsafe impl, const L: usize> Sync for PoolWeakRef where T: Sync {} -impl, const L: usize> Debug for PoolWeakRef -where - T: Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let inner = self.grab(); - f.debug_tuple("PoolWeakRef").field(&inner).finish() - } -} -impl, const L: usize> Display for PoolWeakRef -where - T: Display, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(inner) = self.grab() { - inner.fmt(f) - } else { - f.write_str("Empty") - } - } -} - -/// A multithreading lock guard that prevents another thread from freeing the underlying `T` while it is held. It does not prevent other threads from accessing the underlying `T`. -/// -/// If the same thread that holds this guard attempts to free `T` before dropping the guard, it will deadlock. -pub struct PoolGuard<'a, T>(RwLockReadGuard<'a, ()>, &'a T); -impl<'a, T> Deref for PoolGuard<'a, T> { - type Target = T; - #[inline] - fn deref(&self) -> &Self::Target { - &*self.1 - } -} -impl<'a, T> Debug for PoolGuard<'a, T> -where - T: Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("PoolGuard").field(self.deref()).finish() - } -} -impl<'a, T> Display for PoolGuard<'a, T> -where - T: Display, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.deref().fmt(f) - } -} - -/// Allows for the Atomic Swapping and Loading of a `PoolArc`, similar to how a `RwLock>` would function, but much faster and less verbose. -pub struct PoolArcSwap, const L: usize = DEFAULT_L> { - ptr: AtomicPtr>, - reads: AtomicU32, - _p: PhantomData<*const OriginPool>, -} -impl, const L: usize> PoolArcSwap { - /// Creates a new `PoolArcSwap`, consuming `arc` in the process. - pub fn new(mut arc: PoolArc) -> Self { - unsafe { - let ret = Self { - ptr: AtomicPtr::new(arc.ptr.as_mut()), - reads: AtomicU32::new(0), - _p: arc._p, - }; - // Suppress reference decrement on new - mem::forget(arc); - ret - } - } - /// Atomically swaps the currently stored `PoolArc` with a new one, returning the previous one. - pub fn swap(&self, arc: PoolArc) -> PoolArc { - unsafe { - let pre_ptr = self.ptr.swap(arc.ptr.as_ptr(), Ordering::Relaxed); - - while self.reads.load(Ordering::Acquire) > 0 { - std::hint::spin_loop() - } - - mem::forget(arc); - PoolArc { ptr: NonNull::new_unchecked(pre_ptr), _p: self._p } - } - } - - /// Atomically loads and clones the currently stored `PoolArc`, guaranteeing that the underlying `T` cannot be freed while the clone is held. - pub fn load(&self) -> PoolArc { - unsafe { - self.reads.fetch_add(1, Ordering::Acquire); - let ptr = self.ptr.load(Ordering::Relaxed); - (*ptr).ref_count.fetch_add(1, Ordering::Relaxed); - self.reads.fetch_sub(1, Ordering::Release); - PoolArc { ptr: NonNull::new_unchecked(ptr), _p: self._p } - } - } -} -impl, const L: usize> Drop for PoolArcSwap { - #[inline] - fn drop(&mut self) { - unsafe { - let pre = self.ptr.load(Ordering::SeqCst); - PoolArc { _p: self._p, ptr: NonNull::new_unchecked(pre) }; - } - } -} -unsafe impl, const L: usize> Send for PoolArcSwap where T: Send {} -unsafe impl, const L: usize> Sync for PoolArcSwap where T: Sync {} -impl, const L: usize> Debug for PoolArcSwap -where - T: Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("PoolArcSwap").field(&self.load()).finish() - } -} -impl, const L: usize> Display for PoolArcSwap -where - T: Display, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - (&self.load()).fmt(f) - } -} - -/// Another implementation of a `PoolArcSwap` utalizing a RwLock instead of atomics. -/// This implementation has slower a `load` but a faster `swap` than the previous implementation of `PoolArcSwap`. -/// If you plan on swapping way more often than loading, this may be a better choice. -pub struct PoolArcSwapRw, const L: usize = DEFAULT_L> { - ptr: RwLock>>, - _p: PhantomData<*const OriginPool>, -} - -impl, const L: usize> PoolArcSwapRw { - /// Creates a new `PoolArcSwap`, consuming `arc` in the process. - pub fn new(arc: PoolArc) -> Self { - let ret = Self { ptr: RwLock::new(arc.ptr), _p: arc._p }; - mem::forget(arc); - ret - } - - /// Atomically swaps the currently stored `PoolArc` with a new one, returning the previous one. - pub fn swap(&self, arc: PoolArc) -> PoolArc { - let mut w = self.ptr.write().unwrap(); - let pre = PoolArc { ptr: *w, _p: self._p }; - *w = arc.ptr; - mem::forget(arc); - pre - } - - /// Atomically loads and clones the currently stored `PoolArc`, guaranteeing that the underlying `T` cannot be freed while the clone is held. - pub fn load(&self) -> PoolArc { - let r = self.ptr.read().unwrap(); - unsafe { - r.as_ref().ref_count.fetch_add(1, Ordering::Relaxed); - } - let pre = PoolArc { ptr: *r, _p: self._p }; - pre - } -} -impl, const L: usize> Drop for PoolArcSwapRw { - #[inline] - fn drop(&mut self) { - let w = self.ptr.write().unwrap(); - PoolArc { ptr: *w, _p: self._p }; - } -} -unsafe impl, const L: usize> Send for PoolArcSwapRw where T: Send {} -unsafe impl, const L: usize> Sync for PoolArcSwapRw where T: Sync {} -impl, const L: usize> Debug for PoolArcSwapRw -where - T: Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("PoolArcSwapRw").field(&self.load()).finish() - } -} -impl, const L: usize> Display for PoolArcSwapRw -where - T: Display, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - (&self.load()).fmt(f) - } -} - -/// Automatically generates valid implementations of `StaticPool` onto a chosen identifier, allowing this module to allocate instances of `T` with `alloc`. Users have to generate implementations clientside because rust does not allow for generic globals. -/// -/// The chosen identifier is declared to be a struct with no fields, and instead contains a static global `Pool` for every implementation of `StaticPool` requested. -/// -/// # Example -/// ``` -/// use zerotier_utils::arc_pool::{static_pool, StaticPool, Pool, PoolArc}; -/// -/// static_pool!(pub StaticPool MyPools { -/// Pool, Pool<&u32, 12> -/// }); -/// -/// struct Container { -/// item: PoolArc -/// } -/// -/// let object = 1u32; -/// let arc_object = MyPools::alloc(object); -/// let arc_ref = MyPools::alloc(&object); -/// let arc_container = Container {item: MyPools::alloc(object)}; -/// -/// assert_eq!(*arc_object, **arc_ref); -/// assert_eq!(*arc_object, *arc_container.item); -/// ``` -#[macro_export] -macro_rules! __static_pool__ { - ($m:ident $s:ident { $($($p:ident)::+<$t:ty$(, $l:tt)?>),+ $(,)?}) => { - struct $s {} - $( - impl $m<$t$(, $l)?> for $s { - #[inline(always)] - unsafe fn get_static_pool() -> *const () { - static POOL: $($p)::+<$t$(, $l)?> = $($p)::+::new(); - (&POOL as *const $($p)::+<$t$(, $l)?>).cast() - } - } - )* - }; - ($m:ident::$n:ident $s:ident { $($($p:ident)::+<$t:ty$(, $l:tt)?>),+ $(,)?}) => { - struct $s {} - $( - impl $m::$n<$t$(, $l)?> for $s { - #[inline(always)] - unsafe fn get_static_pool() -> *const () { - static POOL: $($p)::+<$t$(, $l)?> = $($p)::+::new(); - (&POOL as *const $($p)::+<$t$(, $l)?>).cast() - } - } - )* - }; - (pub $m:ident $s:ident { $($($p:ident)::+<$t:ty$(, $l:tt)?>),+ $(,)?}) => { - pub struct $s {} - $( - impl $m<$t$(, $l)?> for $s { - #[inline(always)] - unsafe fn get_static_pool() -> *const () { - static POOL: $($p)::+<$t$(, $l)?> = $($p)::+::new(); - (&POOL as *const $($p)::+<$t$(, $l)?>).cast() - } - } - )* - }; - (pub $m:ident::$n:ident $s:ident { $($($p:ident)::+<$t:ty$(, $l:tt)?>),+ $(,)?}) => { - pub struct $s {} - $( - impl $m::$n<$t$(, $l)?> for $s { - #[inline(always)] - unsafe fn get_static_pool() -> *const () { - static POOL: $($p)::+<$t$(, $l)?> = $($p)::+::new(); - (&POOL as *const $($p)::+<$t$(, $l)?>).cast() - } - } - )* - }; -} -pub use __static_pool__ as static_pool; - -#[cfg(test)] -mod tests { - use super::*; - use std::{ - sync::{atomic::AtomicU64, Arc}, - thread, - }; - - fn rand(r: &mut u32) -> u32 { - /* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */ - *r ^= *r << 13; - *r ^= *r >> 17; - *r ^= *r << 5; - *r - } - const fn prob(p: u64) -> u32 { - (p * (u32::MAX as u64) / 100) as u32 - } - fn rand_idx<'a, T>(v: &'a [T], r: &mut u32) -> Option<&'a T> { - if v.len() > 0 { - Some(&v[(rand(r) as usize) % v.len()]) - } else { - None - } - } - fn rand_i<'a, T>(v: &'a [T], r: &mut u32) -> Option { - if v.len() > 0 { - Some((rand(r) as usize) % v.len()) - } else { - None - } - } - - struct Item { - a: u32, - count: &'static AtomicU64, - b: u32, - } - impl Item { - fn new(r: u32, count: &'static AtomicU64) -> Item { - count.fetch_add(1, Ordering::Relaxed); - Item { a: r, count, b: r } - } - fn check(&self, id: u32) { - assert_eq!(self.a, self.b); - assert_eq!(self.a, id); - } - } - impl Drop for Item { - fn drop(&mut self) { - let _a = self.count.fetch_sub(1, Ordering::Relaxed); - assert_eq!(self.a, self.b); - } - } - - const POOL_U32_LEN: usize = (5 * 12) << 2; - static_pool!(StaticPool TestPools { - Pool, Pool - }); - - #[test] - fn usage() { - let num1 = TestPools::alloc(1u32); - let num2 = TestPools::alloc(2u32); - let num3 = TestPools::alloc(3u32); - let num4 = TestPools::alloc(4u32); - let num2_weak = num2.downgrade(); - - assert_eq!(*num2_weak.grab().unwrap(), 2); - drop(num2); - - assert_eq!(*num1, 1); - assert_eq!(*num3, 3); - assert_eq!(*num4, 4); - assert!(num2_weak.grab().is_none()); - } - #[test] - fn single_thread() { - let mut history = Vec::new(); - - let num1 = TestPools::alloc(1u32); - let num2 = TestPools::alloc(2u32); - let num3 = TestPools::alloc(3u32); - let num4 = TestPools::alloc(4u32); - let num2_weak = num2.downgrade(); - - for i in 0..1000 { - history.push(TestPools::alloc(i as u32)); - } - for i in 0..100 { - let arc = history.remove((i * 10) % history.len()); - assert!(*arc < 1000); - } - for i in 0..1000 { - history.push(TestPools::alloc(i as u32)); - } - - assert_eq!(*num2_weak.grab().unwrap(), 2); - drop(num2); - - assert_eq!(*num1, 1); - assert_eq!(*num3, 3); - assert_eq!(*num4, 4); - assert!(num2_weak.grab().is_none()); - } - - #[test] - fn multi_thread() { - const N: usize = 12345; - static COUNT: AtomicU64 = AtomicU64::new(0); - - let mut joins = Vec::new(); - for i in 0..32 { - joins.push(thread::spawn(move || { - let r = &mut (i + 1234); - - let mut items_dup = Vec::new(); - let mut items = Vec::new(); - for _ in 0..N { - let p = rand(r); - if p < prob(30) { - let id = rand(r); - let s = TestPools::alloc(Item::new(id, &COUNT)); - items.push((id, s.clone(), s.downgrade())); - s.check(id); - } else if p < prob(60) { - if let Some((id, s, w)) = rand_idx(&items, r) { - items_dup.push((*id, s.clone(), (*w).clone())); - s.check(*id); - } - } else if p < prob(80) { - if let Some(i) = rand_i(&items, r) { - let (id, s, w) = items.swap_remove(i); - w.grab().unwrap().check(id); - s.check(id); - } - } else if p < prob(100) { - if let Some(i) = rand_i(&items_dup, r) { - let (id, s, w) = items_dup.swap_remove(i); - w.grab().unwrap().check(id); - s.check(id); - } - } - } - for (id, s, w) in items_dup { - s.check(id); - w.grab().unwrap().check(id); - } - for (id, s, w) in items { - s.check(id); - w.grab().unwrap().check(id); - drop(s); - assert!(w.grab().is_none()) - } - })); - } - for j in joins { - j.join().unwrap(); - } - assert_eq!(COUNT.load(Ordering::Relaxed), 0); - } - - #[test] - fn multi_thread_swap() { - const N: usize = 1234; - static COUNT: AtomicU64 = AtomicU64::new(0); - - let s = Arc::new(PoolArcSwap::new(TestPools::alloc(Item::new(0, &COUNT)))); - - for _ in 0..123 { - let mut joins = Vec::new(); - for _ in 0..8 { - let swaps = s.clone(); - joins.push(thread::spawn(move || { - let r = &mut 1474; - let mut new = TestPools::alloc(Item::new(rand(r), &COUNT)); - for _ in 0..N { - new = swaps.swap(new); - } - })); - } - for j in joins { - j.join().unwrap(); - } - } - drop(s); - assert_eq!(COUNT.load(Ordering::Relaxed), 0); - } - - #[test] - fn multi_thread_swap_load() { - const N: usize = 12345; - static COUNT: AtomicU64 = AtomicU64::new(0); - - let s: Arc<[_; 8]> = Arc::new(std::array::from_fn(|i| PoolArcSwap::new(TestPools::alloc(Item::new(i as u32, &COUNT))))); - - let mut joins = Vec::new(); - - for i in 0..4 { - let swaps = s.clone(); - joins.push(thread::spawn(move || { - let r = &mut (i + 2783); - for _ in 0..N { - if let Some(s) = rand_idx(&swaps[..], r) { - let new = TestPools::alloc(Item::new(rand(r), &COUNT)); - let _a = s.swap(new); - } - } - })); - } - for i in 0..28 { - let swaps = s.clone(); - joins.push(thread::spawn(move || { - let r = &mut (i + 4136); - for _ in 0..N { - if let Some(s) = rand_idx(&swaps[..], r) { - let _a = s.load(); - assert_eq!(_a.a, _a.b); - } - } - })); - } - for j in joins { - j.join().unwrap(); - } - drop(s); - assert_eq!(COUNT.load(Ordering::Relaxed), 0); - } -} diff --git a/src/utils/src/arrayvec.rs b/src/utils/src/arrayvec.rs deleted file mode 100644 index b8a8a07..0000000 --- a/src/utils/src/arrayvec.rs +++ /dev/null @@ -1,369 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::fmt::Debug; -use std::io::Write; -use std::mem::{needs_drop, size_of, MaybeUninit}; -use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut}; - -use serde::ser::SerializeSeq; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -#[derive(Clone, Copy, Debug)] -pub struct OutOfCapacityError(pub T); - -impl std::fmt::Display for OutOfCapacityError { - fn fmt(&self, stream: &mut std::fmt::Formatter) -> std::fmt::Result { - std::fmt::Display::fmt("ArrayVec out of space", stream) - } -} - -impl ::std::error::Error for OutOfCapacityError { - fn description(&self) -> &str { - "ArrayVec out of space" - } -} - -/// A simple vector backed by a static sized array with no memory allocations and no overhead construction. -pub struct ArrayVec { - pub(crate) s: usize, - pub(crate) a: [MaybeUninit; C], -} - -impl Default for ArrayVec { - #[inline(always)] - fn default() -> Self { - Self::new() - } -} - -impl PartialEq for ArrayVec { - #[inline(always)] - fn eq(&self, other: &Self) -> bool { - let tmp: &[T] = self.as_ref(); - tmp.eq(other.as_ref()) - } -} - -impl Eq for ArrayVec {} - -impl Clone for ArrayVec { - #[inline] - fn clone(&self) -> Self { - debug_assert!(self.s <= C); - Self { - s: self.s, - a: unsafe { - let mut tmp: [MaybeUninit; C] = MaybeUninit::uninit().assume_init(); - for i in 0..self.s { - tmp.get_unchecked_mut(i).write(self.a[i].assume_init_ref().clone()); - } - tmp - }, - } - } -} - -impl From<[T; S]> for ArrayVec { - #[inline] - fn from(v: [T; S]) -> Self { - if S <= C { - let mut tmp = Self::new(); - for i in 0..S { - tmp.push(v[i].clone()); - } - tmp - } else { - panic!(); - } - } -} - -impl ToString for ArrayVec { - #[inline] - fn to_string(&self) -> String { - crate::hex::to_string(self.as_bytes()) - } -} - -impl Debug for ArrayVec { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.to_string().as_str()) - } -} - -impl Write for ArrayVec { - #[inline] - fn write(&mut self, buf: &[u8]) -> std::io::Result { - for i in buf.iter() { - if self.try_push(*i).is_err() { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "ArrayVec out of space")); - } - } - Ok(buf.len()) - } - - #[inline(always)] - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} - -impl TryFrom> for ArrayVec { - type Error = OutOfCapacityError; - - #[inline(always)] - fn try_from(mut value: Vec) -> Result { - let mut tmp = Self::new(); - for x in value.drain(..) { - tmp.try_push(x)?; - } - Ok(tmp) - } -} - -impl TryFrom<&Vec> for ArrayVec { - type Error = OutOfCapacityError; - - #[inline(always)] - fn try_from(value: &Vec) -> Result { - let mut tmp = Self::new(); - for x in value.iter() { - tmp.try_push(x.clone())?; - } - Ok(tmp) - } -} - -impl TryFrom<&[T]> for ArrayVec { - type Error = OutOfCapacityError; - - #[inline(always)] - fn try_from(value: &[T]) -> Result { - let mut tmp = Self::new(); - for x in value.iter() { - tmp.try_push(x.clone())?; - } - Ok(tmp) - } -} - -impl ArrayVec { - #[inline(always)] - pub fn new() -> Self { - assert_eq!(size_of::<[T; C]>(), size_of::<[MaybeUninit; C]>()); - Self { s: 0, a: unsafe { MaybeUninit::uninit().assume_init() } } - } - - #[inline] - pub fn push(&mut self, v: T) { - let i = self.s; - if i < C { - unsafe { self.a.get_unchecked_mut(i).write(v) }; - self.s = i + 1; - } else { - panic!(); - } - } - - #[inline] - pub fn try_push(&mut self, v: T) -> Result<(), OutOfCapacityError> { - if self.s < C { - let i = self.s; - unsafe { self.a.get_unchecked_mut(i).write(v) }; - self.s = i + 1; - Ok(()) - } else { - Err(OutOfCapacityError(v)) - } - } - - #[inline(always)] - pub fn as_bytes(&self) -> &[T] { - unsafe { &*slice_from_raw_parts(self.a.as_ptr().cast(), self.s) } - } - - #[inline(always)] - pub fn is_empty(&self) -> bool { - self.s == 0 - } - - #[inline(always)] - pub fn len(&self) -> usize { - self.s - } - - #[inline(always)] - pub fn capacity_remaining(&self) -> usize { - C - self.s - } - - #[inline(always)] - pub fn iter(&self) -> impl DoubleEndedIterator { - self.as_ref().iter() - } - - #[inline(always)] - pub fn iter_mut(&mut self) -> impl DoubleEndedIterator { - self.as_mut().iter_mut() - } - - #[inline(always)] - pub fn first(&self) -> Option<&T> { - if self.s != 0 { - Some(unsafe { self.a.get_unchecked(0).assume_init_ref() }) - } else { - None - } - } - - #[inline(always)] - pub fn last(&self) -> Option<&T> { - if self.s != 0 { - Some(unsafe { self.a.get_unchecked(self.s - 1).assume_init_ref() }) - } else { - None - } - } - - #[inline] - pub fn pop(&mut self) -> Option { - if self.s > 0 { - let i = self.s - 1; - debug_assert!(i < C); - self.s = i; - Some(unsafe { self.a.get_unchecked(i).assume_init_read() }) - } else { - None - } - } - - #[inline] - pub fn clear(&mut self) { - if needs_drop::() { - for i in 0..self.s { - unsafe { self.a.get_unchecked_mut(i).assume_init_drop() }; - } - } - self.s = 0; - } -} - -impl ArrayVec -where - T: Copy, -{ - /// Push a slice of copyable objects, panic if capacity exceeded. - #[inline] - pub fn push_slice(&mut self, v: &[T]) { - let start = self.s; - let end = self.s + v.len(); - if end <= C { - for i in start..end { - unsafe { self.a.get_unchecked_mut(i).write(*v.get_unchecked(i - start)) }; - } - self.s = end; - } else { - panic!(); - } - } -} - -impl Drop for ArrayVec { - #[inline(always)] - fn drop(&mut self) { - if needs_drop::() { - for i in 0..self.s { - unsafe { self.a.get_unchecked_mut(i).assume_init_drop() }; - } - } - } -} - -impl AsRef<[T]> for ArrayVec { - #[inline(always)] - fn as_ref(&self) -> &[T] { - unsafe { &*slice_from_raw_parts(self.a.as_ptr().cast(), self.s) } - } -} - -impl AsMut<[T]> for ArrayVec { - #[inline(always)] - fn as_mut(&mut self) -> &mut [T] { - unsafe { &mut *slice_from_raw_parts_mut(self.a.as_mut_ptr().cast(), self.s) } - } -} - -impl Serialize for ArrayVec { - #[inline] - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut seq = serializer.serialize_seq(Some(self.len()))?; - let sl: &[T] = self.as_ref(); - for i in 0..self.s { - seq.serialize_element(&sl[i])?; - } - seq.end() - } -} - -struct ArrayVecVisitor<'de, T: Deserialize<'de>, const L: usize>(std::marker::PhantomData<&'de T>); - -impl<'de, T: Deserialize<'de>, const L: usize> serde::de::Visitor<'de> for ArrayVecVisitor<'de, T, L> { - type Value = ArrayVec; - - #[inline] - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str(format!("array of up to {} elements", L).as_str()) - } - - #[inline] - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - let mut a = ArrayVec::::new(); - while let Some(x) = seq.next_element()? { - a.push(x); - } - Ok(a) - } -} - -impl<'de, T: Deserialize<'de> + 'de, const L: usize> Deserialize<'de> for ArrayVec { - #[inline] - fn deserialize(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - deserializer.deserialize_seq(ArrayVecVisitor(std::marker::PhantomData::default())) - } -} - -#[cfg(test)] -mod tests { - use super::ArrayVec; - - #[test] - fn array_vec() { - let mut v = ArrayVec::::new(); - for i in 0..128 { - v.push(i); - } - assert_eq!(v.len(), 128); - assert!(v.try_push(1000).is_err()); - assert_eq!(v.len(), 128); - for _ in 0..128 { - assert!(v.pop().is_some()); - } - assert!(v.pop().is_none()); - } -} diff --git a/src/utils/src/base24.rs b/src/utils/src/base24.rs deleted file mode 100644 index 9148de4..0000000 --- a/src/utils/src/base24.rs +++ /dev/null @@ -1,131 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::io::Write; - -use crate::error::InvalidParameterError; - -/// All unambiguous letters, thus easy to type on the alphabetic keyboards on phones without extra shift taps. -/// The letters 'l' and 'u' are skipped. -const BASE24_ALPHABET: [u8; 24] = [ - b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'v', b'w', b'x', b'y', b'z', -]; -/// Reverse table for BASE24 alphabet, indexed relative to 'a' or 'A'. -const BASE24_ALPHABET_INV: [u8; 26] = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 255, 11, 12, 13, 14, 15, 16, 17, 18, 255, 19, 20, 21, 22, 23, -]; - -/// Encode a byte slice into base24 ASCII format (no padding) -pub fn encode_into(mut b: &[u8], s: &mut String) { - while b.len() >= 4 { - let mut n = u32::from_le_bytes(b[..4].try_into().unwrap()); - for _ in 0..6 { - s.push(BASE24_ALPHABET[(n % 24) as usize] as char); - n /= 24; - } - s.push(BASE24_ALPHABET[n as usize] as char); - b = &b[4..]; - } - - if !b.is_empty() { - let mut n = 0u32; - for i in 0..b.len() { - n |= (b[i] as u32).wrapping_shl((i as u32) * 8); - } - for _ in 0..(b.len() * 2) { - s.push(BASE24_ALPHABET[(n % 24) as usize] as char); - n /= 24; - } - } -} - -fn decode_up_to_u32(s: &[u8]) -> Result { - let mut n = 0u32; - for c in s.iter().rev() { - let mut c = *c; - if (97..=122).contains(&c) { - c -= 97; - } else if (65..=90).contains(&c) { - c -= 65; - } else { - return Err(InvalidParameterError("invalid base24 character")); - } - let i = BASE24_ALPHABET_INV[c as usize]; - if i == 255 { - return Err(InvalidParameterError("invalid base24 character")); - } - n *= 24; - n = n.wrapping_add(i as u32); - } - Ok(n) -} - -/// Decode a base24 ASCII slice into bytes (no padding, length determines output length) -pub fn decode_into(s: &[u8], b: &mut W) -> Result<(), InvalidParameterError> { - let mut s = s; - - while s.len() >= 7 { - let _ = b.write_all(&decode_up_to_u32(&s[..7])?.to_le_bytes()); - s = &s[7..]; - } - - if !s.is_empty() { - let _ = b.write_all( - &decode_up_to_u32(s)?.to_le_bytes()[..match s.len() { - 2 => 1, - 4 => 2, - 6 => 3, - _ => return Err(InvalidParameterError("invalid base24 length")), - }], - ); - } - - Ok(()) -} - -#[inline] -pub fn decode_into_slice(s: &[u8], mut b: &mut [u8]) -> Result<(), InvalidParameterError> { - decode_into(s, &mut b) -} - -pub fn encode(b: &[u8]) -> String { - let mut tmp = String::with_capacity(((b.len() / 4) * 7) + 2); - encode_into(b, &mut tmp); - tmp -} - -pub fn decode(s: &[u8]) -> Result, InvalidParameterError> { - let mut tmp = Vec::with_capacity(((s.len() / 7) * 4) + 2); - decode_into(s, &mut tmp)?; - Ok(tmp) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn encode_decode() { - let mut tmp = [0xffu8; 256]; - for _ in 0..3 { - let mut s = String::with_capacity(1024); - let mut v: Vec = Vec::with_capacity(256); - for i in 1..256 { - s.clear(); - encode_into(&tmp[..i], &mut s); - //println!("{}", s); - v.clear(); - decode_into(s.as_str().as_bytes(), &mut v).expect("decode error"); - assert!(v.as_slice().eq(&tmp[..i])); - } - for b in tmp.iter_mut() { - *b -= 3; - } - } - } -} diff --git a/src/utils/src/base62.rs b/src/utils/src/base62.rs deleted file mode 100644 index 344ac27..0000000 --- a/src/utils/src/base62.rs +++ /dev/null @@ -1,187 +0,0 @@ -use std::io::Write; - -use super::arrayvec::ArrayVec; -use super::memory; - -const MAX_LENGTH_WORDS: usize = 128; - -/// Encode a byte array into a base62 string. -/// -/// The pad_output_to_length parameter outputs base62 zeroes at the end to ensure that the output -/// string is at least a given length. Set this to zero if you don't want to pad the output. This -/// has no effect on decoded output length. -pub fn encode_into(b: &[u8], s: &mut String, pad_output_to_length: usize) { - assert!(b.len() <= MAX_LENGTH_WORDS * 4); - let mut n: ArrayVec = ArrayVec::new(); - - let mut i = 0; - let len_words = b.len() & usize::MAX.wrapping_shl(2); - while i < len_words { - n.push(u32::from_le(memory::load_raw(&b[i..]))); - i += 4; - } - if i < b.len() { - let mut w = 0u32; - let mut shift = 0u32; - while i < b.len() { - w |= (b[i] as u32).wrapping_shl(shift); - i += 1; - shift += 8; - } - n.push(w); - } - - let mut string_len = 0; - while !n.is_empty() { - s.push(b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"[big_div_rem::(&mut n) as usize] as char); - string_len += 1; - } - while string_len < pad_output_to_length { - s.push('0'); - string_len += 1; - } -} - -/// Decode Base62 into a vector or other output. -/// -/// Note that base62 doesn't have a way to know the output length. Decoding may be short if there were -/// trailing zeroes in the input. The output length parameter specifies the expected length of the -/// output, which will be zero padded if decoded data does not reach it. If decoded data exceeds this -/// length an error is returned. -pub fn decode_into(s: &[u8], b: &mut W, output_length: usize) -> std::io::Result<()> { - let mut n: ArrayVec = ArrayVec::new(); - - for c in s.iter().rev() { - let mut c = *c as u32; - // 0..9, A..Z, or a..z - if (48..=57).contains(&c) { - c -= 48; - } else if (65..=90).contains(&c) { - c -= 65 - 10; - } else if (97..=122).contains(&c) { - c -= 97 - (10 + 26); - } else { - return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid base62")); - } - big_mul::(&mut n); - big_add(&mut n, c); - } - - let mut bc = output_length; - for w in n.iter() { - if bc > 0 { - let l = bc.min(4); - b.write_all(&w.to_le_bytes()[..l])?; - bc -= l; - } else { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "data too large")); - } - } - while bc > 0 { - b.write_all(&[0])?; - bc -= 1; - } - - Ok(()) -} - -#[inline] -pub fn decode_into_slice(s: &[u8], mut b: &mut [u8]) -> std::io::Result<()> { - let l = b.len(); - decode_into(s, &mut b, l) -} - -/// Decode into and return an array whose length is the desired output_length. -/// None is returned if there is an error. -#[inline] -pub fn decode(s: &[u8]) -> Option<[u8; L]> { - let mut buf = [0u8; L]; - let mut w = &mut buf[..]; - if decode_into(s, &mut w, L).is_ok() { - Some(buf) - } else { - None - } -} - -#[inline(always)] -fn big_div_rem(n: &mut ArrayVec) -> u32 { - while let Some(&0) = n.last() { - n.pop(); - } - let mut rem = 0; - for word in n.iter_mut().rev() { - let temp = (rem as u64).wrapping_shl(32) | (*word as u64); - let (a, b) = (temp / D, temp % D); - *word = a as u32; - rem = b as u32; - } - while let Some(&0) = n.last() { - n.pop(); - } - rem -} - -#[inline(always)] -fn big_add(n: &mut ArrayVec, i: u32) { - let mut carry = i as u64; - for word in n.iter_mut() { - let res = (*word as u64).wrapping_add(carry); - *word = res as u32; - carry = res.wrapping_shr(32); - } - if carry > 0 { - n.push(carry as u32); - } -} - -#[inline(always)] -fn big_mul(n: &mut ArrayVec) { - while let Some(&0) = n.last() { - n.pop(); - } - let mut carry = 0; - for word in n.iter_mut() { - let temp = (*word as u64).wrapping_mul(M).wrapping_add(carry); - *word = temp as u32; - carry = temp.wrapping_shr(32); - } - if carry != 0 { - n.push(carry as u32); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn div_rem() { - let mut n = ArrayVec::::new(); - n.push_slice(&[0xdeadbeef, 0xfeedfeed, 0xcafebabe, 0xf00dd00d]); - let rem = big_div_rem::<4, 63>(&mut n); - let nn = n.as_ref(); - assert!(nn[0] == 0xaa23440b && nn[1] == 0xa696103c && nn[2] == 0x89513fea && nn[3] == 0x03cf7514 && rem == 58); - } - - #[test] - fn encode_decode() { - let mut test = [0xff; 64]; - for tl in 1..64 { - let test = &mut test[..tl]; - test.fill(0xff); - let mut b = Vec::with_capacity(1024); - for _ in 0..10 { - let mut s = String::with_capacity(1024); - encode_into(test, &mut s, 86); - b.clear(); - //println!("{}", s); - assert!(decode_into(s.as_bytes(), &mut b, test.len()).is_ok()); - assert_eq!(b.as_slice(), test); - for c in test.iter_mut() { - *c = crate::rand() as u8; - } - } - } - } -} diff --git a/src/utils/src/blob.rs b/src/utils/src/blob.rs deleted file mode 100644 index 46430a8..0000000 --- a/src/utils/src/blob.rs +++ /dev/null @@ -1,150 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::fmt::Debug; -use std::hash::Hash; - -use serde::ser::SerializeTuple; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -use crate::hex; - -/// Fixed size Serde serializable byte array. -/// This makes it easier to deal with blobs larger than 32 bytes (due to serde array limitations) -#[repr(transparent)] -#[derive(Clone, Eq, PartialEq)] -pub struct Blob([u8; L]); - -impl Blob { - #[inline(always)] - pub fn as_bytes(&self) -> &[u8; L] { - &self.0 - } - - #[inline(always)] - pub const fn len(&self) -> usize { - L - } -} - -impl From<[u8; L]> for Blob { - #[inline(always)] - fn from(a: [u8; L]) -> Self { - Self(a) - } -} - -impl From<&[u8; L]> for Blob { - #[inline(always)] - fn from(a: &[u8; L]) -> Self { - Self(*a) - } -} - -impl Default for Blob { - #[inline(always)] - fn default() -> Self { - unsafe { std::mem::zeroed() } - } -} - -impl AsRef<[u8; L]> for Blob { - #[inline(always)] - fn as_ref(&self) -> &[u8; L] { - &self.0 - } -} - -impl AsMut<[u8; L]> for Blob { - #[inline(always)] - fn as_mut(&mut self) -> &mut [u8; L] { - &mut self.0 - } -} - -impl ToString for Blob { - #[inline(always)] - fn to_string(&self) -> String { - hex::to_string(&self.0) - } -} - -impl PartialOrd for Blob { - #[inline(always)] - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Ord for Blob { - #[inline(always)] - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl Hash for Blob { - #[inline(always)] - fn hash(&self, state: &mut H) { - self.0.hash(state); - } -} - -impl Debug for Blob { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.to_string().as_str()) - } -} - -impl Serialize for Blob { - #[inline] - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut t = serializer.serialize_tuple(L)?; - for i in self.0.iter() { - t.serialize_element(i)?; - } - t.end() - } -} - -struct BlobVisitor; - -impl<'de, const L: usize> serde::de::Visitor<'de> for BlobVisitor { - type Value = Blob; - - #[inline] - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str(format!("array of {} bytes", L).as_str()) - } - - #[inline] - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - let mut blob = Blob::::default(); - for i in 0..L { - blob.0[i] = seq.next_element()?.ok_or_else(|| serde::de::Error::invalid_length(i, &self))?; - } - Ok(blob) - } -} - -impl<'de, const L: usize> Deserialize<'de> for Blob { - #[inline] - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_tuple(L, BlobVisitor::) - } -} diff --git a/src/utils/src/buffer.rs b/src/utils/src/buffer.rs deleted file mode 100644 index 2626803..0000000 --- a/src/utils/src/buffer.rs +++ /dev/null @@ -1,752 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::error::Error; -use std::fmt::{Debug, Display}; -use std::io::{Read, Write}; -use std::mem::{size_of, MaybeUninit}; - -use crate::memory; -use crate::pool::PoolFactory; -use crate::unlikely_branch; -use crate::varint; - -const OUT_OF_BOUNDS_MSG: &str = "Buffer access out of bounds"; - -pub struct OutOfBoundsError; - -impl Display for OutOfBoundsError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(OUT_OF_BOUNDS_MSG) - } -} - -impl Debug for OutOfBoundsError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt(self, f) - } -} - -impl Error for OutOfBoundsError {} - -impl From for std::io::Error { - fn from(_: OutOfBoundsError) -> Self { - std::io::Error::new(std::io::ErrorKind::Other, OUT_OF_BOUNDS_MSG) - } -} - -/// An I/O buffer with extensions for efficiently reading and writing various objects. -/// -/// WARNING: Structures can only be handled through raw read/write here if they are -/// tagged a Copy, meaning they are safe to just copy as raw memory. Care must also -/// be taken to ensure that access to them is safe on architectures that do not support -/// unaligned access. In vl1/protocol.rs this is accomplished by only using byte arrays -/// (including for integers) and accessing via things like u64::from_be_bytes() etc. -/// -/// Needless to say anything with non-Copy internal members or that depends on Drop to -/// not leak resources or other higher level semantics won't work here, but Rust should -/// not let you tag that as Copy in safe code. -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct Buffer(usize, [u8; L]); - -impl Default for Buffer { - #[inline(always)] - fn default() -> Self { - unsafe { std::mem::zeroed() } - } -} - -impl Buffer { - pub const CAPACITY: usize = L; - - /// Create an empty zeroed buffer. - #[inline(always)] - pub fn new() -> Self { - unsafe { std::mem::zeroed() } - } - - /// Create an empty zeroed buffer on the heap without intermediate stack allocation. - /// This can be used to allocate buffers too large for the stack. - #[inline(always)] - pub fn new_boxed() -> Box { - unsafe { Box::from_raw(std::alloc::alloc_zeroed(std::alloc::Layout::new::()).cast()) } - } - - /// Create an empty buffer without internally zeroing its memory. - /// - /// This is unsafe because unwritten memory in the buffer will have undefined contents. - /// This means that some of the append_X_get_mut() functions may return mutable references to - /// undefined memory contents rather than zeroed memory. - #[inline(always)] - pub unsafe fn new_without_memzero() -> Self { - Self(0, MaybeUninit::uninit().assume_init()) - } - - pub const fn capacity(&self) -> usize { - Self::CAPACITY - } - - pub fn from_bytes(b: &[u8]) -> Result { - let l = b.len(); - if l <= L { - let mut tmp = Self::new(); - tmp.0 = l; - tmp.1[0..l].copy_from_slice(b); - Ok(tmp) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn as_bytes(&self) -> &[u8] { - &self.1[..self.0] - } - - #[inline(always)] - pub fn as_bytes_mut(&mut self) -> &mut [u8] { - &mut self.1[..self.0] - } - - #[inline(always)] - pub fn as_ptr(&self) -> *const u8 { - self.1.as_ptr() - } - - #[inline(always)] - pub fn as_mut_ptr(&mut self) -> *mut u8 { - self.1.as_mut_ptr() - } - - #[inline(always)] - pub fn as_bytes_starting_at(&self, start: usize) -> Result<&[u8], OutOfBoundsError> { - if start <= self.0 { - Ok(&self.1[start..self.0]) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn as_bytes_starting_at_mut(&mut self, start: usize) -> Result<&mut [u8], OutOfBoundsError> { - if start <= self.0 { - Ok(&mut self.1[start..self.0]) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn as_byte_range(&self, start: usize, end: usize) -> Result<&[u8], OutOfBoundsError> { - if end <= self.0 { - Ok(&self.1[start..end]) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn clear(&mut self) { - self.1[0..self.0].fill(0); - self.0 = 0; - } - - /// Load array into buffer. - /// This will panic if the array is larger than L. - #[inline(always)] - pub fn set_to(&mut self, b: &[u8]) { - let len = b.len(); - self.0 = len; - self.1[0..len].copy_from_slice(b); - } - - #[inline(always)] - pub fn len(&self) -> usize { - self.0 - } - - #[inline(always)] - pub fn is_empty(&self) -> bool { - self.0 == 0 - } - - /// Set the size of this buffer's data. - /// - /// This will panic if the specified size is larger than L. If the size is larger - /// than the current size uninitialized space will be zeroed. - pub fn set_size(&mut self, s: usize) { - let prev_len = self.0; - self.0 = s; - if s > prev_len { - self.1[prev_len..s].fill(0); - } - } - - /// Get a mutable reference to the entire buffer regardless of the current 'size'. - #[inline(always)] - pub unsafe fn entire_buffer_mut(&mut self) -> &mut [u8; L] { - &mut self.1 - } - - /// Set the size of the data in this buffer without checking bounds or zeroing new space. - #[inline(always)] - pub unsafe fn set_size_unchecked(&mut self, s: usize) { - self.0 = s; - } - - /// Get a byte from this buffer without checking bounds. - #[inline(always)] - pub unsafe fn get_unchecked(&self, i: usize) -> u8 { - *self.1.get_unchecked(i) - } - - /// Erase the first N bytes of this buffer, copying remaining bytes to the front. - pub fn erase_first_n(&mut self, i: usize) -> Result<(), OutOfBoundsError> { - if i < self.0 { - let l = self.0; - self.1.copy_within(i..l, 0); - self.0 = l - i; - Ok(()) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - /// Append a structure and return a mutable reference to its memory. - #[inline(always)] - pub fn append_struct_get_mut(&mut self) -> Result<&mut T, OutOfBoundsError> { - let ptr = self.0; - let end = ptr + size_of::(); - if end <= L { - self.0 = end; - Ok(unsafe { &mut *self.1.as_mut_ptr().add(ptr).cast() }) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - /// Append a fixed size array and return a mutable reference to its memory. - #[inline(always)] - pub fn append_bytes_fixed_get_mut(&mut self) -> Result<&mut [u8; S], OutOfBoundsError> { - let ptr = self.0; - let end = ptr + S; - if end <= L { - self.0 = end; - Ok(unsafe { &mut *self.1.as_mut_ptr().add(ptr).cast() }) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - /// Append a runtime sized array and return a mutable reference to its memory. - #[inline(always)] - pub fn append_bytes_get_mut(&mut self, s: usize) -> Result<&mut [u8], OutOfBoundsError> { - let ptr = self.0; - let end = ptr + s; - if end <= L { - self.0 = end; - Ok(&mut self.1[ptr..end]) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn append_padding(&mut self, b: u8, count: usize) -> Result<(), OutOfBoundsError> { - let ptr = self.0; - let end = ptr + count; - if end <= L { - self.0 = end; - self.1[ptr..end].fill(b); - Ok(()) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn append_bytes(&mut self, buf: &[u8]) -> Result<(), OutOfBoundsError> { - let ptr = self.0; - let end = ptr + buf.len(); - if end <= L { - self.0 = end; - self.1[ptr..end].copy_from_slice(buf); - Ok(()) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn append_bytes_fixed(&mut self, buf: &[u8; S]) -> Result<(), OutOfBoundsError> { - let ptr = self.0; - let end = ptr + S; - if end <= L { - self.0 = end; - self.1[ptr..end].copy_from_slice(buf); - Ok(()) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn append_u8(&mut self, i: u8) -> Result<(), OutOfBoundsError> { - let ptr = self.0; - if ptr < L { - self.0 = ptr + 1; - self.1[ptr] = i; - Ok(()) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn append_u16(&mut self, i: u16) -> Result<(), OutOfBoundsError> { - let ptr = self.0; - let end = ptr + 2; - if end <= L { - self.0 = end; - memory::store_raw(i.to_be(), &mut self.1[ptr..]); - Ok(()) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn append_u32(&mut self, i: u32) -> Result<(), OutOfBoundsError> { - let ptr = self.0; - let end = ptr + 4; - if end <= L { - self.0 = end; - memory::store_raw(i.to_be(), &mut self.1[ptr..]); - Ok(()) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn append_u64(&mut self, i: u64) -> Result<(), OutOfBoundsError> { - let ptr = self.0; - let end = ptr + 8; - if end <= L { - self.0 = end; - memory::store_raw(i.to_be(), &mut self.1[ptr..]); - Ok(()) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn append_u64_le(&mut self, i: u64) -> Result<(), OutOfBoundsError> { - let ptr = self.0; - let end = ptr + 8; - if end <= L { - self.0 = end; - memory::store_raw(i.to_be(), &mut self.1[ptr..]); - Ok(()) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - pub fn append_varint(&mut self, i: u64) -> Result<(), OutOfBoundsError> { - if varint::write(self, i).is_ok() { - Ok(()) - } else { - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn bytes_fixed_at(&self, ptr: usize) -> Result<&[u8; S], OutOfBoundsError> { - if (ptr + S) <= self.0 { - unsafe { Ok(&*self.1.as_ptr().cast::().add(ptr).cast::<[u8; S]>()) } - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn bytes_fixed_mut_at(&mut self, ptr: usize) -> Result<&mut [u8; S], OutOfBoundsError> { - if (ptr + S) <= self.0 { - unsafe { Ok(&mut *self.1.as_mut_ptr().cast::().add(ptr).cast::<[u8; S]>()) } - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn struct_at(&self, ptr: usize) -> Result<&T, OutOfBoundsError> { - if (ptr + size_of::()) <= self.0 { - unsafe { Ok(&*self.1.as_ptr().cast::().add(ptr).cast::()) } - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn struct_mut_at(&mut self, ptr: usize) -> Result<&mut T, OutOfBoundsError> { - if (ptr + size_of::()) <= self.0 { - unsafe { Ok(&mut *self.1.as_mut_ptr().cast::().add(ptr).cast::()) } - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn u8_at(&self, ptr: usize) -> Result { - if ptr < self.0 { - Ok(self.1[ptr]) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn u16_at(&self, ptr: usize) -> Result { - let end = ptr + 2; - debug_assert!(end <= L); - if end <= self.0 { - Ok(u16::from_be(memory::load_raw(&self.1[ptr..]))) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn u32_at(&self, ptr: usize) -> Result { - let end = ptr + 4; - debug_assert!(end <= L); - if end <= self.0 { - Ok(u32::from_be(memory::load_raw(&self.1[ptr..]))) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn u64_at(&self, ptr: usize) -> Result { - let end = ptr + 8; - debug_assert!(end <= L); - if end <= self.0 { - Ok(u64::from_be(memory::load_raw(&self.1[ptr..]))) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn read_struct(&self, cursor: &mut usize) -> Result<&T, OutOfBoundsError> { - let ptr = *cursor; - let end = ptr + size_of::(); - debug_assert!(end <= L); - if end <= self.0 { - *cursor = end; - unsafe { Ok(&*self.1.as_ptr().cast::().add(ptr).cast::()) } - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn read_bytes_fixed(&self, cursor: &mut usize) -> Result<&[u8; S], OutOfBoundsError> { - let ptr = *cursor; - let end = ptr + S; - debug_assert!(end <= L); - if end <= self.0 { - *cursor = end; - unsafe { Ok(&*self.1.as_ptr().cast::().add(ptr).cast::<[u8; S]>()) } - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn read_bytes(&self, l: usize, cursor: &mut usize) -> Result<&[u8], OutOfBoundsError> { - let ptr = *cursor; - let end = ptr + l; - debug_assert!(end <= L); - if end <= self.0 { - *cursor = end; - Ok(&self.1[ptr..end]) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - pub fn read_varint(&self, cursor: &mut usize) -> Result { - let c = *cursor; - if c < self.0 { - let mut a = &self.1[c..]; - varint::read(&mut a) - .map(|r| { - *cursor = c + r.1; - debug_assert!(*cursor <= self.0); - r.0 - }) - .map_err(|_| OutOfBoundsError) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn read_u8(&self, cursor: &mut usize) -> Result { - let ptr = *cursor; - debug_assert!(ptr < L); - if ptr < self.0 { - *cursor = ptr + 1; - Ok(self.1[ptr]) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn read_u16(&self, cursor: &mut usize) -> Result { - let ptr = *cursor; - let end = ptr + 2; - debug_assert!(end <= L); - if end <= self.0 { - *cursor = end; - Ok(u16::from_be(memory::load_raw(&self.1[ptr..]))) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn read_u32(&self, cursor: &mut usize) -> Result { - let ptr = *cursor; - let end = ptr + 4; - debug_assert!(end <= L); - if end <= self.0 { - *cursor = end; - Ok(u32::from_be(memory::load_raw(&self.1[ptr..]))) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } - - #[inline(always)] - pub fn read_u64(&self, cursor: &mut usize) -> Result { - let ptr = *cursor; - let end = ptr + 8; - debug_assert!(end <= L); - if end <= self.0 { - *cursor = end; - Ok(u64::from_be(memory::load_raw(&self.1[ptr..]))) - } else { - unlikely_branch(); - Err(OutOfBoundsError) - } - } -} - -impl Write for Buffer { - #[inline(always)] - fn write(&mut self, buf: &[u8]) -> std::io::Result { - let ptr = self.0; - let end = ptr + buf.len(); - if end <= L { - self.0 = end; - self.1[ptr..end].copy_from_slice(buf); - Ok(buf.len()) - } else { - unlikely_branch(); - Err(std::io::Error::new(std::io::ErrorKind::Other, OUT_OF_BOUNDS_MSG)) - } - } - - #[inline(always)] - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} - -impl AsRef<[u8]> for Buffer { - #[inline(always)] - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -impl AsMut<[u8]> for Buffer { - #[inline(always)] - fn as_mut(&mut self) -> &mut [u8] { - self.as_bytes_mut() - } -} - -impl From<[u8; L]> for Buffer { - #[inline(always)] - fn from(a: [u8; L]) -> Self { - Self(L, a) - } -} - -impl From<&[u8; L]> for Buffer { - #[inline(always)] - fn from(a: &[u8; L]) -> Self { - Self(L, *a) - } -} - -/// Implements std::io::Read for a buffer and a cursor. -pub struct BufferReader<'a, 'b, const L: usize>(&'a Buffer, &'b mut usize); - -impl<'a, 'b, const L: usize> BufferReader<'a, 'b, L> { - #[inline(always)] - pub fn new(b: &'a Buffer, cursor: &'b mut usize) -> Self { - Self(b, cursor) - } -} - -impl<'a, 'b, const L: usize> Read for BufferReader<'a, 'b, L> { - #[inline(always)] - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - buf.copy_from_slice(self.0.read_bytes(buf.len(), self.1)?); - Ok(buf.len()) - } -} - -pub struct PooledBufferFactory; - -impl PooledBufferFactory { - #[inline(always)] - pub fn new() -> Self { - Self {} - } -} - -impl PoolFactory> for PooledBufferFactory { - #[inline(always)] - fn create(&self) -> Buffer { - Buffer::new() - } - - #[inline(always)] - fn reset(&self, obj: &mut Buffer) { - obj.clear(); - } -} - -#[cfg(test)] -mod tests { - use super::Buffer; - - #[test] - fn buffer_basic_u64() { - let mut b = Buffer::<8>::new(); - assert_eq!(b.len(), 0); - assert!(b.is_empty()); - assert!(b.append_u64(1234).is_ok()); - assert_eq!(b.len(), 8); - assert!(!b.is_empty()); - assert_eq!(b.read_u64(&mut 0).unwrap(), 1234); - b.clear(); - assert_eq!(b.len(), 0); - assert!(b.is_empty()); - } - - #[test] - fn buffer_basic_u32() { - let mut b = Buffer::<4>::new(); - assert_eq!(b.len(), 0); - assert!(b.is_empty()); - assert!(b.append_u32(1234).is_ok()); - assert_eq!(b.len(), 4); - assert!(!b.is_empty()); - assert_eq!(b.read_u32(&mut 0).unwrap(), 1234); - b.clear(); - assert_eq!(b.len(), 0); - assert!(b.is_empty()); - } - - #[test] - fn buffer_basic_u16() { - let mut b = Buffer::<2>::new(); - assert_eq!(b.len(), 0); - assert!(b.is_empty()); - assert!(b.append_u16(1234).is_ok()); - assert_eq!(b.len(), 2); - assert!(!b.is_empty()); - assert_eq!(b.read_u16(&mut 0).unwrap(), 1234); - b.clear(); - assert_eq!(b.len(), 0); - assert!(b.is_empty()); - } - - #[test] - fn buffer_basic_u8() { - let mut b = Buffer::<1>::new(); - assert_eq!(b.len(), 0); - assert!(b.is_empty()); - assert!(b.append_u8(128).is_ok()); - assert_eq!(b.len(), 1); - assert!(!b.is_empty()); - assert_eq!(b.read_u8(&mut 0).unwrap(), 128); - b.clear(); - assert_eq!(b.len(), 0); - assert!(b.is_empty()); - } - - #[test] - fn buffer_sizing() { - const SIZE: usize = 100; - - for _ in 0..1000 { - let v = [0u8; SIZE]; - let mut b = Buffer::::new(); - assert!(b.append_bytes(&v).is_ok()); - assert_eq!(b.len(), SIZE); - b.set_size(10); - assert_eq!(b.len(), 10); - unsafe { - b.set_size_unchecked(8675309); - } - assert_eq!(b.len(), 8675309); - } - } -} diff --git a/src/utils/src/canonicalarc.rs b/src/utils/src/canonicalarc.rs deleted file mode 100644 index c1c7d42..0000000 --- a/src/utils/src/canonicalarc.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::borrow::Borrow; -use std::hash::{Hash, Hasher}; -use std::ops::Deref; -use std::sync::Arc; - -/// Wrapper around an Arc that causes it to hash and compare (equality) by its pointer identity. -/// -/// This can be used as e.g. a key in a HashMap to index by concrete object identity rather than the -/// value of the object contained in Arc<>. -#[repr(transparent)] -pub struct CanonicalArc(Arc); - -impl CanonicalArc { - #[inline(always)] - pub fn cast_arc_ref(r: &Arc) -> &Self { - // Should be safe since this is #[repr(transparent)] - debug_assert_eq!(std::mem::size_of::>(), std::mem::size_of::>()); - unsafe { std::mem::transmute(r) } - } -} - -impl Hash for CanonicalArc { - #[inline(always)] - fn hash(&self, state: &mut H) { - Arc::as_ptr(&self.0).hash(state) - } -} - -impl PartialEq for CanonicalArc { - #[inline(always)] - fn eq(&self, other: &Self) -> bool { - Arc::ptr_eq(&self.0, &other.0) - } -} - -impl Eq for CanonicalArc {} - -impl AsRef> for CanonicalArc { - #[inline(always)] - fn as_ref(&self) -> &Arc { - &self.0 - } -} - -impl Deref for CanonicalArc { - type Target = Arc; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From> for CanonicalArc { - #[inline(always)] - fn from(value: Arc) -> Self { - Self(value) - } -} - -impl From> for Arc { - #[inline(always)] - fn from(value: CanonicalArc) -> Self { - value.0 - } -} - -impl Clone for CanonicalArc { - #[inline(always)] - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl Borrow> for CanonicalArc { - #[inline(always)] - fn borrow(&self) -> &Arc { - &self.0 - } -} diff --git a/src/utils/src/cast.rs b/src/utils/src/cast.rs deleted file mode 100644 index 38406aa..0000000 --- a/src/utils/src/cast.rs +++ /dev/null @@ -1,36 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::any::TypeId; -use std::mem::size_of; - -/// Returns true if two types are in fact the same type. -#[inline(always)] -pub fn same_type() -> bool { - TypeId::of::() == TypeId::of::() && size_of::() == size_of::() -} - -/// Cast a reference if the types are equal, such as from a specific type to a generic that it implements. -#[inline(always)] -pub fn cast_ref(u: &U) -> Option<&V> { - if same_type::() { - Some(unsafe { std::mem::transmute::<&U, &V>(u) }) - } else { - None - } -} - -/// Cast a reference if the types are equal, such as from a specific type to a generic that it implements. -#[inline(always)] -pub fn cast_mut(u: &mut U) -> Option<&mut V> { - if same_type::() { - Some(unsafe { std::mem::transmute::<&mut U, &mut V>(u) }) - } else { - None - } -} diff --git a/src/utils/src/defer.rs b/src/utils/src/defer.rs deleted file mode 100644 index 90e398e..0000000 --- a/src/utils/src/defer.rs +++ /dev/null @@ -1,25 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -struct Defer(Option); - -impl Drop for Defer { - fn drop(&mut self) { - if let Some(f) = self.0.take() { - f() - } - } -} - -/// Defer execution of a closure until the return value is dropped. -/// -/// This mimics the defer statement in Go, allowing you to always do some cleanup at -/// the end of a function no matter where it exits. -pub fn defer(f: F) -> impl Drop { - Defer(Some(f)) -} diff --git a/src/utils/src/dictionary.rs b/src/utils/src/dictionary.rs deleted file mode 100644 index 393b1c8..0000000 --- a/src/utils/src/dictionary.rs +++ /dev/null @@ -1,209 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::collections::BTreeMap; -use std::io::Write; - -use crate::hex; - -const BOOL_TRUTH: &str = "1tTyY"; - -/// Dictionary is an extremely simple key=value serialization format. -/// -/// It's designed for extreme parsing simplicity and is human readable if keys and values are strings. -/// It also supports binary keys and values which will be minimally escaped but render the result not -/// entirely human readable. Keys are serialized in natural sort order so the result can be consistently -/// checksummed or hashed. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Dictionary(pub(crate) BTreeMap>); - -fn write_escaped(mut b: &[u8], w: &mut W) -> std::io::Result<()> { - while !b.is_empty() { - match b[0] { - 0 => { - w.write_all(&[b'\\', b'0'])?; - } - b'\n' => { - w.write_all(&[b'\\', b'n'])?; - } - b'\r' => { - w.write_all(&[b'\\', b'r'])?; - } - b'=' => { - w.write_all(&[b'\\', b'e'])?; - } - b'\\' => { - w.write_all(&[b'\\', b'\\'])?; - } - _ => { - w.write_all(&b[..1])?; - } - } - b = &b[1..]; - } - Ok(()) -} - -fn append_printable(s: &mut String, b: &[u8]) { - for c in b { - let c = *c as char; - if c.is_alphanumeric() || c.is_whitespace() { - s.push(c); - } else { - s.push('\\'); - s.push('x'); - s.push(hex::HEX_CHARS[((c as u8) >> 4) as usize] as char); - s.push(hex::HEX_CHARS[((c as u8) & 0xf) as usize] as char); - } - } -} - -impl Dictionary { - pub fn new() -> Self { - Self(BTreeMap::new()) - } - - pub fn clear(&mut self) { - self.0.clear() - } - - #[inline(always)] - pub fn len(&self) -> usize { - self.0.len() - } - - #[inline(always)] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - pub fn get_str(&self, k: &str) -> Option<&str> { - self.0.get(k).and_then(|v| std::str::from_utf8(v.as_slice()).ok()) - } - - pub fn get_bytes(&self, k: &str) -> Option<&[u8]> { - self.0.get(k).map(|v| v.as_slice()) - } - - pub fn get_u64(&self, k: &str) -> Option { - self.get_str(k).and_then(|s| u64::from_str_radix(s, 16).ok()) - } - - pub fn get_i64(&self, k: &str) -> Option { - self.get_str(k).and_then(|s| i64::from_str_radix(s, 16).ok()) - } - - pub fn get_bool(&self, k: &str) -> Option { - self.0 - .get(k) - .and_then(|v| v.first().map_or(Some(false), |c| Some(BOOL_TRUTH.contains(*c as char)))) - } - - pub fn set_str(&mut self, k: &str, v: &str) { - let _ = self.0.insert(String::from(k), v.as_bytes().to_vec()); - } - - pub fn set_u64(&mut self, k: &str, v: u64) { - let _ = self.0.insert(String::from(k), hex::to_vec_u64(v, true)); - } - - pub fn set_bytes(&mut self, k: &str, v: Vec) { - let _ = self.0.insert(String::from(k), v); - } - - pub fn set_bool(&mut self, k: &str, v: bool) { - let _ = self.0.insert( - String::from(k), - vec![if v { - b'1' - } else { - b'0' - }], - ); - } - - pub fn write_to(&self, w: &mut W) -> std::io::Result<()> { - for kv in self.0.iter() { - write_escaped(kv.0.as_bytes(), w)?; - w.write_all(&[b'='])?; - write_escaped(kv.1.as_slice(), w)?; - w.write_all(&[b'\n'])?; - } - Ok(()) - } - - pub fn to_bytes(&self) -> Vec { - let mut b: Vec = Vec::with_capacity(32 * self.0.len()); - let _ = self.write_to(&mut b); - b - } - - pub fn from_bytes(b: &[u8]) -> Option { - let mut d = Dictionary::new(); - let mut kv: [Vec; 2] = [Vec::new(), Vec::new()]; - let mut state = 0; - let mut escape = false; - for c in b { - let c = *c; - if escape { - escape = false; - kv[state].push(match c { - b'0' => 0, - b'n' => b'\n', - b'r' => b'\r', - b'e' => b'=', - _ => c, // =, \, and escapes before other characters are unnecessary but not errors - }); - } else if c == b'\\' { - escape = true; - } else if c == b'=' { - if state != 0 { - return None; - } - state = 1; - } else if c == b'\n' { - if state != 1 { - return None; - } - state = 0; - if !kv[0].is_empty() - && String::from_utf8(kv[0].clone()).map_or(true, |key| { - d.0.insert(key, kv[1].clone()); - false - }) - { - return None; - } - kv[0].clear(); - kv[1].clear(); - } else if c != b'\r' { - kv[state].push(c); - } - } - Some(d) - } - - pub fn iter(&self) -> impl Iterator)> { - self.0.iter() - } -} - -impl ToString for Dictionary { - /// Get the dictionary in an always readable format with non-printable characters replaced by '\xXX'. - /// This is not a serializable output that can be re-imported. Use write_to() for that. - fn to_string(&self) -> String { - let mut s = String::new(); - for kv in self.0.iter() { - append_printable(&mut s, kv.0.as_bytes()); - s.push('='); - append_printable(&mut s, kv.1.as_slice()); - s.push('\n'); - } - s - } -} diff --git a/src/utils/src/error.rs b/src/utils/src/error.rs deleted file mode 100644 index a958e77..0000000 --- a/src/utils/src/error.rs +++ /dev/null @@ -1,61 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::error::Error; -use std::fmt::{Debug, Display}; - -pub struct UnexpectedError; - -impl Display for UnexpectedError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("UnexpectedError") - } -} - -impl Debug for UnexpectedError { - #[inline(always)] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - ::fmt(self, f) - } -} - -impl Error for UnexpectedError {} - -pub struct InvalidFormatError; - -impl Display for InvalidFormatError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("InvalidFormatError") - } -} - -impl Debug for InvalidFormatError { - #[inline(always)] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - ::fmt(self, f) - } -} - -impl Error for InvalidFormatError {} - -pub struct InvalidParameterError(pub &'static str); - -impl Display for InvalidParameterError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "InvalidParameterError: {}", self.0) - } -} - -impl Debug for InvalidParameterError { - #[inline(always)] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - ::fmt(self, f) - } -} - -impl Error for InvalidParameterError {} diff --git a/src/utils/src/exitcode.rs b/src/utils/src/exitcode.rs deleted file mode 100644 index 7cb96c3..0000000 --- a/src/utils/src/exitcode.rs +++ /dev/null @@ -1,22 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -// These were taken from BSD sysexits.h to provide some standard for process exit codes. - -pub const OK: i32 = 0; - -pub const ERR_USAGE: i32 = 64; -pub const ERR_DATA_FORMAT: i32 = 65; -pub const ERR_NO_INPUT: i32 = 66; -pub const ERR_SERVICE_UNAVAILABLE: i32 = 69; -pub const ERR_INTERNAL: i32 = 70; -pub const ERR_OSERR: i32 = 71; -pub const ERR_OSFILE: i32 = 72; -pub const ERR_IOERR: i32 = 74; -pub const ERR_NOPERM: i32 = 77; -pub const ERR_CONFIG: i32 = 78; diff --git a/src/utils/src/flatsortedmap.rs b/src/utils/src/flatsortedmap.rs deleted file mode 100644 index 9e22f59..0000000 --- a/src/utils/src/flatsortedmap.rs +++ /dev/null @@ -1,86 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::borrow::Cow; -use std::iter::{FromIterator, Iterator}; - -use serde::{Deserialize, Serialize}; - -/// A simple flat sorted map backed by a vector and binary search. -/// -/// This doesn't support gradual adding of keys or removal of keys, but only construction -/// from an iterator of keys and values. It also implements Serialize and Deserialize and -/// is mainly intended for memory and space efficient serializable lookup tables. -/// -/// If the iterator supplies more than one key with different values, which of these is -/// included is undefined. -#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] -#[repr(transparent)] -pub struct FlatSortedMap<'a, K: Eq + Ord + Clone, V: Clone>(Cow<'a, [(K, V)]>); - -impl<'a, K: Eq + Ord + Clone, V: Clone> FromIterator<(K, V)> for FlatSortedMap<'a, K, V> { - #[inline] - fn from_iter>(iter: T) -> Self { - let mut tmp = Vec::from_iter(iter); - tmp.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - tmp.dedup_by(|a, b| a.0.eq(&b.0)); - Self(Cow::Owned(tmp)) - } -} - -impl<'a, K: Eq + Ord + Clone, V: Clone> Default for FlatSortedMap<'a, K, V> { - #[inline(always)] - fn default() -> Self { - Self(Cow::Owned(Vec::new())) - } -} - -impl<'a, K: Eq + Ord + Clone, V: Clone> FlatSortedMap<'a, K, V> { - #[inline] - pub fn get(&self, k: &K) -> Option<&V> { - if let Ok(idx) = self.0.binary_search_by(|a| a.0.cmp(k)) { - Some(unsafe { &self.0.get_unchecked(idx).1 }) - } else { - None - } - } - - #[inline] - pub fn contains(&self, k: &K) -> bool { - self.0.binary_search_by(|a| a.0.cmp(k)).is_ok() - } - - /// Returns true if this map is valid, meaning that it contains only one of each key and is sorted. - #[inline] - pub fn is_valid(&self) -> bool { - let l = self.0.len(); - if l > 1 { - for i in 1..l { - if unsafe { !self.0.get_unchecked(i - 1).0.cmp(&self.0.get_unchecked(i).0).is_lt() } { - return false; - } - } - } - true - } - - #[inline(always)] - pub fn iter(&self) -> impl Iterator { - self.0.iter() - } - - #[inline(always)] - pub fn len(&self) -> usize { - self.0.len() - } - - #[inline(always)] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} diff --git a/src/utils/src/gate.rs b/src/utils/src/gate.rs deleted file mode 100644 index e3a8aa6..0000000 --- a/src/utils/src/gate.rs +++ /dev/null @@ -1,35 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -/// Boolean rate limiter with normal (non-atomic) semantics. -#[repr(transparent)] -pub struct IntervalGate(i64); - -impl Default for IntervalGate { - #[inline(always)] - fn default() -> Self { - Self(crate::NEVER_HAPPENED_TICKS) - } -} - -impl IntervalGate { - #[inline(always)] - pub fn new(initial_ts: i64) -> Self { - Self(initial_ts) - } - - #[inline(always)] - pub fn gate(&mut self, time: i64) -> bool { - if (time - self.0) >= FREQ { - self.0 = time; - true - } else { - false - } - } -} diff --git a/src/utils/src/hex.rs b/src/utils/src/hex.rs deleted file mode 100644 index 0fbad1d..0000000 --- a/src/utils/src/hex.rs +++ /dev/null @@ -1,124 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -pub const HEX_CHARS: [u8; 16] = [ - b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f', -]; - -/// Encode a byte slice to a hexadecimal string. -pub fn to_string(b: &[u8]) -> String { - let mut s = String::with_capacity(b.len() * 2); - s.reserve(b.len() * 2); - for c in b { - let x = *c as usize; - s.push(HEX_CHARS[x >> 4] as char); - s.push(HEX_CHARS[x & 0xf] as char); - } - s -} - -/// Encode an unsigned 64-bit value as a hexadecimal string. -pub fn to_string_u64(mut i: u64, skip_leading_zeroes: bool) -> String { - let mut s = String::with_capacity(16); - for _ in 0..16 { - let ii = i >> 60; - if ii != 0 || !s.is_empty() || !skip_leading_zeroes { - s.push(HEX_CHARS[ii as usize] as char); - } - i = i.wrapping_shl(4); - } - s -} - -/// Encode an unsigned 64-bit value as a hexadecimal ASCII string. -pub fn to_vec_u64(mut i: u64, skip_leading_zeroes: bool) -> Vec { - let mut s = Vec::with_capacity(16); - for _ in 0..16 { - let ii = i >> 60; - if ii != 0 || !s.is_empty() || !skip_leading_zeroes { - s.push(HEX_CHARS[ii as usize]); - } - i = i.wrapping_shl(4); - } - s -} - -/// Decode a hex string, ignoring all non-hexadecimal characters. -pub fn from_string(s: &str) -> Vec { - let mut b: Vec = Vec::with_capacity((s.len() / 2) + 1); - let mut byte = 0_u8; - let mut have_8: bool = false; - for cc in s.as_bytes() { - let c = *cc; - if (48..=57).contains(&c) { - byte = (byte.wrapping_shl(4)) | (c - 48); - if have_8 { - b.push(byte); - } - have_8 = !have_8; - } else if (65..=70).contains(&c) { - byte = (byte.wrapping_shl(4)) | (c - 55); - if have_8 { - b.push(byte); - } - have_8 = !have_8; - } else if (97..=102).contains(&c) { - byte = (byte.wrapping_shl(4)) | (c - 87); - if have_8 { - b.push(byte); - } - have_8 = !have_8; - } - } - b -} - -pub fn from_string_u64(s: &str) -> u64 { - let mut n = 0u64; - let mut byte = 0_u8; - let mut have_8: bool = false; - for cc in s.as_bytes() { - let c = *cc; - if (48..=57).contains(&c) { - byte = (byte.wrapping_shl(4)) | (c - 48); - if have_8 { - n = n.wrapping_shl(8); - n |= byte as u64; - } - have_8 = !have_8; - } else if (65..=70).contains(&c) { - byte = (byte.wrapping_shl(4)) | (c - 55); - if have_8 { - n = n.wrapping_shl(8); - n |= byte as u64; - } - have_8 = !have_8; - } else if (97..=102).contains(&c) { - byte = (byte.wrapping_shl(4)) | (c - 87); - if have_8 { - n = n.wrapping_shl(8); - n |= byte as u64; - } - have_8 = !have_8; - } - } - n -} - -/// Encode bytes from 'b' into hex characters in 'dest' and return the number of hex characters written. -/// This will panic if the destination slice is smaller than twice the length of the source. -pub fn to_hex_bytes(b: &[u8], dest: &mut [u8]) -> usize { - let mut j = 0; - for c in b { - let x = *c as usize; - dest[j] = HEX_CHARS[x >> 4]; - dest[j + 1] = HEX_CHARS[x & 0xf]; - j += 2; - } - j -} diff --git a/src/utils/src/io.rs b/src/utils/src/io.rs deleted file mode 100644 index a67f83b..0000000 --- a/src/utils/src/io.rs +++ /dev/null @@ -1,48 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::fs::File; -use std::io::Read; -use std::path::Path; - -/// Default sanity limit parameter for read_limit() used throughout the service. -pub const DEFAULT_FILE_IO_READ_LIMIT: usize = 262144; - -/// Convenience function to read up to limit bytes from a file. -/// -/// If the file is larger than limit, the excess is not read. -pub fn read_limit>(path: P, limit: usize) -> std::io::Result> { - let mut f = File::open(path)?; - let bytes = f.metadata()?.len().min(limit as u64) as usize; - let mut v: Vec = Vec::with_capacity(bytes); - v.resize(bytes, 0); - f.read_exact(v.as_mut_slice())?; - Ok(v) -} - -/// Set permissions on a file or directory to be most restrictive (visible only to the service's user). -#[cfg(unix)] -pub fn fs_restrict_permissions>(path: P) -> bool { - unsafe { - let c_path = std::ffi::CString::new(path.as_ref().to_str().unwrap()).unwrap(); - libc::chmod( - c_path.as_ptr(), - if path.as_ref().is_dir() { - 0o700 - } else { - 0o600 - }, - ) == 0 - } -} - -/// Set permissions on a file or directory to be most restrictive (visible only to the service's user). -#[cfg(windows)] -pub fn fs_restrict_permissions>(path: P) -> bool { - todo!() -} diff --git a/src/utils/src/json.rs b/src/utils/src/json.rs deleted file mode 100644 index 63f63e0..0000000 --- a/src/utils/src/json.rs +++ /dev/null @@ -1,202 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use serde::de::DeserializeOwned; -use serde::Serialize; -use serde_json::ser::Formatter; - -/// Recursively patch a JSON object. -/// -/// This is slightly different from a usual JSON merge. For objects in the target their fields -/// are updated by recursively calling json_patch if the same field is present in the source. -/// If the source tries to set an object to something other than another object, this is ignored. -/// Other fields are replaced. This is used for RESTful config object updates. The depth limit -/// field is to prevent stack overflows via the API. -pub fn json_patch(target: &mut serde_json::value::Value, source: &serde_json::value::Value, depth_limit: usize) { - if target.is_object() { - if source.is_object() { - let target = target.as_object_mut().unwrap(); - let source = source.as_object().unwrap(); - for kv in target.iter_mut() { - let _ = source.get(kv.0).map(|new_value| { - if depth_limit > 0 { - json_patch(kv.1, new_value, depth_limit - 1) - } - }); - } - for kv in source.iter() { - if !target.contains_key(kv.0) && !kv.1.is_null() { - target.insert(kv.0.clone(), kv.1.clone()); - } - } - } - } else if *target != *source { - *target = source.clone(); - } -} - -/// Patch a serializable object with the fields present in a JSON object. -/// -/// If there are no changes, None is returned. The depth limit is passed through to json_patch and -/// should be set to a sanity check value to prevent overflows. -pub fn json_patch_object(obj: O, patch: &str, depth_limit: usize) -> Result, serde_json::Error> { - serde_json::from_str::(patch).map_or_else(Err, |patch| { - serde_json::value::to_value(&obj).map_or_else(Err, |mut obj_value| { - json_patch(&mut obj_value, &patch, depth_limit); - serde_json::value::from_value::(obj_value).map_or_else(Err, |obj_merged| { - if obj == obj_merged { - Ok(None) - } else { - Ok(Some(obj_merged)) - } - }) - }) - }) -} - -/// Shortcut to use serde_json to serialize an object, returns "null" on error. -pub fn to_json(o: &O) -> String { - serde_json::to_string(o).unwrap_or("null".into()) -} - -/// Shortcut to use serde_json to serialize an object, returns "null" on error. -pub fn to_json_pretty(o: &O) -> String { - let mut buf = Vec::new(); - let mut ser = serde_json::Serializer::with_formatter(&mut buf, PrettyFormatter::new()); - if o.serialize(&mut ser).is_ok() { - String::from_utf8(buf).unwrap_or_else(|_| "null".into()) - } else { - "null".into() - } -} - -/// JSON formatter that looks a bit better than the Serde default. -pub struct PrettyFormatter<'a> { - current_indent: usize, - has_value: bool, - indent: &'a [u8], -} - -fn indent(wr: &mut W, n: usize, s: &[u8]) -> std::io::Result<()> -where - W: ?Sized + std::io::Write, -{ - for _ in 0..n { - wr.write_all(s)?; - } - Ok(()) -} - -impl<'a> PrettyFormatter<'a> { - pub fn new() -> Self { - Self::with_indent(b" ") - } - - pub fn with_indent(indent: &'a [u8]) -> Self { - Self { current_indent: 0, has_value: false, indent } - } -} - -impl<'a> Default for PrettyFormatter<'a> { - fn default() -> Self { - Self::new() - } -} - -impl<'a> Formatter for PrettyFormatter<'a> { - fn begin_array(&mut self, writer: &mut W) -> std::io::Result<()> - where - W: ?Sized + std::io::Write, - { - self.current_indent += 1; - self.has_value = false; - writer.write_all(b"[") - } - - fn end_array(&mut self, writer: &mut W) -> std::io::Result<()> - where - W: ?Sized + std::io::Write, - { - self.current_indent -= 1; - if self.has_value { - writer.write_all(b" ]") - } else { - writer.write_all(b"]") - } - } - - fn begin_array_value(&mut self, writer: &mut W, first: bool) -> std::io::Result<()> - where - W: ?Sized + std::io::Write, - { - if first { - writer.write_all(b" ")?; - } else { - writer.write_all(b", ")?; - } - Ok(()) - } - - fn end_array_value(&mut self, _writer: &mut W) -> std::io::Result<()> - where - W: ?Sized + std::io::Write, - { - self.has_value = true; - Ok(()) - } - - fn begin_object(&mut self, writer: &mut W) -> std::io::Result<()> - where - W: ?Sized + std::io::Write, - { - self.current_indent += 1; - self.has_value = false; - writer.write_all(b"{") - } - - fn end_object(&mut self, writer: &mut W) -> std::io::Result<()> - where - W: ?Sized + std::io::Write, - { - self.current_indent -= 1; - - if self.has_value { - writer.write_all(b"\n")?; - indent(writer, self.current_indent, self.indent)?; - } - - writer.write_all(b"}") - } - - fn begin_object_key(&mut self, writer: &mut W, first: bool) -> std::io::Result<()> - where - W: ?Sized + std::io::Write, - { - if first { - writer.write_all(b"\n")?; - } else { - writer.write_all(b",\n")?; - } - indent(writer, self.current_indent, self.indent) - } - - fn begin_object_value(&mut self, writer: &mut W) -> std::io::Result<()> - where - W: ?Sized + std::io::Write, - { - writer.write_all(b": ") - } - - fn end_object_value(&mut self, _writer: &mut W) -> std::io::Result<()> - where - W: ?Sized + std::io::Write, - { - self.has_value = true; - Ok(()) - } -} diff --git a/src/utils/src/lib.rs b/src/utils/src/lib.rs deleted file mode 100644 index 7ea9b63..0000000 --- a/src/utils/src/lib.rs +++ /dev/null @@ -1,114 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -pub mod arrayvec; -pub mod base24; -pub mod base62; -pub mod blob; -pub mod buffer; -pub mod canonicalarc; -pub mod cast; -pub mod defer; -pub mod dictionary; -pub mod error; -#[allow(unused)] -pub mod exitcode; -pub mod flatsortedmap; -pub mod gate; -pub mod hex; -pub mod indexed_heap; -pub mod io; -pub mod json; -pub mod marshalable; -pub mod memory; -pub mod pool; -#[cfg(feature = "tokio")] -pub mod reaper; -pub mod ringbuffer; -pub mod rwu_lock; -pub mod str; -pub mod sync; -pub mod varint; - -#[cfg(feature = "tokio")] -pub use tokio; - -/// Initial value that should be used for monotonic tick time variables. -pub const NEVER_HAPPENED_TICKS: i64 = i64::MIN; - -/// Get milliseconds since unix epoch. -#[inline] -pub fn ms_since_epoch() -> i64 { - std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as i64 -} - -/// Get milliseconds since an arbitrary time in the past, guaranteed to monotonically increase. -#[inline] -pub fn ms_monotonic() -> i64 { - static STARTUP_INSTANT: std::sync::RwLock> = std::sync::RwLock::new(None); - let si = *STARTUP_INSTANT.read().unwrap(); - if let Some(si) = si { - si.elapsed().as_millis() as i64 - } else { - STARTUP_INSTANT - .write() - .unwrap() - .get_or_insert(std::time::Instant::now()) - .elapsed() - .as_millis() as i64 - } -} - -/// Wait for a kill signal (e.g. SIGINT or OS-equivalent) sent to this process and return when received. -#[cfg(unix)] -pub fn wait_for_process_abort() { - if let Ok(mut signals) = signal_hook::iterator::Signals::new([libc::SIGINT, libc::SIGTERM, libc::SIGQUIT]) { - 'wait_for_exit: loop { - for signal in signals.wait() { - match signal as libc::c_int { - libc::SIGINT | libc::SIGTERM | libc::SIGQUIT => { - break 'wait_for_exit; - } - _ => {} - } - } - std::thread::sleep(std::time::Duration::from_millis(100)); - } - } else { - panic!("unable to listen for OS signals"); - } -} - -#[cold] -#[inline(never)] -pub extern "C" fn unlikely_branch() {} - -#[cfg(unix)] -pub fn rand() -> u32 { - unsafe { (libc::rand() as u32) ^ (libc::rand() as u32).wrapping_shr(8) } -} - -#[cfg(test)] -mod tests { - use super::ms_monotonic; - use std::time::Duration; - - #[test] - fn monotonic_clock_sanity_check() { - let start = ms_monotonic(); - std::thread::sleep(Duration::from_millis(500)); - let end = ms_monotonic(); - // per docs: - // - // The thread may sleep longer than the duration specified due to scheduling specifics or - // platform-dependent functionality. It will never sleep less. - // - assert!((end - start).abs() >= 500); - assert!((end - start).abs() < 750); - } -} diff --git a/src/utils/src/marshalable.rs b/src/utils/src/marshalable.rs deleted file mode 100644 index 24a1bde..0000000 --- a/src/utils/src/marshalable.rs +++ /dev/null @@ -1,169 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::error::Error; -use std::fmt::{Debug, Display}; - -use crate::buffer::{Buffer, OutOfBoundsError}; - -/// A super-lightweight zero-allocation serialization interface. -pub trait Marshalable: Sized { - const MAX_MARSHAL_SIZE: usize; - - /// Write this object into a buffer. - fn marshal(&self, buf: &mut Buffer) -> Result<(), OutOfBoundsError>; - - /// Read this object from a buffer. - /// - /// The supplied cursor is advanced by the number of bytes read. If an Err is returned - /// the value of the cursor is undefined but likely points to about where the error - /// occurred. It may also point beyond the buffer, which would indicate an overrun error. - fn unmarshal(buf: &Buffer, cursor: &mut usize) -> Result; - - /// Write this marshalable entity into a buffer of the given size. - /// - /// This will return an Err if the buffer is too small or some other error occurs. It's just - /// a shortcut to creating a buffer and marshaling into it. - #[inline] - fn to_buffer(&self) -> Result, OutOfBoundsError> { - let mut tmp = Buffer::new(); - self.marshal(&mut tmp)?; - Ok(tmp) - } - - /* - /// Write this marshalable entity into a buffer of the given size. - /// - /// This will return an Err if the buffer is too small or some other error occurs. It's just - /// a shortcut to creating a buffer and marshaling into it. - #[inline] - fn to_buffer(&self) -> Result, UnmarshalError> { - let mut tmp = Buffer::new(); - self.marshal(&mut tmp)?; - Ok(tmp) - } - - /// Unmarshal this object from a buffer. - /// - /// This is just a shortcut to calling unmarshal() with a zero cursor and then discarding the cursor. - #[inline] - fn from_buffer(buf: &Buffer) -> Result { - let mut tmp = 0; - Self::unmarshal(buf, &mut tmp) - } - - /// Marshal and convert to a Rust vector. - #[inline] - fn to_bytes(&self) -> Vec { - assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE); - let mut tmp = Buffer::::new(); - assert!(self.marshal(&mut tmp).is_ok()); // panics if TEMP_BUF_SIZE is too small - tmp.as_bytes().to_vec() - } - - /// Unmarshal from a raw slice. - #[inline] - fn from_bytes(b: &[u8]) -> Result { - if b.len() <= TEMP_BUF_SIZE { - let mut tmp = Buffer::::new_boxed(); - assert!(tmp.append_bytes(b).is_ok()); - let mut cursor = 0; - Self::unmarshal(&tmp, &mut cursor) - } else { - Err(UnmarshalError::OutOfBounds) - } - } - - /// Marshal a slice of marshalable objects to a concatenated byte vector. - #[inline] - fn marshal_multiple_to_bytes(objects: &[Self]) -> Result, UnmarshalError> { - assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE); - let mut tmp: Buffer<{ TEMP_BUF_SIZE }> = Buffer::new(); - let mut v: Vec = Vec::with_capacity(objects.len() * Self::MAX_MARSHAL_SIZE); - for i in objects.iter() { - i.marshal(&mut tmp)?; - let _ = v.write_all(tmp.as_bytes()); - tmp.clear(); - } - Ok(v) - } - - /// Unmarshal a concatenated byte slice of marshalable objects. - #[inline] - fn unmarshal_multiple_from_bytes(mut bytes: &[u8]) -> Result, UnmarshalError> { - assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE); - let mut tmp: Buffer<{ TEMP_BUF_SIZE }> = Buffer::new(); - let mut v: Vec = Vec::new(); - while bytes.len() > 0 { - let chunk_size = bytes.len().min(Self::MAX_MARSHAL_SIZE); - if tmp.append_bytes(&bytes[..chunk_size]).is_err() { - return Err(UnmarshalError::OutOfBounds); - } - let mut cursor = 0; - v.push(Self::unmarshal(&mut tmp, &mut cursor)?); - if cursor == 0 { - return Err(UnmarshalError::InvalidData); - } - let _ = tmp.erase_first_n(cursor); - bytes = &bytes[chunk_size..]; - } - Ok(v) - } - - /// Unmarshal a buffer with a byte slice of marshalable objects. - #[inline] - fn unmarshal_multiple(buf: &Buffer, cursor: &mut usize, eof: usize) -> Result, UnmarshalError> { - let mut v: Vec = Vec::new(); - while *cursor < eof { - v.push(Self::unmarshal(buf, cursor)?); - } - Ok(v) - } - */ -} - -pub enum UnmarshalError { - OutOfBounds, - InvalidData, - UnsupportedVersion, - IoError(std::io::Error), -} - -impl Display for UnmarshalError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::OutOfBounds => f.write_str("out of bounds"), - Self::InvalidData => f.write_str("invalid data"), - Self::UnsupportedVersion => f.write_str("unsupported version"), - Self::IoError(e) => f.write_str(e.to_string().as_str()), - } - } -} - -impl Debug for UnmarshalError { - #[inline(always)] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Display::fmt(self, f) - } -} - -impl Error for UnmarshalError {} - -impl From for UnmarshalError { - #[inline(always)] - fn from(_: crate::buffer::OutOfBoundsError) -> Self { - Self::OutOfBounds - } -} - -impl From for UnmarshalError { - #[inline(always)] - fn from(e: std::io::Error) -> Self { - Self::IoError(e) - } -} diff --git a/src/utils/src/memory.rs b/src/utils/src/memory.rs deleted file mode 100644 index 841b2b5..0000000 --- a/src/utils/src/memory.rs +++ /dev/null @@ -1,121 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -// This is a collection of functions that use "unsafe" to do things with memory that should in fact -// be safe. Some of these may eventually get stable standard library replacements. - -#[allow(unused_imports)] -use std::mem::{needs_drop, size_of, MaybeUninit}; - -#[allow(unused_imports)] -use std::ptr::copy_nonoverlapping; - -/// Implement this trait to mark a struct as safe to cast from a byte array. -pub unsafe trait FlatBuffer: Sized {} - -/// Store a raw object to a byte array (for architectures known not to care about unaligned access). -/// This will panic if the slice is too small or the object requires drop. -#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64"))] -#[inline(always)] -pub fn store_raw(o: T, dest: &mut [u8]) { - assert!(!std::mem::needs_drop::()); - assert!(dest.len() >= size_of::()); - unsafe { *dest.as_mut_ptr().cast() = o }; -} - -/// Store a raw object to a byte array (portable). -/// This will panic if the slice is too small or the object requires drop. -#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))] -#[inline(always)] -pub fn store_raw(o: T, dest: &mut [u8]) { - assert!(!std::mem::needs_drop::()); - assert!(dest.len() >= size_of::()); - unsafe { copy_nonoverlapping((&o as *const T).cast(), dest.as_mut_ptr(), size_of::()) }; -} - -/// Load a raw object from a byte array (for architectures known not to care about unaligned access). -/// This will panic if the slice is too small or the object requires drop. -#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64"))] -#[inline(always)] -pub fn load_raw(src: &[u8]) -> T { - assert!(!std::mem::needs_drop::()); - assert!(src.len() >= size_of::()); - unsafe { *src.as_ptr().cast() } -} - -/// Load a raw object from a byte array (portable). -/// This will panic if the slice is too small or the object requires drop. -#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))] -#[inline(always)] -pub fn load_raw(src: &[u8]) -> T { - assert!(!std::mem::needs_drop::()); - assert!(src.len() >= size_of::()); - unsafe { - let mut tmp: T = MaybeUninit::uninit().assume_init(); - copy_nonoverlapping(src.as_ptr(), (&mut tmp as *mut T).cast(), size_of::()); - tmp - } -} - -/// Our version of the not-yet-stable array_chunks method in slice. -#[inline(always)] -pub fn array_chunks_exact(a: &[T]) -> impl Iterator { - let mut i = 0; - let l = a.len(); - std::iter::from_fn(move || { - let j = i + S; - if j <= l { - let next = unsafe { &*a.as_ptr().add(i).cast() }; - i = j; - Some(next) - } else { - None - } - }) -} - -/// Obtain a view into an array cast as another array. -/// This will panic if the template parameters would result in out of bounds access. -#[inline(always)] -pub fn array_range(a: &[T; S]) -> &[T; LEN] { - assert!((START + LEN) <= S); - unsafe { &*a.as_ptr().add(START).cast::<[T; LEN]>() } -} - -/// Get a reference to a raw object as a byte array. -/// The template parameter S must equal the size of the object in bytes or this will panic. -#[inline(always)] -pub fn as_byte_array(o: &T) -> &[u8; S] { - assert_eq!(S, size_of::()); - unsafe { &*(o as *const T).cast() } -} - -/// Get a reference to a raw object as a byte array. -/// The template parameter S must equal the size of the object in bytes or this will panic. -#[inline(always)] -pub fn as_byte_array_mut(o: &mut T) -> &mut [u8; S] { - assert_eq!(S, size_of::()); - unsafe { &mut *(o as *mut T).cast() } -} - -/// Transmute an object to a byte array. -/// The template parameter S must equal the size of the object in bytes or this will panic. -#[inline(always)] -pub fn to_byte_array(o: T) -> [u8; S] { - assert_eq!(S, size_of::()); - assert!(!std::mem::needs_drop::()); - unsafe { *(&o as *const T).cast() } -} - -/// Cast a byte slice into a flat struct. -/// This will panic if the slice is too small or the struct requires drop. -pub fn cast_to_struct(b: &[u8]) -> &T { - assert!(b.len() >= size_of::()); - assert!(!std::mem::needs_drop::()); - unsafe { &*b.as_ptr().cast() } -} diff --git a/src/utils/src/pool.rs b/src/utils/src/pool.rs deleted file mode 100644 index d8a1a5a..0000000 --- a/src/utils/src/pool.rs +++ /dev/null @@ -1,251 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::ops::{Deref, DerefMut}; -use std::ptr::NonNull; -use std::sync::{Arc, Mutex, Weak}; - -/// Each pool requires a factory that creates and resets (for re-use) pooled objects. -pub trait PoolFactory { - fn create(&self) -> O; - fn reset(&self, obj: &mut O); -} - -/// Container for pooled objects that have been checked out of the pool. -/// -/// Objects are automagically returned to the pool when Pooled<> is dropped if the pool still exists. -/// If the pool itself is gone objects are freed. Two methods for conversion to/from raw pointers are -/// available for interoperation with foreign APIs. -#[repr(transparent)] -pub struct Pooled>(NonNull>); - -#[repr(C)] -struct PoolEntry> { - obj: O, // must be first - return_pool: Weak>, -} - -impl> Pooled { - /// Create a pooled object wrapper around an object but with no pool to return it to. - /// The object will be freed when this pooled container is dropped. - #[inline] - pub fn naked(o: O) -> Self { - unsafe { - Self(NonNull::new_unchecked(Box::into_raw(Box::new(PoolEntry:: { - obj: o, - return_pool: Weak::new(), - })))) - } - } - - /// Get a raw pointer to the object wrapped by this pooled object container. - /// - /// The returned pointer MUST be returned to the pooling system with from_raw() or memory - /// will leak. - #[inline] - pub unsafe fn into_raw(self) -> *mut O { - // Verify that the structure is not padded before 'obj'. - assert_eq!( - (&self.0.as_ref().obj as *const O).cast::(), - (self.0.as_ref() as *const PoolEntry).cast::() - ); - - let ptr = self.0.as_ptr().cast::(); - std::mem::forget(self); - ptr - } - - /// Restore a raw pointer from into_raw() into a Pooled object. - /// - /// The supplied pointer MUST have been obtained from a Pooled object. None is returned - /// if the pointer is null. - #[inline] - pub unsafe fn from_raw(raw: *mut O) -> Option { - if !raw.is_null() { - Some(Self(NonNull::new_unchecked(raw.cast()))) - } else { - None - } - } -} - -impl> Clone for Pooled -where - O: Clone, -{ - #[inline] - fn clone(&self) -> Self { - let internal = unsafe { &mut *self.0.as_ptr() }; - if let Some(p) = internal.return_pool.upgrade() { - if let Some(o) = p.pool.lock().unwrap().pop() { - let mut o = Self(o); - *o.as_mut() = self.as_ref().clone(); - o - } else { - Pooled::(unsafe { - NonNull::new_unchecked(Box::into_raw(Box::new(PoolEntry:: { - obj: self.as_ref().clone(), - return_pool: Arc::downgrade(&p), - }))) - }) - } - } else { - Self::naked(self.as_ref().clone()) - } - } -} - -unsafe impl> Send for Pooled where O: Send {} -unsafe impl> Sync for Pooled where O: Sync {} - -impl> Deref for Pooled { - type Target = O; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - unsafe { &self.0.as_ref().obj } - } -} - -impl> DerefMut for Pooled { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut self.0.as_mut().obj } - } -} - -impl> AsRef for Pooled { - #[inline(always)] - fn as_ref(&self) -> &O { - unsafe { &self.0.as_ref().obj } - } -} - -impl> AsMut for Pooled { - #[inline(always)] - fn as_mut(&mut self) -> &mut O { - unsafe { &mut self.0.as_mut().obj } - } -} - -impl> Drop for Pooled { - #[inline] - fn drop(&mut self) { - let internal = unsafe { &mut *self.0.as_ptr() }; - if let Some(p) = internal.return_pool.upgrade() { - p.factory.reset(&mut internal.obj); - p.pool.lock().unwrap().push(self.0); - } else { - drop(unsafe { Box::from_raw(self.0.as_ptr()) }); - } - } -} - -/// An object pool for Reusable objects. -/// Checked out objects are held by a guard object that returns them when dropped if -/// the pool still exists or drops them if the pool has itself been dropped. -pub struct Pool>(Arc>); - -struct PoolInner> { - factory: F, - pool: Mutex>>>, -} - -impl> Pool { - #[inline] - pub fn new(initial_stack_capacity: usize, factory: F) -> Self { - Self(Arc::new(PoolInner:: { - factory, - pool: Mutex::new(Vec::with_capacity(initial_stack_capacity)), - })) - } - - /// Get a pooled object, or allocate one if the pool is empty. - #[inline] - pub fn get(&self) -> Pooled { - if let Some(o) = self.0.pool.lock().unwrap().pop() { - return Pooled::(o); - } - Pooled::(unsafe { - NonNull::new_unchecked(Box::into_raw(Box::new(PoolEntry:: { - obj: self.0.factory.create(), - return_pool: Arc::downgrade(&self.0), - }))) - }) - } - - /// Dispose of all pooled objects, freeing any memory they use. - /// - /// If get() is called after this new objects will be allocated, and any outstanding - /// objects will still be returned on drop unless the pool itself is dropped. This can - /// be done to free some memory if there has been a spike in memory use. - #[inline] - pub fn purge(&self) { - for o in self.0.pool.lock().unwrap().drain(..) { - drop(unsafe { Box::from_raw(o.as_ptr()) }) - } - } -} - -impl> Drop for Pool { - #[inline(always)] - fn drop(&mut self) { - self.purge(); - } -} - -unsafe impl> Send for Pool {} -unsafe impl> Sync for Pool {} - -#[cfg(test)] -mod tests { - use std::sync::atomic::{AtomicUsize, Ordering}; - use std::sync::Arc; - use std::time::Duration; - - use super::*; - - struct TestPoolFactory; - - impl PoolFactory for TestPoolFactory { - fn create(&self) -> String { - String::new() - } - - fn reset(&self, obj: &mut String) { - obj.clear(); - } - } - - #[test] - fn threaded_pool_use() { - let p: Arc> = Arc::new(Pool::new(2, TestPoolFactory {})); - let ctr = Arc::new(AtomicUsize::new(0)); - for _ in 0..64 { - let p2 = p.clone(); - let ctr2 = ctr.clone(); - let _ = std::thread::spawn(move || { - for _ in 0..16384 { - let mut o1 = p2.get(); - o1.push('a'); - let o2 = p2.get(); - drop(o1); - let mut o2 = unsafe { Pooled::::from_raw(o2.into_raw()).unwrap() }; - o2.push('b'); - ctr2.fetch_add(1, Ordering::Relaxed); - } - }); - } - loop { - std::thread::sleep(Duration::from_millis(100)); - if ctr.load(Ordering::Relaxed) >= 16384 * 64 { - break; - } - } - } -} diff --git a/src/utils/src/reaper.rs b/src/utils/src/reaper.rs deleted file mode 100644 index 60624c2..0000000 --- a/src/utils/src/reaper.rs +++ /dev/null @@ -1,57 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::collections::VecDeque; -use std::sync::Arc; - -use tokio::sync::Notify; -use tokio::task::JoinHandle; -use tokio::time::Instant; - -/// Watches tokio jobs and times them out if they run past a deadline or aborts them all if the reaper is dropped. -pub struct Reaper { - q: Arc<(std::sync::Mutex, Instant)>>, Notify)>, - finisher: JoinHandle<()>, -} - -impl Reaper { - pub fn new(runtime: &tokio::runtime::Handle) -> Self { - let q = Arc::new((std::sync::Mutex::new(VecDeque::with_capacity(16)), Notify::new())); - Self { - q: q.clone(), - finisher: runtime.spawn(async move { - loop { - q.1.notified().await; - loop { - let j = q.0.lock().unwrap().pop_front(); - if let Some(j) = j { - let _ = tokio::time::timeout_at(j.1, j.0).await; - } else { - break; - } - } - } - }), - } - } - - /// Add a job to be executed with timeout at a given instant. - #[inline] - pub fn add(&self, job: JoinHandle<()>, deadline: Instant) { - self.q.0.lock().unwrap().push_back((job, deadline)); - self.q.1.notify_waiters(); - } -} - -impl Drop for Reaper { - #[inline] - fn drop(&mut self) { - self.finisher.abort(); - self.q.0.lock().unwrap().drain(..).for_each(|j| j.0.abort()); - } -} diff --git a/src/utils/src/ringbuffer.rs b/src/utils/src/ringbuffer.rs deleted file mode 100644 index 225fbf5..0000000 --- a/src/utils/src/ringbuffer.rs +++ /dev/null @@ -1,120 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::mem::MaybeUninit; - -/// A FIFO ring buffer. -pub struct RingBuffer { - a: [MaybeUninit; C], - p: usize, -} - -impl RingBuffer { - #[inline] - pub fn new() -> Self { - #[allow(invalid_value)] - let mut tmp: Self = unsafe { MaybeUninit::uninit().assume_init() }; - tmp.p = 0; - tmp - } - - /// Add an element to the buffer, replacing old elements if full. - #[inline] - pub fn add(&mut self, o: T) { - let p = self.p; - if p < C { - unsafe { self.a.get_unchecked_mut(p).write(o) }; - } else { - unsafe { *self.a.get_unchecked_mut(p % C).assume_init_mut() = o }; - } - self.p = p.wrapping_add(1); - } - - /// Clear the buffer and drop all elements. - #[inline] - pub fn clear(&mut self) { - for i in 0..C.min(self.p) { - unsafe { self.a.get_unchecked_mut(i).assume_init_drop() }; - } - self.p = 0; - } - - /// Gets an iterator that dumps the contents of the buffer in FIFO order. - #[inline] - pub fn iter(&self) -> RingBufferIterator<'_, T, C> { - let s = C.min(self.p); - RingBufferIterator { b: self, s, i: self.p.wrapping_sub(s) } - } -} - -impl Default for RingBuffer { - #[inline(always)] - fn default() -> Self { - Self::new() - } -} - -impl Drop for RingBuffer { - #[inline(always)] - fn drop(&mut self) { - self.clear(); - } -} - -pub struct RingBufferIterator<'a, T, const C: usize> { - b: &'a RingBuffer, - s: usize, - i: usize, -} - -impl<'a, T, const C: usize> Iterator for RingBufferIterator<'a, T, C> { - type Item = &'a T; - - #[inline] - fn next(&mut self) -> Option { - let s = self.s; - if s > 0 { - let i = self.i; - self.s = s.wrapping_sub(1); - self.i = i.wrapping_add(1); - Some(unsafe { self.b.a.get_unchecked(i % C).assume_init_ref() }) - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn fifo() { - let mut tmp: RingBuffer = RingBuffer::new(); - let mut tmp2 = Vec::new(); - for i in 0..4 { - tmp.add(i); - tmp2.push(i); - } - for (i, j) in tmp.iter().zip(tmp2.iter()) { - assert_eq!(*i, *j); - } - tmp.clear(); - tmp2.clear(); - for i in 0..23 { - tmp.add(i); - tmp2.push(i); - } - while tmp2.len() > 8 { - tmp2.remove(0); - } - for (i, j) in tmp.iter().zip(tmp2.iter()) { - assert_eq!(*i, *j); - } - } -} diff --git a/src/utils/src/rwu_lock.rs b/src/utils/src/rwu_lock.rs deleted file mode 100644 index 17d9488..0000000 --- a/src/utils/src/rwu_lock.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::{ - ops::{Deref, DerefMut}, - sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}, -}; - -/// A wrapper around a `std::sync::RwLock` that allows for atomic upgrades of read locks to write locks. -/// This wrapped struct does not check for lock poisoning. It is assumed that the user will never allow a lock to become poisoned. -/// -/// See the documentation of `std::sync::RwLock` for more details. -pub struct RwuLock(RwLock<(usize, T)>); - -impl RwuLock { - pub fn new(t: T) -> Self { - Self(RwLock::new((0, t))) - } - pub fn read<'a>(&'a self) -> RwuLockReadGuard<'a, T> { - RwuLockReadGuard(self.0.read().unwrap()) - } - pub fn write<'a>(&'a self) -> RwuLockWriteGuard<'a, T> { - let mut w = self.0.write().unwrap(); - w.0 = w.0.wrapping_add(1); - RwuLockWriteGuard(w) - } - - pub fn upgrade<'a, 'b>(&'a self, r: RwuLockReadGuard<'b, T>) -> Option> { - let write_id = r.0 .0; - drop(r); - let mut w = self.0.write().unwrap(); - if w.0 == write_id { - w.0 = w.0.wrapping_add(1); - Some(RwuLockWriteGuard(w)) - } else { - None - } - } -} - -/// RAII structure used to release the shared read access of a lock when dropped. -/// Can be atomically upgraded to a `RwuLockWriteGuard` with `RwuLock::upgrade`. -pub struct RwuLockReadGuard<'a, T>(RwLockReadGuard<'a, (usize, T)>); - -impl<'a, T> Deref for RwuLockReadGuard<'a, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 .1 - } -} -/// RAII structure used to release the exclusive write access of a lock when dropped. -pub struct RwuLockWriteGuard<'a, T>(RwLockWriteGuard<'a, (usize, T)>); - -impl<'a, T> Deref for RwuLockWriteGuard<'a, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 .1 - } -} -impl<'a, T> DerefMut for RwuLockWriteGuard<'a, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 .1 - } -} diff --git a/src/utils/src/str.rs b/src/utils/src/str.rs deleted file mode 100644 index adcc3f8..0000000 --- a/src/utils/src/str.rs +++ /dev/null @@ -1,56 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use crate::hex::HEX_CHARS; - -/// Escape non-ASCII-printable characters in a string. -/// This also escapes quotes and other sensitive characters that cause issues on terminals. -pub fn escape(b: &[u8]) -> String { - let mut s = String::with_capacity(b.len() * 2); - for b in b.iter() { - let b = *b; - if (43..=126).contains(&b) && b != 92 && b != 96 { - s.push(b as char); - } else { - s.push('\\'); - s.push(HEX_CHARS[(b.wrapping_shr(4) & 0xf) as usize] as char); - s.push(HEX_CHARS[(b & 0xf) as usize] as char); - } - } - s -} - -/// Unescape a string with \XX hexadecimal escapes. -pub fn unescape(s: &str) -> Vec { - let mut b = Vec::with_capacity(s.len()); - let mut s = s.as_bytes(); - while let Some(c) = s.first() { - let c = *c; - if c == b'\\' { - if s.len() < 3 { - break; - } - let mut cc = 0u8; - for c in [s[1], s[2]] { - if (48..=57).contains(&c) { - cc = cc.wrapping_shl(4) | (c - 48); - } else if (65..=70).contains(&c) { - cc = cc.wrapping_shl(4) | (c - 55); - } else if (97..=102).contains(&c) { - cc = cc.wrapping_shl(4) | (c - 87); - } - } - b.push(cc); - s = &s[3..]; - } else { - b.push(c); - s = &s[1..]; - } - } - b -} diff --git a/src/utils/src/sync.rs b/src/utils/src/sync.rs deleted file mode 100644 index 58da56e..0000000 --- a/src/utils/src/sync.rs +++ /dev/null @@ -1,47 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; - -/// Variant version of lock for RwLock with automatic conversion to a write lock as needed. -pub enum RMaybeWLockGuard<'a, T> { - R(Option>), - W(RwLockWriteGuard<'a, T>), -} - -impl<'a, T> RMaybeWLockGuard<'a, T> { - #[inline(always)] - pub fn new_read(l: &'a RwLock) -> Self { - Self::R(Some(l.read().unwrap())) - } - - /// Get a readable reference to the object. - #[inline] - pub fn read(&self) -> &T { - match self { - Self::R(r) => r.as_ref().unwrap(), - Self::W(w) => w, - } - } - - /// Get a writable reference to the object, converting this to a write lock if needed. - #[inline] - pub fn write(&mut self, l: &'a RwLock) -> &mut T { - match self { - Self::R(r) => { - let _ = r.take(); - *self = Self::W(l.write().unwrap()); - match self { - Self::W(w) => &mut *w, - _ => panic!(), - } - } - Self::W(w) => &mut *w, - } - } -} diff --git a/src/utils/src/varint.rs b/src/utils/src/varint.rs deleted file mode 100644 index d868aeb..0000000 --- a/src/utils/src/varint.rs +++ /dev/null @@ -1,118 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::io::{Read, Write}; - -pub const VARINT_MAX_SIZE_BYTES: usize = 10; - -/// Encode an integer as a varint. -/// -/// WARNING: if the supplied byte slice does not have at least 10 bytes available this may panic. -/// This is checked in debug mode by an assertion. -pub fn encode(b: &mut [u8], mut v: u64) -> usize { - debug_assert!(b.len() >= VARINT_MAX_SIZE_BYTES); - let mut i = 0; - loop { - if v > 0x7f { - b[i] = (v as u8) & 0x7f; - i += 1; - v = v.wrapping_shr(7); - } else { - b[i] = (v as u8) | 0x80; - i += 1; - break; - } - } - i -} - -/// Write a variable length integer, which can consume up to 10 bytes. -#[inline(always)] -pub fn write(w: &mut W, v: u64) -> std::io::Result<()> { - let mut b = [0_u8; VARINT_MAX_SIZE_BYTES]; - let i = encode(&mut b, v); - w.write_all(&b[0..i]) -} - -/// Dencode up to 10 bytes as a varint. -/// -/// if the supplied byte slice does not contain a valid varint encoding this will return None. -/// if the supplied byte slice is shorter than expected this will return None. -pub fn decode(b: &[u8]) -> Option<(u64, usize)> { - let mut v = 0_u64; - let mut pos = 0; - let mut i = 0_usize; - while i < b.len() && i < VARINT_MAX_SIZE_BYTES { - let b = b[i]; - i += 1; - if b <= 0x7f { - v |= (b as u64).wrapping_shl(pos); - pos += 7; - } else { - v |= ((b & 0x7f) as u64).wrapping_shl(pos); - return Some((v, i)); - } - } - None -} - -/// Read a variable length integer, returning the value and the number of bytes written. -pub fn read(r: &mut R) -> std::io::Result<(u64, usize)> { - let mut v = 0_u64; - let mut buf = [0_u8; 1]; - let mut pos = 0; - let mut i = 0_usize; - loop { - r.read_exact(&mut buf)?; - let b = buf[0]; - i += 1; - if b <= 0x7f { - v |= (b as u64).wrapping_shl(pos); - pos += 7; - } else { - v |= ((b & 0x7f) as u64).wrapping_shl(pos); - return Ok((v, i)); - } - } -} - -/// A container for an encoded varint. Use as_ref() to get bytes. -pub struct Encoded([u8; VARINT_MAX_SIZE_BYTES], u8); - -impl Encoded { - #[inline(always)] - pub fn from(v: u64) -> Encoded { - let mut e = Encoded([0_u8; VARINT_MAX_SIZE_BYTES], 0); - e.1 = encode(&mut e.0, v) as u8; - e - } -} - -impl AsRef<[u8]> for Encoded { - #[inline(always)] - fn as_ref(&self) -> &[u8] { - &self.0[0..(self.1 as usize)] - } -} - -#[cfg(test)] -mod tests { - use crate::varint::*; - - #[test] - fn varint() { - let mut t: Vec = Vec::new(); - for i in 0..131072 { - t.clear(); - let ii = (u64::MAX / 131072) * i; - assert!(write(&mut t, ii).is_ok()); - let mut t2 = t.as_slice(); - assert_eq!(read(&mut t2).unwrap().0, ii); - } - } -} From 39061dbea3c3fe080738ee698c53cf101aa4db70 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 12 Jul 2023 14:50:27 -0400 Subject: [PATCH 04/91] rexported deps --- src/crypto/aes_gcm.rs | 2 +- src/crypto/mod.rs | 4 ++++ src/lib.rs | 2 -- src/proto.rs | 2 +- src/zssp.rs | 9 ++++----- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/crypto/aes_gcm.rs b/src/crypto/aes_gcm.rs index 0f97e1a..250c587 100644 --- a/src/crypto/aes_gcm.rs +++ b/src/crypto/aes_gcm.rs @@ -2,7 +2,7 @@ pub const AES_GCM_TAG_SIZE: usize = 16; pub const AES_GCM_IV_SIZE: usize = 12; -pub const AES_GCM_KEY_SIZE: usize = 32; +pub const AES_GCM_KEY_SIZE: usize = super::aes::AES_256_KEY_SIZE; /// Implementations of this trait does not have to be Send + Sync, /// but if it is wrapped in a `Mutex` it must satisfy the requirements of Send + Sync. diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 71af726..1b2a3bd 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -6,6 +6,10 @@ pub mod sha512; pub mod p384; pub mod secret; +pub use pqc_kyber; +pub use pqc_kyber::RngCore; +pub use pqc_kyber::CryptoRng; + /// Constant time byte slice equality. #[inline] pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { diff --git a/src/lib.rs b/src/lib.rs index fcfb868..13d2f3b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,8 +5,6 @@ * (c) ZeroTier, Inc. * https://www.zerotier.com/ */ -pub const AES_BLOCK_SIZE: usize = 16; - pub mod crypto; mod applicationlayer; diff --git a/src/proto.rs b/src/proto.rs index 121b132..12208fe 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -10,7 +10,7 @@ use std::hash::Hasher; use std::mem::size_of; use hex_literal::hex; -use pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; +use crate::crypto::pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; use crate::crypto::aes_gcm::AES_GCM_TAG_SIZE; use crate::crypto::sha512::{Sha512, SHA512_HASH_SIZE}; use crate::crypto::p384::P384_PUBLIC_KEY_SIZE; diff --git a/src/zssp.rs b/src/zssp.rs index 6d3492c..7616ac7 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -22,17 +22,16 @@ use crate::crypto::p384::{P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE, P3 use crate::crypto::secret::Secret; use crate::crypto::secure_eq; use crate::crypto::sha512::Sha512; +use crate::crypto::pqc_kyber::{KYBER_SECRETKEYBYTES, RngCore}; -use pqc_kyber::{KYBER_SECRETKEYBYTES, RngCore}; -use crate::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; - +use crate::proto::*; use crate::applicationlayer::*; +use crate::log_event::LogEvent; use crate::error::{FaultType, OpenError, ReceiveError, SendError}; +use crate::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; use crate::frag_cache::UnassociatedFragCache; use crate::fragged::{Assembled, Fragged}; use crate::handshake_cache::UnassociatedHandshakeCache; -use crate::log_event::LogEvent; -use crate::proto::*; use crate::symmetric_state::SymmetricState; /// Session context for local application. From cfd3bd578cef088e3c6949091a9f9ba9444979d2 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 12 Jul 2023 14:51:11 -0400 Subject: [PATCH 05/91] cargo fmt --- src/applicationlayer.rs | 11 ++- src/crypto/mod.rs | 4 +- src/lib.rs | 2 +- src/proto.rs | 6 +- src/symmetric_state.rs | 10 ++- src/zssp.rs | 168 ++++++++++++++++++++++++++-------------- 6 files changed, 128 insertions(+), 73 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 782d002..70a922e 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -8,11 +8,11 @@ use std::sync::Arc; -use crate::crypto::aes::{AesEnc, AesDec}; -use crate::crypto::aes_gcm::{AesGcmEnc, AesGcmDec}; -use crate::crypto::sha512::{Sha512, HmacSha512}; +use crate::crypto::aes::{AesDec, AesEnc}; +use crate::crypto::aes_gcm::{AesGcmDec, AesGcmEnc}; +use crate::crypto::p384::{P384KeyPair, P384PublicKey}; +use crate::crypto::sha512::{HmacSha512, Sha512}; use crate::{log_event::LogEvent, Session, RATCHET_FINGERPRINT_SIZE, RATCHET_KEY_SIZE}; -use crate::crypto::p384::{P384PublicKey, P384KeyPair}; /// Trait to implement to integrate the session into an application. /// @@ -83,7 +83,6 @@ pub trait ApplicationLayer: Sized { /// computational work as Bob will when they process Alice's initiation packet. const PROOF_OF_WORK_BIT_DIFFICULTY: u32 = 13; - type BlockCipherEnc: AesEnc; type BlockCipherDec: AesDec; @@ -238,8 +237,8 @@ pub enum SaveRatchetAction { /// state with ratchet number one less than the given ratchet state. DeletePrevious, } +use pqc_kyber::{CryptoRng, RngCore}; use SaveRatchetAction::*; -use pqc_kyber::{RngCore, CryptoRng}; impl SaveRatchetAction { /// If this is true then this is the first time the latest ratchet state has ever been seen, /// so it ought to be immediately saved. diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 1b2a3bd..0de52cd 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -2,13 +2,13 @@ pub mod aes; pub mod aes_gcm; -pub mod sha512; pub mod p384; pub mod secret; +pub mod sha512; pub use pqc_kyber; -pub use pqc_kyber::RngCore; pub use pqc_kyber::CryptoRng; +pub use pqc_kyber::RngCore; /// Constant time byte slice equality. #[inline] diff --git a/src/lib.rs b/src/lib.rs index 13d2f3b..c2221ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,8 +10,8 @@ pub mod crypto; mod applicationlayer; mod frag_cache; mod fragged; -mod indexed_heap; mod handshake_cache; +mod indexed_heap; mod log_event; mod proto; mod symmetric_state; diff --git a/src/proto.rs b/src/proto.rs index 12208fe..789e63f 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -9,11 +9,11 @@ use std::hash::Hasher; use std::mem::size_of; -use hex_literal::hex; -use crate::crypto::pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; use crate::crypto::aes_gcm::AES_GCM_TAG_SIZE; -use crate::crypto::sha512::{Sha512, SHA512_HASH_SIZE}; use crate::crypto::p384::P384_PUBLIC_KEY_SIZE; +use crate::crypto::pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; +use crate::crypto::sha512::{Sha512, SHA512_HASH_SIZE}; +use hex_literal::hex; /// Minimum size of a valid physical ZSSP packet of any type. Anything smaller is discarded. pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index b5cb429..4c6750e 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -8,19 +8,23 @@ use std::marker::PhantomData; use crate::crypto::aes::AES_256_KEY_SIZE; -use crate::crypto::sha512::HmacSha512; use crate::crypto::secret::Secret; +use crate::crypto::sha512::HmacSha512; use crate::proto::NOISE_HASHLEN; pub(crate) struct SymmetricState { chaining_key: Secret, token_counter: u8, - p: PhantomData + p: PhantomData, } impl Clone for SymmetricState { fn clone(&self) -> Self { - Self { chaining_key: self.chaining_key.clone(), token_counter: self.token_counter.clone(), p: PhantomData } + Self { + chaining_key: self.chaining_key.clone(), + token_counter: self.token_counter.clone(), + p: PhantomData, + } } } diff --git a/src/zssp.rs b/src/zssp.rs index 7616ac7..cfe8960 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -9,29 +9,29 @@ // FIPS compliant Noise_XK with Jedi powers (Kyber1024) and built-in attack-resistant large payload (fragmentation) support. use std::cmp::Reverse; -use std::ops::DerefMut; use std::collections::HashMap; use std::hash::Hash; use std::num::NonZeroU32; +use std::ops::DerefMut; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; -use crate::crypto::aes::{AesEnc, AesDec}; -use crate::crypto::aes_gcm::{AES_GCM_KEY_SIZE, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE, AesGcmDec, AesGcmEnc}; -use crate::crypto::p384::{P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE, P384KeyPair, P384PublicKey}; +use crate::crypto::aes::{AesDec, AesEnc}; +use crate::crypto::aes_gcm::{AesGcmDec, AesGcmEnc, AES_GCM_IV_SIZE, AES_GCM_KEY_SIZE, AES_GCM_TAG_SIZE}; +use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; +use crate::crypto::pqc_kyber::{RngCore, KYBER_SECRETKEYBYTES}; use crate::crypto::secret::Secret; use crate::crypto::secure_eq; use crate::crypto::sha512::Sha512; -use crate::crypto::pqc_kyber::{KYBER_SECRETKEYBYTES, RngCore}; -use crate::proto::*; use crate::applicationlayer::*; -use crate::log_event::LogEvent; use crate::error::{FaultType, OpenError, ReceiveError, SendError}; -use crate::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; use crate::frag_cache::UnassociatedFragCache; use crate::fragged::{Assembled, Fragged}; use crate::handshake_cache::UnassociatedHandshakeCache; +use crate::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; +use crate::log_event::LogEvent; +use crate::proto::*; use crate::symmetric_state::SymmetricState; /// Session context for local application. @@ -363,7 +363,8 @@ impl Context { if !handshake_state.reinitialize( &session, &ratchet_fingerprint, - &mut self.0.session_map.write().unwrap(), &mut self.0.rng.lock().unwrap(), + &mut self.0.session_map.write().unwrap(), + &mut self.0.rng.lock().unwrap(), current_time, ) { session.expire_inner(&self.0, &mut session_queue); @@ -497,7 +498,7 @@ impl Context { let mut noise_kk_ss = Secret::new(); if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { - return Err(OpenError::InvalidPublicKey) + return Err(OpenError::InvalidPublicKey); } let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); @@ -508,8 +509,12 @@ impl Context { let mut session_map = self.0.session_map.write().unwrap(); let local_key_id = generate_key_id(&session_map, &mut self.0.rng.lock().unwrap()); // Begin Noise XKhfs+psk2. - let (offer, a2b_header_key, b2a_header_key) = - NoiseXKAliceHandshake::::initialize(local_key_id, &remote_s_public_key, &ratchet_fingerprint, &mut self.0.rng.lock().unwrap())?; + let (offer, a2b_header_key, b2a_header_key) = NoiseXKAliceHandshake::::initialize( + local_key_id, + &remote_s_public_key, + &ratchet_fingerprint, + &mut self.0.rng.lock().unwrap(), + )?; let handshake_state = Box::new(NoiseXKAliceHandshake { next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), @@ -636,9 +641,11 @@ impl Context { let session_map = self.0.session_map.read().unwrap(); if let Some((Some(session), key_index)) = session_map.get(&local_key_id).map(|r| (r.0.upgrade(), r.1 as usize)) { drop(session_map); - session - .header_receive_cipher - .decrypt_in_place((&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); + session.header_receive_cipher.decrypt_in_place( + (&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) + .try_into() + .unwrap(), + ); let (fragment_count, fragment_no, packet_type, incoming_counter, header_nonce) = parse_packet_header(&incoming_physical_packet); // Handle replay protection. if PACKET_TYPE_RANGE_TRANSPORT.contains(&packet_type) { @@ -782,8 +789,11 @@ impl Context { // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 incoming = self.0.unassociated_handshake_states.get(local_key_id); if let Some(incoming) = incoming.as_ref() { - Application::BlockCipherDec::new(incoming.header_receive_key.as_ref()) - .decrypt_in_place((&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); + Application::BlockCipherDec::new(incoming.header_receive_key.as_ref()).decrypt_in_place( + (&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) + .try_into() + .unwrap(), + ); let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(&incoming_physical_packet); app.event_log( LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), @@ -954,8 +964,9 @@ impl Context { // Noise process pattern1 e token. let mut noise_ck = SymmetricState::new(INITIAL_H); let mut noise_es = Secret::new(); - let noise_e_pattern1 = from_bytes_agreement::(&noise_pattern1.noise_e, &self.0.static_keypair, noise_es.as_mut()) - .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; + let noise_e_pattern1 = + from_bytes_agreement::(&noise_pattern1.noise_e, &self.0.static_keypair, noise_es.as_mut()) + .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; let noise_h_e = mix_hash(sha512, &noise_h, &noise_pattern1.noise_e); noise_ck.mix_key(&noise_pattern1.noise_e); // Noise process pattern1 es token. @@ -1021,8 +1032,7 @@ impl Context { noise_ck.mix_key(&noise_pattern2.noise_e); // Noise process pattern2 ee token. let mut noise_ee = Secret::new(); - if !noise_e_pattern2_secret - .agree(&noise_e_pattern1, noise_ee.as_mut()) { + if !noise_e_pattern2_secret.agree(&noise_e_pattern1, noise_ee.as_mut()) { return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); } let noise_k_esee = noise_ck.mix_key_initialize_key(noise_ee.as_ref()); @@ -1187,7 +1197,9 @@ impl Context { // Noise process pattern2 e token. let mut noise_ee = Secret::new(); - if let Some(noise_e_pattern2) = from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) { + if let Some(noise_e_pattern2) = + from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) + { let sha512 = &mut Application::Hash::new(); let mut noise_ck = noise_ck_es.clone(); let noise_h_ee1pe = mix_hash(sha512, noise_h_ee1p, noise_e_pattern2.as_bytes()); @@ -1205,8 +1217,7 @@ impl Context { &mut message[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], ); let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); - let noise_ekem1_secret = - pqc_kyber::decapsulate(&noise_pattern2.noise_ekem1, noise_e1_secret.as_ref()).map(|k| Secret(k)); + let noise_ekem1_secret = pqc_kyber::decapsulate(&noise_pattern2.noise_ekem1, noise_e1_secret.as_ref()).map(|k| Secret(k)); if let Some(Ok(noise_ekem1_secret)) = is_auth.then_some(noise_ekem1_secret) { noise_ck.mix_key(noise_ekem1_secret.as_ref()); drop(noise_ekem1_secret); @@ -1225,8 +1236,14 @@ impl Context { let (temp_h, noise_k_ratchet) = noise_ck_ratchet.mix_key_and_hash_initialize_key(ratchet_key); let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); // Noise process pattern2 payload. - let (is_auth, noise_h_ratchet) = - decrypt_and_hash::(sha512, &noise_k_ratchet, &noise_h_ee1peekem1psk, packet_type, 0, &mut payload); + let (is_auth, noise_h_ratchet) = decrypt_and_hash::( + sha512, + &noise_k_ratchet, + &noise_h_ee1peekem1psk, + packet_type, + 0, + &mut payload, + ); let mut key_id = 0u32.to_ne_bytes(); key_id.copy_from_slice(&payload[..NoiseXKPattern2::P_AUTH_START - NoiseXKPattern2::P_ENC_START]); @@ -1309,16 +1326,18 @@ impl Context { let local_key_id = handshake_state.local_key_id; drop(state); let mut state = session.state.write().unwrap(); - session.kex_send_cipher.lock().unwrap().replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); - session.kex_receive_cipher.lock().unwrap().replace(Application::AeadDec::new(kex_key_b2a.as_ref())); + session + .kex_send_cipher + .lock() + .unwrap() + .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); + session + .kex_receive_cipher + .lock() + .unwrap() + .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); - state.cipher_states[0].replace(SessionKey::new( - noise_ck, - local_key_id, - remote_key_id, - INIT_COUNTER, - false, - )); + state.cipher_states[0].replace(SessionKey::new(noise_ck, local_key_id, remote_key_id, INIT_COUNTER, false)); debug_assert!(state.cipher_states[1].is_none()); if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { handshake_state.next_retry_time = @@ -1359,7 +1378,13 @@ impl Context { let mut state = session.state.write().unwrap(); let ratchet_fingerprint = state.ratchet_fingerprint.clone(); if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if !handshake_state.reinitialize(&session, &ratchet_fingerprint, &mut self.0.session_map.write().unwrap(), &mut self.0.rng.lock().unwrap(), current_time) { + if !handshake_state.reinitialize( + &session, + &ratchet_fingerprint, + &mut self.0.session_map.write().unwrap(), + &mut self.0.rng.lock().unwrap(), + current_time, + ) { session.expire() } } @@ -1443,10 +1468,9 @@ impl Context { ) { AcceptSessionAction::Accept(application_data) => { let mut noise_kk_ss = Secret::new(); - if !self.0.static_keypair - .agree(&remote_s_public_key, noise_kk_ss.as_mut()) { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } + if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); @@ -1622,9 +1646,11 @@ impl Context { fragment_size = tagged_fragment_size; } - session - .header_send_cipher - .encrypt_in_place((&mut mtu_sized_buffer[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); + session.header_send_cipher.encrypt_in_place( + (&mut mtu_sized_buffer[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) + .try_into() + .unwrap(), + ); if !send(&mut mtu_sized_buffer[..fragment_size]) { break; } @@ -1803,7 +1829,11 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu ); if result.is_ok() { if let NoiseKKPattern2 { kex_send_key, .. } = &state.outgoing_offer { - session.kex_send_cipher.lock().unwrap().replace(Application::AeadEnc::new(kex_send_key.as_ref())); + session + .kex_send_cipher + .lock() + .unwrap() + .replace(Application::AeadEnc::new(kex_send_key.as_ref())); } state.ratchet_number = ratchet.0; state.ratchet_fingerprint.overwrite(&ratchet.1); @@ -1889,7 +1919,9 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let mut noise_es = Secret::new(); let mut noise_ee = Secret::new(); let mut noise_se = Secret::new(); - if let Some(alice_e) = from_bytes_agreement::(&noise_pattern1.noise_e, &context.0.static_keypair, noise_es.as_mut()) { + if let Some(alice_e) = + from_bytes_agreement::(&noise_pattern1.noise_e, &context.0.static_keypair, noise_es.as_mut()) + { let bob_e_secret = Application::KeyPair::generate(); if bob_e_secret.agree(&alice_e, noise_ee.as_mut()) && bob_e_secret.agree(&session.remote_s_public_key, noise_se.as_mut()) { let noise_h_pske = mix_hash(sha512, &noise_h_psk, alice_e.as_bytes()); @@ -1970,15 +2002,13 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu drop(state); let mut state = session.state.write().unwrap(); let current_counter = session.send_counter.load(Ordering::Relaxed); - session.kex_receive_cipher.lock().unwrap().replace(Application::AeadDec::new(kex_key_a2b.as_ref())); + session + .kex_receive_cipher + .lock() + .unwrap() + .replace(Application::AeadDec::new(kex_key_a2b.as_ref())); - state.cipher_states[next_key_index].replace(SessionKey::new( - noise_ck, - new_key_id, - remote_key_id, - current_counter, - true, - )); + state.cipher_states[next_key_index].replace(SessionKey::new(noise_ck, new_key_id, remote_key_id, current_counter, true)); let timer = current_time.saturating_add(Application::RETRY_INTERVAL_MS); state.outgoing_offer = NoiseKKPattern2 { next_retry_time: AtomicI64::new(timer), @@ -2067,8 +2097,16 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu if let Some(key) = state.cipher_states[next_key_index].as_ref() { context.0.session_map.write().unwrap().remove(&key.local_key_id); } - session.kex_receive_cipher.lock().unwrap().replace(Application::AeadDec::new(kex_key_b2a.as_ref())); - session.kex_send_cipher.lock().unwrap().replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); + session + .kex_receive_cipher + .lock() + .unwrap() + .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); + session + .kex_send_cipher + .lock() + .unwrap() + .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); state.ratchet_number = new_ratchet_number; state.ratchet_fingerprint.overwrite_first_n(&new_ratchet_fingerprint); @@ -2246,7 +2284,14 @@ impl NoiseXKAliceHandshake { remote_s_public_key: &Application::PublicKey, ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], rng: &mut Application::Rng, - ) -> Result<(NoiseXKAliceHandshakeState, Secret, Secret), OpenError> { + ) -> Result< + ( + NoiseXKAliceHandshakeState, + Secret, + Secret, + ), + OpenError, + > { let mut message = [0u8; NoiseXKPattern1::SIZE]; let sha512 = &mut Application::Hash::new(); // Start of Noise XKhfs+psk2 pattern1. @@ -2552,7 +2597,10 @@ fn assemble_fragments_into(fragments: &[A::IncomingPacketBu return Ok(l); } /// Generate a random local key id that is currently unused. -fn generate_key_id(session_map: &HashMap>, bool)>, rng: &mut Application::Rng) -> NonZeroU32 { +fn generate_key_id( + session_map: &HashMap>, bool)>, + rng: &mut Application::Rng, +) -> NonZeroU32 { loop { if let Some(local_key_id) = NonZeroU32::new(rng.next_u32()) { if !session_map.contains_key(&local_key_id) { @@ -2630,6 +2678,10 @@ fn verify_pow(hasher: &mut Application::Hash, mes n.leading_zeros() >= Application::PROOF_OF_WORK_BIT_DIFFICULTY } #[inline(always)] -fn from_bytes_agreement(public: &[u8], private: &impl P384KeyPair, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> Option { +fn from_bytes_agreement( + public: &[u8], + private: &impl P384KeyPair, + output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE], +) -> Option { PublicKey::from_bytes(public.try_into().unwrap()).and_then(|e| private.agree(&e, output).then_some(e)) } From f35ffb5057b50400541383a37e1b574a1f0d0bba Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 12 Jul 2023 14:58:57 -0400 Subject: [PATCH 06/91] reformatted exports --- Cargo.toml | 8 +------- src/applicationlayer.rs | 2 +- src/crypto/mod.rs | 15 --------------- src/crypto/secret.rs | 29 ++++++++++++++++++++++++++++- src/zssp.rs | 6 +++--- 5 files changed, 33 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2f37c25..cf3fd6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,15 @@ [package] -authors = ["ZeroTier, Inc. ", "Adam Ierymenko "] +authors = ["ZeroTier, Inc. ", "Adam Ierymenko ", "Monica Moniot "] edition = "2021" license = "MPL-2.0" name = "zssp" version = "0.1.0" - [lib] name = "zssp" path = "src/lib.rs" doc = true -[[bin]] -name = "zssp_test" -path = "src/main.rs" -doc = false - [dependencies] pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber1024", "std"] } hex-literal = "0.4.1" diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 70a922e..e5b9c8b 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -12,6 +12,7 @@ use crate::crypto::aes::{AesDec, AesEnc}; use crate::crypto::aes_gcm::{AesGcmDec, AesGcmEnc}; use crate::crypto::p384::{P384KeyPair, P384PublicKey}; use crate::crypto::sha512::{HmacSha512, Sha512}; +use crate::crypto::{CryptoRng, RngCore}; use crate::{log_event::LogEvent, Session, RATCHET_FINGERPRINT_SIZE, RATCHET_KEY_SIZE}; /// Trait to implement to integrate the session into an application. @@ -237,7 +238,6 @@ pub enum SaveRatchetAction { /// state with ratchet number one less than the given ratchet state. DeletePrevious, } -use pqc_kyber::{CryptoRng, RngCore}; use SaveRatchetAction::*; impl SaveRatchetAction { /// If this is true then this is the first time the latest ratchet state has ever been seen, diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 0de52cd..a6ad08a 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -9,18 +9,3 @@ pub mod sha512; pub use pqc_kyber; pub use pqc_kyber::CryptoRng; pub use pqc_kyber::RngCore; - -/// Constant time byte slice equality. -#[inline] -pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { - let (a, b) = (a.as_ref(), b.as_ref()); - if a.len() == b.len() { - let mut x = 0u8; - for (aa, bb) in a.iter().zip(b.iter()) { - x |= *aa ^ *bb; - } - x == 0 - } else { - false - } -} diff --git a/src/crypto/secret.rs b/src/crypto/secret.rs index fc03a40..c06fce2 100644 --- a/src/crypto/secret.rs +++ b/src/crypto/secret.rs @@ -2,6 +2,21 @@ use std::convert::TryInto; +/// Constant time byte slice equality. +#[inline] +pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { + let (a, b) = (a.as_ref(), b.as_ref()); + if a.len() == b.len() { + let mut x = 0u8; + for (aa, bb) in a.iter().zip(b.iter()) { + x |= *aa ^ *bb; + } + x == 0 + } else { + false + } +} + /// Container for secrets that clears them on drop. /// /// We can't be totally sure that things like libraries are doing this and it's @@ -11,7 +26,7 @@ use std::convert::TryInto; /// This is generally a low-risk thing since it's process memory that's protected, /// but it's still not a bad idea due to things like swap or obscure side channel /// attacks that allow memory to be read. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone)] #[repr(transparent)] pub struct Secret(pub [u8; L]); @@ -45,6 +60,11 @@ impl Secret { self.0.as_ptr() } + #[inline(always)] + pub fn as_bytes(&self) -> &[u8; L] { + &self.0 + } + /// Get the first N bytes of this secret as a fixed length array. #[inline(always)] pub fn first_n(&self) -> &[u8; N] { @@ -108,3 +128,10 @@ impl AsMut<[u8; L]> for Secret { &mut self.0 } } + +impl PartialEq for Secret { + fn eq(&self, other: &Self) -> bool { + secure_eq(&self.0, &other.0) + } +} +impl Eq for Secret {} diff --git a/src/zssp.rs b/src/zssp.rs index cfe8960..bdbb5ac 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -19,10 +19,10 @@ use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; use crate::crypto::aes::{AesDec, AesEnc}; use crate::crypto::aes_gcm::{AesGcmDec, AesGcmEnc, AES_GCM_IV_SIZE, AES_GCM_KEY_SIZE, AES_GCM_TAG_SIZE}; use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; -use crate::crypto::pqc_kyber::{RngCore, KYBER_SECRETKEYBYTES}; -use crate::crypto::secret::Secret; -use crate::crypto::secure_eq; +use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; +use crate::crypto::secret::{secure_eq, Secret}; use crate::crypto::sha512::Sha512; +use crate::crypto::RngCore; use crate::applicationlayer::*; use crate::error::{FaultType, OpenError, ReceiveError, SendError}; From 691e34227f3b0f3de80a574fd1e932fc0b9aaf6b Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 12 Jul 2023 15:00:45 -0400 Subject: [PATCH 07/91] added secure eq --- src/crypto/secret.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/crypto/secret.rs b/src/crypto/secret.rs index c06fce2..9e00abd 100644 --- a/src/crypto/secret.rs +++ b/src/crypto/secret.rs @@ -85,6 +85,10 @@ impl Secret { let amount = N.min(L); self.0[..amount].copy_from_slice(&src.0[..amount]); } + + pub fn eq_bytes(&self, other: &[u8]) -> bool { + secure_eq(&self.0, other) + } } impl Drop for Secret { From 0a9cc39da1c4e3468af26546ed44b71dbc78440e Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 12 Jul 2023 16:12:37 -0400 Subject: [PATCH 08/91] fixed rand_core dep --- Cargo.toml | 1 + src/applicationlayer.rs | 2 +- src/crypto/mod.rs | 3 +-- src/zssp.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cf3fd6c..c560dc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,5 @@ doc = true [dependencies] pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber1024", "std"] } +rand_core = "0.6.4" hex-literal = "0.4.1" diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index e5b9c8b..909b6e9 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -12,7 +12,7 @@ use crate::crypto::aes::{AesDec, AesEnc}; use crate::crypto::aes_gcm::{AesGcmDec, AesGcmEnc}; use crate::crypto::p384::{P384KeyPair, P384PublicKey}; use crate::crypto::sha512::{HmacSha512, Sha512}; -use crate::crypto::{CryptoRng, RngCore}; +use crate::crypto::rand_core::{CryptoRng, RngCore}; use crate::{log_event::LogEvent, Session, RATCHET_FINGERPRINT_SIZE, RATCHET_KEY_SIZE}; /// Trait to implement to integrate the session into an application. diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index a6ad08a..b033569 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -7,5 +7,4 @@ pub mod secret; pub mod sha512; pub use pqc_kyber; -pub use pqc_kyber::CryptoRng; -pub use pqc_kyber::RngCore; +pub use rand_core; diff --git a/src/zssp.rs b/src/zssp.rs index bdbb5ac..4bf2f54 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -22,7 +22,7 @@ use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SI use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; use crate::crypto::secret::{secure_eq, Secret}; use crate::crypto::sha512::Sha512; -use crate::crypto::RngCore; +use crate::crypto::rand_core::RngCore; use crate::applicationlayer::*; use crate::error::{FaultType, OpenError, ReceiveError, SendError}; From 6956b97b4920ca46c7d6cd8f6866c8f888164efa Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 12 Jul 2023 16:15:20 -0400 Subject: [PATCH 09/91] changed version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c560dc8..28a59bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ authors = ["ZeroTier, Inc. ", "Adam Ierymenko Date: Wed, 12 Jul 2023 16:33:43 -0400 Subject: [PATCH 10/91] saving progress --- src/applicationlayer.rs | 6 +++--- src/crypto/p384.rs | 8 +++++--- src/zssp.rs | 12 ++++++------ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 909b6e9..8148799 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -84,6 +84,8 @@ pub trait ApplicationLayer: Sized { /// computational work as Bob will when they process Alice's initiation packet. const PROOF_OF_WORK_BIT_DIFFICULTY: u32 = 13; + type Rng: CryptoRng + RngCore; + type BlockCipherEnc: AesEnc; type BlockCipherDec: AesDec; @@ -93,10 +95,8 @@ pub trait ApplicationLayer: Sized { type Hash: Sha512; type HmacHash: HmacSha512; - type KeyPair: P384KeyPair; type PublicKey: P384PublicKey; - - type Rng: CryptoRng + RngCore; + type KeyPair: P384KeyPair; /// Type for arbitrary opaque object for use by the application that is attached to /// each session. diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs index c7ca236..978398a 100644 --- a/src/crypto/p384.rs +++ b/src/crypto/p384.rs @@ -1,5 +1,7 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +use super::rand_core::{RngCore, CryptoRng}; + pub const P384_PUBLIC_KEY_SIZE: usize = 49; pub const P384_ECDH_SHARED_SECRET_SIZE: usize = 48; @@ -12,13 +14,13 @@ pub trait P384PublicKey: Sized + Send + Sync { } /// A NIST P-384 ECDH/ECDSA public/private key pair. -pub trait P384KeyPair: Send + Sync { +pub trait P384KeyPair: Send + Sync { /// Randomly generate a new p384 keypair. - fn generate() -> Self; + fn generate(rng: &mut Rng) -> Self; /// Get the raw bytes that uniquely define the public key. fn public_key_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE]; /// Perform ECDH key agreement, returning the raw (un-hashed!) ECDH secret. - fn agree(&self, other_public: &impl P384PublicKey, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool; + fn agree(&self, other_public: &PubKey, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool; } diff --git a/src/zssp.rs b/src/zssp.rs index 4bf2f54..10bd0d7 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -2045,7 +2045,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu // Noise process pattern2 e token. let mut noise_ee = Secret::new(); let mut noise_se = Secret::new(); - if let Some(bob_e) = from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) { + if let Some(bob_e) = from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) { if context.0.static_keypair.agree(&bob_e, noise_se.as_mut()) { let sha512 = &mut Application::Hash::new(); let mut noise_ck = noise_ck.clone(); @@ -2296,7 +2296,7 @@ impl NoiseXKAliceHandshake { let sha512 = &mut Application::Hash::new(); // Start of Noise XKhfs+psk2 pattern1. let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); - let noise_e_secret = Application::KeyPair::generate(); + let noise_e_secret = Application::KeyPair::generate(rng); let noise_e1_secret = pqc_kyber::keypair(rng); noise_pattern1.alice_key_id = local_key_id.get().to_ne_bytes(); noise_pattern1.noise_e = noise_e_secret.public_key_bytes().clone(); @@ -2678,10 +2678,10 @@ fn verify_pow(hasher: &mut Application::Hash, mes n.leading_zeros() >= Application::PROOF_OF_WORK_BIT_DIFFICULTY } #[inline(always)] -fn from_bytes_agreement( +fn from_bytes_agreement( public: &[u8], - private: &impl P384KeyPair, + private: &Application::KeyPair, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE], -) -> Option { - PublicKey::from_bytes(public.try_into().unwrap()).and_then(|e| private.agree(&e, output).then_some(e)) +) -> Option { + Application::PublicKey::from_bytes(public.try_into().unwrap()).and_then(|e| private.agree(&e, output).then_some(e)) } From fa357018392e90f0f3d7c72bed480de7519e30c2 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 12 Jul 2023 17:24:57 -0400 Subject: [PATCH 11/91] gave generate access to rng --- src/error.rs | 2 ++ src/frag_cache.rs | 1 + src/symmetric_state.rs | 1 + src/zssp.rs | 22 +++++++++++++++------- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/error.rs b/src/error.rs index f332468..2183abd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,6 +14,7 @@ pub enum OpenError { /// Local identity blob is too large to send, even with fragmentation. DataTooLarge, } + #[derive(Debug, PartialEq, Eq)] pub enum SendError { /// An invalid parameter was supplied to the function. @@ -33,6 +34,7 @@ pub enum SendError { /// Data object is too large to send, even with fragmentation. DataTooLarge, } + /// A type of fault occurred because we received a bad packet. /// /// An unauthenticated attacker can intentionally trigger any of these, so it is best to diff --git a/src/frag_cache.rs b/src/frag_cache.rs index 50d97f2..ebb428e 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -21,6 +21,7 @@ struct PacketMetadata { packet_size: u32, creation_time: i64, } + pub(crate) struct UnassociatedFragCache { dos_salt: RandomState, frags_first_unused: usize, diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 4c6750e..79c02ab 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -18,6 +18,7 @@ pub(crate) struct SymmetricState { token_counter: u8, p: PhantomData, } + impl Clone for SymmetricState { fn clone(&self) -> Self { Self { diff --git a/src/zssp.rs b/src/zssp.rs index 10bd0d7..60545cd 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -72,6 +72,7 @@ pub enum ReceiveResult<'b, Application: ApplicationLayer> { /// Relates to callbacks `check_allow_incoming_session` and `check_accept_session`. Rejected, } + #[derive(Debug, PartialEq, Eq)] pub enum SessionEvent<'b> { /// The received packet was valid, and it contained the necessary keys to fully establish a new @@ -106,17 +107,20 @@ pub enum SessionEvent<'b> { /// The received packet was some authentic protocol control packet. No action needs to be taken. Control, } + #[derive(Debug, PartialEq, Eq)] pub enum IncomingSessionAction { Allow, Challenge, Drop, } + pub enum AcceptSessionAction { Accept(Application::Data), SendReject, SilentlyReject, } + /// ZeroTier Secure Session Protocol (ZSSP) Session /// /// A FIPS/NIST compliant variant of Noise_XK with hybrid Kyber1024 PQ data forward secrecy. @@ -171,6 +175,7 @@ struct SessionMutableState { /// This defines the exact state of the offer state machine we are in. outgoing_offer: OfferStateMachine, } + /// These offer enums form a state machine. /// Documented below are the only legal transitions for this state machine. /// A session is initialized with an `outgoing_offer` of either NoiseXKPattern1 or Normal. @@ -203,6 +208,7 @@ enum OfferStateMachine { timeout: i64, }, // -> Normal } + pub(crate) struct NoiseXKBobHandshakeState { remote_key_id: NonZeroU32, local_key_id: NonZeroU32, @@ -216,6 +222,7 @@ pub(crate) struct NoiseXKBobHandshakeState { noise_k_eseeekem1psk: Secret, noise_pattern3_defrag: Mutex>, } + struct NoiseXKAliceHandshake { next_retry_time: AtomicI64, timeout: i64, @@ -225,6 +232,7 @@ struct NoiseXKAliceHandshake { alice_identity_blob: Application::LocalIdentityBlob, offer: NoiseXKAliceHandshakeState, } + enum NoiseXKAliceHandshakeState { NoiseXKPattern1 { noise_h_ee1p: [u8; NOISE_HASHLEN], @@ -965,7 +973,7 @@ impl Context { let mut noise_ck = SymmetricState::new(INITIAL_H); let mut noise_es = Secret::new(); let noise_e_pattern1 = - from_bytes_agreement::(&noise_pattern1.noise_e, &self.0.static_keypair, noise_es.as_mut()) + from_bytes_agreement::(&noise_pattern1.noise_e, &self.0.static_keypair, noise_es.as_mut()) .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; let noise_h_e = mix_hash(sha512, &noise_h, &noise_pattern1.noise_e); noise_ck.mix_key(&noise_pattern1.noise_e); @@ -1026,7 +1034,7 @@ impl Context { let mut message2 = [0u8; NoiseXKPattern2::SIZE]; let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); // Noise process pattern2 e token. - let noise_e_pattern2_secret = Application::KeyPair::generate(); + let noise_e_pattern2_secret = Application::KeyPair::generate(&mut self.0.rng.lock().unwrap()); noise_pattern2.noise_e = noise_e_pattern2_secret.public_key_bytes().clone(); let noise_h_ee1pe = mix_hash(sha512, &noise_h_ee1p, &noise_pattern2.noise_e); noise_ck.mix_key(&noise_pattern2.noise_e); @@ -1198,7 +1206,7 @@ impl Context { // Noise process pattern2 e token. let mut noise_ee = Secret::new(); if let Some(noise_e_pattern2) = - from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) + from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) { let sha512 = &mut Application::Hash::new(); let mut noise_ck = noise_ck_es.clone(); @@ -1440,7 +1448,7 @@ impl Context { // Noise process pattern3 se token. let mut noise_se = Secret::new(); if let Some(remote_s_public_key) = - from_bytes_agreement(&message[s_enc_start..s_auth_start], &handshake_state.noise_e_secret, noise_se.as_mut()) + from_bytes_agreement::(&message[s_enc_start..s_auth_start], &handshake_state.noise_e_secret, noise_se.as_mut()) { let mut noise_ck = handshake_state.noise_ck_eseeekem1psk.clone(); let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(noise_se.as_ref()); @@ -1707,7 +1715,7 @@ fn initiate_rekey( let noise_temp_h = noise_ck.mix_key_and_hash(state.ratchet_key.as_ref()); let noise_h_psk = mix_hash(sha512, &session.noise_kk_local_init_h, &noise_temp_h); // Noise process pattern1 e token. - let noise_e_secret = Application::KeyPair::generate(); + let noise_e_secret = Application::KeyPair::generate(&mut context.rng.lock().unwrap()); let noise_h_pske = mix_hash(sha512, &noise_h_psk, noise_e_secret.public_key_bytes()); noise_ck.mix_key(noise_e_secret.public_key_bytes()); @@ -1920,9 +1928,9 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let mut noise_ee = Secret::new(); let mut noise_se = Secret::new(); if let Some(alice_e) = - from_bytes_agreement::(&noise_pattern1.noise_e, &context.0.static_keypair, noise_es.as_mut()) + from_bytes_agreement::(&noise_pattern1.noise_e, &context.0.static_keypair, noise_es.as_mut()) { - let bob_e_secret = Application::KeyPair::generate(); + let bob_e_secret = Application::KeyPair::generate(&mut context.0.rng.lock().unwrap()); if bob_e_secret.agree(&alice_e, noise_ee.as_mut()) && bob_e_secret.agree(&session.remote_s_public_key, noise_se.as_mut()) { let noise_h_pske = mix_hash(sha512, &noise_h_psk, alice_e.as_bytes()); noise_ck.mix_key(alice_e.as_bytes()); From 733b195c50cc474ba099e9559c47d17b813cda50 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 12 Jul 2023 17:27:52 -0400 Subject: [PATCH 12/91] added comment --- src/crypto/p384.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs index 978398a..e7ac2b2 100644 --- a/src/crypto/p384.rs +++ b/src/crypto/p384.rs @@ -10,12 +10,15 @@ pub trait P384PublicKey: Sized + Send + Sync { /// Create a p384 public key from raw bytes. fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option; + /// Get the raw bytes that uniquely define the public key. fn as_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE]; } /// A NIST P-384 ECDH/ECDSA public/private key pair. pub trait P384KeyPair: Send + Sync { /// Randomly generate a new p384 keypair. + /// This function may use the provided RNG or it's own, so long as the produced keys are + /// cryptographically random. fn generate(rng: &mut Rng) -> Self; /// Get the raw bytes that uniquely define the public key. From 7fbc342a2878c7eaced08350678b78fe47205895 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 12 Jul 2023 17:36:22 -0400 Subject: [PATCH 13/91] improved traits --- Cargo.toml | 2 +- src/applicationlayer.rs | 2 +- src/crypto/p384.rs | 12 +++++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 28a59bf..e6078c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ authors = ["ZeroTier, Inc. ", "Adam Ierymenko ; + type KeyPair: P384KeyPair; /// Type for arbitrary opaque object for use by the application that is attached to /// each session. diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs index e7ac2b2..38b3ec2 100644 --- a/src/crypto/p384.rs +++ b/src/crypto/p384.rs @@ -15,15 +15,17 @@ pub trait P384PublicKey: Sized + Send + Sync { } /// A NIST P-384 ECDH/ECDSA public/private key pair. -pub trait P384KeyPair: Send + Sync { +pub trait P384KeyPair: Send + Sync { + type PublicKey: P384PublicKey; + type Rng: RngCore + CryptoRng; /// Randomly generate a new p384 keypair. - /// This function may use the provided RNG or it's own, so long as the produced keys are - /// cryptographically random. - fn generate(rng: &mut Rng) -> Self; + /// This function may use the provided RNG or it's own, + /// so long as the produced keys are cryptographically random. + fn generate(rng: &mut Self::Rng) -> Self; /// Get the raw bytes that uniquely define the public key. fn public_key_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE]; /// Perform ECDH key agreement, returning the raw (un-hashed!) ECDH secret. - fn agree(&self, other_public: &PubKey, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool; + fn agree(&self, other_public: &Self::PublicKey, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool; } From a5c5f8b5656fd5ca505bdd508206f8247ed641d1 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 12 Jul 2023 18:17:10 -0400 Subject: [PATCH 14/91] improved traits --- src/applicationlayer.rs | 4 +- src/crypto/sha512.rs | 27 +++++++-- src/symmetric_state.rs | 47 ++++++--------- src/zssp.rs | 124 ++++++++++++++++++++++------------------ 4 files changed, 110 insertions(+), 92 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index b9b30e2..e688b72 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -86,8 +86,8 @@ pub trait ApplicationLayer: Sized { type Rng: CryptoRng + RngCore; - type BlockCipherEnc: AesEnc; - type BlockCipherDec: AesDec; + type PrpEnc: AesEnc; + type PrpDec: AesDec; type AeadEnc: AesGcmEnc; type AeadDec: AesGcmDec; diff --git a/src/crypto/sha512.rs b/src/crypto/sha512.rs index d477762..c2161be 100644 --- a/src/crypto/sha512.rs +++ b/src/crypto/sha512.rs @@ -2,22 +2,41 @@ pub const SHA512_HASH_SIZE: usize = 64; +/// Opaque SHA-512 implementation. +/// Does not need to be threadsafe. pub trait Sha512 { + /// Allocate memory on the stack or heap for Sha512. + /// An instance of Sha512 will only ever be held on the stack. fn new() -> Self; + /// Reinitialize the internal state of the hash function for a fresh input. fn reset(&mut self); fn update(&mut self, input: &[u8]); - + /// Finish hashing the input and write the final hash to output. + /// + /// After this function is called, this instance of Sha512 will either be dropped + /// or `reset` will be called. fn finish(&mut self, output: &mut [u8; SHA512_HASH_SIZE]); } +/// Opaque HMAC-SHA-512 implementation. +/// Does not need to be threadsafe. pub trait HmacSha512 { - fn new(key: &[u8]) -> Self; - + /// Allocate memory on the stack or heap for HmacSha512. + /// An instance of HmacSha512 will only ever be held on the stack. + /// + /// `reset` will always be called before `update` on a new instance of HmacSha512, + /// to make sure there is always a set key. + fn new() -> Self; + /// Reinitialize the internal state of the hash function for a fresh input. + /// The provided key should replace the previous Hmac key. fn reset(&mut self, key: &[u8]); fn update(&mut self, input: &[u8]); - + /// Finish hashing the input and write the final hash to output. + /// + /// After this function is called, this instance of HmacSha512 will either be dropped + /// or `reset` will be called. fn finish(&mut self, output: &mut [u8; SHA512_HASH_SIZE]); } diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 79c02ab..0c474d2 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -5,39 +5,27 @@ * (c) ZeroTier, Inc. * https://www.zerotier.com/ */ -use std::marker::PhantomData; - use crate::crypto::aes::AES_256_KEY_SIZE; use crate::crypto::secret::Secret; use crate::crypto::sha512::HmacSha512; use crate::proto::NOISE_HASHLEN; -pub(crate) struct SymmetricState { +#[derive(Clone)] +pub(crate) struct SymmetricState { chaining_key: Secret, token_counter: u8, - p: PhantomData, } -impl Clone for SymmetricState { - fn clone(&self) -> Self { - Self { - chaining_key: self.chaining_key.clone(), - token_counter: self.token_counter.clone(), - p: PhantomData, - } - } -} - -impl SymmetricState { +impl SymmetricState { pub(crate) fn new(h: [u8; NOISE_HASHLEN]) -> Self { - Self { chaining_key: Secret(h), token_counter: b'P', p: PhantomData } + Self { chaining_key: Secret(h), token_counter: b'P' } } /// Corresponds to Noise `MixKey`. - pub(crate) fn mix_key(&mut self, input_key_material: &[u8]) { + pub(crate) fn mix_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) { let mut next_ck = Secret::new(); - self.kbkdf(input_key_material, self.label(), 2, next_ck.as_mut(), None, None); + self.kbkdf(hm, input_key_material, self.label(), 2, next_ck.as_mut(), None, None); self.token_counter += 1; self.chaining_key.overwrite(&next_ck); @@ -46,34 +34,34 @@ impl SymmetricState { } /// Corresponds to Noise `MixKey` followed by `InitializeKey`. #[inline(always)] - pub(crate) fn mix_key_initialize_key(&mut self, input_key_material: &[u8]) -> Secret { + pub(crate) fn mix_key_initialize_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> Secret { let mut next_ck = Secret::new(); let mut temp_k = [0u8; NOISE_HASHLEN]; - self.kbkdf(input_key_material, self.label(), 2, next_ck.as_mut(), Some(&mut temp_k), None); + self.kbkdf(hm, input_key_material, self.label(), 2, next_ck.as_mut(), Some(&mut temp_k), None); self.token_counter += 1; self.chaining_key.overwrite(&next_ck); Secret::from_bytes_then_nuke(&mut temp_k[..AES_256_KEY_SIZE]) } /// Corresponds to Noise `MixKeyAndHash`. - pub(crate) fn mix_key_and_hash(&mut self, input_key_material: &[u8]) -> [u8; NOISE_HASHLEN] { + pub(crate) fn mix_key_and_hash(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> [u8; NOISE_HASHLEN] { let mut next_ck = Secret::new(); let mut temp_h = [0u8; NOISE_HASHLEN]; - self.kbkdf(input_key_material, self.label(), 3, next_ck.as_mut(), Some(&mut temp_h), None); + self.kbkdf(hm, input_key_material, self.label(), 3, next_ck.as_mut(), Some(&mut temp_h), None); self.token_counter += 1; self.chaining_key.overwrite(&next_ck); temp_h } /// Corresponds to Noise `MixKeyAndHash` followed by `InitializeKey`. - pub(crate) fn mix_key_and_hash_initialize_key(&mut self, input_key_material: &[u8]) -> ([u8; NOISE_HASHLEN], Secret) { + pub(crate) fn mix_key_and_hash_initialize_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> ([u8; NOISE_HASHLEN], Secret) { let mut next_ck = Secret::new(); let mut temp_h = [0u8; NOISE_HASHLEN]; let mut temp_k = [0u8; NOISE_HASHLEN]; - self.kbkdf( + self.kbkdf(hm, input_key_material, self.label(), 3, @@ -91,10 +79,10 @@ impl SymmetricState { /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. #[inline(always)] - pub(crate) fn get_ask2(&self, label: u8, noise_h: &[u8; NOISE_HASHLEN]) -> (Secret, Secret) { + pub(crate) fn get_ask2(&self, hm: &mut impl HmacSha512, label: u8, noise_h: &[u8; NOISE_HASHLEN]) -> (Secret, Secret) { let mut temp_k1 = [0u8; NOISE_HASHLEN]; let mut temp_k2 = [0u8; NOISE_HASHLEN]; - self.kbkdf(noise_h, [b'A', b'S', b'K', label], 2, &mut temp_k1, Some(&mut temp_k2), None); + self.kbkdf(hm, noise_h, [b'A', b'S', b'K', label], 2, &mut temp_k1, Some(&mut temp_k2), None); ( Secret::from_bytes_then_nuke(&mut temp_k1[..AES_256_KEY_SIZE]), Secret::from_bytes_then_nuke(&mut temp_k2[..AES_256_KEY_SIZE]), @@ -102,10 +90,10 @@ impl SymmetricState { } /// Corresponds to Noise `Split`. #[inline(always)] - pub(crate) fn split(self) -> (Secret, Secret) { + pub(crate) fn split(self, hm: &mut impl HmacSha512) -> (Secret, Secret) { let mut temp_k1 = [0u8; NOISE_HASHLEN]; let mut temp_k2 = [0u8; NOISE_HASHLEN]; - self.kbkdf(&[], self.label(), 2, &mut temp_k1, Some(&mut temp_k2), None); + self.kbkdf(hm, &[], self.label(), 2, &mut temp_k1, Some(&mut temp_k2), None); // Normally KBKDF would not truncate to derive the correct length of AES keys, // but Noise specifies that the AES keys be truncated from NOISE_HASHLEN to AES_256_KEY_SIZE. ( @@ -131,6 +119,7 @@ impl SymmetricState { #[inline(always)] fn kbkdf( &self, + hm: &mut impl HmacSha512, input_key_material: &[u8], label: [u8; 4], num_outputs: u16, @@ -140,7 +129,7 @@ impl SymmetricState { ) { let l = &(num_outputs * 512u16).to_be_bytes(); - let mut hm = Hmac::new(input_key_material); + hm.reset(input_key_material); hm.update(&[1, label[0], label[1], label[2], label[3], 0x00]); hm.update(self.chaining_key.as_ref()); hm.update(l); diff --git a/src/zssp.rs b/src/zssp.rs index 60545cd..b1de9c0 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -21,7 +21,7 @@ use crate::crypto::aes_gcm::{AesGcmDec, AesGcmEnc, AES_GCM_IV_SIZE, AES_GCM_KEY_ use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; use crate::crypto::secret::{secure_eq, Secret}; -use crate::crypto::sha512::Sha512; +use crate::crypto::sha512::{Sha512, HmacSha512}; use crate::crypto::rand_core::RngCore; use crate::applicationlayer::*; @@ -147,8 +147,8 @@ pub struct Session { state_machine_lock: Mutex<()>, state: RwLock>, defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], - header_send_cipher: Application::BlockCipherEnc, - header_receive_cipher: Application::BlockCipherDec, + header_send_cipher: Application::PrpEnc, + header_receive_cipher: Application::PrpDec, kex_send_cipher: Mutex>, kex_receive_cipher: Mutex>, /// Pre-computed rekeying values. @@ -191,7 +191,7 @@ enum OfferStateMachine { new_key_id: NonZeroU32, noise_e_secret: Application::KeyPair, noise_message: [u8; NoiseKKPattern1or2::SIZE], - noise_ck: SymmetricState, + noise_ck: SymmetricState, noise_h_pskep: [u8; NOISE_HASHLEN], }, // -> NoiseKKPattern2, KeyConfirm NoiseKKPattern2 { @@ -218,7 +218,7 @@ pub(crate) struct NoiseXKBobHandshakeState { ratchet_fingerprint: Option<[u8; RATCHET_FINGERPRINT_SIZE]>, noise_h_ee1peekem1pskp: [u8; NOISE_HASHLEN], noise_e_secret: Application::KeyPair, - noise_ck_eseeekem1psk: SymmetricState, + noise_ck_eseeekem1psk: SymmetricState, noise_k_eseeekem1psk: Secret, noise_pattern3_defrag: Mutex>, } @@ -238,7 +238,7 @@ enum NoiseXKAliceHandshakeState { noise_h_ee1p: [u8; NOISE_HASHLEN], noise_e_secret: Application::KeyPair, noise_e1_secret: Secret, - noise_ck_es: SymmetricState, + noise_ck_es: SymmetricState, /// ZSSP assumes an unreliable, out-of-order physical transport environment, so for that /// reason we have to resend key offers. noise_message: [u8; NoiseXKPattern1::SIZE], @@ -392,7 +392,7 @@ impl Context { PACKET_TYPE_NOISE_XK_PATTERN_1, None, *message_id, - None::<&Application::BlockCipherEnc>, + None::<&Application::PrpEnc>, ); } NoiseXKAliceHandshakeState::NoiseXKPattern3 { noise_message, noise_message_len, .. } => { @@ -538,7 +538,7 @@ impl Context { PACKET_TYPE_NOISE_XK_PATTERN_1, None, *message_id, - None::<&Application::BlockCipherEnc>, + None::<&Application::PrpEnc>, ); } @@ -561,8 +561,8 @@ impl Context { current_key: 1, outgoing_offer: OfferStateMachine::NoiseXKPattern1or3(handshake_state), }), - header_send_cipher: Application::BlockCipherEnc::new(a2b_header_key.as_ref()), - header_receive_cipher: Application::BlockCipherDec::new(b2a_header_key.as_ref()), + header_send_cipher: Application::PrpEnc::new(a2b_header_key.as_ref()), + header_receive_cipher: Application::PrpDec::new(b2a_header_key.as_ref()), kex_receive_cipher: Mutex::new(None), kex_send_cipher: Mutex::new(None), noise_kk_ss: noise_kk_ss.clone(), @@ -797,7 +797,7 @@ impl Context { // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 incoming = self.0.unassociated_handshake_states.get(local_key_id); if let Some(incoming) = incoming.as_ref() { - Application::BlockCipherDec::new(incoming.header_receive_key.as_ref()).decrypt_in_place( + Application::PrpDec::new(incoming.header_receive_key.as_ref()).decrypt_in_place( (&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) .try_into() .unwrap(), @@ -953,7 +953,7 @@ impl Context { PACKET_TYPE_BOB_DOS_CHALLENGE, None, self.0.rng.lock().unwrap().next_u64(), - None::<&Application::BlockCipherEnc>, + None::<&Application::PrpEnc>, ); return Ok(ReceiveResult::Unassociated); } @@ -971,14 +971,15 @@ impl Context { let noise_h = mix_hash(sha512, &noise_h, self.0.static_keypair.public_key_bytes()); // Noise process pattern1 e token. let mut noise_ck = SymmetricState::new(INITIAL_H); + let hmac = &mut Application::HmacHash::new(); let mut noise_es = Secret::new(); let noise_e_pattern1 = from_bytes_agreement::(&noise_pattern1.noise_e, &self.0.static_keypair, noise_es.as_mut()) .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; let noise_h_e = mix_hash(sha512, &noise_h, &noise_pattern1.noise_e); - noise_ck.mix_key(&noise_pattern1.noise_e); + noise_ck.mix_key(hmac, &noise_pattern1.noise_e); // Noise process pattern1 es token. - let noise_k_es = noise_ck.mix_key_initialize_key(noise_es.as_ref()); + let noise_k_es = noise_ck.mix_key_initialize_key(hmac, noise_es.as_ref()); drop(noise_es); // Noise process pattern1 e1 token. let (is_auth, noise_h_ee1) = decrypt_and_hash::( @@ -1011,7 +1012,7 @@ impl Context { if !is_auth { return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); } - let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(LABEL_HEADER_KEY, &noise_h_ee1p); + let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); // Get ratchet key. let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(message); use crate::GetRatchetAction::*; @@ -1037,13 +1038,13 @@ impl Context { let noise_e_pattern2_secret = Application::KeyPair::generate(&mut self.0.rng.lock().unwrap()); noise_pattern2.noise_e = noise_e_pattern2_secret.public_key_bytes().clone(); let noise_h_ee1pe = mix_hash(sha512, &noise_h_ee1p, &noise_pattern2.noise_e); - noise_ck.mix_key(&noise_pattern2.noise_e); + noise_ck.mix_key(hmac, &noise_pattern2.noise_e); // Noise process pattern2 ee token. let mut noise_ee = Secret::new(); if !noise_e_pattern2_secret.agree(&noise_e_pattern1, noise_ee.as_mut()) { return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); } - let noise_k_esee = noise_ck.mix_key_initialize_key(noise_ee.as_ref()); + let noise_k_esee = noise_ck.mix_key_initialize_key(hmac, noise_ee.as_ref()); drop(noise_ee); // Noise process pattern2 ekem1 token. let (noise_ekem1, noise_ekem1_secret) = pqc_kyber::encapsulate(&noise_pattern1.noise_e1, self.0.rng.lock().unwrap().deref_mut()) @@ -1060,10 +1061,10 @@ impl Context { &mut message2[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], ); drop(noise_k_esee); - noise_ck.mix_key(noise_ekem1_secret.as_ref()); + noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); drop(noise_ekem1_secret); // Noise process pattern2 psk token. - let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(&ratchet_key); + let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, &ratchet_key); let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); // Noise process pattern2 payload. // We try to prevent the id we generate from colliding with another session but @@ -1111,7 +1112,7 @@ impl Context { PACKET_TYPE_NOISE_XK_PATTERN_2, Some(remote_key_id), u64::from_be_bytes(pattern2_id), - Some(&Application::BlockCipherEnc::new(header_b2a_key.first_n::())), + Some(&Application::PrpEnc::new(header_b2a_key.first_n::())), ); return Ok(ReceiveResult::Unassociated); @@ -1209,11 +1210,12 @@ impl Context { from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) { let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); let mut noise_ck = noise_ck_es.clone(); let noise_h_ee1pe = mix_hash(sha512, noise_h_ee1p, noise_e_pattern2.as_bytes()); - noise_ck.mix_key(noise_e_pattern2.as_bytes()); + noise_ck.mix_key(hmac, noise_e_pattern2.as_bytes()); // Noise process pattern2 ee token. - let noise_k_esee = noise_ck.mix_key_initialize_key(noise_ee.as_ref()); + let noise_k_esee = noise_ck.mix_key_initialize_key(hmac, noise_ee.as_ref()); drop(noise_ee); // Noise process pattern2 ekem1 token. let (is_auth, noise_h_ee1peekem1) = decrypt_and_hash::( @@ -1227,7 +1229,7 @@ impl Context { let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); let noise_ekem1_secret = pqc_kyber::decapsulate(&noise_pattern2.noise_ekem1, noise_e1_secret.as_ref()).map(|k| Secret(k)); if let Some(Ok(noise_ekem1_secret)) = is_auth.then_some(noise_ekem1_secret) { - noise_ck.mix_key(noise_ekem1_secret.as_ref()); + noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); drop(noise_ekem1_secret); // We attempt to decrypt the payload at most twice. First time with // the ratchet key Alice last remembers, and second time with a ratchet @@ -1241,7 +1243,7 @@ impl Context { let mut payload = [0u8; NoiseXKPattern2::P_AUTH_END - NoiseXKPattern2::P_ENC_START]; payload.copy_from_slice(&message[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END]); // Noise process pattern2 psk token. - let (temp_h, noise_k_ratchet) = noise_ck_ratchet.mix_key_and_hash_initialize_key(ratchet_key); + let (temp_h, noise_k_ratchet) = noise_ck_ratchet.mix_key_and_hash_initialize_key(hmac, ratchet_key); let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); // Noise process pattern2 payload. let (is_auth, noise_h_ratchet) = decrypt_and_hash::( @@ -1299,7 +1301,7 @@ impl Context { ); drop(noise_k_eseeekem1psk); // Noise process pattern3 se token. - let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(noise_se.as_ref()); + let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); drop(noise_se); // Noise process pattern3 payload token. message3[p_enc_start..p_auth_start].copy_from_slice(payload); @@ -1316,7 +1318,7 @@ impl Context { // Transition offer state machine to the NoiseXKPattern3 state. let new_ratchet_number = ratchet_number + 1; let (new_ratchet_key, new_ratchet_fingerprint) = - noise_ck.get_ask2(LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); + noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); let result = app.save_ratchet_state( &session.remote_s_public_key, &session.application_data, @@ -1329,7 +1331,7 @@ impl Context { if result.is_err() { return Err(ReceiveError::RatchetIoError); } - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); let local_key_id = handshake_state.local_key_id; drop(state); @@ -1345,7 +1347,7 @@ impl Context { .unwrap() .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); - state.cipher_states[0].replace(SessionKey::new(noise_ck, local_key_id, remote_key_id, INIT_COUNTER, false)); + state.cipher_states[0].replace(SessionKey::new(hmac, noise_ck, local_key_id, remote_key_id, INIT_COUNTER, false)); debug_assert!(state.cipher_states[1].is_none()); if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { handshake_state.next_retry_time = @@ -1434,6 +1436,7 @@ impl Context { // error is possible. // Noise process pattern3 s token. let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); let (is_auth, noise_h_ee1peekem1pskps) = decrypt_and_hash::( sha512, &handshake_state.noise_k_eseeekem1psk, @@ -1451,7 +1454,7 @@ impl Context { from_bytes_agreement::(&message[s_enc_start..s_auth_start], &handshake_state.noise_e_secret, noise_se.as_mut()) { let mut noise_ck = handshake_state.noise_ck_eseeekem1psk.clone(); - let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(noise_se.as_ref()); + let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); drop(noise_se); // Noise process pattern3 payload. let (is_auth, noise_h_ee1peekem1pskpsp) = decrypt_and_hash::( @@ -1467,8 +1470,8 @@ impl Context { return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); } // Bob finished Noise XKhfs+psk2 handshake. - let header_send_cipher = Application::BlockCipherEnc::new(handshake_state.header_send_key.as_ref()); - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); + let header_send_cipher = Application::PrpEnc::new(handshake_state.header_send_key.as_ref()); + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); match check_accept_session( &remote_s_public_key, &message[p_enc_start..p_auth_start], @@ -1484,7 +1487,7 @@ impl Context { let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); - let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); + let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); // We must make sure the ratchet key is saved before we transition. let new_ratchet_number = handshake_state.ratchet_number + 1; let result = app.save_ratchet_state( @@ -1517,6 +1520,7 @@ impl Context { ratchet_key: new_ratchet_key.clone(), cipher_states: [ Some(SessionKey::new( + hmac, noise_ck, handshake_state.local_key_id, handshake_state.remote_key_id, @@ -1531,7 +1535,7 @@ impl Context { timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), }, }), - header_receive_cipher: Application::BlockCipherDec::new(handshake_state.header_receive_key.as_ref()), + header_receive_cipher: Application::PrpDec::new(handshake_state.header_receive_key.as_ref()), header_send_cipher, kex_send_cipher: Mutex::new(Some(Application::AeadEnc::new(kex_key_b2a.as_ref()))), kex_receive_cipher: Mutex::new(Some(Application::AeadDec::new(kex_key_a2b.as_ref()))), @@ -1709,15 +1713,16 @@ fn initiate_rekey( _ => return Err(()), } let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); // Start of Noise KKpsk0 pattern1. // Noise process pattern1 psk0 token. let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(state.ratchet_key.as_ref()); + let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_key.as_ref()); let noise_h_psk = mix_hash(sha512, &session.noise_kk_local_init_h, &noise_temp_h); // Noise process pattern1 e token. let noise_e_secret = Application::KeyPair::generate(&mut context.rng.lock().unwrap()); let noise_h_pske = mix_hash(sha512, &noise_h_psk, noise_e_secret.public_key_bytes()); - noise_ck.mix_key(noise_e_secret.public_key_bytes()); + noise_ck.mix_key(hmac, noise_e_secret.public_key_bytes()); let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); @@ -1726,10 +1731,10 @@ fn initiate_rekey( if !noise_e_secret.agree(&session.remote_s_public_key, noise_es.as_mut()) { return Err(()); } - noise_ck.mix_key(noise_es.as_ref()); + noise_ck.mix_key(hmac, noise_es.as_ref()); drop(noise_es); // Noise process pattern1 ss token. - let noise_k_pskesss = noise_ck.mix_key_initialize_key(session.noise_kk_ss.as_ref()); + let noise_k_pskesss = noise_ck.mix_key_initialize_key(hmac, session.noise_kk_ss.as_ref()); // Noise process pattern1 payload token. let mut session_map = context.session_map.write().unwrap(); let new_key_id = generate_key_id(&session_map, &mut context.rng.lock().unwrap()); @@ -1919,8 +1924,9 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu } // Noise process pattern1 psk0 token. let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(state.ratchet_key.as_ref()); + let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_key.as_ref()); let noise_h_psk = mix_hash(sha512, &session.noise_kk_remote_init_h, &noise_temp_h); // Noise process pattern1 e token. // Get public key validation out of the way early @@ -1933,12 +1939,12 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let bob_e_secret = Application::KeyPair::generate(&mut context.0.rng.lock().unwrap()); if bob_e_secret.agree(&alice_e, noise_ee.as_mut()) && bob_e_secret.agree(&session.remote_s_public_key, noise_se.as_mut()) { let noise_h_pske = mix_hash(sha512, &noise_h_psk, alice_e.as_bytes()); - noise_ck.mix_key(alice_e.as_bytes()); + noise_ck.mix_key(hmac, alice_e.as_bytes()); // Noise process pattern1 es token. - noise_ck.mix_key(noise_es.as_ref()); + noise_ck.mix_key(hmac, noise_es.as_ref()); drop(noise_es); // Noise process pattern1 ss token. - let noise_k_pskesss = noise_ck.mix_key_initialize_key(session.noise_kk_ss.as_ref()); + let noise_k_pskesss = noise_ck.mix_key_initialize_key(hmac, session.noise_kk_ss.as_ref()); // Noise process pattern1 payload. let (is_auth, noise_h_pskep) = decrypt_and_hash::( @@ -1955,12 +1961,12 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu // Start of Noise KKpsk0 pattern2. // Noise process pattern2 e token. let noise_h_pskepe = mix_hash(sha512, &noise_h_pskep, bob_e_secret.public_key_bytes()); - noise_ck.mix_key(bob_e_secret.public_key_bytes()); + noise_ck.mix_key(hmac, bob_e_secret.public_key_bytes()); // Noise process pattern2 ee token. - noise_ck.mix_key(noise_ee.as_ref()); + noise_ck.mix_key(hmac, noise_ee.as_ref()); drop(noise_ee); // Noise process pattern2 se token. - let noise_k_pskessseese = noise_ck.mix_key_initialize_key(noise_se.as_ref()); + let noise_k_pskessseese = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); drop(noise_se); // Noise process pattern2 payload. let mut message2 = [0u8; NoiseKKPattern1or2::SIZE]; @@ -1982,7 +1988,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu drop(noise_k_pskessseese); // Bob finished Noise KKpsk0 handshake. let new_ratchet_number = state.ratchet_number + 1; - let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(LABEL_RATCHET_STATE, &noise_h_pskepep); + let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); let result = app.save_ratchet_state( &session.remote_s_public_key, &session.application_data, @@ -1997,7 +2003,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu drop(kex_lock); return Err(ReceiveError::RatchetIoError); } - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(LABEL_KEX_KEY, &noise_h_pskepep); + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); // The new "Bob" doesn't know yet if Alice has received the new key, so the // new key is recorded as the "alt" (key_index ^ 1) but the current key is // not advanced yet. @@ -2016,7 +2022,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu .unwrap() .replace(Application::AeadDec::new(kex_key_a2b.as_ref())); - state.cipher_states[next_key_index].replace(SessionKey::new(noise_ck, new_key_id, remote_key_id, current_counter, true)); + state.cipher_states[next_key_index].replace(SessionKey::new(hmac, noise_ck, new_key_id, remote_key_id, current_counter, true)); let timer = current_time.saturating_add(Application::RETRY_INTERVAL_MS); state.outgoing_offer = NoiseKKPattern2 { next_retry_time: AtomicI64::new(timer), @@ -2056,14 +2062,15 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu if let Some(bob_e) = from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) { if context.0.static_keypair.agree(&bob_e, noise_se.as_mut()) { let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); let mut noise_ck = noise_ck.clone(); let noise_h_pskepe = mix_hash(sha512, noise_h_pskep, bob_e.as_bytes()); - noise_ck.mix_key(bob_e.as_bytes()); + noise_ck.mix_key(hmac, bob_e.as_bytes()); // Noise process pattern2 ee token. - noise_ck.mix_key(noise_ee.as_ref()); + noise_ck.mix_key(hmac, noise_ee.as_ref()); drop(noise_ee); // Noise process pattern2 se token. - let noise_k_pskessseese = noise_ck.mix_key_initialize_key(noise_se.as_ref()); + let noise_k_pskessseese = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); drop(noise_se); // Noise process pattern2 payload. let (is_auth, noise_h_pskepep) = decrypt_and_hash::( @@ -2079,7 +2086,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu // Bob fully authenticated. // Alice finished Noise KKpsk0 handshake. let new_ratchet_number = state.ratchet_number + 1; - let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(LABEL_RATCHET_STATE, &noise_h_pskepep); + let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); let result = app.save_ratchet_state( &session.remote_s_public_key, @@ -2095,7 +2102,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu drop(kex_lock); return Err(ReceiveError::RatchetIoError); } - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(LABEL_KEX_KEY, &noise_h_pskepep); + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); let new_key_id = *new_key_id; drop(state); @@ -2121,6 +2128,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu state.ratchet_key.overwrite(&new_ratchet_key); state.cipher_states[next_key_index].replace(SessionKey::new( + hmac, noise_ck, new_key_id, remote_key_id, @@ -2302,6 +2310,7 @@ impl NoiseXKAliceHandshake { > { let mut message = [0u8; NoiseXKPattern1::SIZE]; let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); // Start of Noise XKhfs+psk2 pattern1. let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); let noise_e_secret = Application::KeyPair::generate(rng); @@ -2320,13 +2329,13 @@ impl NoiseXKAliceHandshake { // Noise process pattern1 e token. let mut noise_ck = SymmetricState::new(INITIAL_H); let noise_h_e = mix_hash(sha512, &noise_h, noise_e_secret.public_key_bytes()); - noise_ck.mix_key(noise_e_secret.public_key_bytes()); + noise_ck.mix_key(hmac, noise_e_secret.public_key_bytes()); // Noise process pattern1 es token. let mut noise_es = Secret::new(); if !noise_e_secret.agree(remote_s_public_key, noise_es.as_mut()) { return Err(OpenError::InvalidPublicKey); } - let noise_k_es = noise_ck.mix_key_initialize_key(noise_es.as_ref()); + let noise_k_es = noise_ck.mix_key_initialize_key(hmac, noise_es.as_ref()); drop(noise_es); // Noise process pattern1 e1 token. let noise_h_ee1 = encrypt_and_hash::( @@ -2347,7 +2356,7 @@ impl NoiseXKAliceHandshake { &mut message[NoiseXKPattern1::P_ENC_START..NoiseXKPattern1::P_AUTH_END], ); drop(noise_k_es); - let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(LABEL_HEADER_KEY, &noise_h_ee1p); + let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); let mut pattern1_id = 0u64.to_ne_bytes(); pattern1_id.copy_from_slice(&message[NoiseXKPattern1::P_AUTH_START + 8..NoiseXKPattern1::P_AUTH_END]); Ok(( @@ -2621,13 +2630,14 @@ fn generate_key_id( impl SessionKey { #[inline(always)] fn new( - ck: SymmetricState, + hmac: &mut Application::HmacHash, + ck: SymmetricState, local_key_id: NonZeroU32, remote_key_id: NonZeroU32, current_counter: u64, is_bob: bool, ) -> Self { - let (b2a, a2b) = ck.split(); + let (b2a, a2b) = ck.split(hmac); let (receive_key, send_key) = if is_bob { (&a2b, &b2a) } else { From fd14ded072a6def1fe17ab81e4678d4293d89b0c Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 13 Jul 2023 07:05:46 -0400 Subject: [PATCH 15/91] updated docs --- src/applicationlayer.rs | 185 ++++++++++++++++++++++------------------ src/lib.rs | 2 +- src/zssp.rs | 30 +++---- 3 files changed, 116 insertions(+), 101 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index e688b72..3658be2 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -114,45 +114,18 @@ pub trait ApplicationLayer: Sized { /// It will be dropped as soon as the session is established. type LocalIdentityBlob: AsRef<[u8]>; - /// Save the given ratchet state to persistent storage. - /// A ratchet state consists of a ratchet number, a ratchet fingerprint, and a ratchet key. + /// This function will be called whenever Alice's initial Hello packet contains the zero ratchet + /// fingerprint. Brand new peers will always connect to Bob with the zero ratchet key, but from + /// then on they should be using non-zero ratchet keys. /// - /// Ratchet states are identified by their ratchet number, the latest ratchet state with a - /// specific ratchet number should overwrite any previous ratchet state with the same number. - /// Only the `ratchet_number` and `ratchet_number - 1` ratchet states should be saved to persistent - /// storage, when a new one is saved the `ratchet_number - 2` ratchet state should be deleted. - /// - /// The `last_confirmed_ratchet_number` specifies which of the two saved ratchet states should - /// be used if this local peer needs to re-open this session (i.e. after a system restart). - /// This number should also be saved to persistent storage. - /// - /// A ratchet fingerprint is a 32 byte string unique for each ratchet key, it should be - /// possible to quickly look up the `ratchet_key` from just its `ratchet_fingerprint`. - /// - /// If this returns `Err(())`, the packet which triggered this function to be called will be - /// dropped, and no session state will be mutated, preserving synchronization. The remote peer - /// will eventually resend that packet and so this function will be called again. - /// - /// If persistent storage is supported, this function should not return until the ratchet state - /// is saved, otherwise it is possible, albeit unlikely, for a sudden restart of the local - /// machine to put our ratchet state out of sync with the remote peer. If this happens the only - /// fix is to restart the entire ratchet chain from zero. - /// - /// This function may also save state to volatile storage, or potentially not even save it at - /// all, in which case all peers which connect to us will always have to allow us to downgrade - /// the ratchet chain to zero. Otherwise they might not consider us to be "authentic". + /// If this returns false, we will attempt to connect to Alice with the zero ratchet key. + /// If this returns true, Alice's connection will be silently dropped. + /// If this function is configured to always return true, it means peers will not be able to + /// connect to us unless they had a prior-established ratchet key with us. This is the best way + /// for the paranoid to enforce a manual allow-list. #[allow(unused)] - fn save_ratchet_state( - &self, - alice_s_public: &Self::PublicKey, - application_data: &Self::Data, - ratchet_action: SaveRatchetAction, - latest_ratchet_number: u64, - latest_ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], - latest_ratchet_key: &[u8; RATCHET_KEY_SIZE], - current_time: i64, - ) -> Result<(), ()> { - Ok(()) + fn hello_requires_recognized_ratchet(&self, current_time: i64) -> bool { + false } /// This function is called if we, as Alice, attempted to open a session with Bob using a /// non-zero ratchet key, but Bob does not have this ratchet key and wants to downgrade @@ -173,75 +146,111 @@ pub trait ApplicationLayer: Sized { /// If Alice does decide to reconnect without a ratchet key, be sure to generate some warning /// that something has gone wrong and Bob could not be fully authenticated. #[allow(unused)] - fn allow_downgrade(&self, session: &Arc>, current_time: i64) -> bool { - true + fn initiator_disallows_downgrade(&self, session: &Arc>, current_time: i64) -> bool { + false } /// Lookup a specific ratchet key based on its ratchet fingerprint. /// This function will be called whenever Alice attempts to connect to us with a non-zero - /// ratchet key. + /// ratchet fingerprint. /// - /// If the ratchet key was found, the function should return `RatchetAction::Found`. This will + /// If the ratchet key was found, the function should return `RestoreAction::RestoreRatchet`. This will /// cause us to connect to Alice using the returned ratchet number and ratchet key. - /// We don't know Alice's static identity at this point in the handshake, so a - /// `RemotePeerIdentifier` must be returned, so when Alice does send their identity we - /// can verify it matches what we expect. /// /// If the ratchet key could not be found, the application may choose between returning - /// `RatchetAction::Downgrade` or `RatchetAction::Ignore`. - /// If `RatchetAction::Downgrade` is returned we will attempt to convince Alice to downgrade + /// `RatchetAction::DowngradeRatchet` or `RatchetAction::FailAuthentication`. + /// If `RatchetAction::DowngradeRatchet` is returned we will attempt to convince Alice to downgrade /// to the zero ratchet key, restarting the ratchet chain. - /// If `RatchetAction::Ignore` is returned Alice's connection will be silently dropped. + /// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped. #[allow(unused)] - fn lookup_ratchet(&self, ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], current_time: i64) -> Result { - Ok(GetRatchetAction::Downgrade) + fn restore_ratchet(&self, ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], current_time: i64) -> Result { + Ok(RestoreAction::DowngradeRatchet) } - /// This function will be called whenever Alice's initial Hello packet contains the zero ratchet - /// key. Brand new peers will always connect to Bob with the zero ratchet key, but from then on - /// they should be using non-zero ratchet keys. + /// Atomically save the given ratchet key, fingerprint and number to persistent storage. /// - /// If this returns true, we will attempt to connect to Alice with the zero ratchet key. - /// If this returns false, Alice's connection will be silently dropped. - /// If this function is configured to always return false, it means peers will not be able to - /// connect to us unless they had a prior-established ratchet key with us. This is the best way - /// for the paranoid to enforce a manual allow-list. + /// See the documentation of `SaveAction` for more details on how to save them to storage, + /// and how to handle any pre-existing ratchet keys, fingerprints and numbers. + /// + /// If this returns `Err(())`, the packet which triggered this function to be called will be + /// dropped, and no session state will be mutated, preserving synchronization. The remote peer + /// will eventually resend that packet and so this function will be called again. + /// + /// If persistent storage is supported, this function should not return until the ratchet state + /// is saved, otherwise it is possible, albeit unlikely, for a sudden restart of the local + /// machine to put our ratchet state out of sync with the remote peer. If this happens the only + /// fix is to reset both ratchet keys to zero. + /// + /// This function may also save state to volatile storage, in which case all peers which connect + /// to us will have to allow downgrade + /// (`initiator_disallows_downgrade` returns false and/or`restore_ratchet` returns `DowngradeRatchet`). + /// Otherwise, when we restart, we will not be allowed to reconnect. #[allow(unused)] - fn allow_zero_ratchet(&self, current_time: i64) -> bool { - true + fn save_ratchet_state( + &self, + remote_static_key: &Self::PublicKey, + application_data: &Self::Data, + ratchet_action: SaveAction, + latest_ratchet_number: u64, + latest_ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], + latest_ratchet_key: &[u8; RATCHET_KEY_SIZE], + current_time: i64, + ) -> Result<(), ()> { + Ok(()) } #[allow(unused)] #[inline] fn event_log<'a>(&self, event: LogEvent<'a, Self>, current_time: i64) {} } -pub enum GetRatchetAction { - Found(u64, [u8; RATCHET_KEY_SIZE]), - Downgrade, - Ignore, +pub enum RestoreAction { + RestoreRatchet(u64, [u8; RATCHET_KEY_SIZE]), + DowngradeRatchet, + FailAuthentication, } -/// Only 2 ratchet states may be saved at one time. -/// If a 3rd ratchet state needs to be saved the 1st should be deleted, if it was not already deleted. -/// The "previous ratchet state" may be the zero ratchet state. -pub enum SaveRatchetAction { - /// Save the given new ratchet state and set the previous saved ratchet state as - /// the confirmed ratchet state, if it was not already. - /// If there are currently two saved states delete the oldest state and replace it with this one. +/// Ratchet keys and fingerprints should be saved *per remote peer*. It is up to the application to +/// enforce separate storage for each remote peer based on `remote_static_key` and `application_data`. +/// +/// Only up to 2 ratchet keys and fingerprints may be saved at one time. +/// If a 3rd needs to be saved the 1st should be deleted, if it was not already deleted. +pub enum SaveAction { + /// Save the given `latest_ratchet_fingerprint` and `latest_ratchet_key`, + /// but do not update the confirmed ratchet number. + /// + /// If a ratchet key and fingerprint already exist with ratchet number `latest_ratchet_number`, + /// then `latest_ratchet_fingerprint` and `latest_ratchet_key` should overwrite them. + /// + /// Keep the previous ratchet state saved and searchable until it is explicitly deleted. + /// If there are two saved ratchet keys and fingerprints, replace the oldest pair with + /// the new pair. SaveAsUnconfirmed, - /// Save the given new ratchet state and set it as the confirmed ratchet state. Keep the previous - /// ratchet state saved and searchable until it is explicitly deleted. - /// If there are currently two saved states delete the oldest state and replace it with this one. + /// Save the given `latest_ratchet_fingerprint` and `latest_ratchet_key`, + /// and set the confirmed ratchet number to `latest_ratchet_number`. + /// The confirmed ratchet number should be set equal to `latest_ratchet_number`. + /// + /// If a ratchet key and fingerprint already exist with ratchet number `latest_ratchet_number`, + /// then `latest_ratchet_fingerprint` and `latest_ratchet_key` should overwrite them. + /// + /// Keep the previous ratchet state saved and searchable until it is explicitly deleted. + /// If there are two saved ratchet keys and fingerprints, replace the oldest pair with + /// the new pair. SaveAsConfirmed, - /// The given ratchet state will be identical to that saved during a previous call to - /// `SaveAsUnconfirmedAndConfirmPrevious`. Set the given ratchet state as the - /// confirmed ratchet state and permanently delete the previous ratchet state. + /// Set the confirmed ratchet number to `latest_ratchet_number`, + /// and permanently delete the previous (oldest) ratchet key and fingerprint. + /// + /// The given `latest_ratchet_fingerprint` and `latest_ratchet_key` will be identical to those + /// saved during a prior `SaveAsUnconfirmed` call. + /// These two must be the only saved ratchet key and fingerprint when this call completes. ConfirmLatestAndDeletePrevious, - /// The given ratchet state will be identical to that saved during a previous call to - /// `SaveAsConfirmed`. Permanently delete the previous ratchet state, as in delete the ratchet - /// state with ratchet number one less than the given ratchet state. + /// Permanently delete the previous (oldest) ratchet key and fingerprint. + /// + /// The given `latest_ratchet_fingerprint` and `latest_ratchet_key` will be identical to those + /// saved during a prior `SaveAsConfirmed` call. + /// These two must be the only saved ratchet key and fingerprint when this call completes. DeletePrevious, } -use SaveRatchetAction::*; -impl SaveRatchetAction { - /// If this is true then this is the first time the latest ratchet state has ever been seen, - /// so it ought to be immediately saved. +use SaveAction::*; +impl SaveAction { + /// If this is true then the given `latest_ratchet_fingerprint` and `latest_ratchet_key` + /// must be saved to permanent storage. + /// `latest_ratchet_key` should be searchable using `latest_ratchet_fingerprint`. pub fn save_latest(&self) -> bool { match self { SaveAsUnconfirmed => true, @@ -249,8 +258,9 @@ impl SaveRatchetAction { _ => false, } } - /// If this is true then only the latest ratchet state should be saved. - /// Any previous ratchet states should be deleted now. + /// If this is true then the previous ratchet key and fingerprint should be deleted. + /// If `latest_ratchet_number > 1`, then the previous ratchet key and fingerprint will have + /// ratchet number `latest_ratchet_number - 1`. pub fn delete_previous(&self) -> bool { match self { ConfirmLatestAndDeletePrevious => true, @@ -258,6 +268,11 @@ impl SaveRatchetAction { _ => false, } } + /// If this is true then set the confirmed ratchet number to `latest_ratchet_number`. + /// + /// The confirmed ratchet number is a single 64-bit number saved to persistent storage that denotes its + /// associated ratchet key is "confirmed". Only the confirmed ratchet key should be used to + /// `open` a new session. pub fn confirm_latest(&self) -> bool { match self { ConfirmLatestAndDeletePrevious => true, diff --git a/src/lib.rs b/src/lib.rs index c2221ad..16a3afa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,7 @@ mod symmetric_state; mod zssp; pub mod error; -pub use crate::applicationlayer::{ApplicationLayer, GetRatchetAction, SaveRatchetAction}; +pub use crate::applicationlayer::{ApplicationLayer, RestoreAction, SaveAction}; pub use crate::log_event::LogEvent; pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_FINGERPRINT_SIZE, RATCHET_KEY_SIZE}; pub use crate::zssp::{AcceptSessionAction, Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/zssp.rs b/src/zssp.rs index b1de9c0..dd750d6 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -1015,18 +1015,18 @@ impl Context { let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); // Get ratchet key. let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(message); - use crate::GetRatchetAction::*; + use crate::RestoreAction::*; let (sent_zero, ratchet_number, ratchet_key) = if noise_pattern1.ratchet_fingerprint == [0u8; RATCHET_FINGERPRINT_SIZE] { - if app.allow_zero_ratchet(current_time) { - (true, 0, [0u8; RATCHET_KEY_SIZE]) - } else { + if app.hello_requires_recognized_ratchet(current_time) { return Ok(ReceiveResult::Rejected); + } else { + (true, 0, [0u8; RATCHET_KEY_SIZE]) } } else { - match app.lookup_ratchet(&noise_pattern1.ratchet_fingerprint, current_time) { - Ok(Found(ratchet_number, ratchet_key)) => (false, ratchet_number, ratchet_key), - Ok(Downgrade) => (false, 0, [0u8; RATCHET_KEY_SIZE]), - Ok(Ignore) => return Ok(ReceiveResult::Rejected), + match app.restore_ratchet(&noise_pattern1.ratchet_fingerprint, current_time) { + Ok(RestoreRatchet(ratchet_number, ratchet_key)) => (false, ratchet_number, ratchet_key), + Ok(DowngradeRatchet) => (false, 0, [0u8; RATCHET_KEY_SIZE]), + Ok(FailAuthentication) => return Err(byzantine_fault!(FaultType::FailedAuthentication, false)), Err(()) => return Err(ReceiveError::RatchetIoError), } }; @@ -1265,7 +1265,7 @@ impl Context { break; } } else { - if i > 0 || !app.allow_downgrade(&session, current_time) { + if i > 0 || app.initiator_disallows_downgrade(&session, current_time) { break; } // If auth failed maybe Bob wants to downgrade the ratchet, @@ -1322,7 +1322,7 @@ impl Context { let result = app.save_ratchet_state( &session.remote_s_public_key, &session.application_data, - SaveRatchetAction::SaveAsUnconfirmed, + SaveAction::SaveAsUnconfirmed, new_ratchet_number, new_ratchet_fingerprint.as_ref(), new_ratchet_key.as_ref(), @@ -1493,7 +1493,7 @@ impl Context { let result = app.save_ratchet_state( &remote_s_public_key, &application_data, - SaveRatchetAction::SaveAsConfirmed, + SaveAction::SaveAsConfirmed, new_ratchet_number, new_ratchet_fingerprint.as_ref(), new_ratchet_key.as_ref(), @@ -1834,7 +1834,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let result = app.save_ratchet_state( &session.remote_s_public_key, &session.application_data, - SaveRatchetAction::ConfirmLatestAndDeletePrevious, + SaveAction::ConfirmLatestAndDeletePrevious, ratchet.0, ratchet.1.as_ref(), ratchet.2.as_ref(), @@ -1878,7 +1878,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let result = app.save_ratchet_state( &session.remote_s_public_key, &session.application_data, - SaveRatchetAction::DeletePrevious, + SaveAction::DeletePrevious, state.ratchet_number, state.ratchet_fingerprint.as_ref(), state.ratchet_key.as_ref(), @@ -1992,7 +1992,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let result = app.save_ratchet_state( &session.remote_s_public_key, &session.application_data, - SaveRatchetAction::SaveAsUnconfirmed, + SaveAction::SaveAsUnconfirmed, new_ratchet_number, new_ratchet_fingerprint.as_ref(), new_ratchet_key.as_ref(), @@ -2091,7 +2091,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let result = app.save_ratchet_state( &session.remote_s_public_key, &session.application_data, - SaveRatchetAction::SaveAsConfirmed, + SaveAction::SaveAsConfirmed, new_ratchet_number, new_ratchet_fingerprint.as_ref(), new_ratchet_key.as_ref(), From c2c2a269ae5bb09f9bbd2eb3f016cd915f0823a6 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 13 Jul 2023 14:27:10 -0400 Subject: [PATCH 16/91] fixed bug --- src/applicationlayer.rs | 104 ++------- src/crypto/p384.rs | 2 +- src/lib.rs | 4 +- src/proto.rs | 31 +-- src/symmetric_state.rs | 16 +- src/zssp.rs | 485 +++++++++++++++++++++------------------- 6 files changed, 307 insertions(+), 335 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 3658be2..251e825 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -11,9 +11,10 @@ use std::sync::Arc; use crate::crypto::aes::{AesDec, AesEnc}; use crate::crypto::aes_gcm::{AesGcmDec, AesGcmEnc}; use crate::crypto::p384::{P384KeyPair, P384PublicKey}; -use crate::crypto::sha512::{HmacSha512, Sha512}; use crate::crypto::rand_core::{CryptoRng, RngCore}; -use crate::{log_event::LogEvent, Session, RATCHET_FINGERPRINT_SIZE, RATCHET_KEY_SIZE}; +use crate::crypto::secret::Secret; +use crate::crypto::sha512::{HmacSha512, Sha512}; +use crate::{log_event::LogEvent, Session, RATCHET_SIZE}; /// Trait to implement to integrate the session into an application. /// @@ -162,8 +163,8 @@ pub trait ApplicationLayer: Sized { /// to the zero ratchet key, restarting the ratchet chain. /// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped. #[allow(unused)] - fn restore_ratchet(&self, ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], current_time: i64) -> Result { - Ok(RestoreAction::DowngradeRatchet) + fn restore_ratchet(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE], current_time: i64) -> Result, ()> { + Ok(None) } /// Atomically save the given ratchet key, fingerprint and number to persistent storage. /// @@ -184,14 +185,11 @@ pub trait ApplicationLayer: Sized { /// (`initiator_disallows_downgrade` returns false and/or`restore_ratchet` returns `DowngradeRatchet`). /// Otherwise, when we restart, we will not be allowed to reconnect. #[allow(unused)] - fn save_ratchet_state( + fn save_ratchet_state<'a>( &self, remote_static_key: &Self::PublicKey, application_data: &Self::Data, - ratchet_action: SaveAction, - latest_ratchet_number: u64, - latest_ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], - latest_ratchet_key: &[u8; RATCHET_KEY_SIZE], + save_action: SaveAction<'a>, current_time: i64, ) -> Result<(), ()> { Ok(()) @@ -200,84 +198,20 @@ pub trait ApplicationLayer: Sized { #[inline] fn event_log<'a>(&self, event: LogEvent<'a, Self>, current_time: i64) {} } -pub enum RestoreAction { - RestoreRatchet(u64, [u8; RATCHET_KEY_SIZE]), - DowngradeRatchet, - FailAuthentication, + +#[derive(Default, Clone)] +pub struct RatchetState { + pub fingerprint: [u8; RATCHET_SIZE], + pub key: Secret, + pub ratchet_count: u64, } /// Ratchet keys and fingerprints should be saved *per remote peer*. It is up to the application to /// enforce separate storage for each remote peer based on `remote_static_key` and `application_data`. /// -/// Only up to 2 ratchet keys and fingerprints may be saved at one time. -/// If a 3rd needs to be saved the 1st should be deleted, if it was not already deleted. -pub enum SaveAction { - /// Save the given `latest_ratchet_fingerprint` and `latest_ratchet_key`, - /// but do not update the confirmed ratchet number. - /// - /// If a ratchet key and fingerprint already exist with ratchet number `latest_ratchet_number`, - /// then `latest_ratchet_fingerprint` and `latest_ratchet_key` should overwrite them. - /// - /// Keep the previous ratchet state saved and searchable until it is explicitly deleted. - /// If there are two saved ratchet keys and fingerprints, replace the oldest pair with - /// the new pair. - SaveAsUnconfirmed, - /// Save the given `latest_ratchet_fingerprint` and `latest_ratchet_key`, - /// and set the confirmed ratchet number to `latest_ratchet_number`. - /// The confirmed ratchet number should be set equal to `latest_ratchet_number`. - /// - /// If a ratchet key and fingerprint already exist with ratchet number `latest_ratchet_number`, - /// then `latest_ratchet_fingerprint` and `latest_ratchet_key` should overwrite them. - /// - /// Keep the previous ratchet state saved and searchable until it is explicitly deleted. - /// If there are two saved ratchet keys and fingerprints, replace the oldest pair with - /// the new pair. - SaveAsConfirmed, - /// Set the confirmed ratchet number to `latest_ratchet_number`, - /// and permanently delete the previous (oldest) ratchet key and fingerprint. - /// - /// The given `latest_ratchet_fingerprint` and `latest_ratchet_key` will be identical to those - /// saved during a prior `SaveAsUnconfirmed` call. - /// These two must be the only saved ratchet key and fingerprint when this call completes. - ConfirmLatestAndDeletePrevious, - /// Permanently delete the previous (oldest) ratchet key and fingerprint. - /// - /// The given `latest_ratchet_fingerprint` and `latest_ratchet_key` will be identical to those - /// saved during a prior `SaveAsConfirmed` call. - /// These two must be the only saved ratchet key and fingerprint when this call completes. - DeletePrevious, -} -use SaveAction::*; -impl SaveAction { - /// If this is true then the given `latest_ratchet_fingerprint` and `latest_ratchet_key` - /// must be saved to permanent storage. - /// `latest_ratchet_key` should be searchable using `latest_ratchet_fingerprint`. - pub fn save_latest(&self) -> bool { - match self { - SaveAsUnconfirmed => true, - SaveAsConfirmed => true, - _ => false, - } - } - /// If this is true then the previous ratchet key and fingerprint should be deleted. - /// If `latest_ratchet_number > 1`, then the previous ratchet key and fingerprint will have - /// ratchet number `latest_ratchet_number - 1`. - pub fn delete_previous(&self) -> bool { - match self { - ConfirmLatestAndDeletePrevious => true, - DeletePrevious => true, - _ => false, - } - } - /// If this is true then set the confirmed ratchet number to `latest_ratchet_number`. - /// - /// The confirmed ratchet number is a single 64-bit number saved to persistent storage that denotes its - /// associated ratchet key is "confirmed". Only the confirmed ratchet key should be used to - /// `open` a new session. - pub fn confirm_latest(&self) -> bool { - match self { - ConfirmLatestAndDeletePrevious => true, - SaveAsConfirmed => true, - _ => false, - } - } +/// Only up to 2 ratchet keys and fingerprints will be saved at one time. +pub enum SaveAction<'a> { + AddRatchet(&'a RatchetState), + DeleteRatchet(&'a RatchetState), + DeleteThenAddRatchet(&'a RatchetState, &'a RatchetState), + VerifyThenOverwriteRatchet(Option<&'a RatchetState>, &'a RatchetState), } diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs index 38b3ec2..2a14067 100644 --- a/src/crypto/p384.rs +++ b/src/crypto/p384.rs @@ -1,6 +1,6 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. -use super::rand_core::{RngCore, CryptoRng}; +use super::rand_core::{CryptoRng, RngCore}; pub const P384_PUBLIC_KEY_SIZE: usize = 49; pub const P384_ECDH_SHARED_SECRET_SIZE: usize = 48; diff --git a/src/lib.rs b/src/lib.rs index 16a3afa..6439e61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,7 @@ mod symmetric_state; mod zssp; pub mod error; -pub use crate::applicationlayer::{ApplicationLayer, RestoreAction, SaveAction}; +pub use crate::applicationlayer::{ApplicationLayer, SaveAction}; pub use crate::log_event::LogEvent; -pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_FINGERPRINT_SIZE, RATCHET_KEY_SIZE}; +pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_SIZE}; pub use crate::zssp::{AcceptSessionAction, Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/proto.rs b/src/proto.rs index 789e63f..634e8d5 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -21,9 +21,7 @@ pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; /// Minimum physical MTU for ZSSP to function. pub const MIN_TRANSPORT_MTU: usize = 128; -pub const RATCHET_KEY_SIZE: usize = 32; - -pub const RATCHET_FINGERPRINT_SIZE: usize = 32; +pub const RATCHET_SIZE: usize = 32; /// The application has the ability to attach a data payload to Alice's handshake. /// It will be the first payload Bob receives from Alice. @@ -48,7 +46,7 @@ pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_1: u8 = 0; pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_2: u8 = 1; pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_3: u8 = 2; pub(crate) const PACKET_TYPE_KEY_CONFIRM: u8 = 3; -pub(crate) const PACKET_TYPE_KEY_DELETE: u8 = 4; +pub(crate) const PACKET_TYPE_ACK: u8 = 4; pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_1: u8 = 5; pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_2: u8 = 6; pub(crate) const PACKET_TYPE_SESSION_REJECTED: u8 = 7; @@ -126,7 +124,7 @@ pub(crate) const MAX_UNASSOCIATED_HANDSHAKE_STATES: usize = 32; /// The maximum size a packet that is not associated to a session may be. /// Excludes the size of headers for fragmentation. -pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = NoiseXKPattern1::SIZE - HEADER_SIZE; +pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = NoiseXKPattern1::MAX_SIZE - HEADER_SIZE; /* XKhfs+psk2: @@ -168,10 +166,11 @@ pub(crate) struct NoiseXKPattern1 { pub noise_e1: [u8; KYBER_PUBLICKEYBYTES], /// -- end encrypted section pub e1_gcm_tag: [u8; AES_GCM_TAG_SIZE], - /// -- start AES-GCM(k_es) encrypted section - pub ratchet_fingerprint: [u8; RATCHET_FINGERPRINT_SIZE], - /// -- end encrypted section - pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], + pub payload: [u8; RATCHET_SIZE + RATCHET_SIZE + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE], +} + +#[repr(C, packed)] +pub(crate) struct ChallengeResponse { pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE], pub challenge_mac: [u8; CHALLENGE_MAC_SIZE], pub challenge_pow: [u8; CHALLENGE_POW_SIZE], @@ -183,9 +182,12 @@ impl NoiseXKPattern1 { pub const E1_ENC_START: usize = Self::PROLOGUE_END + P384_PUBLIC_KEY_SIZE; pub const E1_AUTH_START: usize = Self::E1_ENC_START + KYBER_PUBLICKEYBYTES; pub const P_ENC_START: usize = Self::E1_AUTH_START + AES_GCM_TAG_SIZE; - pub const P_AUTH_START: usize = Self::P_ENC_START + RATCHET_FINGERPRINT_SIZE; - pub const P_AUTH_END: usize = Self::P_AUTH_START + AES_GCM_TAG_SIZE; - pub const SIZE: usize = Self::P_AUTH_END + CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; + + pub const MIN_SIZE: usize = Self::P_ENC_START + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE; + pub const MAX_SIZE: usize = Self::MIN_SIZE + RATCHET_SIZE + RATCHET_SIZE; +} +impl ChallengeResponse { + pub const SIZE: usize = CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; } #[repr(C, packed)] @@ -269,16 +271,17 @@ impl ProtocolFlatBuffer for NoiseXKPattern1 {} impl ProtocolFlatBuffer for NoiseXKPattern2 {} impl ProtocolFlatBuffer for NoiseKKPattern1or2 {} impl ProtocolFlatBuffer for BobDOSChallenge {} +impl ProtocolFlatBuffer for ChallengeResponse {} #[inline(always)] pub(crate) fn byte_array_as_proto_buffer(b: &[u8]) -> &B { - assert_eq!(b.len(), size_of::()); + assert!(b.len() >= size_of::()); unsafe { &*b.as_ptr().cast() } } #[inline(always)] pub(crate) fn byte_array_as_proto_buffer_mut(b: &mut [u8]) -> &mut B { - assert_eq!(b.len(), size_of::()); + assert!(b.len() >= size_of::()); unsafe { &mut *b.as_mut_ptr().cast() } } /// Trick rust into letting us use a hasher that returns more than 64 bits. diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 0c474d2..339123c 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -56,12 +56,17 @@ impl SymmetricState { temp_h } /// Corresponds to Noise `MixKeyAndHash` followed by `InitializeKey`. - pub(crate) fn mix_key_and_hash_initialize_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> ([u8; NOISE_HASHLEN], Secret) { + pub(crate) fn mix_key_and_hash_initialize_key( + &mut self, + hm: &mut impl HmacSha512, + input_key_material: &[u8], + ) -> ([u8; NOISE_HASHLEN], Secret) { let mut next_ck = Secret::new(); let mut temp_h = [0u8; NOISE_HASHLEN]; let mut temp_k = [0u8; NOISE_HASHLEN]; - self.kbkdf(hm, + self.kbkdf( + hm, input_key_material, self.label(), 3, @@ -79,7 +84,12 @@ impl SymmetricState { /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. #[inline(always)] - pub(crate) fn get_ask2(&self, hm: &mut impl HmacSha512, label: u8, noise_h: &[u8; NOISE_HASHLEN]) -> (Secret, Secret) { + pub(crate) fn get_ask2( + &self, + hm: &mut impl HmacSha512, + label: u8, + noise_h: &[u8; NOISE_HASHLEN], + ) -> (Secret, Secret) { let mut temp_k1 = [0u8; NOISE_HASHLEN]; let mut temp_k2 = [0u8; NOISE_HASHLEN]; self.kbkdf(hm, noise_h, [b'A', b'S', b'K', label], 2, &mut temp_k1, Some(&mut temp_k2), None); diff --git a/src/zssp.rs b/src/zssp.rs index dd750d6..8c4abef 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -20,9 +20,9 @@ use crate::crypto::aes::{AesDec, AesEnc}; use crate::crypto::aes_gcm::{AesGcmDec, AesGcmEnc, AES_GCM_IV_SIZE, AES_GCM_KEY_SIZE, AES_GCM_TAG_SIZE}; use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; -use crate::crypto::secret::{secure_eq, Secret}; -use crate::crypto::sha512::{Sha512, HmacSha512}; use crate::crypto::rand_core::RngCore; +use crate::crypto::secret::{secure_eq, Secret}; +use crate::crypto::sha512::{HmacSha512, Sha512}; use crate::applicationlayer::*; use crate::error::{FaultType, OpenError, ReceiveError, SendError}; @@ -77,14 +77,12 @@ pub enum ReceiveResult<'b, Application: ApplicationLayer> { pub enum SessionEvent<'b> { /// The received packet was valid, and it contained the necessary keys to fully establish a new /// session with Alice, the handshake initiator. - /// Contains the current ratchet number for metric purposes. /// /// If the session Arc returned is dropped, the session with this peer will be immediately /// terminated. Save the session Arc to some long lived datastructure to keep it alive. - NewSession(u64), + NewSession, /// When Alice calls `Context::open`, a session will be created, but Bob will not yet have /// received this session. They will have to successfully complete a handshake first. - /// Contains the current ratchet number for metric purposes. /// /// Alice will receive this return value when the received packet confirms both parties /// have completed the initial handshake and now have a shared session with each other. @@ -93,7 +91,7 @@ pub enum SessionEvent<'b> { /// /// This return value can only occur once per session, only for session objects that were /// created with `Context::open`. - Established(u64), + Established, /// Bob explicitly refused to establish a session with Alice, and sent us an error code. /// The application should immediately drop this session as Bob will not allow us to connect. /// @@ -101,9 +99,6 @@ pub enum SessionEvent<'b> { Rejected, /// The received packet was valid and a data payload was decoded and authenticated. Data(&'b mut [u8]), - /// The received packet completed a rekey event and a new ratchet key was derived. - /// Contains the current ratchet number for metric purposes. - Ratchet(u64), /// The received packet was some authentic protocol control packet. No action needs to be taken. Control, } @@ -163,9 +158,7 @@ unsafe impl Sync for Session {} /// Session state may only be mutated during atomic transitions of the offer state machine. struct SessionMutableState { - ratchet_number: u64, - ratchet_fingerprint: Secret, - ratchet_key: Secret, + ratchet_state: [Option; 2], /// For OOO rekeying reliability we allow our version of Noise CipherState to hold the last two /// session keys, instead of just the most recent one. cipher_states: [Option>; 2], @@ -199,9 +192,6 @@ enum OfferStateMachine { timeout: i64, noise_message: [u8; NoiseKKPattern1or2::SIZE], kex_send_key: Secret, - new_ratchet_number: u64, - new_ratchet_fingerprint: Secret, - new_ratchet_key: Secret, }, // -> Normal KeyConfirm { next_retry_time: AtomicI64, @@ -214,8 +204,7 @@ pub(crate) struct NoiseXKBobHandshakeState { local_key_id: NonZeroU32, header_receive_key: Secret, header_send_key: Secret, - ratchet_number: u64, - ratchet_fingerprint: Option<[u8; RATCHET_FINGERPRINT_SIZE]>, + ratchet_state: Option, noise_h_ee1peekem1pskp: [u8; NOISE_HASHLEN], noise_e_secret: Application::KeyPair, noise_ck_eseeekem1psk: SymmetricState, @@ -241,15 +230,14 @@ enum NoiseXKAliceHandshakeState { noise_ck_es: SymmetricState, /// ZSSP assumes an unreliable, out-of-order physical transport environment, so for that /// reason we have to resend key offers. - noise_message: [u8; NoiseXKPattern1::SIZE], + noise_message: [u8; NoiseXKPattern1::MAX_SIZE], + noise_message_len: usize, message_id: u64, }, NoiseXKPattern3 { noise_message: [u8; NoiseXKPattern3::MAX_SIZE], noise_message_len: usize, - new_ratchet_number: u64, - new_ratchet_fingerprint: Secret, - new_ratchet_key: Secret, + new_ratchet_state: RatchetState, }, } @@ -360,17 +348,17 @@ impl Context { } else { // We have to eventually time out NoiseXKPattern3 because of unreliable network conditions. if handshake_state.timeout <= current_time { + let ratchet_state = state.ratchet_state.clone(); drop(state); let _kex_lock = session.state_machine_lock.lock().unwrap(); let mut state = session.state.write().unwrap(); - let ratchet_fingerprint = state.ratchet_fingerprint.clone(); // Since we dropped the lock we must re-check if we are in the correct state. if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { if handshake_state.timeout <= current_time { app.event_log(LogEvent::ServiceXKTimeout(&session), current_time); if !handshake_state.reinitialize( &session, - &ratchet_fingerprint, + &ratchet_state, &mut self.0.session_map.write().unwrap(), &mut self.0.rng.lock().unwrap(), current_time, @@ -382,13 +370,13 @@ impl Context { } else if let Some((mut send, mut mtu)) = send_to(&session) { mtu = mtu.max(MIN_TRANSPORT_MTU); match &handshake_state.offer { - NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, message_id, .. } => { + NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } => { app.event_log(LogEvent::ServiceXK1Resend(&session), current_time); // We are in state NoiseXKPattern1 so resend noise_pattern1. send_with_fragmentation( &mut send, mtu, - &mut noise_message.clone(), + &mut noise_message.clone()[..*noise_message_len], PACKET_TYPE_NOISE_XK_PATTERN_1, None, *message_id, @@ -492,7 +480,7 @@ impl Context { mut mtu: usize, remote_s_public_key: Application::PublicKey, application_data: Application::Data, - ratchet_state: Option<(u64, [u8; RATCHET_FINGERPRINT_SIZE], [u8; RATCHET_KEY_SIZE])>, + mut ratchet_state: [Option; 2], local_identity_blob: Application::LocalIdentityBlob, current_time: i64, ) -> Result>, OpenError> { @@ -500,8 +488,17 @@ impl Context { if local_identity_blob.as_ref().len() > MAX_IDENTITY_BLOB_SIZE { return Err(OpenError::DataTooLarge); } - let (ratchet_number, mut ratchet_fingerprint, mut ratchet_key) = - ratchet_state.unwrap_or((0, [0; RATCHET_FINGERPRINT_SIZE], [0; RATCHET_KEY_SIZE])); + // Double check that the application gave us these in the correct order. + if let Some(first) = ratchet_state[0] { + if let Some(second) = ratchet_state[1] { + if first.ratchet_count < second.ratchet_count { + ratchet_state.swap(0, 1); + } + } + } else { + ratchet_state.swap(0, 1); + } + let sha512 = &mut Application::Hash::new(); let mut noise_kk_ss = Secret::new(); @@ -517,12 +514,8 @@ impl Context { let mut session_map = self.0.session_map.write().unwrap(); let local_key_id = generate_key_id(&session_map, &mut self.0.rng.lock().unwrap()); // Begin Noise XKhfs+psk2. - let (offer, a2b_header_key, b2a_header_key) = NoiseXKAliceHandshake::::initialize( - local_key_id, - &remote_s_public_key, - &ratchet_fingerprint, - &mut self.0.rng.lock().unwrap(), - )?; + let (offer, a2b_header_key, b2a_header_key) = + NoiseXKAliceHandshake::::initialize(local_key_id, &remote_s_public_key, &ratchet_state, &mut self.0.rng.lock().unwrap())?; let handshake_state = Box::new(NoiseXKAliceHandshake { next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), @@ -553,9 +546,7 @@ impl Context { counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), state_machine_lock: Mutex::new(()), state: RwLock::new(SessionMutableState { - ratchet_number, - ratchet_fingerprint: Secret::from_bytes_then_nuke(&mut ratchet_fingerprint), - ratchet_key: Secret::from_bytes_then_nuke(&mut ratchet_key), + ratchet_state: ratchet_state.clone(), cipher_states: [None, None], // Points at 1 until the first key is confirmed. current_key: 1, @@ -619,7 +610,7 @@ impl Context { &self, app: &Application, check_allow_incoming_session: impl FnOnce() -> IncomingSessionAction, - check_accept_session: impl FnOnce(&Application::PublicKey, &[u8], Option<&[u8; RATCHET_FINGERPRINT_SIZE]>) -> AcceptSessionAction, + check_accept_session: impl FnOnce(&Application::PublicKey, &[u8]) -> AcceptSessionAction, mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, mut send_unassociated_mtu: usize, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, @@ -876,31 +867,37 @@ impl Context { debug_assert!(fragments.len() >= 1); debug_assert!(incoming.is_none() || session.is_none()); - let mut pkt_assembly_buffer = [0u8; MAX_NOISE_HANDSHAKE_SIZE]; - let message_size = assemble_fragments_into::(fragments, &mut pkt_assembly_buffer)?; + let message = &mut [0u8; MAX_NOISE_HANDSHAKE_SIZE]; + let message_size = assemble_fragments_into::(fragments, message)?; if message_size < MIN_PACKET_SIZE { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } - let message = &mut pkt_assembly_buffer[..message_size]; use OfferStateMachine::*; match packet_type { PACKET_TYPE_NOISE_XK_PATTERN_1 => { - app.event_log(LogEvent::ReceiveUncheckedXK1, current_time); // Alice (remote) --> Bob (local) // -> e, es, e1 + app.event_log(LogEvent::ReceiveUncheckedXK1, current_time); + if session.is_some() || incoming.is_some() { return Err(byzantine_fault!(FaultType::OutOfSequence, false)); } - if message.len() != NoiseXKPattern1::SIZE { + if message.len() < NoiseXKPattern1::MIN_SIZE || message.len() > NoiseXKPattern1::MIN_SIZE { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } - let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); // The message id must be the first 8 bytes of the gcm tag. // This forces the message id to be authenticated along with the entire message. - if noise_pattern1.header[8..] != noise_pattern1.p_gcm_tag[8..] { + let challenge_start_idx = message_size - ChallengeResponse::SIZE; + if message[8..] != message[challenge_start_idx - 8..challenge_start_idx] { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } + let total_ratchet_fingerprints = (challenge_start_idx - AES_GCM_TAG_SIZE) / RATCHET_SIZE; + if (challenge_start_idx - AES_GCM_TAG_SIZE) % RATCHET_SIZE != 0 || total_ratchet_fingerprints > 2 { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + + let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); if let Some(remote_key_id) = NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.alice_key_id)) { let sha512 = &mut Application::Hash::new(); // Let application filter incoming connection attempts by whatever criteria it wants. @@ -908,20 +905,21 @@ impl Context { match check_allow_incoming_session() { IncomingSessionAction::Allow => {} IncomingSessionAction::Challenge => { + let response: &ChallengeResponse = byte_array_as_proto_buffer(&message[challenge_start_idx..message_size]); let mut counter = 0u64.to_ne_bytes(); - counter.copy_from_slice(&noise_pattern1.challenge_counter); + counter.copy_from_slice(&response.challenge_counter); let counter = u64::from_be_bytes(counter); sha512.reset(); let mut hasher = ShaHasher(sha512); let mut output = [0u8; NOISE_HASHLEN]; - hasher.0.update(&noise_pattern1.challenge_counter); + hasher.0.update(&response.challenge_counter); remote_address.hash(&mut hasher); hasher.0.update(&self.0.challenge_salt); hasher.0.finish(&mut output); let is_valid = self.check_challenge_window(counter) - && secure_eq(&output[..CHALLENGE_MAC_SIZE], &noise_pattern1.challenge_mac) - && verify_pow::(&mut hasher.0, &message) + && secure_eq(&output[..CHALLENGE_MAC_SIZE], &response.challenge_mac) + && verify_pow::(&mut hasher.0, &message[challenge_start_idx..message_size]) && self.update_challenge_window(counter); app.event_log(LogEvent::ReceiveCheckXK1Challenge(is_valid), current_time); if !is_valid { @@ -940,7 +938,7 @@ impl Context { hasher.0.update(&self.0.challenge_salt); hasher.0.finish(&mut output); challenge.challenge_mac.copy_from_slice(&output[..CHALLENGE_MAC_SIZE]); - challenge.prior_challenge_pow = noise_pattern1.challenge_pow; + challenge.prior_challenge_pow = response.challenge_pow; // We haven't decrypted any of Alice's packet so we don't know the // header protection cipher. // For DOS resistance Alice will not accept unencrypted headers directly @@ -973,9 +971,8 @@ impl Context { let mut noise_ck = SymmetricState::new(INITIAL_H); let hmac = &mut Application::HmacHash::new(); let mut noise_es = Secret::new(); - let noise_e_pattern1 = - from_bytes_agreement::(&noise_pattern1.noise_e, &self.0.static_keypair, noise_es.as_mut()) - .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; + let noise_e_pattern1 = from_bytes_agreement::(&noise_pattern1.noise_e, &self.0.static_keypair, noise_es.as_mut()) + .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; let noise_h_e = mix_hash(sha512, &noise_h, &noise_pattern1.noise_e); noise_ck.mix_key(hmac, &noise_pattern1.noise_e); // Noise process pattern1 es token. @@ -1006,7 +1003,7 @@ impl Context { &noise_h_ee1, packet_type, 1, - &mut message[NoiseXKPattern1::P_ENC_START..NoiseXKPattern1::P_AUTH_END], + &mut message[NoiseXKPattern1::P_ENC_START..challenge_start_idx], ); drop(noise_k_es); if !is_auth { @@ -1014,22 +1011,24 @@ impl Context { } let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); // Get ratchet key. - let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(message); - use crate::RestoreAction::*; - let (sent_zero, ratchet_number, ratchet_key) = if noise_pattern1.ratchet_fingerprint == [0u8; RATCHET_FINGERPRINT_SIZE] { - if app.hello_requires_recognized_ratchet(current_time) { - return Ok(ReceiveResult::Rejected); - } else { - (true, 0, [0u8; RATCHET_KEY_SIZE]) - } - } else { - match app.restore_ratchet(&noise_pattern1.ratchet_fingerprint, current_time) { - Ok(RestoreRatchet(ratchet_number, ratchet_key)) => (false, ratchet_number, ratchet_key), - Ok(DowngradeRatchet) => (false, 0, [0u8; RATCHET_KEY_SIZE]), - Ok(FailAuthentication) => return Err(byzantine_fault!(FaultType::FailedAuthentication, false)), + let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); + let mut ratchet_state = None; + for i in 0..total_ratchet_fingerprints { + match app.restore_ratchet( + (&noise_pattern1.payload[i * RATCHET_SIZE..(i + 1) * RATCHET_SIZE]).try_into().unwrap(), + current_time, + ) { + Ok(Some(rs)) => { + ratchet_state = Some(rs); + break; + } + Ok(None) => {} Err(()) => return Err(ReceiveError::RatchetIoError), } - }; + } + if ratchet_state.is_none() && app.hello_requires_recognized_ratchet(current_time) { + return Ok(ReceiveResult::Rejected); + } // Start of Noise XKhfs+psk2 pattern2. let mut message2 = [0u8; NoiseXKPattern2::SIZE]; @@ -1064,7 +1063,8 @@ impl Context { noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); drop(noise_ekem1_secret); // Noise process pattern2 psk token. - let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, &ratchet_key); + let ratchet_key = ratchet_state.map(|rs| rs.key.as_ref()).unwrap_or(&[0; RATCHET_SIZE]); + let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); // Noise process pattern2 payload. // We try to prevent the id we generate from colliding with another session but @@ -1087,8 +1087,7 @@ impl Context { let handshake = Arc::new(NoiseXKBobHandshakeState { local_key_id, remote_key_id, - ratchet_number, - ratchet_fingerprint: (!sent_zero).then(|| noise_pattern1.ratchet_fingerprint), + ratchet_state, noise_h_ee1peekem1pskp, noise_ck_eseeekem1psk: noise_ck.clone(), noise_k_eseeekem1psk: noise_k_eseeekem1psk.clone(), @@ -1121,7 +1120,9 @@ impl Context { } } PACKET_TYPE_BOB_DOS_CHALLENGE => { + let message = &mut message[..message_size]; app.event_log(LogEvent::ReceiveUncheckedDOSChallenge, current_time); + // We expect Bob to only send this to us through our unassociated defrag cache. if incoming.is_some() || session.is_some() { return Err(byzantine_fault!(FaultType::OutOfSequence, false)); @@ -1136,23 +1137,25 @@ impl Context { // We don't need to hold the kex lock because we are not transitioning state. let mut state = session.state.write().unwrap(); if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, .. } = &mut handshake_state.offer { - let pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(noise_message); + if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, .. } = &mut handshake_state.offer { + let response_raw = &mut noise_message[*noise_message_len - ChallengeResponse::SIZE..]; + + let response: &ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); // Only people who know what Alice's prior pow was can convince us to // compute a new pow. - if challenge.prior_challenge_pow != pattern1.challenge_pow { + if challenge.prior_challenge_pow != response.challenge_pow { // This can occur if Bob sends us multiple challenges and they // arrive OOO. return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); } - pattern1.challenge_counter.copy_from_slice(&challenge.challenge_counter); - pattern1.challenge_mac.copy_from_slice(&challenge.challenge_mac); + response.challenge_counter.copy_from_slice(&challenge.challenge_counter); + response.challenge_mac.copy_from_slice(&challenge.challenge_mac); let mut pow = self.0.rng.lock().unwrap().next_u64(); let sha512 = &mut Application::Hash::new(); loop { - let pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(noise_message); - pattern1.challenge_pow.copy_from_slice(&pow.to_be_bytes()); - if verify_pow::(sha512, noise_message) { + let response: &ChallengeResponse = byte_array_as_proto_buffer(response_raw); + response.challenge_pow.copy_from_slice(&pow.to_be_bytes()); + if verify_pow::(sha512, response_raw) { break; } pow = pow.wrapping_add(1); @@ -1179,9 +1182,11 @@ impl Context { } } PACKET_TYPE_NOISE_XK_PATTERN_2 => { - app.event_log(LogEvent::ReceiveUncheckedXK2, current_time); // Bob (remote) --> Alice (local) // <- e, ee, ekem1, psk + let message = &mut message[..message_size]; + app.event_log(LogEvent::ReceiveUncheckedXK2, current_time); + if incoming.is_some() { return Err(byzantine_fault!(FaultType::OutOfSequence, false)); } @@ -1231,51 +1236,81 @@ impl Context { if let Some(Ok(noise_ekem1_secret)) = is_auth.then_some(noise_ekem1_secret) { noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); drop(noise_ekem1_secret); - // We attempt to decrypt the payload at most twice. First time with - // the ratchet key Alice last remembers, and second time with a ratchet + // We attempt to decrypt the payload at most three times. First two times with + // the ratchet key Alice remembers, and final time with a ratchet // key of zero if Alice allows ratchet downgrades. - let mut ratchet_number = state.ratchet_number; - let mut ratchet_key = state.ratchet_key.as_ref(); - let mut ratchet_result = None; - for i in 0..2 { - // Constant time ratchet key downgrade check. - let mut noise_ck_ratchet = noise_ck.clone(); + + let test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState, Secret, [u8; 64])> { + // Check for which ratchet key Bob wants to use. + let mut noise_ck = noise_ck.clone(); let mut payload = [0u8; NoiseXKPattern2::P_AUTH_END - NoiseXKPattern2::P_ENC_START]; payload.copy_from_slice(&message[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END]); // Noise process pattern2 psk token. - let (temp_h, noise_k_ratchet) = noise_ck_ratchet.mix_key_and_hash_initialize_key(hmac, ratchet_key); + let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); // Noise process pattern2 payload. - let (is_auth, noise_h_ratchet) = decrypt_and_hash::( + let (is_auth, noise_h_ee1peekem1pskp) = decrypt_and_hash::( sha512, - &noise_k_ratchet, + &noise_k_eseeekem1psk, &noise_h_ee1peekem1psk, packet_type, 0, &mut payload, ); - let mut key_id = 0u32.to_ne_bytes(); - key_id.copy_from_slice(&payload[..NoiseXKPattern2::P_AUTH_START - NoiseXKPattern2::P_ENC_START]); - if is_auth { - if i > 0 { - ratchet_result = - NonZeroU32::new(u32::from_ne_bytes(key_id)).map(|id| (id, noise_k_ratchet, noise_h_ratchet)); - noise_ck = noise_ck_ratchet.clone(); - break; - } + let key_id = NonZeroU32::new(u32::from_ne_bytes( + (&payload[..NoiseXKPattern2::P_AUTH_START - NoiseXKPattern2::P_ENC_START]) + .try_into() + .unwrap(), + )); + key_id.map(|kid| (kid, noise_ck, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) } else { - if i > 0 || app.initiator_disallows_downgrade(&session, current_time) { - break; + None + } + }; + // Check first key. + let mut ratchet_i = 0; + let mut is_auth = false; + let mut ratchet_number = 0; + let remote_key_id; + let noise_k_eseeekem1psk; + let noise_h_ee1peekem1pskp; + if let Some(rs) = state.ratchet_state[0] { + if let Some((key_id, ck, k, h)) = test_ratchet_key(rs.key.as_ref()) { + ratchet_number = rs.ratchet_count; + remote_key_id = key_id; + noise_ck = ck; + noise_k_eseeekem1psk = k; + noise_h_ee1peekem1pskp = h; + is_auth = true; + } + } + // Check second key. + if !is_auth { + ratchet_i = 1; + if let Some(rs) = state.ratchet_state[1] { + if let Some((key_id, ck, k, h)) = test_ratchet_key(rs.key.as_ref()) { + ratchet_number = rs.ratchet_count; + remote_key_id = key_id; + noise_ck = ck; + noise_k_eseeekem1psk = k; + noise_h_ee1peekem1pskp = h; + is_auth = true; } - // If auth failed maybe Bob wants to downgrade the ratchet, - // retry decryption with no ratchet if we have not already. - ratchet_number = 0; - ratchet_key = &[0u8; RATCHET_KEY_SIZE]; + } + } + // Check zero key. + if !is_auth { + if let Some((key_id, ck, k, h)) = test_ratchet_key(&[0u8; RATCHET_SIZE]) { + noise_ck = ck; + remote_key_id = key_id; + noise_k_eseeekem1psk = k; + noise_h_ee1peekem1pskp = h; + is_auth = true; } } - if let Some((remote_key_id, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) = ratchet_result { + if is_auth { // Start of Noise XKhfs+psk2 pattern3. let mut message3 = [0u8; NoiseXKPattern3::MAX_SIZE]; // Noise process pattern3 s token. @@ -1316,21 +1351,24 @@ impl Context { drop(noise_k_eseeekem1pskse); // Alice finished Noise XKhfs+psk2 handshake. // Transition offer state machine to the NoiseXKPattern3 state. - let new_ratchet_number = ratchet_number + 1; let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); - let result = app.save_ratchet_state( - &session.remote_s_public_key, - &session.application_data, - SaveAction::SaveAsUnconfirmed, - new_ratchet_number, - new_ratchet_fingerprint.as_ref(), - new_ratchet_key.as_ref(), - current_time, - ); + let new_ratchet_state = RatchetState { + fingerprint: new_ratchet_fingerprint.0, + key: new_ratchet_key, + ratchet_count: ratchet_number + 1, + }; + let action = if state.ratchet_state[0].is_some() && state.ratchet_state[1].is_some() { + SaveAction::DeleteThenAddRatchet(&state.ratchet_state[1 - ratchet_i].unwrap(), &new_ratchet_state) + } else { + SaveAction::AddRatchet(&new_ratchet_state) + }; + let result = + app.save_ratchet_state(&session.remote_s_public_key, &session.application_data, action, current_time); if result.is_err() { return Err(ReceiveError::RatchetIoError); } + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); let local_key_id = handshake_state.local_key_id; @@ -1346,8 +1384,17 @@ impl Context { .lock() .unwrap() .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); + state.ratchet_state[1] = state.ratchet_state[ratchet_i]; + state.ratchet_state[0] = Some(new_ratchet_state.clone()); - state.cipher_states[0].replace(SessionKey::new(hmac, noise_ck, local_key_id, remote_key_id, INIT_COUNTER, false)); + state.cipher_states[0].replace(SessionKey::new( + hmac, + noise_ck, + local_key_id, + remote_key_id, + INIT_COUNTER, + false, + )); debug_assert!(state.cipher_states[1].is_none()); if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { handshake_state.next_retry_time = @@ -1356,9 +1403,7 @@ impl Context { handshake_state.offer = NoiseXKAliceHandshakeState::NoiseXKPattern3 { noise_message: message3, noise_message_len: p_auth_end, - new_ratchet_number, - new_ratchet_fingerprint: new_ratchet_fingerprint.clone(), - new_ratchet_key: new_ratchet_key.clone(), + new_ratchet_state: new_ratchet_state.clone(), }; } drop(state); @@ -1386,11 +1431,10 @@ impl Context { // We restart the offer instead of dropping the session to defend against DOS. drop(state); let mut state = session.state.write().unwrap(); - let ratchet_fingerprint = state.ratchet_fingerprint.clone(); if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { if !handshake_state.reinitialize( &session, - &ratchet_fingerprint, + &state.ratchet_state, &mut self.0.session_map.write().unwrap(), &mut self.0.rng.lock().unwrap(), current_time, @@ -1472,11 +1516,7 @@ impl Context { // Bob finished Noise XKhfs+psk2 handshake. let header_send_cipher = Application::PrpEnc::new(handshake_state.header_send_key.as_ref()); let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); - match check_accept_session( - &remote_s_public_key, - &message[p_enc_start..p_auth_start], - handshake_state.ratchet_fingerprint.as_ref(), - ) { + match check_accept_session(&remote_s_public_key, &message[p_enc_start..p_auth_start]) { AcceptSessionAction::Accept(application_data) => { let mut noise_kk_ss = Secret::new(); if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { @@ -1489,14 +1529,15 @@ impl Context { let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); // We must make sure the ratchet key is saved before we transition. - let new_ratchet_number = handshake_state.ratchet_number + 1; + let new_ratchet_state = RatchetState { + fingerprint: new_ratchet_fingerprint.0, + key: new_ratchet_key, + ratchet_count: handshake_state.ratchet_state.map(|rs| rs.ratchet_count + 1).unwrap_or(1), + }; let result = app.save_ratchet_state( &remote_s_public_key, &application_data, - SaveAction::SaveAsConfirmed, - new_ratchet_number, - new_ratchet_fingerprint.as_ref(), - new_ratchet_key.as_ref(), + SaveAction::VerifyThenOverwriteRatchet(handshake_state.ratchet_state.as_ref(), &new_ratchet_state), current_time, ); if result.is_err() { @@ -1515,9 +1556,7 @@ impl Context { counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), state_machine_lock: Mutex::new(()), state: RwLock::new(SessionMutableState { - ratchet_number: new_ratchet_number, - ratchet_fingerprint: new_ratchet_fingerprint.clone(), - ratchet_key: new_ratchet_key.clone(), + ratchet_state: [Some(new_ratchet_state.clone()), None], cipher_states: [ Some(SessionKey::new( hmac, @@ -1558,7 +1597,7 @@ impl Context { let _ = session.send_control(&session.state.read().unwrap(), send_unassociated_reply, PACKET_TYPE_KEY_CONFIRM, &[]); app.event_log(LogEvent::ReceiveValidXK3(&session.application_data), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::NewSession(new_ratchet_number))); + return Ok(ReceiveResult::Session(session, SessionEvent::NewSession)); } else { // This can occur if we accidentally generate a key id collision. // There is an extremely short amount of time during which @@ -1717,7 +1756,7 @@ fn initiate_rekey( // Start of Noise KKpsk0 pattern1. // Noise process pattern1 psk0 token. let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_key.as_ref()); + let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_state[0].unwrap().key.as_ref()); let noise_h_psk = mix_hash(sha512, &session.noise_kk_local_init_h, &noise_temp_h); // Noise process pattern1 e token. let noise_e_secret = Application::KeyPair::generate(&mut context.rng.lock().unwrap()); @@ -1806,41 +1845,30 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let mut state = session.state.write().unwrap(); // We only want to stop sending NoiseKKPattern2 offers when the latest derived // key is confirmed. And we only want to do that once. - let (used_latest_key, key_confirmed, ret) = match &state.outgoing_offer { - NoiseKKPattern2 { - new_ratchet_number, new_ratchet_fingerprint, new_ratchet_key, .. - } => ( - true, - Some((*new_ratchet_number, new_ratchet_fingerprint.clone(), new_ratchet_key.clone())), - SessionEvent::Ratchet(*new_ratchet_number), - ), + let (used_latest_key, try_delete, ret) = match &state.outgoing_offer { + NoiseKKPattern2 { .. } => (true, true, SessionEvent::Control), NoiseXKPattern1or3(handshake_state) => { - if let NoiseXKAliceHandshakeState::NoiseXKPattern3 { - new_ratchet_number, new_ratchet_fingerprint, new_ratchet_key, .. - } = &handshake_state.offer - { - ( - true, - Some((*new_ratchet_number, new_ratchet_fingerprint.clone(), new_ratchet_key.clone())), - SessionEvent::Established(*new_ratchet_number), - ) + if let NoiseXKAliceHandshakeState::NoiseXKPattern3 { .. } = &handshake_state.offer { + (true, true, SessionEvent::Established) } else { - (false, None, SessionEvent::Control) + (false, false, SessionEvent::Control) } } - _ => (true, None, SessionEvent::Control), + _ => (true, false, SessionEvent::Control), }; - if let Some(ratchet) = key_confirmed { - let result = app.save_ratchet_state( - &session.remote_s_public_key, - &session.application_data, - SaveAction::ConfirmLatestAndDeletePrevious, - ratchet.0, - ratchet.1.as_ref(), - ratchet.2.as_ref(), - current_time, - ); - if result.is_ok() { + if try_delete { + let is_ok = if let Some(rs) = &state.ratchet_state[1] { + app.save_ratchet_state( + &session.remote_s_public_key, + &session.application_data, + SaveAction::DeleteRatchet(rs), + current_time, + ) + .is_ok() + } else { + true + }; + if is_ok { if let NoiseKKPattern2 { kex_send_key, .. } = &state.outgoing_offer { session .kex_send_cipher @@ -1848,9 +1876,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu .unwrap() .replace(Application::AeadEnc::new(kex_send_key.as_ref())); } - state.ratchet_number = ratchet.0; - state.ratchet_fingerprint.overwrite(&ratchet.1); - state.ratchet_key.overwrite(&ratchet.2); + state.ratchet_state[1] = None; state.current_key ^= 1; state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); } else { @@ -1861,34 +1887,20 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu drop(kex_lock); if used_latest_key { if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_KEY_DELETE, &[]); + let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_ACK, &[]); } } Ok(ReceiveResult::Session(session, ret)) } - PACKET_TYPE_KEY_DELETE => { + PACKET_TYPE_ACK => { drop(state); app.event_log(LogEvent::ReceiveValidKeyDelete(&session), current_time); let kex_lock = session.state_machine_lock.lock().unwrap(); let mut state = session.state.write().unwrap(); - // Check if we should end any current offers and transition - // back to the None state + // Check if we should end any current offers and transition back to Normal state match &state.outgoing_offer { KeyConfirm { .. } => { - let result = app.save_ratchet_state( - &session.remote_s_public_key, - &session.application_data, - SaveAction::DeletePrevious, - state.ratchet_number, - state.ratchet_fingerprint.as_ref(), - state.ratchet_key.as_ref(), - current_time, - ); - if result.is_ok() { - state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); - } else { - return Err(ReceiveError::RatchetIoError); - } + state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); } _ => (), } @@ -1926,16 +1938,14 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let sha512 = &mut Application::Hash::new(); let hmac = &mut Application::HmacHash::new(); let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_key.as_ref()); + let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_state[0].unwrap().key.as_ref()); let noise_h_psk = mix_hash(sha512, &session.noise_kk_remote_init_h, &noise_temp_h); // Noise process pattern1 e token. // Get public key validation out of the way early let mut noise_es = Secret::new(); let mut noise_ee = Secret::new(); let mut noise_se = Secret::new(); - if let Some(alice_e) = - from_bytes_agreement::(&noise_pattern1.noise_e, &context.0.static_keypair, noise_es.as_mut()) - { + if let Some(alice_e) = from_bytes_agreement::(&noise_pattern1.noise_e, &context.0.static_keypair, noise_es.as_mut()) { let bob_e_secret = Application::KeyPair::generate(&mut context.0.rng.lock().unwrap()); if bob_e_secret.agree(&alice_e, noise_ee.as_mut()) && bob_e_secret.agree(&session.remote_s_public_key, noise_se.as_mut()) { let noise_h_pske = mix_hash(sha512, &noise_h_psk, alice_e.as_bytes()); @@ -1987,15 +1997,16 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu ); drop(noise_k_pskessseese); // Bob finished Noise KKpsk0 handshake. - let new_ratchet_number = state.ratchet_number + 1; let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); + let new_ratchet_state = RatchetState { + fingerprint: new_ratchet_fingerprint.0, + key: new_ratchet_key, + ratchet_count: state.ratchet_state[0].map(|rs| rs.ratchet_count + 1).unwrap_or(1), + }; let result = app.save_ratchet_state( &session.remote_s_public_key, &session.application_data, - SaveAction::SaveAsUnconfirmed, - new_ratchet_number, - new_ratchet_fingerprint.as_ref(), - new_ratchet_key.as_ref(), + SaveAction::AddRatchet(&new_ratchet_state), current_time, ); if result.is_err() { @@ -2021,17 +2032,23 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu .lock() .unwrap() .replace(Application::AeadDec::new(kex_key_a2b.as_ref())); + state.ratchet_state[1] = state.ratchet_state[0]; + state.ratchet_state[0] = Some(new_ratchet_state.clone()); - state.cipher_states[next_key_index].replace(SessionKey::new(hmac, noise_ck, new_key_id, remote_key_id, current_counter, true)); + state.cipher_states[next_key_index].replace(SessionKey::new( + hmac, + noise_ck, + new_key_id, + remote_key_id, + current_counter, + true, + )); let timer = current_time.saturating_add(Application::RETRY_INTERVAL_MS); state.outgoing_offer = NoiseKKPattern2 { next_retry_time: AtomicI64::new(timer), timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), noise_message: message2, kex_send_key: kex_key_b2a.clone(), - new_ratchet_number, - new_ratchet_fingerprint: new_ratchet_fingerprint.clone(), - new_ratchet_key: new_ratchet_key.clone(), }; drop(state); drop(kex_lock); @@ -2085,16 +2102,16 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern2.key_id))) { // Bob fully authenticated. // Alice finished Noise KKpsk0 handshake. - let new_ratchet_number = state.ratchet_number + 1; let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); - + let new_ratchet_state = RatchetState { + fingerprint: new_ratchet_fingerprint.0, + key: new_ratchet_key, + ratchet_count: state.ratchet_state[0].map(|rs| rs.ratchet_count + 1).unwrap_or(1), + }; let result = app.save_ratchet_state( &session.remote_s_public_key, &session.application_data, - SaveAction::SaveAsConfirmed, - new_ratchet_number, - new_ratchet_fingerprint.as_ref(), - new_ratchet_key.as_ref(), + SaveAction::DeleteThenAddRatchet(&state.ratchet_state[0].unwrap(), &new_ratchet_state), current_time, ); if result.is_err() { @@ -2122,10 +2139,8 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu .lock() .unwrap() .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); - - state.ratchet_number = new_ratchet_number; - state.ratchet_fingerprint.overwrite_first_n(&new_ratchet_fingerprint); - state.ratchet_key.overwrite(&new_ratchet_key); + state.ratchet_state[1] = None; + state.ratchet_state[0] = Some(new_ratchet_state.clone()); state.cipher_states[next_key_index].replace(SessionKey::new( hmac, @@ -2146,7 +2161,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_KEY_CONFIRM, &[]); } app.event_log(LogEvent::ReceiveValidKK2(&session), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::Ratchet(new_ratchet_number))); + return Ok(ReceiveResult::Session(session, SessionEvent::Control)); } } } @@ -2207,14 +2222,14 @@ impl Session { /// The most recent confirmed ratchet state of this session. /// The returned values are sensitive and should be securely erased before being dropped. #[inline] - pub fn ratchet_state(&self) -> (u64, [u8; RATCHET_FINGERPRINT_SIZE], [u8; RATCHET_KEY_SIZE]) { + pub fn ratchet_state(&self) -> [Option; 2] { let state = self.state.read().unwrap(); - (state.ratchet_number, *state.ratchet_fingerprint.as_ref(), *state.ratchet_key.as_ref()) + state.ratchet_state.clone() } /// The most recent confirmed ratchet number of this session. #[inline] - pub fn ratchet_number(&self) -> u64 { - self.state.read().unwrap().ratchet_number + pub fn ratchet_count(&self) -> u64 { + self.state.read().unwrap().ratchet_state[0].map(|rs| rs.ratchet_count).unwrap_or(0) } /// Mark a session as expired. This will make it impossible for this session to successfully /// receive or send data. It is recommended to simply `drop` the session instead, but this can @@ -2298,7 +2313,7 @@ impl NoiseXKAliceHandshake { fn initialize( local_key_id: NonZeroU32, remote_s_public_key: &Application::PublicKey, - ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], + ratchet_state: &[Option; 2], rng: &mut Application::Rng, ) -> Result< ( @@ -2308,7 +2323,7 @@ impl NoiseXKAliceHandshake { ), OpenError, > { - let mut message = [0u8; NoiseXKPattern1::SIZE]; + let mut message = [0u8; NoiseXKPattern1::MAX_SIZE]; let sha512 = &mut Application::Hash::new(); let hmac = &mut Application::HmacHash::new(); // Start of Noise XKhfs+psk2 pattern1. @@ -2318,7 +2333,6 @@ impl NoiseXKAliceHandshake { noise_pattern1.alice_key_id = local_key_id.get().to_ne_bytes(); noise_pattern1.noise_e = noise_e_secret.public_key_bytes().clone(); noise_pattern1.noise_e1 = noise_e1_secret.public; - noise_pattern1.ratchet_fingerprint = *ratchet_fingerprint; // Noise process prologue. let noise_h = mix_hash( sha512, @@ -2347,26 +2361,39 @@ impl NoiseXKAliceHandshake { &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], ); // Noise process pattern1 payload. + let mut idx = 0; + for r in ratchet_state { + if let Some(rs) = r { + let next_idx = idx + RATCHET_SIZE; + noise_pattern1.payload[idx..next_idx].copy_from_slice(&rs.fingerprint); + idx = next_idx; + } + } + idx += AES_GCM_TAG_SIZE; + let noise_h_ee1p = encrypt_and_hash::( sha512, &noise_k_es, &noise_h_ee1, PACKET_TYPE_NOISE_XK_PATTERN_1, 1, - &mut message[NoiseXKPattern1::P_ENC_START..NoiseXKPattern1::P_AUTH_END], + &mut message[NoiseXKPattern1::P_ENC_START..idx], ); drop(noise_k_es); let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); - let mut pattern1_id = 0u64.to_ne_bytes(); - pattern1_id.copy_from_slice(&message[NoiseXKPattern1::P_AUTH_START + 8..NoiseXKPattern1::P_AUTH_END]); + let message_id = u64::from_be_bytes(message[idx - 8..idx].try_into().unwrap()); + + idx += ChallengeResponse::SIZE; + message[idx - CHALLENGE_POW_SIZE..idx].copy_from_slice(&rng.next_u64().to_ne_bytes()); Ok(( NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_h_ee1p, noise_e_secret, noise_e1_secret: Secret(noise_e1_secret.secret), noise_ck_es: noise_ck, + noise_message_len: idx, noise_message: message, - message_id: u64::from_be_bytes(pattern1_id), + message_id, }, header_a2b_key, header_b2a_key, @@ -2376,15 +2403,13 @@ impl NoiseXKAliceHandshake { fn reinitialize( &mut self, session: &Arc>, - ratchet_fingerprint: &Secret, + ratchet_state: &[Option; 2], session_map: &mut HashMap>, bool)>, rng: &mut Application::Rng, current_time: i64, ) -> bool { let local_key_id = generate_key_id(session_map, rng); - if let Ok((offer, a2b_header_key, b2a_header_key)) = - Self::initialize(local_key_id, &session.remote_s_public_key, ratchet_fingerprint.as_ref(), rng) - { + if let Ok((offer, a2b_header_key, b2a_header_key)) = Self::initialize(local_key_id, &session.remote_s_public_key, ratchet_state, rng) { self.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); session_map.remove(&self.local_key_id); session_map.insert(local_key_id, (Arc::downgrade(session), false)); @@ -2684,12 +2709,12 @@ fn mix_hash(hasher: &mut impl Sha512, h: &[u8; NOISE_HASHLEN], m: &[u8]) -> [u8; /// Check if the proof of work attached to the first message contains the correct number of leading /// zeros. #[inline(always)] -fn verify_pow(hasher: &mut Application::Hash, message: &[u8]) -> bool { +fn verify_pow(hasher: &mut Application::Hash, response: &[u8]) -> bool { if Application::PROOF_OF_WORK_BIT_DIFFICULTY == 0 { return true; } hasher.reset(); - hasher.update(&message[NoiseXKPattern1::P_AUTH_END..NoiseXKPattern1::SIZE]); + hasher.update(response); let mut output = [0u8; NOISE_HASHLEN]; hasher.finish(&mut output); let n = u32::from_be_bytes(output[..4].try_into().unwrap()); From 0c76ec872700348460e1f2ffc21dffd3afd46761 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 13 Jul 2023 14:28:31 -0400 Subject: [PATCH 17/91] fixed bug --- src/zssp.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/zssp.rs b/src/zssp.rs index 8c4abef..beddd96 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -1452,9 +1452,11 @@ impl Context { } } PACKET_TYPE_NOISE_XK_PATTERN_3 => { - app.event_log(LogEvent::ReceiveUncheckedXK3, current_time); // Alice (remote) --> Bob (local) // -> s, se + let message = &mut message[..message_size]; + app.event_log(LogEvent::ReceiveUncheckedXK3, current_time); + if session.is_some() { return Err(byzantine_fault!(FaultType::OutOfSequence, false)); } From 0332e218031cd68e3ca1b1e91d69194d65e182b9 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 13 Jul 2023 14:40:42 -0400 Subject: [PATCH 18/91] removed errors --- src/zssp.rs | 81 +++++++++++++++++++---------------------------------- 1 file changed, 29 insertions(+), 52 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index beddd96..2567e22 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -237,7 +237,6 @@ enum NoiseXKAliceHandshakeState { NoiseXKPattern3 { noise_message: [u8; NoiseXKPattern3::MAX_SIZE], noise_message_len: usize, - new_ratchet_state: RatchetState, }, } @@ -489,8 +488,8 @@ impl Context { return Err(OpenError::DataTooLarge); } // Double check that the application gave us these in the correct order. - if let Some(first) = ratchet_state[0] { - if let Some(second) = ratchet_state[1] { + if let Some(first) = &ratchet_state[0] { + if let Some(second) = &ratchet_state[1] { if first.ratchet_count < second.ratchet_count { ratchet_state.swap(0, 1); } @@ -1063,7 +1062,7 @@ impl Context { noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); drop(noise_ekem1_secret); // Noise process pattern2 psk token. - let ratchet_key = ratchet_state.map(|rs| rs.key.as_ref()).unwrap_or(&[0; RATCHET_SIZE]); + let ratchet_key = ratchet_state.as_ref().map(|rs| rs.key.as_ref()).unwrap_or(&[0; RATCHET_SIZE]); let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); // Noise process pattern2 payload. @@ -1140,7 +1139,7 @@ impl Context { if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, .. } = &mut handshake_state.offer { let response_raw = &mut noise_message[*noise_message_len - ChallengeResponse::SIZE..]; - let response: &ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); + let response: &mut ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); // Only people who know what Alice's prior pow was can convince us to // compute a new pow. if challenge.prior_challenge_pow != response.challenge_pow { @@ -1153,7 +1152,7 @@ impl Context { let mut pow = self.0.rng.lock().unwrap().next_u64(); let sha512 = &mut Application::Hash::new(); loop { - let response: &ChallengeResponse = byte_array_as_proto_buffer(response_raw); + let response: &mut ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); response.challenge_pow.copy_from_slice(&pow.to_be_bytes()); if verify_pow::(sha512, response_raw) { break; @@ -1240,7 +1239,7 @@ impl Context { // the ratchet key Alice remembers, and final time with a ratchet // key of zero if Alice allows ratchet downgrades. - let test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState, Secret, [u8; 64])> { + let mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState, Secret, [u8; 64])> { // Check for which ratchet key Bob wants to use. let mut noise_ck = noise_ck.clone(); let mut payload = [0u8; NoiseXKPattern2::P_AUTH_END - NoiseXKPattern2::P_ENC_START]; @@ -1270,47 +1269,24 @@ impl Context { }; // Check first key. let mut ratchet_i = 0; - let mut is_auth = false; - let mut ratchet_number = 0; - let remote_key_id; - let noise_k_eseeekem1psk; - let noise_h_ee1peekem1pskp; - if let Some(rs) = state.ratchet_state[0] { - if let Some((key_id, ck, k, h)) = test_ratchet_key(rs.key.as_ref()) { - ratchet_number = rs.ratchet_count; - remote_key_id = key_id; - noise_ck = ck; - noise_k_eseeekem1psk = k; - noise_h_ee1peekem1pskp = h; - is_auth = true; - } + let mut result = None; + let ratchet_number = 0; + if let Some(rs) = &state.ratchet_state[0] { + result = test_ratchet_key(rs.key.as_ref()); } // Check second key. - if !is_auth { + if result.is_none() { ratchet_i = 1; - if let Some(rs) = state.ratchet_state[1] { - if let Some((key_id, ck, k, h)) = test_ratchet_key(rs.key.as_ref()) { - ratchet_number = rs.ratchet_count; - remote_key_id = key_id; - noise_ck = ck; - noise_k_eseeekem1psk = k; - noise_h_ee1peekem1pskp = h; - is_auth = true; - } + if let Some(rs) = &state.ratchet_state[1] { + result = test_ratchet_key(rs.key.as_ref()); } } // Check zero key. - if !is_auth { - if let Some((key_id, ck, k, h)) = test_ratchet_key(&[0u8; RATCHET_SIZE]) { - noise_ck = ck; - remote_key_id = key_id; - noise_k_eseeekem1psk = k; - noise_h_ee1peekem1pskp = h; - is_auth = true; - } + if result.is_none() { + result = test_ratchet_key(&[0u8; RATCHET_SIZE]); } - if is_auth { + if let Some((remote_key_id, mut noise_ck, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) = result { // Start of Noise XKhfs+psk2 pattern3. let mut message3 = [0u8; NoiseXKPattern3::MAX_SIZE]; // Noise process pattern3 s token. @@ -1359,7 +1335,7 @@ impl Context { ratchet_count: ratchet_number + 1, }; let action = if state.ratchet_state[0].is_some() && state.ratchet_state[1].is_some() { - SaveAction::DeleteThenAddRatchet(&state.ratchet_state[1 - ratchet_i].unwrap(), &new_ratchet_state) + SaveAction::DeleteThenAddRatchet(state.ratchet_state[1 - ratchet_i].as_ref().unwrap(), &new_ratchet_state) } else { SaveAction::AddRatchet(&new_ratchet_state) }; @@ -1384,7 +1360,7 @@ impl Context { .lock() .unwrap() .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); - state.ratchet_state[1] = state.ratchet_state[ratchet_i]; + state.ratchet_state[1] = state.ratchet_state[ratchet_i].clone(); state.ratchet_state[0] = Some(new_ratchet_state.clone()); state.cipher_states[0].replace(SessionKey::new( @@ -1403,7 +1379,6 @@ impl Context { handshake_state.offer = NoiseXKAliceHandshakeState::NoiseXKPattern3 { noise_message: message3, noise_message_len: p_auth_end, - new_ratchet_state: new_ratchet_state.clone(), }; } drop(state); @@ -1429,12 +1404,13 @@ impl Context { } // Bob failed authentication so we must restart our offer according to Noise. // We restart the offer instead of dropping the session to defend against DOS. + let ratchet_state = state.ratchet_state.clone(); drop(state); let mut state = session.state.write().unwrap(); if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { if !handshake_state.reinitialize( &session, - &state.ratchet_state, + &ratchet_state, &mut self.0.session_map.write().unwrap(), &mut self.0.rng.lock().unwrap(), current_time, @@ -1534,7 +1510,7 @@ impl Context { let new_ratchet_state = RatchetState { fingerprint: new_ratchet_fingerprint.0, key: new_ratchet_key, - ratchet_count: handshake_state.ratchet_state.map(|rs| rs.ratchet_count + 1).unwrap_or(1), + ratchet_count: handshake_state.ratchet_state.as_ref().map(|rs| rs.ratchet_count + 1).unwrap_or(1), }; let result = app.save_ratchet_state( &remote_s_public_key, @@ -1758,7 +1734,7 @@ fn initiate_rekey( // Start of Noise KKpsk0 pattern1. // Noise process pattern1 psk0 token. let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_state[0].unwrap().key.as_ref()); + let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_state[0].as_ref().unwrap().key.as_ref()); let noise_h_psk = mix_hash(sha512, &session.noise_kk_local_init_h, &noise_temp_h); // Noise process pattern1 e token. let noise_e_secret = Application::KeyPair::generate(&mut context.rng.lock().unwrap()); @@ -1940,7 +1916,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let sha512 = &mut Application::Hash::new(); let hmac = &mut Application::HmacHash::new(); let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_state[0].unwrap().key.as_ref()); + let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_state[0].as_ref().unwrap().key.as_ref()); let noise_h_psk = mix_hash(sha512, &session.noise_kk_remote_init_h, &noise_temp_h); // Noise process pattern1 e token. // Get public key validation out of the way early @@ -2003,7 +1979,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let new_ratchet_state = RatchetState { fingerprint: new_ratchet_fingerprint.0, key: new_ratchet_key, - ratchet_count: state.ratchet_state[0].map(|rs| rs.ratchet_count + 1).unwrap_or(1), + ratchet_count: state.ratchet_state[0].as_ref().map(|rs| rs.ratchet_count + 1).unwrap_or(1), }; let result = app.save_ratchet_state( &session.remote_s_public_key, @@ -2034,7 +2010,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu .lock() .unwrap() .replace(Application::AeadDec::new(kex_key_a2b.as_ref())); - state.ratchet_state[1] = state.ratchet_state[0]; + state.ratchet_state[1] = state.ratchet_state[0].clone(); state.ratchet_state[0] = Some(new_ratchet_state.clone()); state.cipher_states[next_key_index].replace(SessionKey::new( @@ -2108,12 +2084,12 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let new_ratchet_state = RatchetState { fingerprint: new_ratchet_fingerprint.0, key: new_ratchet_key, - ratchet_count: state.ratchet_state[0].map(|rs| rs.ratchet_count + 1).unwrap_or(1), + ratchet_count: state.ratchet_state[0].as_ref().map(|rs| rs.ratchet_count + 1).unwrap_or(1), }; let result = app.save_ratchet_state( &session.remote_s_public_key, &session.application_data, - SaveAction::DeleteThenAddRatchet(&state.ratchet_state[0].unwrap(), &new_ratchet_state), + SaveAction::DeleteThenAddRatchet(&state.ratchet_state[0].as_ref().unwrap(), &new_ratchet_state), current_time, ); if result.is_err() { @@ -2231,7 +2207,7 @@ impl Session { /// The most recent confirmed ratchet number of this session. #[inline] pub fn ratchet_count(&self) -> u64 { - self.state.read().unwrap().ratchet_state[0].map(|rs| rs.ratchet_count).unwrap_or(0) + self.state.read().unwrap().ratchet_state[0].as_ref().map(|rs| rs.ratchet_count).unwrap_or(0) } /// Mark a session as expired. This will make it impossible for this session to successfully /// receive or send data. It is recommended to simply `drop` the session instead, but this can @@ -2363,6 +2339,7 @@ impl NoiseXKAliceHandshake { &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], ); // Noise process pattern1 payload. + let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); let mut idx = 0; for r in ratchet_state { if let Some(rs) = r { From 9324a9b3c42620752a3639644696ce8d2897ce53 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 13 Jul 2023 14:46:13 -0400 Subject: [PATCH 19/91] exported ratchet state --- src/lib.rs | 2 +- src/zssp.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6439e61..e5aee5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,7 @@ mod symmetric_state; mod zssp; pub mod error; -pub use crate::applicationlayer::{ApplicationLayer, SaveAction}; +pub use crate::applicationlayer::{ApplicationLayer, RatchetState, SaveAction}; pub use crate::log_event::LogEvent; pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_SIZE}; pub use crate::zssp::{AcceptSessionAction, Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/zssp.rs b/src/zssp.rs index 2567e22..ebdaab1 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -2207,7 +2207,10 @@ impl Session { /// The most recent confirmed ratchet number of this session. #[inline] pub fn ratchet_count(&self) -> u64 { - self.state.read().unwrap().ratchet_state[0].as_ref().map(|rs| rs.ratchet_count).unwrap_or(0) + self.state.read().unwrap().ratchet_state[0] + .as_ref() + .map(|rs| rs.ratchet_count) + .unwrap_or(0) } /// Mark a session as expired. This will make it impossible for this session to successfully /// receive or send data. It is recommended to simply `drop` the session instead, but this can From 2abd65ff2c03cdd6adeec40ae666a5ee7311940b Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 13 Jul 2023 14:57:22 -0400 Subject: [PATCH 20/91] improved struct --- src/applicationlayer.rs | 6 +++--- src/zssp.rs | 38 +++++++++++++++++++------------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 251e825..ae18b62 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -199,11 +199,11 @@ pub trait ApplicationLayer: Sized { fn event_log<'a>(&self, event: LogEvent<'a, Self>, current_time: i64) {} } -#[derive(Default, Clone)] +#[derive(Default, Clone, PartialEq, Eq)] pub struct RatchetState { - pub fingerprint: [u8; RATCHET_SIZE], pub key: Secret, - pub ratchet_count: u64, + pub fingerprint: Secret, + pub chain_len: u64, } /// Ratchet keys and fingerprints should be saved *per remote peer*. It is up to the application to /// enforce separate storage for each remote peer based on `remote_static_key` and `application_data`. diff --git a/src/zssp.rs b/src/zssp.rs index ebdaab1..e4929a2 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -490,7 +490,7 @@ impl Context { // Double check that the application gave us these in the correct order. if let Some(first) = &ratchet_state[0] { if let Some(second) = &ratchet_state[1] { - if first.ratchet_count < second.ratchet_count { + if first.chain_len < second.chain_len { ratchet_state.swap(0, 1); } } @@ -1327,12 +1327,12 @@ impl Context { drop(noise_k_eseeekem1pskse); // Alice finished Noise XKhfs+psk2 handshake. // Transition offer state machine to the NoiseXKPattern3 state. - let (new_ratchet_key, new_ratchet_fingerprint) = + let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); let new_ratchet_state = RatchetState { - fingerprint: new_ratchet_fingerprint.0, - key: new_ratchet_key, - ratchet_count: ratchet_number + 1, + key: rk, + fingerprint: rf, + chain_len: ratchet_number + 1, }; let action = if state.ratchet_state[0].is_some() && state.ratchet_state[1].is_some() { SaveAction::DeleteThenAddRatchet(state.ratchet_state[1 - ratchet_i].as_ref().unwrap(), &new_ratchet_state) @@ -1505,12 +1505,12 @@ impl Context { let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); - let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); + let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); // We must make sure the ratchet key is saved before we transition. let new_ratchet_state = RatchetState { - fingerprint: new_ratchet_fingerprint.0, - key: new_ratchet_key, - ratchet_count: handshake_state.ratchet_state.as_ref().map(|rs| rs.ratchet_count + 1).unwrap_or(1), + key: rk, + fingerprint: rf, + chain_len: handshake_state.ratchet_state.as_ref().map(|rs| rs.chain_len + 1).unwrap_or(1), }; let result = app.save_ratchet_state( &remote_s_public_key, @@ -1975,11 +1975,11 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu ); drop(noise_k_pskessseese); // Bob finished Noise KKpsk0 handshake. - let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); + let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); let new_ratchet_state = RatchetState { - fingerprint: new_ratchet_fingerprint.0, - key: new_ratchet_key, - ratchet_count: state.ratchet_state[0].as_ref().map(|rs| rs.ratchet_count + 1).unwrap_or(1), + key: rk, + fingerprint: rf, + chain_len: state.ratchet_state[0].as_ref().map(|rs| rs.chain_len + 1).unwrap_or(1), }; let result = app.save_ratchet_state( &session.remote_s_public_key, @@ -2080,11 +2080,11 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern2.key_id))) { // Bob fully authenticated. // Alice finished Noise KKpsk0 handshake. - let (new_ratchet_key, new_ratchet_fingerprint) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); + let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); let new_ratchet_state = RatchetState { - fingerprint: new_ratchet_fingerprint.0, - key: new_ratchet_key, - ratchet_count: state.ratchet_state[0].as_ref().map(|rs| rs.ratchet_count + 1).unwrap_or(1), + key: rk, + fingerprint: rf, + chain_len: state.ratchet_state[0].as_ref().map(|rs| rs.chain_len + 1).unwrap_or(1), }; let result = app.save_ratchet_state( &session.remote_s_public_key, @@ -2209,7 +2209,7 @@ impl Session { pub fn ratchet_count(&self) -> u64 { self.state.read().unwrap().ratchet_state[0] .as_ref() - .map(|rs| rs.ratchet_count) + .map(|rs| rs.chain_len) .unwrap_or(0) } /// Mark a session as expired. This will make it impossible for this session to successfully @@ -2347,7 +2347,7 @@ impl NoiseXKAliceHandshake { for r in ratchet_state { if let Some(rs) = r { let next_idx = idx + RATCHET_SIZE; - noise_pattern1.payload[idx..next_idx].copy_from_slice(&rs.fingerprint); + noise_pattern1.payload[idx..next_idx].copy_from_slice(rs.fingerprint.as_ref()); idx = next_idx; } } From 4a5d1065c1f57737428ab821be727fa513a3d6c0 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 13 Jul 2023 15:20:51 -0400 Subject: [PATCH 21/91] fixed aob bug --- src/zssp.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index e4929a2..8c03a3d 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -2351,7 +2351,8 @@ impl NoiseXKAliceHandshake { idx = next_idx; } } - idx += AES_GCM_TAG_SIZE; + let p_auth_end = NoiseXKPattern1::P_ENC_START + idx + AES_GCM_TAG_SIZE; + let noise_message_len = p_auth_end + ChallengeResponse::SIZE; let noise_h_ee1p = encrypt_and_hash::( sha512, @@ -2359,21 +2360,20 @@ impl NoiseXKAliceHandshake { &noise_h_ee1, PACKET_TYPE_NOISE_XK_PATTERN_1, 1, - &mut message[NoiseXKPattern1::P_ENC_START..idx], + &mut message[NoiseXKPattern1::P_ENC_START..p_auth_end], ); drop(noise_k_es); let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); - let message_id = u64::from_be_bytes(message[idx - 8..idx].try_into().unwrap()); + let message_id = u64::from_be_bytes(message[p_auth_end - 8..p_auth_end].try_into().unwrap()); - idx += ChallengeResponse::SIZE; - message[idx - CHALLENGE_POW_SIZE..idx].copy_from_slice(&rng.next_u64().to_ne_bytes()); + message[noise_message_len - CHALLENGE_POW_SIZE..noise_message_len].copy_from_slice(&rng.next_u64().to_ne_bytes()); Ok(( NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_h_ee1p, noise_e_secret, noise_e1_secret: Secret(noise_e1_secret.secret), noise_ck_es: noise_ck, - noise_message_len: idx, + noise_message_len, noise_message: message, message_id, }, From 13e3f865b37ade09a806dd71fb86877b41be5193 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 13 Jul 2023 15:23:09 -0400 Subject: [PATCH 22/91] fixed aob bug --- src/zssp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zssp.rs b/src/zssp.rs index 8c03a3d..d56d9a7 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -882,7 +882,7 @@ impl Context { if session.is_some() || incoming.is_some() { return Err(byzantine_fault!(FaultType::OutOfSequence, false)); } - if message.len() < NoiseXKPattern1::MIN_SIZE || message.len() > NoiseXKPattern1::MIN_SIZE { + if message_size < NoiseXKPattern1::MIN_SIZE || message_size > NoiseXKPattern1::MIN_SIZE { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } // The message id must be the first 8 bytes of the gcm tag. From c2275bf72c8581fb46f5436e5de234b1ebf8ccf4 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 13 Jul 2023 15:30:56 -0400 Subject: [PATCH 23/91] fixed typo --- src/zssp.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index d56d9a7..77377d0 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -522,11 +522,11 @@ impl Context { alice_identity_blob: local_identity_blob, offer, }); - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, message_id, .. } = &handshake_state.offer { + if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } = &handshake_state.offer { send_with_fragmentation( &mut send, mtu, - &mut noise_message.clone(), + &mut noise_message.clone()[..*noise_message_len], PACKET_TYPE_NOISE_XK_PATTERN_1, None, *message_id, @@ -882,7 +882,7 @@ impl Context { if session.is_some() || incoming.is_some() { return Err(byzantine_fault!(FaultType::OutOfSequence, false)); } - if message_size < NoiseXKPattern1::MIN_SIZE || message_size > NoiseXKPattern1::MIN_SIZE { + if message_size < NoiseXKPattern1::MIN_SIZE || message_size > NoiseXKPattern1::MAX_SIZE { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } // The message id must be the first 8 bytes of the gcm tag. From 6fb629edefe5b96f8c089b12daab6d9cf6081963 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 13 Jul 2023 15:36:54 -0400 Subject: [PATCH 24/91] fixed typo --- src/zssp.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index 77377d0..83ec35a 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -887,12 +887,12 @@ impl Context { } // The message id must be the first 8 bytes of the gcm tag. // This forces the message id to be authenticated along with the entire message. - let challenge_start_idx = message_size - ChallengeResponse::SIZE; - if message[8..] != message[challenge_start_idx - 8..challenge_start_idx] { + let p_auth_end = message_size - ChallengeResponse::SIZE; + if message[8..16] != message[p_auth_end - 8..p_auth_end] { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } - let total_ratchet_fingerprints = (challenge_start_idx - AES_GCM_TAG_SIZE) / RATCHET_SIZE; - if (challenge_start_idx - AES_GCM_TAG_SIZE) % RATCHET_SIZE != 0 || total_ratchet_fingerprints > 2 { + let total_ratchet_fingerprints = (p_auth_end - AES_GCM_TAG_SIZE) / RATCHET_SIZE; + if (p_auth_end - AES_GCM_TAG_SIZE) % RATCHET_SIZE != 0 || total_ratchet_fingerprints > 2 { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } @@ -904,7 +904,7 @@ impl Context { match check_allow_incoming_session() { IncomingSessionAction::Allow => {} IncomingSessionAction::Challenge => { - let response: &ChallengeResponse = byte_array_as_proto_buffer(&message[challenge_start_idx..message_size]); + let response: &ChallengeResponse = byte_array_as_proto_buffer(&message[p_auth_end..message_size]); let mut counter = 0u64.to_ne_bytes(); counter.copy_from_slice(&response.challenge_counter); let counter = u64::from_be_bytes(counter); @@ -918,7 +918,7 @@ impl Context { hasher.0.finish(&mut output); let is_valid = self.check_challenge_window(counter) && secure_eq(&output[..CHALLENGE_MAC_SIZE], &response.challenge_mac) - && verify_pow::(&mut hasher.0, &message[challenge_start_idx..message_size]) + && verify_pow::(&mut hasher.0, &message[p_auth_end..message_size]) && self.update_challenge_window(counter); app.event_log(LogEvent::ReceiveCheckXK1Challenge(is_valid), current_time); if !is_valid { @@ -1002,7 +1002,7 @@ impl Context { &noise_h_ee1, packet_type, 1, - &mut message[NoiseXKPattern1::P_ENC_START..challenge_start_idx], + &mut message[NoiseXKPattern1::P_ENC_START..p_auth_end], ); drop(noise_k_es); if !is_auth { From 95a97a756669690f03ec008f114c033865992d4f Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 13 Jul 2023 15:37:11 -0400 Subject: [PATCH 25/91] fixed typo --- src/zssp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zssp.rs b/src/zssp.rs index 83ec35a..938762d 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -1204,7 +1204,7 @@ impl Context { { let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); // Authenticate header counter. - if noise_pattern2.header[13..] != noise_pattern2.p_gcm_tag[13..] { + if noise_pattern2.header[13..16] != noise_pattern2.p_gcm_tag[13..16] { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } From ce5ff0dab4b91f216ec9138f39791c8ce0cb22c4 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 13 Jul 2023 15:40:37 -0400 Subject: [PATCH 26/91] fixed typo --- src/zssp.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index 938762d..c2dfedf 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -891,8 +891,9 @@ impl Context { if message[8..16] != message[p_auth_end - 8..p_auth_end] { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } - let total_ratchet_fingerprints = (p_auth_end - AES_GCM_TAG_SIZE) / RATCHET_SIZE; - if (p_auth_end - AES_GCM_TAG_SIZE) % RATCHET_SIZE != 0 || total_ratchet_fingerprints > 2 { + let p_size = p_auth_end - NoiseXKPattern1::P_ENC_START - AES_GCM_TAG_SIZE; + let total_ratchet_fingerprints = p_size / RATCHET_SIZE; + if p_size % RATCHET_SIZE != 0 || total_ratchet_fingerprints > 2 { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } From 48ff1fc61202b76783526a8a58dc3a5cc0f610e1 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 12:04:40 -0400 Subject: [PATCH 27/91] improved API --- src/applicationlayer.rs | 89 +++++++-- src/error.rs | 2 + src/lib.rs | 2 +- src/zssp.rs | 392 +++++++++++++++++++++------------------- 4 files changed, 280 insertions(+), 205 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index ae18b62..ed9e020 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -6,6 +6,7 @@ * https://www.zerotier.com/ */ +use std::num::NonZeroU64; use std::sync::Arc; use crate::crypto::aes::{AesDec, AesEnc}; @@ -163,8 +164,12 @@ pub trait ApplicationLayer: Sized { /// to the zero ratchet key, restarting the ratchet chain. /// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped. #[allow(unused)] - fn restore_ratchet(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE], current_time: i64) -> Result, ()> { - Ok(None) + fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE], current_time: i64) -> Result { + Ok(RatchetState::Null) + } + #[allow(unused)] + fn restore_by_identity(&self, remote_static_key: &Self::PublicKey, application_data: &Self::Data, current_time: i64) -> Result<[RatchetState; 2], ()> { + Ok([RatchetState::Null, RatchetState::Null]) } /// Atomically save the given ratchet key, fingerprint and number to persistent storage. /// @@ -189,7 +194,8 @@ pub trait ApplicationLayer: Sized { &self, remote_static_key: &Self::PublicKey, application_data: &Self::Data, - save_action: SaveAction<'a>, + previous_ratchet_states: [&RatchetState; 2], + new_ratchet_states: [&RatchetState; 2], current_time: i64, ) -> Result<(), ()> { Ok(()) @@ -199,19 +205,70 @@ pub trait ApplicationLayer: Sized { fn event_log<'a>(&self, event: LogEvent<'a, Self>, current_time: i64) {} } -#[derive(Default, Clone, PartialEq, Eq)] -pub struct RatchetState { +#[derive(Clone, PartialEq, Eq)] +pub enum RatchetState { + Null, + Empty, + NonEmpty(NonEmptyRatchetState), +} +use RatchetState::*; +impl RatchetState { + pub fn new_nonempty( + key: Secret, + fingerprint: Secret, + chain_len: NonZeroU64, + ) -> Self { + NonEmpty(NonEmptyRatchetState { + key, + fingerprint, + chain_len, + }) + } + pub fn new_initial_states() -> [RatchetState; 2] { + [RatchetState::Empty, RatchetState::Null] + } + pub fn is_null(&self) -> bool { + match self { + Null => true, + _ => false, + } + } + pub fn is_empty(&self) -> bool { + match self { + Empty => true, + _ => false, + } + } + #[inline] + pub fn nonempty(&self) -> Option<&NonEmptyRatchetState> { + match self { + NonEmpty(rs) => Some(&rs), + _ => None, + } + } + #[inline] + pub fn chain_len(&self) -> u64 { + self.nonempty().map_or(0, |rs| rs.chain_len.get()) + } + #[inline] + pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { + self.nonempty().map_or(None, |rs| Some(&rs.fingerprint.as_ref())) + } + #[inline] + pub fn key(&self) -> Option<&[u8; RATCHET_SIZE]> { + const ZERO_KEY: [u8; RATCHET_SIZE] = [0u8; RATCHET_SIZE]; + match self { + Null => None, + Empty => Some(&ZERO_KEY), + NonEmpty(rs) => Some(rs.key.as_ref()), + } + } +} +/// A ratchet key and fingerprint, +/// along with the length of the ratchet chain the keys were derived from. +#[derive(Clone, PartialEq, Eq)] +pub struct NonEmptyRatchetState { pub key: Secret, pub fingerprint: Secret, - pub chain_len: u64, -} -/// Ratchet keys and fingerprints should be saved *per remote peer*. It is up to the application to -/// enforce separate storage for each remote peer based on `remote_static_key` and `application_data`. -/// -/// Only up to 2 ratchet keys and fingerprints will be saved at one time. -pub enum SaveAction<'a> { - AddRatchet(&'a RatchetState), - DeleteRatchet(&'a RatchetState), - DeleteThenAddRatchet(&'a RatchetState, &'a RatchetState), - VerifyThenOverwriteRatchet(Option<&'a RatchetState>, &'a RatchetState), + pub chain_len: NonZeroU64, } diff --git a/src/error.rs b/src/error.rs index 2183abd..34e2dbc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,6 +13,8 @@ pub enum OpenError { /// Local identity blob is too large to send, even with fragmentation. DataTooLarge, + + RatchetIoError } #[derive(Debug, PartialEq, Eq)] diff --git a/src/lib.rs b/src/lib.rs index e5aee5a..d0025df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,7 @@ mod symmetric_state; mod zssp; pub mod error; -pub use crate::applicationlayer::{ApplicationLayer, RatchetState, SaveAction}; +pub use crate::applicationlayer::{ApplicationLayer, RatchetState}; pub use crate::log_event::LogEvent; pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_SIZE}; pub use crate::zssp::{AcceptSessionAction, Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/zssp.rs b/src/zssp.rs index c2dfedf..e35bdd5 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -11,7 +11,7 @@ use std::cmp::Reverse; use std::collections::HashMap; use std::hash::Hash; -use std::num::NonZeroU32; +use std::num::{NonZeroU32, NonZeroU64}; use std::ops::DerefMut; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; @@ -128,7 +128,7 @@ pub struct Session { /// Handle into the session queue for changing the update timer. queue_idx: BinaryHeapIndex, - remote_s_public_key: Application::PublicKey, + remote_static_key: Application::PublicKey, send_counter: AtomicU64, /// This bool signals to all threads to stop incrementing the counter and instead error out. session_has_expired: AtomicBool, @@ -158,7 +158,7 @@ unsafe impl Sync for Session {} /// Session state may only be mutated during atomic transitions of the offer state machine. struct SessionMutableState { - ratchet_state: [Option; 2], + ratchet_states: [RatchetState; 2], /// For OOO rekeying reliability we allow our version of Noise CipherState to hold the last two /// session keys, instead of just the most recent one. cipher_states: [Option>; 2], @@ -200,11 +200,12 @@ enum OfferStateMachine { } pub(crate) struct NoiseXKBobHandshakeState { + /// Can never be Null. + ratchet_state: RatchetState, remote_key_id: NonZeroU32, local_key_id: NonZeroU32, header_receive_key: Secret, header_send_key: Secret, - ratchet_state: Option, noise_h_ee1peekem1pskp: [u8; NOISE_HASHLEN], noise_e_secret: Application::KeyPair, noise_ck_eseeekem1psk: SymmetricState, @@ -347,10 +348,10 @@ impl Context { } else { // We have to eventually time out NoiseXKPattern3 because of unreliable network conditions. if handshake_state.timeout <= current_time { - let ratchet_state = state.ratchet_state.clone(); drop(state); let _kex_lock = session.state_machine_lock.lock().unwrap(); let mut state = session.state.write().unwrap(); + let ratchet_state = state.ratchet_states.clone(); // Since we dropped the lock we must re-check if we are in the correct state. if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { if handshake_state.timeout <= current_time { @@ -463,7 +464,7 @@ impl Context { /// * `send` - Function to be called to send one or more initial packets to the remote being /// contacted /// * `mtu` - MTU for initial packets - /// * `remote_s_public_key` - Remote side's static public NIST P-384 key + /// * `remote_static_key` - Remote side's static public NIST P-384 key /// * `application_data` - Arbitrary data meaningful to the application to include with session /// object /// * `ratchet_state` - The last saved and confirmed ratchet state associated with this remote @@ -475,11 +476,11 @@ impl Context { #[inline] pub fn open( &self, + app: &Application, mut send: impl FnMut(&mut [u8]) -> bool, mut mtu: usize, - remote_s_public_key: Application::PublicKey, + remote_static_key: Application::PublicKey, application_data: Application::Data, - mut ratchet_state: [Option; 2], local_identity_blob: Application::LocalIdentityBlob, current_time: i64, ) -> Result>, OpenError> { @@ -487,88 +488,82 @@ impl Context { if local_identity_blob.as_ref().len() > MAX_IDENTITY_BLOB_SIZE { return Err(OpenError::DataTooLarge); } - // Double check that the application gave us these in the correct order. - if let Some(first) = &ratchet_state[0] { - if let Some(second) = &ratchet_state[1] { - if first.chain_len < second.chain_len { - ratchet_state.swap(0, 1); - } + let result = app.restore_by_identity(&remote_static_key, &application_data, current_time); + if let Ok(ratchet_states) = result { + let sha512 = &mut Application::Hash::new(); + + let mut noise_kk_ss = Secret::new(); + if !self.0.static_keypair.agree(&remote_static_key, noise_kk_ss.as_mut()) { + return Err(OpenError::InvalidPublicKey); } - } else { - ratchet_state.swap(0, 1); - } + let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); + let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_static_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_static_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); - let sha512 = &mut Application::Hash::new(); + let mut session_queue = self.0.session_queue.lock().unwrap(); + let mut session_map = self.0.session_map.write().unwrap(); + let local_key_id = generate_key_id(&session_map, &mut self.0.rng.lock().unwrap()); + // Begin Noise XKhfs+psk2. + let (offer, a2b_header_key, b2a_header_key) = + NoiseXKAliceHandshake::::initialize(local_key_id, &remote_static_key, &ratchet_states, &mut self.0.rng.lock().unwrap())?; + let handshake_state = Box::new(NoiseXKAliceHandshake { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), + local_key_id, + alice_identity_blob: local_identity_blob, + offer, + }); + if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } = &handshake_state.offer { + send_with_fragmentation( + &mut send, + mtu, + &mut noise_message.clone()[..*noise_message_len], + PACKET_TYPE_NOISE_XK_PATTERN_1, + None, + *message_id, + None::<&Application::PrpEnc>, + ); + } - let mut noise_kk_ss = Secret::new(); - if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { - return Err(OpenError::InvalidPublicKey); - } - let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); - let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); - - let mut session_queue = self.0.session_queue.lock().unwrap(); - let mut session_map = self.0.session_map.write().unwrap(); - let local_key_id = generate_key_id(&session_map, &mut self.0.rng.lock().unwrap()); - // Begin Noise XKhfs+psk2. - let (offer, a2b_header_key, b2a_header_key) = - NoiseXKAliceHandshake::::initialize(local_key_id, &remote_s_public_key, &ratchet_state, &mut self.0.rng.lock().unwrap())?; - let handshake_state = Box::new(NoiseXKAliceHandshake { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), - local_key_id, - alice_identity_blob: local_identity_blob, - offer, - }); - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } = &handshake_state.offer { - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_1, - None, - *message_id, - None::<&Application::PrpEnc>, + let queue_idx = session_queue.reserve_index(); + let session = Arc::new(Session { + context: Arc::downgrade(&self.0), + queue_idx, + application_data, + remote_static_key, + send_counter: AtomicU64::new(INIT_COUNTER), + session_has_expired: AtomicBool::new(false), + counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), + state_machine_lock: Mutex::new(()), + state: RwLock::new(SessionMutableState { + ratchet_states: ratchet_states.clone(), + cipher_states: [None, None], + // Points at 1 until the first key is confirmed. + current_key: 1, + outgoing_offer: OfferStateMachine::NoiseXKPattern1or3(handshake_state), + }), + header_send_cipher: Application::PrpEnc::new(a2b_header_key.as_ref()), + header_receive_cipher: Application::PrpDec::new(b2a_header_key.as_ref()), + kex_receive_cipher: Mutex::new(None), + kex_send_cipher: Mutex::new(None), + noise_kk_ss: noise_kk_ss.clone(), + noise_kk_local_init_h, + noise_kk_remote_init_h, + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + was_bob: false, + }); + session_map.insert(local_key_id, (Arc::downgrade(&session), false)); + session_queue.push_reserved( + queue_idx, + Arc::downgrade(&session), + Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), ); + + return Ok(session); + } else { + return Err(OpenError::RatchetIoError); } - - let queue_idx = session_queue.reserve_index(); - let session = Arc::new(Session { - context: Arc::downgrade(&self.0), - queue_idx, - application_data, - remote_s_public_key, - send_counter: AtomicU64::new(INIT_COUNTER), - session_has_expired: AtomicBool::new(false), - counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - state_machine_lock: Mutex::new(()), - state: RwLock::new(SessionMutableState { - ratchet_state: ratchet_state.clone(), - cipher_states: [None, None], - // Points at 1 until the first key is confirmed. - current_key: 1, - outgoing_offer: OfferStateMachine::NoiseXKPattern1or3(handshake_state), - }), - header_send_cipher: Application::PrpEnc::new(a2b_header_key.as_ref()), - header_receive_cipher: Application::PrpDec::new(b2a_header_key.as_ref()), - kex_receive_cipher: Mutex::new(None), - kex_send_cipher: Mutex::new(None), - noise_kk_ss: noise_kk_ss.clone(), - noise_kk_local_init_h, - noise_kk_remote_init_h, - defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - was_bob: false, - }); - session_map.insert(local_key_id, (Arc::downgrade(&session), false)); - session_queue.push_reserved( - queue_idx, - Arc::downgrade(&session), - Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - ); - - return Ok(session); } /// Receive, authenticate, decrypt, and process a physical wire packet. @@ -588,11 +583,7 @@ impl Context { /// * `check_accept_session` - Function to accept sessions after final negotiation. /// The second argument is the identity blob that the remote peer sent us. The application /// must verify this identity is associated with the remote peer's static key. - /// If the third argument is `Some`, it is a ratchet fingerprint. The application must verify - /// that it is associated with the remote peer's static key and identity. - /// If the third argument is `None` it means the remote peer connected to us with the zero - /// ratchet. The application should decide whether or not this remote peer is allowed to - /// connect with the zero ratchet. + /// The third argument is true if the remote peer connected to us with a recognized ratchet fingerprint. /// * `send_unassociated_reply` - Function to send reply packets directly when no session exists /// * `send_unassociated_mtu` - MTU for unassociated replies /// * `send_to` - Function to get senders for existing sessions, permitting MTU and path lookup @@ -609,7 +600,7 @@ impl Context { &self, app: &Application, check_allow_incoming_session: impl FnOnce() -> IncomingSessionAction, - check_accept_session: impl FnOnce(&Application::PublicKey, &[u8]) -> AcceptSessionAction, + check_accept_session: impl FnOnce(&Application::PublicKey, &[u8], u64) -> (Option<(bool, Application::Data)>, bool), mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, mut send_unassociated_mtu: usize, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, @@ -1012,22 +1003,25 @@ impl Context { let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); // Get ratchet key. let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); - let mut ratchet_state = None; + let mut ratchet_state = RatchetState::Null; for i in 0..total_ratchet_fingerprints { - match app.restore_ratchet( + match app.restore_by_fingerprint( (&noise_pattern1.payload[i * RATCHET_SIZE..(i + 1) * RATCHET_SIZE]).try_into().unwrap(), current_time, ) { - Ok(Some(rs)) => { - ratchet_state = Some(rs); + Ok(RatchetState::Null) | Ok(RatchetState::Empty) => {} + Ok(rs) => { + ratchet_state = rs; break; } - Ok(None) => {} Err(()) => return Err(ReceiveError::RatchetIoError), } } - if ratchet_state.is_none() && app.hello_requires_recognized_ratchet(current_time) { - return Ok(ReceiveResult::Rejected); + if ratchet_state.is_null() { + if app.hello_requires_recognized_ratchet(current_time) { + return Ok(ReceiveResult::Rejected); + } + ratchet_state = RatchetState::Empty; } // Start of Noise XKhfs+psk2 pattern2. @@ -1063,7 +1057,7 @@ impl Context { noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); drop(noise_ekem1_secret); // Noise process pattern2 psk token. - let ratchet_key = ratchet_state.as_ref().map(|rs| rs.key.as_ref()).unwrap_or(&[0; RATCHET_SIZE]); + let ratchet_key = ratchet_state.key().unwrap(); let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); // Noise process pattern2 payload. @@ -1236,10 +1230,13 @@ impl Context { if let Some(Ok(noise_ekem1_secret)) = is_auth.then_some(noise_ekem1_secret) { noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); drop(noise_ekem1_secret); + // We attempt to decrypt the payload at most three times. First two times with // the ratchet key Alice remembers, and final time with a ratchet // key of zero if Alice allows ratchet downgrades. - + // The following code is not constant time, meaning we leak to an + // attacker whether or not we downgraded. + // We don't currently consider this sensitive enough information to hide. let mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState, Secret, [u8; 64])> { // Check for which ratchet key Bob wants to use. let mut noise_ck = noise_ck.clone(); @@ -1271,19 +1268,22 @@ impl Context { // Check first key. let mut ratchet_i = 0; let mut result = None; - let ratchet_number = 0; - if let Some(rs) = &state.ratchet_state[0] { - result = test_ratchet_key(rs.key.as_ref()); + let mut chain_len = 0; + if let Some(key) = state.ratchet_states[0].key() { + chain_len = state.ratchet_states[0].chain_len(); + result = test_ratchet_key(key); } // Check second key. if result.is_none() { ratchet_i = 1; - if let Some(rs) = &state.ratchet_state[1] { - result = test_ratchet_key(rs.key.as_ref()); + if let Some(key) = state.ratchet_states[1].key() { + chain_len = state.ratchet_states[0].chain_len(); + result = test_ratchet_key(key); } } // Check zero key. - if result.is_none() { + if result.is_none() && !app.initiator_disallows_downgrade(&session, current_time) { + chain_len = 0; result = test_ratchet_key(&[0u8; RATCHET_SIZE]); } @@ -1330,18 +1330,16 @@ impl Context { // Transition offer state machine to the NoiseXKPattern3 state. let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); - let new_ratchet_state = RatchetState { - key: rk, - fingerprint: rf, - chain_len: ratchet_number + 1, - }; - let action = if state.ratchet_state[0].is_some() && state.ratchet_state[1].is_some() { - SaveAction::DeleteThenAddRatchet(state.ratchet_state[1 - ratchet_i].as_ref().unwrap(), &new_ratchet_state) - } else { - SaveAction::AddRatchet(&new_ratchet_state) - }; - let result = - app.save_ratchet_state(&session.remote_s_public_key, &session.application_data, action, current_time); + let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(chain_len + 1).unwrap()); + + let ratchet_to_preserve = &state.ratchet_states[ratchet_i]; + let result = app.save_ratchet_state( + &session.remote_static_key, + &session.application_data, + [&state.ratchet_states[0], &state.ratchet_states[1]], + [&new_ratchet_state, ratchet_to_preserve], + current_time + ); if result.is_err() { return Err(ReceiveError::RatchetIoError); } @@ -1361,8 +1359,8 @@ impl Context { .lock() .unwrap() .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); - state.ratchet_state[1] = state.ratchet_state[ratchet_i].clone(); - state.ratchet_state[0] = Some(new_ratchet_state.clone()); + state.ratchet_states[1] = state.ratchet_states[ratchet_i].clone(); + state.ratchet_states[0] = new_ratchet_state; state.cipher_states[0].replace(SessionKey::new( hmac, @@ -1405,9 +1403,9 @@ impl Context { } // Bob failed authentication so we must restart our offer according to Noise. // We restart the offer instead of dropping the session to defend against DOS. - let ratchet_state = state.ratchet_state.clone(); drop(state); let mut state = session.state.write().unwrap(); + let ratchet_state = state.ratchet_states.clone(); if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { if !handshake_state.reinitialize( &session, @@ -1495,8 +1493,46 @@ impl Context { // Bob finished Noise XKhfs+psk2 handshake. let header_send_cipher = Application::PrpEnc::new(handshake_state.header_send_key.as_ref()); let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); - match check_accept_session(&remote_s_public_key, &message[p_enc_start..p_auth_start]) { - AcceptSessionAction::Accept(application_data) => { + let mut send_reject = || { + // We just used a counter with this key, but we are not storing + // the fact we used it in memory. This is currently ok because the + // handshake is being dropped, so nonce reuse can't happen. + let (mut fragment, len) = encrypt_control( + &mut Application::AeadEnc::new(kex_key_b2a.as_ref()), + &header_send_cipher, + PACKET_TYPE_SESSION_REJECTED, + INIT_COUNTER, + handshake_state.remote_key_id.get(), + &[], + ); + send_unassociated_reply(&mut fragment[..len]); + }; + + let (responder_disallows_downgrade, responder_silently_rejects) = check_accept_session(&remote_s_public_key, &message[p_enc_start..p_auth_start], handshake_state.ratchet_state.chain_len()); + if let Some((responder_disallows_downgrade, application_data)) = responder_disallows_downgrade { + let result = app.restore_by_identity( + &remote_s_public_key, + &application_data, + current_time, + ); + if let Ok(true_ratchet_states) = result { + let mut has_match = false; + for rs in &true_ratchet_states { + if !rs.is_null() { + has_match |= &handshake_state.ratchet_state == rs; + } + } + if !has_match { + if !responder_disallows_downgrade && handshake_state.ratchet_state.is_empty() { + // TODO: add some kind of warning callback or signal. + } else { + if !responder_silently_rejects { + send_reject(); + } + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + } + let mut noise_kk_ss = Secret::new(); if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); @@ -1508,15 +1544,12 @@ impl Context { let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); // We must make sure the ratchet key is saved before we transition. - let new_ratchet_state = RatchetState { - key: rk, - fingerprint: rf, - chain_len: handshake_state.ratchet_state.as_ref().map(|rs| rs.chain_len + 1).unwrap_or(1), - }; + let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(handshake_state.ratchet_state.chain_len() + 1).unwrap()); let result = app.save_ratchet_state( &remote_s_public_key, &application_data, - SaveAction::VerifyThenOverwriteRatchet(handshake_state.ratchet_state.as_ref(), &new_ratchet_state), + [&true_ratchet_states[0], &true_ratchet_states[1]], + [&new_ratchet_state, &RatchetState::Null], current_time, ); if result.is_err() { @@ -1529,13 +1562,13 @@ impl Context { context: Arc::downgrade(&self.0), queue_idx, application_data, - remote_s_public_key, + remote_static_key: remote_s_public_key, send_counter: AtomicU64::new(INIT_COUNTER), session_has_expired: AtomicBool::new(false), counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), state_machine_lock: Mutex::new(()), state: RwLock::new(SessionMutableState { - ratchet_state: [Some(new_ratchet_state.clone()), None], + ratchet_states: [new_ratchet_state.clone(), RatchetState::Null], cipher_states: [ Some(SessionKey::new( hmac, @@ -1584,23 +1617,14 @@ impl Context { // restart the handshake in this case. return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); } + } else { + return Err(ReceiveError::RatchetIoError); } - AcceptSessionAction::SendReject => { - // We just used a counter with this key, but we are not storing - // the fact we used it in memory. This is currently ok because the - // handshake is being dropped, so nonce reuse can't happen. - let (mut fragment, len) = encrypt_control( - &mut Application::AeadEnc::new(kex_key_b2a.as_ref()), - &header_send_cipher, - PACKET_TYPE_SESSION_REJECTED, - INIT_COUNTER, - handshake_state.remote_key_id.get(), - &[], - ); - send_unassociated_reply(&mut fragment[..len]); - return Ok(ReceiveResult::Rejected); + } else { + if !responder_silently_rejects { + send_reject(); } - AcceptSessionAction::SilentlyReject => return Ok(ReceiveResult::Rejected), + return Ok(ReceiveResult::Rejected); } } else { return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); @@ -1735,7 +1759,7 @@ fn initiate_rekey( // Start of Noise KKpsk0 pattern1. // Noise process pattern1 psk0 token. let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_state[0].as_ref().unwrap().key.as_ref()); + let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_states[0].key().unwrap()); let noise_h_psk = mix_hash(sha512, &session.noise_kk_local_init_h, &noise_temp_h); // Noise process pattern1 e token. let noise_e_secret = Application::KeyPair::generate(&mut context.rng.lock().unwrap()); @@ -1746,7 +1770,7 @@ fn initiate_rekey( noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); // Noise process pattern1 es token. let mut noise_es = Secret::new(); - if !noise_e_secret.agree(&session.remote_s_public_key, noise_es.as_mut()) { + if !noise_e_secret.agree(&session.remote_static_key, noise_es.as_mut()) { return Err(()); } noise_ck.mix_key(hmac, noise_es.as_ref()); @@ -1836,11 +1860,12 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu _ => (true, false, SessionEvent::Control), }; if try_delete { - let is_ok = if let Some(rs) = &state.ratchet_state[1] { + let is_ok = if !state.ratchet_states[1].is_null() { app.save_ratchet_state( - &session.remote_s_public_key, + &session.remote_static_key, &session.application_data, - SaveAction::DeleteRatchet(rs), + [&state.ratchet_states[0], &state.ratchet_states[1]], + [&state.ratchet_states[0], &RatchetState::Null], current_time, ) .is_ok() @@ -1855,7 +1880,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu .unwrap() .replace(Application::AeadEnc::new(kex_send_key.as_ref())); } - state.ratchet_state[1] = None; + state.ratchet_states[1] = RatchetState::Null; state.current_key ^= 1; state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); } else { @@ -1917,7 +1942,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let sha512 = &mut Application::Hash::new(); let hmac = &mut Application::HmacHash::new(); let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_state[0].as_ref().unwrap().key.as_ref()); + let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_states[0].key().unwrap()); let noise_h_psk = mix_hash(sha512, &session.noise_kk_remote_init_h, &noise_temp_h); // Noise process pattern1 e token. // Get public key validation out of the way early @@ -1926,7 +1951,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let mut noise_se = Secret::new(); if let Some(alice_e) = from_bytes_agreement::(&noise_pattern1.noise_e, &context.0.static_keypair, noise_es.as_mut()) { let bob_e_secret = Application::KeyPair::generate(&mut context.0.rng.lock().unwrap()); - if bob_e_secret.agree(&alice_e, noise_ee.as_mut()) && bob_e_secret.agree(&session.remote_s_public_key, noise_se.as_mut()) { + if bob_e_secret.agree(&alice_e, noise_ee.as_mut()) && bob_e_secret.agree(&session.remote_static_key, noise_se.as_mut()) { let noise_h_pske = mix_hash(sha512, &noise_h_psk, alice_e.as_bytes()); noise_ck.mix_key(hmac, alice_e.as_bytes()); // Noise process pattern1 es token. @@ -1977,15 +2002,12 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu drop(noise_k_pskessseese); // Bob finished Noise KKpsk0 handshake. let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); - let new_ratchet_state = RatchetState { - key: rk, - fingerprint: rf, - chain_len: state.ratchet_state[0].as_ref().map(|rs| rs.chain_len + 1).unwrap_or(1), - }; + let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); let result = app.save_ratchet_state( - &session.remote_s_public_key, + &session.remote_static_key, &session.application_data, - SaveAction::AddRatchet(&new_ratchet_state), + [&state.ratchet_states[0], &state.ratchet_states[1]], + [&new_ratchet_state, &state.ratchet_states[0]], current_time, ); if result.is_err() { @@ -2011,8 +2033,8 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu .lock() .unwrap() .replace(Application::AeadDec::new(kex_key_a2b.as_ref())); - state.ratchet_state[1] = state.ratchet_state[0].clone(); - state.ratchet_state[0] = Some(new_ratchet_state.clone()); + state.ratchet_states[1] = state.ratchet_states[0].clone(); + state.ratchet_states[0] = new_ratchet_state.clone(); state.cipher_states[next_key_index].replace(SessionKey::new( hmac, @@ -2082,15 +2104,12 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu // Bob fully authenticated. // Alice finished Noise KKpsk0 handshake. let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); - let new_ratchet_state = RatchetState { - key: rk, - fingerprint: rf, - chain_len: state.ratchet_state[0].as_ref().map(|rs| rs.chain_len + 1).unwrap_or(1), - }; + let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); let result = app.save_ratchet_state( - &session.remote_s_public_key, + &session.remote_static_key, &session.application_data, - SaveAction::DeleteThenAddRatchet(&state.ratchet_state[0].as_ref().unwrap(), &new_ratchet_state), + [&state.ratchet_states[0], &state.ratchet_states[1]], + [&new_ratchet_state, &RatchetState::Null], current_time, ); if result.is_err() { @@ -2118,8 +2137,8 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu .lock() .unwrap() .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); - state.ratchet_state[1] = None; - state.ratchet_state[0] = Some(new_ratchet_state.clone()); + state.ratchet_states[1] = RatchetState::Null; + state.ratchet_states[0] = new_ratchet_state.clone(); state.cipher_states[next_key_index].replace(SessionKey::new( hmac, @@ -2196,22 +2215,19 @@ impl Session { /// The static public key of the remote peer. #[inline] pub fn remote_s_public_key(&self) -> &Application::PublicKey { - &self.remote_s_public_key + &self.remote_static_key } - /// The most recent confirmed ratchet state of this session. + /// The current ratchet state of this session. /// The returned values are sensitive and should be securely erased before being dropped. #[inline] - pub fn ratchet_state(&self) -> [Option; 2] { + pub fn ratchet_states(&self) -> [RatchetState; 2] { let state = self.state.read().unwrap(); - state.ratchet_state.clone() + state.ratchet_states.clone() } - /// The most recent confirmed ratchet number of this session. + /// The current ratchet count of this session. #[inline] pub fn ratchet_count(&self) -> u64 { - self.state.read().unwrap().ratchet_state[0] - .as_ref() - .map(|rs| rs.chain_len) - .unwrap_or(0) + self.state.read().unwrap().ratchet_states[0].chain_len() } /// Mark a session as expired. This will make it impossible for this session to successfully /// receive or send data. It is recommended to simply `drop` the session instead, but this can @@ -2295,7 +2311,7 @@ impl NoiseXKAliceHandshake { fn initialize( local_key_id: NonZeroU32, remote_s_public_key: &Application::PublicKey, - ratchet_state: &[Option; 2], + ratchet_state: &[RatchetState; 2], rng: &mut Application::Rng, ) -> Result< ( @@ -2345,10 +2361,10 @@ impl NoiseXKAliceHandshake { // Noise process pattern1 payload. let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); let mut idx = 0; - for r in ratchet_state { - if let Some(rs) = r { + for rs in ratchet_state { + if let Some(rf) = rs.fingerprint() { let next_idx = idx + RATCHET_SIZE; - noise_pattern1.payload[idx..next_idx].copy_from_slice(rs.fingerprint.as_ref()); + noise_pattern1.payload[idx..next_idx].copy_from_slice(rf); idx = next_idx; } } @@ -2386,13 +2402,13 @@ impl NoiseXKAliceHandshake { fn reinitialize( &mut self, session: &Arc>, - ratchet_state: &[Option; 2], + ratchet_state: &[RatchetState; 2], session_map: &mut HashMap>, bool)>, rng: &mut Application::Rng, current_time: i64, ) -> bool { let local_key_id = generate_key_id(session_map, rng); - if let Ok((offer, a2b_header_key, b2a_header_key)) = Self::initialize(local_key_id, &session.remote_s_public_key, ratchet_state, rng) { + if let Ok((offer, a2b_header_key, b2a_header_key)) = Self::initialize(local_key_id, &session.remote_static_key, ratchet_state, rng) { self.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); session_map.remove(&self.local_key_id); session_map.insert(local_key_id, (Arc::downgrade(session), false)); @@ -2580,7 +2596,7 @@ fn send_with_fragmentation( fragment_count as u8, fragment_no as u8, packet_type, - remote_key_id.map(|n| n.get()).unwrap_or(0), + remote_key_id.map_or(0, |n| n.get()), counter_or_id, ); if let Some(hcc) = header_cipher { From 67f7bb5891c8847e94bfe89862e24745270f8270 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 12:17:37 -0400 Subject: [PATCH 28/91] cargo fmt --- src/applicationlayer.rs | 19 ++++++++----------- src/error.rs | 2 +- src/lib.rs | 2 +- src/zssp.rs | 29 ++++++++++++----------------- 4 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index ed9e020..7ac2bf2 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -168,7 +168,12 @@ pub trait ApplicationLayer: Sized { Ok(RatchetState::Null) } #[allow(unused)] - fn restore_by_identity(&self, remote_static_key: &Self::PublicKey, application_data: &Self::Data, current_time: i64) -> Result<[RatchetState; 2], ()> { + fn restore_by_identity( + &self, + remote_static_key: &Self::PublicKey, + application_data: &Self::Data, + current_time: i64, + ) -> Result<[RatchetState; 2], ()> { Ok([RatchetState::Null, RatchetState::Null]) } /// Atomically save the given ratchet key, fingerprint and number to persistent storage. @@ -213,16 +218,8 @@ pub enum RatchetState { } use RatchetState::*; impl RatchetState { - pub fn new_nonempty( - key: Secret, - fingerprint: Secret, - chain_len: NonZeroU64, - ) -> Self { - NonEmpty(NonEmptyRatchetState { - key, - fingerprint, - chain_len, - }) + pub fn new_nonempty(key: Secret, fingerprint: Secret, chain_len: NonZeroU64) -> Self { + NonEmpty(NonEmptyRatchetState { key, fingerprint, chain_len }) } pub fn new_initial_states() -> [RatchetState; 2] { [RatchetState::Empty, RatchetState::Null] diff --git a/src/error.rs b/src/error.rs index 34e2dbc..84ead7e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,7 +14,7 @@ pub enum OpenError { /// Local identity blob is too large to send, even with fragmentation. DataTooLarge, - RatchetIoError + RatchetIoError, } #[derive(Debug, PartialEq, Eq)] diff --git a/src/lib.rs b/src/lib.rs index d0025df..221d7a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,4 +21,4 @@ pub mod error; pub use crate::applicationlayer::{ApplicationLayer, RatchetState}; pub use crate::log_event::LogEvent; pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_SIZE}; -pub use crate::zssp::{AcceptSessionAction, Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; +pub use crate::zssp::{Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/zssp.rs b/src/zssp.rs index e35bdd5..c4ee0a3 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -110,12 +110,6 @@ pub enum IncomingSessionAction { Drop, } -pub enum AcceptSessionAction { - Accept(Application::Data), - SendReject, - SilentlyReject, -} - /// ZeroTier Secure Session Protocol (ZSSP) Session /// /// A FIPS/NIST compliant variant of Noise_XK with hybrid Kyber1024 PQ data forward secrecy. @@ -1328,8 +1322,7 @@ impl Context { drop(noise_k_eseeekem1pskse); // Alice finished Noise XKhfs+psk2 handshake. // Transition offer state machine to the NoiseXKPattern3 state. - let (rk, rf) = - noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); + let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(chain_len + 1).unwrap()); let ratchet_to_preserve = &state.ratchet_states[ratchet_i]; @@ -1338,7 +1331,7 @@ impl Context { &session.application_data, [&state.ratchet_states[0], &state.ratchet_states[1]], [&new_ratchet_state, ratchet_to_preserve], - current_time + current_time, ); if result.is_err() { return Err(ReceiveError::RatchetIoError); @@ -1508,13 +1501,13 @@ impl Context { send_unassociated_reply(&mut fragment[..len]); }; - let (responder_disallows_downgrade, responder_silently_rejects) = check_accept_session(&remote_s_public_key, &message[p_enc_start..p_auth_start], handshake_state.ratchet_state.chain_len()); + let (responder_disallows_downgrade, responder_silently_rejects) = check_accept_session( + &remote_s_public_key, + &message[p_enc_start..p_auth_start], + handshake_state.ratchet_state.chain_len(), + ); if let Some((responder_disallows_downgrade, application_data)) = responder_disallows_downgrade { - let result = app.restore_by_identity( - &remote_s_public_key, - &application_data, - current_time, - ); + let result = app.restore_by_identity(&remote_s_public_key, &application_data, current_time); if let Ok(true_ratchet_states) = result { let mut has_match = false; for rs in &true_ratchet_states { @@ -1544,7 +1537,8 @@ impl Context { let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); // We must make sure the ratchet key is saved before we transition. - let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(handshake_state.ratchet_state.chain_len() + 1).unwrap()); + let new_ratchet_state = + RatchetState::new_nonempty(rk, rf, NonZeroU64::new(handshake_state.ratchet_state.chain_len() + 1).unwrap()); let result = app.save_ratchet_state( &remote_s_public_key, &application_data, @@ -2104,7 +2098,8 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu // Bob fully authenticated. // Alice finished Noise KKpsk0 handshake. let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); - let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); + let new_ratchet_state = + RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); let result = app.save_ratchet_state( &session.remote_static_key, &session.application_data, From 85b15b4d240031a945359f00556e44644e565902 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 12:39:37 -0400 Subject: [PATCH 29/91] updated readme --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bdb94cd..d11113b 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,15 @@ Specifically ZSSP implements the [Noise XK](http://noiseprotocol.org/noise.html# Hybrid post-quantum forward secrecy using Kyber1024 is performed alongside Noise with the result being mixed in alongside an optional pre-shared key at the end of session negotiation. -ZSSP is designed for use in ZeroTier 2 but is payload-agnostic and could easily be adapted for use in other projects. +ZSSP is designed for use in ZeroTier but is payload-agnostic and could easily be adapted for use in other projects. + +Further information can be found in the ZSSP whitepaper (pending official release). ## Cryptographic Primitives Used - AES-256-GCM: Authenticated encryption - - HMAC-SHA384: Key mixing, sub-key derivation in key-based KDF construction + - SHA512: Used with the KBKDF construction, also used in a proof of work and ip ownership DOS mitigation scheme + - KBKDF: Key mixing, sub-key derivation - NIST P-384 ECDH: Elliptic curve key exchange during initial handshake and for periodic re-keying during the session - Kyber1024: Quantum attack resistant lattice-based key exchange during initial handshake - - AES-256-ECB: Single 128-bit block encryption of header information to harden the fragmentation protocol against denial of service attack (see section on header protection) - + - AES-256: 128-bit PRP for authenticated encryption of header information to harden the fragmentation protocol against DOS (see section on header protection) From ffca778c79cf91974571f377be788b355ee933e5 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 12:47:27 -0400 Subject: [PATCH 30/91] updated readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d11113b..6164906 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ Further information can be found in the ZSSP whitepaper (pending official releas ## Cryptographic Primitives Used - AES-256-GCM: Authenticated encryption - - SHA512: Used with the KBKDF construction, also used in a proof of work and ip ownership DOS mitigation scheme + - SHA512: Used with the KBKDF construction, also used in a proof of work and IP ownership DOS mitigation scheme - KBKDF: Key mixing, sub-key derivation - NIST P-384 ECDH: Elliptic curve key exchange during initial handshake and for periodic re-keying during the session - Kyber1024: Quantum attack resistant lattice-based key exchange during initial handshake - - AES-256: 128-bit PRP for authenticated encryption of header information to harden the fragmentation protocol against DOS (see section on header protection) + - AES-256: 128-bit PRP for AES-256-GCM and for authenticated encryption of headera to harden fragmentation against DOS (see section on header protection) From 308fc3da70ec80fb5eeebab31393b2280668a508 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 12:57:46 -0400 Subject: [PATCH 31/91] added comment --- src/crypto/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index b033569..0c7f601 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -6,5 +6,7 @@ pub mod p384; pub mod secret; pub mod sha512; +// We re-export our dependencies so it is less of a headache for the implementor to use the same +// exact version of them. pub use pqc_kyber; pub use rand_core; From 99556bb1eb69f2b85f92095ee3fcbdf1e16a9b82 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 13:38:03 -0400 Subject: [PATCH 32/91] updated docs --- Cargo.toml | 2 +- src/applicationlayer.rs | 84 ++++++----------------------------------- src/crypto/secret.rs | 18 +++------ src/lib.rs | 4 +- src/ratchet_state.rs | 76 +++++++++++++++++++++++++++++++++++++ src/symmetric_state.rs | 12 +++--- src/zssp.rs | 7 +++- 7 files changed, 108 insertions(+), 95 deletions(-) create mode 100644 src/ratchet_state.rs diff --git a/Cargo.toml b/Cargo.toml index e6078c3..74aa8d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ authors = ["ZeroTier, Inc. ", "Adam Ierymenko ; - /// This function will be called whenever Alice's initial Hello packet contains the zero ratchet - /// fingerprint. Brand new peers will always connect to Bob with the zero ratchet key, but from - /// then on they should be using non-zero ratchet keys. + /// This function will be called whenever Alice's initial Hello packet contains the empty ratchet + /// fingerprint. Brand new peers will always connect to Bob with the empty ratchet, but from + /// then on they should be using non-empty ratchet states. /// - /// If this returns false, we will attempt to connect to Alice with the zero ratchet key. + /// If this returns false, we will attempt to connect to Alice with the empty ratchet state. /// If this returns true, Alice's connection will be silently dropped. /// If this function is configured to always return true, it means peers will not be able to /// connect to us unless they had a prior-established ratchet key with us. This is the best way @@ -130,11 +128,11 @@ pub trait ApplicationLayer: Sized { false } /// This function is called if we, as Alice, attempted to open a session with Bob using a - /// non-zero ratchet key, but Bob does not have this ratchet key and wants to downgrade + /// non-empty ratchet key, but Bob does not have this ratchet key and wants to downgrade /// to the zero ratchet key. /// - /// If it returns true Alice will downgrade their ratchet number to 0, potentially ending their - /// current ratchet chain. + /// If it returns true Alice will downgrade their ratchet state to emtpy, potentially ending + /// their current ratchet chain. /// If it returns false then we will consider Bob as having failed authentication, and this /// packet will be dropped. The session will continue attempting to connect to Bob. /// @@ -152,7 +150,7 @@ pub trait ApplicationLayer: Sized { false } /// Lookup a specific ratchet key based on its ratchet fingerprint. - /// This function will be called whenever Alice attempts to connect to us with a non-zero + /// This function will be called whenever Alice attempts to connect to us with a non-empty /// ratchet fingerprint. /// /// If the ratchet key was found, the function should return `RestoreAction::RestoreRatchet`. This will @@ -161,7 +159,7 @@ pub trait ApplicationLayer: Sized { /// If the ratchet key could not be found, the application may choose between returning /// `RatchetAction::DowngradeRatchet` or `RatchetAction::FailAuthentication`. /// If `RatchetAction::DowngradeRatchet` is returned we will attempt to convince Alice to downgrade - /// to the zero ratchet key, restarting the ratchet chain. + /// to the empty ratchet key, restarting the ratchet chain. /// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped. #[allow(unused)] fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE], current_time: i64) -> Result { @@ -188,7 +186,7 @@ pub trait ApplicationLayer: Sized { /// If persistent storage is supported, this function should not return until the ratchet state /// is saved, otherwise it is possible, albeit unlikely, for a sudden restart of the local /// machine to put our ratchet state out of sync with the remote peer. If this happens the only - /// fix is to reset both ratchet keys to zero. + /// fix is to reset both ratchet keys to empty. /// /// This function may also save state to volatile storage, in which case all peers which connect /// to us will have to allow downgrade @@ -209,63 +207,3 @@ pub trait ApplicationLayer: Sized { #[inline] fn event_log<'a>(&self, event: LogEvent<'a, Self>, current_time: i64) {} } - -#[derive(Clone, PartialEq, Eq)] -pub enum RatchetState { - Null, - Empty, - NonEmpty(NonEmptyRatchetState), -} -use RatchetState::*; -impl RatchetState { - pub fn new_nonempty(key: Secret, fingerprint: Secret, chain_len: NonZeroU64) -> Self { - NonEmpty(NonEmptyRatchetState { key, fingerprint, chain_len }) - } - pub fn new_initial_states() -> [RatchetState; 2] { - [RatchetState::Empty, RatchetState::Null] - } - pub fn is_null(&self) -> bool { - match self { - Null => true, - _ => false, - } - } - pub fn is_empty(&self) -> bool { - match self { - Empty => true, - _ => false, - } - } - #[inline] - pub fn nonempty(&self) -> Option<&NonEmptyRatchetState> { - match self { - NonEmpty(rs) => Some(&rs), - _ => None, - } - } - #[inline] - pub fn chain_len(&self) -> u64 { - self.nonempty().map_or(0, |rs| rs.chain_len.get()) - } - #[inline] - pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { - self.nonempty().map_or(None, |rs| Some(&rs.fingerprint.as_ref())) - } - #[inline] - pub fn key(&self) -> Option<&[u8; RATCHET_SIZE]> { - const ZERO_KEY: [u8; RATCHET_SIZE] = [0u8; RATCHET_SIZE]; - match self { - Null => None, - Empty => Some(&ZERO_KEY), - NonEmpty(rs) => Some(rs.key.as_ref()), - } - } -} -/// A ratchet key and fingerprint, -/// along with the length of the ratchet chain the keys were derived from. -#[derive(Clone, PartialEq, Eq)] -pub struct NonEmptyRatchetState { - pub key: Secret, - pub fingerprint: Secret, - pub chain_len: NonZeroU64, -} diff --git a/src/crypto/secret.rs b/src/crypto/secret.rs index 9e00abd..53aa9dc 100644 --- a/src/crypto/secret.rs +++ b/src/crypto/secret.rs @@ -1,5 +1,4 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. - use std::convert::TryInto; /// Constant time byte slice equality. @@ -36,20 +35,15 @@ impl Secret { pub fn new() -> Self { Self([0_u8; L]) } - - /// Moves bytes into secret, will panic if the slice does not match the size of this secret. - #[inline(always)] - pub fn move_bytes(b: [u8; L]) -> Self { - Self(b) - } - - /// Copy bytes into secret, then nuke the previous value, will panic if the slice does not match the size of this secret. - #[inline(always)] - pub fn from_bytes_then_nuke(b: &mut [u8]) -> Self { + /// Copy bytes into secret, then delete the previous value, will panic if the slice does not match the size of this secret. + #[inline(never)] + pub fn from_bytes_then_delete(b: &mut [u8]) -> Self { let ret = Self(b.try_into().unwrap()); b.fill(0); ret } + /// Moves bytes into secret, will panic if the slice does not match the size of this secret. + /// This is unsafe because it will not destroy the contents of its input. #[inline(always)] pub unsafe fn from_bytes(b: &[u8]) -> Self { Self(b.try_into().unwrap()) @@ -92,7 +86,7 @@ impl Secret { } impl Drop for Secret { - #[inline(always)] + #[inline(never)] fn drop(&mut self) { self.0.fill(0); } diff --git a/src/lib.rs b/src/lib.rs index 221d7a6..46d0713 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,11 +14,13 @@ mod handshake_cache; mod indexed_heap; mod log_event; mod proto; +mod ratchet_state; mod symmetric_state; mod zssp; pub mod error; -pub use crate::applicationlayer::{ApplicationLayer, RatchetState}; +pub use crate::applicationlayer::ApplicationLayer; pub use crate::log_event::LogEvent; pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_SIZE}; +pub use crate::ratchet_state::RatchetState; pub use crate::zssp::{Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs new file mode 100644 index 0000000..c78d0f0 --- /dev/null +++ b/src/ratchet_state.rs @@ -0,0 +1,76 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::num::NonZeroU64; + +use crate::crypto::secret::Secret; +use crate::RATCHET_SIZE; + +#[derive(Clone, PartialEq, Eq)] +pub enum RatchetState { + Null, + Empty, + NonEmpty(NonEmptyRatchetState), +} +use RatchetState::*; +impl RatchetState { + #[inline] + pub fn new_nonempty(key: Secret, fingerprint: Secret, chain_len: NonZeroU64) -> Self { + NonEmpty(NonEmptyRatchetState { key, fingerprint, chain_len }) + } + #[inline] + pub fn new_initial_states() -> [RatchetState; 2] { + [RatchetState::Empty, RatchetState::Null] + } + #[inline] + pub fn is_null(&self) -> bool { + match self { + Null => true, + _ => false, + } + } + #[inline] + pub fn is_empty(&self) -> bool { + match self { + Empty => true, + _ => false, + } + } + #[inline] + pub fn nonempty(&self) -> Option<&NonEmptyRatchetState> { + match self { + NonEmpty(rs) => Some(&rs), + _ => None, + } + } + #[inline] + pub fn chain_len(&self) -> u64 { + self.nonempty().map_or(0, |rs| rs.chain_len.get()) + } + #[inline] + pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { + self.nonempty().map_or(None, |rs| Some(&rs.fingerprint.as_ref())) + } + #[inline] + pub fn key(&self) -> Option<&[u8; RATCHET_SIZE]> { + const ZERO_KEY: [u8; RATCHET_SIZE] = [0u8; RATCHET_SIZE]; + match self { + Null => None, + Empty => Some(&ZERO_KEY), + NonEmpty(rs) => Some(rs.key.as_ref()), + } + } +} +/// A ratchet key and fingerprint, +/// along with the length of the ratchet chain the keys were derived from. +#[derive(Clone, PartialEq, Eq)] +pub struct NonEmptyRatchetState { + pub key: Secret, + pub fingerprint: Secret, + pub chain_len: NonZeroU64, +} diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 339123c..6f6ac6f 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -42,7 +42,7 @@ impl SymmetricState { self.token_counter += 1; self.chaining_key.overwrite(&next_ck); - Secret::from_bytes_then_nuke(&mut temp_k[..AES_256_KEY_SIZE]) + Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE]) } /// Corresponds to Noise `MixKeyAndHash`. pub(crate) fn mix_key_and_hash(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> [u8; NOISE_HASHLEN] { @@ -77,7 +77,7 @@ impl SymmetricState { self.token_counter += 1; self.chaining_key.overwrite(&next_ck); - (temp_h, Secret::from_bytes_then_nuke(&mut temp_k[..AES_256_KEY_SIZE])) + (temp_h, Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE])) } /// Get an additional symmetric key (ASK) that is a collision resistant hash of the transcript, /// is forward secrect and is cryptographically independent from all other produced keys. @@ -94,8 +94,8 @@ impl SymmetricState { let mut temp_k2 = [0u8; NOISE_HASHLEN]; self.kbkdf(hm, noise_h, [b'A', b'S', b'K', label], 2, &mut temp_k1, Some(&mut temp_k2), None); ( - Secret::from_bytes_then_nuke(&mut temp_k1[..AES_256_KEY_SIZE]), - Secret::from_bytes_then_nuke(&mut temp_k2[..AES_256_KEY_SIZE]), + Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]), + Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), ) } /// Corresponds to Noise `Split`. @@ -107,8 +107,8 @@ impl SymmetricState { // Normally KBKDF would not truncate to derive the correct length of AES keys, // but Noise specifies that the AES keys be truncated from NOISE_HASHLEN to AES_256_KEY_SIZE. ( - Secret::from_bytes_then_nuke(&mut temp_k1[..AES_256_KEY_SIZE]), - Secret::from_bytes_then_nuke(&mut temp_k2[..AES_256_KEY_SIZE]), + Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]), + Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), ) } #[inline(always)] diff --git a/src/zssp.rs b/src/zssp.rs index c4ee0a3..4eaeb95 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -24,7 +24,6 @@ use crate::crypto::rand_core::RngCore; use crate::crypto::secret::{secure_eq, Secret}; use crate::crypto::sha512::{HmacSha512, Sha512}; -use crate::applicationlayer::*; use crate::error::{FaultType, OpenError, ReceiveError, SendError}; use crate::frag_cache::UnassociatedFragCache; use crate::fragged::{Assembled, Fragged}; @@ -33,6 +32,7 @@ use crate::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; use crate::log_event::LogEvent; use crate::proto::*; use crate::symmetric_state::SymmetricState; +use crate::{applicationlayer::*, RatchetState}; /// Session context for local application. /// @@ -1036,7 +1036,7 @@ impl Context { // Noise process pattern2 ekem1 token. let (noise_ekem1, noise_ekem1_secret) = pqc_kyber::encapsulate(&noise_pattern1.noise_e1, self.0.rng.lock().unwrap().deref_mut()) .map_err(|_| byzantine_fault!(FaultType::FailedAuthentication, false)) - .map(|(ct, ekem1)| (ct, Secret::move_bytes(ekem1)))?; + .map(|(ct, ekem1)| (ct, Secret(ekem1)))?; // Alice fully authenticated. noise_pattern2.noise_ekem1 = noise_ekem1; let noise_h_ee1peekem1 = encrypt_and_hash::( @@ -1279,6 +1279,9 @@ impl Context { if result.is_none() && !app.initiator_disallows_downgrade(&session, current_time) { chain_len = 0; result = test_ratchet_key(&[0u8; RATCHET_SIZE]); + if result.is_some() { + // TODO: add some kind of warning callback or signal. + } } if let Some((remote_key_id, mut noise_ck, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) = result { From e5a7901482541ebe320190834da1c4e52eb2f15b Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 13:42:39 -0400 Subject: [PATCH 33/91] cargo clippy --- src/zssp.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index 4eaeb95..a4c8991 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -2499,7 +2499,6 @@ fn encrypt_control( c.encrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); } c.finish_encrypt((&mut fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]).try_into().unwrap()); - drop(c); set_packet_header(&mut fragment, 1, 0, packet_type, remote_key_id, counter); header_cipher.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); (fragment, fragment_len) @@ -2678,7 +2677,7 @@ impl SessionKey { } #[inline(always)] - fn get_send_cipher<'a>(&'a self, counter: u64) -> Result, SendError> { + fn get_send_cipher(&self, counter: u64) -> Result, SendError> { if counter < self.expire_at_counter { Ok(self.send_cipher_pool[(counter as usize) % self.send_cipher_pool.len()].lock().unwrap()) } else { @@ -2687,7 +2686,7 @@ impl SessionKey { } #[inline(always)] - fn get_receive_cipher<'a>(&'a self, counter: u64) -> MutexGuard<'a, Application::AeadDec> { + fn get_receive_cipher(&self, counter: u64) -> MutexGuard { let idx = (counter as usize) % self.receive_cipher_pool.len(); self.receive_cipher_pool[idx].lock().unwrap() } From 14d62543e3e37fc7060f274ec3248600b619ddc2 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 14:16:07 -0400 Subject: [PATCH 34/91] cargo clippy --- src/applicationlayer.rs | 14 ++- src/crypto/secret.rs | 4 +- src/error.rs | 8 +- src/handshake_cache.rs | 2 +- src/indexed_heap.rs | 2 +- src/ratchet_state.rs | 14 +-- src/zssp.rs | 265 ++++++++++++++++++++-------------------- 7 files changed, 152 insertions(+), 157 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index d880129..4d77d9f 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -98,6 +98,8 @@ pub trait ApplicationLayer: Sized { type PublicKey: P384PublicKey; type KeyPair: P384KeyPair; + type IoError: std::fmt::Debug; + /// Type for arbitrary opaque object for use by the application that is attached to /// each session. type Data; @@ -162,7 +164,7 @@ pub trait ApplicationLayer: Sized { /// to the empty ratchet key, restarting the ratchet chain. /// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped. #[allow(unused)] - fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE], current_time: i64) -> Result { + fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE], current_time: i64) -> Result { Ok(RatchetState::Null) } #[allow(unused)] @@ -171,7 +173,7 @@ pub trait ApplicationLayer: Sized { remote_static_key: &Self::PublicKey, application_data: &Self::Data, current_time: i64, - ) -> Result<[RatchetState; 2], ()> { + ) -> Result<[RatchetState; 2], Self::IoError> { Ok([RatchetState::Null, RatchetState::Null]) } /// Atomically save the given ratchet key, fingerprint and number to persistent storage. @@ -179,7 +181,7 @@ pub trait ApplicationLayer: Sized { /// See the documentation of `SaveAction` for more details on how to save them to storage, /// and how to handle any pre-existing ratchet keys, fingerprints and numbers. /// - /// If this returns `Err(())`, the packet which triggered this function to be called will be + /// If this returns `Err(IoError)`, the packet which triggered this function to be called will be /// dropped, and no session state will be mutated, preserving synchronization. The remote peer /// will eventually resend that packet and so this function will be called again. /// @@ -193,17 +195,17 @@ pub trait ApplicationLayer: Sized { /// (`initiator_disallows_downgrade` returns false and/or`restore_ratchet` returns `DowngradeRatchet`). /// Otherwise, when we restart, we will not be allowed to reconnect. #[allow(unused)] - fn save_ratchet_state<'a>( + fn save_ratchet_state( &self, remote_static_key: &Self::PublicKey, application_data: &Self::Data, previous_ratchet_states: [&RatchetState; 2], new_ratchet_states: [&RatchetState; 2], current_time: i64, - ) -> Result<(), ()> { + ) -> Result<(), Self::IoError> { Ok(()) } #[allow(unused)] #[inline] - fn event_log<'a>(&self, event: LogEvent<'a, Self>, current_time: i64) {} + fn event_log(&self, event: LogEvent, current_time: i64) {} } diff --git a/src/crypto/secret.rs b/src/crypto/secret.rs index 53aa9dc..60399b3 100644 --- a/src/crypto/secret.rs +++ b/src/crypto/secret.rs @@ -36,7 +36,6 @@ impl Secret { Self([0_u8; L]) } /// Copy bytes into secret, then delete the previous value, will panic if the slice does not match the size of this secret. - #[inline(never)] pub fn from_bytes_then_delete(b: &mut [u8]) -> Self { let ret = Self(b.try_into().unwrap()); b.fill(0); @@ -44,6 +43,8 @@ impl Secret { } /// Moves bytes into secret, will panic if the slice does not match the size of this secret. /// This is unsafe because it will not destroy the contents of its input. + /// # Safety + /// Make sure the contents of the input are securely deleted. #[inline(always)] pub unsafe fn from_bytes(b: &[u8]) -> Self { Self(b.try_into().unwrap()) @@ -86,7 +87,6 @@ impl Secret { } impl Drop for Secret { - #[inline(never)] fn drop(&mut self) { self.0.fill(0); } diff --git a/src/error.rs b/src/error.rs index 84ead7e..63c31c0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,14 +7,14 @@ */ #[derive(Debug, PartialEq, Eq)] -pub enum OpenError { +pub enum OpenError { /// An invalid parameter was supplied to the function. InvalidPublicKey, /// Local identity blob is too large to send, even with fragmentation. DataTooLarge, - RatchetIoError, + RatchetIoError(IoError), } #[derive(Debug, PartialEq, Eq)] @@ -61,7 +61,7 @@ pub enum FaultType { } #[derive(Debug, PartialEq, Eq)] -pub enum ReceiveError { +pub enum ReceiveError { /// A type of fault that can occur because a remote peer sent us a bad packet. /// Such packets will be ignored by ZSSP but a user of ZSSP might want to log /// them for debugging or tracing. @@ -106,5 +106,5 @@ pub enum ReceiveError { /// One of the ratchet saving or lookup functions returned an error, so the packet had to be /// dropped. - RatchetIoError, + RatchetIoError(IoError), } diff --git a/src/handshake_cache.rs b/src/handshake_cache.rs index a69ce68..25f219a 100644 --- a/src/handshake_cache.rs +++ b/src/handshake_cache.rs @@ -73,7 +73,7 @@ impl UnassociatedHandshakeCache { return true; } } - return false; + false } pub(crate) fn service(&self, current_time: i64) { // Only check for expiration if we have a pending packet. diff --git a/src/indexed_heap.rs b/src/indexed_heap.rs index 721f287..8a0547c 100644 --- a/src/indexed_heap.rs +++ b/src/indexed_heap.rs @@ -94,7 +94,7 @@ impl IndexedBinaryHeap { (idx.0 < self.map.len() && self.map[idx.0].1 == idx.1).then(|| self.map[idx.0].0) } pub fn pop(&mut self) -> Option<(T, P)> { - (self.data.len() > 0).then(|| self.remove_idx(0)) + (!self.data.is_empty()).then(|| self.remove_idx(0)) } /// Add an item to the queue and get back a generational index which allows for quick updating /// of this item and its priority. diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index c78d0f0..fddbc57 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -29,22 +29,16 @@ impl RatchetState { } #[inline] pub fn is_null(&self) -> bool { - match self { - Null => true, - _ => false, - } + matches!(self, Null) } #[inline] pub fn is_empty(&self) -> bool { - match self { - Empty => true, - _ => false, - } + matches!(self, Empty) } #[inline] pub fn nonempty(&self) -> Option<&NonEmptyRatchetState> { match self { - NonEmpty(rs) => Some(&rs), + NonEmpty(rs) => Some(rs), _ => None, } } @@ -54,7 +48,7 @@ impl RatchetState { } #[inline] pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { - self.nonempty().map_or(None, |rs| Some(&rs.fingerprint.as_ref())) + self.nonempty().map(|rs| rs.fingerprint.as_ref()) } #[inline] pub fn key(&self) -> Option<&[u8; RATCHET_SIZE]> { diff --git a/src/zssp.rs b/src/zssp.rs index a4c8991..0bc0763 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -395,7 +395,7 @@ impl Context { } } NoiseKKPattern1 { next_retry_time, timeout, noise_message, .. } | NoiseKKPattern2 { next_retry_time, timeout, noise_message, .. } => { - if let Some(ts) = process_timer(&next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { + if let Some(ts) = process_timer(next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { ts } else { if *timeout <= current_time { @@ -418,7 +418,7 @@ impl Context { } } KeyConfirm { next_retry_time, timeout, .. } => { - if let Some(ts) = process_timer(&next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { + if let Some(ts) = process_timer(next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { ts } else { if *timeout <= current_time { @@ -477,86 +477,89 @@ impl Context { application_data: Application::Data, local_identity_blob: Application::LocalIdentityBlob, current_time: i64, - ) -> Result>, OpenError> { + ) -> Result>, OpenError> { mtu = mtu.max(MIN_TRANSPORT_MTU); if local_identity_blob.as_ref().len() > MAX_IDENTITY_BLOB_SIZE { return Err(OpenError::DataTooLarge); } let result = app.restore_by_identity(&remote_static_key, &application_data, current_time); - if let Ok(ratchet_states) = result { - let sha512 = &mut Application::Hash::new(); + match result { + Ok(ratchet_states) => { + let sha512 = &mut Application::Hash::new(); - let mut noise_kk_ss = Secret::new(); - if !self.0.static_keypair.agree(&remote_static_key, noise_kk_ss.as_mut()) { - return Err(OpenError::InvalidPublicKey); - } - let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); - let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_static_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_static_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); + let mut noise_kk_ss = Secret::new(); + if !self.0.static_keypair.agree(&remote_static_key, noise_kk_ss.as_mut()) { + return Err(OpenError::InvalidPublicKey); + } + let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); + let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_static_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_static_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); - let mut session_queue = self.0.session_queue.lock().unwrap(); - let mut session_map = self.0.session_map.write().unwrap(); - let local_key_id = generate_key_id(&session_map, &mut self.0.rng.lock().unwrap()); - // Begin Noise XKhfs+psk2. - let (offer, a2b_header_key, b2a_header_key) = - NoiseXKAliceHandshake::::initialize(local_key_id, &remote_static_key, &ratchet_states, &mut self.0.rng.lock().unwrap())?; - let handshake_state = Box::new(NoiseXKAliceHandshake { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), - local_key_id, - alice_identity_blob: local_identity_blob, - offer, - }); - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } = &handshake_state.offer { - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_1, - None, - *message_id, - None::<&Application::PrpEnc>, + let mut session_queue = self.0.session_queue.lock().unwrap(); + let mut session_map = self.0.session_map.write().unwrap(); + let local_key_id = generate_key_id(&session_map, &mut self.0.rng.lock().unwrap()); + // Begin Noise XKhfs+psk2. + let (offer, a2b_header_key, b2a_header_key) = + NoiseXKAliceHandshake::::initialize(local_key_id, &remote_static_key, &ratchet_states, &mut self.0.rng.lock().unwrap())?; + let handshake_state = Box::new(NoiseXKAliceHandshake { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), + local_key_id, + alice_identity_blob: local_identity_blob, + offer, + }); + if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } = &handshake_state.offer { + send_with_fragmentation( + &mut send, + mtu, + &mut noise_message.clone()[..*noise_message_len], + PACKET_TYPE_NOISE_XK_PATTERN_1, + None, + *message_id, + None::<&Application::PrpEnc>, + ); + } + + let queue_idx = session_queue.reserve_index(); + let session = Arc::new(Session { + context: Arc::downgrade(&self.0), + queue_idx, + application_data, + remote_static_key, + send_counter: AtomicU64::new(INIT_COUNTER), + session_has_expired: AtomicBool::new(false), + counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), + state_machine_lock: Mutex::new(()), + state: RwLock::new(SessionMutableState { + ratchet_states: ratchet_states.clone(), + cipher_states: [None, None], + // Points at 1 until the first key is confirmed. + current_key: 1, + outgoing_offer: OfferStateMachine::NoiseXKPattern1or3(handshake_state), + }), + header_send_cipher: Application::PrpEnc::new(a2b_header_key.as_ref()), + header_receive_cipher: Application::PrpDec::new(b2a_header_key.as_ref()), + kex_receive_cipher: Mutex::new(None), + kex_send_cipher: Mutex::new(None), + noise_kk_ss: noise_kk_ss.clone(), + noise_kk_local_init_h, + noise_kk_remote_init_h, + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + was_bob: false, + }); + session_map.insert(local_key_id, (Arc::downgrade(&session), false)); + session_queue.push_reserved( + queue_idx, + Arc::downgrade(&session), + Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), ); + + Ok(session) + } + Err(e) => { + Err(OpenError::RatchetIoError(e)) } - - let queue_idx = session_queue.reserve_index(); - let session = Arc::new(Session { - context: Arc::downgrade(&self.0), - queue_idx, - application_data, - remote_static_key, - send_counter: AtomicU64::new(INIT_COUNTER), - session_has_expired: AtomicBool::new(false), - counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - state_machine_lock: Mutex::new(()), - state: RwLock::new(SessionMutableState { - ratchet_states: ratchet_states.clone(), - cipher_states: [None, None], - // Points at 1 until the first key is confirmed. - current_key: 1, - outgoing_offer: OfferStateMachine::NoiseXKPattern1or3(handshake_state), - }), - header_send_cipher: Application::PrpEnc::new(a2b_header_key.as_ref()), - header_receive_cipher: Application::PrpDec::new(b2a_header_key.as_ref()), - kex_receive_cipher: Mutex::new(None), - kex_send_cipher: Mutex::new(None), - noise_kk_ss: noise_kk_ss.clone(), - noise_kk_local_init_h, - noise_kk_remote_init_h, - defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - was_bob: false, - }); - session_map.insert(local_key_id, (Arc::downgrade(&session), false)); - session_queue.push_reserved( - queue_idx, - Arc::downgrade(&session), - Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - ); - - return Ok(session); - } else { - return Err(OpenError::RatchetIoError); } } @@ -602,7 +605,7 @@ impl Context { data_buf: &'a mut [u8], mut incoming_physical_packet_buf: Application::IncomingPacketBuffer, current_time: i64, - ) -> Result, ReceiveError> { + ) -> Result, ReceiveError> { send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); let incoming_physical_packet: &mut [u8] = incoming_physical_packet_buf.as_mut(); let incoming_physical_packet_len = incoming_physical_packet.len(); @@ -629,7 +632,7 @@ impl Context { .try_into() .unwrap(), ); - let (fragment_count, fragment_no, packet_type, incoming_counter, header_nonce) = parse_packet_header(&incoming_physical_packet); + let (fragment_count, fragment_no, packet_type, incoming_counter, header_nonce) = parse_packet_header(incoming_physical_packet); // Handle replay protection. if PACKET_TYPE_RANGE_TRANSPORT.contains(&packet_type) { // For DOS resistant reply-protection we need to check that the given counter is @@ -777,7 +780,7 @@ impl Context { .try_into() .unwrap(), ); - let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(&incoming_physical_packet); + let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(incoming_physical_packet); app.event_log( LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), current_time, @@ -816,7 +819,7 @@ impl Context { } } } else { - let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(&incoming_physical_packet); + let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(incoming_physical_packet); app.event_log( LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), current_time, @@ -848,7 +851,7 @@ impl Context { } }; - debug_assert!(fragments.len() >= 1); + debug_assert!(!fragments.is_empty()); debug_assert!(incoming.is_none() || session.is_none()); let message = &mut [0u8; MAX_NOISE_HANDSHAKE_SIZE]; @@ -867,7 +870,7 @@ impl Context { if session.is_some() || incoming.is_some() { return Err(byzantine_fault!(FaultType::OutOfSequence, false)); } - if message_size < NoiseXKPattern1::MIN_SIZE || message_size > NoiseXKPattern1::MAX_SIZE { + if (NoiseXKPattern1::MIN_SIZE..=NoiseXKPattern1::MAX_SIZE).contains(&message_size) { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } // The message id must be the first 8 bytes of the gcm tag. @@ -904,7 +907,7 @@ impl Context { hasher.0.finish(&mut output); let is_valid = self.check_challenge_window(counter) && secure_eq(&output[..CHALLENGE_MAC_SIZE], &response.challenge_mac) - && verify_pow::(&mut hasher.0, &message[p_auth_end..message_size]) + && verify_pow::(hasher.0, &message[p_auth_end..message_size]) && self.update_challenge_window(counter); app.event_log(LogEvent::ReceiveCheckXK1Challenge(is_valid), current_time); if !is_valid { @@ -1008,7 +1011,7 @@ impl Context { ratchet_state = rs; break; } - Err(()) => return Err(ReceiveError::RatchetIoError), + Err(e) => return Err(ReceiveError::RatchetIoError(e)), } } if ratchet_state.is_null() { @@ -1023,7 +1026,7 @@ impl Context { let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); // Noise process pattern2 e token. let noise_e_pattern2_secret = Application::KeyPair::generate(&mut self.0.rng.lock().unwrap()); - noise_pattern2.noise_e = noise_e_pattern2_secret.public_key_bytes().clone(); + noise_pattern2.noise_e = *noise_e_pattern2_secret.public_key_bytes(); let noise_h_ee1pe = mix_hash(sha512, &noise_h_ee1p, &noise_pattern2.noise_e); noise_ck.mix_key(hmac, &noise_pattern2.noise_e); // Noise process pattern2 ee token. @@ -1220,7 +1223,7 @@ impl Context { &mut message[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], ); let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); - let noise_ekem1_secret = pqc_kyber::decapsulate(&noise_pattern2.noise_ekem1, noise_e1_secret.as_ref()).map(|k| Secret(k)); + let noise_ekem1_secret = pqc_kyber::decapsulate(&noise_pattern2.noise_ekem1, noise_e1_secret.as_ref()).map(Secret); if let Some(Ok(noise_ekem1_secret)) = is_auth.then_some(noise_ekem1_secret) { noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); drop(noise_ekem1_secret); @@ -1336,8 +1339,8 @@ impl Context { [&new_ratchet_state, ratchet_to_preserve], current_time, ); - if result.is_err() { - return Err(ReceiveError::RatchetIoError); + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); } let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); @@ -1511,7 +1514,8 @@ impl Context { ); if let Some((responder_disallows_downgrade, application_data)) = responder_disallows_downgrade { let result = app.restore_by_identity(&remote_s_public_key, &application_data, current_time); - if let Ok(true_ratchet_states) = result { + match result { + Ok(true_ratchet_states) => { let mut has_match = false; for rs in &true_ratchet_states { if !rs.is_null() { @@ -1549,10 +1553,11 @@ impl Context { [&new_ratchet_state, &RatchetState::Null], current_time, ); - if result.is_err() { - return Err(ReceiveError::RatchetIoError); + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); } + let mut session_queue = self.0.session_queue.lock().unwrap(); let queue_idx = session_queue.reserve_index(); let session = Arc::new(Session { @@ -1600,8 +1605,8 @@ impl Context { // in use, in which case we have to drop this session like // nothing ever happened. let mut session_map = self.0.session_map.write().unwrap(); - if !session_map.contains_key(&handshake_state.local_key_id) { - session_map.insert(handshake_state.local_key_id, (Arc::downgrade(&session), false)); + if let std::collections::hash_map::Entry::Vacant(e) = session_map.entry(handshake_state.local_key_id) { + e.insert((Arc::downgrade(&session), false)); drop(session_map); let _ = session.send_control(&session.state.read().unwrap(), send_unassociated_reply, PACKET_TYPE_KEY_CONFIRM, &[]); @@ -1614,9 +1619,11 @@ impl Context { // restart the handshake in this case. return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); } - } else { - return Err(ReceiveError::RatchetIoError); } + Err(e) => { + return Err(ReceiveError::RatchetIoError(e)); + } + } } else { if !responder_silently_rejects { send_reject(); @@ -1715,7 +1722,7 @@ impl Context { } } } - return Ok(()); + Ok(()) } /// Update the challenge window, returning true if the challenge is still valid. #[inline(always)] @@ -1807,7 +1814,7 @@ fn initiate_rekey( drop(state); drop(kex_lock); let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_NOISE_KK_PATTERN_1, &message); - return Ok(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); + Ok(current_time.saturating_add(Application::RETRY_INTERVAL_MS)) } fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mut [u8]) -> bool>( context: &Context, @@ -1818,7 +1825,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu counter: u64, fragment: &mut [u8], current_time: i64, -) -> Result, ReceiveError> { +) -> Result, ReceiveError> { let state = session.state.read().unwrap(); let mut c = session.kex_receive_cipher.lock().unwrap(); let message = decrypt_control( @@ -1857,7 +1864,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu _ => (true, false, SessionEvent::Control), }; if try_delete { - let is_ok = if !state.ratchet_states[1].is_null() { + let result = if !state.ratchet_states[1].is_null() { app.save_ratchet_state( &session.remote_static_key, &session.application_data, @@ -1865,24 +1872,22 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu [&state.ratchet_states[0], &RatchetState::Null], current_time, ) - .is_ok() } else { - true + Ok(()) }; - if is_ok { - if let NoiseKKPattern2 { kex_send_key, .. } = &state.outgoing_offer { - session - .kex_send_cipher - .lock() - .unwrap() - .replace(Application::AeadEnc::new(kex_send_key.as_ref())); - } - state.ratchet_states[1] = RatchetState::Null; - state.current_key ^= 1; - state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); - } else { - return Err(ReceiveError::RatchetIoError); + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); } + if let NoiseKKPattern2 { kex_send_key, .. } = &state.outgoing_offer { + session + .kex_send_cipher + .lock() + .unwrap() + .replace(Application::AeadEnc::new(kex_send_key.as_ref())); + } + state.ratchet_states[1] = RatchetState::Null; + state.current_key ^= 1; + state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); } drop(state); drop(kex_lock); @@ -1899,11 +1904,8 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let kex_lock = session.state_machine_lock.lock().unwrap(); let mut state = session.state.write().unwrap(); // Check if we should end any current offers and transition back to Normal state - match &state.outgoing_offer { - KeyConfirm { .. } => { - state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); - } - _ => (), + if let KeyConfirm { .. } = &state.outgoing_offer { + state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); } drop(state); drop(kex_lock); @@ -2007,10 +2009,10 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu [&new_ratchet_state, &state.ratchet_states[0]], current_time, ); - if result.is_err() { + if let Err(e) = result { drop(state); drop(kex_lock); - return Err(ReceiveError::RatchetIoError); + return Err(ReceiveError::RatchetIoError(e)); } let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); // The new "Bob" doesn't know yet if Alice has received the new key, so the @@ -2110,10 +2112,10 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu [&new_ratchet_state, &RatchetState::Null], current_time, ); - if result.is_err() { + if let Err(e) = result { drop(state); drop(kex_lock); - return Err(ReceiveError::RatchetIoError); + return Err(ReceiveError::RatchetIoError(e)); } let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); @@ -2199,16 +2201,13 @@ impl Session { packet, ); send(&mut fragment[..len]); - return Ok(()); + Ok(()) } /// Check whether this session is established. #[inline] pub fn established(&self) -> bool { let state = self.state.read().unwrap(); - return match &state.outgoing_offer { - OfferStateMachine::NoiseXKPattern1or3(_) => false, - _ => true, - }; + !matches!(&state.outgoing_offer, OfferStateMachine::NoiseXKPattern1or3(_)) } /// The static public key of the remote peer. #[inline] @@ -2317,7 +2316,7 @@ impl NoiseXKAliceHandshake { Secret, Secret, ), - OpenError, + OpenError, > { let mut message = [0u8; NoiseXKPattern1::MAX_SIZE]; let sha512 = &mut Application::Hash::new(); @@ -2327,7 +2326,7 @@ impl NoiseXKAliceHandshake { let noise_e_secret = Application::KeyPair::generate(rng); let noise_e1_secret = pqc_kyber::keypair(rng); noise_pattern1.alice_key_id = local_key_id.get().to_ne_bytes(); - noise_pattern1.noise_e = noise_e_secret.public_key_bytes().clone(); + noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); noise_pattern1.noise_e1 = noise_e1_secret.public; // Noise process prologue. let noise_h = mix_hash( @@ -2494,7 +2493,7 @@ fn encrypt_control( let fragment_len = packet.len() + HEADER_SIZE + AES_GCM_TAG_SIZE; c.set_iv(&create_message_nonce(packet_type, counter)); - if packet.len() > 0 { + if !packet.is_empty(){ fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE].copy_from_slice(packet); c.encrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); } @@ -2504,9 +2503,9 @@ fn encrypt_control( (fragment, fragment_len) } #[inline] -fn decrypt_control<'a>(c: &mut impl AesGcmDec, packet_type: u8, counter: u64, fragment: &'a mut [u8]) -> Result<&'a mut [u8], ReceiveError> { +fn decrypt_control<'a, IoError>(c: &mut impl AesGcmDec, packet_type: u8, counter: u64, fragment: &'a mut [u8]) -> Result<&'a mut [u8], ReceiveError> { let fragment_len = fragment.len(); - if fragment_len < CONTROL_PACKET_MIN_SIZE || fragment_len > CONTROL_PACKET_MAX_SIZE { + if (CONTROL_PACKET_MIN_SIZE..=CONTROL_PACKET_MAX_SIZE).contains(&fragment_len) { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } c.set_iv(&create_message_nonce(packet_type, counter)); @@ -2610,7 +2609,7 @@ fn send_with_fragmentation( break; } } - return true; + true } /// Assemble a series of fragments into a buffer and return the length of the assembled packet in @@ -2618,7 +2617,7 @@ fn send_with_fragmentation( /// /// This is also only used for key exchange and control packets. For data packets decryption and /// assembly happen in one pass for better performance. -fn assemble_fragments_into(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result { +fn assemble_fragments_into(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result> { let mut l = 0; for i in 0..fragments.len() { let mut ff = fragments[i].as_ref(); @@ -2632,7 +2631,7 @@ fn assemble_fragments_into(fragments: &[A::IncomingPacketBu d[l..j].copy_from_slice(ff); l = j; } - return Ok(l); + Ok(l) } /// Generate a random local key id that is currently unused. fn generate_key_id( From b4fab13d9a4db041ddbbe1ec004d1449902ebcff Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 14:19:08 -0400 Subject: [PATCH 35/91] fixed typo --- src/zssp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index 0bc0763..d6f1b44 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -870,7 +870,7 @@ impl Context { if session.is_some() || incoming.is_some() { return Err(byzantine_fault!(FaultType::OutOfSequence, false)); } - if (NoiseXKPattern1::MIN_SIZE..=NoiseXKPattern1::MAX_SIZE).contains(&message_size) { + if !(NoiseXKPattern1::MIN_SIZE..=NoiseXKPattern1::MAX_SIZE).contains(&message_size) { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } // The message id must be the first 8 bytes of the gcm tag. @@ -2505,7 +2505,7 @@ fn encrypt_control( #[inline] fn decrypt_control<'a, IoError>(c: &mut impl AesGcmDec, packet_type: u8, counter: u64, fragment: &'a mut [u8]) -> Result<&'a mut [u8], ReceiveError> { let fragment_len = fragment.len(); - if (CONTROL_PACKET_MIN_SIZE..=CONTROL_PACKET_MAX_SIZE).contains(&fragment_len) { + if !(CONTROL_PACKET_MIN_SIZE..=CONTROL_PACKET_MAX_SIZE).contains(&fragment_len) { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } c.set_iv(&create_message_nonce(packet_type, counter)); From c3c66ef49bfc477411c809ea7edfd286de79f9c2 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 14:54:26 -0400 Subject: [PATCH 36/91] updated API --- src/applicationlayer.rs | 47 ++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 4d77d9f..0485f7f 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -125,10 +125,7 @@ pub trait ApplicationLayer: Sized { /// If this function is configured to always return true, it means peers will not be able to /// connect to us unless they had a prior-established ratchet key with us. This is the best way /// for the paranoid to enforce a manual allow-list. - #[allow(unused)] - fn hello_requires_recognized_ratchet(&self, current_time: i64) -> bool { - false - } + fn hello_requires_recognized_ratchet(&self, current_time: i64) -> bool; /// This function is called if we, as Alice, attempted to open a session with Bob using a /// non-empty ratchet key, but Bob does not have this ratchet key and wants to downgrade /// to the zero ratchet key. @@ -144,14 +141,8 @@ pub trait ApplicationLayer: Sized { /// least one party is misconfigured and got their ratchet keys corrupted or lost, or Bob has /// been compromised and is being impersonated. An attacker must at least have Bob's private /// static key to be able to ask Alice to downgrade. - /// - /// If Alice does decide to reconnect without a ratchet key, be sure to generate some warning - /// that something has gone wrong and Bob could not be fully authenticated. - #[allow(unused)] - fn initiator_disallows_downgrade(&self, session: &Arc>, current_time: i64) -> bool { - false - } - /// Lookup a specific ratchet key based on its ratchet fingerprint. + fn initiator_disallows_downgrade(&self, session: &Arc>, current_time: i64) -> bool; + /// Lookup a specific ratchet state based on its ratchet fingerprint. /// This function will be called whenever Alice attempts to connect to us with a non-empty /// ratchet fingerprint. /// @@ -163,23 +154,19 @@ pub trait ApplicationLayer: Sized { /// If `RatchetAction::DowngradeRatchet` is returned we will attempt to convince Alice to downgrade /// to the empty ratchet key, restarting the ratchet chain. /// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped. - #[allow(unused)] - fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE], current_time: i64) -> Result { - Ok(RatchetState::Null) - } - #[allow(unused)] + fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE], current_time: i64) -> Result; + + /// Lookup a specific ratchet state based on the identity of the peer being communicated with. + /// This function will be called whenever Alice attempts to open a session, or Bob attempts + /// to verify Alice's identity. fn restore_by_identity( &self, remote_static_key: &Self::PublicKey, application_data: &Self::Data, current_time: i64, - ) -> Result<[RatchetState; 2], Self::IoError> { - Ok([RatchetState::Null, RatchetState::Null]) - } - /// Atomically save the given ratchet key, fingerprint and number to persistent storage. - /// - /// See the documentation of `SaveAction` for more details on how to save them to storage, - /// and how to handle any pre-existing ratchet keys, fingerprints and numbers. + ) -> Result<[RatchetState; 2], Self::IoError>; + /// Atomically save the given `new_ratchet_states` to persistent storage. + /// `pre_ratchet_states` contains what should be the previous contents of persistent storage. /// /// If this returns `Err(IoError)`, the packet which triggered this function to be called will be /// dropped, and no session state will be mutated, preserving synchronization. The remote peer @@ -191,20 +178,18 @@ pub trait ApplicationLayer: Sized { /// fix is to reset both ratchet keys to empty. /// /// This function may also save state to volatile storage, in which case all peers which connect - /// to us will have to allow downgrade - /// (`initiator_disallows_downgrade` returns false and/or`restore_ratchet` returns `DowngradeRatchet`). + /// to us will have to allow downgrade, i.e. `initiator_disallows_downgrade` returns false + /// and/or `check_accept_session` returns `(Some(true, _), _)`. /// Otherwise, when we restart, we will not be allowed to reconnect. - #[allow(unused)] fn save_ratchet_state( &self, remote_static_key: &Self::PublicKey, application_data: &Self::Data, - previous_ratchet_states: [&RatchetState; 2], + pre_ratchet_states: [&RatchetState; 2], new_ratchet_states: [&RatchetState; 2], current_time: i64, - ) -> Result<(), Self::IoError> { - Ok(()) - } + ) -> Result<(), Self::IoError>; + #[allow(unused)] #[inline] fn event_log(&self, event: LogEvent, current_time: i64) {} From b3d6330dd01bd342a26e3fd60aad1d4a73bc5a4c Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 15:12:54 -0400 Subject: [PATCH 37/91] fixed typo --- src/zssp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zssp.rs b/src/zssp.rs index d6f1b44..aa0a77c 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -1274,7 +1274,7 @@ impl Context { if result.is_none() { ratchet_i = 1; if let Some(key) = state.ratchet_states[1].key() { - chain_len = state.ratchet_states[0].chain_len(); + chain_len = state.ratchet_states[1].chain_len(); result = test_ratchet_key(key); } } From 62a8ff4e4c023a5c8e124c2c3886b53d63c80b8e Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 15:24:29 -0400 Subject: [PATCH 38/91] updated docs --- src/zssp.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/zssp.rs b/src/zssp.rs index aa0a77c..897391b 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -69,7 +69,8 @@ pub enum ReceiveResult<'b, Application: ApplicationLayer> { Session(Arc>, SessionEvent<'b>), /// Packet was a part of a handshake, and while it superficially appeared valid the application /// explicitly rejected it. - /// Relates to callbacks `check_allow_incoming_session` and `check_accept_session`. + /// Relates to callbacks `check_allow_incoming_session`, `hello_requires_recognized_ratchet` + /// and `check_accept_session`. Rejected, } From 5116fcbb78398a2ec1fe374884ae6f71854343e0 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 15:31:50 -0400 Subject: [PATCH 39/91] updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6164906..dc61d4b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -ZeroTier Secure Socket Protocol +ZeroTier Secure Sessions Protocol ====== # Introduction From d085e2ab5fb4b42701c0c2494c662b1d0ce378f6 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 15:46:05 -0400 Subject: [PATCH 40/91] updated readme --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index dc61d4b..39d3988 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ ZeroTier Secure Sessions Protocol ====== -# Introduction +## Introduction ZeroTier Secure Socket Protocol (ZSSP) is a [Noise](http://noiseprotocol.org) protocol implementation using NIST/FIPS/CfSC compliant cryptographic primitives plus post-quantum forward secrecy via [Kyber1024](https://pq-crystals.org/kyber/). It also includes built-in support for fragmentation and defragmentation of large messages with strong resistance against denial of service attacks targeted against the fragmentation protocol. -Specifically ZSSP implements the [Noise XK](http://noiseprotocol.org/noise.html#interactive-handshake-patterns-fundamental) interactive handshake pattern which provides strong forward secrecy not only for data but for the identities of the two participants in the sesssion. The XK pattern was chosen instead of the more popular IK pattern used in popular Noise implementations like Wireguard due to ZeroTier identities being long lived and potentially tied to the real world identity of the user. As a result a Noise pattern providing identity forward secrecy was considered preferable as it offers some level of deniability for recorded traffic even after secrec key compromise. +Specifically ZSSP implements the [Noise XK](http://noiseprotocol.org/noise.html#interactive-handshake-patterns-fundamental) interactive handshake pattern which provides strong forward secrecy not only for data but for the identities of the two participants in the session. The XK pattern was chosen instead of the more popular IK pattern used in popular Noise implementations like Wireguard due to ZeroTier identities being long lived and potentially tied to the real world identity of the user. As a result a Noise pattern providing identity forward secrecy was considered preferable as it offers some level of deniability for recorded traffic even after secret key compromise. Hybrid post-quantum forward secrecy using Kyber1024 is performed alongside Noise with the result being mixed in alongside an optional pre-shared key at the end of session negotiation. @@ -15,9 +15,46 @@ Further information can be found in the ZSSP whitepaper (pending official releas ## Cryptographic Primitives Used - - AES-256-GCM: Authenticated encryption - - SHA512: Used with the KBKDF construction, also used in a proof of work and IP ownership DOS mitigation scheme - - KBKDF: Key mixing, sub-key derivation - - NIST P-384 ECDH: Elliptic curve key exchange during initial handshake and for periodic re-keying during the session - - Kyber1024: Quantum attack resistant lattice-based key exchange during initial handshake - - AES-256: 128-bit PRP for AES-256-GCM and for authenticated encryption of headera to harden fragmentation against DOS (see section on header protection) + - **NIST P-384 ECDH**: Elliptic curve key exchange during initial handshake and for periodic re-keying during the session + - **Kyber1024**: Quantum attack resistant lattice-based key exchange during initial handshake + - **SHA-512**: Used to construct KBKDF, also used in a proof of work and IP ownership DOS mitigation scheme + - **KBKDF**: Key mixing, sub-key derivation + - **AES-256**: 128-bit PRP for AES-256-GCM and for authenticated encryption of header to harden fragmentation against DOS (see section on header protection) + - **AES-256-GCM**: Authenticated encryption + +## Security Properties + +| | Persistent ZSSP | Opportunistic ZSSP| WireGuard | ZeroTier Legacy Transport | +| --- | --- | --- | --- | --- | +|**Construction**|Noise\_XKhfs+psk2|Noise\_XKhfs+psk2|Noise\_IKpsk2|Static Diffie-Helman| +|**Perfect Forward Secrecy**|Yes|Yes|Yes|No| +|**Forward Secret Identity Hiding**|Yes|Yes|No|No| +|**Quantum Forward Secret**|Yes|Yes|No|No| +|**Ratcheted Forward Secrecy**|Yes|Yes|No|No| +|**Silence is a Virtue**|Yes|No|Yes|No| +|**Key-Compromise Impersonation**|Resistant|Resistant|Resistant|Vulnerable| +|**Compromise-and-Impersonate**|Resistant|Detectable|Vulnerable|Vulnerable| +|**Single Key-Compromise MitM**|Resistant|Resistant|Resistant|Vulnerable| +|**Double Key-Compromise MitM**|Resistant|Detectable|Vulnerable|Vulnerable| +|**DOS Mitigation**|Yes|Yes|Yes|No| +|**Supports Fragmentation**|Yes|Yes|No|Yes| +|**FIPS Compliant**|Yes|Yes|No|No| +|**Small Code Footprint**|Yes|Yes|Yes|No| +|**RTT**|2|2|1|1| + +### Definitions + +* **Construction**: The mathematical construction the protocol is based upon. +* **Perfect Forward Secrecy**: An attacker with the static private keys of both party cannot decrypt recordings of messages sent between those parties. +* **Forward Secret Identity Hiding**: An attacker with the static private key of one or more parties cannot determine the identity of everyone they have previously communicated with. +* **Quantum Forward Secret**: A quantum computer powerful enough to break Elliptic-curve cryptography is not sufficient in order to decrypt recordings of messages sent between parties. +* **Ratcheted Forward Secrecy**: In order to break forward secrecy an attacker must record and break every single key exchange two parties perform, in order, starting from the first time they began communicating. Improves secrecy under weak or compromised RNG. +* **Key-Compromise Impersonation**: The attacker has a memory image of a single party, and attempts to create a brand new session with that party, pretending to be someone else. +* **Compromise-and-Impersonate**: The attacker has a memory image of a single party, and attempts to impersonate them on a brand new session with the other party. +* **Single Key-Compromise MitM**: The attacker has a memory image of a single party, and attempts to become a Man-in-the-Middle between them and any other party. +* **Double Key-Compromise MitM**: The attacker has a memory image of both parties, and attempts to become a Man-in-the-Middle between them. +* **Silence is a Virtue**: A server running the protocol can be configured in such a way that it will not respond to an unauthenticated, anonymous or replayed message. +* **Supports Fragmentation**: Transmission data can be fragmented into smaller units to support jumbo-sized data or MTU discovery. +* **FIPS Compliant**: The protocol uses FIPS approved cryptographic algorithms. +* **Small Code Footprint**: The Codebase implementing the protocol can be easily audited by anyone on the internet. +* **RTT**: "Round-Trip-Time" - How many round trips from initiator to responder it takes to establish a session. From bec4fb9976337d23b51055486a968ccd95a297ac Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 14 Jul 2023 15:49:23 -0400 Subject: [PATCH 41/91] updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 39d3988..95f2984 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,11 @@ Further information can be found in the ZSSP whitepaper (pending official releas * **Forward Secret Identity Hiding**: An attacker with the static private key of one or more parties cannot determine the identity of everyone they have previously communicated with. * **Quantum Forward Secret**: A quantum computer powerful enough to break Elliptic-curve cryptography is not sufficient in order to decrypt recordings of messages sent between parties. * **Ratcheted Forward Secrecy**: In order to break forward secrecy an attacker must record and break every single key exchange two parties perform, in order, starting from the first time they began communicating. Improves secrecy under weak or compromised RNG. +* **Silence is a Virtue**: A server running the protocol can be configured in such a way that it will not respond to an unauthenticated, anonymous or replayed message. * **Key-Compromise Impersonation**: The attacker has a memory image of a single party, and attempts to create a brand new session with that party, pretending to be someone else. * **Compromise-and-Impersonate**: The attacker has a memory image of a single party, and attempts to impersonate them on a brand new session with the other party. * **Single Key-Compromise MitM**: The attacker has a memory image of a single party, and attempts to become a Man-in-the-Middle between them and any other party. * **Double Key-Compromise MitM**: The attacker has a memory image of both parties, and attempts to become a Man-in-the-Middle between them. -* **Silence is a Virtue**: A server running the protocol can be configured in such a way that it will not respond to an unauthenticated, anonymous or replayed message. * **Supports Fragmentation**: Transmission data can be fragmented into smaller units to support jumbo-sized data or MTU discovery. * **FIPS Compliant**: The protocol uses FIPS approved cryptographic algorithms. * **Small Code Footprint**: The Codebase implementing the protocol can be easily audited by anyone on the internet. From 7c8ff59408168ff603f527f358a9581a140a48bd Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 19 Jul 2023 14:04:33 -0400 Subject: [PATCH 42/91] fixed typo --- src/log_event.rs | 4 ++-- src/zssp.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/log_event.rs b/src/log_event.rs index 3c033f7..bdc94b5 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -36,7 +36,7 @@ pub enum LogEvent<'a, Application: ApplicationLayer> { ReceiveUncheckedKK2, ReceiveValidKK2(&'a Arc>), ReceiveValidKeyConfirm(&'a Arc>), - ReceiveValidKeyDelete(&'a Arc>), + ReceiveValidAck(&'a Arc>), } impl<'a, Application: ApplicationLayer> std::fmt::Debug for LogEvent<'a, Application> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -68,7 +68,7 @@ impl<'a, Application: ApplicationLayer> std::fmt::Debug for LogEvent<'a, Applica ReceiveUncheckedKK2 => write!(f, "ReceiveUncheckedKK2"), ReceiveValidKK2(_) => write!(f, "ReceiveValidKK2"), ReceiveValidKeyConfirm(_) => write!(f, "ReceiveValidKeyConfirm"), - ReceiveValidKeyDelete(_) => write!(f, "ReceiveValidKeyDelete"), + ReceiveValidAck(_) => write!(f, "ReceiveValidAck"), } } } diff --git a/src/zssp.rs b/src/zssp.rs index 897391b..40a58b1 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -1901,7 +1901,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu } PACKET_TYPE_ACK => { drop(state); - app.event_log(LogEvent::ReceiveValidKeyDelete(&session), current_time); + app.event_log(LogEvent::ReceiveValidAck(&session), current_time); let kex_lock = session.state_machine_lock.lock().unwrap(); let mut state = session.state.write().unwrap(); // Check if we should end any current offers and transition back to Normal state From ce9e64448d57bd7a7d95e68fe4b1afb28affca31 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 19 Jul 2023 17:43:05 -0400 Subject: [PATCH 43/91] updated docs --- src/zssp.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index 40a58b1..e1bed47 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -581,7 +581,9 @@ impl Context { /// * `check_accept_session` - Function to accept sessions after final negotiation. /// The second argument is the identity blob that the remote peer sent us. The application /// must verify this identity is associated with the remote peer's static key. - /// The third argument is true if the remote peer connected to us with a recognized ratchet fingerprint. + /// The third argument is the ratchet chain length, or ratchet count. + /// To prevent desync, if this function returns (Some(_), _), no other open session with the + /// same remote peer must exist. /// * `send_unassociated_reply` - Function to send reply packets directly when no session exists /// * `send_unassociated_mtu` - MTU for unassociated replies /// * `send_to` - Function to get senders for existing sessions, permitting MTU and path lookup @@ -2228,8 +2230,8 @@ impl Session { self.state.read().unwrap().ratchet_states[0].chain_len() } /// Mark a session as expired. This will make it impossible for this session to successfully - /// receive or send data. It is recommended to simply `drop` the session instead, but this can - /// provide some reassurance in complex shared ownership situations. + /// receive or send data or control packets. It is recommended to simply `drop` the session + /// instead, but this can provide some reassurance in complex shared ownership situations. pub fn expire(&self) { if let Some(context) = self.context.upgrade() { self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); From 8468213814014b191e0c35d94af480517b379111 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 19 Jul 2023 18:12:29 -0400 Subject: [PATCH 44/91] fixed minor race condition --- src/zssp.rs | 252 +++++++++++++++++++++++++++------------------------- 1 file changed, 131 insertions(+), 121 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index e1bed47..8896b6f 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -234,6 +234,7 @@ enum NoiseXKAliceHandshakeState { noise_message: [u8; NoiseXKPattern3::MAX_SIZE], noise_message_len: usize, }, + Rejected, } struct SessionKey { @@ -390,6 +391,7 @@ impl Context { Some(&session.header_send_cipher), ); } + NoiseXKAliceHandshakeState::Rejected => {} } } retry_next @@ -501,8 +503,12 @@ impl Context { let mut session_map = self.0.session_map.write().unwrap(); let local_key_id = generate_key_id(&session_map, &mut self.0.rng.lock().unwrap()); // Begin Noise XKhfs+psk2. - let (offer, a2b_header_key, b2a_header_key) = - NoiseXKAliceHandshake::::initialize(local_key_id, &remote_static_key, &ratchet_states, &mut self.0.rng.lock().unwrap())?; + let (offer, a2b_header_key, b2a_header_key) = NoiseXKAliceHandshake::::initialize( + local_key_id, + &remote_static_key, + &ratchet_states, + &mut self.0.rng.lock().unwrap(), + )?; let handshake_state = Box::new(NoiseXKAliceHandshake { next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), @@ -558,9 +564,7 @@ impl Context { Ok(session) } - Err(e) => { - Err(OpenError::RatchetIoError(e)) - } + Err(e) => Err(OpenError::RatchetIoError(e)), } } @@ -681,7 +685,7 @@ impl Context { } // This error can occur naturally if Bob's initial reply to Alice had a // resend that was delayed massively and arrived out of order. - NoiseXKAliceHandshakeState::NoiseXKPattern3 { .. } => return Err(byzantine_fault!(FaultType::OutOfSequence, true)), + _ => return Err(byzantine_fault!(FaultType::OutOfSequence, true)), }, _ => return Err(byzantine_fault!(FaultType::OutOfSequence, false)), }; @@ -1519,114 +1523,114 @@ impl Context { let result = app.restore_by_identity(&remote_s_public_key, &application_data, current_time); match result { Ok(true_ratchet_states) => { - let mut has_match = false; - for rs in &true_ratchet_states { - if !rs.is_null() { - has_match |= &handshake_state.ratchet_state == rs; - } - } - if !has_match { - if !responder_disallows_downgrade && handshake_state.ratchet_state.is_empty() { - // TODO: add some kind of warning callback or signal. - } else { - if !responder_silently_rejects { - send_reject(); + let mut has_match = false; + for rs in &true_ratchet_states { + if !rs.is_null() { + has_match |= &handshake_state.ratchet_state == rs; } + } + if !has_match { + if !responder_disallows_downgrade && handshake_state.ratchet_state.is_empty() { + // TODO: add some kind of warning callback or signal. + } else { + if !responder_silently_rejects { + send_reject(); + } + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + } + + let mut noise_kk_ss = Secret::new(); + if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); } - } + let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); + let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); - let mut noise_kk_ss = Secret::new(); - if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); - let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); + let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); + // We must make sure the ratchet key is saved before we transition. + let new_ratchet_state = + RatchetState::new_nonempty(rk, rf, NonZeroU64::new(handshake_state.ratchet_state.chain_len() + 1).unwrap()); + let result = app.save_ratchet_state( + &remote_s_public_key, + &application_data, + [&true_ratchet_states[0], &true_ratchet_states[1]], + [&new_ratchet_state, &RatchetState::Null], + current_time, + ); + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); + } - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); - // We must make sure the ratchet key is saved before we transition. - let new_ratchet_state = - RatchetState::new_nonempty(rk, rf, NonZeroU64::new(handshake_state.ratchet_state.chain_len() + 1).unwrap()); - let result = app.save_ratchet_state( - &remote_s_public_key, - &application_data, - [&true_ratchet_states[0], &true_ratchet_states[1]], - [&new_ratchet_state, &RatchetState::Null], - current_time, - ); - if let Err(e) = result { + let mut session_queue = self.0.session_queue.lock().unwrap(); + let queue_idx = session_queue.reserve_index(); + let session = Arc::new(Session { + context: Arc::downgrade(&self.0), + queue_idx, + application_data, + remote_static_key: remote_s_public_key, + send_counter: AtomicU64::new(INIT_COUNTER), + session_has_expired: AtomicBool::new(false), + counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), + state_machine_lock: Mutex::new(()), + state: RwLock::new(SessionMutableState { + ratchet_states: [new_ratchet_state.clone(), RatchetState::Null], + cipher_states: [ + Some(SessionKey::new( + hmac, + noise_ck, + handshake_state.local_key_id, + handshake_state.remote_key_id, + INIT_COUNTER, + true, + )), + None, + ], + current_key: 0, + outgoing_offer: KeyConfirm { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), + }, + }), + header_receive_cipher: Application::PrpDec::new(handshake_state.header_receive_key.as_ref()), + header_send_cipher, + kex_send_cipher: Mutex::new(Some(Application::AeadEnc::new(kex_key_b2a.as_ref()))), + kex_receive_cipher: Mutex::new(Some(Application::AeadDec::new(kex_key_a2b.as_ref()))), + noise_kk_ss, + noise_kk_local_init_h, + noise_kk_remote_init_h, + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + was_bob: true, + }); + let timer = Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); + session_queue.push_reserved(queue_idx, Arc::downgrade(&session), timer); + drop(session_queue); + // There is the miniscule possibility this key id is already + // in use, in which case we have to drop this session like + // nothing ever happened. + let mut session_map = self.0.session_map.write().unwrap(); + if let std::collections::hash_map::Entry::Vacant(e) = session_map.entry(handshake_state.local_key_id) { + e.insert((Arc::downgrade(&session), false)); + drop(session_map); + let _ = + session.send_control(&session.state.read().unwrap(), send_unassociated_reply, PACKET_TYPE_KEY_CONFIRM, &[]); + + app.event_log(LogEvent::ReceiveValidXK3(&session.application_data), current_time); + return Ok(ReceiveResult::Session(session, SessionEvent::NewSession)); + } else { + // This can occur if we accidentally generate a key id collision. + // There is an extremely short amount of time during which + // another session can steal this session's id, we'll have to + // restart the handshake in this case. + return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); + } + } + Err(e) => { return Err(ReceiveError::RatchetIoError(e)); } - - - let mut session_queue = self.0.session_queue.lock().unwrap(); - let queue_idx = session_queue.reserve_index(); - let session = Arc::new(Session { - context: Arc::downgrade(&self.0), - queue_idx, - application_data, - remote_static_key: remote_s_public_key, - send_counter: AtomicU64::new(INIT_COUNTER), - session_has_expired: AtomicBool::new(false), - counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - state_machine_lock: Mutex::new(()), - state: RwLock::new(SessionMutableState { - ratchet_states: [new_ratchet_state.clone(), RatchetState::Null], - cipher_states: [ - Some(SessionKey::new( - hmac, - noise_ck, - handshake_state.local_key_id, - handshake_state.remote_key_id, - INIT_COUNTER, - true, - )), - None, - ], - current_key: 0, - outgoing_offer: KeyConfirm { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - }, - }), - header_receive_cipher: Application::PrpDec::new(handshake_state.header_receive_key.as_ref()), - header_send_cipher, - kex_send_cipher: Mutex::new(Some(Application::AeadEnc::new(kex_key_b2a.as_ref()))), - kex_receive_cipher: Mutex::new(Some(Application::AeadDec::new(kex_key_a2b.as_ref()))), - noise_kk_ss, - noise_kk_local_init_h, - noise_kk_remote_init_h, - defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - was_bob: true, - }); - let timer = Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); - session_queue.push_reserved(queue_idx, Arc::downgrade(&session), timer); - drop(session_queue); - // There is the miniscule possibility this key id is already - // in use, in which case we have to drop this session like - // nothing ever happened. - let mut session_map = self.0.session_map.write().unwrap(); - if let std::collections::hash_map::Entry::Vacant(e) = session_map.entry(handshake_state.local_key_id) { - e.insert((Arc::downgrade(&session), false)); - drop(session_map); - let _ = session.send_control(&session.state.read().unwrap(), send_unassociated_reply, PACKET_TYPE_KEY_CONFIRM, &[]); - - app.event_log(LogEvent::ReceiveValidXK3(&session.application_data), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::NewSession)); - } else { - // This can occur if we accidentally generate a key id collision. - // There is an extremely short amount of time during which - // another session can steal this session's id, we'll have to - // restart the handshake in this case. - return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); - } } - Err(e) => { - return Err(ReceiveError::RatchetIoError(e)); - } - } } else { if !responder_silently_rejects { send_reject(); @@ -1829,6 +1833,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu fragment: &mut [u8], current_time: i64, ) -> Result, ReceiveError> { + let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); let mut c = session.kex_receive_cipher.lock().unwrap(); let message = decrypt_control( @@ -1841,17 +1846,25 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu session.update_receive_window(counter); use OfferStateMachine::*; return match packet_type { - PACKET_TYPE_SESSION_REJECTED => match &state.outgoing_offer { - NoiseXKPattern1or3(_) => { + PACKET_TYPE_SESSION_REJECTED => { + if let NoiseXKPattern1or3(_) = &state.outgoing_offer { drop(state); + let mut state = session.state.write().unwrap(); + if let NoiseXKPattern1or3(state) = &mut state.outgoing_offer { + // We have to make sure that a different thread cannot receive + // `PACKET_TYPE_KEY_CONFIRM` at the same time. + state.offer = NoiseXKAliceHandshakeState::Rejected; + } + drop(state); + drop(kex_lock); Ok(ReceiveResult::Session(session, SessionEvent::Rejected)) + } else { + Err(byzantine_fault!(FaultType::OutOfSequence, false)) } - _ => Err(byzantine_fault!(FaultType::OutOfSequence, false)), - }, + } PACKET_TYPE_KEY_CONFIRM => { drop(state); app.event_log(LogEvent::ReceiveValidKeyConfirm(&session), current_time); - let kex_lock = session.state_machine_lock.lock().unwrap(); let mut state = session.state.write().unwrap(); // We only want to stop sending NoiseKKPattern2 offers when the latest derived // key is confirmed. And we only want to do that once. @@ -1904,7 +1917,6 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu PACKET_TYPE_ACK => { drop(state); app.event_log(LogEvent::ReceiveValidAck(&session), current_time); - let kex_lock = session.state_machine_lock.lock().unwrap(); let mut state = session.state.write().unwrap(); // Check if we should end any current offers and transition back to Normal state if let KeyConfirm { .. } = &state.outgoing_offer { @@ -1918,10 +1930,6 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu app.event_log(LogEvent::ReceiveUncheckedKK1, current_time); let message = &mut message[..NoiseKKPattern1or2::SIZE]; let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - - drop(state); - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); // We need the following operation to be atomic with the change of offer type let (should_rekey_as_bob, chosen_id) = match &state.outgoing_offer { // Check rekey rate limits. @@ -2072,9 +2080,6 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let message = &mut message[..NoiseKKPattern1or2::SIZE]; let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - drop(state); - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); if let NoiseKKPattern1 { new_key_id, noise_e_secret, noise_ck, noise_h_pskep, .. } = &state.outgoing_offer { // Noise process pattern2 e token. let mut noise_ee = Secret::new(); @@ -2496,7 +2501,7 @@ fn encrypt_control( let fragment_len = packet.len() + HEADER_SIZE + AES_GCM_TAG_SIZE; c.set_iv(&create_message_nonce(packet_type, counter)); - if !packet.is_empty(){ + if !packet.is_empty() { fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE].copy_from_slice(packet); c.encrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); } @@ -2506,7 +2511,12 @@ fn encrypt_control( (fragment, fragment_len) } #[inline] -fn decrypt_control<'a, IoError>(c: &mut impl AesGcmDec, packet_type: u8, counter: u64, fragment: &'a mut [u8]) -> Result<&'a mut [u8], ReceiveError> { +fn decrypt_control<'a, IoError>( + c: &mut impl AesGcmDec, + packet_type: u8, + counter: u64, + fragment: &'a mut [u8], +) -> Result<&'a mut [u8], ReceiveError> { let fragment_len = fragment.len(); if !(CONTROL_PACKET_MIN_SIZE..=CONTROL_PACKET_MAX_SIZE).contains(&fragment_len) { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); From 712fb8b2de0d40a65a2c2e49b34c0252abd8e3cd Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 19 Jul 2023 19:44:41 -0400 Subject: [PATCH 45/91] improved multithreading --- src/zssp.rs | 53 +++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index 8896b6f..8375e48 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -192,6 +192,7 @@ enum OfferStateMachine { next_retry_time: AtomicI64, timeout: i64, }, // -> Normal + Null, } pub(crate) struct NoiseXKBobHandshakeState { @@ -234,7 +235,6 @@ enum NoiseXKAliceHandshakeState { noise_message: [u8; NoiseXKPattern3::MAX_SIZE], noise_message_len: usize, }, - Rejected, } struct SessionKey { @@ -352,15 +352,13 @@ impl Context { if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { if handshake_state.timeout <= current_time { app.event_log(LogEvent::ServiceXKTimeout(&session), current_time); - if !handshake_state.reinitialize( + handshake_state.reinitialize( &session, &ratchet_state, &mut self.0.session_map.write().unwrap(), &mut self.0.rng.lock().unwrap(), current_time, - ) { - session.expire_inner(&self.0, &mut session_queue); - } + ); } } } else if let Some((mut send, mut mtu)) = send_to(&session) { @@ -391,7 +389,6 @@ impl Context { Some(&session.header_send_cipher), ); } - NoiseXKAliceHandshakeState::Rejected => {} } } retry_next @@ -404,6 +401,7 @@ impl Context { if *timeout <= current_time { app.event_log(LogEvent::ServiceKKTimeout(&session), current_time); next_retry_time.store(i64::MAX, Ordering::Relaxed); + drop(state); session.expire_inner(&self.0, &mut session_queue); } else { let packet_type = if let NoiseKKPattern1 { .. } = &state.outgoing_offer { @@ -427,6 +425,7 @@ impl Context { if *timeout <= current_time { app.event_log(LogEvent::ServiceKeyConfirmTimeout(&session), current_time); next_retry_time.store(i64::MAX, Ordering::Relaxed); + drop(state); session.expire_inner(&self.0, &mut session_queue); } else { app.event_log(LogEvent::ServiceKeyConfirmResend(&session), current_time); @@ -437,6 +436,7 @@ impl Context { retry_next } } + Null => retry_next, }; session_queue.change_priority(queue_idx, Reverse(next_timer)); } @@ -1850,16 +1850,13 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu if let NoiseXKPattern1or3(_) = &state.outgoing_offer { drop(state); let mut state = session.state.write().unwrap(); - if let NoiseXKPattern1or3(state) = &mut state.outgoing_offer { - // We have to make sure that a different thread cannot receive - // `PACKET_TYPE_KEY_CONFIRM` at the same time. - state.offer = NoiseXKAliceHandshakeState::Rejected; - } + state.outgoing_offer = OfferStateMachine::Null; drop(state); drop(kex_lock); Ok(ReceiveResult::Session(session, SessionEvent::Rejected)) } else { - Err(byzantine_fault!(FaultType::OutOfSequence, false)) + // This can occur naturally because of control packet resends. + Err(byzantine_fault!(FaultType::OutOfSequence, true)) } } PACKET_TYPE_KEY_CONFIRM => { @@ -1877,6 +1874,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu (false, false, SessionEvent::Control) } } + Null => (false, false, SessionEvent::Control), _ => (true, false, SessionEvent::Control), }; if try_delete { @@ -1915,16 +1913,19 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu Ok(ReceiveResult::Session(session, ret)) } PACKET_TYPE_ACK => { - drop(state); - app.event_log(LogEvent::ReceiveValidAck(&session), current_time); - let mut state = session.state.write().unwrap(); - // Check if we should end any current offers and transition back to Normal state if let KeyConfirm { .. } = &state.outgoing_offer { + drop(state); + app.event_log(LogEvent::ReceiveValidAck(&session), current_time); + let mut state = session.state.write().unwrap(); + // Check if we should end any current offers and transition back to Normal state state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); + drop(kex_lock); + drop(state); + Ok(ReceiveResult::Session(session, SessionEvent::Control)) + } else { + // This can occur naturally because of control packet resends. + Err(byzantine_fault!(FaultType::OutOfSequence, true)) } - drop(state); - drop(kex_lock); - Ok(ReceiveResult::Session(session, SessionEvent::Control)) } PACKET_TYPE_NOISE_KK_PATTERN_1 => { app.event_log(LogEvent::ReceiveUncheckedKK1, current_time); @@ -2215,7 +2216,7 @@ impl Session { #[inline] pub fn established(&self) -> bool { let state = self.state.read().unwrap(); - !matches!(&state.outgoing_offer, OfferStateMachine::NoiseXKPattern1or3(_)) + !matches!(&state.outgoing_offer, OfferStateMachine::NoiseXKPattern1or3(_) | OfferStateMachine::Null) } /// The static public key of the remote peer. #[inline] @@ -2251,7 +2252,7 @@ impl Session { session_queue.remove(self.queue_idx); self.session_has_expired.store(true, Ordering::Relaxed); let _kex_lock = self.state_machine_lock.lock().unwrap(); - let state = self.state.read().unwrap(); + let mut state = self.state.write().unwrap(); let mut session_map = context.session_map.write().unwrap(); for key in &state.cipher_states { if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { @@ -2259,12 +2260,12 @@ impl Session { } } use OfferStateMachine::*; - let id = match &state.outgoing_offer { - NoiseXKPattern1or3(handshake_state) => handshake_state.local_key_id, - NoiseKKPattern1 { new_key_id, .. } => *new_key_id, - _ => return, + match &state.outgoing_offer { + NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), + NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), + _ => None, }; - session_map.remove(&id); + state.outgoing_offer = OfferStateMachine::Null; } /// Get the next outgoing counter value. From 20736ee1088238b1e55ce015e7fb9317a94e29f7 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Sat, 22 Jul 2023 11:50:44 -0400 Subject: [PATCH 46/91] made was_bob pub --- src/zssp.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/zssp.rs b/src/zssp.rs index 8375e48..366307f 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -117,6 +117,8 @@ pub enum IncomingSessionAction { pub struct Session { /// An arbitrary application defined object associated with each session. pub application_data: Application::Data, + /// Is true if the local peer acted as Bob, the responder in the initial key exchange. + pub was_bob: bool, /// The receive context associated with this session, /// only this context can receive messages from the remote peer. context: Weak>, @@ -145,7 +147,6 @@ pub struct Session { noise_kk_ss: Secret, noise_kk_local_init_h: [u8; NOISE_HASHLEN], noise_kk_remote_init_h: [u8; NOISE_HASHLEN], - was_bob: bool, } /// `AesGcm` is not threadsafe, but it is threadsafe when inside a `Mutex`. unsafe impl Send for Session {} From a7f814e15dab137d800187dcdcbb5e001236775b Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 10:37:40 -0400 Subject: [PATCH 47/91] fixed an old test --- src/frag_cache.rs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/frag_cache.rs b/src/frag_cache.rs index ebb428e..62ddfc0 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -234,9 +234,20 @@ impl Drop for UnassociatedFragCache { } } -/* #[test] fn test_cache() { + use std::sync::Mutex; + fn xorshift64_random() -> u64 { + static XORSHIFT64_STATE: Mutex = Mutex::new(12); + let mut x = XORSHIFT64_STATE.lock().unwrap(); + *x ^= x.wrapping_shr(12); + *x ^= x.wrapping_shl(25); + *x ^= x.wrapping_shr(27); + let r = *x; + drop(x); + r.wrapping_mul(0x2545F4914F6CDD1Du64) + } + let mut cache = UnassociatedFragCache::new(); let mut assembled = Assembled::new(); @@ -245,8 +256,8 @@ fn test_cache() { let mut in_progress_fragments = 0; // A basic fuzzer for testing the cache. for i in 0..5000u32 { - let fragment_count = (random::xorshift64_random() as usize % MAX_FRAGMENTS) + 1; - let r = random::xorshift64_random() as u8; + let fragment_count = (xorshift64_random() as usize % MAX_FRAGMENTS) + 1; + let r = xorshift64_random() as u8; if r & 1 == 0 { let mut packet = Vec::new(); for j in 0..fragment_count { @@ -256,7 +267,7 @@ fn test_cache() { in_progress.push((i, fragment_count as u8, packet)); } else { assembled.empty(); - let drop = random::xorshift64_random() as usize % (2 * fragment_count); + let drop = xorshift64_random() as usize % (2 * fragment_count); for j in 0..fragment_count { if drop != j { let fragment = vec![0, 1, 2, 3, 4, 5, 6, r]; @@ -279,11 +290,11 @@ fn test_cache() { } if r > 200 { if in_progress.len() > 0 { - let to_remain = (random::xorshift64_random() as usize % in_progress_fragments) + 16; + let to_remain = (xorshift64_random() as usize % in_progress_fragments) + 16; while in_progress_fragments > to_remain { - let (id, fragment_count, mut packet) = in_progress.swap_remove(random::xorshift64_random() as usize % in_progress.len()); - for _ in 0..((random::xorshift64_random() as usize % packet.len()) + 1) { - let (no, fragment) = packet.swap_remove(random::xorshift64_random() as usize % packet.len()); + let (id, fragment_count, mut packet) = in_progress.swap_remove(xorshift64_random() as usize % in_progress.len()); + for _ in 0..((xorshift64_random() as usize % packet.len()) + 1) { + let (no, fragment) = packet.swap_remove(xorshift64_random() as usize % packet.len()); assembled.empty(); let mut nonce = [0; 10]; @@ -304,4 +315,3 @@ fn test_cache() { } } } - */ From 0ea6935b6e715c0dcf8e59e6bc9ec6d020ebf9a4 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 11:33:27 -0400 Subject: [PATCH 48/91] switched to try_into syntax --- src/zssp.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index 366307f..77b4f9f 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -628,8 +628,7 @@ impl Context { let mut assembled_packet = Assembled::new(); // needs to outlive the block below let mut incoming = None; let (session, packet_type, fragments) = { - let mut local_key_id = [0u8; SESSION_ID_SIZE]; - local_key_id.copy_from_slice(&incoming_physical_packet[0..SESSION_ID_SIZE]); + let local_key_id = incoming_physical_packet[0..SESSION_ID_SIZE].try_into().unwrap(); // `from_ne_bytes` because this id was generated locally. if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(local_key_id)) { let session_map = self.0.session_map.read().unwrap(); @@ -902,9 +901,7 @@ impl Context { IncomingSessionAction::Allow => {} IncomingSessionAction::Challenge => { let response: &ChallengeResponse = byte_array_as_proto_buffer(&message[p_auth_end..message_size]); - let mut counter = 0u64.to_ne_bytes(); - counter.copy_from_slice(&response.challenge_counter); - let counter = u64::from_be_bytes(counter); + let counter = u64::from_be_bytes(response.challenge_counter.try_into().unwrap()); sha512.reset(); let mut hasher = ShaHasher(sha512); @@ -2572,10 +2569,8 @@ fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] /// returns `(fragment_count, fragment_no, packet_type, counter, header_nonce)`. #[inline(always)] fn parse_packet_header(packet: &[u8]) -> (u8, u8, u8, u64, [u8; 10]) { - let mut header_nonce = [0; 10]; - let mut counter = 0u64.to_ne_bytes(); - header_nonce.copy_from_slice(&packet[6..16]); - counter.copy_from_slice(&packet[8..16]); + let header_nonce = packet[6..16].try_into().unwrap(); + let counter = packet[8..16].try_into().unwrap(); // We intentionally ignore the version number for future revisions. (packet[4], packet[5], packet[7], u64::from_be_bytes(counter), header_nonce) } From e137ecc209ff254c08276abc36edfeb2a80904a6 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 11:34:19 -0400 Subject: [PATCH 49/91] added volatile write destruction --- src/crypto/secret.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/crypto/secret.rs b/src/crypto/secret.rs index 60399b3..461470f 100644 --- a/src/crypto/secret.rs +++ b/src/crypto/secret.rs @@ -1,5 +1,5 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. -use std::convert::TryInto; +use std::{convert::TryInto, ptr::write_volatile}; /// Constant time byte slice equality. #[inline] @@ -88,14 +88,18 @@ impl Secret { impl Drop for Secret { fn drop(&mut self) { - self.0.fill(0); + unsafe { + for v in self.0.iter_mut() { + write_volatile(v, 0u8); + } + } } } impl Default for Secret { #[inline(always)] fn default() -> Self { - Self([0_u8; L]) + Self([0u8; L]) } } From 4a038f4e2c5ae2bf17d8b85704bf388f9e1d0bbb Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 11:34:29 -0400 Subject: [PATCH 50/91] added docs --- src/crypto/aes_gcm.rs | 16 ++++++++++++++++ src/crypto/p384.rs | 16 +++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/crypto/aes_gcm.rs b/src/crypto/aes_gcm.rs index 250c587..303280e 100644 --- a/src/crypto/aes_gcm.rs +++ b/src/crypto/aes_gcm.rs @@ -4,6 +4,14 @@ pub const AES_GCM_TAG_SIZE: usize = 16; pub const AES_GCM_IV_SIZE: usize = 12; pub const AES_GCM_KEY_SIZE: usize = super::aes::AES_256_KEY_SIZE; +/// The order of calls to this trait is always +/// `set_iv` -> `set_aad` -> `encrypt` -> `finish_encrypt`. +/// `set_aad` and `encrypt` may not always be called. `encrypt_in_place` may be called instead of +/// `encrypt`. `encrypt` may be called multiple times, it should start encryption of the input at the point in +/// the keystream where encryption previously ended. +/// An instance of this trait may be reused multiple times, each reuse should use the same key. +/// If it is reused, `set_iv` will always be the very next call after `finish_encrypt`. +/// /// Implementations of this trait does not have to be Send + Sync, /// but if it is wrapped in a `Mutex` it must satisfy the requirements of Send + Sync. pub trait AesGcmEnc { @@ -20,6 +28,14 @@ pub trait AesGcmEnc { fn finish_encrypt(&mut self, output: &mut [u8; AES_GCM_TAG_SIZE]); } +/// The order of calls to this trait is always +/// `set_iv` -> `set_aad` -> `decrypt` -> `finish_decrypt`. +/// `set_aad` and `decrypt` may not always be called. `decrypt_in_place` may be called instead of +/// `decrypt`. `decrypt` may be called multiple times, it should start decryption of the input at the point in +/// the keystream where decryption previously ended. +/// An instance of this trait may be reused multiple times, each reuse should use the same key. +/// If it is reused, `set_iv` will always be the very next call after `finish_decrypt`. +/// /// Implementations of this trait does not have to be Send + Sync, /// but if it is wrapped in a `Mutex` it must satisfy the requirements of Send + Sync. pub trait AesGcmDec { diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs index 2a14067..f524972 100644 --- a/src/crypto/p384.rs +++ b/src/crypto/p384.rs @@ -8,9 +8,14 @@ pub const P384_ECDH_SHARED_SECRET_SIZE: usize = 48; /// A NIST P-384 ECDH/ECDSA public key. pub trait P384PublicKey: Sized + Send + Sync { /// Create a p384 public key from raw bytes. + /// + /// **CRITICAL**: This function must return `None` if the input `raw_key` is not on the P384 curve, + /// or if it breaks the P384 standard in any other way. fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option; /// Get the raw bytes that uniquely define the public key. + /// + /// This must output the standard 49 byte NIST encoding of P384 public keys. fn as_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE]; } @@ -24,8 +29,17 @@ pub trait P384KeyPair: Send + Sync { fn generate(rng: &mut Self::Rng) -> Self; /// Get the raw bytes that uniquely define the public key. + /// + /// This must output the standard 49 byte NIST encoding of P384 public keys. fn public_key_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE]; - /// Perform ECDH key agreement, returning the raw (un-hashed!) ECDH secret. + /// Perform ECDH key agreement, writing the raw (un-hashed!) ECDH secret to `output`. + /// + /// **CRITICAL**: This function must return `false` if key agreement between this private key and + /// the input `other_public` key would result in an invalid, non-standard or predictable ECDH secret. + /// Please refer to the NIST spec for P384 ECDH key agreement, or better yet use a peer reviewed + /// library that has already implemented this correctly. + /// + /// If this function returns `false` then the contents of `output` will be discarded. fn agree(&self, other_public: &Self::PublicKey, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool; } From 8b1105f36f14a3fc9331b57223651639c581673d Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 12:00:41 -0400 Subject: [PATCH 51/91] added docs --- src/crypto/aes.rs | 18 ++++++++++++++++++ src/crypto/aes_gcm.rs | 12 ++++++++---- src/crypto/p384.rs | 2 ++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index 842e1ed..637d009 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -3,18 +3,36 @@ pub const AES_256_BLOCK_SIZE: usize = 16; pub const AES_256_KEY_SIZE: usize = 32; +/// A trait for encrypting individual blocks of plaintext using AES-256. +/// It is used for header authentication, for which we have a standard model proof that our +/// algorithm is secure. +/// +/// Instances must securely delete their keys when dropped or reset. pub trait AesEnc: Send + Sync { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self; + /// Change the encryption key to `key` so that all future encryption is performed with it. + /// This function is very rarely called so it does not have to be particularly efficient. fn reset(&self, key: &[u8; AES_256_KEY_SIZE]); + /// Decrypt the given `block` of plaintext directly using the AES block cipher + /// (i.e. AES-256 in zero-padding ECB mode). + /// The ciphertext should be written directly back out to `block`. fn encrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]); } +/// A trait for decrypting individual blocks of plaintext using AES-256. +/// +/// Instances must securely delete their keys when dropped or reset. pub trait AesDec: Send + Sync { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self; + /// Change the decryption key to `key` so that all future decryption is performed with it. + /// This function is very rarely called so it does not have to be particularly efficient. fn reset(&self, key: &[u8; AES_256_KEY_SIZE]); + /// Decrypt the given `block` of ciphertext directly using the AES 256 block cipher + /// (i.e. AES-256 in zero-padding ECB mode). + /// The plaintext should be written directly back out to `block`. fn decrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]); } diff --git a/src/crypto/aes_gcm.rs b/src/crypto/aes_gcm.rs index 303280e..c8328ed 100644 --- a/src/crypto/aes_gcm.rs +++ b/src/crypto/aes_gcm.rs @@ -12,8 +12,10 @@ pub const AES_GCM_KEY_SIZE: usize = super::aes::AES_256_KEY_SIZE; /// An instance of this trait may be reused multiple times, each reuse should use the same key. /// If it is reused, `set_iv` will always be the very next call after `finish_encrypt`. /// -/// Implementations of this trait does not have to be Send + Sync, -/// but if it is wrapped in a `Mutex` it must satisfy the requirements of Send + Sync. +/// Implementations of this trait do not have to be Send + Sync, +/// but if instances are wrapped in a `Mutex` they must satisfy the requirements of Send + Sync. +/// +/// Instances must securely delete their keys when dropped. pub trait AesGcmEnc { fn new(key: &[u8; AES_GCM_KEY_SIZE]) -> Self; @@ -36,8 +38,10 @@ pub trait AesGcmEnc { /// An instance of this trait may be reused multiple times, each reuse should use the same key. /// If it is reused, `set_iv` will always be the very next call after `finish_decrypt`. /// -/// Implementations of this trait does not have to be Send + Sync, -/// but if it is wrapped in a `Mutex` it must satisfy the requirements of Send + Sync. +/// Implementations of this trait do not have to be Send + Sync, +/// but if instances are wrapped in a `Mutex` they must satisfy the requirements of Send + Sync. +/// +/// Instances must securely delete their keys when dropped. pub trait AesGcmDec { fn new(key: &[u8; AES_GCM_KEY_SIZE]) -> Self; diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs index f524972..d340356 100644 --- a/src/crypto/p384.rs +++ b/src/crypto/p384.rs @@ -20,6 +20,8 @@ pub trait P384PublicKey: Sized + Send + Sync { } /// A NIST P-384 ECDH/ECDSA public/private key pair. +/// +/// Instances must securely delete the private key when dropped. pub trait P384KeyPair: Send + Sync { type PublicKey: P384PublicKey; type Rng: RngCore + CryptoRng; From 4e7e33b468eeb0b42b77f256935badc170533934 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 12:23:53 -0400 Subject: [PATCH 52/91] removed inline --- src/applicationlayer.rs | 1 - src/fragged.rs | 6 ------ src/indexed_heap.rs | 1 - src/ratchet_state.rs | 8 -------- src/symmetric_state.rs | 5 ----- src/zssp.rs | 29 ----------------------------- 6 files changed, 50 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 0485f7f..ff0a957 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -191,6 +191,5 @@ pub trait ApplicationLayer: Sized { ) -> Result<(), Self::IoError>; #[allow(unused)] - #[inline] fn event_log(&self, event: LogEvent, current_time: i64) {} } diff --git a/src/fragged.rs b/src/fragged.rs index a9eb2cc..77ee095 100644 --- a/src/fragged.rs +++ b/src/fragged.rs @@ -30,13 +30,11 @@ impl Assembled { } } impl AsRef<[Fragment]> for Assembled { - #[inline(always)] fn as_ref(&self) -> &[Fragment] { unsafe { &*slice_from_raw_parts(self.0.as_ptr().cast::(), self.1) } } } impl Drop for Assembled { - #[inline(always)] fn drop(&mut self) { self.empty() } @@ -52,7 +50,6 @@ pub struct Fragged { } impl Fragged { - #[inline(always)] pub fn new() -> Self { debug_assert!(MAX_FRAGMENTS <= 64); unsafe { zeroed() } @@ -64,7 +61,6 @@ impl Fragged { /// be reused to assemble another packet. /// /// Will check that aad is the same for all fragments. - #[inline] pub(crate) fn assemble( &mut self, nonce: [u8; 10], @@ -107,7 +103,6 @@ impl Fragged { } /// Drops any remaining fragments and resets this object. - #[inline(always)] pub fn drop_in_place(&mut self) { if needs_drop::() { let mut have = self.have; @@ -129,7 +124,6 @@ impl Fragged { } impl Drop for Fragged { - #[inline(always)] fn drop(&mut self) { self.drop_in_place(); } diff --git a/src/indexed_heap.rs b/src/indexed_heap.rs index 8a0547c..70085dc 100644 --- a/src/indexed_heap.rs +++ b/src/indexed_heap.rs @@ -42,7 +42,6 @@ impl IndexedBinaryHeap { .first_mut() .map(|entry| (&mut entry.0, &entry.1, BinaryHeapIndex(entry.2, self.map[entry.2].1))) } - #[inline] fn swap(&mut self, a: usize, b: usize) { self.map[self.data[a].2].0 = b; self.map[self.data[b].2].0 = a; diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index fddbc57..273a358 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -19,38 +19,30 @@ pub enum RatchetState { } use RatchetState::*; impl RatchetState { - #[inline] pub fn new_nonempty(key: Secret, fingerprint: Secret, chain_len: NonZeroU64) -> Self { NonEmpty(NonEmptyRatchetState { key, fingerprint, chain_len }) } - #[inline] pub fn new_initial_states() -> [RatchetState; 2] { [RatchetState::Empty, RatchetState::Null] } - #[inline] pub fn is_null(&self) -> bool { matches!(self, Null) } - #[inline] pub fn is_empty(&self) -> bool { matches!(self, Empty) } - #[inline] pub fn nonempty(&self) -> Option<&NonEmptyRatchetState> { match self { NonEmpty(rs) => Some(rs), _ => None, } } - #[inline] pub fn chain_len(&self) -> u64 { self.nonempty().map_or(0, |rs| rs.chain_len.get()) } - #[inline] pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { self.nonempty().map(|rs| rs.fingerprint.as_ref()) } - #[inline] pub fn key(&self) -> Option<&[u8; RATCHET_SIZE]> { const ZERO_KEY: [u8; RATCHET_SIZE] = [0u8; RATCHET_SIZE]; match self { diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 6f6ac6f..e6e5e52 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -33,7 +33,6 @@ impl SymmetricState { // `InitializeKey` would be completely pointless. } /// Corresponds to Noise `MixKey` followed by `InitializeKey`. - #[inline(always)] pub(crate) fn mix_key_initialize_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> Secret { let mut next_ck = Secret::new(); let mut temp_k = [0u8; NOISE_HASHLEN]; @@ -83,7 +82,6 @@ impl SymmetricState { /// is forward secrect and is cryptographically independent from all other produced keys. /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. - #[inline(always)] pub(crate) fn get_ask2( &self, hm: &mut impl HmacSha512, @@ -99,7 +97,6 @@ impl SymmetricState { ) } /// Corresponds to Noise `Split`. - #[inline(always)] pub(crate) fn split(self, hm: &mut impl HmacSha512) -> (Secret, Secret) { let mut temp_k1 = [0u8; NOISE_HASHLEN]; let mut temp_k2 = [0u8; NOISE_HASHLEN]; @@ -111,7 +108,6 @@ impl SymmetricState { Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), ) } - #[inline(always)] fn label(&self) -> [u8; 4] { [b'Z', b'S', b'S', self.token_counter] } @@ -126,7 +122,6 @@ impl SymmetricState { /// * L = `num_outputs*512u16` /// We have intentionally made every input small and fixed size to avoid unnecessary complexity /// and data representation ambiguity. - #[inline(always)] fn kbkdf( &self, hm: &mut impl HmacSha512, diff --git a/src/zssp.rs b/src/zssp.rs index 77b4f9f..98f9a3f 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -292,7 +292,6 @@ impl Context { /// with remote peers (although both of these properties would help reliability slightly). /// Used to determine if any current handshakes should be resent or timed-out, or if a session /// should rekey. - #[inline] pub fn service bool>( &self, app: &Application, @@ -471,7 +470,6 @@ impl Context { /// for the upper protocol to authenticate and approve of Alice's identity. /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced /// with the remote peer. Used to determine when this offer should be resent. - #[inline] pub fn open( &self, app: &Application, @@ -600,7 +598,6 @@ impl Context { /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced /// with the remote peer. Used to check the state of local offers we may currently have or want /// to put in-flight. - #[inline] pub fn receive<'a, SendFn: FnMut(&mut [u8]) -> bool>( &self, app: &Application, @@ -1648,7 +1645,6 @@ impl Context { /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a /// slice of `data` /// * `current_time` - Current time in milliseconds - #[inline] pub fn send_empty(&self, session: &Arc>, send: impl FnMut(&mut [u8]) -> bool, current_time: i64) -> Result<(), SendError> { self.send(session, send, &mut [0u8; MIN_TRANSPORT_MTU], &[], current_time) } @@ -1660,7 +1656,6 @@ impl Context { /// * `mtu_sized_buffer` - A writable work buffer whose size equals the MTU /// * `data` - Data to send /// * `current_time` - Current time in milliseconds - #[inline] pub fn send( &self, session: &Arc>, @@ -1730,7 +1725,6 @@ impl Context { Ok(()) } /// Update the challenge window, returning true if the challenge is still valid. - #[inline(always)] fn check_challenge_window(&self, counter: u64) -> bool { let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; let counter = counter.wrapping_add(1); @@ -1738,7 +1732,6 @@ impl Context { prev_counter < counter } /// Update the challenge window, returning true if the challenge is still valid. - #[inline(always)] fn update_challenge_window(&self, counter: u64) -> bool { let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; let counter = counter.wrapping_add(1); @@ -2188,7 +2181,6 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu impl Session { /// This can only fail with `MaxKeyLifetimeExceeded` or `SessionNotEstablished`. - #[inline] fn send_control( &self, state: &SessionMutableState, @@ -2211,25 +2203,21 @@ impl Session { Ok(()) } /// Check whether this session is established. - #[inline] pub fn established(&self) -> bool { let state = self.state.read().unwrap(); !matches!(&state.outgoing_offer, OfferStateMachine::NoiseXKPattern1or3(_) | OfferStateMachine::Null) } /// The static public key of the remote peer. - #[inline] pub fn remote_s_public_key(&self) -> &Application::PublicKey { &self.remote_static_key } /// The current ratchet state of this session. /// The returned values are sensitive and should be securely erased before being dropped. - #[inline] pub fn ratchet_states(&self) -> [RatchetState; 2] { let state = self.state.read().unwrap(); state.ratchet_states.clone() } /// The current ratchet count of this session. - #[inline] pub fn ratchet_count(&self) -> u64 { self.state.read().unwrap().ratchet_states[0].chain_len() } @@ -2267,7 +2255,6 @@ impl Session { } /// Get the next outgoing counter value. - #[inline(always)] fn get_next_outgoing_counter(&self) -> Result { if self.session_has_expired.load(Ordering::Relaxed) { Err(SendError::SessionExpired) @@ -2283,7 +2270,6 @@ impl Session { } } /// Check the receive window without mutating state. - #[inline(always)] fn check_receive_window(&self, counter: u64) -> bool { let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; let counter = counter.wrapping_add(1); @@ -2292,7 +2278,6 @@ impl Session { } /// Update the receive window, returning true if the packet is still valid. /// This should only be called after the packet is authenticated. - #[inline(always)] fn update_receive_window(&self, counter: u64) -> bool { let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; let counter = counter.wrapping_add(1); @@ -2311,7 +2296,6 @@ impl Drop for Session { impl NoiseXKAliceHandshake { /// Can only fail with `OpenError::InvalidPublicKey` because of remote_s_public_key. /// Corresponds to Noise `Initialize`. - #[inline] fn initialize( local_key_id: NonZeroU32, remote_s_public_key: &Application::PublicKey, @@ -2446,7 +2430,6 @@ fn process_timer(timer: &AtomicI64, wait_time: i64, current_time: i64) -> Option } /// Corresponds to Noise `EncryptAndHash`. -#[inline] fn encrypt_and_hash( sha512: &mut Application::Hash, noise_k: &Secret, @@ -2467,7 +2450,6 @@ fn encrypt_and_hash( mix_hash(sha512, noise_h, message) } /// Corresponds to Noise `DecryptAndHash`. -#[inline] fn decrypt_and_hash( sha512: &mut Application::Hash, noise_k: &Secret, @@ -2487,7 +2469,6 @@ fn decrypt_and_hash( (gcm.finish_decrypt((&message[auth_start..]).try_into().unwrap()), noise_h_c) } /// Encrypt a standardized control packet. -#[inline] fn encrypt_control( c: &mut impl AesGcmEnc, header_cipher: &impl AesEnc, @@ -2509,7 +2490,6 @@ fn encrypt_control( header_cipher.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); (fragment, fragment_len) } -#[inline] fn decrypt_control<'a, IoError>( c: &mut impl AesGcmDec, packet_type: u8, @@ -2530,7 +2510,6 @@ fn decrypt_control<'a, IoError>( Ok(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]) } -#[inline(always)] fn set_packet_header(packet: &mut [u8], fragment_count: u8, fragment_no: u8, packet_type: u8, remote_key_id: u32, counter_or_id: u64) { debug_assert!(packet.len() >= MIN_PACKET_SIZE); debug_assert!(fragment_count > 0); @@ -2558,7 +2537,6 @@ fn set_packet_header(packet: &mut [u8], fragment_count: u8, fragment_no: u8, pac /// it as effectively AAD. Other elements of the header are either not authenticated, /// like fragmentation info, or their authentication is implied via key exchange like /// the key id. -#[inline(always)] fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] { let mut ret = [0u8; AES_GCM_IV_SIZE]; ret[3] = packet_type; @@ -2567,7 +2545,6 @@ fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] ret } /// returns `(fragment_count, fragment_no, packet_type, counter, header_nonce)`. -#[inline(always)] fn parse_packet_header(packet: &[u8]) -> (u8, u8, u8, u64, [u8; 10]) { let header_nonce = packet[6..16].try_into().unwrap(); let counter = packet[8..16].try_into().unwrap(); @@ -2658,7 +2635,6 @@ fn generate_key_id( } impl SessionKey { - #[inline(always)] fn new( hmac: &mut Application::HmacHash, ck: SymmetricState, @@ -2685,7 +2661,6 @@ impl SessionKey { } } - #[inline(always)] fn get_send_cipher(&self, counter: u64) -> Result, SendError> { if counter < self.expire_at_counter { Ok(self.send_cipher_pool[(counter as usize) % self.send_cipher_pool.len()].lock().unwrap()) @@ -2694,7 +2669,6 @@ impl SessionKey { } } - #[inline(always)] fn get_receive_cipher(&self, counter: u64) -> MutexGuard { let idx = (counter as usize) % self.receive_cipher_pool.len(); self.receive_cipher_pool[idx].lock().unwrap() @@ -2702,7 +2676,6 @@ impl SessionKey { } /// MixHash to update 'h' during negotiation. -#[inline(always)] fn mix_hash(hasher: &mut impl Sha512, h: &[u8; NOISE_HASHLEN], m: &[u8]) -> [u8; NOISE_HASHLEN] { let mut output = [0u8; NOISE_HASHLEN]; hasher.reset(); @@ -2713,7 +2686,6 @@ fn mix_hash(hasher: &mut impl Sha512, h: &[u8; NOISE_HASHLEN], m: &[u8]) -> [u8; } /// Check if the proof of work attached to the first message contains the correct number of leading /// zeros. -#[inline(always)] fn verify_pow(hasher: &mut Application::Hash, response: &[u8]) -> bool { if Application::PROOF_OF_WORK_BIT_DIFFICULTY == 0 { return true; @@ -2725,7 +2697,6 @@ fn verify_pow(hasher: &mut Application::Hash, res let n = u32::from_be_bytes(output[..4].try_into().unwrap()); n.leading_zeros() >= Application::PROOF_OF_WORK_BIT_DIFFICULTY } -#[inline(always)] fn from_bytes_agreement( public: &[u8], private: &Application::KeyPair, From a6092a56be576e12b3a330da7f10f713a3cac227 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 12:25:18 -0400 Subject: [PATCH 53/91] removed inline --- src/crypto/secret.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/crypto/secret.rs b/src/crypto/secret.rs index 461470f..902eb6d 100644 --- a/src/crypto/secret.rs +++ b/src/crypto/secret.rs @@ -2,7 +2,6 @@ use std::{convert::TryInto, ptr::write_volatile}; /// Constant time byte slice equality. -#[inline] pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { let (a, b) = (a.as_ref(), b.as_ref()); if a.len() == b.len() { @@ -31,7 +30,6 @@ pub struct Secret(pub [u8; L]); impl Secret { /// Create a new all-zero secret. - #[inline(always)] pub fn new() -> Self { Self([0_u8; L]) } @@ -45,30 +43,25 @@ impl Secret { /// This is unsafe because it will not destroy the contents of its input. /// # Safety /// Make sure the contents of the input are securely deleted. - #[inline(always)] pub unsafe fn from_bytes(b: &[u8]) -> Self { Self(b.try_into().unwrap()) } - #[inline(always)] pub fn as_ptr(&self) -> *const u8 { self.0.as_ptr() } - #[inline(always)] pub fn as_bytes(&self) -> &[u8; L] { &self.0 } /// Get the first N bytes of this secret as a fixed length array. - #[inline(always)] pub fn first_n(&self) -> &[u8; N] { assert!(N <= L); unsafe { &*self.0.as_ptr().cast() } } /// Clone the first N bytes of this secret as another secret. - #[inline(always)] pub fn first_n_clone(&self) -> Secret { Secret::(*self.first_n()) } @@ -97,35 +90,30 @@ impl Drop for Secret { } impl Default for Secret { - #[inline(always)] fn default() -> Self { Self([0u8; L]) } } impl AsRef<[u8]> for Secret { - #[inline(always)] fn as_ref(&self) -> &[u8] { &self.0 } } impl AsRef<[u8; L]> for Secret { - #[inline(always)] fn as_ref(&self) -> &[u8; L] { &self.0 } } impl AsMut<[u8]> for Secret { - #[inline(always)] fn as_mut(&mut self) -> &mut [u8] { &mut self.0 } } impl AsMut<[u8; L]> for Secret { - #[inline(always)] fn as_mut(&mut self) -> &mut [u8; L] { &mut self.0 } From 270e39d1650ff16871aaf659b459928545929b6c Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 25 Jul 2023 12:28:09 -0400 Subject: [PATCH 54/91] reset gitignore --- .gitignore | 14 -------------- Cargo.lock | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index 7dd2bd4..ea8c4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1 @@ /target -/**/target -/**/Cargo.lock - -.DS_* -.Icon* -._* -*.o -*.so -*.dylib -*.dSYM -*.a -/.idea -/.nova -*.secret diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..cd3ec00 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,33 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "pqc_kyber" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c578a7eab95d8649f115dd6f90894d167766e82a6bcf66ff25f60e7dfc857e" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "zssp" +version = "0.0.3" +dependencies = [ + "hex-literal", + "pqc_kyber", + "rand_core", +] From e6df05b6b0faad17812c6da12473b4fe8d8d8d75 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 7 Aug 2023 09:03:55 -0400 Subject: [PATCH 55/91] integrated some files --- Cargo.lock | 18 +- Cargo.toml | 3 +- src/antireplay.rs | 24 + src/applicationlayer.rs | 264 ++-- src/challenge.rs | 105 ++ src/context.rs | 438 ++++++ src/crypto/aes.rs | 34 +- src/crypto/aes_gcm.rs | 57 - src/crypto/kyber1024.rs | 36 + src/crypto/mod.rs | 17 +- src/crypto/p384.rs | 43 +- src/crypto/secret.rs | 127 -- src/crypto/sha512.rs | 43 +- src/lib.rs | 23 +- src/log_event.rs | 4 +- src/proto.rs | 303 +--- src/proto_old.rs | 296 ++++ src/ratchet_state.rs | 130 +- src/ratchet_state_old.rs | 62 + src/result.rs | 162 +++ src/symmetric_state.rs | 264 ++-- src/symmetric_state_old.rs | 156 +++ src/zeta.rs | 1394 +++++++++++++++++++ src/zssp copy.rs | 2704 ++++++++++++++++++++++++++++++++++++ src/zssp.rs | 140 +- 25 files changed, 6011 insertions(+), 836 deletions(-) create mode 100644 src/antireplay.rs create mode 100644 src/challenge.rs create mode 100644 src/context.rs delete mode 100644 src/crypto/aes_gcm.rs create mode 100644 src/crypto/kyber1024.rs delete mode 100644 src/crypto/secret.rs create mode 100644 src/proto_old.rs create mode 100644 src/ratchet_state_old.rs create mode 100644 src/result.rs create mode 100644 src/symmetric_state_old.rs create mode 100644 src/zeta.rs create mode 100644 src/zssp copy.rs diff --git a/Cargo.lock b/Cargo.lock index cd3ec00..e0caaa4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,10 +3,13 @@ version = 3 [[package]] -name = "hex-literal" -version = "0.4.1" +name = "arrayvec" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +dependencies = [ + "zeroize", +] [[package]] name = "pqc_kyber" @@ -23,11 +26,18 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" + [[package]] name = "zssp" version = "0.0.3" dependencies = [ - "hex-literal", + "arrayvec", "pqc_kyber", "rand_core", + "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index 74aa8d5..ca0967b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,5 @@ doc = true [dependencies] pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber1024", "std"] } rand_core = "0.6.4" -hex-literal = "0.4.1" +zeroize = { version = "1.6.0" } +arrayvec = { version = "0.7.4", default-features = false, features = ["std", "zeroize"] } diff --git a/src/antireplay.rs b/src/antireplay.rs new file mode 100644 index 0000000..80cd707 --- /dev/null +++ b/src/antireplay.rs @@ -0,0 +1,24 @@ +use std::sync::atomic::{AtomicU64, Ordering}; + +pub struct Window ([AtomicU64; L]); + +impl Window { + pub fn new() -> Self { + Self(std::array::from_fn(|_| AtomicU64::new(0))) + } + /// Check the window without mutating state. + pub fn check(&self, counter: u64) -> bool { + let slot = &self.0[(counter as usize) % self.0.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.load(Ordering::Relaxed); + prev_counter < counter && counter.wrapping_sub(prev_counter) <= MAX + } + /// Update the window, returning true if the packet is still valid. + /// This should only be called after the packet is authenticated. + pub fn update(&self, counter: u64) -> bool { + let slot = &self.0[(counter as usize) % self.0.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); + prev_counter < counter && counter.wrapping_sub(prev_counter) <= MAX + } +} diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index ff0a957..7f18f60 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. @@ -5,15 +7,89 @@ * (c) ZeroTier, Inc. * https://www.zerotier.com/ */ -use std::sync::Arc; - -use crate::crypto::aes::{AesDec, AesEnc}; -use crate::crypto::aes_gcm::{AesGcmDec, AesGcmEnc}; +use crate::crypto::aes::{AesDec, AesEnc, HighThroughputAesGcmPool, LowThroughputAesGcm}; +use crate::crypto::kyber1024::Kyber1024PrivateKey; use crate::crypto::p384::{P384KeyPair, P384PublicKey}; use crate::crypto::rand_core::{CryptoRng, RngCore}; -use crate::crypto::sha512::{HmacSha512, Sha512}; +use crate::crypto::sha512::{HmacSha512, HashSha512}; use crate::RatchetState; -use crate::{log_event::LogEvent, Session, RATCHET_SIZE}; +use crate::proto::RATCHET_SIZE; +use crate::zssp::Session; +//use crate::{log_event::LogEvent, Session}; + +/// A container for a vast majority of the dynamic settings within ZSSP, including all time-based settings. +/// If the user wishes to measure time in units other than milliseconds for some reason, then they can +/// create an adjusted version of this struct with those units, and use it instead of the default. +pub struct Settings { + /// Timeout for how long Alice should wait for Bob to confirm that the Noise_XK handshake + /// was completed successfully. The handshake attempt will be assumed as failed and + /// restarted if Bob does not respond by this cut-off. + pub initial_offer_timeout: u64, + /// Timeout for how long ZSSP should wait before expiring and closing a session when it has + /// lingered in certain states for too long, primarily the rekeying states. + /// If a remote peer does not send the correct information to rekey a session before this + /// timeout then the session will close. + pub rekey_timeout: u64, + /// How long until rekeying should occur for each new session key. + pub rekey_after_time: u64, + /// Maximum random jitter to subtract from the rekey after time timer. + /// Must be greater than 0. + /// This prevents rekeying from occurring predictably on the hour, so traffic analysis is harder. + pub rekey_time_max_jitter: u64, + /// How many key uses may occur before the session starts attempting to rekey. + /// The session will forceably close at 2^32 key uses so it is recommended this value be smaller. + pub rekey_after_key_uses: u64, + /// Retry interval for outgoing connection initiation or rekey attempts. + /// + /// Retry attempts will be no more often than this, but the delay may end up being + /// slightly more in some cases based on the rate of calls to `service`. + pub resend_time: u64, + /// How long fragments are allowed to linger in the defragmentation buffer before they are dropped. + /// This implementation of a defrag buffer only bounds memory consumption based on this value. + pub fragment_assembly_timeout: u64, +} +impl Settings { + /// Default value for the `initial_offer_timeout`. + /// The default value is 10 seconds in ms. + pub const INITIAL_OFFER_TIMEOUT_MS: u64 = 10 * 1000; + /// Default value for the `rekey_timeout`. + /// The default value is 1 minute in ms. + pub const REKEY_TIMEOUT_MS: u64 = 60 * 1000; + /// Default value for the `rekey_after_time`. + /// The default value is 1 hour in ms. + pub const REKEY_AFTER_TIME_MS: u64 = 60 * 60 * 1000; + /// Default value for the `rekey_time_max_jitter`. + /// The default is 10 minutes in ms. + pub const REKEY_AFTER_TIME_MAX_JITTER_MS: u64 = 10 * 60 * 1000; + /// Default value for the `rekey_after_key_uses`. + /// The default is 2^30. + pub const REKEY_AFTER_KEY_USES: u64 = 1 << 30; + /// Default value for the `resend_time`. + /// The default is 1 second in ms. + pub const RESEND_TIME: u64 = 1000; + /// Default value for the `fragment_assembly_timeout`. + /// The default is 5 seconds in ms. + pub const FRAGMENT_ASSEMBLY_TIMEOUT_MS: u64 = 5 * 1000; + /// Create an instance of Settings with all default values. + /// These defaults are in units of milliseconds, so if these defaults are used, `App::time` + /// must return timestamps in unts of milliseconds as well. + pub const fn new_ms() -> Self { + Self { + initial_offer_timeout: Self::INITIAL_OFFER_TIMEOUT_MS, + rekey_timeout: Self::REKEY_TIMEOUT_MS, + rekey_after_time: Self::REKEY_AFTER_TIME_MS, + rekey_time_max_jitter: Self::REKEY_AFTER_TIME_MAX_JITTER_MS, + rekey_after_key_uses: Self::REKEY_AFTER_KEY_USES, + resend_time: Self::RESEND_TIME, + fragment_assembly_timeout: Self::FRAGMENT_ASSEMBLY_TIMEOUT_MS, + } + } +} +impl Default for Settings { + fn default() -> Self { + Self::new_ms() + } +} /// Trait to implement to integrate the session into an application. /// @@ -25,84 +101,51 @@ use crate::{log_event::LogEvent, Session, RATCHET_SIZE}; /// set to the same values. Changing these constants is generally discouraged unless you know /// what you are doing. pub trait ApplicationLayer: Sized { - /// Retry interval for outgoing connection initiation or rekey attempts. - /// - /// Retry attempts will be no more often than this, but the delay may end up being - /// slightly more in some cases depending on where in the cycle the initial attempt - /// falls. - /// - /// Default value is 1 second. - const RETRY_INTERVAL_MS: i64 = 1000; - /// Timeout for how long Alice should wait for Bob to confirm that the Noise_XK handshake - /// was completed successfully. The handshake attempt will be assumed as failed and - /// restarted if Bob does not respond by this cut-off. - /// - /// Default is 10 seconds. - const INITIAL_OFFER_TIMEOUT_MS: i64 = 10 * 1000; - /// Timeout for how long ZSSP should wait before expiring and closing a session when it has - /// lingered in certain states for too long, primarily the rekeying states. - /// If a remote peer does not send the correct information to rekey a session before this - /// timeout then the session will close. - /// - /// Default is 1 minute. - const EXPIRATION_TIMEOUT_MS: i64 = 60 * 1000; - /// Start attempting to rekey after a key has been in use for this many milliseconds. - /// - /// Default is 1 hour. - const REKEY_AFTER_TIME_MS: i64 = 1000 * 60 * 60; - /// Maximum random jitter to subtract from the rekey after time timer. - /// Must be greater than 0 and less than u32::MAX. - /// This prevents rekeying from occurring predictably on the hour, so traffic analysis is harder. - /// - /// Default is 10 minutes. - const REKEY_AFTER_TIME_MAX_JITTER_MS: i64 = 1000 * 60 * 10; - /// Rekey after this many key uses. - /// - /// The default is 1/4 the recommended NIST limit for AES-GCM. Unless you are transferring - /// a massive amount of data REKEY_AFTER_TIME_MS is probably going to kick in first. - const REKEY_AFTER_USES: u64 = 1073741824; - - /// Hard expiration of a key after this many uses. - /// - /// Attempting to encrypt more than this many messages with a key will cause a hard error - /// and prevent all encryption. - /// This should basically never occur in practice because of rekeying. - /// - /// Default value is 2^32 - 1, one less than NIST's recommended limit. - /// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf - const EXPIRE_AFTER_USES: u64 = 4294967295; - - /// Determines how computationally difficult the proof of work is when Bob challenges Alice. - /// It is extremely computationally expensive on Bob to process Alice's initiation packet. So - /// Bob has the option to challenge Alice to prove ownership of address and to prove work before - /// they attempt process Alice's initiation packet. - /// The amount of computational work Alice has to prove increases exponentially with this value. - /// - /// This value must be between 0 and 32 (inclusive). - /// - /// Default is 13, which, on a modern processor, ensures Alice will have to do about as much - /// computational work as Bob will when they process Alice's initiation packet. - const PROOF_OF_WORK_BIT_DIFFICULTY: u32 = 13; + /// These are constants that can be redefined from their defaults to change rekey + /// and negotiation timeout behavior. If two sides of a ZSSP session have different constants, + /// the protocol will tend to default to the smaller constants. + const SETTINGS: Settings = Settings::new_ms(); type Rng: CryptoRng + RngCore; + /// The implementation of AES-256 Encryption that ZSSP should use. + /// + /// FIPS compliance requires use of a FIPS certified implementation. type PrpEnc: AesEnc; + /// The implementation of AES-256 Decryption that ZSSP should use. + /// + /// FIPS compliance requires use of a FIPS certified implementation. type PrpDec: AesDec; - type AeadEnc: AesGcmEnc; - type AeadDec: AesGcmDec; + type Aead: LowThroughputAesGcm; + type AeadPool: HighThroughputAesGcmPool; - type Hash: Sha512; + /// The implementation of SHA-512 that ZSSP should use. + /// + /// FIPS compliance requires use of a FIPS certified implementation. + type Hash: HashSha512; type HmacHash: HmacSha512; - + /// The implementation of P-384 public keys that ZSSP should use. + /// + /// FIPS compliance requires a FIPS certified implementation. type PublicKey: P384PublicKey; - type KeyPair: P384KeyPair; + /// The implementation of P-384 private keys that ZSSP should use. + /// + /// FIPS compliance requires use of a FIPS certified implementation. + type KeyPair: P384KeyPair; + /// The implementation of Kyber1024 that ZSSP should use. + /// + /// No implementation of Kyber1024 can be FIPS certified, but this is not required + /// for ZSSP to achieve FIPS compliance. + type Kem: Kyber1024PrivateKey; - type IoError: std::fmt::Debug; + /// A user-defined error returned when the `ApplicationLayer` fails to access persistent storage + /// for a peer's ratchet states. + type StorageError: std::error::Error; /// Type for arbitrary opaque object for use by the application that is attached to /// each session. - type Data; + type SessionData; /// Data type for incoming packet buffers. /// @@ -116,6 +159,12 @@ pub trait ApplicationLayer: Sized { /// It will be dropped as soon as the session is established. type LocalIdentityBlob: AsRef<[u8]>; + /// Should return the current time in milliseconds. Does not have to be monotonic, nor synced + /// with remote peers (although both of these properties would help reliability slightly). + /// Used to determine if any current handshakes should be resent or timed-out, or if a session + /// should rekey. + fn time(&self) -> i64; + /// This function will be called whenever Alice's initial Hello packet contains the empty ratchet /// fingerprint. Brand new peers will always connect to Bob with the empty ratchet, but from /// then on they should be using non-empty ratchet states. @@ -125,7 +174,7 @@ pub trait ApplicationLayer: Sized { /// If this function is configured to always return true, it means peers will not be able to /// connect to us unless they had a prior-established ratchet key with us. This is the best way /// for the paranoid to enforce a manual allow-list. - fn hello_requires_recognized_ratchet(&self, current_time: i64) -> bool; + fn hello_requires_recognized_ratchet(&self) -> bool; /// This function is called if we, as Alice, attempted to open a session with Bob using a /// non-empty ratchet key, but Bob does not have this ratchet key and wants to downgrade /// to the zero ratchet key. @@ -141,7 +190,14 @@ pub trait ApplicationLayer: Sized { /// least one party is misconfigured and got their ratchet keys corrupted or lost, or Bob has /// been compromised and is being impersonated. An attacker must at least have Bob's private /// static key to be able to ask Alice to downgrade. - fn initiator_disallows_downgrade(&self, session: &Arc>, current_time: i64) -> bool; + fn initiator_disallows_downgrade(&self, session: &Arc>) -> bool; + /// Function to accept sessions after final negotiation. + /// The second argument is the identity that the remote peer sent us. The application + /// must verify this identity is associated with the remote peer's static key. + /// To prevent desync, if this function returns (Some(_), _), no other open session with the + /// same remote peer must exist. Drop or call expire on any pre-existing sessions before returning. + fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> AcceptAction; + /// Lookup a specific ratchet state based on its ratchet fingerprint. /// This function will be called whenever Alice attempts to connect to us with a non-empty /// ratchet fingerprint. @@ -154,19 +210,36 @@ pub trait ApplicationLayer: Sized { /// If `RatchetAction::DowngradeRatchet` is returned we will attempt to convince Alice to downgrade /// to the empty ratchet key, restarting the ratchet chain. /// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped. - fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE], current_time: i64) -> Result; - - /// Lookup a specific ratchet state based on the identity of the peer being communicated with. + fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, Self::StorageError>; + /// Lookup the specific ratchet states based on the identity of the peer being communicated with. /// This function will be called whenever Alice attempts to open a session, or Bob attempts /// to verify Alice's identity. + /// + /// If the peer's ratchet states could not be could, this function should return + /// `RatchetState::new_initial_states()`. + /// + /// If a one-time-password has been pre-shared with this peer, `RatchetState::new_otp_states(...)` + /// should be pre-saved to the storage backend as if it is a normal ratchet state. + /// This is to ensure it can both be restored and eventually deleted when it is used. + /// + /// This function is not responsible for deciding whether or not to connect to this remote peer. + /// Filtering peers should be done by the caller to `Context::open` as well as by the + /// function `ApplicationLayer::check_accept_session`. fn restore_by_identity( &self, remote_static_key: &Self::PublicKey, - application_data: &Self::Data, - current_time: i64, - ) -> Result<[RatchetState; 2], Self::IoError>; - /// Atomically save the given `new_ratchet_states` to persistent storage. - /// `pre_ratchet_states` contains what should be the previous contents of persistent storage. + session_data: &Self::SessionData, + ) -> Result<(RatchetState, Option), Self::StorageError>; + /// Atomically save `current_state1` and `current_state2` so that them and only them can be + /// restored with `restore_by_identity` and `restore_by_fingerprint` through a system restart. + /// Theses should overwrite the previous ratchet states 1 and 2 saved to storage. + /// + /// `state_added` will be equal to the brand new ratchet state that was added in this update, + /// or `None` if there is not a new ratchet state this update. `state_deleted1` and + /// `state_deleted2` will be equal to any ratchet states that are to be deleted and overwritten + /// as a result of this update, or `None` if there is not one to be deleted. + /// `state_added` will always have a non-empty (`Some()`) ratchet fingerprint, and it will + /// always be equal to `current_state1`. /// /// If this returns `Err(IoError)`, the packet which triggered this function to be called will be /// dropped, and no session state will be mutated, preserving synchronization. The remote peer @@ -184,12 +257,27 @@ pub trait ApplicationLayer: Sized { fn save_ratchet_state( &self, remote_static_key: &Self::PublicKey, - application_data: &Self::Data, - pre_ratchet_states: [&RatchetState; 2], - new_ratchet_states: [&RatchetState; 2], - current_time: i64, - ) -> Result<(), Self::IoError>; + session_data: &Self::SessionData, + update_data: RatchetUpdate<'_>, + ) -> Result<(), Self::StorageError>; - #[allow(unused)] - fn event_log(&self, event: LogEvent, current_time: i64) {} + /// Receives a stream of events that occur during an execution of ZSSP. + /// These are provided for debugging, logging or metrics purposes, and must be used for + /// nothing else. Do not base protocol-level decisions upon the events passed to this function. + #[cfg(feature = "logging")] + fn event_log(&self, event: LogEvent<'_, Self>); +} + +pub struct RatchetUpdate<'a> { + pub state1: &'a RatchetState, + pub state2: Option<&'a RatchetState>, + pub state1_was_just_added: bool, + pub state_deleted1: Option<&'a RatchetState>, + pub state_deleted2: Option<&'a RatchetState>, +} + +pub struct AcceptAction { + pub session_data: Option, + pub responder_disallows_downgrade: bool, + pub responder_silently_rejects: bool, } diff --git a/src/challenge.rs b/src/challenge.rs new file mode 100644 index 0000000..76e6cc5 --- /dev/null +++ b/src/challenge.rs @@ -0,0 +1,105 @@ +use std::hash::Hasher; +use std::sync::atomic::{AtomicU64, Ordering}; + +use rand_core::{CryptoRng, RngCore}; + +use crate::antireplay::Window; +use crate::crypto::{secure_eq, sha512::{HashSha512, SHA512_HASH_SIZE}}; +use crate::proto::*; + +pub struct ChallengeContext { + counter: AtomicU64, + antireplay_window: Window, + salt: [u8; SALT_SIZE], +} + +/// Corresponds to Algorithm 11 found in Section 5. +pub fn gen_null_response(rng: &mut Rng) -> [u8; CHALLENGE_SIZE] { + let mut response = [0u8; CHALLENGE_SIZE]; + response[POW_START..].copy_from_slice(&rng.next_u64().to_be_bytes()); + response +} +/// Corresponds to Algorithm 13 found in Section 5. +pub fn respond_to_challenge_in_place( + rng: &mut Rng, + challenge: &[u8; CHALLENGE_SIZE], + pre_response: &mut [u8; CHALLENGE_SIZE], +) { + if &challenge[POW_START..] == &pre_response[POW_START..] { + pre_response.copy_from_slice(challenge); + let mut pow = rng.next_u64(); + let mut work_buf = [0u8; SHA512_HASH_SIZE]; + loop { + pre_response[POW_START..].copy_from_slice(&pow.to_be_bytes()); + if verify_pow::(pre_response, &mut work_buf) { + return; + } + pow = pow.wrapping_add(1); + } + } +} + +impl ChallengeContext { + pub fn new(rng: &mut Rng) -> Self { + let mut salt = [0u8; SALT_SIZE]; + rng.fill_bytes(&mut salt); + Self { + counter: AtomicU64::new(0), + antireplay_window: Window::new(), + salt, + } + } + /// Corresponds to Algorithm 12 found in Section 5. + pub fn process_hello( + &self, + addr: &impl std::hash::Hash, + response: &[u8; CHALLENGE_SIZE], + ) -> Result { + let c = u64::from_be_bytes(response[..COUNTER_SIZE].try_into().unwrap()); + let mut work_buf = [0u8; SHA512_HASH_SIZE]; + if self.antireplay_window.check(c) && secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac::(c, addr)) && verify_pow::(response, &mut work_buf) { + self.antireplay_window.update(c); + Ok(true) + } else { + let mut challenge = [0u8; CHALLENGE_SIZE]; + let d = self.counter.fetch_add(1, Ordering::Relaxed); + challenge[..COUNTER_SIZE].copy_from_slice(&d.to_be_bytes()); + challenge[COUNTER_SIZE..POW_START].copy_from_slice(&self.create_mac::(d, addr)); + challenge[POW_START..].copy_from_slice(&response[POW_START..]); + Err(challenge) + } + } + fn create_mac(&self, c: u64, addr: &impl std::hash::Hash) -> [u8; MAC_SIZE] { + let mut h = Hash::new(); + let mut hasher = ShaHasher(&mut h); + hasher.write(&c.to_be_bytes()); + addr.hash(&mut hasher); + hasher.write(&self.salt); + drop(hasher); + + let mut mac = [0u8; SHA512_HASH_SIZE]; + h.finish_and_reset(&mut mac); + mac[..MAC_SIZE].try_into().unwrap() + } +} + +/// Trick rust into letting us use a hasher that returns more than 64 bits. +struct ShaHasher<'a, ShaImpl: HashSha512>(&'a mut ShaImpl); +impl<'a, ShaImpl: HashSha512> Hasher for ShaHasher<'a, ShaImpl> { + fn finish(&self) -> u64 { + unimplemented!() + } + fn write(&mut self, bytes: &[u8]) { + self.0.update(bytes) + } +} + +/// Check if the proof of work attached to the first message contains the correct number of leading +/// zeros. +fn verify_pow(response: &[u8], work_buf: &mut [u8; SHA512_HASH_SIZE]) -> bool { + let mut hasher = Hash::new(); + hasher.update(response); + hasher.finish_and_reset(work_buf); + let n = u32::from_be_bytes(work_buf[..4].try_into().unwrap()); + n.leading_zeros() >= DIFFICULTY +} diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..ba69585 --- /dev/null +++ b/src/context.rs @@ -0,0 +1,438 @@ +use rand_core::RngCore; +use std::cmp::Reverse; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::hash::Hash; +use std::num::NonZeroU32; +use std::sync::{Arc, Mutex, Weak, RwLock}; + +use crate::applicationlayer::ApplicationLayer; +use crate::crypto::aes::{AES_256_KEY_SIZE, AES_GCM_IV_SIZE}; +use crate::indexed_heap::IndexedBinaryHeap; +//use crate::fragmentation::{send_with_fragmentation, DefragBuffer}; +use crate::proto::*; +use crate::result::{byzantine_fault, ReceiveError, ReceiveOk, SendError, SessionEvent}; +use crate::zeta::*; +#[cfg(feature = "logging")] +use crate::LogEvent::*; +use crate::{challenge::ChallengeContext, result::OpenError}; + +/// Macro to turn off logging at compile time. +macro_rules! log { + ($app:expr, $event:expr) => { + #[cfg(feature = "logging")] + $app.event_log($event); + }; +} +pub(crate) use log; + +/// Session context for local application. +/// +/// Each application using ZSSP must create an instance of this to own sessions and +/// defragment incoming packets that are not yet associated with a session. +/// +/// Internally this is just a clonable Arc, so it can be safely shared with multiple threads. +pub struct Context(Arc>); +impl Clone for Context { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +pub(crate) type SessionMap = RwLock>>>; + +pub(crate) struct ContextInner { + pub(crate) rng: Mutex, + pub(crate) s_secret: App::KeyPair, + pub(crate) session_queue: Mutex>, Reverse>>, + pub(crate) session_map: SessionMap, + //pub(crate) b2_map: Mutex>>, + + //hello_defrag: Mutex, + challenge: ChallengeContext, +} + +/// Corresponds to Figure 10 found in Section 4.3. +fn to_aes_nonce(pn: &[u8; PACKET_NONCE_SIZE]) -> [u8; AES_GCM_IV_SIZE] { + let mut an = [0u8; AES_GCM_IV_SIZE]; + an[2..].copy_from_slice(pn); + an +} +/// Corresponds to Figure 14 found near Section 6. +fn to_packet_nonce(n: &[u8; AES_GCM_IV_SIZE]) -> &[u8; PACKET_NONCE_SIZE] { + (&n[n.len() - PACKET_NONCE_SIZE..]).try_into().unwrap() +} + +impl Context { + /// Create a new session context. + pub fn new(static_secret_key: App::KeyPair, mut rng: App::Rng) -> Self { + let challenge = ChallengeContext::new(&mut rng); + Self(Arc::new(ContextInner { + rng: Mutex::new(rng), + s_secret: static_secret_key, + session_map: Mutex::new(HashMap::new()), + b2_map: Mutex::new(HashMap::new()), + hello_defrag: Mutex::new(DefragBuffer::new(None)), + challenge: Mutex::new(challenge), + sessions: Mutex::new(HashMap::new()), + })) + } + /// Enable the ZeroTier Challenge Protocol, to protect this machine from CPU exhaustion DDOS + /// attacks. + pub fn enable_challenge(&self, enabled: bool) { + self.0.challenge.lock().unwrap().enabled = enabled; + } + + /// Create a new session and send initial packet(s) to other side. + /// + /// This will return SendError::DataTooLarge if the combined size of the metadata and the local + /// static public blob (as retrieved from the application layer) exceed MAX_INIT_PAYLOAD_SIZE. + /// + /// * `app` - Application layer instance + /// * `send` - Function to be called to send one or more initial packets to the remote being + /// contacted + /// * `mtu` - MTU for initial packets + /// * `static_remote_key` - Remote side's static public NIST P-384 key + /// * `session_data` - Arbitrary data meaningful to the application to include with session + /// object + /// * `identity` - Payload to be sent to Bob that contains the information necessary + /// for the upper protocol to authenticate and approve of Alice's identity + pub fn open( + &self, + app: App, + send: impl FnMut(Vec) -> bool, + mut mtu: usize, + static_remote_key: App::PublicKey, + session_data: App::SessionData, + identity: Vec, + ) -> Result>, OpenError> { + mtu = mtu.max(MIN_TRANSPORT_MTU); + let ctx = &self.0; + + // Process zeta layer. + trans_to_a1( + app, + &ctx, + static_remote_key, + session_data, + identity, + |Packet(kid, nonce, payload): &Packet| { + // Process fragmentation layer. + send_with_fragmentation::(send, mtu, *kid, to_packet_nonce(&nonce), payload, None); + }, + ) + } + + /// Receive, authenticate, decrypt, and process a physical wire packet. + /// + /// * `app` - Interface to application using ZSSP + /// * `send_unassociated_reply` - Function to send reply packets directly when no session exists + /// * `send_unassociated_mtu` - MTU for unassociated replies + /// * `send_to` - Function to get senders for existing sessions, permitting MTU and path lookup + /// * `remote_address` - Whatever the remote address is, as long as you can Hash it + /// * `raw_fragment` - Buffer containing incoming wire packet + pub fn receive<'a, SendFn: FnMut(Vec) -> bool>( + &self, + app: App, + send_unassociated_reply: impl FnMut(Vec) -> bool, + mut send_unassociated_mtu: usize, + send_to: impl FnOnce(&Arc>) -> Option<(SendFn, usize)>, + remote_address: &impl Hash, + raw_fragment: Vec, + ) -> Result, ReceiveError> { + use crate::result::FaultType::*; + send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); + let ctx = &self.0; + + // Multiplex session. + let kid_recv = u32::from_be_bytes(raw_fragment[..KID_SIZE].try_into().unwrap()); + if let Some(kid_recv) = NonZeroU32::new(kid_recv) { + let session = ctx.session_map.lock().unwrap().get(&kid_recv).map(|r| r.upgrade()); + if let Some(Some(session)) = session { + // Process recv fragmentation layer. + let mut zeta = session.0.lock().unwrap(); + let result = zeta.defrag.received_fragment::(raw_fragment, app.time(), |n, frag_no, frag_count| { + let (p, c) = from_nonce(n); + if p != PACKET_TYPE_DATA { + log!(app, ReceivedRawFragment(p, c, frag_no, frag_count)); + } + if p == PACKET_TYPE_HANDSHAKE_RESPONSE { + if !matches!(&zeta.beta, ZetaAutomata::A1(_)) { + // A resent handshake response from Bob may have arrived out of order, + // after we already received one. + return Err(byzantine_fault!(OutOfSequence, false)); + } + if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD { + return Err(byzantine_fault!(ExpiredCounter, true)); + } + Ok(()) + } else if PACKET_TYPE_USES_COUNTER_RANGE.contains(&p) { + if !zeta.check_counter_window(c) { + // The counter window has finite memory and so will occasionally give + // false positives on very out-of-order packets. + return Err(byzantine_fault!(ExpiredCounter, false)); + } + Ok(()) + } else if p == PACKET_TYPE_HANDSHAKE_COMPLETION { + // The handshake completion packet could have been resent. + return Err(byzantine_fault!(InvalidPacket, false)); + } else { + return Err(byzantine_fault!(InvalidPacket, true)); + } + })?; + if let Some((pn, mut assembled_packet)) = result { + // Process recv zeta layer. + let send_associated = |Packet(kid, nonce, payload): &Packet, hk: Option<&[u8; AES_256_KEY_SIZE]>| { + if let Some((send_fragment, mut mtu)) = send_to(&session) { + mtu = mtu.max(MIN_TRANSPORT_MTU); + send_with_fragmentation::(send_fragment, mtu, *kid, to_packet_nonce(&nonce), payload, hk); + } + }; + + let (p, _) = from_nonce(&pn); + let ret = match p { + PACKET_TYPE_DATA => { + received_payload_in_place(&mut zeta, kid_recv, to_aes_nonce(&pn), &mut assembled_packet)?; + SessionEvent::Data(assembled_packet) + } + PACKET_TYPE_HANDSHAKE_RESPONSE => { + log!(app, ReceivedRawX2); + received_x2_trans( + &mut zeta, + &session, + &app, + &ctx, + kid_recv, + to_aes_nonce(&pn), + assembled_packet, + send_associated, + )?; + log!(app, X2IsAuthSentX3(&session)); + SessionEvent::Control + } + PACKET_TYPE_KEY_CONFIRM => { + log!(app, ReceivedRawKeyConfirm); + let result = + received_c1_trans(&mut zeta, &app, &ctx.rng, kid_recv, to_aes_nonce(&pn), assembled_packet, send_associated)?; + log!(app, KeyConfirmIsAuthSentAck(&session)); + if result { + SessionEvent::Established + } else { + SessionEvent::Control + } + } + PACKET_TYPE_ACK => { + log!(app, ReceivedRawAck); + received_c2_trans(&mut zeta, &app, &ctx.rng, kid_recv, to_aes_nonce(&pn), assembled_packet)?; + log!(app, AckIsAuth(&session)); + SessionEvent::Control + } + PACKET_TYPE_REKEY_INIT => { + log!(app, ReceivedRawK1); + received_k1_trans( + &mut zeta, + &session, + &app, + &ctx.rng, + &ctx.session_map, + &ctx.s_secret, + kid_recv, + to_aes_nonce(&pn), + assembled_packet, + send_associated, + )?; + log!(app, K1IsAuthSentK2(&session)); + SessionEvent::Control + } + PACKET_TYPE_REKEY_COMPLETE => { + log!(app, ReceivedRawK2); + received_k2_trans(&mut zeta, &app, kid_recv, to_aes_nonce(&pn), assembled_packet, send_associated)?; + log!(app, K2IsAuthSentKeyConfirm(&session)); + SessionEvent::Control + } + PACKET_TYPE_SESSION_REJECTED => { + log!(app, ReceivedRawD); + received_d_trans(&mut zeta, kid_recv, to_aes_nonce(&pn), assembled_packet)?; + log!(app, DIsAuthClosedSession(&session)); + SessionEvent::Rejected + } + _ => return Err(byzantine_fault!(InvalidPacket, true)), // This is unreachable. + }; + drop(zeta); + Ok(ReceiveOk::Session(session, ret)) + } else { + Ok(ReceiveOk::Unassociated) + } + } else { + let mut b2_map = ctx.b2_map.lock().unwrap(); + if let Entry::Occupied(mut entry) = b2_map.entry(kid_recv) { + let zeta = entry.get_mut(); + // Process recv fragmentation layer. + let result = zeta.defrag.received_fragment::(raw_fragment, app.time(), |n, frag_no, frag_count| { + let (p, c) = from_nonce(n); + log!(app, ReceivedRawFragment(p, c, frag_no, frag_count)); + if p == PACKET_TYPE_HANDSHAKE_COMPLETION && c == 0 { + Ok(()) + } else { + Err(byzantine_fault!(InvalidPacket, true)) + } + })?; + if let Some((_, assembled_packet)) = result { + log!(app, ReceivedRawX3); + let zeta = entry.remove(); + let session = received_x3_trans(zeta, &app, ctx, kid_recv, assembled_packet, |Packet(kid, nonce, payload), hk| { + send_with_fragmentation::( + send_unassociated_reply, + send_unassociated_mtu, + *kid, + to_packet_nonce(&nonce), + payload, + hk, + ); + })?; + log!(app, X3IsAuthSentKeyConfirm(&session)); + Ok(ReceiveOk::Session(session, SessionEvent::NewSession)) + } else { + Ok(ReceiveOk::Unassociated) + } + } else { + // When sessions are added or dropped or packets arrive extremely delayed it is + // possible to receive no longer recognized key ids. + Err(byzantine_fault!(UnknownLocalKeyId, false)) + } + } + } else { + // Process recv fragmentation layer. + let result = ctx + .hello_defrag + .lock() + .unwrap() + .received_fragment::(raw_fragment, app.time(), |n, frag_no, frag_count| { + let (p, c) = from_nonce(n); + log!(app, ReceivedRawFragment(p, c, frag_no, frag_count)); + if p == PACKET_TYPE_HANDSHAKE_HELLO || p == PACKET_TYPE_CHALLENGE { + Ok(()) + } else { + Err(byzantine_fault!(InvalidPacket, true)) + } + })?; + if let Some((n, mut assembled_packet)) = result { + let (p, _) = from_nonce(&n); + if p == PACKET_TYPE_HANDSHAKE_HELLO { + log!(app, ReceivedRawX1); + // Process recv challenge layer. + let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; + let result = ctx + .challenge + .lock() + .unwrap() + .process_hello::(remote_address, (&assembled_packet[challenge_start..]).try_into().unwrap()); + if let Err(challenge) = result { + log!(app, X1FailedChallengeSentNewChallenge); + let mut challenge_packet = Vec::new(); + challenge_packet.extend(&assembled_packet[..KID_SIZE]); + challenge_packet.extend(&challenge); + let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); + send_with_fragmentation::( + send_unassociated_reply, + send_unassociated_mtu, + 0, + to_packet_nonce(&nonce), + &challenge_packet, + None, + ); + // If we issue a challenge the first hello packet will always fail. + return Err(byzantine_fault!(FailedAuth, false)); + } else if let Ok(true) = result { + log!(app, X1SucceededChallenge); + } + assembled_packet.truncate(challenge_start); + + // Process recv zeta layer. + received_x1_trans(&app, &ctx, to_aes_nonce(&n), assembled_packet, |Packet(kid, nonce, payload), hk| { + send_with_fragmentation::( + send_unassociated_reply, + send_unassociated_mtu, + *kid, + to_packet_nonce(&nonce), + payload, + Some(hk), + ); + })?; + log!(app, X1IsAuthSentX2); + Ok(ReceiveOk::Unassociated) + } else if p == PACKET_TYPE_CHALLENGE { + log!(app, ReceivedRawChallenge); + // Process recv challenge layer. + if assembled_packet.len() != KID_SIZE + CHALLENGE_SIZE { + return Err(byzantine_fault!(InvalidPacket, true)); + } + if let Some(kid_recv) = NonZeroU32::new(u32::from_be_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) { + if let Some(Some(session)) = ctx.session_map.lock().unwrap().get(&kid_recv).map(|r| r.upgrade()) { + let mut zeta = session.0.lock().unwrap(); + respond_to_challenge(&mut zeta, &ctx.rng, &assembled_packet[KID_SIZE..].try_into().unwrap()); + log!(app, ChallengeIsAuth(&session)); + return Ok(ReceiveOk::Unassociated); + } + } + Err(byzantine_fault!(UnknownLocalKeyId, true)) + } else { + Err(byzantine_fault!(InvalidPacket, true)) + } + } else { + Ok(ReceiveOk::Unassociated) + } + } + } + + /// Send data over the session. + /// + /// * `session` - The session to send to + /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a + /// slice of `data` + /// * `mtu` - The MTU of the link, all packets passed to `send` will be at most `mtu` in length + /// * `payload` - Data to send + pub fn send(&self, session: &Arc>, send: impl FnMut(Vec) -> bool, mut mtu: usize, payload: Vec) -> Result<(), SendError> { + mtu = mtu.max(MIN_TRANSPORT_MTU); + let mut zeta = session.0.lock().unwrap(); + send_payload(&mut zeta, payload, |Packet(kid, nonce, payload), hk| { + send_with_fragmentation::(send, mtu, *kid, to_packet_nonce(nonce), &payload, hk); + }) + } + + /// Perform periodic background service and cleanup tasks. + /// + /// This returns the number of milliseconds until it should be called again. The caller should + /// try to satisfy this but small variations in timing of up to a few seconds are not + /// a problem. + /// + /// * `send_to` - Function to get a sender and an MTU to send something over an active session + pub fn service) -> bool>(&self, app: App, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>) -> i64 { + let ctx = &self.0; + let sessions = ctx.sessions.lock().unwrap(); + let current_time = app.time(); + let mut next_timer = i64::MAX; + for (_, session) in sessions.iter() { + if let Some(session) = session.upgrade() { + let mut zeta = session.0.lock().unwrap(); + service( + &mut zeta, + &session, + ctx, + &app, + current_time, + |Packet(kid, nonce, payload): &Packet, hk| { + if let Some((send_fragment, mut mtu)) = send_to(&session) { + mtu = mtu.max(MIN_TRANSPORT_MTU); + send_with_fragmentation::(send_fragment, mtu, *kid, to_packet_nonce(&nonce), payload, hk); + } + }, + ); + next_timer = next_timer.min(zeta.next_timer()); + zeta.defrag.service::(current_time); + } + } + ctx.hello_defrag.lock().unwrap().service::(current_time); + (App::SETTINGS.resend_time as i64).min(next_timer - current_time) + } +} diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index 637d009..baec450 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -1,7 +1,9 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. -pub const AES_256_BLOCK_SIZE: usize = 16; pub const AES_256_KEY_SIZE: usize = 32; +pub const AES_256_BLOCK_SIZE: usize = 16; +pub const AES_GCM_TAG_SIZE: usize = 16; +pub const AES_GCM_IV_SIZE: usize = 12; /// A trait for encrypting individual blocks of plaintext using AES-256. /// It is used for header authentication, for which we have a standard model proof that our @@ -36,3 +38,33 @@ pub trait AesDec: Send + Sync { /// The plaintext should be written directly back out to `block`. fn decrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]); } + + +pub trait AesGcmEncContext { + fn encrypt(&mut self, input: &[u8], output: &mut [u8]); + + fn finish(&mut self) -> [u8; AES_GCM_TAG_SIZE]; +} + +pub trait AesGcmDecContext { + fn decrypt(&mut self, input: &[u8], output: &mut [u8]); + + #[must_use] + fn finish(&mut self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; +} + +pub trait HighThroughputAesGcmPool: Send + Sync { + type EncContext<'a>: AesGcmEncContext where Self: 'a; + type DecContext<'a>: AesGcmDecContext where Self: 'a; + + fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self; + + fn start_enc<'a>(&'a self, iv: &[u8; AES_GCM_IV_SIZE]) -> Self::EncContext<'a>; + fn start_dec<'a>(&'a self, iv: &[u8; AES_GCM_IV_SIZE]) -> Self::DecContext<'a>; +} + +pub trait LowThroughputAesGcm { + fn encrypt_in_place(key: &[u8; AES_256_KEY_SIZE], iv: &[u8; AES_GCM_IV_SIZE], aad: &[u8], data: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE]; + #[must_use] + fn decrypt_in_place(key: &[u8; AES_256_KEY_SIZE], iv: &[u8; AES_GCM_IV_SIZE], aad: &[u8], data: &mut [u8], tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; +} diff --git a/src/crypto/aes_gcm.rs b/src/crypto/aes_gcm.rs deleted file mode 100644 index c8328ed..0000000 --- a/src/crypto/aes_gcm.rs +++ /dev/null @@ -1,57 +0,0 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. - -pub const AES_GCM_TAG_SIZE: usize = 16; -pub const AES_GCM_IV_SIZE: usize = 12; -pub const AES_GCM_KEY_SIZE: usize = super::aes::AES_256_KEY_SIZE; - -/// The order of calls to this trait is always -/// `set_iv` -> `set_aad` -> `encrypt` -> `finish_encrypt`. -/// `set_aad` and `encrypt` may not always be called. `encrypt_in_place` may be called instead of -/// `encrypt`. `encrypt` may be called multiple times, it should start encryption of the input at the point in -/// the keystream where encryption previously ended. -/// An instance of this trait may be reused multiple times, each reuse should use the same key. -/// If it is reused, `set_iv` will always be the very next call after `finish_encrypt`. -/// -/// Implementations of this trait do not have to be Send + Sync, -/// but if instances are wrapped in a `Mutex` they must satisfy the requirements of Send + Sync. -/// -/// Instances must securely delete their keys when dropped. -pub trait AesGcmEnc { - fn new(key: &[u8; AES_GCM_KEY_SIZE]) -> Self; - - fn set_iv(&mut self, iv: &[u8; AES_GCM_IV_SIZE]); - - fn set_aad(&mut self, aad: &[u8]); - - fn encrypt(&mut self, input: &[u8], output: &mut [u8]); - - fn encrypt_in_place(&mut self, data: &mut [u8]); - - fn finish_encrypt(&mut self, output: &mut [u8; AES_GCM_TAG_SIZE]); -} - -/// The order of calls to this trait is always -/// `set_iv` -> `set_aad` -> `decrypt` -> `finish_decrypt`. -/// `set_aad` and `decrypt` may not always be called. `decrypt_in_place` may be called instead of -/// `decrypt`. `decrypt` may be called multiple times, it should start decryption of the input at the point in -/// the keystream where decryption previously ended. -/// An instance of this trait may be reused multiple times, each reuse should use the same key. -/// If it is reused, `set_iv` will always be the very next call after `finish_decrypt`. -/// -/// Implementations of this trait do not have to be Send + Sync, -/// but if instances are wrapped in a `Mutex` they must satisfy the requirements of Send + Sync. -/// -/// Instances must securely delete their keys when dropped. -pub trait AesGcmDec { - fn new(key: &[u8; AES_GCM_KEY_SIZE]) -> Self; - - fn set_iv(&mut self, iv: &[u8; AES_GCM_IV_SIZE]); - - fn set_aad(&mut self, aad: &[u8]); - - fn decrypt(&mut self, input: &[u8], output: &mut [u8]); - - fn decrypt_in_place(&mut self, data: &mut [u8]); - - fn finish_decrypt(&mut self, expected_tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; -} diff --git a/src/crypto/kyber1024.rs b/src/crypto/kyber1024.rs new file mode 100644 index 0000000..5cccc30 --- /dev/null +++ b/src/crypto/kyber1024.rs @@ -0,0 +1,36 @@ +use rand_core::{CryptoRng, RngCore}; + +/// The size of a Kyber1024 public key, which is 1568 bytes. +pub const KYBER_PUBLIC_KEY_SIZE: usize = 1568; +/// The size of a Kyber1024 KEM ciphertext, which is 1568 bytes. +pub const KYBER_CIPHERTEXT_SIZE: usize = 1568; +/// The size of a Kyber1024 KEM plaintext, which is 32 bytes. +pub const KYBER_PLAINTEXT_SIZE: usize = 32; + +/// Instances must securely delete the private key when dropped. +pub trait Kyber1024PrivateKey: Sized + Send + Sync { + /// Generate a Kyber1024 private key and public key pair, and return the raw bytes of the public + /// key. + /// The private key will be temporarily held in memory but the public key will be immediately + /// sent to the remote peer. + /// + /// This function may use the provided RNG or its own, so long as the output is cryptographically random. + fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]); + /// Generate a Kyber1024 key encapsulation based on the given `public_key`, and return the + /// raw bytes of the generated ciphertext and plaintext. The ciphertext is immediately sent to + /// the remote peer and the plaintext is immediately hashed, both are quickly deleted. + /// + /// This function may use the provided RNG or its own, so long as the output is cryptographically random. + /// + /// **CRITICAL**: This must return `None` if the given `public_key` is invalid in any way + /// according to the Kyber1024 spec. + #[must_use] + fn encapsulate(rng: &mut Rng, public_key: &[u8; KYBER_PUBLIC_KEY_SIZE], plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE]) -> Option<[u8; KYBER_CIPHERTEXT_SIZE]>; + /// Decapsulate a Kyber1024 `ciphertext` received from the remote peer, retreiving + /// the raw bytes of the original plaintext. This plaintext is immediately hashed and deleted. + /// + /// **CRITICAL**: This must return `None` if the given `ciphertext` is invalid in any way + /// according to the Kyber1024 spec. + #[must_use] + fn decapsulate(&self, ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE], plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE]) -> bool; +} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 0c7f601..22dcb9a 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,12 +1,25 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. pub mod aes; -pub mod aes_gcm; pub mod p384; -pub mod secret; pub mod sha512; +pub mod kyber1024; // We re-export our dependencies so it is less of a headache for the implementor to use the same // exact version of them. pub use pqc_kyber; pub use rand_core; + +/// Constant time byte slice equality. +pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { + let (a, b) = (a.as_ref(), b.as_ref()); + if a.len() == b.len() { + let mut x = 0u8; + for (aa, bb) in a.iter().zip(b.iter()) { + x |= *aa ^ *bb; + } + x == 0 + } else { + false + } +} diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs index d340356..647ac93 100644 --- a/src/crypto/p384.rs +++ b/src/crypto/p384.rs @@ -1,47 +1,46 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. - -use super::rand_core::{CryptoRng, RngCore}; +use rand_core::{CryptoRng, RngCore}; +/// The size in bytes of a P-384 public key when in compressed SEC1-encoded format. pub const P384_PUBLIC_KEY_SIZE: usize = 49; +/// The size in bytes of the raw output of ECDH between a P-384 public and private key. pub const P384_ECDH_SHARED_SECRET_SIZE: usize = 48; /// A NIST P-384 ECDH/ECDSA public key. pub trait P384PublicKey: Sized + Send + Sync { - /// Create a p384 public key from raw bytes. + /// Create a P-384 public key from raw bytes. /// - /// **CRITICAL**: This function must return `None` if the input `raw_key` is not on the P384 curve, - /// or if it breaks the P384 standard in any other way. + /// **CRITICAL**: This function must return `None` if the input `raw_key` is not on the P-384 + /// curve, or if it breaks the P-384 spec in any other way. fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option; /// Get the raw bytes that uniquely define the public key. /// - /// This must output the standard 49 byte NIST encoding of P384 public keys. - fn as_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE]; + /// This must output the compressed SEC1 NIST encoding of P-384 public keys. + fn to_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE]; } /// A NIST P-384 ECDH/ECDSA public/private key pair. /// /// Instances must securely delete the private key when dropped. -pub trait P384KeyPair: Send + Sync { +pub trait P384KeyPair { + /// The `PublicKeyP384` implementation which matches this `KeyPairP384` implementation. type PublicKey: P384PublicKey; - type Rng: RngCore + CryptoRng; - /// Randomly generate a new p384 keypair. - /// This function may use the provided RNG or it's own, - /// so long as the produced keys are cryptographically random. - fn generate(rng: &mut Self::Rng) -> Self; + /// Randomly generate a new P-384 keypair. + /// + /// This function may use the provided RNG or its own, so long as the output is cryptographically random. + fn generate(rng: &mut Rng) -> Self; /// Get the raw bytes that uniquely define the public key. /// - /// This must output the standard 49 byte NIST encoding of P384 public keys. - fn public_key_bytes(&self) -> &[u8; P384_PUBLIC_KEY_SIZE]; + /// This must output the compressed SEC1 NIST encoding of P-384 public keys. + fn public_key_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE]; /// Perform ECDH key agreement, writing the raw (un-hashed!) ECDH secret to `output`. /// - /// **CRITICAL**: This function must return `false` if key agreement between this private key and - /// the input `other_public` key would result in an invalid, non-standard or predictable ECDH secret. - /// Please refer to the NIST spec for P384 ECDH key agreement, or better yet use a peer reviewed + /// **CRITICAL**: This function must return `None` if key agreement between this private key and + /// the input `public_key` key would result in an invalid, non-standard or predictable ECDH secret. + /// Please refer to the NIST spec for P-384 ECDH key agreement, or better yet use a peer reviewed /// library that has already implemented this correctly. - /// - /// If this function returns `false` then the contents of `output` will be discarded. - fn agree(&self, other_public: &Self::PublicKey, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool; + #[must_use] + fn agree(&self, public_key: &Self::PublicKey, ecdh_out: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool; } diff --git a/src/crypto/secret.rs b/src/crypto/secret.rs deleted file mode 100644 index 902eb6d..0000000 --- a/src/crypto/secret.rs +++ /dev/null @@ -1,127 +0,0 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. -use std::{convert::TryInto, ptr::write_volatile}; - -/// Constant time byte slice equality. -pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { - let (a, b) = (a.as_ref(), b.as_ref()); - if a.len() == b.len() { - let mut x = 0u8; - for (aa, bb) in a.iter().zip(b.iter()) { - x |= *aa ^ *bb; - } - x == 0 - } else { - false - } -} - -/// Container for secrets that clears them on drop. -/// -/// We can't be totally sure that things like libraries are doing this and it's -/// hard to get every use of a secret anywhere, but using this in our code at -/// least reduces the number of secrets that are left lying around in memory. -/// -/// This is generally a low-risk thing since it's process memory that's protected, -/// but it's still not a bad idea due to things like swap or obscure side channel -/// attacks that allow memory to be read. -#[derive(Clone)] -#[repr(transparent)] -pub struct Secret(pub [u8; L]); - -impl Secret { - /// Create a new all-zero secret. - pub fn new() -> Self { - Self([0_u8; L]) - } - /// Copy bytes into secret, then delete the previous value, will panic if the slice does not match the size of this secret. - pub fn from_bytes_then_delete(b: &mut [u8]) -> Self { - let ret = Self(b.try_into().unwrap()); - b.fill(0); - ret - } - /// Moves bytes into secret, will panic if the slice does not match the size of this secret. - /// This is unsafe because it will not destroy the contents of its input. - /// # Safety - /// Make sure the contents of the input are securely deleted. - pub unsafe fn from_bytes(b: &[u8]) -> Self { - Self(b.try_into().unwrap()) - } - - pub fn as_ptr(&self) -> *const u8 { - self.0.as_ptr() - } - - pub fn as_bytes(&self) -> &[u8; L] { - &self.0 - } - - /// Get the first N bytes of this secret as a fixed length array. - pub fn first_n(&self) -> &[u8; N] { - assert!(N <= L); - unsafe { &*self.0.as_ptr().cast() } - } - - /// Clone the first N bytes of this secret as another secret. - pub fn first_n_clone(&self) -> Secret { - Secret::(*self.first_n()) - } - - pub fn overwrite(&mut self, src: &Self) { - self.0.copy_from_slice(&src.0); - } - pub fn overwrite_first_n(&mut self, src: &Secret) { - let amount = N.min(L); - self.0[..amount].copy_from_slice(&src.0[..amount]); - } - - pub fn eq_bytes(&self, other: &[u8]) -> bool { - secure_eq(&self.0, other) - } -} - -impl Drop for Secret { - fn drop(&mut self) { - unsafe { - for v in self.0.iter_mut() { - write_volatile(v, 0u8); - } - } - } -} - -impl Default for Secret { - fn default() -> Self { - Self([0u8; L]) - } -} - -impl AsRef<[u8]> for Secret { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl AsRef<[u8; L]> for Secret { - fn as_ref(&self) -> &[u8; L] { - &self.0 - } -} - -impl AsMut<[u8]> for Secret { - fn as_mut(&mut self) -> &mut [u8] { - &mut self.0 - } -} - -impl AsMut<[u8; L]> for Secret { - fn as_mut(&mut self) -> &mut [u8; L] { - &mut self.0 - } -} - -impl PartialEq for Secret { - fn eq(&self, other: &Self) -> bool { - secure_eq(&self.0, &other.0) - } -} -impl Eq for Secret {} diff --git a/src/crypto/sha512.rs b/src/crypto/sha512.rs index c2161be..bfb4b0d 100644 --- a/src/crypto/sha512.rs +++ b/src/crypto/sha512.rs @@ -2,41 +2,24 @@ pub const SHA512_HASH_SIZE: usize = 64; -/// Opaque SHA-512 implementation. -/// Does not need to be threadsafe. -pub trait Sha512 { - /// Allocate memory on the stack or heap for Sha512. - /// An instance of Sha512 will only ever be held on the stack. +/// A SHA-512 implementation. +pub trait HashSha512 { + /// Create a new instance of SHA-512 for streaming data to. fn new() -> Self; - - /// Reinitialize the internal state of the hash function for a fresh input. - fn reset(&mut self); - - fn update(&mut self, input: &[u8]); - /// Finish hashing the input and write the final hash to output. - /// - /// After this function is called, this instance of Sha512 will either be dropped - /// or `reset` will be called. - fn finish(&mut self, output: &mut [u8; SHA512_HASH_SIZE]); + /// Update the instance of SHA-512 with input `data`. + /// This must update the state of SHA-512 as if `data` was appended to the previous input. + fn update(&mut self, data: &[u8]); + /// Finish streaming input and output the final hash. + fn finish_and_reset(&mut self, output: &mut [u8; SHA512_HASH_SIZE]); } + /// Opaque HMAC-SHA-512 implementation. /// Does not need to be threadsafe. pub trait HmacSha512 { - /// Allocate memory on the stack or heap for HmacSha512. - /// An instance of HmacSha512 will only ever be held on the stack. - /// - /// `reset` will always be called before `update` on a new instance of HmacSha512, - /// to make sure there is always a set key. + /// Allocate space on the stack or heap for repeated Hmac invocations. fn new() -> Self; - /// Reinitialize the internal state of the hash function for a fresh input. - /// The provided key should replace the previous Hmac key. - fn reset(&mut self, key: &[u8]); - - fn update(&mut self, input: &[u8]); - /// Finish hashing the input and write the final hash to output. - /// - /// After this function is called, this instance of HmacSha512 will either be dropped - /// or `reset` will be called. - fn finish(&mut self, output: &mut [u8; SHA512_HASH_SIZE]); + /// Pure function for computing a single HMAC Hash. Repeat invocations of this function should + /// have no effect on each other. + fn hash(&mut self, key: &[u8], full_input: &[u8], output: &mut [u8; SHA512_HASH_SIZE]); } diff --git a/src/lib.rs b/src/lib.rs index 46d0713..16c9ec0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,19 +8,24 @@ pub mod crypto; mod applicationlayer; -mod frag_cache; -mod fragged; -mod handshake_cache; +//mod frag_cache; +//mod fragged; +//mod handshake_cache; mod indexed_heap; -mod log_event; +//mod log_event; mod proto; mod ratchet_state; mod symmetric_state; -mod zssp; +mod antireplay; +mod challenge; +pub mod result; +//mod zssp; +mod zeta; +mod context; -pub mod error; +//pub mod error; pub use crate::applicationlayer::ApplicationLayer; -pub use crate::log_event::LogEvent; -pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_SIZE}; +//pub use crate::log_event::LogEvent; +//pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_SIZE}; pub use crate::ratchet_state::RatchetState; -pub use crate::zssp::{Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; +//pub use crate::zssp::{Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/log_event.rs b/src/log_event.rs index bdc94b5..be543aa 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -7,7 +7,7 @@ */ use std::sync::Arc; -use crate::{ApplicationLayer, Session}; +use crate::{ApplicationLayer, zssp::Session}; /// ZSSP events that might be interesting to log or aggregate into metrics. pub enum LogEvent<'a, Application: ApplicationLayer> { @@ -30,7 +30,7 @@ pub enum LogEvent<'a, Application: ApplicationLayer> { ReceiveUncheckedXK2, ReceiveValidXK2(&'a Arc>), ReceiveUncheckedXK3, - ReceiveValidXK3(&'a Application::Data), + ReceiveValidXK3(&'a Application::SessionData), ReceiveUncheckedKK1, ReceiveValidKK1(&'a Arc>), ReceiveUncheckedKK2, diff --git a/src/proto.rs b/src/proto.rs index 634e8d5..0d466a7 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -1,110 +1,55 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ -use std::hash::Hasher; -use std::mem::size_of; +/* Common constants */ -use crate::crypto::aes_gcm::AES_GCM_TAG_SIZE; -use crate::crypto::p384::P384_PUBLIC_KEY_SIZE; -use crate::crypto::pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; -use crate::crypto::sha512::{Sha512, SHA512_HASH_SIZE}; -use hex_literal::hex; - -/// Minimum size of a valid physical ZSSP packet of any type. Anything smaller is discarded. -pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; +use crate::crypto::{sha512::SHA512_HASH_SIZE, p384::P384_PUBLIC_KEY_SIZE, kyber1024::{KYBER_PUBLIC_KEY_SIZE, KYBER_CIPHERTEXT_SIZE}, aes::AES_GCM_TAG_SIZE}; /// Minimum physical MTU for ZSSP to function. +/// If an MTU is passed to ZSSP that is lower than this, it will be ignored and instead this value +/// will be used. pub const MIN_TRANSPORT_MTU: usize = 128; -pub const RATCHET_SIZE: usize = 32; +pub(crate) const KID_SIZE: usize = 4; -/// The application has the ability to attach a data payload to Alice's handshake. -/// It will be the first payload Bob receives from Alice. -/// The application also must attach a static public identity to their handshake. -/// The combined size of both in bytes must be at most this value. -/// -/// If not ZSSP will return `OpenError::DataTooLarge` and refuse to create a session object. -pub const MAX_IDENTITY_BLOB_SIZE: usize = NoiseXKPattern3::MAX_SIZE - NoiseXKPattern3::MIN_SIZE; +/* Challenge protocol constants */ -/// Initial value of 'h'. -/// echo -n 'Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512' | shasum -a 512 -pub(crate) const INITIAL_H: [u8; SHA512_HASH_SIZE] = - hex!("cd1f422196a5a614e24392cf34dcbf340ee61ad6ee6834274ff35fd42a7a5c44d04a045101555548a291778dd036b93ae21005a26c003213f57a5df9fb17f745"); -/// Initial value of 'ck' for rekeying. -/// echo -n 'Noise_KKpsk0_P384_AESGCM_SHA512' | shasum -a 512 -pub(crate) const INITIAL_H_REKEY: [u8; SHA512_HASH_SIZE] = - hex!("daeedd651ac9c5173f2eaaff996beebac6f3f1bfe9a70bb1cc54fa1fb2bf46260d71a3c4fb4d4ee36f654c31773a8a15e5d5be974a0668dc7db70f4e13ed172e"); +pub(crate) const SALT_SIZE: usize = 32; -pub(crate) const SESSION_ID_SIZE: usize = 4; +pub(crate) const COUNTER_SIZE: usize = 8; +pub(crate) const MAC_SIZE: usize = 16; +pub(crate) const POW_SIZE: usize = 8; +pub(crate) const POW_START: usize = COUNTER_SIZE + MAC_SIZE; -pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_1: u8 = 0; -pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_2: u8 = 1; -pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_3: u8 = 2; -pub(crate) const PACKET_TYPE_KEY_CONFIRM: u8 = 3; -pub(crate) const PACKET_TYPE_ACK: u8 = 4; -pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_1: u8 = 5; -pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_2: u8 = 6; -pub(crate) const PACKET_TYPE_SESSION_REJECTED: u8 = 7; -pub(crate) const PACKET_TYPE_DATA: u8 = 8; -pub(crate) const PACKET_TYPE_BOB_DOS_CHALLENGE: u8 = 9; -pub(crate) const PACKET_TYPE_RANGE_TRANSPORT: std::ops::Range = 3..9; - -/// Noise asks that the counter be initialized to 0 but for out of order reasons we have -/// to start it at 1. -/// Since with unreliable transport the first counter could always end up dropped this is -/// functionally equivalent to initializing to 0. -pub(crate) const INIT_COUNTER: u64 = 0; -pub(crate) const LABEL_RATCHET_STATE: u8 = b'R'; -pub(crate) const LABEL_HEADER_KEY: u8 = b'H'; -pub(crate) const LABEL_KEX_KEY: u8 = b'K'; - -/// Size of keys used during derivation, mixing, etc. -pub(crate) const NOISE_HASHLEN: usize = SHA512_HASH_SIZE; +pub(crate) const CHALLENGE_SIZE: usize = COUNTER_SIZE + MAC_SIZE + POW_SIZE; +pub(crate) const DIFFICULTY: u32 = 13; +/* Fragmentation constants */ +/* +Header: + [0..4] recipient key id +-- start AES(ck_es * h_e_e1_p) encrypted block -- + [5] fragment number (0..254) + [4] fragment count (1..255) +-- start packet nonce -- + [6] reserved zero + [7] packet type + [8..16] 64-bit counter +*/ pub(crate) const HEADER_SIZE: usize = 16; -pub(crate) const HEADER_PROTECT_ENC_START: usize = 4; -pub(crate) const HEADER_PROTECT_ENC_END: usize = 20; -pub(crate) const CHALLENGE_COUNTER_SIZE: usize = 8; -pub(crate) const CHALLENGE_MAC_SIZE: usize = 16; -pub(crate) const CHALLENGE_POW_SIZE: usize = 8; -pub(crate) const CHALLENGE_SALT_SIZE: usize = 32; +pub(crate) const PACKET_NONCE_SIZE: usize = 10; -pub(crate) const MAX_NOISE_HANDSHAKE_SIZE: usize = MAX_FRAGMENTS * MIN_TRANSPORT_MTU; -pub(crate) const CONTROL_PACKET_MAX_SIZE: usize = HEADER_SIZE + NoiseKKPattern1or2::SIZE + AES_GCM_TAG_SIZE; -pub(crate) const CONTROL_PACKET_MIN_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; +pub(crate) const HEADER_AUTH_START: usize = 4; +pub(crate) const HEADER_AUTH_END: usize = 20; +pub(crate) const PACKET_NONCE_START: usize = HEADER_SIZE - PACKET_NONCE_SIZE; -/// Determines the number of counters a session will remember. If a counter arrives over -/// this amount out of order relative to other received counters, it is likely to be -/// rejected on the basis that the session can't remember if this counter was replayed. -/// Increasing this value makes a session consume more memory. -pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 64; -/// Maximum number of counter steps that the counter is allowed to skip ahead. -/// This cannot be changed away from 2^24 without changing the header nonce handling code. -pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 16777216; -/// Similar to `COUNTER_WINDOW_MAX_OOO`, except this governs the receive context challenge -/// counter rather than the session counter. -/// When Bob issues a challenge to Alice to mitigate DDOS, Bob will only accept Alice's -/// response once, and then its attached counter is added to the window. -pub(crate) const CHALLENGE_COUNTER_WINDOW_MAX_OOO: usize = 32; -/// We hard-expire the Noise counter long before we reach u64::MAX because of the ABA problem. -/// Over (1<<16) threads would have to attempt to increment the counter at the same time -/// to overflow it. -/// Having (1<<16) threads active at the same time would crash basically any system. -pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - (1 << 16); +pub(crate) const FRAGMENT_NO_IDX: usize = 4; +pub(crate) const FRAGMENT_COUNT_IDX: usize = 5; + +pub(crate) const MAX_FRAGMENTS: usize = 48; -/// Maximum number of fragments a single packet may be split into. If a packet cannot fit -/// into this number of fragments it will be dropped. -pub(crate) const MAX_FRAGMENTS: usize = 48; // hard protocol max: 63 /// Maximum window over which session packets may be reordered to be defragmented and /// reassembled. Out of order fragments may be dropped in favor of newer fragments. /// Increasing this value makes a session consume more significantly more memory. pub(crate) const SESSION_MAX_FRAGMENTS_OOO: usize = 32; - /// The maximum number of unassociated packets that a receive context will cache. /// Additional packets will either be dropped or cause a different packet to be dropped /// from the cache. @@ -124,8 +69,10 @@ pub(crate) const MAX_UNASSOCIATED_HANDSHAKE_STATES: usize = 32; /// The maximum size a packet that is not associated to a session may be. /// Excludes the size of headers for fragmentation. -pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = NoiseXKPattern1::MAX_SIZE - HEADER_SIZE; +pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = HANDSHAKE_HELLO_MAX_SIZE - HEADER_SIZE; + +/* Key exchange constants */ /* XKhfs+psk2: <- s @@ -142,155 +89,53 @@ KKpsk0: -> psk, e, es, ss <- e, ee, se */ -/* -Header: - [0..4] recipient key id --- start AES(ck_es * h_e_e1_p) encrypted block -- - [4] fragment count (1..255) - [5] fragment number (0..254) - [6] reserved zero --- start AES-GCM Nonce -- - [7] packet type - [8..16] 64-bit counter or packet id -*/ -/// The first packet in Noise_XK exchange containing Alice's ephemeral keys, key id, -/// and a random symmetric key to protect header fragmentation fields for this session. -#[repr(C, packed)] -pub(crate) struct NoiseXKPattern1 { - pub header: [u8; HEADER_SIZE], - /// -- start prologue -- - pub alice_key_id: [u8; SESSION_ID_SIZE], - /// -- end prologue -- - pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], - /// -- start AES-GCM(k_es) encrypted section - pub noise_e1: [u8; KYBER_PUBLICKEYBYTES], - /// -- end encrypted section - pub e1_gcm_tag: [u8; AES_GCM_TAG_SIZE], - pub payload: [u8; RATCHET_SIZE + RATCHET_SIZE + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE], -} +pub(crate) const HASHLEN: usize = SHA512_HASH_SIZE; +/// The size in bytes of both a ratchet key and a ratchet fingerprint. +pub const RATCHET_SIZE: usize = 32; -#[repr(C, packed)] -pub(crate) struct ChallengeResponse { - pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE], - pub challenge_mac: [u8; CHALLENGE_MAC_SIZE], - pub challenge_pow: [u8; CHALLENGE_POW_SIZE], -} +pub(crate) const PROTOCOL_NAME_NOISE_XK: [u8; HASHLEN] = *b"Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +pub(crate) const PROTOCOL_NAME_NOISE_KK: [u8; HASHLEN] = + *b"Noise_KKpsk0_P384_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; -impl NoiseXKPattern1 { - pub const PROLOGUE_START: usize = HEADER_SIZE; - pub const PROLOGUE_END: usize = Self::PROLOGUE_START + SESSION_ID_SIZE; - pub const E1_ENC_START: usize = Self::PROLOGUE_END + P384_PUBLIC_KEY_SIZE; - pub const E1_AUTH_START: usize = Self::E1_ENC_START + KYBER_PUBLICKEYBYTES; - pub const P_ENC_START: usize = Self::E1_AUTH_START + AES_GCM_TAG_SIZE; +pub(crate) const LABEL_OTP_TO_RATCHET: &[u8; 19] = b"ZSSP_OTP_TO_RATCHET"; +pub(crate) const LABEL_KBKDF_CHAIN: &[u8; 4] = b"ZSSP"; +pub(crate) const LABEL_RATCHET_STATE: &[u8; 4] = b"ASKR"; +pub(crate) const LABEL_HEADER_KEY: &[u8; 4] = b"ASKH"; +pub(crate) const LABEL_KEX_KEY: &[u8; 4] = b"ASKK"; - pub const MIN_SIZE: usize = Self::P_ENC_START + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE; - pub const MAX_SIZE: usize = Self::MIN_SIZE + RATCHET_SIZE + RATCHET_SIZE; -} -impl ChallengeResponse { - pub const SIZE: usize = CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; -} +pub(crate) const INIT_COUNTER: u64 = 0; +pub(crate) const EXPIRE_AFTER_USES: u64 = 4294967295; +pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 64; +pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 1 << 24; -#[repr(C, packed)] -pub(crate) struct BobDOSChallenge { - pub header: [u8; HEADER_SIZE], - pub alice_key_id: [u8; SESSION_ID_SIZE], - pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE], - pub challenge_mac: [u8; CHALLENGE_MAC_SIZE], - pub prior_challenge_pow: [u8; CHALLENGE_POW_SIZE], -} +/* Packet constants */ -impl BobDOSChallenge { - pub const SIZE: usize = HEADER_SIZE + SESSION_ID_SIZE + CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; -} +pub(crate) const PACKET_TYPE_HANDSHAKE_HELLO: u8 = 0; +pub(crate) const PACKET_TYPE_HANDSHAKE_RESPONSE: u8 = 1; +pub(crate) const PACKET_TYPE_HANDSHAKE_COMPLETION: u8 = 2; +pub(crate) const PACKET_TYPE_KEY_CONFIRM: u8 = 3; +pub(crate) const PACKET_TYPE_ACK: u8 = 4; +pub(crate) const PACKET_TYPE_REKEY_INIT: u8 = 5; +pub(crate) const PACKET_TYPE_REKEY_COMPLETE: u8 = 6; +pub(crate) const PACKET_TYPE_SESSION_REJECTED: u8 = 7; +pub(crate) const PACKET_TYPE_DATA: u8 = 8; +pub(crate) const PACKET_TYPE_CHALLENGE: u8 = 9; +pub(crate) const PACKET_TYPE_USES_COUNTER_RANGE: std::ops::Range = 3..9; -/// The response to NoiseXKPattern1 containing Bob's ephemeral keys. -#[repr(C, packed)] -pub(crate) struct NoiseXKPattern2 { - pub header: [u8; HEADER_SIZE], - pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], - /// -- start AES-GCM(k_es_ee) encrypted section - pub noise_ekem1: [u8; KYBER_CIPHERTEXTBYTES], - /// -- end encrypted section - pub ekem1_gcm_tag: [u8; AES_GCM_TAG_SIZE], - /// -- start AES-GCM(k_es_ee_ekem1_psk) encrypted section - pub bob_key_id: [u8; SESSION_ID_SIZE], - /// -- end encrypted section - pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], -} +pub(crate) const MAX_HANDSHAKE_SIZE: usize = MAX_FRAGMENTS * MIN_TRANSPORT_MTU; -impl NoiseXKPattern2 { - pub const EKEM1_ENC_START: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE; - pub const EKEM1_AUTH_START: usize = Self::EKEM1_ENC_START + KYBER_CIPHERTEXTBYTES; - pub const P_ENC_START: usize = Self::EKEM1_AUTH_START + AES_GCM_TAG_SIZE; - pub const P_AUTH_START: usize = Self::P_ENC_START + SESSION_ID_SIZE; - pub const P_AUTH_END: usize = Self::P_AUTH_START + AES_GCM_TAG_SIZE; - pub const SIZE: usize = Self::P_AUTH_END; -} +pub(crate) const HANDSHAKE_HELLO_MIN_SIZE: usize = KID_SIZE + P384_PUBLIC_KEY_SIZE + KYBER_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; +pub(crate) const HANDSHAKE_HELLO_MAX_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + RATCHET_SIZE; -/// Alice's final response containing her identity (she already knows Bob's) and meta-data. -/// While Alice's response does match what is described in this struct, -/// this struct is unused because it would contain variable length fields. -/// It is present here for documentation purposes. -#[repr(C, packed)] -pub(crate) struct NoiseXKPattern3 { - pub header: [u8; HEADER_SIZE], - /// -- start AES-GCM(k_es_ee_ekem1_psk) encrypted section - pub noise_s: [u8; P384_PUBLIC_KEY_SIZE], - /// -- end encrypted section - pub s_gcm_tag: [u8; AES_GCM_TAG_SIZE], - /// -- start AES-GCM(k_es_ee_ekem1_psk_se) encrypted section - pub alice_blob: [u8; 0], - /// -- end encrypted section - pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], -} -impl NoiseXKPattern3 { - pub const MIN_SIZE: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; - pub const MAX_SIZE: usize = MAX_NOISE_HANDSHAKE_SIZE; -} +pub(crate) const HANDSHAKE_RESPONSE_SIZE: usize = P384_PUBLIC_KEY_SIZE + KYBER_CIPHERTEXT_SIZE + AES_GCM_TAG_SIZE + KID_SIZE + AES_GCM_TAG_SIZE; -#[repr(C, packed)] -pub(crate) struct NoiseKKPattern1or2 { - pub header: [u8; HEADER_SIZE], - pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], - pub key_id: [u8; SESSION_ID_SIZE], - pub gcm_tag: [u8; AES_GCM_TAG_SIZE], - pub kek_tag: [u8; AES_GCM_TAG_SIZE], -} -impl NoiseKKPattern1or2 { - pub const ENC_START: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE; - pub const AUTH_START: usize = Self::ENC_START + SESSION_ID_SIZE; - pub const AUTH_END: usize = Self::AUTH_START + AES_GCM_TAG_SIZE; - pub const SIZE: usize = Self::AUTH_END + AES_GCM_TAG_SIZE; -} +pub(crate) const HANDSHAKE_COMPLETION_MIN_SIZE: usize = P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + 0 + AES_GCM_TAG_SIZE; +pub(crate) const HANDSHAKE_COMPLETION_MAX_SIZE: usize = MAX_HANDSHAKE_SIZE; -// Annotate only these structs as being compatible with byte_array_as_proto_buffer(). These structs -// are packed flat buffers containing only byte or byte array fields, making them safe to treat -// this way even on architectures that require type size aligned access. -pub(crate) trait ProtocolFlatBuffer {} -impl ProtocolFlatBuffer for NoiseXKPattern1 {} -impl ProtocolFlatBuffer for NoiseXKPattern2 {} -impl ProtocolFlatBuffer for NoiseKKPattern1or2 {} -impl ProtocolFlatBuffer for BobDOSChallenge {} -impl ProtocolFlatBuffer for ChallengeResponse {} +pub(crate) const KEY_CONFIRMATION_SIZE: usize = AES_GCM_TAG_SIZE; +pub(crate) const ACKNOWLEDGEMENT_SIZE: usize = AES_GCM_TAG_SIZE; +pub(crate) const SESSION_REJECTED_SIZE: usize = AES_GCM_TAG_SIZE; -#[inline(always)] -pub(crate) fn byte_array_as_proto_buffer(b: &[u8]) -> &B { - assert!(b.len() >= size_of::()); - unsafe { &*b.as_ptr().cast() } -} +pub(crate) const REKEY_SIZE: usize = P384_PUBLIC_KEY_SIZE + KID_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; -#[inline(always)] -pub(crate) fn byte_array_as_proto_buffer_mut(b: &mut [u8]) -> &mut B { - assert!(b.len() >= size_of::()); - unsafe { &mut *b.as_mut_ptr().cast() } -} -/// Trick rust into letting us use a hasher that returns more than 64 bits. -pub(crate) struct ShaHasher<'a, ShaImpl: Sha512>(pub &'a mut ShaImpl); -impl<'a, ShaImpl: Sha512> Hasher for ShaHasher<'a, ShaImpl> { - fn finish(&self) -> u64 { - panic!() - } - fn write(&mut self, bytes: &[u8]) { - self.0.update(bytes) - } -} +pub(crate) const MAX_IDENTITY_SIZE: usize = MAX_HANDSHAKE_SIZE - HANDSHAKE_COMPLETION_MIN_SIZE; diff --git a/src/proto_old.rs b/src/proto_old.rs new file mode 100644 index 0000000..2f83f3e --- /dev/null +++ b/src/proto_old.rs @@ -0,0 +1,296 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::hash::Hasher; +use std::mem::size_of; + +use crate::crypto::aes::AES_GCM_TAG_SIZE; +use crate::crypto::p384::P384_PUBLIC_KEY_SIZE; +use crate::crypto::pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; +use crate::crypto::sha512::{HashSha512, SHA512_HASH_SIZE}; +use hex_literal::hex; + +/// Minimum size of a valid physical ZSSP packet of any type. Anything smaller is discarded. +pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; + +/// Minimum physical MTU for ZSSP to function. +pub const MIN_TRANSPORT_MTU: usize = 128; + +pub const RATCHET_SIZE: usize = 32; + +/// The application has the ability to attach a data payload to Alice's handshake. +/// It will be the first payload Bob receives from Alice. +/// The application also must attach a static public identity to their handshake. +/// The combined size of both in bytes must be at most this value. +/// +/// If not ZSSP will return `OpenError::DataTooLarge` and refuse to create a session object. +pub const MAX_IDENTITY_BLOB_SIZE: usize = NoiseXKPattern3::MAX_SIZE - NoiseXKPattern3::MIN_SIZE; + +/// Initial value of 'h'. +/// echo -n 'Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512' | shasum -a 512 +pub(crate) const INITIAL_H: [u8; SHA512_HASH_SIZE] = + hex!("cd1f422196a5a614e24392cf34dcbf340ee61ad6ee6834274ff35fd42a7a5c44d04a045101555548a291778dd036b93ae21005a26c003213f57a5df9fb17f745"); +/// Initial value of 'ck' for rekeying. +/// echo -n 'Noise_KKpsk0_P384_AESGCM_SHA512' | shasum -a 512 +pub(crate) const INITIAL_H_REKEY: [u8; SHA512_HASH_SIZE] = + hex!("daeedd651ac9c5173f2eaaff996beebac6f3f1bfe9a70bb1cc54fa1fb2bf46260d71a3c4fb4d4ee36f654c31773a8a15e5d5be974a0668dc7db70f4e13ed172e"); + +pub(crate) const SESSION_ID_SIZE: usize = 4; + +pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_1: u8 = 0; +pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_2: u8 = 1; +pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_3: u8 = 2; +pub(crate) const PACKET_TYPE_KEY_CONFIRM: u8 = 3; +pub(crate) const PACKET_TYPE_ACK: u8 = 4; +pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_1: u8 = 5; +pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_2: u8 = 6; +pub(crate) const PACKET_TYPE_SESSION_REJECTED: u8 = 7; +pub(crate) const PACKET_TYPE_DATA: u8 = 8; +pub(crate) const PACKET_TYPE_BOB_DOS_CHALLENGE: u8 = 9; +pub(crate) const PACKET_TYPE_RANGE_TRANSPORT: std::ops::Range = 3..9; + +/// Noise asks that the counter be initialized to 0 but for out of order reasons we have +/// to start it at 1. +/// Since with unreliable transport the first counter could always end up dropped this is +/// functionally equivalent to initializing to 0. +pub(crate) const INIT_COUNTER: u64 = 0; +pub(crate) const LABEL_RATCHET_STATE: u8 = b'R'; +pub(crate) const LABEL_HEADER_KEY: u8 = b'H'; +pub(crate) const LABEL_KEX_KEY: u8 = b'K'; + +/// Size of keys used during derivation, mixing, etc. +pub(crate) const HASHLEN: usize = SHA512_HASH_SIZE; + +pub(crate) const HEADER_SIZE: usize = 16; +pub(crate) const HEADER_PROTECT_ENC_START: usize = 4; +pub(crate) const HEADER_PROTECT_ENC_END: usize = 20; +pub(crate) const CHALLENGE_COUNTER_SIZE: usize = 8; +pub(crate) const CHALLENGE_MAC_SIZE: usize = 16; +pub(crate) const CHALLENGE_POW_SIZE: usize = 8; +pub(crate) const CHALLENGE_SALT_SIZE: usize = 32; + +pub(crate) const MAX_NOISE_HANDSHAKE_SIZE: usize = MAX_FRAGMENTS * MIN_TRANSPORT_MTU; +pub(crate) const CONTROL_PACKET_MAX_SIZE: usize = HEADER_SIZE + NoiseKKPattern1or2::SIZE + AES_GCM_TAG_SIZE; +pub(crate) const CONTROL_PACKET_MIN_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; + +/// Determines the number of counters a session will remember. If a counter arrives over +/// this amount out of order relative to other received counters, it is likely to be +/// rejected on the basis that the session can't remember if this counter was replayed. +/// Increasing this value makes a session consume more memory. +pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 64; +/// Maximum number of counter steps that the counter is allowed to skip ahead. +/// This cannot be changed away from 2^24 without changing the header nonce handling code. +pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 16777216; +/// Similar to `COUNTER_WINDOW_MAX_OOO`, except this governs the receive context challenge +/// counter rather than the session counter. +/// When Bob issues a challenge to Alice to mitigate DDOS, Bob will only accept Alice's +/// response once, and then its attached counter is added to the window. +pub(crate) const CHALLENGE_COUNTER_WINDOW_MAX_OOO: usize = 32; +/// We hard-expire the Noise counter long before we reach u64::MAX because of the ABA problem. +/// Over (1<<16) threads would have to attempt to increment the counter at the same time +/// to overflow it. +/// Having (1<<16) threads active at the same time would crash basically any system. +pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - (1 << 16); + +/// Maximum number of fragments a single packet may be split into. If a packet cannot fit +/// into this number of fragments it will be dropped. +pub(crate) const MAX_FRAGMENTS: usize = 48; // hard protocol max: 63 +/// Maximum window over which session packets may be reordered to be defragmented and +/// reassembled. Out of order fragments may be dropped in favor of newer fragments. +/// Increasing this value makes a session consume more significantly more memory. +pub(crate) const SESSION_MAX_FRAGMENTS_OOO: usize = 32; + +/// The maximum number of unassociated packets that a receive context will cache. +/// Additional packets will either be dropped or cause a different packet to be dropped +/// from the cache. +/// Larger values consume more memory but provide better reliability and DDOS resistance. +pub(crate) const MAX_UNASSOCIATED_PACKETS: usize = 32; +/// The maximum number of fragments of unassociated packets that a receive context will +/// cache. +/// All unassociated fragments share the same buffer, when it fills up additional +/// fragments will be dropped or cause other fragments to be dropped from the cache. +/// Larger values consume more memory but provide better reliability and DDOS resistance. +pub(crate) const MAX_UNASSOCIATED_FRAGMENTS: usize = 32 * 32; +/// The maximum number of `NoiseXKBobHandshakeState` that a receive context will cache. +/// These are extremely large and since Alice has not been authenticated we put a hard +/// limit to how many we cache. +/// Larger values consume more memory but provide better reliability and DDOS resistance. +pub(crate) const MAX_UNASSOCIATED_HANDSHAKE_STATES: usize = 32; + +/// The maximum size a packet that is not associated to a session may be. +/// Excludes the size of headers for fragmentation. +pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = NoiseXKPattern1::MAX_SIZE - HEADER_SIZE; + +/* +XKhfs+psk2: + <- s + ... + -> e, es, e1 + <- e, ee, ekem1, psk + -> s, se +*/ +/* +KKpsk0: + -> s + <- s + ... + -> psk, e, es, ss + <- e, ee, se +*/ +/* +Header: + [0..4] recipient key id +-- start AES(ck_es * h_e_e1_p) encrypted block -- + [4] fragment count (1..255) + [5] fragment number (0..254) + [6] reserved zero +-- start AES-GCM Nonce -- + [7] packet type + [8..16] 64-bit counter or packet id +*/ +/// The first packet in Noise_XK exchange containing Alice's ephemeral keys, key id, +/// and a random symmetric key to protect header fragmentation fields for this session. +#[repr(C, packed)] +pub(crate) struct NoiseXKPattern1 { + pub header: [u8; HEADER_SIZE], + /// -- start prologue -- + pub alice_key_id: [u8; SESSION_ID_SIZE], + /// -- end prologue -- + pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], + /// -- start AES-GCM(k_es) encrypted section + pub noise_e1: [u8; KYBER_PUBLICKEYBYTES], + /// -- end encrypted section + pub e1_gcm_tag: [u8; AES_GCM_TAG_SIZE], + pub payload: [u8; RATCHET_SIZE + RATCHET_SIZE + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE], +} + +#[repr(C, packed)] +pub(crate) struct ChallengeResponse { + pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE], + pub challenge_mac: [u8; CHALLENGE_MAC_SIZE], + pub challenge_pow: [u8; CHALLENGE_POW_SIZE], +} + +impl NoiseXKPattern1 { + pub const PROLOGUE_START: usize = HEADER_SIZE; + pub const PROLOGUE_END: usize = Self::PROLOGUE_START + SESSION_ID_SIZE; + pub const E1_ENC_START: usize = Self::PROLOGUE_END + P384_PUBLIC_KEY_SIZE; + pub const E1_AUTH_START: usize = Self::E1_ENC_START + KYBER_PUBLICKEYBYTES; + pub const P_ENC_START: usize = Self::E1_AUTH_START + AES_GCM_TAG_SIZE; + + pub const MIN_SIZE: usize = Self::P_ENC_START + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE; + pub const MAX_SIZE: usize = Self::MIN_SIZE + RATCHET_SIZE + RATCHET_SIZE; +} +impl ChallengeResponse { + pub const SIZE: usize = CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; +} + +#[repr(C, packed)] +pub(crate) struct BobDOSChallenge { + pub header: [u8; HEADER_SIZE], + pub alice_key_id: [u8; SESSION_ID_SIZE], + pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE], + pub challenge_mac: [u8; CHALLENGE_MAC_SIZE], + pub prior_challenge_pow: [u8; CHALLENGE_POW_SIZE], +} + +impl BobDOSChallenge { + pub const SIZE: usize = HEADER_SIZE + SESSION_ID_SIZE + CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; +} + +/// The response to NoiseXKPattern1 containing Bob's ephemeral keys. +#[repr(C, packed)] +pub(crate) struct NoiseXKPattern2 { + pub header: [u8; HEADER_SIZE], + pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], + /// -- start AES-GCM(k_es_ee) encrypted section + pub noise_ekem1: [u8; KYBER_CIPHERTEXTBYTES], + /// -- end encrypted section + pub ekem1_gcm_tag: [u8; AES_GCM_TAG_SIZE], + /// -- start AES-GCM(k_es_ee_ekem1_psk) encrypted section + pub bob_key_id: [u8; SESSION_ID_SIZE], + /// -- end encrypted section + pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], +} + +impl NoiseXKPattern2 { + pub const EKEM1_ENC_START: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE; + pub const EKEM1_AUTH_START: usize = Self::EKEM1_ENC_START + KYBER_CIPHERTEXTBYTES; + pub const P_ENC_START: usize = Self::EKEM1_AUTH_START + AES_GCM_TAG_SIZE; + pub const P_AUTH_START: usize = Self::P_ENC_START + SESSION_ID_SIZE; + pub const P_AUTH_END: usize = Self::P_AUTH_START + AES_GCM_TAG_SIZE; + pub const SIZE: usize = Self::P_AUTH_END; +} + +/// Alice's final response containing her identity (she already knows Bob's) and meta-data. +/// While Alice's response does match what is described in this struct, +/// this struct is unused because it would contain variable length fields. +/// It is present here for documentation purposes. +#[repr(C, packed)] +pub(crate) struct NoiseXKPattern3 { + pub header: [u8; HEADER_SIZE], + /// -- start AES-GCM(k_es_ee_ekem1_psk) encrypted section + pub noise_s: [u8; P384_PUBLIC_KEY_SIZE], + /// -- end encrypted section + pub s_gcm_tag: [u8; AES_GCM_TAG_SIZE], + /// -- start AES-GCM(k_es_ee_ekem1_psk_se) encrypted section + pub alice_blob: [u8; 0], + /// -- end encrypted section + pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], +} +impl NoiseXKPattern3 { + pub const MIN_SIZE: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; + pub const MAX_SIZE: usize = MAX_NOISE_HANDSHAKE_SIZE; +} + +#[repr(C, packed)] +pub(crate) struct NoiseKKPattern1or2 { + pub header: [u8; HEADER_SIZE], + pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], + pub key_id: [u8; SESSION_ID_SIZE], + pub gcm_tag: [u8; AES_GCM_TAG_SIZE], + pub kek_tag: [u8; AES_GCM_TAG_SIZE], +} +impl NoiseKKPattern1or2 { + pub const ENC_START: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE; + pub const AUTH_START: usize = Self::ENC_START + SESSION_ID_SIZE; + pub const AUTH_END: usize = Self::AUTH_START + AES_GCM_TAG_SIZE; + pub const SIZE: usize = Self::AUTH_END + AES_GCM_TAG_SIZE; +} + +// Annotate only these structs as being compatible with byte_array_as_proto_buffer(). These structs +// are packed flat buffers containing only byte or byte array fields, making them safe to treat +// this way even on architectures that require type size aligned access. +pub(crate) trait ProtocolFlatBuffer {} +impl ProtocolFlatBuffer for NoiseXKPattern1 {} +impl ProtocolFlatBuffer for NoiseXKPattern2 {} +impl ProtocolFlatBuffer for NoiseKKPattern1or2 {} +impl ProtocolFlatBuffer for BobDOSChallenge {} +impl ProtocolFlatBuffer for ChallengeResponse {} + +#[inline(always)] +pub(crate) fn byte_array_as_proto_buffer(b: &[u8]) -> &B { + assert!(b.len() >= size_of::()); + unsafe { &*b.as_ptr().cast() } +} + +#[inline(always)] +pub(crate) fn byte_array_as_proto_buffer_mut(b: &mut [u8]) -> &mut B { + assert!(b.len() >= size_of::()); + unsafe { &mut *b.as_mut_ptr().cast() } +} +/// Trick rust into letting us use a hasher that returns more than 64 bits. +pub(crate) struct ShaHasher<'a, ShaImpl: HashSha512>(pub &'a mut ShaImpl); +impl<'a, ShaImpl: HashSha512> Hasher for ShaHasher<'a, ShaImpl> { + fn finish(&self) -> u64 { + panic!() + } + fn write(&mut self, bytes: &[u8]) { + self.0.update(bytes) + } +} diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index 273a358..378eb78 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -1,62 +1,76 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ +use zeroize::Zeroizing; -use std::num::NonZeroU64; - -use crate::crypto::secret::Secret; -use crate::RATCHET_SIZE; - -#[derive(Clone, PartialEq, Eq)] -pub enum RatchetState { - Null, - Empty, - NonEmpty(NonEmptyRatchetState), -} -use RatchetState::*; -impl RatchetState { - pub fn new_nonempty(key: Secret, fingerprint: Secret, chain_len: NonZeroU64) -> Self { - NonEmpty(NonEmptyRatchetState { key, fingerprint, chain_len }) - } - pub fn new_initial_states() -> [RatchetState; 2] { - [RatchetState::Empty, RatchetState::Null] - } - pub fn is_null(&self) -> bool { - matches!(self, Null) - } - pub fn is_empty(&self) -> bool { - matches!(self, Empty) - } - pub fn nonempty(&self) -> Option<&NonEmptyRatchetState> { - match self { - NonEmpty(rs) => Some(rs), - _ => None, - } - } - pub fn chain_len(&self) -> u64 { - self.nonempty().map_or(0, |rs| rs.chain_len.get()) - } - pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { - self.nonempty().map(|rs| rs.fingerprint.as_ref()) - } - pub fn key(&self) -> Option<&[u8; RATCHET_SIZE]> { - const ZERO_KEY: [u8; RATCHET_SIZE] = [0u8; RATCHET_SIZE]; - match self { - Null => None, - Empty => Some(&ZERO_KEY), - NonEmpty(rs) => Some(rs.key.as_ref()), - } - } -} +use crate::crypto::secure_eq; +use crate::proto::*; /// A ratchet key and fingerprint, /// along with the length of the ratchet chain the keys were derived from. -#[derive(Clone, PartialEq, Eq)] -pub struct NonEmptyRatchetState { - pub key: Secret, - pub fingerprint: Secret, - pub chain_len: NonZeroU64, +/// +/// Corresponds to the Ratchet Key and Ratchet Fingerprint described in Section 3. +#[derive(Clone, Eq)] +pub struct RatchetState { + pub key: Zeroizing<[u8; RATCHET_SIZE]>, + pub fingerprint: Option>, + pub chain_len: u64, +} +impl PartialEq for RatchetState { + fn eq(&self, other: &Self) -> bool { + secure_eq(&self.key, &other.key) + & (self.chain_len == other.chain_len) + & match (self.fingerprint.as_ref(), other.fingerprint.as_ref()) { + (Some(rf1), Some(rf2)) => secure_eq(rf1, rf2), + (None, None) => true, + _ => false, + } + } +} +impl RatchetState { + pub fn new(key: Zeroizing<[u8; RATCHET_SIZE]>, fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, chain_len: u64) -> Self { + RatchetState { key, fingerprint: Some(fingerprint), chain_len } + } + pub fn new_raw(key: [u8; RATCHET_SIZE], fingerprint: [u8; RATCHET_SIZE], chain_len: u64) -> Self { + RatchetState { + key: Zeroizing::new(key), + fingerprint: Some(Zeroizing::new(fingerprint)), + chain_len, + } + } + pub fn empty() -> Self { + RatchetState { + key: Zeroizing::new([0u8; RATCHET_SIZE]), + fingerprint: None, + chain_len: 0, + } + } + //pub fn new_from_otp(otp: &[u8]) -> RatchetState { + // let mut buffer = Vec::new(); + // buffer.push(1); + // buffer.extend(LABEL_OTP_TO_RATCHET); + // buffer.push(0x00); + // buffer.extend((2u16 * 512u16).to_be_bytes()); + // let r1 = Hmac::hmac(otp, &buffer); + // buffer[0] = 2; + // let r2 = Hmac::hmac(otp, &buffer); + // Self::new( + // Zeroizing::new(r1[..RATCHET_SIZE].try_into().unwrap()), + // Zeroizing::new(r2[..RATCHET_SIZE].try_into().unwrap()), + // 1, + // ) + //} + + pub fn new_initial_states() -> (RatchetState, Option) { + (RatchetState::empty(), None) + } + //pub fn new_otp_states(otp: &[u8]) -> (RatchetState, Option) { + // (RatchetState::new_from_otp::(otp), None) + //} + + pub fn is_empty(&self) -> bool { + self.fingerprint.is_none() + } + pub fn fingerprint_eq(&self, rf: &[u8; RATCHET_SIZE]) -> bool { + self.fingerprint.as_ref().map_or(false, |rf0| secure_eq(rf0, rf)) + } + pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { + self.fingerprint.as_deref() + } } diff --git a/src/ratchet_state_old.rs b/src/ratchet_state_old.rs new file mode 100644 index 0000000..73519e6 --- /dev/null +++ b/src/ratchet_state_old.rs @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::num::NonZeroU64; + +#[derive(Clone, PartialEq, Eq)] +pub enum RatchetState { + Null, + Empty, + NonEmpty(NonEmptyRatchetState), +} +use RatchetState::*; +use zeroize::Zeroizing; + +use crate::proto::RATCHET_SIZE; +impl RatchetState { + pub fn new_nonempty(key: Zeroizing<[u8; RATCHET_SIZE]>, fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, chain_len: NonZeroU64) -> Self { + NonEmpty(NonEmptyRatchetState { key, fingerprint, chain_len }) + } + pub fn new_initial_states() -> [RatchetState; 2] { + [RatchetState::Empty, RatchetState::Null] + } + pub fn is_null(&self) -> bool { + matches!(self, Null) + } + pub fn is_empty(&self) -> bool { + matches!(self, Empty) + } + pub fn nonempty(&self) -> Option<&NonEmptyRatchetState> { + match self { + NonEmpty(rs) => Some(rs), + _ => None, + } + } + pub fn chain_len(&self) -> u64 { + self.nonempty().map_or(0, |rs| rs.chain_len.get()) + } + //pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { + // self.nonempty().map(|rs| rs.fingerprint.as_ref()) + //} + //pub fn key(&self) -> Option<&[u8; RATCHET_SIZE]> { + // const ZERO_KEY: [u8; RATCHET_SIZE] = [0u8; RATCHET_SIZE]; + // match self { + // Null => None, + // Empty => Some(&ZERO_KEY), + // NonEmpty(rs) => Some(rs.key.as_ref()), + // } + //} +} +/// A ratchet key and fingerprint, +/// along with the length of the ratchet chain the keys were derived from. +#[derive(Clone, PartialEq, Eq)] +pub struct NonEmptyRatchetState { + pub key: Zeroizing<[u8; RATCHET_SIZE]>, + pub fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, + pub chain_len: NonZeroU64, +} diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 0000000..d542227 --- /dev/null +++ b/src/result.rs @@ -0,0 +1,162 @@ +use std::sync::Arc; + +use crate::applicationlayer::ApplicationLayer; +use crate::zeta::Session; + +/// An error that can occur when attempting to open a session. +/// Depending on the error type trying again may not work. +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum OpenError { + /// An invalid parameter was supplied to the function. + InvalidPublicKey, + + RatchetIoError(IoError), +} + +/// An error that can occur when attempting to send data over a session. +/// Depending on the error type trying again may not work. +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum SendError { + /// An invalid parameter was supplied to the function. + InvalidParameter, + + /// The session has been marked as expired and refuses to send data. + /// Several components of ZSSP can cause this to occur, but the most likely situation to be seen + /// in practice is where rekeying repeatedly fails due to exceedingly bad network conditions. + /// + /// The associated session will no longer send or receive data and must be immediately dropped. + SessionExpired, + + /// Attempt to send using a session without a shared symmetric key. + /// The caller should wait until the handshake has completed. + SessionNotEstablished, + + /// Data object is too large to send, even with fragmentation. + DataTooLarge, +} + +/// A type of fault occurred because we received a bad packet. +/// +/// An unauthenticated attacker can intentionally trigger any of these, so it is best to +/// treat these as raw user input that needs to be sanitize. +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum FaultType { + /// The received packet was addressed to an unrecognized local session. + UnknownLocalKeyId, + + /// The received packet from the remote peer was not well formed. + InvalidPacket, + + /// Packet failed one or more authentication (MAC) checks. + FailedAuth, + + /// Packet counter was repeated or outside window of allowed counter values. + ExpiredCounter, + + /// Packet contained protocol control parameters that are disallowed at this point in + /// time by ZSSP. + OutOfSequence, +} + +/// An error that occurred during the receipt of a given packet. +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum ReceiveError { + /// A type of fault that can occur because a remote peer sent us a bad packet. + /// Such packets will be ignored by ZSSP but a user of ZSSP might want to log + /// them for debugging or tracing. + /// + /// Because an unauthenticated remote peer can force these to occur with specific + /// contained information, it is recommended in production to either drop these + /// immediately, or log them safely to a local output stream and then drop them. + ByzantineFault { + /// The type of fault that has occurred. Be cautious if you choose to read this + /// value, as an attacker has control over it. + error: FaultType, + /// Some byzantine faults within ZSSP are naturally occurring, i.e. they can occur + /// between two well behaved and trusted parties executing the protocol. + /// This boolean is false if this is one of these faults. If you go to the file and + /// line number specified by this error you will find a comment describing + /// how and why exactly this fault can occur naturally. + /// + /// Faults that can occur because the underlying communication medium is lossy and + /// sequentially inconsistent (as in UDP) are considered naturally occurring. + /// However ZSSP considers faults that occur because data integrity has not been + /// persevered (i.e. bits have been flipped) to be unnatural. + /// ZSSP also considers collisions of what are supposed to be uniform random + /// numbers to be unnatural. + unnatural: bool, + /// The file of this implementation of ZSSP from which this error was generated. + #[cfg(feature = "debug")] + file: &'static str, + /// The line number of this implementation of ZSSP from which this error was + /// generated. As such this number uniquely identifies each possible fault that + /// can occur during ZSSP. Advanced user can use this information to debug more + /// complicated usages of ZSSP. + #[cfg(feature = "debug")] + line: u32, + }, + + /// Rekeying failed and session secret has reached its hard usage count limit. + /// The associated session will no longer function and has to be dropped. + MaxKeyLifetimeExceeded, + + /// One of the ratchet saving or lookup functions returned an error, so the packet had to be + /// dropped. + RatchetIoError(IoError), +} + +macro_rules! byzantine_fault { + ($name:expr, $unnatural:ident) => { + ReceiveError::ByzantineFault { + #[cfg(feature = "debug")] + file: file!(), + #[cfg(feature = "debug")] + line: line!(), + error: $name, + unnatural: $unnatural, + } + }; +} +pub(crate) use byzantine_fault; + +/// Result generated by the context packet receive function, with possible payloads. +#[derive(Clone)] +pub enum ReceiveOk { + /// Packet superficially appeared valid but is not associated with a session yet. + /// This can occur because the packet was only a fragment of a larger packet, + /// or if it was a control packet that does not go through full Noise authentication. + Unassociated, + /// Packet was authentic and belongs to this specific session. + Session(Arc>, SessionEvent), +} +/// Something that can occur to an associated session when a packet is received successfully, +/// including receiving a payload of decrypted, authenticated data. +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub enum SessionEvent { + /// The received packet was valid, and it contained the necessary keys to fully establish a new + /// session with Alice, the handshake initiator. + /// + /// If the session Arc returned is dropped, the session with this peer will be immediately + /// terminated. Save the session Arc to some long lived datastructure to keep it alive. + NewSession, + /// When Alice calls `Context::open`, a session will be created, but Bob will not yet have + /// received this session. They will have to successfully complete a handshake first. + /// + /// Alice will receive this return value when the received packet confirms both parties + /// have completed the initial handshake and now have a shared session with each other. + /// If according to the upper protocol, Bob is the first party to send data, it is possible for + /// Alice to start receiving data from Bob before this value is returned. + /// + /// This return value can only occur once per session, only for session objects that were + /// created with `Context::open`. + Established, + /// Bob explicitly refused to establish a session with Alice, and sent us an error code. + /// The application should immediately drop this session as Bob will not allow us to connect. + /// + /// This return value cannot occur after a session is fully established. + Rejected, + /// The received packet was valid and a data payload was decoded and authenticated. + Data(Vec), + /// The received packet was some authentic protocol control packet. No action needs to be taken. + Control, +} diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index e6e5e52..a2caf2b 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -1,116 +1,34 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ -use crate::crypto::aes::AES_256_KEY_SIZE; -use crate::crypto::secret::Secret; -use crate::crypto::sha512::HmacSha512; +use std::marker::PhantomData; -use crate::proto::NOISE_HASHLEN; +use arrayvec::ArrayVec; +use zeroize::Zeroizing; -#[derive(Clone)] -pub(crate) struct SymmetricState { - chaining_key: Secret, - token_counter: u8, +use crate::crypto::aes::{LowThroughputAesGcm, HighThroughputAesGcmPool, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}; +use crate::crypto::sha512::{HashSha512, HmacSha512}; +use crate::{applicationlayer::ApplicationLayer, crypto::aes::AES_256_KEY_SIZE}; +use crate::proto::*; + +pub struct SymmetricState { + k: Zeroizing<[u8; AES_256_KEY_SIZE]>, + ck: Zeroizing<[u8; HASHLEN]>, + h: [u8; HASHLEN], + /// If anyone knows a better way to get rid of the "parameter `App` is never used" error please + /// let me know. + _app: PhantomData App::SessionData>, +} +impl Clone for SymmetricState { + fn clone(&self) -> Self { + Self { + k: self.k.clone(), + ck: self.ck.clone(), + h: self.h.clone(), + _app: PhantomData, + } + } } -impl SymmetricState { - pub(crate) fn new(h: [u8; NOISE_HASHLEN]) -> Self { - Self { chaining_key: Secret(h), token_counter: b'P' } - } - /// Corresponds to Noise `MixKey`. - pub(crate) fn mix_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) { - let mut next_ck = Secret::new(); - self.kbkdf(hm, input_key_material, self.label(), 2, next_ck.as_mut(), None, None); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - // We don't need a key at this step of Noise, so generating that key and calling - // `InitializeKey` would be completely pointless. - } - /// Corresponds to Noise `MixKey` followed by `InitializeKey`. - pub(crate) fn mix_key_initialize_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> Secret { - let mut next_ck = Secret::new(); - let mut temp_k = [0u8; NOISE_HASHLEN]; - - self.kbkdf(hm, input_key_material, self.label(), 2, next_ck.as_mut(), Some(&mut temp_k), None); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE]) - } - /// Corresponds to Noise `MixKeyAndHash`. - pub(crate) fn mix_key_and_hash(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> [u8; NOISE_HASHLEN] { - let mut next_ck = Secret::new(); - let mut temp_h = [0u8; NOISE_HASHLEN]; - - self.kbkdf(hm, input_key_material, self.label(), 3, next_ck.as_mut(), Some(&mut temp_h), None); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - temp_h - } - /// Corresponds to Noise `MixKeyAndHash` followed by `InitializeKey`. - pub(crate) fn mix_key_and_hash_initialize_key( - &mut self, - hm: &mut impl HmacSha512, - input_key_material: &[u8], - ) -> ([u8; NOISE_HASHLEN], Secret) { - let mut next_ck = Secret::new(); - let mut temp_h = [0u8; NOISE_HASHLEN]; - let mut temp_k = [0u8; NOISE_HASHLEN]; - - self.kbkdf( - hm, - input_key_material, - self.label(), - 3, - next_ck.as_mut(), - Some(&mut temp_h), - Some(&mut temp_k), - ); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - (temp_h, Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE])) - } - /// Get an additional symmetric key (ASK) that is a collision resistant hash of the transcript, - /// is forward secrect and is cryptographically independent from all other produced keys. - /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. - /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. - pub(crate) fn get_ask2( - &self, - hm: &mut impl HmacSha512, - label: u8, - noise_h: &[u8; NOISE_HASHLEN], - ) -> (Secret, Secret) { - let mut temp_k1 = [0u8; NOISE_HASHLEN]; - let mut temp_k2 = [0u8; NOISE_HASHLEN]; - self.kbkdf(hm, noise_h, [b'A', b'S', b'K', label], 2, &mut temp_k1, Some(&mut temp_k2), None); - ( - Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]), - Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), - ) - } - /// Corresponds to Noise `Split`. - pub(crate) fn split(self, hm: &mut impl HmacSha512) -> (Secret, Secret) { - let mut temp_k1 = [0u8; NOISE_HASHLEN]; - let mut temp_k2 = [0u8; NOISE_HASHLEN]; - self.kbkdf(hm, &[], self.label(), 2, &mut temp_k1, Some(&mut temp_k2), None); - // Normally KBKDF would not truncate to derive the correct length of AES keys, - // but Noise specifies that the AES keys be truncated from NOISE_HASHLEN to AES_256_KEY_SIZE. - ( - Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]), - Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), - ) - } - fn label(&self) -> [u8; 4] { - [b'Z', b'S', b'S', self.token_counter] - } +impl SymmetricState { /// HMAC-SHA512 key derivation based on KBKDF Counter Mode: /// https://csrc.nist.gov/publications/detail/sp/800-108/rev-1/final. /// Cryptographically this isn't meaningfully different from @@ -122,36 +40,124 @@ impl SymmetricState { /// * L = `num_outputs*512u16` /// We have intentionally made every input small and fixed size to avoid unnecessary complexity /// and data representation ambiguity. + /// Corresponds to Noise `HKDF`. fn kbkdf( &self, - hm: &mut impl HmacSha512, + hmac: &mut App::HmacHash, input_key_material: &[u8], - label: [u8; 4], + label: &[u8; 4], num_outputs: u16, - output1: &mut [u8; NOISE_HASHLEN], - output2: Option<&mut [u8; NOISE_HASHLEN]>, - output3: Option<&mut [u8; NOISE_HASHLEN]>, + output1: &mut [u8; HASHLEN], + output2: Option<&mut [u8; HASHLEN]>, + output3: Option<&mut [u8; HASHLEN]>, ) { - let l = &(num_outputs * 512u16).to_be_bytes(); + const LABEL_START: usize = 1; + const LABEL_END: usize = 5; + const CONTEXT_START: usize = 6; + const LEN_START: usize = 70; + const LEN_END: usize = 72; + let mut buffer = Zeroizing::new([0u8; LEN_END]); + buffer[0] = 1; + buffer[LABEL_START..LABEL_END].copy_from_slice(label); + buffer[LABEL_END] = 0x00; + buffer[CONTEXT_START..LEN_START].copy_from_slice(self.ck.as_ref()); + buffer[LEN_START..LEN_END].copy_from_slice(&(num_outputs * 8 * HASHLEN as u16).to_be_bytes()); + + debug_assert!(num_outputs >= 1); + hmac.hash(input_key_material, buffer.as_ref(), output1); - hm.reset(input_key_material); - hm.update(&[1, label[0], label[1], label[2], label[3], 0x00]); - hm.update(self.chaining_key.as_ref()); - hm.update(l); - hm.finish(output1); if let Some(output2) = output2 { - hm.reset(input_key_material); - hm.update(&[2, label[0], label[1], label[2], label[3], 0x00]); - hm.update(self.chaining_key.as_ref()); - hm.update(l); - hm.finish(output2); + debug_assert!(num_outputs >= 2); + buffer[0] = 2; + hmac.hash(input_key_material, buffer.as_ref(), output2); } + if let Some(output3) = output3 { - hm.reset(input_key_material); - hm.update(&[3, label[0], label[1], label[2], label[3], 0x00]); - hm.update(self.chaining_key.as_ref()); - hm.update(l); - hm.finish(output3); + debug_assert!(num_outputs >= 3); + buffer[0] = 3; + hmac.hash(input_key_material, buffer.as_ref(), output3); } } + + /// Corresponds to Noise `Initialize` on a SymmetricState. + pub fn initialize(h: [u8; HASHLEN]) -> Self { + Self { + k: Zeroizing::default(), + ck: Zeroizing::new(h), + h, + _app: PhantomData, + } + } + /// Corresponds to Noise `MixKey`. + pub fn mix_key(&mut self, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + let mut next_ck = Zeroizing::new([0u8; HASHLEN]); + let mut temp_k = Zeroizing::new([0u8; HASHLEN]); + + self.kbkdf(hmac, input_key_material, LABEL_KBKDF_CHAIN, 2, &mut next_ck, Some(&mut temp_k), None); + + *self.ck = *next_ck; + self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); + } + /// Corresponds to Noise `MixHash`. + pub fn mix_hash(&mut self, hash: &mut App::Hash, data: &[u8]) { + hash.update(&self.h); + hash.update(data); + hash.finish_and_reset(&mut self.h); + } + /// Corresponds to Noise `MixKeyAndHash`. + pub fn mix_key_and_hash(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + let mut next_ck = Zeroizing::new([0u8; HASHLEN]); + let mut temp_h = [0u8; HASHLEN]; + let mut temp_k = Zeroizing::new([0u8; HASHLEN]); + + self.kbkdf( + hmac, + input_key_material, + LABEL_KBKDF_CHAIN, + 3, + &mut next_ck, + Some(&mut temp_h), + Some(&mut temp_k), + ); + + *self.ck = *next_ck; + self.mix_hash(hash, &temp_h); + self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); + } + /// Corresponds to Noise `EncryptAndHash`. + #[must_use] + pub fn encrypt_and_hash_in_place(&mut self, hash: &mut App::Hash, iv: [u8; AES_GCM_IV_SIZE], data: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE] { + let tag = App::Aead::encrypt_in_place(&self.k, &iv, &self.h, data); + hash.update(&self.h); + hash.update(data); + hash.update(&tag); + hash.finish_and_reset(&mut self.h); + tag + } + /// Corresponds to Noise `DecryptAndHash`. + #[must_use] + pub fn decrypt_and_hash_in_place(&mut self, hash: &mut App::Hash, iv: [u8; AES_GCM_IV_SIZE], data: &mut [u8], tag: [u8; AES_GCM_TAG_SIZE]) -> bool { + hash.update(&self.h); + hash.update(data); + hash.update(&tag); + let is_auth = App::Aead::decrypt_in_place(&self.k, &iv, &self.h, data, tag.as_ref().try_into().unwrap()); + hash.finish_and_reset(&mut self.h); + is_auth + } + /// Corresponds to Noise `Split`. + pub fn split(self, hmac: &mut App::HmacHash, key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { + self.kbkdf(hmac, &[], LABEL_KBKDF_CHAIN, 2, key1, Some(key2), None); + } + /// Get an additional symmetric key (ASK) that is a collision resistant hash of the transcript, + /// is forward secrect and is cryptographically independent from all other produced keys. + /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. + /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. + pub fn get_ask(&self, hmac: &mut App::HmacHash, label: &[u8; 4], key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { + self.kbkdf(hmac, &self.h, label, 2, key1, Some(key2), None); + } + /// Used for internally debugging a key exchange. + #[allow(unused)] + pub(crate) fn finger(&self) -> (u8, u8, u8) { + (self.k[0], self.ck[0], self.h[0]) + } } diff --git a/src/symmetric_state_old.rs b/src/symmetric_state_old.rs new file mode 100644 index 0000000..264fc45 --- /dev/null +++ b/src/symmetric_state_old.rs @@ -0,0 +1,156 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ +use crate::crypto::aes::AES_256_KEY_SIZE; +use crate::crypto::sha512::HmacSha512; + +use crate::proto::NOISE_HASHLEN; + +#[derive(Clone)] +pub(crate) struct SymmetricState { + chaining_key: Secret, + token_counter: u8, +} + +impl SymmetricState { + pub(crate) fn new(h: [u8; NOISE_HASHLEN]) -> Self { + Self { chaining_key: Secret(h), token_counter: b'P' } + } + /// Corresponds to Noise `MixKey`. + pub(crate) fn mix_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) { + let mut next_ck = Secret::new(); + + self.kbkdf(hm, input_key_material, self.label(), 2, next_ck.as_mut(), None, None); + self.token_counter += 1; + + self.chaining_key.overwrite(&next_ck); + // We don't need a key at this step of Noise, so generating that key and calling + // `InitializeKey` would be completely pointless. + } + /// Corresponds to Noise `MixKey` followed by `InitializeKey`. + pub(crate) fn mix_key_initialize_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> Secret { + let mut next_ck = Secret::new(); + let mut temp_k = [0u8; NOISE_HASHLEN]; + + self.kbkdf(hm, input_key_material, self.label(), 2, next_ck.as_mut(), Some(&mut temp_k), None); + self.token_counter += 1; + + self.chaining_key.overwrite(&next_ck); + Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE]) + } + /// Corresponds to Noise `MixKeyAndHash`. + pub(crate) fn mix_key_and_hash(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> [u8; NOISE_HASHLEN] { + let mut next_ck = Secret::new(); + let mut temp_h = [0u8; NOISE_HASHLEN]; + + self.kbkdf(hm, input_key_material, self.label(), 3, next_ck.as_mut(), Some(&mut temp_h), None); + self.token_counter += 1; + + self.chaining_key.overwrite(&next_ck); + temp_h + } + /// Corresponds to Noise `MixKeyAndHash` followed by `InitializeKey`. + pub(crate) fn mix_key_and_hash_initialize_key( + &mut self, + hm: &mut impl HmacSha512, + input_key_material: &[u8], + ) -> ([u8; NOISE_HASHLEN], Secret) { + let mut next_ck = Secret::new(); + let mut temp_h = [0u8; NOISE_HASHLEN]; + let mut temp_k = [0u8; NOISE_HASHLEN]; + + self.kbkdf( + hm, + input_key_material, + self.label(), + 3, + next_ck.as_mut(), + Some(&mut temp_h), + Some(&mut temp_k), + ); + self.token_counter += 1; + + self.chaining_key.overwrite(&next_ck); + (temp_h, Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE])) + } + /// Get an additional symmetric key (ASK) that is a collision resistant hash of the transcript, + /// is forward secrect and is cryptographically independent from all other produced keys. + /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. + /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. + pub(crate) fn get_ask2( + &self, + hm: &mut impl HmacSha512, + label: u8, + noise_h: &[u8; NOISE_HASHLEN], + ) -> (Secret, Secret) { + let mut temp_k1 = [0u8; NOISE_HASHLEN]; + let mut temp_k2 = [0u8; NOISE_HASHLEN]; + self.kbkdf(hm, noise_h, [b'A', b'S', b'K', label], 2, &mut temp_k1, Some(&mut temp_k2), None); + ( + Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]), + Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), + ) + } + /// Corresponds to Noise `Split`. + pub(crate) fn split(self, hm: &mut impl HmacSha512) -> (Secret, Secret) { + let mut temp_k1 = [0u8; NOISE_HASHLEN]; + let mut temp_k2 = [0u8; NOISE_HASHLEN]; + self.kbkdf(hm, &[], self.label(), 2, &mut temp_k1, Some(&mut temp_k2), None); + // Normally KBKDF would not truncate to derive the correct length of AES keys, + // but Noise specifies that the AES keys be truncated from NOISE_HASHLEN to AES_256_KEY_SIZE. + ( + Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]), + Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), + ) + } + fn label(&self) -> [u8; 4] { + [b'Z', b'S', b'S', self.token_counter] + } + /// HMAC-SHA512 key derivation based on KBKDF Counter Mode: + /// https://csrc.nist.gov/publications/detail/sp/800-108/rev-1/final. + /// Cryptographically this isn't meaningfully different from + /// `HKDF(self.chaining_key, input_key_material)` but this is how NIST rolls. + /// These are the values we have assigned to the 4 variables involved in their KDF: + /// * K_IN = `input_key_material` + /// * Label = `label` + /// * Context = `self.chaining_key` + /// * L = `num_outputs*512u16` + /// We have intentionally made every input small and fixed size to avoid unnecessary complexity + /// and data representation ambiguity. + fn kbkdf( + &self, + hm: &mut impl HmacSha512, + input_key_material: &[u8], + label: [u8; 4], + num_outputs: u16, + output1: &mut [u8; NOISE_HASHLEN], + output2: Option<&mut [u8; NOISE_HASHLEN]>, + output3: Option<&mut [u8; NOISE_HASHLEN]>, + ) { + let l = &(num_outputs * 512u16).to_be_bytes(); + + hm.reset(input_key_material); + hm.update(&[1, label[0], label[1], label[2], label[3], 0x00]); + hm.update(self.chaining_key.as_ref()); + hm.update(l); + hm.finish(output1); + if let Some(output2) = output2 { + hm.reset(input_key_material); + hm.update(&[2, label[0], label[1], label[2], label[3], 0x00]); + hm.update(self.chaining_key.as_ref()); + hm.update(l); + hm.finish(output2); + } + if let Some(output3) = output3 { + hm.reset(input_key_material); + hm.update(&[3, label[0], label[1], label[2], label[3], 0x00]); + hm.update(self.chaining_key.as_ref()); + hm.update(l); + hm.finish(output3); + } + } +} diff --git a/src/zeta.rs b/src/zeta.rs new file mode 100644 index 0000000..159df8b --- /dev/null +++ b/src/zeta.rs @@ -0,0 +1,1394 @@ +use arrayvec::ArrayVec; +use rand_core::RngCore; +use std::cmp::Reverse; +use std::collections::HashMap; +use std::num::NonZeroU32; +use std::ops::{Deref, DerefMut}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Arc, Mutex, Weak, RwLock}; +use zeroize::Zeroizing; + +use crate::antireplay::Window; +use crate::applicationlayer::ApplicationLayer; +use crate::applicationlayer::RatchetUpdate; +use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; +use crate::context::ContextInner; +//use crate::context::{log, ContextInner, SessionMap}; +use crate::crypto::aes::{HighThroughputAesGcmPool, LowThroughputAesGcm, AES_GCM_IV_SIZE, AES_256_KEY_SIZE, AES_GCM_TAG_SIZE}; +use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; +use crate::crypto::sha512::{HashSha512, HmacSha512}; +use crate::crypto::kyber1024::{Kyber1024PrivateKey, KYBER_PUBLIC_KEY_SIZE, KYBER_CIPHERTEXT_SIZE, KYBER_PLAINTEXT_SIZE}; +use crate::indexed_heap::BinaryHeapIndex; +//use crate::indexed_heap::BinaryHeapIndex; +//use crate::fragmentation::DefragBuffer; +use crate::proto::*; +use crate::ratchet_state::RatchetState; +use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; +use crate::symmetric_state::SymmetricState; +#[cfg(feature = "logging")] +use crate::LogEvent::*; + +/// Create a 96-bit AES-GCM nonce. +/// +/// The primary information that we want to be contained here is the counter and the +/// packet type. The former makes this unique and the latter's inclusion authenticates +/// it as effectively AAD. Other elements of the header are either not authenticated, +/// like fragmentation info, or their authentication is implied via key exchange like +/// the key id. +/// +/// Corresponds to Figure 10 found in Section 4.3. +pub(crate) fn to_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] { + let mut ret = [0u8; AES_GCM_IV_SIZE]; + ret[3] = packet_type; + // Noise requires a big endian counter at the end of the Nonce + ret[4..].copy_from_slice(&counter.to_be_bytes()); + ret +} +/// Corresponds to Figure 10 and Figure 14 found in Section 4.3. +pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { + assert!(n.len() >= PACKET_NONCE_SIZE); + let c_start = n.len() - 8; + (n[c_start - 1], u64::from_be_bytes(n[c_start..].try_into().unwrap())) +} +fn create_ratchet_state(hmac: &mut App::HmacHash, noise: &mut SymmetricState, pre_chain_len: u64) -> RatchetState { + let mut rk = Zeroizing::new([0u8; HASHLEN]); + let mut rf = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_RATCHET_STATE, &mut rk, &mut rf); + RatchetState::new(Zeroizing::new(rk[..RATCHET_SIZE].try_into().unwrap()), Zeroizing::new(rf[..RATCHET_SIZE].try_into().unwrap()), pre_chain_len + 1) +} + +/// Corresponds to the Zeta State Machine found in Section 4.1. +pub(crate) struct Session { + //ctx: Weak>, + /// An arbitrary application defined object associated with each session. + pub session_data: App::SessionData, + /// Is true if the local peer acted as Bob, the responder in the initial key exchange. + pub was_bob: bool, + queue_idx: BinaryHeapIndex, + + s_remote: App::PublicKey, + send_counter: AtomicU64, + + pub window: Window, + //defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], + + state_machine_lock: Mutex<()>, + state: RwLock>, + + /// Pre-computed rekeying values. + noise_kk_ss: Zeroizing<[u8; P384_ECDH_SHARED_SECRET_SIZE]>, +} +pub(crate) struct MutableState { + ratchet_state1: RatchetState, + ratchet_state2: Option, + + key_creation_counter: u64, + key_index: bool, + keys: [DuplexKey; 2], + pub hk_send: Zeroizing<[u8; AES_256_KEY_SIZE]>, + + resend_timer: i64, + timeout_timer: i64, + pub beta: ZetaAutomata, +} + +/// Corresponds to State B_2 of the Zeta State Machine found in Section 4.1 - Definition 3. +pub(crate) struct StateB2 { + ratchet_state: RatchetState, + kid_send: NonZeroU32, + pub kid_recv: NonZeroU32, + pub hk_send: Zeroizing<[u8; AES_256_KEY_SIZE]>, + e_secret: App::KeyPair, + noise: SymmetricState, + //pub defrag: DefragBuffer, +} + +#[derive(Default)] +pub(crate) struct DuplexKey { + send: Keys, + recv: Keys, + nk: Option, +} + +#[derive(Default)] +pub(crate) struct Keys { + kek: Option>, + kid: Option, +} + +/// Corresponds to the tuple of values the Transition Algorithms send to the remote peer in Section 4.3. +//#[derive(Clone)] +//pub(crate) struct Packet(pub u32, pub [u8; AES_GCM_IV_SIZE], pub Vec); + +/// Corresponds to State A_1 of the Zeta State Machine found in Section 4.1. +#[derive(Clone)] +pub(crate) struct StateA1 { + noise: SymmetricState, + e_secret: App::KeyPair, + e1_secret: App::Kem, + identity: ArrayVec, + kid_send: u32, + nonce: [u8; AES_GCM_IV_SIZE], + packet: ArrayVec, +} + +/// Corresponds to the ZKE Automata found in Section 4.1 - Definition 2. +pub(crate) enum ZetaAutomata { + Null, + A1(StateA1), + A3 { + identity: ArrayVec, + kid_send: u32, + nonce: [u8; AES_GCM_IV_SIZE], + packet: ArrayVec, + }, + S1, + S2, + R1 { + noise: SymmetricState, + e_secret: App::KeyPair, + k1: Vec, + }, + R2 { + k2: Vec, + }, +} + +impl SymmetricState { + fn write_e(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, rng: &Mutex, packet: &mut ArrayVec) -> App::KeyPair { + let e_secret = App::KeyPair::generate(rng.lock().unwrap().deref_mut()); + let pub_key = e_secret.public_key_bytes(); + packet.extend(pub_key); + self.mix_hash(hash, &pub_key); + self.mix_key(hmac, &pub_key); + e_secret + } + fn read_e(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, i: &mut usize, packet: &[u8]) -> Option { + let j = *i + P384_PUBLIC_KEY_SIZE; + let pub_key = &packet[*i..j]; + self.mix_hash(hash, pub_key); + self.mix_key(hmac, pub_key); + *i = j; + App::PublicKey::from_bytes((pub_key).try_into().unwrap()) + } + fn mix_dh(&mut self, hmac: &mut App::HmacHash, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { + let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); + if secret.agree(&remote, &mut ecdh_secret) { + self.mix_key(hmac, ecdh_secret.as_ref()); + Some(()) + } else { + None + } + } +} + +/// Generate a random local key id that is currently unused. +fn gen_kid(session_map: &HashMap, rng: &mut impl RngCore) -> NonZeroU32 { + loop { + if let Some(kid) = NonZeroU32::new(rng.next_u32()) { + if !session_map.contains_key(&kid) { + return kid; + } + } + } +} + +impl MutableState { + fn key_ref(&self, is_next: bool) -> &DuplexKey { + &self.keys[(self.key_index ^ is_next) as usize] + } + fn key_mut(&mut self, is_next: bool) -> &mut DuplexKey { + &mut self.keys[(self.key_index ^ is_next) as usize] + } + pub(crate) fn next_timer(&self) -> i64 { + self.timeout_timer.min(self.resend_timer) + } +} + +fn create_a1_state( + hash: &mut App::Hash, hmac: &mut App::HmacHash, rng: &Mutex, + s_remote: &App::PublicKey, + kid_recv: NonZeroU32, + ratchet_state1: &RatchetState, + ratchet_state2: Option<&RatchetState>, + identity: &[u8], +) -> Option> { + // <- s + // ... + // -> e, es, e1 + let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); + let mut x1 = ArrayVec::::new(); + // Noise process prologue. + let kid = kid_recv.get().to_be_bytes(); + x1.extend(kid); + noise.mix_hash(hash, &kid); + noise.mix_hash(hash, &s_remote.to_bytes()); + // Process message pattern 1 e token. + let e_secret = noise.write_e(hash, hmac, rng, &mut x1); + // Process message pattern 1 es token. + noise.mix_dh(hmac, &e_secret, s_remote)?; + // Process message pattern 1 e1 token. + let i = x1.len(); + let (e1_secret, e1_public) = App::Kem::generate(rng.lock().unwrap().deref_mut()); + x1.extend(e1_public); + x1.extend([0u8; AES_GCM_IV_SIZE]); + x1.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..])); + // Process message pattern 1 payload. + let i = x1.len(); + if let Some(rf) = ratchet_state1.fingerprint() { + x1.try_extend_from_slice(rf).unwrap(); + } + if let Some(Some(rf)) = ratchet_state2.map(|rs| rs.fingerprint()) { + x1.try_extend_from_slice(rf).unwrap(); + } + x1.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 1), &mut x1[i..])); + + let c = u64::from_be_bytes(x1[x1.len() - 8..].try_into().unwrap()); + + x1.extend(gen_null_response(rng.lock().unwrap().deref_mut())); + Some(StateA1 { + noise, + e_secret, + e1_secret, + identity: identity.try_into().unwrap(), + kid_send: 0, + nonce: to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, c), + packet: x1, + }) +} +/// Corresponds to Transition Algorithm 1 found in Section 4.3. +pub(crate) fn trans_to_a1( + app: App, + ctx: &Arc>, + s_remote: App::PublicKey, + session_data: App::SessionData, + identity: &[u8], + //send: impl FnOnce(&Packet), +) -> Result>, OpenError> { + let (ratchet_state1, ratchet_state2) = app + .restore_by_identity(&s_remote, &session_data) + .map_err(|e| OpenError::RatchetIoError(e))?; + + let mut session_queue = ctx.session_queue.lock().unwrap(); + let mut session_map = ctx.session_map.write().unwrap(); + let kid_recv = gen_kid(session_map.deref(), ctx.rng.lock().unwrap().deref_mut()); + + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); + let a1 = create_a1_state(hash, hmac, &ctx.rng, &s_remote, kid_recv, &ratchet_state1, ratchet_state2.as_ref(), identity).ok_or(OpenError::InvalidPublicKey)?; + let packet = a1.packet.clone(); + + let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut hk_send = Zeroizing::new([0u8; HASHLEN]); + a1.noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); + + let current_time = app.time(); + let queue_idx = session_queue.reserve_index(); + let mut session = Arc::new(Session { + session_data, + was_bob: false, + queue_idx, + s_remote, + send_counter: AtomicU64::new(0), + window: Window::new(), + state_machine_lock: Mutex::new(()), + state: RwLock::new(MutableState { + ratchet_state1, + ratchet_state2, + key_creation_counter: 0, + key_index: true, + keys: [DuplexKey::default(), DuplexKey::default()], + hk_send: Zeroizing::new(hk_send[..AES_256_KEY_SIZE].try_into().unwrap()), + resend_timer: current_time + App::SETTINGS.resend_time as i64, + timeout_timer: current_time + App::SETTINGS.initial_offer_timeout as i64, + beta: ZetaAutomata::A1(a1), + }), + noise_kk_ss: Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]), + }); + let mut state = session.state.write().unwrap(); + state.key_mut(true).recv.kid = Some(kid_recv); + + session_map.insert(kid_recv, Arc::downgrade(&session)); + session_queue.push_reserved( + queue_idx, + Arc::downgrade(&session), + Reverse(state.next_timer()), + ); + + //send(&packet); + + Ok(session) +} +/// Corresponds to Algorithm 13 found in Section 5. +//pub(crate) fn respond_to_challenge(zeta: &mut Zeta, rng: &Mutex, challenge: &[u8; CHALLENGE_SIZE]) { +// if let ZetaAutomata::A1(StateA1 { packet: Packet(_, _, x1), .. }) = &mut zeta.beta { +// let response_start = x1.len() - CHALLENGE_SIZE; +// respond_to_challenge_in_place::( +// rng.lock().unwrap().deref_mut(), +// challenge, +// (&mut x1[response_start..]).try_into().unwrap(), +// ); +// } +//} +/// Corresponds to Transition Algorithm 2 found in Section 4.3. +pub(crate) fn received_x1_trans( + app: &App, + ctx: &ContextInner, + n: [u8; AES_GCM_IV_SIZE], + mut x1: Vec, + //send: impl FnOnce(&Packet, &[u8; AES_256_KEY_SIZE]), +) -> Result<(), ReceiveError> { + use FaultType::*; + // <- s + // ... + // -> e, es, e1 + // <- e, ee, ekem1, psk + if !(HANDSHAKE_HELLO_MIN_SIZE..=HANDSHAKE_HELLO_MAX_SIZE).contains(&x1.len()) { + return Err(byzantine_fault!(InvalidPacket, true)); + } + if &n[AES_GCM_IV_SIZE - 8..] != &x1[x1.len() - 8..] { + return Err(byzantine_fault!(FailedAuth, true)); + } + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); + let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); + let mut i = 0; + // Noise process prologue. + let j = i + KID_SIZE; + noise.mix_hash(hash, &x1[i..j]); + let kid_send = NonZeroU32::new(u32::from_be_bytes(x1[i..j].try_into().unwrap())).ok_or(byzantine_fault!(InvalidPacket, true))?; + noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); + i = j; + // Process message pattern 1 e token. + let e_remote = noise.read_e(hash, hmac, &mut i, &x1).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 1 es token. + noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 1 e1 token. + let j = i + KYBER_PUBLIC_KEY_SIZE; + let k = i + AES_GCM_TAG_SIZE; + let tag = x1[j..k].try_into().unwrap(); + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..j], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let e1_start = i; + let e1_end = j; + i = j; + // Process message pattern 1 payload. + let k = x1.len(); + let j = k - AES_GCM_TAG_SIZE; + let tag = x1[j..k].try_into().unwrap(); + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 1), &mut x1[i..j], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + + let mut ratchet_state = None; + while i + RATCHET_SIZE <= j { + match app.restore_by_fingerprint((&x1[i..i + RATCHET_SIZE]).try_into().unwrap()) { + Ok(None) => {} + Ok(Some(rs)) => { + ratchet_state = Some(rs); + break; + } + Err(e) => return Err(ReceiveError::RatchetIoError(e)), + } + i += RATCHET_SIZE; + } + let ratchet_state = if let Some(rs) = ratchet_state { + rs + } else { + if app.hello_requires_recognized_ratchet() { + return Err(byzantine_fault!(FailedAuth, true)); + } + RatchetState::empty() + }; + let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut hk_send = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); + + let mut x2 = ArrayVec::new(); + // Process message pattern 2 e token. + let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut x2); + // Process message pattern 2 ee token. + noise.mix_dh(hmac, &e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 2 ekem1 token. + { + let i = x2.len(); + let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); + let ekem1 = App::Kem::encapsulate(ctx.rng.lock().unwrap().deref_mut(), (&x1[e1_start..e1_end]).try_into().unwrap(), &mut ekem1_secret).ok_or(byzantine_fault!(FailedAuth, true))?; + x2.extend(ekem1); + x2.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..])); + noise.mix_key(hmac, ekem1_secret.as_ref()); + } + // Process message pattern 2 psk2 token. + noise.mix_key_and_hash(hash, hmac, ratchet_state.key.as_ref()); + // Process message pattern 2 payload. + let kid_recv = gen_kid(ctx.session_map.read().unwrap().deref(), ctx.rng.lock().unwrap().deref_mut()); + + let i = x2.len(); + x2.extend(kid_recv.get().to_be_bytes()); + x2.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..])); + + let i = x2.len(); + let mut c = 0u64.to_be_bytes(); + c[5] = x2[i - 3]; + c[6] = x2[i - 2]; + c[7] = x2[i - 1]; + let c = u64::from_be_bytes(c); + + /// + //ctx.b2_map.lock().unwrap().insert( + // kid_recv, + // StateB2 { + // ratchet_state, + // kid_send, + // kid_recv, + // hk_send: hk_send.clone(), + // e_secret, + // noise, + // defrag: DefragBuffer::new(Some(hk_recv)), + // }, + //); + + //send(&Packet(kid_send.get(), to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, c), x2), &hk_send); + Ok(()) +} +/// Corresponds to Transition Algorithm 3 found in Section 4.3. +pub(crate) fn received_x2_trans( + app: &App, + ctx: &Arc>, + session: &Arc>, + kid: NonZeroU32, + n: [u8; AES_GCM_IV_SIZE], + mut x2: &[u8], + //send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) -> Result<(), ReceiveError> { + use FaultType::*; + // <- e, ee, ekem1, psk + // -> s, se + if HANDSHAKE_RESPONSE_SIZE != x2.len() { + return Err(byzantine_fault!(InvalidPacket, true)); + } + + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); + + if Some(kid) != state.key_ref(true).recv.kid { + return Err(byzantine_fault!(UnknownLocalKeyId, true)); + } + let (_, c) = from_nonce(&n); + if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD || &n[AES_GCM_IV_SIZE - 3..] != &x2[x2.len() - 3..] { + return Err(byzantine_fault!(FailedAuth, true)); + } + let result = (|| { + if let ZetaAutomata::A1(StateA1 { noise, e_secret, e1_secret, identity, .. }) = &state.beta { + let mut noise = noise.clone(); + let mut i = 0; + // Process message pattern 2 e token. + let e_remote = noise.read_e(hash, hmac, &mut i, &x2).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 2 ee token. + noise.mix_dh(hmac, e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 2 ekem1 token. + let j = i + KYBER_CIPHERTEXT_SIZE; + let k = j + AES_GCM_TAG_SIZE; + let tag = x2[j..k].try_into().unwrap(); + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..j], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); + if !e1_secret.decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) { + return Err(byzantine_fault!(FailedAuth, true)); + } + noise.mix_key(hmac, ekem1_secret.as_ref()); + drop(ekem1_secret); + i = j; + // We attempt to decrypt the payload at most three times. First two times with + // the ratchet key Alice remembers, and final time with a ratchet + // key of zero if Alice allows ratchet downgrades. + // The following code is not constant time, meaning we leak to an + // attacker whether or not we downgraded. + // We don't currently consider this sensitive enough information to hide. + let j = i + KID_SIZE; + let k = j + AES_GCM_TAG_SIZE; + let payload: [u8; KID_SIZE] = x2[i..j].try_into().unwrap(); + let tag = x2[j..k].try_into().unwrap(); + // Check for which ratchet key Bob wants to use. + let test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState)> { + let mut noise = noise.clone(); + let mut payload = payload.clone(); + // Process message pattern 2 psk token. + noise.mix_key_and_hash(hash, hmac, ratchet_key); + // Process message pattern 2 payload. + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut payload, tag) { + return None; + } + NonZeroU32::new(u32::from_be_bytes(payload)).map(|kid2| (kid2, noise)) + }; + // Check first key. + let mut ratchet_i = 1; + let mut chain_len = state.ratchet_state1.chain_len; + let mut result = test_ratchet_key(state.ratchet_state1.key.as_ref()); + // Check second key. + if result.is_none() { + ratchet_i = 2; + if let Some(rs) = state.ratchet_state2.as_ref() { + chain_len = rs.chain_len; + result = test_ratchet_key(rs.key.as_ref()); + } + } + // Check zero key. + if result.is_none() && !app.initiator_disallows_downgrade(session) { + chain_len = 0; + result = test_ratchet_key(&[0u8; RATCHET_SIZE]); + if result.is_some() { + // TODO: add some kind of warning callback or signal. + } + } + + let (kid_send, mut noise) = result.ok_or(byzantine_fault!(FailedAuth, true))?; + let mut x3 = ArrayVec::new(); + + // Process message pattern 3 s token. + let i = x3.len(); + x3.extend(ctx.s_secret.public_key_bytes()); + x3.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..])); + // Process message pattern 3 se token. + noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 3 payload. + let i = x3.len(); + x3.try_extend_from_slice(identity).unwrap(); + x3.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), &mut x3[i..])); + + let new_ratchet_state = create_ratchet_state(hmac, &mut noise, chain_len); + + let (ratchet_to_preserve, ratchet_to_delete) = if ratchet_i == 1 { + (Some(&state.ratchet_state1), state.ratchet_state2.as_ref()) + } else { + (state.ratchet_state2.as_ref(), Some(&state.ratchet_state1)) + }; + let result = app.save_ratchet_state( + &session.s_remote, + &session.session_data, + RatchetUpdate { + state1: &new_ratchet_state, + state2: ratchet_to_preserve, + state1_was_just_added: true, + state_deleted1: ratchet_to_delete, + state_deleted2: None, + }, + ); + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); + } + + let kek_recv = Zeroizing::new([0u8; HASHLEN]); + let kek_send = Zeroizing::new([0u8; HASHLEN]); + let nk_recv = Zeroizing::new([0u8; HASHLEN]); + let nk_send = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_recv, &mut kek_send); + noise.split(hmac, &mut nk_recv, &mut nk_send); + let nonce = to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0); + + //let identity = identity.clone(); + drop(state); + let mut state = session.state.write().unwrap(); + + state.key_mut(true).send.kid = Some(kid_send); + state.key_mut(true).send.kek = Some(Zeroizing::new(kek_send[..AES_256_KEY_SIZE].try_into().unwrap())); + state.key_mut(true).recv.kek = Some(Zeroizing::new(kek_recv[..AES_256_KEY_SIZE].try_into().unwrap())); + state.key_mut(true).nk = Some(App::AeadPool::new((&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap())); + state.ratchet_state2 = Some(state.ratchet_state1.clone()); + state.ratchet_state1 = new_ratchet_state.clone(); + let current_time = app.time(); + state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); + state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; + state.beta = ZetaAutomata::A3 { identity: identity.clone(), packet: x3, kid_send: kid_send.get(), nonce }; + + Ok(()) + } else { + Err(byzantine_fault!(FailedAuth, true)) + } + })(); + match &result { + Err(ReceiveError::ByzantineFault { .. }) => timeout_trans(state, session, app, ctx, app.time(), send), + Ok(packet) => send(packet, Some(&state.hk_send)), + _ => {} + } + result.map(|_| ()) +} +/// Corresponds to Transition Algorithm 4 found in Section 4.3. +pub(crate) fn received_x3_trans( + zeta: StateB2, + app: &App, + ctx: &Arc>, + kid: NonZeroU32, + mut x3: Vec, + send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) -> Result>, ReceiveError> { + use FaultType::*; + // -> s, se + if x3.len() < HANDSHAKE_COMPLETION_MIN_SIZE { + return Err(byzantine_fault!(InvalidPacket, true)); + } + if kid != zeta.kid_recv { + return Err(byzantine_fault!(UnknownLocalKeyId, true)); + } + + let mut noise = zeta.noise.clone(); + let mut i = 0; + // Process message pattern 3 s token. + let j = i + P384_PUBLIC_KEY_SIZE; + let k = j + AES_GCM_TAG_SIZE; + let tag = x3[j..k].try_into().unwrap(); + if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..j], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let s_remote = App::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()).ok_or(byzantine_fault!(FailedAuth, true))?; + i = k; + // Process message pattern 3 se token. + noise.mix_dh(&zeta.e_secret, &s_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 3 payload. + let k = x3.len(); + let j = k - AES_GCM_TAG_SIZE; + let tag = x3[j..k].try_into().unwrap(); + if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), &mut x3[i..j], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let identity_start = i; + let identity_end = j; + + let (kek_send, kek_recv) = noise.get_ask(LABEL_KEX_KEY); + let c = INIT_COUNTER; + + let action = app.check_accept_session(&s_remote, &x3[identity_start..identity_end]); + let responder_disallows_downgrade = action.responder_disallows_downgrade; + let responder_silently_rejects = action.responder_silently_rejects; + let session_data = action.session_data; + let create_reject = || { + let mut d = Vec::::new(); + let n = to_nonce(PACKET_TYPE_SESSION_REJECTED, c); + let tag = App::Aead::encrypt_in_place(&kek_send, n, None, &mut []); + d.extend(&tag); + // We just used a counter with this key, but we are not storing + // the fact we used it in memory. This is currently ok because the + // handshake is being dropped, so nonce reuse can't happen. + Packet(zeta.kid_send.get(), n, d) + }; + if let Some(session_data) = session_data { + let result = app.restore_by_identity(&s_remote, &session_data); + match result { + Ok((ratchet_state1, ratchet_state2)) => { + if (&zeta.ratchet_state != &ratchet_state1) & (Some(&zeta.ratchet_state) != ratchet_state2.as_ref()) { + if !responder_disallows_downgrade && zeta.ratchet_state.fingerprint().is_none() { + // TODO: add some kind of warning callback or signal. + } else { + if !responder_silently_rejects { + send(&create_reject(), Some(&zeta.hk_send)) + } + return Err(byzantine_fault!(FailedAuth, true)); + } + } + + let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); + // We must make sure the ratchet key is saved before we transition. + let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state.chain_len + 1); + let result = app.save_ratchet_state( + &s_remote, + &session_data, + RatchetUpdate { + state1: &new_ratchet_state, + state2: None, + state1_was_just_added: true, + state_deleted1: Some(&ratchet_state1), + state_deleted2: ratchet_state2.as_ref(), + }, + ); + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); + } + + let mut c1 = Vec::new(); + let n = to_nonce(PACKET_TYPE_KEY_CONFIRM, c); + let tag = App::Aead::encrypt_in_place(&kek_send, n, None, &mut []); + c1.extend(&tag); + + let (nk1, nk2) = noise.split(); + let keys = DuplexKey { + send: Keys { kek: Some(kek_send), nk: Some(nk1), kid: Some(zeta.kid_send) }, + recv: Keys { kek: Some(kek_recv), nk: Some(nk2), kid: Some(zeta.kid_recv) }, + }; + let current_time = app.time(); + + let mut session_map = ctx.session_map.lock().unwrap(); + use std::collections::hash_map::Entry::*; + let entry = match session_map.entry(zeta.kid_recv) { + // We could have issued the kid that we initially offered Alice to someone else + // before Alice was able to respond. It is unlikely but possible. + Occupied(_) => return Err(byzantine_fault!(OutOfSequence, false)), + Vacant(entry) => entry, + }; + let session = Arc::new(Session(Mutex::new(Zeta { + ctx: Arc::downgrade(ctx), + session_data, + was_bob: true, + s_remote, + send_counter: INIT_COUNTER + 1, + key_creation_counter: INIT_COUNTER + 1, + key_index: false, + keys: [keys, DuplexKey::default()], + ratchet_state1: new_ratchet_state, + ratchet_state2: None, + hk_send: zeta.hk_send.clone(), + resend_timer: current_time + App::SETTINGS.resend_time as i64, + timeout_timer: current_time + App::SETTINGS.rekey_timeout as i64, + beta: ZetaAutomata::S1, + counter_antireplay_window: std::array::from_fn(|_| 0), + defrag: zeta.defrag, + }))); + entry.insert(Arc::downgrade(&session)); + ctx.sessions.lock().unwrap().insert(Arc::as_ptr(&session), Arc::downgrade(&session)); + + send(&Packet(zeta.kid_send.get(), n, c1), Some(&zeta.hk_send)); + Ok(session) + } + Err(e) => Err(ReceiveError::RatchetIoError(e)), + } + } else { + if !responder_silently_rejects { + send(&create_reject(), Some(&zeta.hk_send)) + } + Err(byzantine_fault!(FailedAuth, true)) + } +} +/// Corresponds to Transition Algorithm 5 found in Section 4.3. +pub(crate) fn received_c1_trans( + zeta: &mut Zeta, + app: &App, + rng: &Mutex, + kid: NonZeroU32, + n: [u8; AES_GCM_IV_SIZE], + c1: Vec, + send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) -> Result> { + use FaultType::*; + + if c1.len() != KEY_CONFIRMATION_SIZE { + return Err(byzantine_fault!(InvalidPacket, true)); + } + let is_other = if Some(kid) == zeta.key_ref(true).recv.kid { + true + } else if Some(kid) == zeta.key_ref(false).recv.kid { + false + } else { + // Some key confirmation may have arrived extremely delayed. + // It is unlikely but possible. + return Err(byzantine_fault!(OutOfSequence, false)); + }; + + let specified_key = zeta.key_ref(is_other).recv.kek.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; + let tag = c1[..].try_into().unwrap(); + if !App::Aead::decrypt_in_place(specified_key, n, None, &mut [], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let (_, c) = from_nonce(&n); + if !zeta.update_counter_window(c) { + return Err(byzantine_fault!(ExpiredCounter, true)); + } + + let just_establised = is_other && matches!(&zeta.beta, ZetaAutomata::A3 { .. }); + if is_other { + if let ZetaAutomata::A3 { .. } | ZetaAutomata::R2 { .. } = &zeta.beta { + if zeta.ratchet_state2.is_some() { + let result = app.save_ratchet_state( + &zeta.s_remote, + &zeta.session_data, + RatchetUpdate { + state1: &zeta.ratchet_state1, + state2: None, + state1_was_just_added: false, + state_deleted1: zeta.ratchet_state2.as_ref(), + state_deleted2: None, + }, + ); + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); + } + } + + zeta.ratchet_state2 = None; + zeta.key_index ^= true; + zeta.timeout_timer = app.time() + + App::SETTINGS + .rekey_after_time + .saturating_sub(rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + zeta.resend_timer = i64::MAX; + zeta.beta = ZetaAutomata::S2; + } + } + let mut c2 = Vec::new(); + + let c = zeta.send_counter; + zeta.send_counter += 1; + let n = to_nonce(PACKET_TYPE_ACK, c); + let latest_confirmed_key = zeta.key_ref(false).send.kek.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; + let tag = App::Aead::encrypt_in_place(latest_confirmed_key, n, None, &mut []); + c2.extend(&tag); + + send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, c2), Some(&zeta.hk_send)); + Ok(just_establised) +} +/// Corresponds to the trivial Transition Algorithm described for processing C_2 packets found in +/// Section 4.3. +pub(crate) fn received_c2_trans( + zeta: &mut Zeta, + app: &App, + rng: &Mutex, + kid: NonZeroU32, + n: [u8; AES_GCM_IV_SIZE], + c2: Vec, +) -> Result<(), ReceiveError> { + use FaultType::*; + + if c2.len() != ACKNOWLEDGEMENT_SIZE { + return Err(byzantine_fault!(InvalidPacket, true)); + } + if Some(kid) != zeta.key_ref(false).recv.kid { + // Some acknowledgement may have arrived extremely delayed. + return Err(byzantine_fault!(UnknownLocalKeyId, false)); + } + if !matches!(&zeta.beta, ZetaAutomata::S1) { + // Some acknowledgement may have arrived extremely delayed. + return Err(byzantine_fault!(OutOfSequence, false)); + } + + let tag = c2[..].try_into().unwrap(); + if !App::Aead::decrypt_in_place(zeta.key_ref(false).recv.kek.as_ref().unwrap(), n, None, &mut [], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let (_, c) = from_nonce(&n); + if !zeta.update_counter_window(c) { + return Err(byzantine_fault!(ExpiredCounter, true)); + } + + zeta.timeout_timer = app.time() + + App::SETTINGS + .rekey_after_time + .saturating_sub(rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + zeta.resend_timer = i64::MAX; + zeta.beta = ZetaAutomata::S2; + Ok(()) +} +/// Corresponds to the trivial Transition Algorithm described for processing D packets found in +/// Section 4.3. +pub(crate) fn received_d_trans( + zeta: &mut Zeta, + kid: NonZeroU32, + n: [u8; AES_GCM_IV_SIZE], + d: Vec, +) -> Result<(), ReceiveError> { + use FaultType::*; + + if d.len() != SESSION_REJECTED_SIZE { + return Err(byzantine_fault!(InvalidPacket, true)); + } + if Some(kid) != zeta.key_ref(true).recv.kid || !matches!(&zeta.beta, ZetaAutomata::A3 { .. }) { + return Err(byzantine_fault!(OutOfSequence, true)); + } + + let tag = d[..].try_into().unwrap(); + if !App::Aead::decrypt_in_place(zeta.key_ref(true).recv.kek.as_ref().unwrap(), n, None, &mut [], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let (_, c) = from_nonce(&n); + if !zeta.update_counter_window(c) { + return Err(byzantine_fault!(ExpiredCounter, true)); + } + + zeta.expire(); + Ok(()) +} +/// Corresponds to the timer rules of the Zeta State Machine found in Section 4.1 - Definition 3. +pub(crate) fn service( + zeta: &mut Zeta, + session: &Arc>, + ctx: &Arc>, + app: &App, + current_time: i64, + send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) { + if zeta.timeout_timer <= current_time { + timeout_trans(zeta, session, app, ctx, current_time, send); + } else if zeta.resend_timer <= current_time { + // Corresponds to the resend timer rules found in Section 4.1 - Definition 3. + zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; + + let (p, mut control_payload) = match &zeta.beta { + ZetaAutomata::Null => return, + ZetaAutomata::A1(StateA1 { packet, .. }) => { + log!(app, ResentX1(session)); + return send(packet, None); + } + ZetaAutomata::A3 { packet, .. } => { + log!(app, ResentX3(session)); + return send(packet, Some(&zeta.hk_send)); + } + ZetaAutomata::S1 => { + log!(app, ResentKeyConfirm(session)); + (PACKET_TYPE_KEY_CONFIRM, Vec::new()) + } + ZetaAutomata::S2 => return, + ZetaAutomata::R1 { k1, .. } => { + log!(app, ResentK1(session)); + (PACKET_TYPE_REKEY_INIT, k1.clone()) + } + ZetaAutomata::R2 { k2, .. } => { + log!(app, ResentK2(session)); + (PACKET_TYPE_REKEY_COMPLETE, k2.clone()) + } + }; + let c = zeta.send_counter; + zeta.send_counter += 1; + let n = to_nonce(p, c); + let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut control_payload); + control_payload.extend(&tag); + send( + &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, control_payload), + Some(&zeta.hk_send), + ); + } +} +fn remap(session: &Arc>, zeta: &Zeta, rng: &Mutex, session_map: &SessionMap) -> NonZeroU32 { + let mut session_map = session_map.lock().unwrap(); + let weak = if let Some(Some(weak)) = zeta.key_ref(true).recv.kid.as_ref().map(|kid| session_map.remove(kid)) { + weak + } else { + Arc::downgrade(&session) + }; + let new_kid_recv = gen_kid(session_map.deref(), rng.lock().unwrap().deref_mut()); + session_map.insert(new_kid_recv, weak); + new_kid_recv +} +/// Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. +fn timeout_trans( + zeta: &mut Zeta, + session: &Arc>, + app: &App, + ctx: &Arc>, + current_time: i64, + send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) { + match &zeta.beta { + ZetaAutomata::Null => {} + ZetaAutomata::A1(StateA1 { identity, .. }) | ZetaAutomata::A3 { identity, .. } => { + if matches!(&zeta.beta, ZetaAutomata::A1(_)) { + log!(app, TimeoutX1(session)); + } else { + log!(app, TimeoutX3(session)); + } + let new_kid_recv = remap(session, &zeta, &ctx.rng, &ctx.session_map); + + if let Some(a1) = create_a1_state( + &ctx.rng, + &zeta.s_remote, + new_kid_recv, + &zeta.ratchet_state1, + zeta.ratchet_state2.as_ref(), + identity.clone(), + ) { + let (hk_recv, hk_send) = a1.noise.get_ask(LABEL_HEADER_KEY); + let packet = a1.packet.clone(); + + zeta.hk_send = hk_send; + *zeta.key_mut(true) = DuplexKey::default(); + zeta.key_mut(true).recv.kid = Some(new_kid_recv); + zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; + zeta.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; + zeta.beta = ZetaAutomata::A1(a1); + zeta.defrag = DefragBuffer::new(Some(hk_recv)); + + send(&packet, None); + } else { + zeta.expire(); + } + } + ZetaAutomata::S2 => { + // Corresponds to Transition Algorithm 6 found in Section 4.3. + log!(app, StartedRekeyingSentK1(session)); + let new_kid_recv = remap(session, &zeta, &ctx.rng, &ctx.session_map); + // -> s + // <- s + // ... + // -> psk, e, es, ss + let mut k1 = Vec::new(); + let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); + // Noise process prologue. + noise.mix_hash(&ctx.s_secret.public_key_bytes()); + noise.mix_hash(&zeta.s_remote.to_bytes()); + // Process message pattern 1 psk0 token. + noise.mix_key_and_hash(zeta.ratchet_state1.key.as_ref()); + // Process message pattern 1 e token. + let e_secret = noise.write_e(&ctx.rng, &mut k1); + // Process message pattern 1 es token. + if noise.mix_dh(&e_secret, &zeta.s_remote).is_none() { + zeta.expire(); + return; + } + // Process message pattern 1 ss token. + if noise.mix_dh(&ctx.s_secret, &zeta.s_remote).is_none() { + zeta.expire(); + return; + } + // Process message pattern 1 payload. + let i = k1.len(); + k1.extend(&new_kid_recv.get().to_be_bytes()); + noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_INIT, 0), i, &mut k1); + + zeta.key_mut(true).recv.kid = Some(new_kid_recv); + zeta.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; + zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; + zeta.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; + + let c = zeta.send_counter; + zeta.send_counter += 1; + let n = to_nonce(PACKET_TYPE_REKEY_INIT, c); + let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut k1); + k1.extend(&tag); + + send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k1), Some(&zeta.hk_send)); + } + ZetaAutomata::S1 { .. } => { + log!(app, TimeoutKeyConfirm(session)); + zeta.expire(); + } + ZetaAutomata::R1 { .. } => { + log!(app, TimeoutK1(session)); + zeta.expire(); + } + ZetaAutomata::R2 { .. } => { + log!(app, TimeoutK2(session)); + zeta.expire(); + } + } +} +/// Corresponds to Transition Algorithm 7 found in Section 4.3. +pub(crate) fn received_k1_trans( + zeta: &mut Zeta, + session: &Arc>, + app: &App, + rng: &Mutex, + session_map: &SessionMap, + s_secret: &App::KeyPair, + kid: NonZeroU32, + n: [u8; AES_GCM_IV_SIZE], + mut k1: Vec, + send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) -> Result<(), ReceiveError> { + use FaultType::*; + // -> s + // <- s + // ... + // -> psk, e, es, ss + // <- e, ee, se + if k1.len() != REKEY_SIZE { + return Err(byzantine_fault!(InvalidPacket, true)); + } + if Some(kid) != zeta.key_ref(false).recv.kid { + // Some rekey packet may have arrived extremely delayed. + return Err(byzantine_fault!(UnknownLocalKeyId, false)); + } + let should_rekey_as_bob = match &zeta.beta { + ZetaAutomata::S2 { .. } => true, + ZetaAutomata::R1 { .. } => zeta.was_bob, + _ => false, + }; + if !should_rekey_as_bob { + // Some rekey packet may have arrived extremely delayed. + return Err(byzantine_fault!(OutOfSequence, false)); + } + + let i = k1.len() - AES_GCM_TAG_SIZE; + let tag = k1[i..].try_into().unwrap(); + if !App::Aead::decrypt_in_place(zeta.key_ref(false).recv.kek.as_ref().unwrap(), n, None, &mut k1[..i], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let (_, c) = from_nonce(&n); + if !zeta.update_counter_window(c) { + return Err(byzantine_fault!(ExpiredCounter, true)); + } + k1.truncate(i); + + let result = (|| { + let mut i = 0; + let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_KK); + // Noise process prologue. + noise.mix_hash(&zeta.s_remote.to_bytes()); + noise.mix_hash(&s_secret.public_key_bytes()); + // Process message pattern 1 psk0 token. + noise.mix_key_and_hash(zeta.ratchet_state1.key.as_ref()); + // Process message pattern 1 e token. + let e_remote = noise.read_e(&mut i, &k1).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 1 es token. + noise.mix_dh(s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 1 ss token. + noise.mix_dh(s_secret, &zeta.s_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 1 payload. + let j = i + KID_SIZE; + let k = j + AES_GCM_TAG_SIZE; + let tag = k1[j..k].try_into().unwrap(); + if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..j], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let kid_send = NonZeroU32::new(u32::from_be_bytes(k1[i..j].try_into().unwrap())).ok_or(byzantine_fault!(FailedAuth, true))?; + + let mut k2 = Vec::new(); + // Process message pattern 2 e token. + let e_secret = noise.write_e(rng, &mut k2); + // Process message pattern 2 ee token. + noise.mix_dh(&e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 2 se token. + noise.mix_dh(&s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 2 payload. + let i = k2.len(); + let new_kid_recv = remap(session, &zeta, rng, session_map); + k2.extend(&new_kid_recv.get().to_be_bytes()); + noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), i, &mut k2); + + let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); + let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state1.chain_len + 1); + let result = app.save_ratchet_state( + &zeta.s_remote, + &zeta.session_data, + RatchetUpdate { + state1: &new_ratchet_state, + state2: Some(&zeta.ratchet_state1), + state1_was_just_added: true, + state_deleted1: zeta.ratchet_state2.as_ref(), + state_deleted2: None, + }, + ); + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); + } + let (kek_send, kek_recv) = noise.get_ask(LABEL_KEX_KEY); + let (nk_send, nk_recv) = noise.split(); + + zeta.key_mut(true).send.kid = Some(kid_send); + zeta.key_mut(true).send.kek = Some(kek_send); + zeta.key_mut(true).send.nk = Some(nk_send); + zeta.key_mut(true).recv.kid = Some(new_kid_recv); + zeta.key_mut(true).recv.kek = Some(kek_recv); + zeta.key_mut(true).recv.nk = Some(nk_recv); + zeta.ratchet_state2 = Some(zeta.ratchet_state1.clone()); + zeta.ratchet_state1 = new_ratchet_state; + let current_time = app.time(); + zeta.key_creation_counter = zeta.send_counter; + zeta.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; + zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; + zeta.beta = ZetaAutomata::R2 { k2: k2.clone() }; + + let c = zeta.send_counter; + zeta.send_counter += 1; + let n = to_nonce(PACKET_TYPE_REKEY_COMPLETE, c); + let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut k2); + k2.extend(&tag); + + send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k2), Some(&zeta.hk_send)); + Ok(()) + })(); + if matches!(result, Err(ReceiveError::ByzantineFault { .. })) { + zeta.expire(); + } + result +} +/// Corresponds to Transition Algorithm 8 found in Section 4.3. +pub(crate) fn received_k2_trans( + zeta: &mut Zeta, + app: &App, + kid: NonZeroU32, + n: [u8; AES_GCM_IV_SIZE], + mut k2: Vec, + send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) -> Result<(), ReceiveError> { + use FaultType::*; + // <- e, ee, se + if k2.len() != REKEY_SIZE { + return Err(byzantine_fault!(InvalidPacket, true)); + } + if Some(kid) != zeta.key_ref(false).recv.kid { + // Some rekey packet may have arrived extremely delayed. + return Err(byzantine_fault!(UnknownLocalKeyId, false)); + } + if !matches!(&zeta.beta, ZetaAutomata::R1 { .. }) { + // Some rekey packet may have arrived extremely delayed. + return Err(byzantine_fault!(OutOfSequence, false)); + } + + let i = k2.len() - AES_GCM_TAG_SIZE; + let tag = k2[i..].try_into().unwrap(); + if !App::Aead::decrypt_in_place(zeta.key_ref(false).recv.kek.as_ref().unwrap(), n, None, &mut k2[..i], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let (_, c) = from_nonce(&n); + if !zeta.update_counter_window(c) { + return Err(byzantine_fault!(ExpiredCounter, true)); + } + k2.truncate(i); + let result = (|| { + if let ZetaAutomata::R1 { noise, e_secret, .. } = &zeta.beta { + let mut noise = noise.clone(); + let mut i = 0; + // Process message pattern 2 e token. + let e_remote = noise.read_e(&mut i, &k2).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 2 ee token. + noise.mix_dh(e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 2 se token. + noise.mix_dh(e_secret, &zeta.s_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 2 payload. + let j = i + KID_SIZE; + let k = j + AES_GCM_TAG_SIZE; + let tag = k2[j..k].try_into().unwrap(); + if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..j], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let kid_send = NonZeroU32::new(u32::from_be_bytes(k2[i..j].try_into().unwrap())).ok_or(byzantine_fault!(InvalidPacket, true))?; + + let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); + let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state1.chain_len + 1); + let result = app.save_ratchet_state( + &zeta.s_remote, + &zeta.session_data, + RatchetUpdate { + state1: &new_ratchet_state, + state2: None, + state1_was_just_added: true, + state_deleted1: Some(&zeta.ratchet_state1), + state_deleted2: zeta.ratchet_state2.as_ref(), + }, + ); + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); + } + let (kek_recv, kek_send) = noise.get_ask(LABEL_KEX_KEY); + let (nk_recv, nk_send) = noise.split(); + + zeta.key_mut(true).send.kid = Some(kid_send); + zeta.key_mut(true).send.kek = Some(kek_send); + zeta.key_mut(true).send.nk = Some(nk_send); + zeta.key_mut(true).recv.kek = Some(kek_recv); + zeta.key_mut(true).recv.nk = Some(nk_recv); + zeta.ratchet_state1 = new_ratchet_state; + zeta.key_index ^= true; + let current_time = app.time(); + zeta.key_creation_counter = zeta.send_counter; + zeta.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; + zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; + zeta.beta = ZetaAutomata::S1; + + let mut c1 = Vec::new(); + let c = zeta.send_counter; + zeta.send_counter += 1; + let n = to_nonce(PACKET_TYPE_KEY_CONFIRM, c); + let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut []); + c1.extend(&tag); + + send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, c1), Some(&zeta.hk_send)); + Ok(()) + } else { + unreachable!() + } + })(); + if matches!(result, Err(ReceiveError::ByzantineFault { .. })) { + zeta.expire(); + } + result +} +/// Corresponds to Algorithm 9 found in Section 4.3. +pub(crate) fn send_payload( + zeta: &mut Zeta, + mut payload: Vec, + send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), +) -> Result<(), SendError> { + use SendError::*; + + if matches!(&zeta.beta, ZetaAutomata::Null) { + return Err(SessionExpired); + } + if !matches!( + &zeta.beta, + ZetaAutomata::S1 | ZetaAutomata::S2 | ZetaAutomata::R1 { .. } | ZetaAutomata::R2 { .. } + ) { + return Err(SessionNotEstablished); + } + let c = zeta.send_counter; + zeta.send_counter += 1; + if c >= zeta.key_creation_counter + App::SETTINGS.rekey_after_key_uses { + if c >= zeta.key_creation_counter + EXPIRE_AFTER_USES { + zeta.expire(); + } else { + // Cause timeout to occur next service interval. + zeta.timeout_timer = i64::MIN; + } + } + + let n = to_nonce(PACKET_TYPE_DATA, c); + let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.nk.as_ref().unwrap(), n, None, &mut payload); + payload.extend(&tag); + + send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, payload), Some(&zeta.hk_send)); + Ok(()) +} +/// Corresponds to Algorithm 10 found in Section 4.3. +pub(crate) fn received_payload_in_place( + zeta: &mut Zeta, + kid: NonZeroU32, + n: [u8; AES_GCM_IV_SIZE], + payload: &mut Vec, +) -> Result<(), ReceiveError> { + use FaultType::*; + + if payload.len() < AES_GCM_TAG_SIZE { + return Err(byzantine_fault!(FailedAuth, true)); + } + let is_other = if Some(kid) == zeta.key_ref(true).recv.kid { + true + } else if Some(kid) == zeta.key_ref(false).recv.kid { + false + } else { + // A packet would have to be delayed by around an hour for this error to occur, but it can + // occur naturally due to just out-of-order transport. + return Err(byzantine_fault!(OutOfSequence, false)); + }; + + let i = payload.len() - AES_GCM_TAG_SIZE; + let specified_key = zeta.key_ref(is_other).recv.nk.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; + let tag = payload[i..].try_into().unwrap(); + if !App::Aead::decrypt_in_place(specified_key, n, None, &mut payload[..i], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let (_, c) = from_nonce(&n); + if !zeta.update_counter_window(c) { + // This error is marked as not happening naturally, but it could occur if something about + // the transport protocol is duplicating packets. + return Err(byzantine_fault!(ExpiredCounter, true)); + } + payload.truncate(i); + + Ok(()) +} + +impl Session { + /// Mark a session as expired. This will make it impossible for this session to successfully + /// receive or send data or control packets. It is recommended to simply `drop` the session + /// instead, but this can provide some reassurance in complex shared ownership situations. + pub fn expire(&mut self) { + self.0.lock().unwrap().expire(); + } +} + +impl Drop for Session { + fn drop(&mut self) { + self.expire(); + } +} diff --git a/src/zssp copy.rs b/src/zssp copy.rs new file mode 100644 index 0000000..413c38d --- /dev/null +++ b/src/zssp copy.rs @@ -0,0 +1,2704 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at https://mozilla.org/MPL/2.0/. +* +* (c) ZeroTier, Inc. +* https://www.zerotier.com/ +*/ +// ZSSP: ZeroTier Secure Session Protocol +// FIPS compliant Noise_XK with Jedi powers (Kyber1024) and built-in attack-resistant large payload (fragmentation) support. + +use std::cmp::Reverse; +use std::collections::HashMap; +use std::hash::Hash; +use std::num::{NonZeroU32, NonZeroU64}; +use std::ops::DerefMut; +use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; +use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; + +use crate::crypto::aes::{AesDec, AesEnc}; +use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; +use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; +use crate::crypto::rand_core::RngCore; +use crate::crypto::sha512::{HmacSha512, HashSha512}; + +use crate::error::{FaultType, OpenError, ReceiveError, SendError}; +use crate::frag_cache::UnassociatedFragCache; +use crate::fragged::{Assembled, Fragged}; +use crate::handshake_cache::UnassociatedHandshakeCache; +use crate::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; +use crate::log_event::LogEvent; +use crate::proto::*; +use crate::symmetric_state::SymmetricState; +use crate::{applicationlayer::*, RatchetState}; + +/// Session context for local application. +/// +/// Each application using ZSSP must create an instance of this to own sessions and +/// defragment incoming packets that are not yet associated with a session. +/// +/// Internally this is just a clonable Arc, so it can be safely shared with multiple threads. +pub struct Context(pub Arc>); +impl Clone for Context { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} +pub struct ContextInner { + static_keypair: Application::KeyPair, + unassociated_defrag_cache: Mutex>, + unassociated_handshake_states: UnassociatedHandshakeCache, + /// `session_queue -> state_machine_lock -> state -> session_map` + session_queue: Mutex>, Reverse>>, + session_map: RwLock>, bool)>>, + challenge_counter: AtomicU64, + challenge_antireplay_window: [AtomicU64; CHALLENGE_COUNTER_WINDOW_MAX_OOO], + challenge_salt: [u8; CHALLENGE_SALT_SIZE], + rng: Mutex, +} + +/// Result generated by the context packet receive function, with possible payloads. +pub enum ReceiveResult<'b, Application: ApplicationLayer> { + /// Packet superficially appeared valid but is not associated with a session yet. + /// This can occur because the packet was only a fragment of a larger packet, + /// or if it was a control packet that does not go through full Noise authentication. + Unassociated, + /// Packet was authentic and belongs to this specific session. + Session(Arc>, SessionEvent<'b>), + /// Packet was a part of a handshake, and while it superficially appeared valid the application + /// explicitly rejected it. + /// Relates to callbacks `check_allow_incoming_session`, `hello_requires_recognized_ratchet` + /// and `check_accept_session`. + Rejected, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum SessionEvent<'b> { + /// The received packet was valid, and it contained the necessary keys to fully establish a new + /// session with Alice, the handshake initiator. + /// + /// If the session Arc returned is dropped, the session with this peer will be immediately + /// terminated. Save the session Arc to some long lived datastructure to keep it alive. + NewSession, + /// When Alice calls `Context::open`, a session will be created, but Bob will not yet have + /// received this session. They will have to successfully complete a handshake first. + /// + /// Alice will receive this return value when the received packet confirms both parties + /// have completed the initial handshake and now have a shared session with each other. + /// If according to the upper protocol, Bob is the first party to send data, it is possible for + /// Alice to start receiving data from Bob before this value is returned. + /// + /// This return value can only occur once per session, only for session objects that were + /// created with `Context::open`. + Established, + /// Bob explicitly refused to establish a session with Alice, and sent us an error code. + /// The application should immediately drop this session as Bob will not allow us to connect. + /// + /// This return value cannot occur after a session is fully established. + Rejected, + /// The received packet was valid and a data payload was decoded and authenticated. + Data(&'b mut [u8]), + /// The received packet was some authentic protocol control packet. No action needs to be taken. + Control, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum IncomingSessionAction { + Allow, + Challenge, + Drop, +} + +/// ZeroTier Secure Session Protocol (ZSSP) Session +/// +/// A FIPS/NIST compliant variant of Noise_XK with hybrid Kyber1024 PQ data forward secrecy. +pub struct Session { + /// An arbitrary application defined object associated with each session. + pub application_data: Application::SessionData, + /// Is true if the local peer acted as Bob, the responder in the initial key exchange. + pub was_bob: bool, + /// The receive context associated with this session, + /// only this context can receive messages from the remote peer. + context: Weak>, + /// Handle into the session queue for changing the update timer. + queue_idx: BinaryHeapIndex, + + remote_static_key: Application::PublicKey, + send_counter: AtomicU64, + /// This bool signals to all threads to stop incrementing the counter and instead error out. + session_has_expired: AtomicBool, + /// The following is a ring buffer of previously seen counter values, where we use the counter's + /// value as the index of the head of the ring buffer. + counter_antireplay_window: [AtomicU64; COUNTER_WINDOW_MAX_OOO], + /// Enforces atomicity of state machine transitions. + /// There is a standard locking sequence, + /// it goes `session_queue -> state_machine_lock -> state -> session_map`. + /// Any lock can be skipped but they must be locked in that order. + state_machine_lock: Mutex<()>, + state: RwLock>, + defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], + header_send_cipher: Application::PrpEnc, + header_receive_cipher: Application::PrpDec, + kex_send_cipher: Mutex>, + kex_receive_cipher: Mutex>, + /// Pre-computed rekeying values. + noise_kk_ss: Secret, + noise_kk_local_init_h: [u8; HASHLEN], + noise_kk_remote_init_h: [u8; HASHLEN], +} +/// `AesGcm` is not threadsafe, but it is threadsafe when inside a `Mutex`. +unsafe impl Send for Session {} +unsafe impl Sync for Session {} + +/// Session state may only be mutated during atomic transitions of the offer state machine. +struct SessionMutableState { + ratchet_states: [RatchetState; 2], + /// For OOO rekeying reliability we allow our version of Noise CipherState to hold the last two + /// session keys, instead of just the most recent one. + cipher_states: [Option>; 2], + /// This is the index of `noise_cipher_state` that contains the most recent key. + /// It will be attached to fragment headers to help with OOO transport. + current_key: usize, + /// This defines the exact state of the offer state machine we are in. + outgoing_offer: OfferStateMachine, +} + +/// These offer enums form a state machine. +/// Documented below are the only legal transitions for this state machine. +/// A session is initialized with an `outgoing_offer` of either NoiseXKPattern1 or Normal. +enum OfferStateMachine { + Normal { + timeout: i64, + }, // -> NoiseKKPattern1, NoiseKKPattern2 + /// This state uses a lot of memory so we put it on the heap. + NoiseXKPattern1or3(Box>), // -> Normal + NoiseKKPattern1 { + next_retry_time: AtomicI64, + timeout: i64, + new_key_id: NonZeroU32, + noise_e_secret: Application::KeyPair, + noise_message: [u8; NoiseKKPattern1or2::SIZE], + noise_ck: SymmetricState, + noise_h_pskep: [u8; HASHLEN], + }, // -> NoiseKKPattern2, KeyConfirm + NoiseKKPattern2 { + next_retry_time: AtomicI64, + timeout: i64, + noise_message: [u8; NoiseKKPattern1or2::SIZE], + kex_send_key: Secret, + }, // -> Normal + KeyConfirm { + next_retry_time: AtomicI64, + timeout: i64, + }, // -> Normal + Null, +} + +pub(crate) struct NoiseXKBobHandshakeState { + /// Can never be Null. + ratchet_state: RatchetState, + remote_key_id: NonZeroU32, + local_key_id: NonZeroU32, + header_receive_key: Secret, + header_send_key: Secret, + noise_h_ee1peekem1pskp: [u8; HASHLEN], + noise_e_secret: Application::KeyPair, + noise_ck_eseeekem1psk: SymmetricState, + noise_k_eseeekem1psk: Secret, + noise_pattern3_defrag: Mutex>, +} + +struct NoiseXKAliceHandshake { + next_retry_time: AtomicI64, + timeout: i64, + /// A secure random number put in the header of Alice's fragments to identify them. + /// If a DDOS attacker could guess this they could block Alice starting the handshake. + local_key_id: NonZeroU32, + alice_identity_blob: Application::LocalIdentityBlob, + offer: NoiseXKAliceHandshakeState, +} + +enum NoiseXKAliceHandshakeState { + NoiseXKPattern1 { + noise_h_ee1p: [u8; HASHLEN], + noise_e_secret: Application::KeyPair, + noise_e1_secret: Secret, + noise_ck_es: SymmetricState, + /// ZSSP assumes an unreliable, out-of-order physical transport environment, so for that + /// reason we have to resend key offers. + noise_message: [u8; NoiseXKPattern1::MAX_SIZE], + noise_message_len: usize, + message_id: u64, + }, + NoiseXKPattern3 { + noise_message: [u8; NoiseXKPattern3::MAX_SIZE], + noise_message_len: usize, + }, +} + +struct SessionKey { + remote_key_id: NonZeroU32, + local_key_id: NonZeroU32, + /// Pool of reusable sending ciphers. + receive_cipher_pool: [Mutex; 8], + /// Pool of reusable receiving ciphers. + send_cipher_pool: [Mutex; 8], + /// Rekey at or after this counter. + rekey_at_counter: u64, + /// Hard error when this counter value is reached or exceeded. + expire_at_counter: u64, +} + +macro_rules! byzantine_fault { + ($name:expr, $is_natural:ident) => { + ReceiveError::ByzantineFault { + file: file!(), + line: line!(), + error: $name, + is_naturally_occurring: $is_natural, + } + }; +} + +impl Context { + /// Create a new session context. + pub fn new(static_keypair: Application::KeyPair, mut rng: Application::Rng) -> Self { + debug_assert!(Application::REKEY_AFTER_TIME_MAX_JITTER_MS > 0, "Invalid protocol constant"); + let mut challenge_salt = [0u8; CHALLENGE_SALT_SIZE]; + rng.fill_bytes(&mut challenge_salt); + Self(Arc::new(ContextInner { + static_keypair, + unassociated_defrag_cache: Mutex::new(UnassociatedFragCache::new()), + unassociated_handshake_states: UnassociatedHandshakeCache::new(), + session_map: RwLock::new(HashMap::new()), + session_queue: Mutex::new(IndexedBinaryHeap::new()), + challenge_counter: AtomicU64::new(INIT_COUNTER), + challenge_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), + challenge_salt, + rng: Mutex::new(rng), + })) + } + + /// Perform periodic background service and cleanup tasks. + /// + /// This returns the number of milliseconds until it should be called again. The caller should + /// try to satisfy this but small variations in timing of up to +/- a second or two are not + /// a problem. + /// + /// * `send_to` - Function to get a sender and an MTU to send something over an active session + /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced + /// with remote peers (although both of these properties would help reliability slightly). + /// Used to determine if any current handshakes should be resent or timed-out, or if a session + /// should rekey. + pub fn service bool>( + &self, + app: &Application, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + current_time: i64, + ) -> i64 { + let retry_next = current_time.saturating_add(Application::RETRY_INTERVAL_MS); + let mut next_service_time = 2 * Application::RETRY_INTERVAL_MS; + + let mut session_queue = self.0.session_queue.lock().unwrap(); + // This update system takes heavy advantage of the fact that sessions only need to be updated + // either roughly every second or roughly every hour. That big gap allows for minor optimizations. + // If the gap changes (unlikely) this code may need to be rewritten. + while let Some((session, timer, queue_idx)) = session_queue.peek() { + if timer.0 >= current_time { + next_service_time = next_service_time.min(timer.0 - current_time); + break; + } + let session = match session.upgrade() { + Some(s) => s, + _ => { + session_queue.remove(queue_idx); + continue; + } + }; + let state = session.state.read().unwrap(); + use OfferStateMachine::*; + let next_timer = match &state.outgoing_offer { + Normal { timeout, .. } => { + if *timeout <= current_time { + drop(state); + if let Some((send, _)) = send_to(&session) { + let result = initiate_rekey(&self.0, &session, send, current_time); + if result.is_ok() { + app.event_log(LogEvent::ServiceKKStart(&session), current_time); + } + result.unwrap_or(retry_next) + } else { + retry_next + } + } else { + *timeout + } + } + // If there's an outstanding attempt to open a session, retransmit this + // periodically in case the initial packet doesn't make it. + NoiseXKPattern1or3(handshake_state) => { + if let Some(ts) = process_timer(&handshake_state.next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { + ts + } else { + // We have to eventually time out NoiseXKPattern3 because of unreliable network conditions. + if handshake_state.timeout <= current_time { + drop(state); + let _kex_lock = session.state_machine_lock.lock().unwrap(); + let mut state = session.state.write().unwrap(); + let ratchet_state = state.ratchet_states.clone(); + // Since we dropped the lock we must re-check if we are in the correct state. + if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { + if handshake_state.timeout <= current_time { + app.event_log(LogEvent::ServiceXKTimeout(&session), current_time); + handshake_state.reinitialize( + &session, + &ratchet_state, + &mut self.0.session_map.write().unwrap(), + &mut self.0.rng.lock().unwrap(), + current_time, + ); + } + } + } else if let Some((mut send, mut mtu)) = send_to(&session) { + mtu = mtu.max(MIN_TRANSPORT_MTU); + match &handshake_state.offer { + NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } => { + app.event_log(LogEvent::ServiceXK1Resend(&session), current_time); + // We are in state NoiseXKPattern1 so resend noise_pattern1. + send_with_fragmentation( + &mut send, + mtu, + &mut noise_message.clone()[..*noise_message_len], + PACKET_TYPE_NOISE_XK_PATTERN_1, + None, + *message_id, + None::<&Application::PrpEnc>, + ); + } + NoiseXKAliceHandshakeState::NoiseXKPattern3 { noise_message, noise_message_len, .. } => { + app.event_log(LogEvent::ServiceXK3Resend(&session), current_time); + send_with_fragmentation( + &mut send, + mtu, + &mut noise_message.clone()[..*noise_message_len], + PACKET_TYPE_NOISE_XK_PATTERN_3, + state.cipher_states[0].as_ref().map(|k| k.remote_key_id), + 0, + Some(&session.header_send_cipher), + ); + } + } + } + retry_next + } + } + NoiseKKPattern1 { next_retry_time, timeout, noise_message, .. } | NoiseKKPattern2 { next_retry_time, timeout, noise_message, .. } => { + if let Some(ts) = process_timer(next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { + ts + } else { + if *timeout <= current_time { + app.event_log(LogEvent::ServiceKKTimeout(&session), current_time); + next_retry_time.store(i64::MAX, Ordering::Relaxed); + drop(state); + session.expire_inner(&self.0, &mut session_queue); + } else { + let packet_type = if let NoiseKKPattern1 { .. } = &state.outgoing_offer { + app.event_log(LogEvent::ServiceKK1Resend(&session), current_time); + PACKET_TYPE_NOISE_KK_PATTERN_1 + } else { + app.event_log(LogEvent::ServiceKK2Resend(&session), current_time); + PACKET_TYPE_NOISE_KK_PATTERN_2 + }; + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&state, send, packet_type, noise_message); + } + } + retry_next + } + } + KeyConfirm { next_retry_time, timeout, .. } => { + if let Some(ts) = process_timer(next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { + ts + } else { + if *timeout <= current_time { + app.event_log(LogEvent::ServiceKeyConfirmTimeout(&session), current_time); + next_retry_time.store(i64::MAX, Ordering::Relaxed); + drop(state); + session.expire_inner(&self.0, &mut session_queue); + } else { + app.event_log(LogEvent::ServiceKeyConfirmResend(&session), current_time); + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&state, send, PACKET_TYPE_KEY_CONFIRM, &[]); + } + } + retry_next + } + } + Null => retry_next, + }; + session_queue.change_priority(queue_idx, Reverse(next_timer)); + } + drop(session_queue); + + self.0 + .unassociated_defrag_cache + .lock() + .unwrap() + .check_for_expiry(Application::INITIAL_OFFER_TIMEOUT_MS, current_time); + self.0.unassociated_handshake_states.service(current_time); + + next_service_time + } + + /// Create a new session and send initial packet(s) to other side. + /// + /// This will return SendError::DataTooLarge if the combined size of the metadata and the local + /// static public blob (as retrieved from the application layer) exceed MAX_INIT_PAYLOAD_SIZE. + /// + /// * `app` - Application layer instance + /// * `send` - Function to be called to send one or more initial packets to the remote being + /// contacted + /// * `mtu` - MTU for initial packets + /// * `remote_static_key` - Remote side's static public NIST P-384 key + /// * `application_data` - Arbitrary data meaningful to the application to include with session + /// object + /// * `ratchet_state` - The last saved and confirmed ratchet state associated with this remote + /// peer, or None if we do not have one. + /// * `local_identity_blob` - Payload to be sent to Bob that contains the information necessary + /// for the upper protocol to authenticate and approve of Alice's identity. + /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced + /// with the remote peer. Used to determine when this offer should be resent. + pub fn open( + &self, + app: &Application, + mut send: impl FnMut(&mut [u8]) -> bool, + mut mtu: usize, + remote_static_key: Application::PublicKey, + application_data: Application::SessionData, + local_identity_blob: Application::LocalIdentityBlob, + current_time: i64, + ) -> Result>, OpenError> { + mtu = mtu.max(MIN_TRANSPORT_MTU); + if local_identity_blob.as_ref().len() > MAX_IDENTITY_BLOB_SIZE { + return Err(OpenError::DataTooLarge); + } + let result = app.restore_by_identity(&remote_static_key, &application_data, current_time); + match result { + Ok(ratchet_states) => { + let sha512 = &mut Application::Hash::new(); + + let mut noise_kk_ss = Secret::new(); + if !self.0.static_keypair.agree(&remote_static_key, noise_kk_ss.as_mut()) { + return Err(OpenError::InvalidPublicKey); + } + let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); + let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_static_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_static_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); + + let mut session_queue = self.0.session_queue.lock().unwrap(); + let mut session_map = self.0.session_map.write().unwrap(); + let local_key_id = generate_key_id(&session_map, &mut self.0.rng.lock().unwrap()); + // Begin Noise XKhfs+psk2. + let (offer, a2b_header_key, b2a_header_key) = NoiseXKAliceHandshake::::initialize( + local_key_id, + &remote_static_key, + &ratchet_states, + &mut self.0.rng.lock().unwrap(), + )?; + let handshake_state = Box::new(NoiseXKAliceHandshake { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), + local_key_id, + alice_identity_blob: local_identity_blob, + offer, + }); + if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } = &handshake_state.offer { + send_with_fragmentation( + &mut send, + mtu, + &mut noise_message.clone()[..*noise_message_len], + PACKET_TYPE_NOISE_XK_PATTERN_1, + None, + *message_id, + None::<&Application::PrpEnc>, + ); + } + + let queue_idx = session_queue.reserve_index(); + let session = Arc::new(Session { + context: Arc::downgrade(&self.0), + queue_idx, + application_data, + remote_static_key, + send_counter: AtomicU64::new(INIT_COUNTER), + session_has_expired: AtomicBool::new(false), + counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), + state_machine_lock: Mutex::new(()), + state: RwLock::new(SessionMutableState { + ratchet_states: ratchet_states.clone(), + cipher_states: [None, None], + // Points at 1 until the first key is confirmed. + current_key: 1, + outgoing_offer: OfferStateMachine::NoiseXKPattern1or3(handshake_state), + }), + header_send_cipher: Application::PrpEnc::new(a2b_header_key.as_ref()), + header_receive_cipher: Application::PrpDec::new(b2a_header_key.as_ref()), + kex_receive_cipher: Mutex::new(None), + kex_send_cipher: Mutex::new(None), + noise_kk_ss: noise_kk_ss.clone(), + noise_kk_local_init_h, + noise_kk_remote_init_h, + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + was_bob: false, + }); + session_map.insert(local_key_id, (Arc::downgrade(&session), false)); + session_queue.push_reserved( + queue_idx, + Arc::downgrade(&session), + Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + ); + + Ok(session) + } + Err(e) => Err(OpenError::RatchetIoError(e)), + } + } + + /// Receive, authenticate, decrypt, and process a physical wire packet. + /// + /// The check_allow_incoming_session function is called when an initial Noise_XK init message is + /// received. This is before anything is known about the caller. A return value of true proceeds + /// with negotiation. False drops the packet and ignores the inbound attempt. + /// + /// The check_accept_session function is called at the end of negotiation for an incoming + /// session with the caller's static public blob. It must return the P-384 static public key + /// extracted from the supplied blob and application data. A return of Some() accepts the + /// session and will always result in a new session ReceiveResult being returned. + /// + /// * `app` - Interface to application using ZSSP + /// * `check_allow_incoming_session` - Function to call to check whether an unidentified new + /// session should be accepted + /// * `check_accept_session` - Function to accept sessions after final negotiation. + /// The second argument is the identity blob that the remote peer sent us. The application + /// must verify this identity is associated with the remote peer's static key. + /// The third argument is the ratchet chain length, or ratchet count. + /// To prevent desync, if this function returns (Some(_), _), no other open session with the + /// same remote peer must exist. + /// * `send_unassociated_reply` - Function to send reply packets directly when no session exists + /// * `send_unassociated_mtu` - MTU for unassociated replies + /// * `send_to` - Function to get senders for existing sessions, permitting MTU and path lookup + /// * `remote_address` - Whatever the remote address is, as long as you can Hash it + /// * `data_buf` - Buffer to receive decrypted and authenticated object data (an error is + /// returned if too small) + /// * `incoming_physical_packet_buf` - Buffer containing incoming wire packet + /// (receive() takes ownership) + /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced + /// with the remote peer. Used to check the state of local offers we may currently have or want + /// to put in-flight. + pub fn receive<'a, SendFn: FnMut(&mut [u8]) -> bool>( + &self, + app: &Application, + check_allow_incoming_session: impl FnOnce() -> IncomingSessionAction, + check_accept_session: impl FnOnce(&Application::PublicKey, &[u8], u64) -> (Option<(bool, Application::SessionData)>, bool), + mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, + mut send_unassociated_mtu: usize, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + remote_address: &impl Hash, + data_buf: &'a mut [u8], + mut incoming_physical_packet_buf: Application::IncomingPacketBuffer, + current_time: i64, + ) -> Result, ReceiveError> { + send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); + let incoming_physical_packet: &mut [u8] = incoming_physical_packet_buf.as_mut(); + let incoming_physical_packet_len = incoming_physical_packet.len(); + if incoming_physical_packet_len < MIN_PACKET_SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + + // The first section parses the header and looks up relevant state information. If it's a DATA + // or NOP packet it gets handled right here, otherwise we pull out a set of variables and + // continue to the logic that handles KEX and session control packets. + + let mut assembled_packet = Assembled::new(); // needs to outlive the block below + let mut incoming = None; + let (session, packet_type, fragments) = { + let local_key_id = incoming_physical_packet[0..SESSION_ID_SIZE].try_into().unwrap(); + // `from_ne_bytes` because this id was generated locally. + if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(local_key_id)) { + let session_map = self.0.session_map.read().unwrap(); + if let Some((Some(session), key_index)) = session_map.get(&local_key_id).map(|r| (r.0.upgrade(), r.1 as usize)) { + drop(session_map); + session.header_receive_cipher.decrypt_in_place( + (&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) + .try_into() + .unwrap(), + ); + let (fragment_count, fragment_no, packet_type, incoming_counter, header_nonce) = parse_packet_header(incoming_physical_packet); + // Handle replay protection. + if PACKET_TYPE_RANGE_TRANSPORT.contains(&packet_type) { + // For DOS resistant reply-protection we need to check that the given counter is + // in the window of valid counters immediately. + // But for packets larger than 1 fragment we can't actually record the + // counter as received until we've authenticated the packet. + // So we check the counter window twice, and only update it the second time + // after the packet has been authenticated. + if !session.check_receive_window(incoming_counter) { + // This can occur naturally if packets arrive way out of order, or + // if they are duplicates. + // This can also be naturally triggered if Bob has just successfully + // received the first session key and is reject all of Alice's resends. + // This can also occur if a session was manually expired, but not + // dropped, and the remote party is still sending us data. + return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); + } + if packet_type != PACKET_TYPE_DATA { + // This is a control packet. + if fragment_count != 1 || fragment_no > 0 { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + return receive_control_fragment( + self, + session, + app, + send_to, + packet_type, + incoming_counter, + incoming_physical_packet_buf.as_mut(), + current_time, + ); + } + } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_2 { + // We need to reject fragments marked with this type if they are sent out + // of sequence, since an attacker is able to replay them. + match &session.state.read().unwrap().outgoing_offer { + OfferStateMachine::NoiseXKPattern1or3(handshake_state) => match &handshake_state.offer { + NoiseXKAliceHandshakeState::NoiseXKPattern1 { .. } => { + if incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + } + // This error can occur naturally if Bob's initial reply to Alice had a + // resend that was delayed massively and arrived out of order. + _ => return Err(byzantine_fault!(FaultType::OutOfSequence, true)), + }, + _ => return Err(byzantine_fault!(FaultType::OutOfSequence, false)), + }; + } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_3 { + // This can be triggered if Bob successfully received a session key and + // needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3. + return Err(byzantine_fault!(FaultType::OutOfSequence, true)); + } else { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + // Handle defragmentation. + let fragments = if fragment_count > 1 { + let idx = incoming_counter as usize % session.defrag.len(); + session.defrag[idx].lock().unwrap().assemble( + header_nonce, + incoming_physical_packet_buf, + fragment_no, + fragment_count, + &mut assembled_packet, + ); + if assembled_packet.is_empty() { + // We have not yet authenticated the sender so we do not report + // receiving a packet from them. + return Ok(ReceiveResult::Unassociated); + } else { + assembled_packet.as_ref() + } + } else { + std::array::from_ref(&incoming_physical_packet_buf) + }; + // Handle DATA in the fastest path when we have a session. + if packet_type == PACKET_TYPE_DATA { + let state = session.state.read().unwrap(); + // The error here can occur because the other party is using a brand new + // session key that we have not received yet. + let key = state.cipher_states[key_index] + .as_ref() + .ok_or(byzantine_fault!(FaultType::OutOfSequence, true))?; + let mut c = key.get_receive_cipher(incoming_counter); + c.set_iv(&create_message_nonce(packet_type, incoming_counter)); + + let mut data_len = 0; + + // Decrypt fragments 0..N-1 where N is the number of fragments. + for f in fragments[..(fragments.len() - 1)].iter() { + let f: &[u8] = f.as_ref(); + debug_assert!(f.len() >= HEADER_SIZE); + let current_frag_data_start = data_len; + data_len += f.len() - HEADER_SIZE; + if data_len > data_buf.len() { + return Err(ReceiveError::DataBufferTooSmall); + } + c.decrypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); + } + + // Decrypt final fragment (or only fragment if not fragmented) + let current_frag_data_start = data_len; + let last_fragment = fragments.last().unwrap().as_ref(); + if last_fragment.len() < (HEADER_SIZE + AES_GCM_TAG_SIZE) { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE); + if data_len > data_buf.len() { + return Err(ReceiveError::DataBufferTooSmall); + } + let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE; + c.decrypt(&last_fragment[HEADER_SIZE..payload_end], &mut data_buf[current_frag_data_start..data_len]); + + let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..].try_into().unwrap()); + drop(c); + drop(state); + + if !aead_authentication_ok { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + if !session.update_receive_window(incoming_counter) { + // This can be naturally triggered because Bob has just + // successfully received a session key and needs to reject + // all of Alice's resends. + // This can also occur naturally if some part of the outer + // system is duplicating the packets being sent to us. + // We are safely deduplicating them here. + return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); + } + // Packet fully authenticated + return Ok(ReceiveResult::Session(session, SessionEvent::Data(&mut data_buf[..data_len]))); + } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_2 { + (Some(session), packet_type, fragments) + } else { + unreachable!() + } + } else { + drop(session_map); + // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 + incoming = self.0.unassociated_handshake_states.get(local_key_id); + if let Some(incoming) = incoming.as_ref() { + Application::PrpDec::new(incoming.header_receive_key.as_ref()).decrypt_in_place( + (&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) + .try_into() + .unwrap(), + ); + let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(incoming_physical_packet); + app.event_log( + LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), + current_time, + ); + if packet_type != PACKET_TYPE_NOISE_XK_PATTERN_3 { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + let fragments = if fragment_count > 1 { + incoming.noise_pattern3_defrag.lock().unwrap().assemble( + header_nonce, + incoming_physical_packet_buf, + fragment_no, + fragment_count, + &mut assembled_packet, + ); + if !assembled_packet.is_empty() { + assembled_packet.as_ref() + } else { + return Ok(ReceiveResult::Unassociated); + } + } else { + std::array::from_ref(&incoming_physical_packet_buf) + }; + // We must guarantee that this incoming handshake is processed once and only + // once. This prevents catastrophic nonce reuse caused by multithreading. + if self.0.unassociated_handshake_states.remove(local_key_id) { + (None, PACKET_TYPE_NOISE_XK_PATTERN_3, fragments) + } else { + return Ok(ReceiveResult::Unassociated); + } + } else { + // This can occur naturally because either Bob's incoming_sessions cache got + // full so Alice's incoming session was dropped, or the session this packet + // was for was dropped by the application. + return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); + } + } + } else { + let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(incoming_physical_packet); + app.event_log( + LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), + current_time, + ); + if packet_type != PACKET_TYPE_NOISE_XK_PATTERN_1 && packet_type != PACKET_TYPE_BOB_DOS_CHALLENGE { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + let fragments = if fragment_count > 1 { + self.0.unassociated_defrag_cache.lock().unwrap().assemble( + header_nonce, + remote_address, + incoming_physical_packet_len - HEADER_SIZE, + incoming_physical_packet_buf, + fragment_no, + fragment_count, + Application::RETRY_INTERVAL_MS, + current_time, + &mut assembled_packet, + ); + if !assembled_packet.is_empty() { + assembled_packet.as_ref() + } else { + return Ok(ReceiveResult::Unassociated); + } + } else { + std::array::from_ref(&incoming_physical_packet_buf) + }; + (None, packet_type, fragments) + } + }; + + debug_assert!(!fragments.is_empty()); + debug_assert!(incoming.is_none() || session.is_none()); + + let message = &mut [0u8; MAX_NOISE_HANDSHAKE_SIZE]; + let message_size = assemble_fragments_into::(fragments, message)?; + if message_size < MIN_PACKET_SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + + use OfferStateMachine::*; + match packet_type { + PACKET_TYPE_NOISE_XK_PATTERN_1 => { + // Alice (remote) --> Bob (local) + // -> e, es, e1 + app.event_log(LogEvent::ReceiveUncheckedXK1, current_time); + + if session.is_some() || incoming.is_some() { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + if !(NoiseXKPattern1::MIN_SIZE..=NoiseXKPattern1::MAX_SIZE).contains(&message_size) { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + // The message id must be the first 8 bytes of the gcm tag. + // This forces the message id to be authenticated along with the entire message. + let p_auth_end = message_size - ChallengeResponse::SIZE; + if message[8..16] != message[p_auth_end - 8..p_auth_end] { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + let p_size = p_auth_end - NoiseXKPattern1::P_ENC_START - AES_GCM_TAG_SIZE; + let total_ratchet_fingerprints = p_size / RATCHET_SIZE; + if p_size % RATCHET_SIZE != 0 || total_ratchet_fingerprints > 2 { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + + let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); + if let Some(remote_key_id) = NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.alice_key_id)) { + let sha512 = &mut Application::Hash::new(); + // Let application filter incoming connection attempts by whatever criteria it wants. + // This should ideally prevent ZSSP from wasting time on DDOS attacks. + match check_allow_incoming_session() { + IncomingSessionAction::Allow => {} + IncomingSessionAction::Challenge => { + let response: &ChallengeResponse = byte_array_as_proto_buffer(&message[p_auth_end..message_size]); + let counter = u64::from_be_bytes(response.challenge_counter.try_into().unwrap()); + + sha512.reset(); + let mut hasher = ShaHasher(sha512); + let mut output = [0u8; HASHLEN]; + hasher.0.update(&response.challenge_counter); + remote_address.hash(&mut hasher); + hasher.0.update(&self.0.challenge_salt); + hasher.0.finish(&mut output); + let is_valid = self.check_challenge_window(counter) + && secure_eq(&output[..CHALLENGE_MAC_SIZE], &response.challenge_mac) + && verify_pow::(hasher.0, &message[p_auth_end..message_size]) + && self.update_challenge_window(counter); + app.event_log(LogEvent::ReceiveCheckXK1Challenge(is_valid), current_time); + if !is_valid { + // Alice failed the challenge so issue them a new challenge. + let mut challenge_buffer = [0u8; BobDOSChallenge::SIZE]; + let challenge: &mut BobDOSChallenge = byte_array_as_proto_buffer_mut(&mut challenge_buffer); + challenge.alice_key_id = remote_key_id.get().to_ne_bytes(); + // We attach a monotonically increasing counter value to the challenge + // so it cannot be replayed. + let counter = self.0.challenge_counter.fetch_add(1, Ordering::Relaxed); + challenge.challenge_counter = counter.to_be_bytes(); + + hasher.0.reset(); + hasher.0.update(&counter.to_be_bytes()); + remote_address.hash(&mut hasher); + hasher.0.update(&self.0.challenge_salt); + hasher.0.finish(&mut output); + challenge.challenge_mac.copy_from_slice(&output[..CHALLENGE_MAC_SIZE]); + challenge.prior_challenge_pow = response.challenge_pow; + // We haven't decrypted any of Alice's packet so we don't know the + // header protection cipher. + // For DOS resistance Alice will not accept unencrypted headers directly + // into their session defrag buffer, so we have to send them this reply + // through their incoming sessions cache. + send_with_fragmentation( + &mut send_unassociated_reply, + send_unassociated_mtu, + &mut challenge_buffer, + PACKET_TYPE_BOB_DOS_CHALLENGE, + None, + self.0.rng.lock().unwrap().next_u64(), + None::<&Application::PrpEnc>, + ); + return Ok(ReceiveResult::Unassociated); + } + // Alice succeeded at the challenge so continue to decryption. + } + IncomingSessionAction::Drop => return Ok(ReceiveResult::Rejected), + } + + // Noise process handshake prologue. + let noise_h = mix_hash( + sha512, + &INITIAL_H, + &message[NoiseXKPattern1::PROLOGUE_START..NoiseXKPattern1::PROLOGUE_END], + ); + let noise_h = mix_hash(sha512, &noise_h, self.0.static_keypair.public_key_bytes()); + // Noise process pattern1 e token. + let mut noise_ck = SymmetricState::new(INITIAL_H); + let hmac = &mut Application::HmacHash::new(); + let mut noise_es = Secret::new(); + let noise_e_pattern1 = from_bytes_agreement::(&noise_pattern1.noise_e, &self.0.static_keypair, noise_es.as_mut()) + .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; + let noise_h_e = mix_hash(sha512, &noise_h, &noise_pattern1.noise_e); + noise_ck.mix_key(hmac, &noise_pattern1.noise_e); + // Noise process pattern1 es token. + let noise_k_es = noise_ck.mix_key_initialize_key(hmac, noise_es.as_ref()); + drop(noise_es); + // Noise process pattern1 e1 token. + let (is_auth, noise_h_ee1) = decrypt_and_hash::( + sha512, + &noise_k_es, + &noise_h_e, + packet_type, + 0, + &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], + ); + if !is_auth { + // This could occur naturally if Alice's ApplicationLayer is dynamically + // changing their mtu, which in bad network conditions could clobber their + // resent KEX packet. + // Or maybe Alice randomly generated the same temporary id twice in a row. + // Since these situations are super unlikely to occur we still mark this error + // as unnatural. + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + // Noise process pattern1 payload. + let (is_auth, noise_h_ee1p) = decrypt_and_hash::( + sha512, + &noise_k_es, + &noise_h_ee1, + packet_type, + 1, + &mut message[NoiseXKPattern1::P_ENC_START..p_auth_end], + ); + drop(noise_k_es); + if !is_auth { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); + // Get ratchet key. + let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); + let mut ratchet_state = RatchetState::Null; + for i in 0..total_ratchet_fingerprints { + match app.restore_by_fingerprint( + (&noise_pattern1.payload[i * RATCHET_SIZE..(i + 1) * RATCHET_SIZE]).try_into().unwrap(), + current_time, + ) { + Ok(RatchetState::Null) | Ok(RatchetState::Empty) => {} + Ok(rs) => { + ratchet_state = rs; + break; + } + Err(e) => return Err(ReceiveError::RatchetIoError(e)), + } + } + if ratchet_state.is_null() { + if app.hello_requires_recognized_ratchet(current_time) { + return Ok(ReceiveResult::Rejected); + } + ratchet_state = RatchetState::Empty; + } + + // Start of Noise XKhfs+psk2 pattern2. + let mut message2 = [0u8; NoiseXKPattern2::SIZE]; + let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); + // Noise process pattern2 e token. + let noise_e_pattern2_secret = Application::KeyPair::generate(&mut self.0.rng.lock().unwrap()); + noise_pattern2.noise_e = *noise_e_pattern2_secret.public_key_bytes(); + let noise_h_ee1pe = mix_hash(sha512, &noise_h_ee1p, &noise_pattern2.noise_e); + noise_ck.mix_key(hmac, &noise_pattern2.noise_e); + // Noise process pattern2 ee token. + let mut noise_ee = Secret::new(); + if !noise_e_pattern2_secret.agree(&noise_e_pattern1, noise_ee.as_mut()) { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + let noise_k_esee = noise_ck.mix_key_initialize_key(hmac, noise_ee.as_ref()); + drop(noise_ee); + // Noise process pattern2 ekem1 token. + let (noise_ekem1, noise_ekem1_secret) = pqc_kyber::encapsulate(&noise_pattern1.noise_e1, self.0.rng.lock().unwrap().deref_mut()) + .map_err(|_| byzantine_fault!(FaultType::FailedAuthentication, false)) + .map(|(ct, ekem1)| (ct, Secret(ekem1)))?; + // Alice fully authenticated. + noise_pattern2.noise_ekem1 = noise_ekem1; + let noise_h_ee1peekem1 = encrypt_and_hash::( + sha512, + &noise_k_esee, + &noise_h_ee1pe, + PACKET_TYPE_NOISE_XK_PATTERN_2, + 0, + &mut message2[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], + ); + drop(noise_k_esee); + noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); + drop(noise_ekem1_secret); + // Noise process pattern2 psk token. + let ratchet_key = ratchet_state.key().unwrap(); + let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); + let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); + // Noise process pattern2 payload. + // We try to prevent the id we generate from colliding with another session but + // because we might have handshakes in flight it's impossible to 100% prevent. + // In those exceedingly rare cases we have to drop Alice's session and start over. + let local_key_id = generate_key_id(&self.0.session_map.read().unwrap(), &mut self.0.rng.lock().unwrap()); + let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); + noise_pattern2.bob_key_id = local_key_id.get().to_ne_bytes(); + + let noise_h_ee1peekem1pskp = encrypt_and_hash::( + sha512, + &noise_k_eseeekem1psk, + &noise_h_ee1peekem1psk, + PACKET_TYPE_NOISE_XK_PATTERN_2, + 0, + &mut message2[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END], + ); + + app.event_log(LogEvent::ReceiveValidXK1, current_time); + let handshake = Arc::new(NoiseXKBobHandshakeState { + local_key_id, + remote_key_id, + ratchet_state, + noise_h_ee1peekem1pskp, + noise_ck_eseeekem1psk: noise_ck.clone(), + noise_k_eseeekem1psk: noise_k_eseeekem1psk.clone(), + noise_e_secret: noise_e_pattern2_secret, + header_receive_key: header_a2b_key.clone(), + header_send_key: header_b2a_key.clone(), + noise_pattern3_defrag: Mutex::new(Fragged::new()), + }); + self.0.unassociated_handshake_states.insert(local_key_id, handshake, current_time); + + // We put a copy of the gcm tag in the header so Alice can tell this packet apart + // from any other pattern 1 packet we send, without having to make Bob maintain state. + let mut pattern2_id = 0u64.to_ne_bytes(); + pattern2_id[5] = message2[NoiseXKPattern2::P_AUTH_END - 3]; + pattern2_id[6] = message2[NoiseXKPattern2::P_AUTH_END - 2]; + pattern2_id[7] = message2[NoiseXKPattern2::P_AUTH_END - 1]; + send_with_fragmentation( + &mut send_unassociated_reply, + send_unassociated_mtu, + &mut message2, + PACKET_TYPE_NOISE_XK_PATTERN_2, + Some(remote_key_id), + u64::from_be_bytes(pattern2_id), + Some(&Application::PrpEnc::new(header_b2a_key.first_n::())), + ); + + return Ok(ReceiveResult::Unassociated); + } else { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + } + PACKET_TYPE_BOB_DOS_CHALLENGE => { + let message = &mut message[..message_size]; + app.event_log(LogEvent::ReceiveUncheckedDOSChallenge, current_time); + + // We expect Bob to only send this to us through our unassociated defrag cache. + if incoming.is_some() || session.is_some() { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + if message.len() != BobDOSChallenge::SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + let challenge: &BobDOSChallenge = byte_array_as_proto_buffer(message); + + if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(challenge.alice_key_id)) { + if let Some(session) = self.0.session_map.read().unwrap().get(&local_key_id).and_then(|s| s.0.upgrade()) { + // We don't need to hold the kex lock because we are not transitioning state. + let mut state = session.state.write().unwrap(); + if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { + if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, .. } = &mut handshake_state.offer { + let response_raw = &mut noise_message[*noise_message_len - ChallengeResponse::SIZE..]; + + let response: &mut ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); + // Only people who know what Alice's prior pow was can convince us to + // compute a new pow. + if challenge.prior_challenge_pow != response.challenge_pow { + // This can occur if Bob sends us multiple challenges and they + // arrive OOO. + return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); + } + response.challenge_counter.copy_from_slice(&challenge.challenge_counter); + response.challenge_mac.copy_from_slice(&challenge.challenge_mac); + let mut pow = self.0.rng.lock().unwrap().next_u64(); + let sha512 = &mut Application::Hash::new(); + loop { + let response: &mut ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); + response.challenge_pow.copy_from_slice(&pow.to_be_bytes()); + if verify_pow::(sha512, response_raw) { + break; + } + pow = pow.wrapping_add(1); + } + + app.event_log(LogEvent::ReceiveValidDOSChallenge(&session), current_time); + return Ok(ReceiveResult::Unassociated); + } else { + // This could happen if Bob challenges Alice, but their challenge packet + // gets massively delayed. + return Err(byzantine_fault!(FaultType::OutOfSequence, true)); + } + } else { + // This could happen if Bob challenges Alice, but their challenge packet + // gets massively delayed. + return Err(byzantine_fault!(FaultType::OutOfSequence, true)); + } + } else { + // This can occur naturally if Alice's session was dropped. + return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); + } + } else { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + } + PACKET_TYPE_NOISE_XK_PATTERN_2 => { + // Bob (remote) --> Alice (local) + // <- e, ee, ekem1, psk + let message = &mut message[..message_size]; + app.event_log(LogEvent::ReceiveUncheckedXK2, current_time); + + if incoming.is_some() { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + if message.len() != NoiseXKPattern2::SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + + let session = session.ok_or(byzantine_fault!(FaultType::UnknownLocalKeyId, false))?; + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + + if let NoiseXKPattern1or3(handshake_state) = &state.outgoing_offer { + if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { + noise_h_ee1p, noise_e_secret, noise_e1_secret, noise_ck_es, .. + } = &handshake_state.offer + { + let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); + // Authenticate header counter. + if noise_pattern2.header[13..16] != noise_pattern2.p_gcm_tag[13..16] { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + + // Noise process pattern2 e token. + let mut noise_ee = Secret::new(); + if let Some(noise_e_pattern2) = + from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) + { + let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); + let mut noise_ck = noise_ck_es.clone(); + let noise_h_ee1pe = mix_hash(sha512, noise_h_ee1p, noise_e_pattern2.as_bytes()); + noise_ck.mix_key(hmac, noise_e_pattern2.as_bytes()); + // Noise process pattern2 ee token. + let noise_k_esee = noise_ck.mix_key_initialize_key(hmac, noise_ee.as_ref()); + drop(noise_ee); + // Noise process pattern2 ekem1 token. + let (is_auth, noise_h_ee1peekem1) = decrypt_and_hash::( + sha512, + &noise_k_esee, + &noise_h_ee1pe, + packet_type, + 0, + &mut message[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], + ); + let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); + let noise_ekem1_secret = pqc_kyber::decapsulate(&noise_pattern2.noise_ekem1, noise_e1_secret.as_ref()).map(Secret); + if let Some(Ok(noise_ekem1_secret)) = is_auth.then_some(noise_ekem1_secret) { + noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); + drop(noise_ekem1_secret); + + // We attempt to decrypt the payload at most three times. First two times with + // the ratchet key Alice remembers, and final time with a ratchet + // key of zero if Alice allows ratchet downgrades. + // The following code is not constant time, meaning we leak to an + // attacker whether or not we downgraded. + // We don't currently consider this sensitive enough information to hide. + let mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState, Secret, [u8; 64])> { + // Check for which ratchet key Bob wants to use. + let mut noise_ck = noise_ck.clone(); + let mut payload = [0u8; NoiseXKPattern2::P_AUTH_END - NoiseXKPattern2::P_ENC_START]; + payload.copy_from_slice(&message[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END]); + // Noise process pattern2 psk token. + let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); + let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); + // Noise process pattern2 payload. + let (is_auth, noise_h_ee1peekem1pskp) = decrypt_and_hash::( + sha512, + &noise_k_eseeekem1psk, + &noise_h_ee1peekem1psk, + packet_type, + 0, + &mut payload, + ); + if is_auth { + let key_id = NonZeroU32::new(u32::from_ne_bytes( + (&payload[..NoiseXKPattern2::P_AUTH_START - NoiseXKPattern2::P_ENC_START]) + .try_into() + .unwrap(), + )); + key_id.map(|kid| (kid, noise_ck, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) + } else { + None + } + }; + // Check first key. + let mut ratchet_i = 0; + let mut result = None; + let mut chain_len = 0; + if let Some(key) = state.ratchet_states[0].key() { + chain_len = state.ratchet_states[0].chain_len(); + result = test_ratchet_key(key); + } + // Check second key. + if result.is_none() { + ratchet_i = 1; + if let Some(key) = state.ratchet_states[1].key() { + chain_len = state.ratchet_states[1].chain_len(); + result = test_ratchet_key(key); + } + } + // Check zero key. + if result.is_none() && !app.initiator_disallows_downgrade(&session, current_time) { + chain_len = 0; + result = test_ratchet_key(&[0u8; RATCHET_SIZE]); + if result.is_some() { + // TODO: add some kind of warning callback or signal. + } + } + + if let Some((remote_key_id, mut noise_ck, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) = result { + // Start of Noise XKhfs+psk2 pattern3. + let mut message3 = [0u8; NoiseXKPattern3::MAX_SIZE]; + // Noise process pattern3 s token. + let mut noise_se = Secret::new(); + if self.0.static_keypair.agree(&noise_e_pattern2, noise_se.as_mut()) { + let payload = handshake_state.alice_identity_blob.as_ref(); + // Packet fully authenticated. + let s_enc_start = HEADER_SIZE; + let s_auth_start = s_enc_start + P384_PUBLIC_KEY_SIZE; + let p_enc_start = s_auth_start + AES_GCM_TAG_SIZE; + let p_auth_start = p_enc_start + payload.len(); + let p_auth_end = p_auth_start + AES_GCM_TAG_SIZE; + let message3_len = p_auth_end; + + message3[s_enc_start..s_auth_start].copy_from_slice(self.0.static_keypair.public_key_bytes()); + let noise_h_ee1peekem1pskps = encrypt_and_hash::( + sha512, + &noise_k_eseeekem1psk, + &noise_h_ee1peekem1pskp, + PACKET_TYPE_NOISE_XK_PATTERN_3, + 1, + &mut message3[s_enc_start..p_enc_start], + ); + drop(noise_k_eseeekem1psk); + // Noise process pattern3 se token. + let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); + drop(noise_se); + // Noise process pattern3 payload token. + message3[p_enc_start..p_auth_start].copy_from_slice(payload); + let noise_h_ee1peekem1pskpsp = encrypt_and_hash::( + sha512, + &noise_k_eseeekem1pskse, + &noise_h_ee1peekem1pskps, + PACKET_TYPE_NOISE_XK_PATTERN_3, + 0, + &mut message3[p_enc_start..p_auth_end], + ); + drop(noise_k_eseeekem1pskse); + // Alice finished Noise XKhfs+psk2 handshake. + // Transition offer state machine to the NoiseXKPattern3 state. + let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); + let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(chain_len + 1).unwrap()); + + let ratchet_to_preserve = &state.ratchet_states[ratchet_i]; + let result = app.save_ratchet_state( + &session.remote_static_key, + &session.application_data, + [&state.ratchet_states[0], &state.ratchet_states[1]], + [&new_ratchet_state, ratchet_to_preserve], + current_time, + ); + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); + } + + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); + + let local_key_id = handshake_state.local_key_id; + drop(state); + let mut state = session.state.write().unwrap(); + session + .kex_send_cipher + .lock() + .unwrap() + .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); + session + .kex_receive_cipher + .lock() + .unwrap() + .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); + state.ratchet_states[1] = state.ratchet_states[ratchet_i].clone(); + state.ratchet_states[0] = new_ratchet_state; + + state.cipher_states[0].replace(SessionKey::new( + hmac, + noise_ck, + local_key_id, + remote_key_id, + INIT_COUNTER, + false, + )); + debug_assert!(state.cipher_states[1].is_none()); + if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { + handshake_state.next_retry_time = + AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); + handshake_state.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); + handshake_state.offer = NoiseXKAliceHandshakeState::NoiseXKPattern3 { + noise_message: message3, + noise_message_len: p_auth_end, + }; + } + drop(state); + drop(kex_lock); + + if let Some((mut send, mut mtu)) = send_to(&session) { + mtu = mtu.max(MIN_TRANSPORT_MTU); + send_with_fragmentation( + &mut send, + mtu, + &mut message3[..message3_len], + PACKET_TYPE_NOISE_XK_PATTERN_3, + Some(remote_key_id), + 0, + Some(&session.header_send_cipher), + ); + } + app.event_log(LogEvent::ReceiveValidXK2(&session), current_time); + return Ok(ReceiveResult::Session(session, SessionEvent::Control)); + } + } + } + } + // Bob failed authentication so we must restart our offer according to Noise. + // We restart the offer instead of dropping the session to defend against DOS. + drop(state); + let mut state = session.state.write().unwrap(); + let ratchet_state = state.ratchet_states.clone(); + if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { + if !handshake_state.reinitialize( + &session, + &ratchet_state, + &mut self.0.session_map.write().unwrap(), + &mut self.0.rng.lock().unwrap(), + current_time, + ) { + session.expire() + } + } + drop(state); + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } else { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + } else { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + } + PACKET_TYPE_NOISE_XK_PATTERN_3 => { + // Alice (remote) --> Bob (local) + // -> s, se + let message = &mut message[..message_size]; + app.event_log(LogEvent::ReceiveUncheckedXK3, current_time); + + if session.is_some() { + return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + } + if message.len() < NoiseXKPattern3::MIN_SIZE || message.len() > NoiseXKPattern3::MAX_SIZE { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + // The code above guarantees to us that each `incoming` handshake state that reaches + // this point will be strictly unique, even for the same remote peer. + // This property is strictly necessary to prevent catastrophic nonce reuse due to + // two session being created with the same set of keys. + let handshake_state = incoming.ok_or(byzantine_fault!(FaultType::UnknownLocalKeyId, false))?; + let s_enc_start = HEADER_SIZE; + + let s_auth_start = s_enc_start + P384_PUBLIC_KEY_SIZE; + let p_enc_start = s_auth_start + AES_GCM_TAG_SIZE; + let p_auth_end = message.len(); + let p_auth_start = p_auth_end - AES_GCM_TAG_SIZE; + + if !(p_enc_start <= p_auth_start) { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + // Do not read from the message before this point, otherwise an array out of bounds + // error is possible. + // Noise process pattern3 s token. + let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); + let (is_auth, noise_h_ee1peekem1pskps) = decrypt_and_hash::( + sha512, + &handshake_state.noise_k_eseeekem1psk, + &handshake_state.noise_h_ee1peekem1pskp, + packet_type, + 1, + &mut message[s_enc_start..p_enc_start], + ); + if !is_auth { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + // Noise process pattern3 se token. + let mut noise_se = Secret::new(); + if let Some(remote_s_public_key) = + from_bytes_agreement::(&message[s_enc_start..s_auth_start], &handshake_state.noise_e_secret, noise_se.as_mut()) + { + let mut noise_ck = handshake_state.noise_ck_eseeekem1psk.clone(); + let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); + drop(noise_se); + // Noise process pattern3 payload. + let (is_auth, noise_h_ee1peekem1pskpsp) = decrypt_and_hash::( + sha512, + &noise_k_eseeekem1pskse, + &noise_h_ee1peekem1pskps, + packet_type, + 0, + &mut message[p_enc_start..p_auth_end], + ); + drop(noise_k_eseeekem1pskse); + if !is_auth { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + // Bob finished Noise XKhfs+psk2 handshake. + let header_send_cipher = Application::PrpEnc::new(handshake_state.header_send_key.as_ref()); + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); + let mut send_reject = || { + // We just used a counter with this key, but we are not storing + // the fact we used it in memory. This is currently ok because the + // handshake is being dropped, so nonce reuse can't happen. + let (mut fragment, len) = encrypt_control( + &mut Application::AeadEnc::new(kex_key_b2a.as_ref()), + &header_send_cipher, + PACKET_TYPE_SESSION_REJECTED, + INIT_COUNTER, + handshake_state.remote_key_id.get(), + &[], + ); + send_unassociated_reply(&mut fragment[..len]); + }; + + let (responder_disallows_downgrade, responder_silently_rejects) = check_accept_session( + &remote_s_public_key, + &message[p_enc_start..p_auth_start], + handshake_state.ratchet_state.chain_len(), + ); + if let Some((responder_disallows_downgrade, application_data)) = responder_disallows_downgrade { + let result = app.restore_by_identity(&remote_s_public_key, &application_data, current_time); + match result { + Ok(true_ratchet_states) => { + let mut has_match = false; + for rs in &true_ratchet_states { + if !rs.is_null() { + has_match |= &handshake_state.ratchet_state == rs; + } + } + if !has_match { + if !responder_disallows_downgrade && handshake_state.ratchet_state.is_empty() { + // TODO: add some kind of warning callback or signal. + } else { + if !responder_silently_rejects { + send_reject(); + } + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + } + + let mut noise_kk_ss = Secret::new(); + if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); + let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); + let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); + + let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); + // We must make sure the ratchet key is saved before we transition. + let new_ratchet_state = + RatchetState::new_nonempty(rk, rf, NonZeroU64::new(handshake_state.ratchet_state.chain_len() + 1).unwrap()); + let result = app.save_ratchet_state( + &remote_s_public_key, + &application_data, + [&true_ratchet_states[0], &true_ratchet_states[1]], + [&new_ratchet_state, &RatchetState::Null], + current_time, + ); + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); + } + + let mut session_queue = self.0.session_queue.lock().unwrap(); + let queue_idx = session_queue.reserve_index(); + let session = Arc::new(Session { + context: Arc::downgrade(&self.0), + queue_idx, + application_data, + remote_static_key: remote_s_public_key, + send_counter: AtomicU64::new(INIT_COUNTER), + session_has_expired: AtomicBool::new(false), + counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), + state_machine_lock: Mutex::new(()), + state: RwLock::new(SessionMutableState { + ratchet_states: [new_ratchet_state.clone(), RatchetState::Null], + cipher_states: [ + Some(SessionKey::new( + hmac, + noise_ck, + handshake_state.local_key_id, + handshake_state.remote_key_id, + INIT_COUNTER, + true, + )), + None, + ], + current_key: 0, + outgoing_offer: KeyConfirm { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), + }, + }), + header_receive_cipher: Application::PrpDec::new(handshake_state.header_receive_key.as_ref()), + header_send_cipher, + kex_send_cipher: Mutex::new(Some(Application::AeadEnc::new(kex_key_b2a.as_ref()))), + kex_receive_cipher: Mutex::new(Some(Application::AeadDec::new(kex_key_a2b.as_ref()))), + noise_kk_ss, + noise_kk_local_init_h, + noise_kk_remote_init_h, + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + was_bob: true, + }); + let timer = Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); + session_queue.push_reserved(queue_idx, Arc::downgrade(&session), timer); + drop(session_queue); + // There is the miniscule possibility this key id is already + // in use, in which case we have to drop this session like + // nothing ever happened. + let mut session_map = self.0.session_map.write().unwrap(); + if let std::collections::hash_map::Entry::Vacant(e) = session_map.entry(handshake_state.local_key_id) { + e.insert((Arc::downgrade(&session), false)); + drop(session_map); + let _ = + session.send_control(&session.state.read().unwrap(), send_unassociated_reply, PACKET_TYPE_KEY_CONFIRM, &[]); + + app.event_log(LogEvent::ReceiveValidXK3(&session.application_data), current_time); + return Ok(ReceiveResult::Session(session, SessionEvent::NewSession)); + } else { + // This can occur if we accidentally generate a key id collision. + // There is an extremely short amount of time during which + // another session can steal this session's id, we'll have to + // restart the handshake in this case. + return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); + } + } + Err(e) => { + return Err(ReceiveError::RatchetIoError(e)); + } + } + } else { + if !responder_silently_rejects { + send_reject(); + } + return Ok(ReceiveResult::Rejected); + } + } else { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + } + _ => return Err(byzantine_fault!(FaultType::InvalidPacket, false)), + } + } + /// Helper function for sending the empty string over the session. Useful for keep-alives. + /// + /// * `session` - The session to send to + /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a + /// slice of `data` + /// * `current_time` - Current time in milliseconds + pub fn send_empty(&self, session: &Arc>, send: impl FnMut(&mut [u8]) -> bool, current_time: i64) -> Result<(), SendError> { + self.send(session, send, &mut [0u8; MIN_TRANSPORT_MTU], &[], current_time) + } + /// Send data over the session. + /// + /// * `session` - The session to send to + /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a + /// slice of `data` + /// * `mtu_sized_buffer` - A writable work buffer whose size equals the MTU + /// * `data` - Data to send + /// * `current_time` - Current time in milliseconds + pub fn send( + &self, + session: &Arc>, + mut send: impl FnMut(&mut [u8]) -> bool, + mtu_sized_buffer: &mut [u8], + mut data: &[u8], + current_time: i64, + ) -> Result<(), SendError> { + if mtu_sized_buffer.len() < MIN_TRANSPORT_MTU { + return Err(SendError::InvalidParameter); + } + let state = session.state.read().unwrap(); + let key = state.cipher_states[state.current_key].as_ref().ok_or(SendError::SessionNotEstablished)?; + let counter = session.get_next_outgoing_counter()?; + + let mut c = key.get_send_cipher(counter)?; + c.set_iv(&create_message_nonce(PACKET_TYPE_DATA, counter)); + + let fragment_max_chunk_size = mtu_sized_buffer.len() - HEADER_SIZE; + let fragment_count = (data.len() + AES_GCM_TAG_SIZE + (fragment_max_chunk_size - 1)) / fragment_max_chunk_size; + if fragment_count > MAX_FRAGMENTS { + return Err(SendError::DataTooLarge); + } + let last_fragment_no = fragment_count - 1; + + for fragment_no in 0..fragment_count { + let chunk_size = fragment_max_chunk_size.min(data.len()); + let mut fragment_size = chunk_size + HEADER_SIZE; + + set_packet_header( + mtu_sized_buffer, + fragment_count as u8, + fragment_no as u8, + PACKET_TYPE_DATA, + key.remote_key_id.get(), + counter, + ); + + c.encrypt(&data[..chunk_size], &mut mtu_sized_buffer[HEADER_SIZE..fragment_size]); + data = &data[chunk_size..]; + + if fragment_no == last_fragment_no { + debug_assert!(data.is_empty()); + let tagged_fragment_size = fragment_size + AES_GCM_TAG_SIZE; + c.finish_encrypt((&mut mtu_sized_buffer[fragment_size..tagged_fragment_size]).try_into().unwrap()); + fragment_size = tagged_fragment_size; + } + + session.header_send_cipher.encrypt_in_place( + (&mut mtu_sized_buffer[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) + .try_into() + .unwrap(), + ); + if !send(&mut mtu_sized_buffer[..fragment_size]) { + break; + } + } + drop(c); + if counter >= key.rekey_at_counter { + if let OfferStateMachine::Normal { .. } = &state.outgoing_offer { + drop(state); + if let Ok(timer) = initiate_rekey(&self.0, session, send, current_time) { + self.0.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timer)); + } + } + } + Ok(()) + } + /// Update the challenge window, returning true if the challenge is still valid. + fn check_challenge_window(&self, counter: u64) -> bool { + let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.load(Ordering::Relaxed); + prev_counter < counter + } + /// Update the challenge window, returning true if the challenge is still valid. + fn update_challenge_window(&self, counter: u64) -> bool { + let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); + prev_counter < counter + } +} +/// Initiate the rekeying protocol. This session will now begin attempting to rekey this session +/// with its peer, if it was not already. +fn initiate_rekey( + context: &Arc>, + session: &Arc>, + send: impl FnOnce(&mut [u8]) -> bool, + current_time: i64, +) -> Result { + let mut message = [0u8; NoiseKKPattern1or2::SIZE]; + + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + // We may only attempt to rekey if we are not already doing so. + match &state.outgoing_offer { + OfferStateMachine::Normal { .. } => (), + _ => return Err(()), + } + let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); + // Start of Noise KKpsk0 pattern1. + // Noise process pattern1 psk0 token. + let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); + let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_states[0].key().unwrap()); + let noise_h_psk = mix_hash(sha512, &session.noise_kk_local_init_h, &noise_temp_h); + // Noise process pattern1 e token. + let noise_e_secret = Application::KeyPair::generate(&mut context.rng.lock().unwrap()); + let noise_h_pske = mix_hash(sha512, &noise_h_psk, noise_e_secret.public_key_bytes()); + noise_ck.mix_key(hmac, noise_e_secret.public_key_bytes()); + + let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); + noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); + // Noise process pattern1 es token. + let mut noise_es = Secret::new(); + if !noise_e_secret.agree(&session.remote_static_key, noise_es.as_mut()) { + return Err(()); + } + noise_ck.mix_key(hmac, noise_es.as_ref()); + drop(noise_es); + // Noise process pattern1 ss token. + let noise_k_pskesss = noise_ck.mix_key_initialize_key(hmac, session.noise_kk_ss.as_ref()); + // Noise process pattern1 payload token. + let mut session_map = context.session_map.write().unwrap(); + let new_key_id = generate_key_id(&session_map, &mut context.rng.lock().unwrap()); + let next_key_index = state.current_key ^ 1; + session_map.insert(new_key_id, (Arc::downgrade(session), next_key_index > 0)); + drop(session_map); + + let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); + noise_pattern1.key_id = new_key_id.get().to_ne_bytes(); + let noise_h_pskep = encrypt_and_hash::( + sha512, + &noise_k_pskesss, + &noise_h_pske, + PACKET_TYPE_NOISE_KK_PATTERN_1, + 0, + &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], + ); + drop(noise_k_pskesss); + + drop(state); + let mut state = session.state.write().unwrap(); + state.outgoing_offer = OfferStateMachine::NoiseKKPattern1 { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), + new_key_id, + noise_e_secret, + noise_message: message.clone(), + noise_h_pskep, + noise_ck: noise_ck.clone(), + }; + drop(state); + drop(kex_lock); + let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_NOISE_KK_PATTERN_1, &message); + Ok(current_time.saturating_add(Application::RETRY_INTERVAL_MS)) +} +fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mut [u8]) -> bool>( + context: &Context, + session: Arc>, + app: &Application, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + packet_type: u8, + counter: u64, + fragment: &mut [u8], + current_time: i64, +) -> Result, ReceiveError> { + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + let mut c = session.kex_receive_cipher.lock().unwrap(); + let message = decrypt_control( + c.as_mut().ok_or(byzantine_fault!(FaultType::OutOfSequence, false))?, + packet_type, + counter, + fragment, + )?; + drop(c); + session.update_receive_window(counter); + use OfferStateMachine::*; + return match packet_type { + PACKET_TYPE_SESSION_REJECTED => { + if let NoiseXKPattern1or3(_) = &state.outgoing_offer { + drop(state); + let mut state = session.state.write().unwrap(); + state.outgoing_offer = OfferStateMachine::Null; + drop(state); + drop(kex_lock); + Ok(ReceiveResult::Session(session, SessionEvent::Rejected)) + } else { + // This can occur naturally because of control packet resends. + Err(byzantine_fault!(FaultType::OutOfSequence, true)) + } + } + PACKET_TYPE_KEY_CONFIRM => { + drop(state); + app.event_log(LogEvent::ReceiveValidKeyConfirm(&session), current_time); + let mut state = session.state.write().unwrap(); + // We only want to stop sending NoiseKKPattern2 offers when the latest derived + // key is confirmed. And we only want to do that once. + let (used_latest_key, try_delete, ret) = match &state.outgoing_offer { + NoiseKKPattern2 { .. } => (true, true, SessionEvent::Control), + NoiseXKPattern1or3(handshake_state) => { + if let NoiseXKAliceHandshakeState::NoiseXKPattern3 { .. } = &handshake_state.offer { + (true, true, SessionEvent::Established) + } else { + (false, false, SessionEvent::Control) + } + } + Null => (false, false, SessionEvent::Control), + _ => (true, false, SessionEvent::Control), + }; + if try_delete { + let result = if !state.ratchet_states[1].is_null() { + app.save_ratchet_state( + &session.remote_static_key, + &session.application_data, + [&state.ratchet_states[0], &state.ratchet_states[1]], + [&state.ratchet_states[0], &RatchetState::Null], + current_time, + ) + } else { + Ok(()) + }; + if let Err(e) = result { + return Err(ReceiveError::RatchetIoError(e)); + } + if let NoiseKKPattern2 { kex_send_key, .. } = &state.outgoing_offer { + session + .kex_send_cipher + .lock() + .unwrap() + .replace(Application::AeadEnc::new(kex_send_key.as_ref())); + } + state.ratchet_states[1] = RatchetState::Null; + state.current_key ^= 1; + state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); + } + drop(state); + drop(kex_lock); + if used_latest_key { + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_ACK, &[]); + } + } + Ok(ReceiveResult::Session(session, ret)) + } + PACKET_TYPE_ACK => { + if let KeyConfirm { .. } = &state.outgoing_offer { + drop(state); + app.event_log(LogEvent::ReceiveValidAck(&session), current_time); + let mut state = session.state.write().unwrap(); + // Check if we should end any current offers and transition back to Normal state + state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); + drop(kex_lock); + drop(state); + Ok(ReceiveResult::Session(session, SessionEvent::Control)) + } else { + // This can occur naturally because of control packet resends. + Err(byzantine_fault!(FaultType::OutOfSequence, true)) + } + } + PACKET_TYPE_NOISE_KK_PATTERN_1 => { + app.event_log(LogEvent::ReceiveUncheckedKK1, current_time); + let message = &mut message[..NoiseKKPattern1or2::SIZE]; + let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); + // We need the following operation to be atomic with the change of offer type + let (should_rekey_as_bob, chosen_id) = match &state.outgoing_offer { + // Check rekey rate limits. + Normal { .. } => (true, None), + // In the following situation, both parties are in state NoiseKKPattern1, + // we need to deterministically allow only one of them to transition to + // NoiseKKPattern2. + NoiseKKPattern1 { new_key_id, .. } => (session.was_bob, Some(*new_key_id)), + _ => (false, None), + }; + if !should_rekey_as_bob { + // This can be triggered if both parties attempt rekeying simultaneously, or if the + // remote party sent us a duplicate rekey request. + // The code above handles this case and only lets one party through to rekeying. + drop(state); + drop(kex_lock); + return Ok(ReceiveResult::Session(session, SessionEvent::Control)); + } + // Noise process pattern1 psk0 token. + let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); + let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); + let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_states[0].key().unwrap()); + let noise_h_psk = mix_hash(sha512, &session.noise_kk_remote_init_h, &noise_temp_h); + // Noise process pattern1 e token. + // Get public key validation out of the way early + let mut noise_es = Secret::new(); + let mut noise_ee = Secret::new(); + let mut noise_se = Secret::new(); + if let Some(alice_e) = from_bytes_agreement::(&noise_pattern1.noise_e, &context.0.static_keypair, noise_es.as_mut()) { + let bob_e_secret = Application::KeyPair::generate(&mut context.0.rng.lock().unwrap()); + if bob_e_secret.agree(&alice_e, noise_ee.as_mut()) && bob_e_secret.agree(&session.remote_static_key, noise_se.as_mut()) { + let noise_h_pske = mix_hash(sha512, &noise_h_psk, alice_e.as_bytes()); + noise_ck.mix_key(hmac, alice_e.as_bytes()); + // Noise process pattern1 es token. + noise_ck.mix_key(hmac, noise_es.as_ref()); + drop(noise_es); + // Noise process pattern1 ss token. + let noise_k_pskesss = noise_ck.mix_key_initialize_key(hmac, session.noise_kk_ss.as_ref()); + + // Noise process pattern1 payload. + let (is_auth, noise_h_pskep) = decrypt_and_hash::( + sha512, + &noise_k_pskesss, + &noise_h_pske, + packet_type, + 0, + &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], + ); + let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); + if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.key_id))) { + // Alice fully authenticated. + // Start of Noise KKpsk0 pattern2. + // Noise process pattern2 e token. + let noise_h_pskepe = mix_hash(sha512, &noise_h_pskep, bob_e_secret.public_key_bytes()); + noise_ck.mix_key(hmac, bob_e_secret.public_key_bytes()); + // Noise process pattern2 ee token. + noise_ck.mix_key(hmac, noise_ee.as_ref()); + drop(noise_ee); + // Noise process pattern2 se token. + let noise_k_pskessseese = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); + drop(noise_se); + // Noise process pattern2 payload. + let mut message2 = [0u8; NoiseKKPattern1or2::SIZE]; + let noise_pattern2: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message2); + noise_pattern2.noise_e = *bob_e_secret.public_key_bytes(); + let mut session_map = context.0.session_map.write().unwrap(); + // If we already generated a new key id mapping reuse it. + let new_key_id = chosen_id.unwrap_or_else(|| generate_key_id(&session_map, &mut context.0.rng.lock().unwrap())); + noise_pattern2.key_id = new_key_id.get().to_ne_bytes(); + + let noise_h_pskepep = encrypt_and_hash::( + sha512, + &noise_k_pskessseese, + &noise_h_pskepe, + PACKET_TYPE_NOISE_KK_PATTERN_2, + 0, + &mut message2[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], + ); + drop(noise_k_pskessseese); + // Bob finished Noise KKpsk0 handshake. + let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); + let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); + let result = app.save_ratchet_state( + &session.remote_static_key, + &session.application_data, + [&state.ratchet_states[0], &state.ratchet_states[1]], + [&new_ratchet_state, &state.ratchet_states[0]], + current_time, + ); + if let Err(e) = result { + drop(state); + drop(kex_lock); + return Err(ReceiveError::RatchetIoError(e)); + } + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); + // The new "Bob" doesn't know yet if Alice has received the new key, so the + // new key is recorded as the "alt" (key_index ^ 1) but the current key is + // not advanced yet. + let next_key_index = state.current_key ^ 1; + session_map.insert(new_key_id, (Arc::downgrade(&session), next_key_index > 0)); + if let Some(pre_id) = state.cipher_states[next_key_index].as_ref().map(|k| k.local_key_id) { + session_map.remove(&pre_id); + } + drop(session_map); + drop(state); + let mut state = session.state.write().unwrap(); + let current_counter = session.send_counter.load(Ordering::Relaxed); + session + .kex_receive_cipher + .lock() + .unwrap() + .replace(Application::AeadDec::new(kex_key_a2b.as_ref())); + state.ratchet_states[1] = state.ratchet_states[0].clone(); + state.ratchet_states[0] = new_ratchet_state.clone(); + + state.cipher_states[next_key_index].replace(SessionKey::new( + hmac, + noise_ck, + new_key_id, + remote_key_id, + current_counter, + true, + )); + let timer = current_time.saturating_add(Application::RETRY_INTERVAL_MS); + state.outgoing_offer = NoiseKKPattern2 { + next_retry_time: AtomicI64::new(timer), + timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), + noise_message: message2, + kex_send_key: kex_key_b2a.clone(), + }; + drop(state); + drop(kex_lock); + context.0.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timer)); + + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_NOISE_KK_PATTERN_2, &message2); + } + app.event_log(LogEvent::ReceiveValidKK1(&session), current_time); + return Ok(ReceiveResult::Session(session, SessionEvent::Control)); + } + } + } + Err(byzantine_fault!(FaultType::FailedAuthentication, false)) + } + PACKET_TYPE_NOISE_KK_PATTERN_2 => { + app.event_log(LogEvent::ReceiveUncheckedKK2, current_time); + let message = &mut message[..NoiseKKPattern1or2::SIZE]; + let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); + + if let NoiseKKPattern1 { new_key_id, noise_e_secret, noise_ck, noise_h_pskep, .. } = &state.outgoing_offer { + // Noise process pattern2 e token. + let mut noise_ee = Secret::new(); + let mut noise_se = Secret::new(); + if let Some(bob_e) = from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) { + if context.0.static_keypair.agree(&bob_e, noise_se.as_mut()) { + let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); + let mut noise_ck = noise_ck.clone(); + let noise_h_pskepe = mix_hash(sha512, noise_h_pskep, bob_e.as_bytes()); + noise_ck.mix_key(hmac, bob_e.as_bytes()); + // Noise process pattern2 ee token. + noise_ck.mix_key(hmac, noise_ee.as_ref()); + drop(noise_ee); + // Noise process pattern2 se token. + let noise_k_pskessseese = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); + drop(noise_se); + // Noise process pattern2 payload. + let (is_auth, noise_h_pskepep) = decrypt_and_hash::( + sha512, + &noise_k_pskessseese, + &noise_h_pskepe, + packet_type, + 0, + &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], + ); + let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); + if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern2.key_id))) { + // Bob fully authenticated. + // Alice finished Noise KKpsk0 handshake. + let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); + let new_ratchet_state = + RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); + let result = app.save_ratchet_state( + &session.remote_static_key, + &session.application_data, + [&state.ratchet_states[0], &state.ratchet_states[1]], + [&new_ratchet_state, &RatchetState::Null], + current_time, + ); + if let Err(e) = result { + drop(state); + drop(kex_lock); + return Err(ReceiveError::RatchetIoError(e)); + } + let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); + + let new_key_id = *new_key_id; + drop(state); + let mut state = session.state.write().unwrap(); + let next_key_index = state.current_key ^ 1; + state.current_key = next_key_index; + if let Some(key) = state.cipher_states[next_key_index].as_ref() { + context.0.session_map.write().unwrap().remove(&key.local_key_id); + } + session + .kex_receive_cipher + .lock() + .unwrap() + .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); + session + .kex_send_cipher + .lock() + .unwrap() + .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); + state.ratchet_states[1] = RatchetState::Null; + state.ratchet_states[0] = new_ratchet_state.clone(); + + state.cipher_states[next_key_index].replace(SessionKey::new( + hmac, + noise_ck, + new_key_id, + remote_key_id, + session.send_counter.load(Ordering::Relaxed), + false, + )); + state.outgoing_offer = KeyConfirm { + next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), + timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), + }; + drop(state); + drop(kex_lock); + // Let Bob know we got the key. + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_KEY_CONFIRM, &[]); + } + app.event_log(LogEvent::ReceiveValidKK2(&session), current_time); + return Ok(ReceiveResult::Session(session, SessionEvent::Control)); + } + } + } + // Bob failed authentication so according to Noise we must terminate this + // handshake. + // This should not happen in practice since this packet will have already passed + // authentication under the current key. + session.expire(); + Err(byzantine_fault!(FaultType::FailedAuthentication, false)) + } else { + drop(state); + drop(kex_lock); + Ok(ReceiveResult::Session(session, SessionEvent::Control)) + } + } + _ => Err(byzantine_fault!(FaultType::InvalidPacket, false)), + }; +} + +impl Session { + /// This can only fail with `MaxKeyLifetimeExceeded` or `SessionNotEstablished`. + fn send_control( + &self, + state: &SessionMutableState, + send: impl FnOnce(&mut [u8]) -> bool, + packet_type: u8, + packet: &[u8], + ) -> Result<(), SendError> { + let key = state.cipher_states[state.current_key].as_ref().ok_or(SendError::SessionNotEstablished)?; + let counter = self.get_next_outgoing_counter()?; + let mut c = self.kex_send_cipher.lock().unwrap(); + let (mut fragment, len) = encrypt_control( + c.as_mut().ok_or(SendError::SessionNotEstablished)?, + &self.header_send_cipher, + packet_type, + counter, + key.remote_key_id.get(), + packet, + ); + send(&mut fragment[..len]); + Ok(()) + } + /// Check whether this session is established. + pub fn established(&self) -> bool { + let state = self.state.read().unwrap(); + !matches!(&state.outgoing_offer, OfferStateMachine::NoiseXKPattern1or3(_) | OfferStateMachine::Null) + } + /// The static public key of the remote peer. + pub fn remote_s_public_key(&self) -> &Application::PublicKey { + &self.remote_static_key + } + /// The current ratchet state of this session. + /// The returned values are sensitive and should be securely erased before being dropped. + pub fn ratchet_states(&self) -> [RatchetState; 2] { + let state = self.state.read().unwrap(); + state.ratchet_states.clone() + } + /// The current ratchet count of this session. + pub fn ratchet_count(&self) -> u64 { + self.state.read().unwrap().ratchet_states[0].chain_len() + } + /// Mark a session as expired. This will make it impossible for this session to successfully + /// receive or send data or control packets. It is recommended to simply `drop` the session + /// instead, but this can provide some reassurance in complex shared ownership situations. + pub fn expire(&self) { + if let Some(context) = self.context.upgrade() { + self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); + } + } + fn expire_inner( + &self, + context: &Arc>, + session_queue: &mut IndexedBinaryHeap>, Reverse>, + ) { + // Prevent this session from being updated. + session_queue.remove(self.queue_idx); + self.session_has_expired.store(true, Ordering::Relaxed); + let _kex_lock = self.state_machine_lock.lock().unwrap(); + let mut state = self.state.write().unwrap(); + let mut session_map = context.session_map.write().unwrap(); + for key in &state.cipher_states { + if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { + session_map.remove(&pre_id); + } + } + use OfferStateMachine::*; + match &state.outgoing_offer { + NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), + NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), + _ => None, + }; + state.outgoing_offer = OfferStateMachine::Null; + } + + /// Get the next outgoing counter value. + fn get_next_outgoing_counter(&self) -> Result { + if self.session_has_expired.load(Ordering::Relaxed) { + Err(SendError::SessionExpired) + } else { + let counter = self.send_counter.fetch_add(1, Ordering::Relaxed); + if counter > THREAD_SAFE_COUNTER_HARD_EXPIRE { + // Because this thread sets the flag itself it will never be able to increment the + // counter again. + // For that reason the other atomic orderings can be `Relaxed`. + self.session_has_expired.store(true, Ordering::SeqCst) + } + Ok(counter) + } + } + /// Check the receive window without mutating state. + fn check_receive_window(&self, counter: u64) -> bool { + let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.load(Ordering::Relaxed); + prev_counter < counter && counter.wrapping_sub(prev_counter) <= COUNTER_WINDOW_MAX_SKIP_AHEAD + } + /// Update the receive window, returning true if the packet is still valid. + /// This should only be called after the packet is authenticated. + fn update_receive_window(&self, counter: u64) -> bool { + let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; + let counter = counter.wrapping_add(1); + let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); + prev_counter < counter && counter.wrapping_sub(prev_counter) <= COUNTER_WINDOW_MAX_SKIP_AHEAD + } +} +impl Drop for Session { + fn drop(&mut self) { + if let Some(context) = self.context.upgrade() { + self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); + } + } +} + +impl NoiseXKAliceHandshake { + /// Can only fail with `OpenError::InvalidPublicKey` because of remote_s_public_key. + /// Corresponds to Noise `Initialize`. + fn initialize( + local_key_id: NonZeroU32, + remote_s_public_key: &Application::PublicKey, + ratchet_state: &[RatchetState; 2], + rng: &mut Application::Rng, + ) -> Result< + ( + NoiseXKAliceHandshakeState, + Secret, + Secret, + ), + OpenError, + > { + let mut message = [0u8; NoiseXKPattern1::MAX_SIZE]; + let sha512 = &mut Application::Hash::new(); + let hmac = &mut Application::HmacHash::new(); + // Start of Noise XKhfs+psk2 pattern1. + let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); + let noise_e_secret = Application::KeyPair::generate(rng); + let noise_e1_secret = pqc_kyber::keypair(rng); + noise_pattern1.alice_key_id = local_key_id.get().to_ne_bytes(); + noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); + noise_pattern1.noise_e1 = noise_e1_secret.public; + // Noise process prologue. + let noise_h = mix_hash( + sha512, + &INITIAL_H, + &message[NoiseXKPattern1::PROLOGUE_START..NoiseXKPattern1::PROLOGUE_END], + ); + let noise_h = mix_hash(sha512, &noise_h, remote_s_public_key.as_bytes()); + // Noise process pattern1 e token. + let mut noise_ck = SymmetricState::new(INITIAL_H); + let noise_h_e = mix_hash(sha512, &noise_h, noise_e_secret.public_key_bytes()); + noise_ck.mix_key(hmac, noise_e_secret.public_key_bytes()); + // Noise process pattern1 es token. + let mut noise_es = Secret::new(); + if !noise_e_secret.agree(remote_s_public_key, noise_es.as_mut()) { + return Err(OpenError::InvalidPublicKey); + } + let noise_k_es = noise_ck.mix_key_initialize_key(hmac, noise_es.as_ref()); + drop(noise_es); + // Noise process pattern1 e1 token. + let noise_h_ee1 = encrypt_and_hash::( + sha512, + &noise_k_es, + &noise_h_e, + PACKET_TYPE_NOISE_XK_PATTERN_1, + 0, + &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], + ); + // Noise process pattern1 payload. + let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); + let mut idx = 0; + for rs in ratchet_state { + if let Some(rf) = rs.fingerprint() { + let next_idx = idx + RATCHET_SIZE; + noise_pattern1.payload[idx..next_idx].copy_from_slice(rf); + idx = next_idx; + } + } + let p_auth_end = NoiseXKPattern1::P_ENC_START + idx + AES_GCM_TAG_SIZE; + let noise_message_len = p_auth_end + ChallengeResponse::SIZE; + + let noise_h_ee1p = encrypt_and_hash::( + sha512, + &noise_k_es, + &noise_h_ee1, + PACKET_TYPE_NOISE_XK_PATTERN_1, + 1, + &mut message[NoiseXKPattern1::P_ENC_START..p_auth_end], + ); + drop(noise_k_es); + let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); + let message_id = u64::from_be_bytes(message[p_auth_end - 8..p_auth_end].try_into().unwrap()); + + message[noise_message_len - CHALLENGE_POW_SIZE..noise_message_len].copy_from_slice(&rng.next_u64().to_ne_bytes()); + Ok(( + NoiseXKAliceHandshakeState::NoiseXKPattern1 { + noise_h_ee1p, + noise_e_secret, + noise_e1_secret: Secret(noise_e1_secret.secret), + noise_ck_es: noise_ck, + noise_message_len, + noise_message: message, + message_id, + }, + header_a2b_key, + header_b2a_key, + )) + } + /// Should not fail unless Bob's public key is adversarial. + fn reinitialize( + &mut self, + session: &Arc>, + ratchet_state: &[RatchetState; 2], + session_map: &mut HashMap>, bool)>, + rng: &mut Application::Rng, + current_time: i64, + ) -> bool { + let local_key_id = generate_key_id(session_map, rng); + if let Ok((offer, a2b_header_key, b2a_header_key)) = Self::initialize(local_key_id, &session.remote_static_key, ratchet_state, rng) { + self.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); + session_map.remove(&self.local_key_id); + session_map.insert(local_key_id, (Arc::downgrade(session), false)); + self.local_key_id = local_key_id; + self.offer = offer; + session.header_send_cipher.reset(a2b_header_key.as_ref()); + session.header_receive_cipher.reset(b2a_header_key.as_ref()); + true + } else { + false + } + } +} + +/// Create the normal state of the offer state machine, with the correct timestamps. +fn new_normal_state(rand: u64, current_time: i64) -> OfferStateMachine { + OfferStateMachine::Normal { + timeout: current_time + .saturating_add(Application::REKEY_AFTER_TIME_MS) + .saturating_sub(rand as i64 % Application::REKEY_AFTER_TIME_MAX_JITTER_MS), + } +} +/// Get a timestamp of when this timer should trigger next, or None if it should trigger now. +fn process_timer(timer: &AtomicI64, wait_time: i64, current_time: i64) -> Option { + let ts = timer.load(Ordering::Relaxed); + if ts <= current_time && timer.fetch_max(ts.saturating_add(wait_time), Ordering::Relaxed) == ts { + None + } else { + Some(ts) + } +} + +/// Corresponds to Noise `EncryptAndHash`. +fn encrypt_and_hash( + sha512: &mut Application::Hash, + noise_k: &Secret, + noise_h: &[u8; HASHLEN], + packet_type: u8, + noise_k_uses: u64, + message: &mut [u8], +) -> [u8; HASHLEN] { + let auth_start = message.len() - AES_GCM_TAG_SIZE; + let mut gcm = Application::AeadEnc::new(noise_k.as_ref()); + // Encrypt and add authentication tag. + gcm.set_iv(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); + gcm.set_aad(noise_h); + if auth_start > 0 { + gcm.encrypt_in_place(&mut message[..auth_start]); + } + gcm.finish_encrypt((&mut message[auth_start..]).try_into().unwrap()); + mix_hash(sha512, noise_h, message) +} +/// Corresponds to Noise `DecryptAndHash`. +fn decrypt_and_hash( + sha512: &mut Application::Hash, + noise_k: &Secret, + noise_h: &[u8; HASHLEN], + packet_type: u8, + noise_k_uses: u64, + message: &mut [u8], +) -> (bool, [u8; HASHLEN]) { + let auth_start = message.len() - AES_GCM_TAG_SIZE; + let noise_h_c = mix_hash(sha512, noise_h, message); + let mut gcm = Application::AeadDec::new(noise_k.as_ref()); + gcm.set_iv(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); + gcm.set_aad(noise_h); + if auth_start > 0 { + gcm.decrypt_in_place(&mut message[..auth_start]); + } + (gcm.finish_decrypt((&message[auth_start..]).try_into().unwrap()), noise_h_c) +} +/// Encrypt a standardized control packet. +fn encrypt_control( + c: &mut impl AesGcmEnc, + header_cipher: &impl AesEnc, + packet_type: u8, + counter: u64, + remote_key_id: u32, + packet: &[u8], +) -> ([u8; CONTROL_PACKET_MAX_SIZE], usize) { + let mut fragment = [0u8; CONTROL_PACKET_MAX_SIZE]; + let fragment_len = packet.len() + HEADER_SIZE + AES_GCM_TAG_SIZE; + + c.set_iv(&create_message_nonce(packet_type, counter)); + if !packet.is_empty() { + fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE].copy_from_slice(packet); + c.encrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); + } + c.finish_encrypt((&mut fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]).try_into().unwrap()); + set_packet_header(&mut fragment, 1, 0, packet_type, remote_key_id, counter); + header_cipher.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); + (fragment, fragment_len) +} +fn decrypt_control<'a, IoError>( + c: &mut impl AesGcmDec, + packet_type: u8, + counter: u64, + fragment: &'a mut [u8], +) -> Result<&'a mut [u8], ReceiveError> { + let fragment_len = fragment.len(); + if !(CONTROL_PACKET_MIN_SIZE..=CONTROL_PACKET_MAX_SIZE).contains(&fragment_len) { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + c.set_iv(&create_message_nonce(packet_type, counter)); + c.decrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); + if !c.finish_decrypt((&fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]).try_into().unwrap()) { + // This can occur naturally if one of the remote peers resent a + // control packet that got delayed and arrived out of order. + return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); + } + Ok(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]) +} + +fn set_packet_header(packet: &mut [u8], fragment_count: u8, fragment_no: u8, packet_type: u8, remote_key_id: u32, counter_or_id: u64) { + debug_assert!(packet.len() >= MIN_PACKET_SIZE); + debug_assert!(fragment_count > 0); + debug_assert!(fragment_count <= MAX_FRAGMENTS as u8); + debug_assert!(fragment_no < MAX_FRAGMENTS as u8); + debug_assert_eq!((packet_type << 1) >> 1, packet_type); + // [0..4] recipient key id + // -- start AES(ck_es * h_e_e1_p) encrypted block -- + // [4] fragment count (1..255) + // [5] fragment number (0..254) + // [6] reserved zero + // -- start of AES-GCM Nonce -- + // [7] packet type + // [8..16] 64-bit counter or packet id (big endian) + packet[4..16].copy_from_slice(&create_message_nonce(packet_type, counter_or_id)); + packet[0..4].copy_from_slice(&remote_key_id.to_ne_bytes()); + packet[4] = fragment_count; + packet[5] = fragment_no; + packet[6] = 0; +} +/// Create a 96-bit AES-GCM nonce. +/// +/// The primary information that we want to be contained here is the counter and the +/// packet type. The former makes this unique and the latter's inclusion authenticates +/// it as effectively AAD. Other elements of the header are either not authenticated, +/// like fragmentation info, or their authentication is implied via key exchange like +/// the key id. +fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] { + let mut ret = [0u8; AES_GCM_IV_SIZE]; + ret[3] = packet_type; + // Noise requires a big endian counter at the end of the Nonce + ret[4..].copy_from_slice(&counter.to_be_bytes()); + ret +} +/// returns `(fragment_count, fragment_no, packet_type, counter, header_nonce)`. +fn parse_packet_header(packet: &[u8]) -> (u8, u8, u8, u64, [u8; 10]) { + let header_nonce = packet[6..16].try_into().unwrap(); + let counter = packet[8..16].try_into().unwrap(); + // We intentionally ignore the version number for future revisions. + (packet[4], packet[5], packet[7], u64::from_be_bytes(counter), header_nonce) +} + +/// Break a packet into fragments and send them all. +/// +/// The contents of packet[] are mangled during this operation, so it should be discarded after. +/// This is only used for key exchange and control packets. For data packets this is done inline +/// for better performance with encryption and fragmentation happening at the same time. +fn send_with_fragmentation( + send: &mut impl FnMut(&mut [u8]) -> bool, + mtu: usize, + packet: &mut [u8], + packet_type: u8, + remote_key_id: Option, + counter_or_id: u64, + header_cipher: Option<&impl AesEnc>, +) -> bool { + let packet_len = packet.len(); + let fragment_count = (packet_len.saturating_add(mtu - 1)) / mtu; // integer ceiling divide + debug_assert!(fragment_count <= MAX_FRAGMENTS); + let mut fragment_start = 0; + let mut fragment_end = packet_len.min(mtu); + let mut fragment_no = 0; + loop { + let fragment = &mut packet[fragment_start..fragment_end]; + set_packet_header( + fragment, + fragment_count as u8, + fragment_no as u8, + packet_type, + remote_key_id.map_or(0, |n| n.get()), + counter_or_id, + ); + if let Some(hcc) = header_cipher { + hcc.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); + } + if !send(fragment) { + return false; + } + fragment_no += 1; + if fragment_no < fragment_count { + fragment_start = fragment_end - HEADER_SIZE; + fragment_end = (fragment_start.saturating_add(mtu)).min(packet_len); + } else { + break; + } + } + true +} + +/// Assemble a series of fragments into a buffer and return the length of the assembled packet in +/// bytes. +/// +/// This is also only used for key exchange and control packets. For data packets decryption and +/// assembly happen in one pass for better performance. +fn assemble_fragments_into(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result> { + let mut l = 0; + for i in 0..fragments.len() { + let mut ff = fragments[i].as_ref(); + if i > 0 { + ff = &ff[HEADER_SIZE..]; + } + let j = l + ff.len(); + if j > d.len() { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + d[l..j].copy_from_slice(ff); + l = j; + } + Ok(l) +} +/// Generate a random local key id that is currently unused. +fn generate_key_id( + session_map: &HashMap>, bool)>, + rng: &mut Application::Rng, +) -> NonZeroU32 { + loop { + if let Some(local_key_id) = NonZeroU32::new(rng.next_u32()) { + if !session_map.contains_key(&local_key_id) { + return local_key_id; + } + } + } +} + +impl SessionKey { + fn new( + hmac: &mut Application::HmacHash, + ck: SymmetricState, + local_key_id: NonZeroU32, + remote_key_id: NonZeroU32, + current_counter: u64, + is_bob: bool, + ) -> Self { + let (b2a, a2b) = ck.split(hmac); + let (receive_key, send_key) = if is_bob { + (&a2b, &b2a) + } else { + (&b2a, &a2b) + }; + let send_cipher_pool = std::array::from_fn(|_| Mutex::new(Application::AeadEnc::new(send_key.as_ref()))); + let receive_cipher_pool = std::array::from_fn(|_| Mutex::new(Application::AeadDec::new(receive_key.as_ref()))); + Self { + local_key_id, + remote_key_id, + send_cipher_pool, + receive_cipher_pool, + rekey_at_counter: current_counter.saturating_add(Application::REKEY_AFTER_USES), + expire_at_counter: current_counter.saturating_add(Application::EXPIRE_AFTER_USES), + } + } + + fn get_send_cipher(&self, counter: u64) -> Result, SendError> { + if counter < self.expire_at_counter { + Ok(self.send_cipher_pool[(counter as usize) % self.send_cipher_pool.len()].lock().unwrap()) + } else { + Err(SendError::SessionExpired) + } + } + + fn get_receive_cipher(&self, counter: u64) -> MutexGuard { + let idx = (counter as usize) % self.receive_cipher_pool.len(); + self.receive_cipher_pool[idx].lock().unwrap() + } +} + +/// MixHash to update 'h' during negotiation. +fn mix_hash(hasher: &mut impl Sha512, h: &[u8; HASHLEN], m: &[u8]) -> [u8; HASHLEN] { + let mut output = [0u8; HASHLEN]; + hasher.reset(); + hasher.update(h); + hasher.update(m); + hasher.finish(&mut output); + output +} +/// Check if the proof of work attached to the first message contains the correct number of leading +/// zeros. +fn verify_pow(hasher: &mut Application::Hash, response: &[u8]) -> bool { + if Application::PROOF_OF_WORK_BIT_DIFFICULTY == 0 { + return true; + } + hasher.reset(); + hasher.update(response); + let mut output = [0u8; HASHLEN]; + hasher.finish(&mut output); + let n = u32::from_be_bytes(output[..4].try_into().unwrap()); + n.leading_zeros() >= Application::PROOF_OF_WORK_BIT_DIFFICULTY +} +fn from_bytes_agreement( + public: &[u8], + private: &Application::KeyPair, + output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE], +) -> Option { + Application::PublicKey::from_bytes(public.try_into().unwrap()).and_then(|e| private.agree(&e, output).then_some(e)) +} diff --git a/src/zssp.rs b/src/zssp.rs index 98f9a3f..2288a43 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -16,15 +16,17 @@ use std::ops::DerefMut; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; -use crate::crypto::aes::{AesDec, AesEnc}; -use crate::crypto::aes_gcm::{AesGcmDec, AesGcmEnc, AES_GCM_IV_SIZE, AES_GCM_KEY_SIZE, AES_GCM_TAG_SIZE}; +use arrayvec::ArrayVec; +use zeroize::Zeroizing; + +use crate::challenge::ChallengeContext; +use crate::crypto::aes::{AesDec, AesEnc, AES_256_KEY_SIZE}; use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; use crate::crypto::rand_core::RngCore; -use crate::crypto::secret::{secure_eq, Secret}; -use crate::crypto::sha512::{HmacSha512, Sha512}; +use crate::crypto::sha512::{HmacSha512, HashSha512}; -use crate::error::{FaultType, OpenError, ReceiveError, SendError}; +use crate::result::{FaultType, OpenError, ReceiveError, SendError}; use crate::frag_cache::UnassociatedFragCache; use crate::fragged::{Assembled, Fragged}; use crate::handshake_cache::UnassociatedHandshakeCache; @@ -53,9 +55,7 @@ pub struct ContextInner { /// `session_queue -> state_machine_lock -> state -> session_map` session_queue: Mutex>, Reverse>>, session_map: RwLock>, bool)>>, - challenge_counter: AtomicU64, - challenge_antireplay_window: [AtomicU64; CHALLENGE_COUNTER_WINDOW_MAX_OOO], - challenge_salt: [u8; CHALLENGE_SALT_SIZE], + challenge: ChallengeContext, rng: Mutex, } @@ -114,18 +114,18 @@ pub enum IncomingSessionAction { /// ZeroTier Secure Session Protocol (ZSSP) Session /// /// A FIPS/NIST compliant variant of Noise_XK with hybrid Kyber1024 PQ data forward secrecy. -pub struct Session { +pub struct Session { /// An arbitrary application defined object associated with each session. - pub application_data: Application::Data, + pub application_data: App::SessionData, /// Is true if the local peer acted as Bob, the responder in the initial key exchange. pub was_bob: bool, /// The receive context associated with this session, /// only this context can receive messages from the remote peer. - context: Weak>, + context: Weak>, /// Handle into the session queue for changing the update timer. queue_idx: BinaryHeapIndex, - remote_static_key: Application::PublicKey, + remote_static_key: App::PublicKey, send_counter: AtomicU64, /// This bool signals to all threads to stop incrementing the counter and instead error out. session_has_expired: AtomicBool, @@ -137,16 +137,16 @@ pub struct Session { /// it goes `session_queue -> state_machine_lock -> state -> session_map`. /// Any lock can be skipped but they must be locked in that order. state_machine_lock: Mutex<()>, - state: RwLock>, - defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], - header_send_cipher: Application::PrpEnc, - header_receive_cipher: Application::PrpDec, - kex_send_cipher: Mutex>, - kex_receive_cipher: Mutex>, + state: RwLock>, + defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], + header_send_cipher: App::PrpEnc, + header_receive_cipher: App::PrpDec, + kex_send_cipher: Mutex>, + kex_receive_cipher: Mutex>, /// Pre-computed rekeying values. - noise_kk_ss: Secret, - noise_kk_local_init_h: [u8; NOISE_HASHLEN], - noise_kk_remote_init_h: [u8; NOISE_HASHLEN], + noise_kk_ss: Zeroizing<[u8; P384_ECDH_SHARED_SECRET_SIZE]>, + noise_kk_local_init_h: [u8; HASHLEN], + noise_kk_remote_init_h: [u8; HASHLEN], } /// `AesGcm` is not threadsafe, but it is threadsafe when inside a `Mutex`. unsafe impl Send for Session {} @@ -161,6 +161,8 @@ struct SessionMutableState { /// This is the index of `noise_cipher_state` that contains the most recent key. /// It will be attached to fragment headers to help with OOO transport. current_key: usize, + resent_timer: AtomicI64, + timeout_timer: i64, /// This defines the exact state of the offer state machine we are in. outgoing_offer: OfferStateMachine, } @@ -168,73 +170,57 @@ struct SessionMutableState { /// These offer enums form a state machine. /// Documented below are the only legal transitions for this state machine. /// A session is initialized with an `outgoing_offer` of either NoiseXKPattern1 or Normal. -enum OfferStateMachine { - Normal { - timeout: i64, - }, // -> NoiseKKPattern1, NoiseKKPattern2 +enum OfferStateMachine { + Normal, // -> NoiseKKPattern1, NoiseKKPattern2 /// This state uses a lot of memory so we put it on the heap. - NoiseXKPattern1or3(Box>), // -> Normal + NoiseXKPattern1or3(Box>), // -> Normal NoiseKKPattern1 { - next_retry_time: AtomicI64, - timeout: i64, new_key_id: NonZeroU32, - noise_e_secret: Application::KeyPair, - noise_message: [u8; NoiseKKPattern1or2::SIZE], - noise_ck: SymmetricState, - noise_h_pskep: [u8; NOISE_HASHLEN], + noise_e_secret: App::KeyPair, + noise_message: ArrayVec, + noise_ck: SymmetricState, }, // -> NoiseKKPattern2, KeyConfirm NoiseKKPattern2 { - next_retry_time: AtomicI64, - timeout: i64, - noise_message: [u8; NoiseKKPattern1or2::SIZE], - kex_send_key: Secret, - }, // -> Normal - KeyConfirm { - next_retry_time: AtomicI64, - timeout: i64, + noise_message: ArrayVec, + kex_send_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, }, // -> Normal + KeyConfirm, // -> Normal Null, } -pub(crate) struct NoiseXKBobHandshakeState { +pub(crate) struct NoiseXKBobHandshakeState { /// Can never be Null. ratchet_state: RatchetState, remote_key_id: NonZeroU32, local_key_id: NonZeroU32, - header_receive_key: Secret, - header_send_key: Secret, - noise_h_ee1peekem1pskp: [u8; NOISE_HASHLEN], - noise_e_secret: Application::KeyPair, - noise_ck_eseeekem1psk: SymmetricState, - noise_k_eseeekem1psk: Secret, - noise_pattern3_defrag: Mutex>, + header_receive_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, + header_send_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, + noise_e_secret: App::KeyPair, + noise_ck_eseeekem1psk: SymmetricState, + noise_pattern3_defrag: Mutex>, } -struct NoiseXKAliceHandshake { - next_retry_time: AtomicI64, - timeout: i64, +struct NoiseXKAliceHandshake { /// A secure random number put in the header of Alice's fragments to identify them. /// If a DDOS attacker could guess this they could block Alice starting the handshake. local_key_id: NonZeroU32, - alice_identity_blob: Application::LocalIdentityBlob, - offer: NoiseXKAliceHandshakeState, + alice_identity_blob: App::LocalIdentityBlob, + offer: NoiseXKAliceHandshakeState, } -enum NoiseXKAliceHandshakeState { +enum NoiseXKAliceHandshakeState { NoiseXKPattern1 { - noise_h_ee1p: [u8; NOISE_HASHLEN], - noise_e_secret: Application::KeyPair, - noise_e1_secret: Secret, - noise_ck_es: SymmetricState, + noise_h_ee1p: [u8; HASHLEN], + noise_e_secret: App::KeyPair, + noise_e1_secret: App::Kem, + noise_ck_es: SymmetricState, /// ZSSP assumes an unreliable, out-of-order physical transport environment, so for that /// reason we have to resend key offers. - noise_message: [u8; NoiseXKPattern1::MAX_SIZE], - noise_message_len: usize, + noise_message: ArrayVec, message_id: u64, }, NoiseXKPattern3 { - noise_message: [u8; NoiseXKPattern3::MAX_SIZE], - noise_message_len: usize, + noise_message: ArrayVec, }, } @@ -476,10 +462,10 @@ impl Context { mut send: impl FnMut(&mut [u8]) -> bool, mut mtu: usize, remote_static_key: Application::PublicKey, - application_data: Application::Data, + application_data: Application::SessionData, local_identity_blob: Application::LocalIdentityBlob, current_time: i64, - ) -> Result>, OpenError> { + ) -> Result>, OpenError> { mtu = mtu.max(MIN_TRANSPORT_MTU); if local_identity_blob.as_ref().len() > MAX_IDENTITY_BLOB_SIZE { return Err(OpenError::DataTooLarge); @@ -602,7 +588,7 @@ impl Context { &self, app: &Application, check_allow_incoming_session: impl FnOnce() -> IncomingSessionAction, - check_accept_session: impl FnOnce(&Application::PublicKey, &[u8], u64) -> (Option<(bool, Application::Data)>, bool), + check_accept_session: impl FnOnce(&Application::PublicKey, &[u8], u64) -> (Option<(bool, Application::SessionData)>, bool), mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, mut send_unassociated_mtu: usize, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, @@ -610,7 +596,7 @@ impl Context { data_buf: &'a mut [u8], mut incoming_physical_packet_buf: Application::IncomingPacketBuffer, current_time: i64, - ) -> Result, ReceiveError> { + ) -> Result, ReceiveError> { send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); let incoming_physical_packet: &mut [u8] = incoming_physical_packet_buf.as_mut(); let incoming_physical_packet_len = incoming_physical_packet.len(); @@ -902,7 +888,7 @@ impl Context { sha512.reset(); let mut hasher = ShaHasher(sha512); - let mut output = [0u8; NOISE_HASHLEN]; + let mut output = [0u8; HASHLEN]; hasher.0.update(&response.challenge_counter); remote_address.hash(&mut hasher); hasher.0.update(&self.0.challenge_salt); @@ -1823,7 +1809,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu counter: u64, fragment: &mut [u8], current_time: i64, -) -> Result, ReceiveError> { +) -> Result, ReceiveError> { let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); let mut c = session.kex_receive_cipher.lock().unwrap(); @@ -2307,7 +2293,7 @@ impl NoiseXKAliceHandshake { Secret, Secret, ), - OpenError, + OpenError, > { let mut message = [0u8; NoiseXKPattern1::MAX_SIZE]; let sha512 = &mut Application::Hash::new(); @@ -2433,11 +2419,11 @@ fn process_timer(timer: &AtomicI64, wait_time: i64, current_time: i64) -> Option fn encrypt_and_hash( sha512: &mut Application::Hash, noise_k: &Secret, - noise_h: &[u8; NOISE_HASHLEN], + noise_h: &[u8; HASHLEN], packet_type: u8, noise_k_uses: u64, message: &mut [u8], -) -> [u8; NOISE_HASHLEN] { +) -> [u8; HASHLEN] { let auth_start = message.len() - AES_GCM_TAG_SIZE; let mut gcm = Application::AeadEnc::new(noise_k.as_ref()); // Encrypt and add authentication tag. @@ -2453,11 +2439,11 @@ fn encrypt_and_hash( fn decrypt_and_hash( sha512: &mut Application::Hash, noise_k: &Secret, - noise_h: &[u8; NOISE_HASHLEN], + noise_h: &[u8; HASHLEN], packet_type: u8, noise_k_uses: u64, message: &mut [u8], -) -> (bool, [u8; NOISE_HASHLEN]) { +) -> (bool, [u8; HASHLEN]) { let auth_start = message.len() - AES_GCM_TAG_SIZE; let noise_h_c = mix_hash(sha512, noise_h, message); let mut gcm = Application::AeadDec::new(noise_k.as_ref()); @@ -2604,7 +2590,7 @@ fn send_with_fragmentation( /// /// This is also only used for key exchange and control packets. For data packets decryption and /// assembly happen in one pass for better performance. -fn assemble_fragments_into(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result> { +fn assemble_fragments_into(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result> { let mut l = 0; for i in 0..fragments.len() { let mut ff = fragments[i].as_ref(); @@ -2676,8 +2662,8 @@ impl SessionKey { } /// MixHash to update 'h' during negotiation. -fn mix_hash(hasher: &mut impl Sha512, h: &[u8; NOISE_HASHLEN], m: &[u8]) -> [u8; NOISE_HASHLEN] { - let mut output = [0u8; NOISE_HASHLEN]; +fn mix_hash(hasher: &mut impl Sha512, h: &[u8; HASHLEN], m: &[u8]) -> [u8; HASHLEN] { + let mut output = [0u8; HASHLEN]; hasher.reset(); hasher.update(h); hasher.update(m); @@ -2692,7 +2678,7 @@ fn verify_pow(hasher: &mut Application::Hash, res } hasher.reset(); hasher.update(response); - let mut output = [0u8; NOISE_HASHLEN]; + let mut output = [0u8; HASHLEN]; hasher.finish(&mut output); let n = u32::from_be_bytes(output[..4].try_into().unwrap()); n.leading_zeros() >= Application::PROOF_OF_WORK_BIT_DIFFICULTY From 82e3bc647f9dbd559d36858a893333a96bfc564c Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 7 Aug 2023 15:04:02 -0400 Subject: [PATCH 56/91] added sending --- rustfmt.toml | 2 +- src/applicationlayer.rs | 2 +- src/challenge.rs | 4 +- src/context.rs | 20 +- src/frag_cache.rs | 31 +- src/fragged.rs | 22 +- src/handshake_cache.rs | 12 +- src/lib.rs | 12 +- src/log_event.rs | 2 +- src/proto.rs | 107 +- src/result.rs | 2 + src/symmetric_state.rs | 32 +- src/zeta.rs | 1085 +++++++-------- src/zssp.rs | 2761 ++++++--------------------------------- 14 files changed, 1129 insertions(+), 2965 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index 3a3929c..9c9fedd 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,4 @@ -max_width = 150 +max_width = 120 edition = "2021" newline_style = "Unix" struct_lit_width = 60 diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 7f18f60..2dcfc96 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -14,7 +14,7 @@ use crate::crypto::rand_core::{CryptoRng, RngCore}; use crate::crypto::sha512::{HmacSha512, HashSha512}; use crate::RatchetState; use crate::proto::RATCHET_SIZE; -use crate::zssp::Session; +use crate::zeta::Session; //use crate::{log_event::LogEvent, Session}; /// A container for a vast majority of the dynamic settings within ZSSP, including all time-based settings. diff --git a/src/challenge.rs b/src/challenge.rs index 76e6cc5..c47af7b 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -54,12 +54,12 @@ impl ChallengeContext { &self, addr: &impl std::hash::Hash, response: &[u8; CHALLENGE_SIZE], - ) -> Result { + ) -> Result<(), [u8; CHALLENGE_SIZE]> { let c = u64::from_be_bytes(response[..COUNTER_SIZE].try_into().unwrap()); let mut work_buf = [0u8; SHA512_HASH_SIZE]; if self.antireplay_window.check(c) && secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac::(c, addr)) && verify_pow::(response, &mut work_buf) { self.antireplay_window.update(c); - Ok(true) + Ok(()) } else { let mut challenge = [0u8; CHALLENGE_SIZE]; let d = self.counter.fetch_add(1, Ordering::Relaxed); diff --git a/src/context.rs b/src/context.rs index ba69585..2320365 100644 --- a/src/context.rs +++ b/src/context.rs @@ -8,6 +8,8 @@ use std::sync::{Arc, Mutex, Weak, RwLock}; use crate::applicationlayer::ApplicationLayer; use crate::crypto::aes::{AES_256_KEY_SIZE, AES_GCM_IV_SIZE}; +use crate::frag_cache::UnassociatedFragCache; +use crate::handshake_cache::UnassociatedHandshakeCache; use crate::indexed_heap::IndexedBinaryHeap; //use crate::fragmentation::{send_with_fragmentation, DefragBuffer}; use crate::proto::*; @@ -46,6 +48,8 @@ pub(crate) struct ContextInner { pub(crate) s_secret: App::KeyPair, pub(crate) session_queue: Mutex>, Reverse>>, pub(crate) session_map: SessionMap, + unassociated_defrag_cache: Mutex>, + unassociated_handshake_states: UnassociatedHandshakeCache, //pub(crate) b2_map: Mutex>>, //hello_defrag: Mutex, @@ -70,19 +74,13 @@ impl Context { Self(Arc::new(ContextInner { rng: Mutex::new(rng), s_secret: static_secret_key, - session_map: Mutex::new(HashMap::new()), - b2_map: Mutex::new(HashMap::new()), - hello_defrag: Mutex::new(DefragBuffer::new(None)), - challenge: Mutex::new(challenge), - sessions: Mutex::new(HashMap::new()), + session_map: RwLock::new(HashMap::new()), + challenge, + session_queue: Mutex::new(IndexedBinaryHeap::new()), + unassociated_defrag_cache: Mutex::new(UnassociatedFragCache::new()), + unassociated_handshake_states: UnassociatedHandshakeCache::new(), })) } - /// Enable the ZeroTier Challenge Protocol, to protect this machine from CPU exhaustion DDOS - /// attacks. - pub fn enable_challenge(&self, enabled: bool) { - self.0.challenge.lock().unwrap().enabled = enabled; - } - /// Create a new session and send initial packet(s) to other side. /// /// This will return SendError::DataTooLarge if the combined size of the metadata and the local diff --git a/src/frag_cache.rs b/src/frag_cache.rs index 62ddfc0..f0c9c27 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -10,6 +10,7 @@ use std::collections::hash_map::RandomState; use std::hash::{BuildHasher, Hash, Hasher}; use std::mem::MaybeUninit; +use crate::crypto::aes::AES_GCM_IV_SIZE; use crate::fragged::Assembled; use crate::proto::{MAX_FRAGMENTS, MAX_UNASSOCIATED_FRAGMENTS, MAX_UNASSOCIATED_PACKETS, MAX_UNASSOCIATED_PACKET_SIZE}; @@ -17,7 +18,7 @@ struct PacketMetadata { key: u64, frags_idx: u32, fragment_have: u64, - fragment_count: u32, + fragment_count: u8, packet_size: u32, creation_time: i64, } @@ -55,24 +56,24 @@ impl UnassociatedFragCache { /// Will check that aad is the same for all fragments. pub(crate) fn assemble( &mut self, - nonce: [u8; 10], + nonce: &[u8; AES_GCM_IV_SIZE], remote_address: impl Hash, fragment_size: usize, fragment: Fragment, - fragment_no: u8, - fragment_count: u8, - timeout: i64, + fragment_no: usize, + fragment_count: usize, + timeout_interval: i64, current_time: i64, ret_assembled: &mut Assembled, ) { debug_assert!(MAX_FRAGMENTS < MAX_UNASSOCIATED_FRAGMENTS); - if fragment_no >= fragment_count || (fragment_count as usize) > MAX_FRAGMENTS || fragment_size > MAX_UNASSOCIATED_PACKET_SIZE { + if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS || fragment_size > MAX_UNASSOCIATED_PACKET_SIZE { return; } let mut hasher = self.dos_salt.build_hasher(); remote_address.hash(&mut hasher); - hasher.write(&nonce); + hasher.write(nonce); let mut key = hasher.finish(); if key == 0 { key = 1; @@ -98,7 +99,7 @@ impl UnassociatedFragCache { } else if self.map[idx0].key == 0 || self.map[idx1].key == 0 { if (fragment_count as usize) > self.frags_unused_size { // There are not enough free fragment slots so attempt to expire a bunch of entries. - self.check_for_expiry(timeout, current_time); + self.check_for_expiry(timeout_interval, current_time); } if self.map[idx0].key == 0 { idx0 @@ -107,7 +108,7 @@ impl UnassociatedFragCache { } } else { // No room for a new entry so attempt to expire a bunch of entries. - self.check_for_expiry(timeout, current_time); + self.check_for_expiry(timeout_interval, current_time); if self.map[idx0].key == 0 { idx0 } else if self.map[idx1].key == 0 { @@ -125,7 +126,7 @@ impl UnassociatedFragCache { entry.key = key; entry.frags_idx = self.frags_first_unused as u32; entry.fragment_have = 0; - entry.fragment_count = fragment_count as u32; + entry.fragment_count = fragment_count as u8; entry.packet_size = 0; entry.creation_time = current_time; @@ -143,7 +144,7 @@ impl UnassociatedFragCache { let new_size = entry.packet_size + fragment_size as u32; let got = 1u64.wrapping_shl(fragment_no as u32); - if got & entry.fragment_have == 0 && fragment_count == entry.fragment_count as u8 && new_size <= MAX_UNASSOCIATED_PACKET_SIZE as u32 { + if got & entry.fragment_have == 0 && fragment_count == entry.fragment_count as usize && new_size <= MAX_UNASSOCIATED_PACKET_SIZE as u32 { entry.packet_size = new_size; entry.fragment_have |= got; @@ -272,9 +273,9 @@ fn test_cache() { if drop != j { let fragment = vec![0, 1, 2, 3, 4, 5, 6, r]; // If the timeout is 1 we should be guaranteed to get our packet cached. - let mut nonce = [0; 10]; + let mut nonce = [0; 12]; nonce[..4].copy_from_slice(&i.to_be_bytes()); - cache.assemble(nonce, 0, fragment.len(), fragment, j as u8, fragment_count as u8, 1, time, &mut assembled); + cache.assemble(&nonce, 0, fragment.len(), fragment, j, fragment_count, 1, time, &mut assembled); time += 1; } } @@ -297,9 +298,9 @@ fn test_cache() { let (no, fragment) = packet.swap_remove(xorshift64_random() as usize % packet.len()); assembled.empty(); - let mut nonce = [0; 10]; + let mut nonce = [0; 12]; nonce[..4].copy_from_slice(&id.to_be_bytes()); - cache.assemble(nonce, 0, fragment.len(), fragment, no, fragment_count, 1000, time, &mut assembled); + cache.assemble(&nonce, 0, fragment.len(), fragment, no as usize, fragment_count as usize, 1000, time, &mut assembled); time += 1; in_progress_fragments -= 1; diff --git a/src/fragged.rs b/src/fragged.rs index 77ee095..145b5d8 100644 --- a/src/fragged.rs +++ b/src/fragged.rs @@ -9,7 +9,8 @@ use std::mem::{needs_drop, zeroed, MaybeUninit}; use std::ptr::slice_from_raw_parts; -use crate::proto::MAX_FRAGMENTS; +use crate::crypto::aes::AES_GCM_IV_SIZE; +use crate::proto::{MAX_FRAGMENTS, NONCE_SIZE_DIFF}; pub(crate) struct Assembled(pub(crate) [MaybeUninit; MAX_FRAGMENTS], pub(crate) usize); @@ -42,9 +43,9 @@ impl Drop for Assembled { /// Fast packet defragmenter pub struct Fragged { - count: u32, - have: u64, nonce: [u8; 10], + count: u8, + have: u64, size: usize, frags: [MaybeUninit; MAX_FRAGMENTS], } @@ -63,28 +64,29 @@ impl Fragged { /// Will check that aad is the same for all fragments. pub(crate) fn assemble( &mut self, - nonce: [u8; 10], + nonce: &[u8; AES_GCM_IV_SIZE], fragment: Fragment, - fragment_no: u8, - fragment_count: u8, + fragment_no: usize, + fragment_count: usize, ret_assembled: &mut Assembled, ) { - if fragment_no < fragment_count && (fragment_count as usize) <= MAX_FRAGMENTS { + if fragment_no < fragment_count && fragment_count <= MAX_FRAGMENTS { + let nonce = nonce[NONCE_SIZE_DIFF..].try_into().unwrap(); // If the counter has changed, reset the structure to receive a new packet. if nonce != self.nonce { self.drop_in_place(); - self.count = fragment_count as u32; + self.count = fragment_count as u8; self.nonce = nonce; self.size = 0; } let got = 1u64.wrapping_shl(fragment_no as u32); - if got & self.have == 0 && self.count as u8 == fragment_count { + if got & self.have == 0 && self.count == fragment_count as u8 { self.have |= got; unsafe { self.frags.get_unchecked_mut(fragment_no as usize).write(fragment); } - if self.have == 1u64.wrapping_shl(self.count) - 1 { + if self.have == 1u64.wrapping_shl(self.count as u32) - 1 { self.have = 0; self.count = 0; self.nonce = [0; 10]; diff --git a/src/handshake_cache.rs b/src/handshake_cache.rs index 25f219a..8ce3852 100644 --- a/src/handshake_cache.rs +++ b/src/handshake_cache.rs @@ -10,7 +10,7 @@ use std::num::NonZeroU32; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; -use crate::zssp::NoiseXKBobHandshakeState; +use crate::zeta::StateB2; use crate::{proto::MAX_UNASSOCIATED_HANDSHAKE_STATES, ApplicationLayer}; pub(crate) struct UnassociatedHandshakeCache { @@ -18,10 +18,10 @@ pub(crate) struct UnassociatedHandshakeCache { cache: RwLock>, } /// SoA format -struct CacheInner { +struct CacheInner { local_ids: [Option; MAX_UNASSOCIATED_HANDSHAKE_STATES], timeouts: [i64; MAX_UNASSOCIATED_HANDSHAKE_STATES], - handshakes: [Option>>; MAX_UNASSOCIATED_HANDSHAKE_STATES], + handshakes: [Option>>; MAX_UNASSOCIATED_HANDSHAKE_STATES], } /// Linear-search cache for capping the memory consumption of handshake data. @@ -38,7 +38,7 @@ impl UnassociatedHandshakeCache { }), } } - pub(crate) fn get(&self, local_id: NonZeroU32) -> Option>> { + pub(crate) fn get(&self, local_id: NonZeroU32) -> Option>> { let cache = self.cache.read().unwrap(); for (i, id) in cache.local_ids.iter().enumerate() { if *id == Some(local_id) { @@ -47,7 +47,7 @@ impl UnassociatedHandshakeCache { } None } - pub(crate) fn insert(&self, local_id: NonZeroU32, state: Arc>, current_time: i64) { + pub(crate) fn insert(&self, local_id: NonZeroU32, state: Arc>, current_time: i64) { let mut cache = self.cache.write().unwrap(); let mut idx = 0; for i in 0..cache.local_ids.len() { @@ -59,7 +59,7 @@ impl UnassociatedHandshakeCache { } } cache.local_ids[idx] = Some(local_id); - cache.timeouts[idx] = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); + cache.timeouts[idx] = current_time + Application::SETTINGS.fragment_assembly_timeout as i64; cache.handshakes[idx] = Some(state); self.has_pending.store(true, Ordering::Release); } diff --git a/src/lib.rs b/src/lib.rs index 16c9ec0..61c0db5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,20 +8,20 @@ pub mod crypto; mod applicationlayer; -//mod frag_cache; -//mod fragged; -//mod handshake_cache; +mod fragged; +mod frag_cache; +mod handshake_cache; mod indexed_heap; -//mod log_event; +mod log_event; mod proto; mod ratchet_state; mod symmetric_state; mod antireplay; mod challenge; pub mod result; -//mod zssp; +mod zssp; mod zeta; -mod context; +//mod context; //pub mod error; pub use crate::applicationlayer::ApplicationLayer; diff --git a/src/log_event.rs b/src/log_event.rs index be543aa..cf96367 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -7,7 +7,7 @@ */ use std::sync::Arc; -use crate::{ApplicationLayer, zssp::Session}; +use crate::{ApplicationLayer, zeta::Session}; /// ZSSP events that might be interesting to log or aggregate into metrics. pub enum LogEvent<'a, Application: ApplicationLayer> { diff --git a/src/proto.rs b/src/proto.rs index 0d466a7..3f11bb1 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -1,8 +1,9 @@ +use crate::crypto::{aes::{AES_GCM_TAG_SIZE, AES_GCM_IV_SIZE}, kyber1024::{KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE}, p384::P384_PUBLIC_KEY_SIZE, sha512::SHA512_HASH_SIZE}; /* Common constants */ -use crate::crypto::{sha512::SHA512_HASH_SIZE, p384::P384_PUBLIC_KEY_SIZE, kyber1024::{KYBER_PUBLIC_KEY_SIZE, KYBER_CIPHERTEXT_SIZE}, aes::AES_GCM_TAG_SIZE}; - +/// Minimum size of a valid physical ZSSP packet of any type. Anything smaller is discarded. +pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; /// Minimum physical MTU for ZSSP to function. /// If an MTU is passed to ZSSP that is lower than this, it will be ignored and instead this value /// will be used. @@ -44,33 +45,11 @@ pub(crate) const PACKET_NONCE_START: usize = HEADER_SIZE - PACKET_NONCE_SIZE; pub(crate) const FRAGMENT_NO_IDX: usize = 4; pub(crate) const FRAGMENT_COUNT_IDX: usize = 5; +/// Maximum number of fragments a single packet may be split into. If a packet cannot fit +/// into this number of fragments it will be dropped. pub(crate) const MAX_FRAGMENTS: usize = 48; -/// Maximum window over which session packets may be reordered to be defragmented and -/// reassembled. Out of order fragments may be dropped in favor of newer fragments. -/// Increasing this value makes a session consume more significantly more memory. -pub(crate) const SESSION_MAX_FRAGMENTS_OOO: usize = 32; -/// The maximum number of unassociated packets that a receive context will cache. -/// Additional packets will either be dropped or cause a different packet to be dropped -/// from the cache. -/// Larger values consume more memory but provide better reliability and DDOS resistance. -pub(crate) const MAX_UNASSOCIATED_PACKETS: usize = 32; -/// The maximum number of fragments of unassociated packets that a receive context will -/// cache. -/// All unassociated fragments share the same buffer, when it fills up additional -/// fragments will be dropped or cause other fragments to be dropped from the cache. -/// Larger values consume more memory but provide better reliability and DDOS resistance. -pub(crate) const MAX_UNASSOCIATED_FRAGMENTS: usize = 32 * 32; -/// The maximum number of `NoiseXKBobHandshakeState` that a receive context will cache. -/// These are extremely large and since Alice has not been authenticated we put a hard -/// limit to how many we cache. -/// Larger values consume more memory but provide better reliability and DDOS resistance. -pub(crate) const MAX_UNASSOCIATED_HANDSHAKE_STATES: usize = 32; - -/// The maximum size a packet that is not associated to a session may be. -/// Excludes the size of headers for fragmentation. -pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = HANDSHAKE_HELLO_MAX_SIZE - HEADER_SIZE; - +pub(crate) const NONCE_SIZE_DIFF: usize = AES_GCM_IV_SIZE - PACKET_NONCE_SIZE; /* Key exchange constants */ /* @@ -93,9 +72,11 @@ pub(crate) const HASHLEN: usize = SHA512_HASH_SIZE; /// The size in bytes of both a ratchet key and a ratchet fingerprint. pub const RATCHET_SIZE: usize = 32; -pub(crate) const PROTOCOL_NAME_NOISE_XK: [u8; HASHLEN] = *b"Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; -pub(crate) const PROTOCOL_NAME_NOISE_KK: [u8; HASHLEN] = - *b"Noise_KKpsk0_P384_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +/// Initial value of 'h'. +pub(crate) const PROTOCOL_NAME_NOISE_XK: &[u8; HASHLEN] = b"Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +/// Initial value of 'ck' for rekeying. +pub(crate) const PROTOCOL_NAME_NOISE_KK: &[u8; HASHLEN] = + b"Noise_KKpsk0_P384_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; pub(crate) const LABEL_OTP_TO_RATCHET: &[u8; 19] = b"ZSSP_OTP_TO_RATCHET"; pub(crate) const LABEL_KBKDF_CHAIN: &[u8; 4] = b"ZSSP"; @@ -104,9 +85,21 @@ pub(crate) const LABEL_HEADER_KEY: &[u8; 4] = b"ASKH"; pub(crate) const LABEL_KEX_KEY: &[u8; 4] = b"ASKK"; pub(crate) const INIT_COUNTER: u64 = 0; -pub(crate) const EXPIRE_AFTER_USES: u64 = 4294967295; +pub(crate) const EXPIRE_AFTER_USES: u64 = 1 << 32 - 1; +/// Determines the number of counters a session will remember. If a counter arrives over +/// this amount out of order relative to other received counters, it is likely to be +/// rejected on the basis that the session can't remember if this counter was replayed. +/// Increasing this value makes a session consume more memory. pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 64; +/// Maximum number of counter steps that the counter is allowed to skip ahead. +/// This cannot be changed away from 2^24 without changing the header nonce handling code. pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 1 << 24; +/// Similar to `COUNTER_WINDOW_MAX_OOO`, except this governs the receive context challenge +/// counter rather than the session counter. +/// When Bob issues a challenge to Alice to mitigate DDOS, Bob will only accept Alice's +/// response once, and then its attached counter is added to the window. +pub(crate) const CHALLENGE_COUNTER_WINDOW_MAX_OOO: usize = 32; +pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - 1<<16; /* Packet constants */ @@ -122,20 +115,64 @@ pub(crate) const PACKET_TYPE_DATA: u8 = 8; pub(crate) const PACKET_TYPE_CHALLENGE: u8 = 9; pub(crate) const PACKET_TYPE_USES_COUNTER_RANGE: std::ops::Range = 3..9; -pub(crate) const MAX_HANDSHAKE_SIZE: usize = MAX_FRAGMENTS * MIN_TRANSPORT_MTU; - pub(crate) const HANDSHAKE_HELLO_MIN_SIZE: usize = KID_SIZE + P384_PUBLIC_KEY_SIZE + KYBER_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; pub(crate) const HANDSHAKE_HELLO_MAX_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + RATCHET_SIZE; +pub(crate) const HANDSHAKE_HELLO_CHALLENGE_MIN_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + CHALLENGE_SIZE; +pub(crate) const HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE: usize = HANDSHAKE_HELLO_MAX_SIZE + CHALLENGE_SIZE; + +pub(crate) const HEADERED_HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE: usize = HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE + HEADER_SIZE; + pub(crate) const HANDSHAKE_RESPONSE_SIZE: usize = P384_PUBLIC_KEY_SIZE + KYBER_CIPHERTEXT_SIZE + AES_GCM_TAG_SIZE + KID_SIZE + AES_GCM_TAG_SIZE; +pub(crate) const HEADERED_HANDSHAKE_RESPONSE_SIZE: usize = HANDSHAKE_RESPONSE_SIZE + HEADER_SIZE; pub(crate) const HANDSHAKE_COMPLETION_MIN_SIZE: usize = P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + 0 + AES_GCM_TAG_SIZE; -pub(crate) const HANDSHAKE_COMPLETION_MAX_SIZE: usize = MAX_HANDSHAKE_SIZE; +pub(crate) const HANDSHAKE_COMPLETION_MAX_SIZE: usize = HANDSHAKE_COMPLETION_MIN_SIZE + IDENTITY_MAX_SIZE; + +pub(crate) const HEADERED_HANDSHAKE_COMPLETION_MAX_SIZE: usize = HANDSHAKE_COMPLETION_MAX_SIZE + HEADER_SIZE; pub(crate) const KEY_CONFIRMATION_SIZE: usize = AES_GCM_TAG_SIZE; +pub(crate) const HEADERED_KEY_CONFIRMATION_SIZE: usize = KEY_CONFIRMATION_SIZE + HEADER_SIZE; + pub(crate) const ACKNOWLEDGEMENT_SIZE: usize = AES_GCM_TAG_SIZE; +pub(crate) const HEADERED_ACKNOWLEDGEMENT_SIZE: usize = ACKNOWLEDGEMENT_SIZE + HEADER_SIZE; + pub(crate) const SESSION_REJECTED_SIZE: usize = AES_GCM_TAG_SIZE; +pub(crate) const HEADERED_SESSION_REJECTED_SIZE: usize = SESSION_REJECTED_SIZE + HEADER_SIZE; pub(crate) const REKEY_SIZE: usize = P384_PUBLIC_KEY_SIZE + KID_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; +pub(crate) const HEADERED_REKEY_SIZE: usize = P384_PUBLIC_KEY_SIZE + KID_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; -pub(crate) const MAX_IDENTITY_SIZE: usize = MAX_HANDSHAKE_SIZE - HANDSHAKE_COMPLETION_MIN_SIZE; +/// The application has the ability to attach a data payload to Alice's handshake. +/// It will be the first payload Bob receives from Alice. +/// The application also must attach a static public identity to their handshake. +/// The combined size of both in bytes must be at most this value. +/// +/// If not ZSSP will return `OpenError::DataTooLarge` and refuse to create a session object. +pub const IDENTITY_MAX_SIZE: usize = 4096; + +/* DOS mitigation constants */ + +/// The maximum number of `NoiseXKBobHandshakeState` that a receive context will cache. +/// These are extremely large and since Alice has not been authenticated we put a hard +/// limit to how many we cache. +/// Larger values consume more memory but provide better reliability and DDOS resistance. +pub(crate) const MAX_UNASSOCIATED_HANDSHAKE_STATES: usize = 32; + +/// The maximum number of unassociated packets that a receive context will cache. +/// Additional packets will either be dropped or cause a different packet to be dropped +/// from the cache. +/// Larger values consume more memory but provide better reliability and DDOS resistance. +pub(crate) const MAX_UNASSOCIATED_PACKETS: usize = 32; +/// The maximum number of fragments of unassociated packets that a receive context will +/// cache. +/// All unassociated fragments share the same buffer, when it fills up additional +/// fragments will be dropped or cause other fragments to be dropped from the cache. +/// Larger values consume more memory but provide better reliability and DDOS resistance. +pub(crate) const MAX_UNASSOCIATED_FRAGMENTS: usize = 32 * 32; + +/// The maximum size a packet that is not associated to a session may be. +/// Excludes the size of headers for fragmentation. +pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = HANDSHAKE_HELLO_MAX_SIZE; + +pub(crate) const SESSION_MAX_FRAGMENTS_OOO: usize = 64; diff --git a/src/result.rs b/src/result.rs index d542227..4710492 100644 --- a/src/result.rs +++ b/src/result.rs @@ -10,6 +10,8 @@ pub enum OpenError { /// An invalid parameter was supplied to the function. InvalidPublicKey, + IdentityTooLarge, + RatchetIoError(IoError), } diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index a2caf2b..f2e2a22 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -80,11 +80,11 @@ impl SymmetricState { } /// Corresponds to Noise `Initialize` on a SymmetricState. - pub fn initialize(h: [u8; HASHLEN]) -> Self { + pub fn initialize(h: &[u8; HASHLEN]) -> Self { Self { k: Zeroizing::default(), - ck: Zeroizing::new(h), - h, + ck: Zeroizing::new(*h), + h: *h, _app: PhantomData, } } @@ -98,6 +98,14 @@ impl SymmetricState { *self.ck = *next_ck; self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } + /// Corresponds to Noise `MixKey`. + pub fn mix_key_no_init(&mut self, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + let mut next_ck = Zeroizing::new([0u8; HASHLEN]); + + self.kbkdf(hmac, input_key_material, LABEL_KBKDF_CHAIN, 2, &mut next_ck, None, None); + + *self.ck = *next_ck; + } /// Corresponds to Noise `MixHash`. pub fn mix_hash(&mut self, hash: &mut App::Hash, data: &[u8]) { hash.update(&self.h); @@ -124,6 +132,24 @@ impl SymmetricState { self.mix_hash(hash, &temp_h); self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } + /// Corresponds to Noise `MixKeyAndHash`. + pub fn mix_key_and_hash_no_init(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + let mut next_ck = Zeroizing::new([0u8; HASHLEN]); + let mut temp_h = [0u8; HASHLEN]; + + self.kbkdf( + hmac, + input_key_material, + LABEL_KBKDF_CHAIN, + 3, + &mut next_ck, + Some(&mut temp_h), + None, + ); + + *self.ck = *next_ck; + self.mix_hash(hash, &temp_h); + } /// Corresponds to Noise `EncryptAndHash`. #[must_use] pub fn encrypt_and_hash_in_place(&mut self, hash: &mut App::Hash, iv: [u8; AES_GCM_IV_SIZE], data: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE] { diff --git a/src/zeta.rs b/src/zeta.rs index 159df8b..9203426 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -4,7 +4,7 @@ use std::cmp::Reverse; use std::collections::HashMap; use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; -use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::atomic::{AtomicU64, Ordering, AtomicBool}; use std::sync::{Arc, Mutex, Weak, RwLock}; use zeroize::Zeroizing; @@ -12,19 +12,18 @@ use crate::antireplay::Window; use crate::applicationlayer::ApplicationLayer; use crate::applicationlayer::RatchetUpdate; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; -use crate::context::ContextInner; +use crate::zssp::{ContextInner, log}; //use crate::context::{log, ContextInner, SessionMap}; -use crate::crypto::aes::{HighThroughputAesGcmPool, LowThroughputAesGcm, AES_GCM_IV_SIZE, AES_256_KEY_SIZE, AES_GCM_TAG_SIZE}; +use crate::crypto::aes::*; use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; use crate::crypto::sha512::{HashSha512, HmacSha512}; use crate::crypto::kyber1024::{Kyber1024PrivateKey, KYBER_PUBLIC_KEY_SIZE, KYBER_CIPHERTEXT_SIZE, KYBER_PLAINTEXT_SIZE}; use crate::indexed_heap::BinaryHeapIndex; -//use crate::indexed_heap::BinaryHeapIndex; -//use crate::fragmentation::DefragBuffer; use crate::proto::*; use crate::ratchet_state::RatchetState; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; use crate::symmetric_state::SymmetricState; +use crate::fragged::Fragged; #[cfg(feature = "logging")] use crate::LogEvent::*; @@ -50,12 +49,23 @@ pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { let c_start = n.len() - 8; (n[c_start - 1], u64::from_be_bytes(n[c_start..].try_into().unwrap())) } -fn create_ratchet_state(hmac: &mut App::HmacHash, noise: &mut SymmetricState, pre_chain_len: u64) -> RatchetState { +fn create_ratchet_state(hmac: &mut App::HmacHash, noise: &SymmetricState, pre_chain_len: u64) -> RatchetState { let mut rk = Zeroizing::new([0u8; HASHLEN]); let mut rf = Zeroizing::new([0u8; HASHLEN]); noise.get_ask(hmac, LABEL_RATCHET_STATE, &mut rk, &mut rf); RatchetState::new(Zeroizing::new(rk[..RATCHET_SIZE].try_into().unwrap()), Zeroizing::new(rf[..RATCHET_SIZE].try_into().unwrap()), pre_chain_len + 1) } +fn get_counter(session: &Session, state: &MutableState) -> Option<(u64, bool)> { + if session.session_has_expired.load(Ordering::Relaxed) { + None + } else { + let c = session.send_counter.fetch_add(1, Ordering::Relaxed); + if c > THREAD_SAFE_COUNTER_HARD_EXPIRE { + session.session_has_expired.store(true, Ordering::SeqCst) + } + Some((c, c > state.key_creation_counter + App::SETTINGS.rekey_after_key_uses)) + } +} /// Corresponds to the Zeta State Machine found in Section 4.1. pub(crate) struct Session { @@ -66,16 +76,19 @@ pub(crate) struct Session { pub was_bob: bool, queue_idx: BinaryHeapIndex, - s_remote: App::PublicKey, + pub(crate) s_remote: App::PublicKey, send_counter: AtomicU64, + session_has_expired: AtomicBool, pub window: Window, - //defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], + pub(crate) defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], + pub(crate) hk_send: App::PrpEnc, + pub(crate) hk_recv: App::PrpDec, state_machine_lock: Mutex<()>, - state: RwLock>, + pub(crate) state: RwLock>, - /// Pre-computed rekeying values. + /// Pre-computed rekeying value. noise_kk_ss: Zeroizing<[u8; P384_ECDH_SHARED_SECRET_SIZE]>, } pub(crate) struct MutableState { @@ -85,7 +98,6 @@ pub(crate) struct MutableState { key_creation_counter: u64, key_index: bool, keys: [DuplexKey; 2], - pub hk_send: Zeroizing<[u8; AES_256_KEY_SIZE]>, resend_timer: i64, timeout_timer: i64, @@ -98,23 +110,40 @@ pub(crate) struct StateB2 { kid_send: NonZeroU32, pub kid_recv: NonZeroU32, pub hk_send: Zeroizing<[u8; AES_256_KEY_SIZE]>, + pub hk_recv: Zeroizing<[u8; AES_256_KEY_SIZE]>, e_secret: App::KeyPair, noise: SymmetricState, - //pub defrag: DefragBuffer, + pub defrag: Mutex>, } -#[derive(Default)] pub(crate) struct DuplexKey { send: Keys, recv: Keys, nk: Option, } +impl Default for DuplexKey { + fn default() -> Self { + Self { send: Default::default(), recv: Default::default(), nk: None } + } +} +impl DuplexKey { + fn replace_nk(&mut self, nk_send: &[u8; HASHLEN], nk_recv: &[u8; HASHLEN]) { + self.nk = Some(App::AeadPool::new((&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap())) + } +} #[derive(Default)] pub(crate) struct Keys { kek: Option>, kid: Option, } +impl Keys { + fn replace_kek(&mut self, kek: &[u8; HASHLEN]) { + // We want to give rust the best chance of implementing this in a way that does + // not leak the key on the stack. + self.kek.get_or_insert(Zeroizing::new([0u8; AES_256_KEY_SIZE])).copy_from_slice(&kek[..AES_256_KEY_SIZE]); + } +} /// Corresponds to the tuple of values the Transition Algorithms send to the remote peer in Section 4.3. //#[derive(Clone)] @@ -126,31 +155,29 @@ pub(crate) struct StateA1 { noise: SymmetricState, e_secret: App::KeyPair, e1_secret: App::Kem, - identity: ArrayVec, - kid_send: u32, - nonce: [u8; AES_GCM_IV_SIZE], - packet: ArrayVec, + identity: ArrayVec, + x1: ArrayVec, } /// Corresponds to the ZKE Automata found in Section 4.1 - Definition 2. pub(crate) enum ZetaAutomata { Null, - A1(StateA1), + A1(Box>), A3 { - identity: ArrayVec, + identity: ArrayVec, kid_send: u32, nonce: [u8; AES_GCM_IV_SIZE], - packet: ArrayVec, + x3: ArrayVec, }, S1, S2, R1 { noise: SymmetricState, e_secret: App::KeyPair, - k1: Vec, + k1: ArrayVec, }, R2 { - k2: Vec, + k2: ArrayVec, }, } @@ -205,6 +232,11 @@ impl MutableState { } } +fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_IV_SIZE]) { + packet[..KID_SIZE].copy_from_slice(&kid_send.to_be_bytes()); + packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); +} + fn create_a1_state( hash: &mut App::Hash, hmac: &mut App::HmacHash, rng: &Mutex, s_remote: &App::PublicKey, @@ -212,12 +244,13 @@ fn create_a1_state( ratchet_state1: &RatchetState, ratchet_state2: Option<&RatchetState>, identity: &[u8], -) -> Option> { +) -> Option>> { // <- s // ... // -> e, es, e1 let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); - let mut x1 = ArrayVec::::new(); + let mut x1 = ArrayVec::::new(); + x1.extend([0u8; HEADER_SIZE]); // Noise process prologue. let kid = kid_recv.get().to_be_bytes(); x1.extend(kid); @@ -245,16 +278,18 @@ fn create_a1_state( let c = u64::from_be_bytes(x1[x1.len() - 8..].try_into().unwrap()); + // Process challenge x1.extend(gen_null_response(rng.lock().unwrap().deref_mut())); - Some(StateA1 { + + set_header(&mut x1, 0, &to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, c)); + + Some(Box::new(StateA1 { noise, e_secret, e1_secret, identity: identity.try_into().unwrap(), - kid_send: 0, - nonce: to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, c), - packet: x1, - }) + x1, + })) } /// Corresponds to Transition Algorithm 1 found in Section 4.3. pub(crate) fn trans_to_a1( @@ -263,7 +298,7 @@ pub(crate) fn trans_to_a1( s_remote: App::PublicKey, session_data: App::SessionData, identity: &[u8], - //send: impl FnOnce(&Packet), + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result>, OpenError> { let (ratchet_state1, ratchet_state2) = app .restore_by_identity(&s_remote, &session_data) @@ -276,12 +311,18 @@ pub(crate) fn trans_to_a1( let hash = &mut App::Hash::new(); let hmac = &mut App::HmacHash::new(); let a1 = create_a1_state(hash, hmac, &ctx.rng, &s_remote, kid_recv, &ratchet_state1, ratchet_state2.as_ref(), identity).ok_or(OpenError::InvalidPublicKey)?; - let packet = a1.packet.clone(); + + let mut noise_kk_ss = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); + if !ctx.s_secret.agree(&s_remote, &mut noise_kk_ss) { + return Err(OpenError::InvalidPublicKey) + } let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); let mut hk_send = Zeroizing::new([0u8; HASHLEN]); a1.noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); + let mut x1 = a1.x1.clone(); + let current_time = app.time(); let queue_idx = session_queue.reserve_index(); let mut session = Arc::new(Session { @@ -290,20 +331,23 @@ pub(crate) fn trans_to_a1( queue_idx, s_remote, send_counter: AtomicU64::new(0), + session_has_expired: AtomicBool::new(false), window: Window::new(), state_machine_lock: Mutex::new(()), state: RwLock::new(MutableState { - ratchet_state1, - ratchet_state2, + ratchet_state1: ratchet_state1.clone(), + ratchet_state2: ratchet_state2.clone(), key_creation_counter: 0, key_index: true, keys: [DuplexKey::default(), DuplexKey::default()], - hk_send: Zeroizing::new(hk_send[..AES_256_KEY_SIZE].try_into().unwrap()), resend_timer: current_time + App::SETTINGS.resend_time as i64, timeout_timer: current_time + App::SETTINGS.initial_offer_timeout as i64, beta: ZetaAutomata::A1(a1), }), - noise_kk_ss: Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]), + noise_kk_ss: noise_kk_ss.clone(), + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + hk_send: App::PrpEnc::new((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()), + hk_recv: App::PrpDec::new((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()), }); let mut state = session.state.write().unwrap(); state.key_mut(true).recv.kid = Some(kid_recv); @@ -315,37 +359,46 @@ pub(crate) fn trans_to_a1( Reverse(state.next_timer()), ); - //send(&packet); + send(&mut x1, None); Ok(session) } /// Corresponds to Algorithm 13 found in Section 5. -//pub(crate) fn respond_to_challenge(zeta: &mut Zeta, rng: &Mutex, challenge: &[u8; CHALLENGE_SIZE]) { -// if let ZetaAutomata::A1(StateA1 { packet: Packet(_, _, x1), .. }) = &mut zeta.beta { -// let response_start = x1.len() - CHALLENGE_SIZE; -// respond_to_challenge_in_place::( -// rng.lock().unwrap().deref_mut(), -// challenge, -// (&mut x1[response_start..]).try_into().unwrap(), -// ); -// } -//} +pub(crate) fn respond_to_challenge(session: &mut Session, rng: &Mutex, challenge: &[u8; CHALLENGE_SIZE]) { + let mut state = session.state.write().unwrap(); + if let ZetaAutomata::A1(a1) = &mut state.beta { + let response_start = a1.x1.len() - CHALLENGE_SIZE; + respond_to_challenge_in_place::( + rng.lock().unwrap().deref_mut(), + challenge, + (&mut a1.x1[response_start..]).try_into().unwrap(), + ); + } +} /// Corresponds to Transition Algorithm 2 found in Section 4.3. pub(crate) fn received_x1_trans( app: &App, ctx: &ContextInner, + remote_address: &impl std::hash::Hash, n: [u8; AES_GCM_IV_SIZE], - mut x1: Vec, - //send: impl FnOnce(&Packet, &[u8; AES_256_KEY_SIZE]), + x1: &mut [u8], + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { use FaultType::*; // <- s // ... // -> e, es, e1 // <- e, ee, ekem1, psk - if !(HANDSHAKE_HELLO_MIN_SIZE..=HANDSHAKE_HELLO_MAX_SIZE).contains(&x1.len()) { + if !(HANDSHAKE_HELLO_CHALLENGE_MIN_SIZE..=HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE).contains(&x1.len()) { return Err(byzantine_fault!(InvalidPacket, true)); } + + if let Err(challenge) = ctx.challenge.process_hello::(remote_address, (&x1[x1.len() - CHALLENGE_SIZE..]).try_into().unwrap()) { + /// + + return Err(byzantine_fault!(FailedAuth, false)); + } + if &n[AES_GCM_IV_SIZE - 8..] != &x1[x1.len() - 8..] { return Err(byzantine_fault!(FailedAuth, true)); } @@ -405,7 +458,8 @@ pub(crate) fn received_x1_trans( let mut hk_send = Zeroizing::new([0u8; HASHLEN]); noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); - let mut x2 = ArrayVec::new(); + let mut x2 = ArrayVec::::new(); + x2.extend([0u8; HEADER_SIZE]); // Process message pattern 2 e token. let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut x2); // Process message pattern 2 ee token. @@ -435,21 +489,24 @@ pub(crate) fn received_x1_trans( c[7] = x2[i - 1]; let c = u64::from_be_bytes(c); - /// - //ctx.b2_map.lock().unwrap().insert( - // kid_recv, - // StateB2 { - // ratchet_state, - // kid_send, - // kid_recv, - // hk_send: hk_send.clone(), - // e_secret, - // noise, - // defrag: DefragBuffer::new(Some(hk_recv)), - // }, - //); + set_header(&mut x2, kid_send.get(), &to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, c)); - //send(&Packet(kid_send.get(), to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, c), x2), &hk_send); + ctx.unassociated_handshake_states.insert( + kid_recv, + Arc::new(StateB2 { + ratchet_state, + kid_send, + kid_recv, + hk_send: Zeroizing::new(hk_send[..AES_256_KEY_SIZE].try_into().unwrap()), + hk_recv: Zeroizing::new(hk_recv[..AES_256_KEY_SIZE].try_into().unwrap()), + e_secret, + noise, + defrag: Mutex::new(Fragged::new()), + }), + app.time() + ); + + send(&mut x2, Some(&App::PrpEnc::new(&hk_send[..AES_256_KEY_SIZE].try_into().unwrap()))); Ok(()) } /// Corresponds to Transition Algorithm 3 found in Section 4.3. @@ -460,7 +517,7 @@ pub(crate) fn received_x2_trans( kid: NonZeroU32, n: [u8; AES_GCM_IV_SIZE], mut x2: &[u8], - //send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { use FaultType::*; // <- e, ee, ekem1, psk @@ -481,14 +538,14 @@ pub(crate) fn received_x2_trans( if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD || &n[AES_GCM_IV_SIZE - 3..] != &x2[x2.len() - 3..] { return Err(byzantine_fault!(FailedAuth, true)); } - let result = (|| { - if let ZetaAutomata::A1(StateA1 { noise, e_secret, e1_secret, identity, .. }) = &state.beta { - let mut noise = noise.clone(); + let mut result = (|| { + if let ZetaAutomata::A1(a1) = &state.beta { + let mut noise = a1.noise.clone(); let mut i = 0; // Process message pattern 2 e token. let e_remote = noise.read_e(hash, hmac, &mut i, &x2).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. - noise.mix_dh(hmac, e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(hmac, &a1.e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ekem1 token. let j = i + KYBER_CIPHERTEXT_SIZE; let k = j + AES_GCM_TAG_SIZE; @@ -497,7 +554,7 @@ pub(crate) fn received_x2_trans( return Err(byzantine_fault!(FailedAuth, true)); } let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); - if !e1_secret.decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) { + if !a1.e1_secret.decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) { return Err(byzantine_fault!(FailedAuth, true)); } noise.mix_key(hmac, ekem1_secret.as_ref()); @@ -514,7 +571,7 @@ pub(crate) fn received_x2_trans( let payload: [u8; KID_SIZE] = x2[i..j].try_into().unwrap(); let tag = x2[j..k].try_into().unwrap(); // Check for which ratchet key Bob wants to use. - let test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState)> { + let mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState)> { let mut noise = noise.clone(); let mut payload = payload.clone(); // Process message pattern 2 psk token. @@ -547,8 +604,9 @@ pub(crate) fn received_x2_trans( } let (kid_send, mut noise) = result.ok_or(byzantine_fault!(FailedAuth, true))?; - let mut x3 = ArrayVec::new(); + let mut x3 = ArrayVec::::new(); + x3.extend([0u8; HEADER_SIZE]); // Process message pattern 3 s token. let i = x3.len(); x3.extend(ctx.s_secret.public_key_bytes()); @@ -557,7 +615,7 @@ pub(crate) fn received_x2_trans( noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 3 payload. let i = x3.len(); - x3.try_extend_from_slice(identity).unwrap(); + x3.try_extend_from_slice(&a1.identity).unwrap(); x3.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), &mut x3[i..])); let new_ratchet_state = create_ratchet_state(hmac, &mut noise, chain_len); @@ -582,10 +640,10 @@ pub(crate) fn received_x2_trans( return Err(ReceiveError::RatchetIoError(e)); } - let kek_recv = Zeroizing::new([0u8; HASHLEN]); - let kek_send = Zeroizing::new([0u8; HASHLEN]); - let nk_recv = Zeroizing::new([0u8; HASHLEN]); - let nk_send = Zeroizing::new([0u8; HASHLEN]); + let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); + let mut kek_send = Zeroizing::new([0u8; HASHLEN]); + let mut nk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut nk_send = Zeroizing::new([0u8; HASHLEN]); noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_recv, &mut kek_send); noise.split(hmac, &mut nk_recv, &mut nk_send); let nonce = to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0); @@ -595,37 +653,37 @@ pub(crate) fn received_x2_trans( let mut state = session.state.write().unwrap(); state.key_mut(true).send.kid = Some(kid_send); - state.key_mut(true).send.kek = Some(Zeroizing::new(kek_send[..AES_256_KEY_SIZE].try_into().unwrap())); - state.key_mut(true).recv.kek = Some(Zeroizing::new(kek_recv[..AES_256_KEY_SIZE].try_into().unwrap())); - state.key_mut(true).nk = Some(App::AeadPool::new((&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap())); + state.key_mut(true).send.replace_kek(&kek_send); + state.key_mut(true).recv.replace_kek(&kek_recv); + state.key_mut(true).replace_nk(&nk_send, &nk_recv); state.ratchet_state2 = Some(state.ratchet_state1.clone()); state.ratchet_state1 = new_ratchet_state.clone(); let current_time = app.time(); state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); state.resend_timer = current_time + App::SETTINGS.resend_time as i64; state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; - state.beta = ZetaAutomata::A3 { identity: identity.clone(), packet: x3, kid_send: kid_send.get(), nonce }; + state.beta = ZetaAutomata::A3 { identity: a1.identity.clone(), x3: x3.clone(), kid_send: kid_send.get(), nonce }; - Ok(()) + Ok(x3) } else { Err(byzantine_fault!(FailedAuth, true)) } })(); - match &result { - Err(ReceiveError::ByzantineFault { .. }) => timeout_trans(state, session, app, ctx, app.time(), send), - Ok(packet) => send(packet, Some(&state.hk_send)), + match &mut result { + Err(ReceiveError::ByzantineFault { .. }) => process_timers(app, ctx, session, app.time(), true, false, send), + Ok(mut packet) => send(&mut packet, Some(&session.hk_send)), _ => {} } result.map(|_| ()) } /// Corresponds to Transition Algorithm 4 found in Section 4.3. pub(crate) fn received_x3_trans( - zeta: StateB2, app: &App, ctx: &Arc>, + zeta: StateB2, kid: NonZeroU32, mut x3: Vec, - send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result>, ReceiveError> { use FaultType::*; // -> s, se @@ -635,6 +693,8 @@ pub(crate) fn received_x3_trans( if kid != zeta.kid_recv { return Err(byzantine_fault!(UnknownLocalKeyId, true)); } + let mut hash = &mut App::Hash::new(); + let mut hmac = &mut App::HmacHash::new(); let mut noise = zeta.noise.clone(); let mut i = 0; @@ -642,39 +702,42 @@ pub(crate) fn received_x3_trans( let j = i + P384_PUBLIC_KEY_SIZE; let k = j + AES_GCM_TAG_SIZE; let tag = x3[j..k].try_into().unwrap(); - if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..j], tag) { + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let s_remote = App::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()).ok_or(byzantine_fault!(FailedAuth, true))?; i = k; // Process message pattern 3 se token. - noise.mix_dh(&zeta.e_secret, &s_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(hmac, &zeta.e_secret, &s_remote).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 3 payload. let k = x3.len(); let j = k - AES_GCM_TAG_SIZE; let tag = x3[j..k].try_into().unwrap(); - if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), &mut x3[i..j], tag) { + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), &mut x3[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let identity_start = i; let identity_end = j; - let (kek_send, kek_recv) = noise.get_ask(LABEL_KEX_KEY); - let c = INIT_COUNTER; + let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); + let mut kek_send = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_send, &mut kek_recv); + let c = 0; let action = app.check_accept_session(&s_remote, &x3[identity_start..identity_end]); let responder_disallows_downgrade = action.responder_disallows_downgrade; let responder_silently_rejects = action.responder_silently_rejects; let session_data = action.session_data; let create_reject = || { - let mut d = Vec::::new(); - let n = to_nonce(PACKET_TYPE_SESSION_REJECTED, c); - let tag = App::Aead::encrypt_in_place(&kek_send, n, None, &mut []); - d.extend(&tag); // We just used a counter with this key, but we are not storing // the fact we used it in memory. This is currently ok because the // handshake is being dropped, so nonce reuse can't happen. - Packet(zeta.kid_send.get(), n, d) + let mut d = ArrayVec::::new(); + d.extend([0u8; HEADER_SIZE]); + let nonce = to_nonce(PACKET_TYPE_SESSION_REJECTED, c); + d.extend(App::Aead::encrypt_in_place((&kek_send[..AES_256_KEY_SIZE]).try_into().unwrap(), &nonce, &[], &mut [])); + set_header(&mut d, zeta.kid_send.get(), &nonce); + d }; if let Some(session_data) = session_data { let result = app.restore_by_identity(&s_remote, &session_data); @@ -685,15 +748,22 @@ pub(crate) fn received_x3_trans( // TODO: add some kind of warning callback or signal. } else { if !responder_silently_rejects { - send(&create_reject(), Some(&zeta.hk_send)) + send(&mut create_reject(), Some(&App::PrpEnc::new(&zeta.hk_send))) } return Err(byzantine_fault!(FailedAuth, true)); } } - let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); + let mut noise_kk_ss = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); + if !ctx.s_secret.agree(&s_remote, &mut noise_kk_ss) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let mut nk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut nk_send = Zeroizing::new([0u8; HASHLEN]); + noise.split(hmac, &mut nk_send, &mut nk_recv); + + let new_ratchet_state = create_ratchet_state(hmac, &mut noise, zeta.ratchet_state.chain_len); // We must make sure the ratchet key is saved before we transition. - let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state.chain_len + 1); let result = app.save_ratchet_state( &s_remote, &session_data, @@ -709,77 +779,89 @@ pub(crate) fn received_x3_trans( return Err(ReceiveError::RatchetIoError(e)); } - let mut c1 = Vec::new(); - let n = to_nonce(PACKET_TYPE_KEY_CONFIRM, c); - let tag = App::Aead::encrypt_in_place(&kek_send, n, None, &mut []); - c1.extend(&tag); + let (session, current_time) = { + let mut session_map = ctx.session_map.write().unwrap(); + use std::collections::hash_map::Entry::*; + let entry = match session_map.entry(zeta.kid_recv) { + // We could have issued the kid that we initially offered Alice to someone else + // before Alice was able to respond. It is unlikely but possible. + Occupied(_) => return Err(byzantine_fault!(OutOfSequence, false)), + Vacant(entry) => entry, + }; + let mut session_queue = ctx.session_queue.lock().unwrap(); + let queue_idx = session_queue.reserve_index(); + let current_time = app.time(); + let session = Arc::new(Session { + session_data, + was_bob: true, + s_remote, + send_counter: AtomicU64::new(c + 1), + session_has_expired: AtomicBool::new(false), + state_machine_lock: Mutex::new(()), + state: RwLock::new(MutableState { + ratchet_state1: new_ratchet_state.clone(), + ratchet_state2: None, + key_creation_counter: c + 1, + key_index: false, + keys: [DuplexKey::default(), DuplexKey::default()], + resend_timer: current_time + App::SETTINGS.resend_time as i64, + timeout_timer: current_time + App::SETTINGS.rekey_timeout as i64, + beta: ZetaAutomata::S1, + }), + window: Window::new(), + queue_idx, + noise_kk_ss: noise_kk_ss.clone(), + defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), + hk_send: App::PrpEnc::new(&zeta.hk_send), + hk_recv: App::PrpDec::new(&zeta.hk_recv), + }); - let (nk1, nk2) = noise.split(); - let keys = DuplexKey { - send: Keys { kek: Some(kek_send), nk: Some(nk1), kid: Some(zeta.kid_send) }, - recv: Keys { kek: Some(kek_recv), nk: Some(nk2), kid: Some(zeta.kid_recv) }, + let mut state = session.state.write().unwrap(); + state.key_mut(false).replace_nk(&nk_send, &nk_recv); + state.key_mut(false).recv.kid = Some(zeta.kid_recv); + state.key_mut(false).recv.replace_kek(&kek_recv); + state.key_mut(false).send.kid = Some(zeta.kid_send); + state.key_mut(false).send.replace_kek(&kek_send); + + session_queue.push_reserved(queue_idx, Arc::downgrade(&session), Reverse(state.next_timer())); + entry.insert(Arc::downgrade(&session)); + (session, current_time) }; - let current_time = app.time(); + process_timers(app, ctx, &session, current_time, false, true, send); - let mut session_map = ctx.session_map.lock().unwrap(); - use std::collections::hash_map::Entry::*; - let entry = match session_map.entry(zeta.kid_recv) { - // We could have issued the kid that we initially offered Alice to someone else - // before Alice was able to respond. It is unlikely but possible. - Occupied(_) => return Err(byzantine_fault!(OutOfSequence, false)), - Vacant(entry) => entry, - }; - let session = Arc::new(Session(Mutex::new(Zeta { - ctx: Arc::downgrade(ctx), - session_data, - was_bob: true, - s_remote, - send_counter: INIT_COUNTER + 1, - key_creation_counter: INIT_COUNTER + 1, - key_index: false, - keys: [keys, DuplexKey::default()], - ratchet_state1: new_ratchet_state, - ratchet_state2: None, - hk_send: zeta.hk_send.clone(), - resend_timer: current_time + App::SETTINGS.resend_time as i64, - timeout_timer: current_time + App::SETTINGS.rekey_timeout as i64, - beta: ZetaAutomata::S1, - counter_antireplay_window: std::array::from_fn(|_| 0), - defrag: zeta.defrag, - }))); - entry.insert(Arc::downgrade(&session)); - ctx.sessions.lock().unwrap().insert(Arc::as_ptr(&session), Arc::downgrade(&session)); - - send(&Packet(zeta.kid_send.get(), n, c1), Some(&zeta.hk_send)); Ok(session) } Err(e) => Err(ReceiveError::RatchetIoError(e)), } } else { if !responder_silently_rejects { - send(&create_reject(), Some(&zeta.hk_send)) + //send(&create_reject(), Some(&zeta.hk_send)) } Err(byzantine_fault!(FailedAuth, true)) } } /// Corresponds to Transition Algorithm 5 found in Section 4.3. pub(crate) fn received_c1_trans( - zeta: &mut Zeta, app: &App, - rng: &Mutex, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], - c1: Vec, - send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), + n: &[u8; AES_GCM_IV_SIZE], + c1: &[u8], + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result> { use FaultType::*; if c1.len() != KEY_CONFIRMATION_SIZE { return Err(byzantine_fault!(InvalidPacket, true)); } - let is_other = if Some(kid) == zeta.key_ref(true).recv.kid { + + let kex_lock = session.state_machine_lock.lock().unwrap(); + let mut state = session.state.read().unwrap(); + + let is_other = if Some(kid) == state.key_ref(true).recv.kid { true - } else if Some(kid) == zeta.key_ref(false).recv.kid { + } else if Some(kid) == state.key_ref(false).recv.kid { false } else { // Some key confirmation may have arrived extremely delayed. @@ -787,28 +869,28 @@ pub(crate) fn received_c1_trans( return Err(byzantine_fault!(OutOfSequence, false)); }; - let specified_key = zeta.key_ref(is_other).recv.kek.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; + let specified_key = state.key_ref(is_other).recv.kek.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; let tag = c1[..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(specified_key, n, None, &mut [], tag) { + if !App::Aead::decrypt_in_place(specified_key, n, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let (_, c) = from_nonce(&n); - if !zeta.update_counter_window(c) { + let (_, c) = from_nonce(n); + if !session.window.update(c) { return Err(byzantine_fault!(ExpiredCounter, true)); } - let just_establised = is_other && matches!(&zeta.beta, ZetaAutomata::A3 { .. }); + let just_establised = is_other && matches!(&state.beta, ZetaAutomata::A3 { .. }); if is_other { - if let ZetaAutomata::A3 { .. } | ZetaAutomata::R2 { .. } = &zeta.beta { - if zeta.ratchet_state2.is_some() { + if let ZetaAutomata::A3 { .. } | ZetaAutomata::R2 { .. } = &state.beta { + if state.ratchet_state2.is_some() { let result = app.save_ratchet_state( - &zeta.s_remote, - &zeta.session_data, + &session.s_remote, + &session.session_data, RatchetUpdate { - state1: &zeta.ratchet_state1, + state1: &state.ratchet_state1, state2: None, state1_was_just_added: false, - state_deleted1: zeta.ratchet_state2.as_ref(), + state_deleted1: state.ratchet_state2.as_ref(), state_deleted2: None, }, ); @@ -816,127 +898,261 @@ pub(crate) fn received_c1_trans( return Err(ReceiveError::RatchetIoError(e)); } } + drop(state); + let mut state_mut = session.state.write().unwrap(); - zeta.ratchet_state2 = None; - zeta.key_index ^= true; - zeta.timeout_timer = app.time() + state_mut.ratchet_state2 = None; + state_mut.key_index ^= true; + state_mut.timeout_timer = app.time() + App::SETTINGS .rekey_after_time - .saturating_sub(rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; - zeta.resend_timer = i64::MAX; - zeta.beta = ZetaAutomata::S2; + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + state_mut.resend_timer = i64::MAX; + state_mut.beta = ZetaAutomata::S2; + drop(state); + state = session.state.read().unwrap(); } } - let mut c2 = Vec::new(); - let c = zeta.send_counter; - zeta.send_counter += 1; - let n = to_nonce(PACKET_TYPE_ACK, c); - let latest_confirmed_key = zeta.key_ref(false).send.kek.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; - let tag = App::Aead::encrypt_in_place(latest_confirmed_key, n, None, &mut []); - c2.extend(&tag); + let mut c2 = ArrayVec::::new(); + c2.extend([0u8; HEADER_SIZE]); + let (c, should_rekey) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; + let nonce = to_nonce(PACKET_TYPE_ACK, c); + let latest_confirmed_key = state.key_ref(false).send.kek.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; + c2.extend(App::Aead::encrypt_in_place(latest_confirmed_key, &nonce, &[], &mut [])); + let kid_send = state.key_ref(false).send.kid.ok_or(byzantine_fault!(OutOfSequence, false))?; + set_header(&mut c2, kid_send.get(), &nonce); - send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, c2), Some(&zeta.hk_send)); + send(&mut c2, Some(&session.hk_send)); Ok(just_establised) } /// Corresponds to the trivial Transition Algorithm described for processing C_2 packets found in /// Section 4.3. pub(crate) fn received_c2_trans( - zeta: &mut Zeta, app: &App, - rng: &Mutex, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], - c2: Vec, + n: &[u8; AES_GCM_IV_SIZE], + c2: &[u8], ) -> Result<(), ReceiveError> { use FaultType::*; if c2.len() != ACKNOWLEDGEMENT_SIZE { return Err(byzantine_fault!(InvalidPacket, true)); } - if Some(kid) != zeta.key_ref(false).recv.kid { + + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + + if Some(kid) != state.key_ref(false).recv.kid { // Some acknowledgement may have arrived extremely delayed. return Err(byzantine_fault!(UnknownLocalKeyId, false)); } - if !matches!(&zeta.beta, ZetaAutomata::S1) { + if !matches!(&state.beta, ZetaAutomata::S1) { // Some acknowledgement may have arrived extremely delayed. return Err(byzantine_fault!(OutOfSequence, false)); } let tag = c2[..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(zeta.key_ref(false).recv.kek.as_ref().unwrap(), n, None, &mut [], tag) { + if !App::Aead::decrypt_in_place(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let (_, c) = from_nonce(&n); - if !zeta.update_counter_window(c) { + let (_, c) = from_nonce(n); + if !session.window.update(c) { return Err(byzantine_fault!(ExpiredCounter, true)); } + drop(state); + let mut state = session.state.write().unwrap(); - zeta.timeout_timer = app.time() + state.timeout_timer = app.time() + App::SETTINGS .rekey_after_time - .saturating_sub(rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; - zeta.resend_timer = i64::MAX; - zeta.beta = ZetaAutomata::S2; + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + state.resend_timer = i64::MAX; + state.beta = ZetaAutomata::S2; Ok(()) } /// Corresponds to the trivial Transition Algorithm described for processing D packets found in /// Section 4.3. pub(crate) fn received_d_trans( - zeta: &mut Zeta, + app: &App, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], - d: Vec, + n: &[u8; AES_GCM_IV_SIZE], + d: &[u8], ) -> Result<(), ReceiveError> { use FaultType::*; if d.len() != SESSION_REJECTED_SIZE { return Err(byzantine_fault!(InvalidPacket, true)); } - if Some(kid) != zeta.key_ref(true).recv.kid || !matches!(&zeta.beta, ZetaAutomata::A3 { .. }) { + + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + + if Some(kid) != state.key_ref(true).recv.kid || !matches!(&state.beta, ZetaAutomata::A3 { .. }) { return Err(byzantine_fault!(OutOfSequence, true)); } let tag = d[..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(zeta.key_ref(true).recv.kek.as_ref().unwrap(), n, None, &mut [], tag) { + if !App::Aead::decrypt_in_place(state.key_ref(true).recv.kek.as_ref().unwrap(), n, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let (_, c) = from_nonce(&n); - if !zeta.update_counter_window(c) { + let (_, c) = from_nonce(n); + if !session.window.update(c) { return Err(byzantine_fault!(ExpiredCounter, true)); } - - zeta.expire(); + /// + //zeta.expire(); Ok(()) } /// Corresponds to the timer rules of the Zeta State Machine found in Section 4.1 - Definition 3. -pub(crate) fn service( - zeta: &mut Zeta, - session: &Arc>, - ctx: &Arc>, +pub(crate) fn process_timers( app: &App, + ctx: &Arc>, + session: &Arc>, current_time: i64, - send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), + force_timeout: bool, + force_resend: bool, + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) { - if zeta.timeout_timer <= current_time { - timeout_trans(zeta, session, app, ctx, current_time, send); - } else if zeta.resend_timer <= current_time { - // Corresponds to the resend timer rules found in Section 4.1 - Definition 3. - zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; + let kex_lock = session.state_machine_lock.lock().unwrap(); + let mut state = session.state.read().unwrap(); + if force_timeout || state.timeout_timer <= current_time { + // Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. + match &state.beta { + ZetaAutomata::Null => {} + ZetaAutomata::A1(_) | ZetaAutomata::A3 { .. } => { + let identity = match &state.beta { + ZetaAutomata::A1(a1) => &a1.identity, + ZetaAutomata::A3 { identity, .. } => identity, + _ => unreachable!(), + }; + if matches!(&state.beta, ZetaAutomata::A1(_)) { + log!(app, TimeoutX1(session)); + } else { + log!(app, TimeoutX3(session)); + } + let new_kid_recv = remap(ctx, session, &state); - let (p, mut control_payload) = match &zeta.beta { - ZetaAutomata::Null => return, - ZetaAutomata::A1(StateA1 { packet, .. }) => { - log!(app, ResentX1(session)); - return send(packet, None); + let mut hash = &mut App::Hash::new(); + let mut hmac = &mut App::HmacHash::new(); + if let Some(a1) = create_a1_state( + hash, + hmac, + &ctx.rng, + &session.s_remote, + new_kid_recv, + &state.ratchet_state1, + state.ratchet_state2.as_ref(), + identity, + ) { + let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut hk_send = Zeroizing::new([0u8; HASHLEN]); + a1.noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); + let mut x1 = a1.x1.clone(); + + drop(state); + { + let mut state = session.state.write().unwrap(); + session.hk_recv.reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); + session.hk_send.reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); + *state.key_mut(true) = DuplexKey::default(); + state.key_mut(true).recv.kid = Some(new_kid_recv); + state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; + state.beta = ZetaAutomata::A1(a1); + } + + send(&mut x1, None); + } else { + //zeta.expire(); + } } - ZetaAutomata::A3 { packet, .. } => { + ZetaAutomata::S2 => { + // Corresponds to Transition Algorithm 6 found in Section 4.3. + log!(app, StartedRekeyingSentK1(session)); + let new_kid_recv = remap(ctx, session, &state); + // -> s + // <- s + // ... + // -> psk, e, es, ss + let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); + let mut hash = &mut App::Hash::new(); + let mut hmac = &mut App::HmacHash::new(); + let mut k1 = ArrayVec::::new(); + k1.extend([0u8; HEADER_SIZE]); + // Noise process prologue. + noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); + noise.mix_hash(hash, &session.s_remote.to_bytes()); + // Process message pattern 1 psk0 token. + noise.mix_key_and_hash(hash, hmac, state.ratchet_state1.key.as_ref()); + // Process message pattern 1 e token. + let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut k1); + // Process message pattern 1 es token. + if noise.mix_dh(hmac, &e_secret, &session.s_remote).is_none() { + //zeta.expire(); + return; + } + // Process message pattern 1 ss token. + noise.mix_key(hmac, session.noise_kk_ss.as_ref()); + // Process message pattern 1 payload. + let i = k1.len(); + k1.extend(new_kid_recv.get().to_be_bytes()); + k1.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..])); + + drop(state); + { + let mut state = session.state.write().unwrap(); + state.key_mut(true).recv.kid = Some(new_kid_recv); + state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; + state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; + } + let state = session.state.read().unwrap(); + + if let Some((c, should_rekey)) = get_counter(session, &state) { + let nonce = to_nonce(PACKET_TYPE_REKEY_INIT, c); + k1.extend(App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k1)); + set_header(&mut k1, state.key_ref(false).send.kid.unwrap().get(), &nonce); + + send(&mut k1, Some(&session.hk_send)); + } + } + ZetaAutomata::S1 { .. } => { + log!(app, TimeoutKeyConfirm(session)); + //zeta.expire(); + } + ZetaAutomata::R1 { .. } => { + log!(app, TimeoutK1(session)); + //zeta.expire(); + } + ZetaAutomata::R2 { .. } => { + log!(app, TimeoutK2(session)); + //zeta.expire(); + } + } + } else if force_resend || state.resend_timer <= current_time { + // Corresponds to the resend timer rules found in Section 4.1 - Definition 3. + state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + + let (packet_type, mut control_payload) = match &state.beta { + ZetaAutomata::Null => return, + ZetaAutomata::A1(a1) => { + log!(app, ResentX1(session)); + return send(&mut a1.x1.clone(), None); + } + ZetaAutomata::A3 { x3, .. } => { log!(app, ResentX3(session)); - return send(packet, Some(&zeta.hk_send)); + return send(&mut x3.clone(), Some(&session.hk_send)); } ZetaAutomata::S1 => { log!(app, ResentKeyConfirm(session)); - (PACKET_TYPE_KEY_CONFIRM, Vec::new()) + let mut c1 = ArrayVec::new(); + c1.extend([0u8; HEADER_SIZE]); + (PACKET_TYPE_KEY_CONFIRM, c1) } ZetaAutomata::S2 => return, ZetaAutomata::R1 { k1, .. } => { @@ -948,142 +1164,37 @@ pub(crate) fn service( (PACKET_TYPE_REKEY_COMPLETE, k2.clone()) } }; - let c = zeta.send_counter; - zeta.send_counter += 1; - let n = to_nonce(p, c); - let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut control_payload); - control_payload.extend(&tag); - send( - &Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, control_payload), - Some(&zeta.hk_send), - ); + if let Some((c, should_rekey)) = get_counter(session, &state) { + let nonce = to_nonce(packet_type, c); + let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut control_payload); + control_payload.extend(tag); + set_header(&mut control_payload, state.key_ref(false).send.kid.unwrap().get(), &nonce); + + send(&mut control_payload, Some(&session.hk_send)); + } } } -fn remap(session: &Arc>, zeta: &Zeta, rng: &Mutex, session_map: &SessionMap) -> NonZeroU32 { - let mut session_map = session_map.lock().unwrap(); - let weak = if let Some(Some(weak)) = zeta.key_ref(true).recv.kid.as_ref().map(|kid| session_map.remove(kid)) { +fn remap(ctx: &Arc>, session: &Arc>, state: &MutableState) -> NonZeroU32 { + let mut session_map = ctx.session_map.write().unwrap(); + let weak = if let Some(Some(weak)) = state.key_ref(true).recv.kid.as_ref().map(|kid| session_map.remove(kid)) { weak } else { Arc::downgrade(&session) }; - let new_kid_recv = gen_kid(session_map.deref(), rng.lock().unwrap().deref_mut()); + let new_kid_recv = gen_kid(session_map.deref(), ctx.rng.lock().unwrap().deref_mut()); session_map.insert(new_kid_recv, weak); new_kid_recv } -/// Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. -fn timeout_trans( - zeta: &mut Zeta, - session: &Arc>, - app: &App, - ctx: &Arc>, - current_time: i64, - send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), -) { - match &zeta.beta { - ZetaAutomata::Null => {} - ZetaAutomata::A1(StateA1 { identity, .. }) | ZetaAutomata::A3 { identity, .. } => { - if matches!(&zeta.beta, ZetaAutomata::A1(_)) { - log!(app, TimeoutX1(session)); - } else { - log!(app, TimeoutX3(session)); - } - let new_kid_recv = remap(session, &zeta, &ctx.rng, &ctx.session_map); - - if let Some(a1) = create_a1_state( - &ctx.rng, - &zeta.s_remote, - new_kid_recv, - &zeta.ratchet_state1, - zeta.ratchet_state2.as_ref(), - identity.clone(), - ) { - let (hk_recv, hk_send) = a1.noise.get_ask(LABEL_HEADER_KEY); - let packet = a1.packet.clone(); - - zeta.hk_send = hk_send; - *zeta.key_mut(true) = DuplexKey::default(); - zeta.key_mut(true).recv.kid = Some(new_kid_recv); - zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; - zeta.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; - zeta.beta = ZetaAutomata::A1(a1); - zeta.defrag = DefragBuffer::new(Some(hk_recv)); - - send(&packet, None); - } else { - zeta.expire(); - } - } - ZetaAutomata::S2 => { - // Corresponds to Transition Algorithm 6 found in Section 4.3. - log!(app, StartedRekeyingSentK1(session)); - let new_kid_recv = remap(session, &zeta, &ctx.rng, &ctx.session_map); - // -> s - // <- s - // ... - // -> psk, e, es, ss - let mut k1 = Vec::new(); - let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); - // Noise process prologue. - noise.mix_hash(&ctx.s_secret.public_key_bytes()); - noise.mix_hash(&zeta.s_remote.to_bytes()); - // Process message pattern 1 psk0 token. - noise.mix_key_and_hash(zeta.ratchet_state1.key.as_ref()); - // Process message pattern 1 e token. - let e_secret = noise.write_e(&ctx.rng, &mut k1); - // Process message pattern 1 es token. - if noise.mix_dh(&e_secret, &zeta.s_remote).is_none() { - zeta.expire(); - return; - } - // Process message pattern 1 ss token. - if noise.mix_dh(&ctx.s_secret, &zeta.s_remote).is_none() { - zeta.expire(); - return; - } - // Process message pattern 1 payload. - let i = k1.len(); - k1.extend(&new_kid_recv.get().to_be_bytes()); - noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_INIT, 0), i, &mut k1); - - zeta.key_mut(true).recv.kid = Some(new_kid_recv); - zeta.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; - zeta.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; - - let c = zeta.send_counter; - zeta.send_counter += 1; - let n = to_nonce(PACKET_TYPE_REKEY_INIT, c); - let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut k1); - k1.extend(&tag); - - send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k1), Some(&zeta.hk_send)); - } - ZetaAutomata::S1 { .. } => { - log!(app, TimeoutKeyConfirm(session)); - zeta.expire(); - } - ZetaAutomata::R1 { .. } => { - log!(app, TimeoutK1(session)); - zeta.expire(); - } - ZetaAutomata::R2 { .. } => { - log!(app, TimeoutK2(session)); - zeta.expire(); - } - } -} /// Corresponds to Transition Algorithm 7 found in Section 4.3. pub(crate) fn received_k1_trans( - zeta: &mut Zeta, - session: &Arc>, app: &App, - rng: &Mutex, - session_map: &SessionMap, + ctx: &Arc>, + session: &Arc>, s_secret: &App::KeyPair, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], - mut k1: Vec, - send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), + n: &[u8; AES_GCM_IV_SIZE], + k1: &mut [u8], + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { use FaultType::*; // -> s @@ -1094,13 +1205,17 @@ pub(crate) fn received_k1_trans( if k1.len() != REKEY_SIZE { return Err(byzantine_fault!(InvalidPacket, true)); } - if Some(kid) != zeta.key_ref(false).recv.kid { + + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + + if Some(kid) != state.key_ref(false).recv.kid { // Some rekey packet may have arrived extremely delayed. return Err(byzantine_fault!(UnknownLocalKeyId, false)); } - let should_rekey_as_bob = match &zeta.beta { + let should_rekey_as_bob = match &state.beta { ZetaAutomata::S2 { .. } => true, - ZetaAutomata::R1 { .. } => zeta.was_bob, + ZetaAutomata::R1 { .. } => session.was_bob, _ => false, }; if !should_rekey_as_bob { @@ -1110,285 +1225,229 @@ pub(crate) fn received_k1_trans( let i = k1.len() - AES_GCM_TAG_SIZE; let tag = k1[i..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(zeta.key_ref(false).recv.kek.as_ref().unwrap(), n, None, &mut k1[..i], tag) { + if !App::Aead::decrypt_in_place(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k1[..i], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let (_, c) = from_nonce(&n); - if !zeta.update_counter_window(c) { + let (_, c) = from_nonce(n); + if !session.window.update(c) { return Err(byzantine_fault!(ExpiredCounter, true)); } - k1.truncate(i); let result = (|| { let mut i = 0; let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_KK); + let mut hash = &mut App::Hash::new(); + let mut hmac = &mut App::HmacHash::new(); // Noise process prologue. - noise.mix_hash(&zeta.s_remote.to_bytes()); - noise.mix_hash(&s_secret.public_key_bytes()); + noise.mix_hash(hash, &session.s_remote.to_bytes()); + noise.mix_hash(hash, &s_secret.public_key_bytes()); // Process message pattern 1 psk0 token. - noise.mix_key_and_hash(zeta.ratchet_state1.key.as_ref()); + noise.mix_key_and_hash(hash, hmac, state.ratchet_state1.key.as_ref()); // Process message pattern 1 e token. - let e_remote = noise.read_e(&mut i, &k1).ok_or(byzantine_fault!(FailedAuth, true))?; + let e_remote = noise.read_e(hash, hmac, &mut i, &k1).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. - noise.mix_dh(s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(hmac, s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 ss token. - noise.mix_dh(s_secret, &zeta.s_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_key(hmac, session.noise_kk_ss.as_ref()); // Process message pattern 1 payload. let j = i + KID_SIZE; let k = j + AES_GCM_TAG_SIZE; let tag = k1[j..k].try_into().unwrap(); - if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..j], tag) { + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let kid_send = NonZeroU32::new(u32::from_be_bytes(k1[i..j].try_into().unwrap())).ok_or(byzantine_fault!(FailedAuth, true))?; - let mut k2 = Vec::new(); + let mut k2 = ArrayVec::::new(); + k2.extend([0u8; HEADER_SIZE]); // Process message pattern 2 e token. - let e_secret = noise.write_e(rng, &mut k2); + let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut k2); // Process message pattern 2 ee token. - noise.mix_dh(&e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(hmac, &e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 se token. - noise.mix_dh(&s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(hmac, &s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 payload. let i = k2.len(); - let new_kid_recv = remap(session, &zeta, rng, session_map); - k2.extend(&new_kid_recv.get().to_be_bytes()); - noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), i, &mut k2); + let new_kid_recv = remap(ctx, session, &state); + k2.extend(new_kid_recv.get().to_be_bytes()); + k2.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..])); - let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); - let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state1.chain_len + 1); + let new_ratchet_state = create_ratchet_state(hmac, &noise, state.ratchet_state1.chain_len); let result = app.save_ratchet_state( - &zeta.s_remote, - &zeta.session_data, + &session.s_remote, + &session.session_data, RatchetUpdate { state1: &new_ratchet_state, - state2: Some(&zeta.ratchet_state1), + state2: Some(&state.ratchet_state1), state1_was_just_added: true, - state_deleted1: zeta.ratchet_state2.as_ref(), + state_deleted1: state.ratchet_state2.as_ref(), state_deleted2: None, }, ); if let Err(e) = result { return Err(ReceiveError::RatchetIoError(e)); } - let (kek_send, kek_recv) = noise.get_ask(LABEL_KEX_KEY); - let (nk_send, nk_recv) = noise.split(); - zeta.key_mut(true).send.kid = Some(kid_send); - zeta.key_mut(true).send.kek = Some(kek_send); - zeta.key_mut(true).send.nk = Some(nk_send); - zeta.key_mut(true).recv.kid = Some(new_kid_recv); - zeta.key_mut(true).recv.kek = Some(kek_recv); - zeta.key_mut(true).recv.nk = Some(nk_recv); - zeta.ratchet_state2 = Some(zeta.ratchet_state1.clone()); - zeta.ratchet_state1 = new_ratchet_state; - let current_time = app.time(); - zeta.key_creation_counter = zeta.send_counter; - zeta.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; - zeta.beta = ZetaAutomata::R2 { k2: k2.clone() }; + let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); + let mut kek_send = Zeroizing::new([0u8; HASHLEN]); + let mut nk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut nk_send = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_send, &mut kek_recv); + noise.split(hmac, &mut nk_send, &mut nk_recv); - let c = zeta.send_counter; - zeta.send_counter += 1; - let n = to_nonce(PACKET_TYPE_REKEY_COMPLETE, c); - let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut k2); - k2.extend(&tag); + drop(state); + { + let mut state = session.state.write().unwrap(); + state.key_mut(true).replace_nk(&nk_send, &nk_recv); + state.key_mut(true).send.kid = Some(kid_send); + state.key_mut(true).send.replace_kek(&kek_send); + state.key_mut(true).recv.kid = Some(new_kid_recv); + state.key_mut(true).recv.replace_kek(&kek_recv); + state.ratchet_state2 = Some(state.ratchet_state1.clone()); + state.ratchet_state1 = new_ratchet_state.clone(); + let current_time = app.time(); + state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); + state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; + state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.beta = ZetaAutomata::R2 { k2: k2.clone() }; + } + let mut state = session.state.read().unwrap(); - send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, k2), Some(&zeta.hk_send)); + /// + let (c, should_rekey) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; + let nonce = to_nonce(PACKET_TYPE_REKEY_COMPLETE, c); + k2.extend(App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k2)); + set_header(&mut k2, state.key_ref(false).send.kid.unwrap().get(), &nonce); + + send(&mut k2, Some(&session.hk_send)); Ok(()) })(); + /// if matches!(result, Err(ReceiveError::ByzantineFault { .. })) { - zeta.expire(); + //zeta.expire(); } result } /// Corresponds to Transition Algorithm 8 found in Section 4.3. pub(crate) fn received_k2_trans( - zeta: &mut Zeta, app: &App, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], - mut k2: Vec, - send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), + n: &[u8; AES_GCM_IV_SIZE], + mut k2: &mut [u8], + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { use FaultType::*; // <- e, ee, se if k2.len() != REKEY_SIZE { return Err(byzantine_fault!(InvalidPacket, true)); } - if Some(kid) != zeta.key_ref(false).recv.kid { + + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + + if Some(kid) != state.key_ref(false).recv.kid { // Some rekey packet may have arrived extremely delayed. return Err(byzantine_fault!(UnknownLocalKeyId, false)); } - if !matches!(&zeta.beta, ZetaAutomata::R1 { .. }) { + if !matches!(&state.beta, ZetaAutomata::R1 { .. }) { // Some rekey packet may have arrived extremely delayed. return Err(byzantine_fault!(OutOfSequence, false)); } let i = k2.len() - AES_GCM_TAG_SIZE; let tag = k2[i..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(zeta.key_ref(false).recv.kek.as_ref().unwrap(), n, None, &mut k2[..i], tag) { + if !App::Aead::decrypt_in_place(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k2[..i], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let (_, c) = from_nonce(&n); - if !zeta.update_counter_window(c) { + let (_, c) = from_nonce(n); + if !session.window.update(c) { return Err(byzantine_fault!(ExpiredCounter, true)); } - k2.truncate(i); let result = (|| { - if let ZetaAutomata::R1 { noise, e_secret, .. } = &zeta.beta { + if let ZetaAutomata::R1 { noise, e_secret, .. } = &state.beta { let mut noise = noise.clone(); let mut i = 0; + let mut hash = &mut App::Hash::new(); + let mut hmac = &mut App::HmacHash::new(); // Process message pattern 2 e token. - let e_remote = noise.read_e(&mut i, &k2).ok_or(byzantine_fault!(FailedAuth, true))?; + let e_remote = noise.read_e(hash, hmac, &mut i, &k2).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. - noise.mix_dh(e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(hmac, e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 se token. - noise.mix_dh(e_secret, &zeta.s_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(hmac, e_secret, &session.s_remote).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 payload. let j = i + KID_SIZE; let k = j + AES_GCM_TAG_SIZE; let tag = k2[j..k].try_into().unwrap(); - if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..j], tag) { + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let kid_send = NonZeroU32::new(u32::from_be_bytes(k2[i..j].try_into().unwrap())).ok_or(byzantine_fault!(InvalidPacket, true))?; - let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); - let new_ratchet_state = RatchetState::new(rk, rf, zeta.ratchet_state1.chain_len + 1); + let new_ratchet_state = create_ratchet_state(hmac, &noise, state.ratchet_state1.chain_len); let result = app.save_ratchet_state( - &zeta.s_remote, - &zeta.session_data, + &session.s_remote, + &session.session_data, RatchetUpdate { state1: &new_ratchet_state, state2: None, state1_was_just_added: true, - state_deleted1: Some(&zeta.ratchet_state1), - state_deleted2: zeta.ratchet_state2.as_ref(), + state_deleted1: Some(&state.ratchet_state1), + state_deleted2: state.ratchet_state2.as_ref(), }, ); if let Err(e) = result { return Err(ReceiveError::RatchetIoError(e)); } - let (kek_recv, kek_send) = noise.get_ask(LABEL_KEX_KEY); - let (nk_recv, nk_send) = noise.split(); + let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); + let mut kek_send = Zeroizing::new([0u8; HASHLEN]); + let mut nk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut nk_send = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_recv, &mut kek_send); + noise.split(hmac, &mut nk_recv, &mut nk_send); - zeta.key_mut(true).send.kid = Some(kid_send); - zeta.key_mut(true).send.kek = Some(kek_send); - zeta.key_mut(true).send.nk = Some(nk_send); - zeta.key_mut(true).recv.kek = Some(kek_recv); - zeta.key_mut(true).recv.nk = Some(nk_recv); - zeta.ratchet_state1 = new_ratchet_state; - zeta.key_index ^= true; - let current_time = app.time(); - zeta.key_creation_counter = zeta.send_counter; - zeta.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - zeta.resend_timer = current_time + App::SETTINGS.resend_time as i64; - zeta.beta = ZetaAutomata::S1; + drop(state); + let current_time = { + let mut state = session.state.write().unwrap(); + state.key_mut(true).replace_nk(&nk_send, &nk_recv); + state.key_mut(true).send.kid = Some(kid_send); + state.key_mut(true).send.replace_kek(&kek_send); + state.key_mut(true).recv.replace_kek(&kek_recv); + state.ratchet_state1 = new_ratchet_state.clone(); + state.key_index ^= true; + let current_time = app.time(); + state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); + state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; + state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.beta = ZetaAutomata::S1; + current_time + }; + process_timers(app, ctx, session, current_time, false, true, send); - let mut c1 = Vec::new(); - let c = zeta.send_counter; - zeta.send_counter += 1; - let n = to_nonce(PACKET_TYPE_KEY_CONFIRM, c); - let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.kek.as_ref().unwrap(), n, None, &mut []); - c1.extend(&tag); - - send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, c1), Some(&zeta.hk_send)); Ok(()) } else { unreachable!() } })(); if matches!(result, Err(ReceiveError::ByzantineFault { .. })) { - zeta.expire(); + //zeta.expire(); } result } -/// Corresponds to Algorithm 9 found in Section 4.3. -pub(crate) fn send_payload( - zeta: &mut Zeta, - mut payload: Vec, - send: impl FnOnce(&Packet, Option<&[u8; AES_256_KEY_SIZE]>), -) -> Result<(), SendError> { - use SendError::*; - if matches!(&zeta.beta, ZetaAutomata::Null) { - return Err(SessionExpired); - } - if !matches!( - &zeta.beta, - ZetaAutomata::S1 | ZetaAutomata::S2 | ZetaAutomata::R1 { .. } | ZetaAutomata::R2 { .. } - ) { - return Err(SessionNotEstablished); - } - let c = zeta.send_counter; - zeta.send_counter += 1; - if c >= zeta.key_creation_counter + App::SETTINGS.rekey_after_key_uses { - if c >= zeta.key_creation_counter + EXPIRE_AFTER_USES { - zeta.expire(); - } else { - // Cause timeout to occur next service interval. - zeta.timeout_timer = i64::MIN; - } - } +//impl Session { +// /// Mark a session as expired. This will make it impossible for this session to successfully +// /// receive or send data or control packets. It is recommended to simply `drop` the session +// /// instead, but this can provide some reassurance in complex shared ownership situations. +// pub fn expire(&mut self) { +// self.0.lock().unwrap().expire(); +// } +//} - let n = to_nonce(PACKET_TYPE_DATA, c); - let tag = App::Aead::encrypt_in_place(zeta.key_ref(false).send.nk.as_ref().unwrap(), n, None, &mut payload); - payload.extend(&tag); - - send(&Packet(zeta.key_ref(false).send.kid.unwrap().get(), n, payload), Some(&zeta.hk_send)); - Ok(()) -} -/// Corresponds to Algorithm 10 found in Section 4.3. -pub(crate) fn received_payload_in_place( - zeta: &mut Zeta, - kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], - payload: &mut Vec, -) -> Result<(), ReceiveError> { - use FaultType::*; - - if payload.len() < AES_GCM_TAG_SIZE { - return Err(byzantine_fault!(FailedAuth, true)); - } - let is_other = if Some(kid) == zeta.key_ref(true).recv.kid { - true - } else if Some(kid) == zeta.key_ref(false).recv.kid { - false - } else { - // A packet would have to be delayed by around an hour for this error to occur, but it can - // occur naturally due to just out-of-order transport. - return Err(byzantine_fault!(OutOfSequence, false)); - }; - - let i = payload.len() - AES_GCM_TAG_SIZE; - let specified_key = zeta.key_ref(is_other).recv.nk.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; - let tag = payload[i..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(specified_key, n, None, &mut payload[..i], tag) { - return Err(byzantine_fault!(FailedAuth, true)); - } - let (_, c) = from_nonce(&n); - if !zeta.update_counter_window(c) { - // This error is marked as not happening naturally, but it could occur if something about - // the transport protocol is duplicating packets. - return Err(byzantine_fault!(ExpiredCounter, true)); - } - payload.truncate(i); - - Ok(()) -} - -impl Session { - /// Mark a session as expired. This will make it impossible for this session to successfully - /// receive or send data or control packets. It is recommended to simply `drop` the session - /// instead, but this can provide some reassurance in complex shared ownership situations. - pub fn expire(&mut self) { - self.0.lock().unwrap().expire(); - } -} - -impl Drop for Session { - fn drop(&mut self) { - self.expire(); - } -} +//impl Drop for Session { +// fn drop(&mut self) { +// self.expire(); +// } +//} diff --git a/src/zssp.rs b/src/zssp.rs index 2288a43..ef49568 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -19,14 +19,15 @@ use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; use arrayvec::ArrayVec; use zeroize::Zeroizing; +use crate::zeta::*; use crate::challenge::ChallengeContext; -use crate::crypto::aes::{AesDec, AesEnc, AES_256_KEY_SIZE}; +use crate::crypto::aes::{AesDec, AesEnc, AES_256_KEY_SIZE, AES_GCM_TAG_SIZE, AES_GCM_IV_SIZE}; use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; use crate::crypto::rand_core::RngCore; use crate::crypto::sha512::{HmacSha512, HashSha512}; -use crate::result::{FaultType, OpenError, ReceiveError, SendError}; +use crate::result::{FaultType, OpenError, ReceiveError, SendError, ReceiveOk, byzantine_fault, SessionEvent}; use crate::frag_cache::UnassociatedFragCache; use crate::fragged::{Assembled, Fragged}; use crate::handshake_cache::UnassociatedHandshakeCache; @@ -36,6 +37,15 @@ use crate::proto::*; use crate::symmetric_state::SymmetricState; use crate::{applicationlayer::*, RatchetState}; +/// Macro to turn off logging at compile time. +macro_rules! log { + ($app:expr, $event:expr) => { + #[cfg(feature = "logging")] + $app.event_log($event); + }; +} +pub(crate) use log; + /// Session context for local application. /// /// Each application using ZSSP must create an instance of this to own sessions and @@ -48,60 +58,19 @@ impl Clone for Context { Self(self.0.clone()) } } -pub struct ContextInner { - static_keypair: Application::KeyPair, - unassociated_defrag_cache: Mutex>, - unassociated_handshake_states: UnassociatedHandshakeCache, - /// `session_queue -> state_machine_lock -> state -> session_map` - session_queue: Mutex>, Reverse>>, - session_map: RwLock>, bool)>>, - challenge: ChallengeContext, - rng: Mutex, -} -/// Result generated by the context packet receive function, with possible payloads. -pub enum ReceiveResult<'b, Application: ApplicationLayer> { - /// Packet superficially appeared valid but is not associated with a session yet. - /// This can occur because the packet was only a fragment of a larger packet, - /// or if it was a control packet that does not go through full Noise authentication. - Unassociated, - /// Packet was authentic and belongs to this specific session. - Session(Arc>, SessionEvent<'b>), - /// Packet was a part of a handshake, and while it superficially appeared valid the application - /// explicitly rejected it. - /// Relates to callbacks `check_allow_incoming_session`, `hello_requires_recognized_ratchet` - /// and `check_accept_session`. - Rejected, -} +pub(crate) type SessionMap = RwLock>>>; +pub(crate) struct ContextInner { + pub rng: Mutex, + pub(crate) s_secret: App::KeyPair, + pub(crate) session_queue: Mutex>, Reverse>>, + pub(crate) session_map: SessionMap, + pub(crate) unassociated_defrag_cache: Mutex>, + pub(crate) unassociated_handshake_states: UnassociatedHandshakeCache, + //pub(crate) b2_map: Mutex>>, -#[derive(Debug, PartialEq, Eq)] -pub enum SessionEvent<'b> { - /// The received packet was valid, and it contained the necessary keys to fully establish a new - /// session with Alice, the handshake initiator. - /// - /// If the session Arc returned is dropped, the session with this peer will be immediately - /// terminated. Save the session Arc to some long lived datastructure to keep it alive. - NewSession, - /// When Alice calls `Context::open`, a session will be created, but Bob will not yet have - /// received this session. They will have to successfully complete a handshake first. - /// - /// Alice will receive this return value when the received packet confirms both parties - /// have completed the initial handshake and now have a shared session with each other. - /// If according to the upper protocol, Bob is the first party to send data, it is possible for - /// Alice to start receiving data from Bob before this value is returned. - /// - /// This return value can only occur once per session, only for session objects that were - /// created with `Context::open`. - Established, - /// Bob explicitly refused to establish a session with Alice, and sent us an error code. - /// The application should immediately drop this session as Bob will not allow us to connect. - /// - /// This return value cannot occur after a session is fully established. - Rejected, - /// The received packet was valid and a data payload was decoded and authenticated. - Data(&'b mut [u8]), - /// The received packet was some authentic protocol control packet. No action needs to be taken. - Control, + //hello_defrag: Mutex, + pub(crate) challenge: ChallengeContext, } #[derive(Debug, PartialEq, Eq)] @@ -111,333 +80,33 @@ pub enum IncomingSessionAction { Drop, } -/// ZeroTier Secure Session Protocol (ZSSP) Session -/// -/// A FIPS/NIST compliant variant of Noise_XK with hybrid Kyber1024 PQ data forward secrecy. -pub struct Session { - /// An arbitrary application defined object associated with each session. - pub application_data: App::SessionData, - /// Is true if the local peer acted as Bob, the responder in the initial key exchange. - pub was_bob: bool, - /// The receive context associated with this session, - /// only this context can receive messages from the remote peer. - context: Weak>, - /// Handle into the session queue for changing the update timer. - queue_idx: BinaryHeapIndex, - - remote_static_key: App::PublicKey, - send_counter: AtomicU64, - /// This bool signals to all threads to stop incrementing the counter and instead error out. - session_has_expired: AtomicBool, - /// The following is a ring buffer of previously seen counter values, where we use the counter's - /// value as the index of the head of the ring buffer. - counter_antireplay_window: [AtomicU64; COUNTER_WINDOW_MAX_OOO], - /// Enforces atomicity of state machine transitions. - /// There is a standard locking sequence, - /// it goes `session_queue -> state_machine_lock -> state -> session_map`. - /// Any lock can be skipped but they must be locked in that order. - state_machine_lock: Mutex<()>, - state: RwLock>, - defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], - header_send_cipher: App::PrpEnc, - header_receive_cipher: App::PrpDec, - kex_send_cipher: Mutex>, - kex_receive_cipher: Mutex>, - /// Pre-computed rekeying values. - noise_kk_ss: Zeroizing<[u8; P384_ECDH_SHARED_SECRET_SIZE]>, - noise_kk_local_init_h: [u8; HASHLEN], - noise_kk_remote_init_h: [u8; HASHLEN], -} -/// `AesGcm` is not threadsafe, but it is threadsafe when inside a `Mutex`. -unsafe impl Send for Session {} -unsafe impl Sync for Session {} - -/// Session state may only be mutated during atomic transitions of the offer state machine. -struct SessionMutableState { - ratchet_states: [RatchetState; 2], - /// For OOO rekeying reliability we allow our version of Noise CipherState to hold the last two - /// session keys, instead of just the most recent one. - cipher_states: [Option>; 2], - /// This is the index of `noise_cipher_state` that contains the most recent key. - /// It will be attached to fragment headers to help with OOO transport. - current_key: usize, - resent_timer: AtomicI64, - timeout_timer: i64, - /// This defines the exact state of the offer state machine we are in. - outgoing_offer: OfferStateMachine, +fn parse_fragment_header(incoming_fragment: &[u8]) -> Result<(usize, usize, [u8; AES_GCM_IV_SIZE]), ReceiveError> { + let fragment_no = incoming_fragment[FRAGMENT_NO_IDX] as usize; + let fragment_count = incoming_fragment[FRAGMENT_COUNT_IDX] as usize; + if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS { + return Err(byzantine_fault!(FaultType::InvalidPacket, true)); + } + let mut nonce = [0u8; AES_GCM_IV_SIZE]; + nonce[2..].copy_from_slice(&incoming_fragment[PACKET_NONCE_START..HEADER_SIZE]); + Ok((fragment_no, fragment_count, nonce)) } -/// These offer enums form a state machine. -/// Documented below are the only legal transitions for this state machine. -/// A session is initialized with an `outgoing_offer` of either NoiseXKPattern1 or Normal. -enum OfferStateMachine { - Normal, // -> NoiseKKPattern1, NoiseKKPattern2 - /// This state uses a lot of memory so we put it on the heap. - NoiseXKPattern1or3(Box>), // -> Normal - NoiseKKPattern1 { - new_key_id: NonZeroU32, - noise_e_secret: App::KeyPair, - noise_message: ArrayVec, - noise_ck: SymmetricState, - }, // -> NoiseKKPattern2, KeyConfirm - NoiseKKPattern2 { - noise_message: ArrayVec, - kex_send_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, - }, // -> Normal - KeyConfirm, // -> Normal - Null, -} -pub(crate) struct NoiseXKBobHandshakeState { - /// Can never be Null. - ratchet_state: RatchetState, - remote_key_id: NonZeroU32, - local_key_id: NonZeroU32, - header_receive_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, - header_send_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, - noise_e_secret: App::KeyPair, - noise_ck_eseeekem1psk: SymmetricState, - noise_pattern3_defrag: Mutex>, -} - -struct NoiseXKAliceHandshake { - /// A secure random number put in the header of Alice's fragments to identify them. - /// If a DDOS attacker could guess this they could block Alice starting the handshake. - local_key_id: NonZeroU32, - alice_identity_blob: App::LocalIdentityBlob, - offer: NoiseXKAliceHandshakeState, -} - -enum NoiseXKAliceHandshakeState { - NoiseXKPattern1 { - noise_h_ee1p: [u8; HASHLEN], - noise_e_secret: App::KeyPair, - noise_e1_secret: App::Kem, - noise_ck_es: SymmetricState, - /// ZSSP assumes an unreliable, out-of-order physical transport environment, so for that - /// reason we have to resend key offers. - noise_message: ArrayVec, - message_id: u64, - }, - NoiseXKPattern3 { - noise_message: ArrayVec, - }, -} - -struct SessionKey { - remote_key_id: NonZeroU32, - local_key_id: NonZeroU32, - /// Pool of reusable sending ciphers. - receive_cipher_pool: [Mutex; 8], - /// Pool of reusable receiving ciphers. - send_cipher_pool: [Mutex; 8], - /// Rekey at or after this counter. - rekey_at_counter: u64, - /// Hard error when this counter value is reached or exceeded. - expire_at_counter: u64, -} - -macro_rules! byzantine_fault { - ($name:expr, $is_natural:ident) => { - ReceiveError::ByzantineFault { - file: file!(), - line: line!(), - error: $name, - is_naturally_occurring: $is_natural, - } - }; -} - -impl Context { +impl Context { /// Create a new session context. - pub fn new(static_keypair: Application::KeyPair, mut rng: Application::Rng) -> Self { - debug_assert!(Application::REKEY_AFTER_TIME_MAX_JITTER_MS > 0, "Invalid protocol constant"); - let mut challenge_salt = [0u8; CHALLENGE_SALT_SIZE]; - rng.fill_bytes(&mut challenge_salt); + pub fn new(static_secret_key: App::KeyPair, mut rng: App::Rng) -> Self { + let challenge = ChallengeContext::new(&mut rng); Self(Arc::new(ContextInner { - static_keypair, + rng: Mutex::new(rng), + s_secret: static_secret_key, + session_map: RwLock::new(HashMap::new()), + challenge, + session_queue: Mutex::new(IndexedBinaryHeap::new()), unassociated_defrag_cache: Mutex::new(UnassociatedFragCache::new()), unassociated_handshake_states: UnassociatedHandshakeCache::new(), - session_map: RwLock::new(HashMap::new()), - session_queue: Mutex::new(IndexedBinaryHeap::new()), - challenge_counter: AtomicU64::new(INIT_COUNTER), - challenge_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - challenge_salt, - rng: Mutex::new(rng), })) } - /// Perform periodic background service and cleanup tasks. - /// - /// This returns the number of milliseconds until it should be called again. The caller should - /// try to satisfy this but small variations in timing of up to +/- a second or two are not - /// a problem. - /// - /// * `send_to` - Function to get a sender and an MTU to send something over an active session - /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced - /// with remote peers (although both of these properties would help reliability slightly). - /// Used to determine if any current handshakes should be resent or timed-out, or if a session - /// should rekey. - pub fn service bool>( - &self, - app: &Application, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, - current_time: i64, - ) -> i64 { - let retry_next = current_time.saturating_add(Application::RETRY_INTERVAL_MS); - let mut next_service_time = 2 * Application::RETRY_INTERVAL_MS; - - let mut session_queue = self.0.session_queue.lock().unwrap(); - // This update system takes heavy advantage of the fact that sessions only need to be updated - // either roughly every second or roughly every hour. That big gap allows for minor optimizations. - // If the gap changes (unlikely) this code may need to be rewritten. - while let Some((session, timer, queue_idx)) = session_queue.peek() { - if timer.0 >= current_time { - next_service_time = next_service_time.min(timer.0 - current_time); - break; - } - let session = match session.upgrade() { - Some(s) => s, - _ => { - session_queue.remove(queue_idx); - continue; - } - }; - let state = session.state.read().unwrap(); - use OfferStateMachine::*; - let next_timer = match &state.outgoing_offer { - Normal { timeout, .. } => { - if *timeout <= current_time { - drop(state); - if let Some((send, _)) = send_to(&session) { - let result = initiate_rekey(&self.0, &session, send, current_time); - if result.is_ok() { - app.event_log(LogEvent::ServiceKKStart(&session), current_time); - } - result.unwrap_or(retry_next) - } else { - retry_next - } - } else { - *timeout - } - } - // If there's an outstanding attempt to open a session, retransmit this - // periodically in case the initial packet doesn't make it. - NoiseXKPattern1or3(handshake_state) => { - if let Some(ts) = process_timer(&handshake_state.next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { - ts - } else { - // We have to eventually time out NoiseXKPattern3 because of unreliable network conditions. - if handshake_state.timeout <= current_time { - drop(state); - let _kex_lock = session.state_machine_lock.lock().unwrap(); - let mut state = session.state.write().unwrap(); - let ratchet_state = state.ratchet_states.clone(); - // Since we dropped the lock we must re-check if we are in the correct state. - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if handshake_state.timeout <= current_time { - app.event_log(LogEvent::ServiceXKTimeout(&session), current_time); - handshake_state.reinitialize( - &session, - &ratchet_state, - &mut self.0.session_map.write().unwrap(), - &mut self.0.rng.lock().unwrap(), - current_time, - ); - } - } - } else if let Some((mut send, mut mtu)) = send_to(&session) { - mtu = mtu.max(MIN_TRANSPORT_MTU); - match &handshake_state.offer { - NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } => { - app.event_log(LogEvent::ServiceXK1Resend(&session), current_time); - // We are in state NoiseXKPattern1 so resend noise_pattern1. - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_1, - None, - *message_id, - None::<&Application::PrpEnc>, - ); - } - NoiseXKAliceHandshakeState::NoiseXKPattern3 { noise_message, noise_message_len, .. } => { - app.event_log(LogEvent::ServiceXK3Resend(&session), current_time); - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_3, - state.cipher_states[0].as_ref().map(|k| k.remote_key_id), - 0, - Some(&session.header_send_cipher), - ); - } - } - } - retry_next - } - } - NoiseKKPattern1 { next_retry_time, timeout, noise_message, .. } | NoiseKKPattern2 { next_retry_time, timeout, noise_message, .. } => { - if let Some(ts) = process_timer(next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { - ts - } else { - if *timeout <= current_time { - app.event_log(LogEvent::ServiceKKTimeout(&session), current_time); - next_retry_time.store(i64::MAX, Ordering::Relaxed); - drop(state); - session.expire_inner(&self.0, &mut session_queue); - } else { - let packet_type = if let NoiseKKPattern1 { .. } = &state.outgoing_offer { - app.event_log(LogEvent::ServiceKK1Resend(&session), current_time); - PACKET_TYPE_NOISE_KK_PATTERN_1 - } else { - app.event_log(LogEvent::ServiceKK2Resend(&session), current_time); - PACKET_TYPE_NOISE_KK_PATTERN_2 - }; - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&state, send, packet_type, noise_message); - } - } - retry_next - } - } - KeyConfirm { next_retry_time, timeout, .. } => { - if let Some(ts) = process_timer(next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { - ts - } else { - if *timeout <= current_time { - app.event_log(LogEvent::ServiceKeyConfirmTimeout(&session), current_time); - next_retry_time.store(i64::MAX, Ordering::Relaxed); - drop(state); - session.expire_inner(&self.0, &mut session_queue); - } else { - app.event_log(LogEvent::ServiceKeyConfirmResend(&session), current_time); - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&state, send, PACKET_TYPE_KEY_CONFIRM, &[]); - } - } - retry_next - } - } - Null => retry_next, - }; - session_queue.change_priority(queue_idx, Reverse(next_timer)); - } - drop(session_queue); - - self.0 - .unassociated_defrag_cache - .lock() - .unwrap() - .check_for_expiry(Application::INITIAL_OFFER_TIMEOUT_MS, current_time); - self.0.unassociated_handshake_states.service(current_time); - - next_service_time - } - /// Create a new session and send initial packet(s) to other side. /// /// This will return SendError::DataTooLarge if the combined size of the metadata and the local @@ -454,103 +123,27 @@ impl Context { /// peer, or None if we do not have one. /// * `local_identity_blob` - Payload to be sent to Bob that contains the information necessary /// for the upper protocol to authenticate and approve of Alice's identity. - /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced - /// with the remote peer. Used to determine when this offer should be resent. pub fn open( &self, - app: &Application, + app: App, mut send: impl FnMut(&mut [u8]) -> bool, mut mtu: usize, - remote_static_key: Application::PublicKey, - application_data: Application::SessionData, - local_identity_blob: Application::LocalIdentityBlob, - current_time: i64, - ) -> Result>, OpenError> { + static_remote_key: App::PublicKey, + session_data: App::SessionData, + identity: &[u8], + ) -> Result>, OpenError> { mtu = mtu.max(MIN_TRANSPORT_MTU); - if local_identity_blob.as_ref().len() > MAX_IDENTITY_BLOB_SIZE { - return Err(OpenError::DataTooLarge); - } - let result = app.restore_by_identity(&remote_static_key, &application_data, current_time); - match result { - Ok(ratchet_states) => { - let sha512 = &mut Application::Hash::new(); - - let mut noise_kk_ss = Secret::new(); - if !self.0.static_keypair.agree(&remote_static_key, noise_kk_ss.as_mut()) { - return Err(OpenError::InvalidPublicKey); - } - let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); - let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_static_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_static_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); - - let mut session_queue = self.0.session_queue.lock().unwrap(); - let mut session_map = self.0.session_map.write().unwrap(); - let local_key_id = generate_key_id(&session_map, &mut self.0.rng.lock().unwrap()); - // Begin Noise XKhfs+psk2. - let (offer, a2b_header_key, b2a_header_key) = NoiseXKAliceHandshake::::initialize( - local_key_id, - &remote_static_key, - &ratchet_states, - &mut self.0.rng.lock().unwrap(), - )?; - let handshake_state = Box::new(NoiseXKAliceHandshake { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), - local_key_id, - alice_identity_blob: local_identity_blob, - offer, - }); - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } = &handshake_state.offer { - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_1, - None, - *message_id, - None::<&Application::PrpEnc>, - ); - } - - let queue_idx = session_queue.reserve_index(); - let session = Arc::new(Session { - context: Arc::downgrade(&self.0), - queue_idx, - application_data, - remote_static_key, - send_counter: AtomicU64::new(INIT_COUNTER), - session_has_expired: AtomicBool::new(false), - counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - state_machine_lock: Mutex::new(()), - state: RwLock::new(SessionMutableState { - ratchet_states: ratchet_states.clone(), - cipher_states: [None, None], - // Points at 1 until the first key is confirmed. - current_key: 1, - outgoing_offer: OfferStateMachine::NoiseXKPattern1or3(handshake_state), - }), - header_send_cipher: Application::PrpEnc::new(a2b_header_key.as_ref()), - header_receive_cipher: Application::PrpDec::new(b2a_header_key.as_ref()), - kex_receive_cipher: Mutex::new(None), - kex_send_cipher: Mutex::new(None), - noise_kk_ss: noise_kk_ss.clone(), - noise_kk_local_init_h, - noise_kk_remote_init_h, - defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - was_bob: false, - }); - session_map.insert(local_key_id, (Arc::downgrade(&session), false)); - session_queue.push_reserved( - queue_idx, - Arc::downgrade(&session), - Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - ); - - Ok(session) - } - Err(e) => Err(OpenError::RatchetIoError(e)), + if identity.len() > IDENTITY_MAX_SIZE { + return Err(OpenError::IdentityTooLarge); } + // Process zeta layer. + trans_to_a1( + app, + &self.0, + static_remote_key, + session_data, + identity, + ) } /// Receive, authenticate, decrypt, and process a physical wire packet. @@ -562,7 +155,7 @@ impl Context { /// The check_accept_session function is called at the end of negotiation for an incoming /// session with the caller's static public blob. It must return the P-384 static public key /// extracted from the supplied blob and application data. A return of Some() accepts the - /// session and will always result in a new session ReceiveResult being returned. + /// session and will always result in a new session ReceiveOk being returned. /// /// * `app` - Interface to application using ZSSP /// * `check_allow_incoming_session` - Function to call to check whether an unidentified new @@ -586,21 +179,22 @@ impl Context { /// to put in-flight. pub fn receive<'a, SendFn: FnMut(&mut [u8]) -> bool>( &self, - app: &Application, + app: &App, check_allow_incoming_session: impl FnOnce() -> IncomingSessionAction, - check_accept_session: impl FnOnce(&Application::PublicKey, &[u8], u64) -> (Option<(bool, Application::SessionData)>, bool), + check_accept_session: impl FnOnce(&App::PublicKey, &[u8], u64) -> (Option<(bool, App::SessionData)>, bool), mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, mut send_unassociated_mtu: usize, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, remote_address: &impl Hash, data_buf: &'a mut [u8], - mut incoming_physical_packet_buf: Application::IncomingPacketBuffer, + mut incoming_fragment_buf: App::IncomingPacketBuffer, current_time: i64, - ) -> Result, ReceiveError> { + ) -> Result, ReceiveError> { + use crate::result::FaultType::*; + let ctx = &self.0; send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); - let incoming_physical_packet: &mut [u8] = incoming_physical_packet_buf.as_mut(); - let incoming_physical_packet_len = incoming_physical_packet.len(); - if incoming_physical_packet_len < MIN_PACKET_SIZE { + let incoming_fragment: &mut [u8] = incoming_fragment_buf.as_mut(); + if incoming_fragment.len() < MIN_PACKET_SIZE { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } @@ -611,80 +205,66 @@ impl Context { let mut assembled_packet = Assembled::new(); // needs to outlive the block below let mut incoming = None; let (session, packet_type, fragments) = { - let local_key_id = incoming_physical_packet[0..SESSION_ID_SIZE].try_into().unwrap(); + let kid_recv = incoming_fragment[0..KID_SIZE].try_into().unwrap(); // `from_ne_bytes` because this id was generated locally. - if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(local_key_id)) { + if let Some(kid_recv) = NonZeroU32::new(u32::from_ne_bytes(kid_recv)) { let session_map = self.0.session_map.read().unwrap(); - if let Some((Some(session), key_index)) = session_map.get(&local_key_id).map(|r| (r.0.upgrade(), r.1 as usize)) { + let session = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()); + if let Some(Some(session)) = session { drop(session_map); - session.header_receive_cipher.decrypt_in_place( - (&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) + session.hk_recv.decrypt_in_place( + (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) .try_into() .unwrap(), ); - let (fragment_count, fragment_no, packet_type, incoming_counter, header_nonce) = parse_packet_header(incoming_physical_packet); - // Handle replay protection. - if PACKET_TYPE_RANGE_TRANSPORT.contains(&packet_type) { - // For DOS resistant reply-protection we need to check that the given counter is - // in the window of valid counters immediately. - // But for packets larger than 1 fragment we can't actually record the - // counter as received until we've authenticated the packet. - // So we check the counter window twice, and only update it the second time - // after the packet has been authenticated. - if !session.check_receive_window(incoming_counter) { - // This can occur naturally if packets arrive way out of order, or - // if they are duplicates. - // This can also be naturally triggered if Bob has just successfully - // received the first session key and is reject all of Alice's resends. - // This can also occur if a session was manually expired, but not - // dropped, and the remote party is still sending us data. - return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); - } + + let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; + let (packet_type, incoming_counter) = from_nonce(&nonce); + + {//vrfy if packet_type != PACKET_TYPE_DATA { - // This is a control packet. - if fragment_count != 1 || fragment_no > 0 { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + log!(app, ReceivedRawFragment(p, c, fragment_no, fragment_count)); + } + if packet_type == PACKET_TYPE_HANDSHAKE_RESPONSE { + if !matches!(&session.state.read().unwrap().beta, ZetaAutomata::A1(_)) { + // A resent handshake response from Bob may have arrived out of order, + // after we already received one. + return Err(byzantine_fault!(OutOfSequence, false)); } - return receive_control_fragment( - self, - session, - app, - send_to, - packet_type, - incoming_counter, - incoming_physical_packet_buf.as_mut(), - current_time, - ); + if incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD { + return Err(byzantine_fault!(ExpiredCounter, true)); + } + } else if PACKET_TYPE_USES_COUNTER_RANGE.contains(&packet_type) { + // For DOS resistant reply-protection we need to check that the given counter is + // in the window of valid counters immediately. + // But for packets larger than 1 fragment we can't actually record the + // counter as received until we've authenticated the packet. + // So we check the counter window twice, and only update it the second time + // after the packet has been authenticated. + if !session.window.check(incoming_counter) { + // This can occur naturally if packets arrive way out of order, or + // if they are duplicates. + // This can also be naturally triggered if Bob has just successfully + // received the first session key and is reject all of Alice's resends. + // This can also occur if a session was manually expired, but not + // dropped, and the remote party is still sending us data. + return Err(byzantine_fault!(ExpiredCounter, false)); + } + } else if packet_type == PACKET_TYPE_HANDSHAKE_COMPLETION { + // This can be triggered if Bob successfully received a session key and + // needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3. + return Err(byzantine_fault!(InvalidPacket, false)); + } else { + return Err(byzantine_fault!(InvalidPacket, true)); } - } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_2 { - // We need to reject fragments marked with this type if they are sent out - // of sequence, since an attacker is able to replay them. - match &session.state.read().unwrap().outgoing_offer { - OfferStateMachine::NoiseXKPattern1or3(handshake_state) => match &handshake_state.offer { - NoiseXKAliceHandshakeState::NoiseXKPattern1 { .. } => { - if incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - } - // This error can occur naturally if Bob's initial reply to Alice had a - // resend that was delayed massively and arrived out of order. - _ => return Err(byzantine_fault!(FaultType::OutOfSequence, true)), - }, - _ => return Err(byzantine_fault!(FaultType::OutOfSequence, false)), - }; - } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_3 { - // This can be triggered if Bob successfully received a session key and - // needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3. - return Err(byzantine_fault!(FaultType::OutOfSequence, true)); - } else { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } + // Handle defragmentation. let fragments = if fragment_count > 1 { let idx = incoming_counter as usize % session.defrag.len(); session.defrag[idx].lock().unwrap().assemble( - header_nonce, - incoming_physical_packet_buf, + &nonce, + incoming_fragment_buf, fragment_no, fragment_count, &mut assembled_packet, @@ -692,96 +272,100 @@ impl Context { if assembled_packet.is_empty() { // We have not yet authenticated the sender so we do not report // receiving a packet from them. - return Ok(ReceiveResult::Unassociated); + return Ok(ReceiveOk::Unassociated); } else { assembled_packet.as_ref() } } else { - std::array::from_ref(&incoming_physical_packet_buf) + std::slice::from_ref(&incoming_fragment_buf) }; - // Handle DATA in the fastest path when we have a session. - if packet_type == PACKET_TYPE_DATA { - let state = session.state.read().unwrap(); - // The error here can occur because the other party is using a brand new - // session key that we have not received yet. - let key = state.cipher_states[key_index] - .as_ref() - .ok_or(byzantine_fault!(FaultType::OutOfSequence, true))?; - let mut c = key.get_receive_cipher(incoming_counter); - c.set_iv(&create_message_nonce(packet_type, incoming_counter)); - let mut data_len = 0; + match packet_type { + PACKET_TYPE_DATA => { + let state = session.state.read().unwrap(); + // The error here can occur because the other party is using a brand new + // session key that we have not received yet. + let key = state.cipher_states[key_index] + .as_ref() + .ok_or(byzantine_fault!(FaultType::OutOfSequence, true))?; + let mut c = key.get_receive_cipher(incoming_counter); + c.set_iv(&create_message_nonce(packet_type, incoming_counter)); - // Decrypt fragments 0..N-1 where N is the number of fragments. - for f in fragments[..(fragments.len() - 1)].iter() { - let f: &[u8] = f.as_ref(); - debug_assert!(f.len() >= HEADER_SIZE); + let mut data_len = 0; + + // Decrypt fragments 0..N-1 where N is the number of fragments. + for f in fragments[..(fragments.len() - 1)].iter() { + let f: &[u8] = f.as_ref(); + debug_assert!(f.len() >= HEADER_SIZE); + let current_frag_data_start = data_len; + data_len += f.len() - HEADER_SIZE; + if data_len > data_buf.len() { + return Err(ReceiveError::DataBufferTooSmall); + } + c.decrypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); + } + + // Decrypt final fragment (or only fragment if not fragmented) let current_frag_data_start = data_len; - data_len += f.len() - HEADER_SIZE; + let last_fragment = fragments.last().unwrap().as_ref(); + if last_fragment.len() < (HEADER_SIZE + AES_GCM_TAG_SIZE) { + return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + } + data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE); if data_len > data_buf.len() { return Err(ReceiveError::DataBufferTooSmall); } - c.decrypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); - } + let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE; + c.decrypt(&last_fragment[HEADER_SIZE..payload_end], &mut data_buf[current_frag_data_start..data_len]); - // Decrypt final fragment (or only fragment if not fragmented) - let current_frag_data_start = data_len; - let last_fragment = fragments.last().unwrap().as_ref(); - if last_fragment.len() < (HEADER_SIZE + AES_GCM_TAG_SIZE) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE); - if data_len > data_buf.len() { - return Err(ReceiveError::DataBufferTooSmall); - } - let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE; - c.decrypt(&last_fragment[HEADER_SIZE..payload_end], &mut data_buf[current_frag_data_start..data_len]); + let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..].try_into().unwrap()); + drop(c); + drop(state); - let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..].try_into().unwrap()); - drop(c); - drop(state); - - if !aead_authentication_ok { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + if !aead_authentication_ok { + return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); + } + if !session.update_receive_window(incoming_counter) { + // This can be naturally triggered because Bob has just + // successfully received a session key and needs to reject + // all of Alice's resends. + // This can also occur naturally if some part of the outer + // system is duplicating the packets being sent to us. + // We are safely deduplicating them here. + return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); + } + // Packet fully authenticated + return Ok(ReceiveOk::Session(session, SessionEvent::Data(&mut data_buf[..data_len]))); } - if !session.update_receive_window(incoming_counter) { - // This can be naturally triggered because Bob has just - // successfully received a session key and needs to reject - // all of Alice's resends. - // This can also occur naturally if some part of the outer - // system is duplicating the packets being sent to us. - // We are safely deduplicating them here. - return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); + PACKET_TYPE_HANDSHAKE_RESPONSE => { + (Some(session), packet_type, fragments) } - // Packet fully authenticated - return Ok(ReceiveResult::Session(session, SessionEvent::Data(&mut data_buf[..data_len]))); - } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_2 { - (Some(session), packet_type, fragments) - } else { - unreachable!() } } else { drop(session_map); // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 - incoming = self.0.unassociated_handshake_states.get(local_key_id); + incoming = self.0.unassociated_handshake_states.get(kid_recv); if let Some(incoming) = incoming.as_ref() { - Application::PrpDec::new(incoming.header_receive_key.as_ref()).decrypt_in_place( - (&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) + App::PrpDec::new(&incoming.hk_recv).decrypt_in_place( + (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) .try_into() .unwrap(), ); - let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(incoming_physical_packet); - app.event_log( - LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), - current_time, - ); - if packet_type != PACKET_TYPE_NOISE_XK_PATTERN_3 { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + + let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; + let (packet_type, incoming_counter) = from_nonce(&nonce); + + {//vrfy + log!(app, ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count)); + if packet_type != PACKET_TYPE_HANDSHAKE_COMPLETION || incoming_counter != 0 { + return Err(byzantine_fault!(InvalidPacket, true)) + } } + let fragments = if fragment_count > 1 { - incoming.noise_pattern3_defrag.lock().unwrap().assemble( - header_nonce, - incoming_physical_packet_buf, + incoming.defrag.lock().unwrap().assemble( + &nonce, + incoming_fragment_buf, fragment_no, fragment_count, &mut assembled_packet, @@ -789,850 +373,59 @@ impl Context { if !assembled_packet.is_empty() { assembled_packet.as_ref() } else { - return Ok(ReceiveResult::Unassociated); + return Ok(ReceiveOk::Unassociated); } } else { - std::array::from_ref(&incoming_physical_packet_buf) + std::slice::from_ref(&incoming_fragment_buf) }; // We must guarantee that this incoming handshake is processed once and only // once. This prevents catastrophic nonce reuse caused by multithreading. - if self.0.unassociated_handshake_states.remove(local_key_id) { - (None, PACKET_TYPE_NOISE_XK_PATTERN_3, fragments) + if self.0.unassociated_handshake_states.remove(kid_recv) { + (None, PACKET_TYPE_HANDSHAKE_COMPLETION, fragments) } else { - return Ok(ReceiveResult::Unassociated); + return Ok(ReceiveOk::Unassociated); } } else { // This can occur naturally because either Bob's incoming_sessions cache got // full so Alice's incoming session was dropped, or the session this packet // was for was dropped by the application. - return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); + return Err(byzantine_fault!(UnknownLocalKeyId, true)); } } } else { - let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(incoming_physical_packet); - app.event_log( - LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), - current_time, - ); - if packet_type != PACKET_TYPE_NOISE_XK_PATTERN_1 && packet_type != PACKET_TYPE_BOB_DOS_CHALLENGE { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); + let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; + let (packet_type, incoming_counter) = from_nonce(&nonce); + + {//vrfy + log!(app, ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count)); + if packet_type != PACKET_TYPE_HANDSHAKE_HELLO && packet_type != PACKET_TYPE_CHALLENGE { + return Err(byzantine_fault!(InvalidPacket, true)) + } } + let fragments = if fragment_count > 1 { self.0.unassociated_defrag_cache.lock().unwrap().assemble( - header_nonce, + &nonce, remote_address, - incoming_physical_packet_len - HEADER_SIZE, - incoming_physical_packet_buf, + incoming_fragment.len() - HEADER_SIZE, + incoming_fragment_buf, fragment_no, fragment_count, - Application::RETRY_INTERVAL_MS, + App::SETTINGS.resend_time as i64, current_time, &mut assembled_packet, ); if !assembled_packet.is_empty() { assembled_packet.as_ref() } else { - return Ok(ReceiveResult::Unassociated); + return Ok(ReceiveOk::Unassociated); } } else { - std::array::from_ref(&incoming_physical_packet_buf) + std::array::from_ref(&incoming_fragment_buf) }; (None, packet_type, fragments) } }; - - debug_assert!(!fragments.is_empty()); - debug_assert!(incoming.is_none() || session.is_none()); - - let message = &mut [0u8; MAX_NOISE_HANDSHAKE_SIZE]; - let message_size = assemble_fragments_into::(fragments, message)?; - if message_size < MIN_PACKET_SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - use OfferStateMachine::*; - match packet_type { - PACKET_TYPE_NOISE_XK_PATTERN_1 => { - // Alice (remote) --> Bob (local) - // -> e, es, e1 - app.event_log(LogEvent::ReceiveUncheckedXK1, current_time); - - if session.is_some() || incoming.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if !(NoiseXKPattern1::MIN_SIZE..=NoiseXKPattern1::MAX_SIZE).contains(&message_size) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - // The message id must be the first 8 bytes of the gcm tag. - // This forces the message id to be authenticated along with the entire message. - let p_auth_end = message_size - ChallengeResponse::SIZE; - if message[8..16] != message[p_auth_end - 8..p_auth_end] { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - let p_size = p_auth_end - NoiseXKPattern1::P_ENC_START - AES_GCM_TAG_SIZE; - let total_ratchet_fingerprints = p_size / RATCHET_SIZE; - if p_size % RATCHET_SIZE != 0 || total_ratchet_fingerprints > 2 { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); - if let Some(remote_key_id) = NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.alice_key_id)) { - let sha512 = &mut Application::Hash::new(); - // Let application filter incoming connection attempts by whatever criteria it wants. - // This should ideally prevent ZSSP from wasting time on DDOS attacks. - match check_allow_incoming_session() { - IncomingSessionAction::Allow => {} - IncomingSessionAction::Challenge => { - let response: &ChallengeResponse = byte_array_as_proto_buffer(&message[p_auth_end..message_size]); - let counter = u64::from_be_bytes(response.challenge_counter.try_into().unwrap()); - - sha512.reset(); - let mut hasher = ShaHasher(sha512); - let mut output = [0u8; HASHLEN]; - hasher.0.update(&response.challenge_counter); - remote_address.hash(&mut hasher); - hasher.0.update(&self.0.challenge_salt); - hasher.0.finish(&mut output); - let is_valid = self.check_challenge_window(counter) - && secure_eq(&output[..CHALLENGE_MAC_SIZE], &response.challenge_mac) - && verify_pow::(hasher.0, &message[p_auth_end..message_size]) - && self.update_challenge_window(counter); - app.event_log(LogEvent::ReceiveCheckXK1Challenge(is_valid), current_time); - if !is_valid { - // Alice failed the challenge so issue them a new challenge. - let mut challenge_buffer = [0u8; BobDOSChallenge::SIZE]; - let challenge: &mut BobDOSChallenge = byte_array_as_proto_buffer_mut(&mut challenge_buffer); - challenge.alice_key_id = remote_key_id.get().to_ne_bytes(); - // We attach a monotonically increasing counter value to the challenge - // so it cannot be replayed. - let counter = self.0.challenge_counter.fetch_add(1, Ordering::Relaxed); - challenge.challenge_counter = counter.to_be_bytes(); - - hasher.0.reset(); - hasher.0.update(&counter.to_be_bytes()); - remote_address.hash(&mut hasher); - hasher.0.update(&self.0.challenge_salt); - hasher.0.finish(&mut output); - challenge.challenge_mac.copy_from_slice(&output[..CHALLENGE_MAC_SIZE]); - challenge.prior_challenge_pow = response.challenge_pow; - // We haven't decrypted any of Alice's packet so we don't know the - // header protection cipher. - // For DOS resistance Alice will not accept unencrypted headers directly - // into their session defrag buffer, so we have to send them this reply - // through their incoming sessions cache. - send_with_fragmentation( - &mut send_unassociated_reply, - send_unassociated_mtu, - &mut challenge_buffer, - PACKET_TYPE_BOB_DOS_CHALLENGE, - None, - self.0.rng.lock().unwrap().next_u64(), - None::<&Application::PrpEnc>, - ); - return Ok(ReceiveResult::Unassociated); - } - // Alice succeeded at the challenge so continue to decryption. - } - IncomingSessionAction::Drop => return Ok(ReceiveResult::Rejected), - } - - // Noise process handshake prologue. - let noise_h = mix_hash( - sha512, - &INITIAL_H, - &message[NoiseXKPattern1::PROLOGUE_START..NoiseXKPattern1::PROLOGUE_END], - ); - let noise_h = mix_hash(sha512, &noise_h, self.0.static_keypair.public_key_bytes()); - // Noise process pattern1 e token. - let mut noise_ck = SymmetricState::new(INITIAL_H); - let hmac = &mut Application::HmacHash::new(); - let mut noise_es = Secret::new(); - let noise_e_pattern1 = from_bytes_agreement::(&noise_pattern1.noise_e, &self.0.static_keypair, noise_es.as_mut()) - .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; - let noise_h_e = mix_hash(sha512, &noise_h, &noise_pattern1.noise_e); - noise_ck.mix_key(hmac, &noise_pattern1.noise_e); - // Noise process pattern1 es token. - let noise_k_es = noise_ck.mix_key_initialize_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 e1 token. - let (is_auth, noise_h_ee1) = decrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_e, - packet_type, - 0, - &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], - ); - if !is_auth { - // This could occur naturally if Alice's ApplicationLayer is dynamically - // changing their mtu, which in bad network conditions could clobber their - // resent KEX packet. - // Or maybe Alice randomly generated the same temporary id twice in a row. - // Since these situations are super unlikely to occur we still mark this error - // as unnatural. - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - // Noise process pattern1 payload. - let (is_auth, noise_h_ee1p) = decrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_ee1, - packet_type, - 1, - &mut message[NoiseXKPattern1::P_ENC_START..p_auth_end], - ); - drop(noise_k_es); - if !is_auth { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); - // Get ratchet key. - let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); - let mut ratchet_state = RatchetState::Null; - for i in 0..total_ratchet_fingerprints { - match app.restore_by_fingerprint( - (&noise_pattern1.payload[i * RATCHET_SIZE..(i + 1) * RATCHET_SIZE]).try_into().unwrap(), - current_time, - ) { - Ok(RatchetState::Null) | Ok(RatchetState::Empty) => {} - Ok(rs) => { - ratchet_state = rs; - break; - } - Err(e) => return Err(ReceiveError::RatchetIoError(e)), - } - } - if ratchet_state.is_null() { - if app.hello_requires_recognized_ratchet(current_time) { - return Ok(ReceiveResult::Rejected); - } - ratchet_state = RatchetState::Empty; - } - - // Start of Noise XKhfs+psk2 pattern2. - let mut message2 = [0u8; NoiseXKPattern2::SIZE]; - let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); - // Noise process pattern2 e token. - let noise_e_pattern2_secret = Application::KeyPair::generate(&mut self.0.rng.lock().unwrap()); - noise_pattern2.noise_e = *noise_e_pattern2_secret.public_key_bytes(); - let noise_h_ee1pe = mix_hash(sha512, &noise_h_ee1p, &noise_pattern2.noise_e); - noise_ck.mix_key(hmac, &noise_pattern2.noise_e); - // Noise process pattern2 ee token. - let mut noise_ee = Secret::new(); - if !noise_e_pattern2_secret.agree(&noise_e_pattern1, noise_ee.as_mut()) { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - let noise_k_esee = noise_ck.mix_key_initialize_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 ekem1 token. - let (noise_ekem1, noise_ekem1_secret) = pqc_kyber::encapsulate(&noise_pattern1.noise_e1, self.0.rng.lock().unwrap().deref_mut()) - .map_err(|_| byzantine_fault!(FaultType::FailedAuthentication, false)) - .map(|(ct, ekem1)| (ct, Secret(ekem1)))?; - // Alice fully authenticated. - noise_pattern2.noise_ekem1 = noise_ekem1; - let noise_h_ee1peekem1 = encrypt_and_hash::( - sha512, - &noise_k_esee, - &noise_h_ee1pe, - PACKET_TYPE_NOISE_XK_PATTERN_2, - 0, - &mut message2[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], - ); - drop(noise_k_esee); - noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); - drop(noise_ekem1_secret); - // Noise process pattern2 psk token. - let ratchet_key = ratchet_state.key().unwrap(); - let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); - let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); - // Noise process pattern2 payload. - // We try to prevent the id we generate from colliding with another session but - // because we might have handshakes in flight it's impossible to 100% prevent. - // In those exceedingly rare cases we have to drop Alice's session and start over. - let local_key_id = generate_key_id(&self.0.session_map.read().unwrap(), &mut self.0.rng.lock().unwrap()); - let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); - noise_pattern2.bob_key_id = local_key_id.get().to_ne_bytes(); - - let noise_h_ee1peekem1pskp = encrypt_and_hash::( - sha512, - &noise_k_eseeekem1psk, - &noise_h_ee1peekem1psk, - PACKET_TYPE_NOISE_XK_PATTERN_2, - 0, - &mut message2[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END], - ); - - app.event_log(LogEvent::ReceiveValidXK1, current_time); - let handshake = Arc::new(NoiseXKBobHandshakeState { - local_key_id, - remote_key_id, - ratchet_state, - noise_h_ee1peekem1pskp, - noise_ck_eseeekem1psk: noise_ck.clone(), - noise_k_eseeekem1psk: noise_k_eseeekem1psk.clone(), - noise_e_secret: noise_e_pattern2_secret, - header_receive_key: header_a2b_key.clone(), - header_send_key: header_b2a_key.clone(), - noise_pattern3_defrag: Mutex::new(Fragged::new()), - }); - self.0.unassociated_handshake_states.insert(local_key_id, handshake, current_time); - - // We put a copy of the gcm tag in the header so Alice can tell this packet apart - // from any other pattern 1 packet we send, without having to make Bob maintain state. - let mut pattern2_id = 0u64.to_ne_bytes(); - pattern2_id[5] = message2[NoiseXKPattern2::P_AUTH_END - 3]; - pattern2_id[6] = message2[NoiseXKPattern2::P_AUTH_END - 2]; - pattern2_id[7] = message2[NoiseXKPattern2::P_AUTH_END - 1]; - send_with_fragmentation( - &mut send_unassociated_reply, - send_unassociated_mtu, - &mut message2, - PACKET_TYPE_NOISE_XK_PATTERN_2, - Some(remote_key_id), - u64::from_be_bytes(pattern2_id), - Some(&Application::PrpEnc::new(header_b2a_key.first_n::())), - ); - - return Ok(ReceiveResult::Unassociated); - } else { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - } - PACKET_TYPE_BOB_DOS_CHALLENGE => { - let message = &mut message[..message_size]; - app.event_log(LogEvent::ReceiveUncheckedDOSChallenge, current_time); - - // We expect Bob to only send this to us through our unassociated defrag cache. - if incoming.is_some() || session.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if message.len() != BobDOSChallenge::SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - let challenge: &BobDOSChallenge = byte_array_as_proto_buffer(message); - - if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(challenge.alice_key_id)) { - if let Some(session) = self.0.session_map.read().unwrap().get(&local_key_id).and_then(|s| s.0.upgrade()) { - // We don't need to hold the kex lock because we are not transitioning state. - let mut state = session.state.write().unwrap(); - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, .. } = &mut handshake_state.offer { - let response_raw = &mut noise_message[*noise_message_len - ChallengeResponse::SIZE..]; - - let response: &mut ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); - // Only people who know what Alice's prior pow was can convince us to - // compute a new pow. - if challenge.prior_challenge_pow != response.challenge_pow { - // This can occur if Bob sends us multiple challenges and they - // arrive OOO. - return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); - } - response.challenge_counter.copy_from_slice(&challenge.challenge_counter); - response.challenge_mac.copy_from_slice(&challenge.challenge_mac); - let mut pow = self.0.rng.lock().unwrap().next_u64(); - let sha512 = &mut Application::Hash::new(); - loop { - let response: &mut ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); - response.challenge_pow.copy_from_slice(&pow.to_be_bytes()); - if verify_pow::(sha512, response_raw) { - break; - } - pow = pow.wrapping_add(1); - } - - app.event_log(LogEvent::ReceiveValidDOSChallenge(&session), current_time); - return Ok(ReceiveResult::Unassociated); - } else { - // This could happen if Bob challenges Alice, but their challenge packet - // gets massively delayed. - return Err(byzantine_fault!(FaultType::OutOfSequence, true)); - } - } else { - // This could happen if Bob challenges Alice, but their challenge packet - // gets massively delayed. - return Err(byzantine_fault!(FaultType::OutOfSequence, true)); - } - } else { - // This can occur naturally if Alice's session was dropped. - return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); - } - } else { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - } - PACKET_TYPE_NOISE_XK_PATTERN_2 => { - // Bob (remote) --> Alice (local) - // <- e, ee, ekem1, psk - let message = &mut message[..message_size]; - app.event_log(LogEvent::ReceiveUncheckedXK2, current_time); - - if incoming.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if message.len() != NoiseXKPattern2::SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - let session = session.ok_or(byzantine_fault!(FaultType::UnknownLocalKeyId, false))?; - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); - - if let NoiseXKPattern1or3(handshake_state) = &state.outgoing_offer { - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { - noise_h_ee1p, noise_e_secret, noise_e1_secret, noise_ck_es, .. - } = &handshake_state.offer - { - let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); - // Authenticate header counter. - if noise_pattern2.header[13..16] != noise_pattern2.p_gcm_tag[13..16] { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - // Noise process pattern2 e token. - let mut noise_ee = Secret::new(); - if let Some(noise_e_pattern2) = - from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) - { - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let mut noise_ck = noise_ck_es.clone(); - let noise_h_ee1pe = mix_hash(sha512, noise_h_ee1p, noise_e_pattern2.as_bytes()); - noise_ck.mix_key(hmac, noise_e_pattern2.as_bytes()); - // Noise process pattern2 ee token. - let noise_k_esee = noise_ck.mix_key_initialize_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 ekem1 token. - let (is_auth, noise_h_ee1peekem1) = decrypt_and_hash::( - sha512, - &noise_k_esee, - &noise_h_ee1pe, - packet_type, - 0, - &mut message[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], - ); - let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); - let noise_ekem1_secret = pqc_kyber::decapsulate(&noise_pattern2.noise_ekem1, noise_e1_secret.as_ref()).map(Secret); - if let Some(Ok(noise_ekem1_secret)) = is_auth.then_some(noise_ekem1_secret) { - noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); - drop(noise_ekem1_secret); - - // We attempt to decrypt the payload at most three times. First two times with - // the ratchet key Alice remembers, and final time with a ratchet - // key of zero if Alice allows ratchet downgrades. - // The following code is not constant time, meaning we leak to an - // attacker whether or not we downgraded. - // We don't currently consider this sensitive enough information to hide. - let mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState, Secret, [u8; 64])> { - // Check for which ratchet key Bob wants to use. - let mut noise_ck = noise_ck.clone(); - let mut payload = [0u8; NoiseXKPattern2::P_AUTH_END - NoiseXKPattern2::P_ENC_START]; - payload.copy_from_slice(&message[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END]); - // Noise process pattern2 psk token. - let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); - let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); - // Noise process pattern2 payload. - let (is_auth, noise_h_ee1peekem1pskp) = decrypt_and_hash::( - sha512, - &noise_k_eseeekem1psk, - &noise_h_ee1peekem1psk, - packet_type, - 0, - &mut payload, - ); - if is_auth { - let key_id = NonZeroU32::new(u32::from_ne_bytes( - (&payload[..NoiseXKPattern2::P_AUTH_START - NoiseXKPattern2::P_ENC_START]) - .try_into() - .unwrap(), - )); - key_id.map(|kid| (kid, noise_ck, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) - } else { - None - } - }; - // Check first key. - let mut ratchet_i = 0; - let mut result = None; - let mut chain_len = 0; - if let Some(key) = state.ratchet_states[0].key() { - chain_len = state.ratchet_states[0].chain_len(); - result = test_ratchet_key(key); - } - // Check second key. - if result.is_none() { - ratchet_i = 1; - if let Some(key) = state.ratchet_states[1].key() { - chain_len = state.ratchet_states[1].chain_len(); - result = test_ratchet_key(key); - } - } - // Check zero key. - if result.is_none() && !app.initiator_disallows_downgrade(&session, current_time) { - chain_len = 0; - result = test_ratchet_key(&[0u8; RATCHET_SIZE]); - if result.is_some() { - // TODO: add some kind of warning callback or signal. - } - } - - if let Some((remote_key_id, mut noise_ck, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) = result { - // Start of Noise XKhfs+psk2 pattern3. - let mut message3 = [0u8; NoiseXKPattern3::MAX_SIZE]; - // Noise process pattern3 s token. - let mut noise_se = Secret::new(); - if self.0.static_keypair.agree(&noise_e_pattern2, noise_se.as_mut()) { - let payload = handshake_state.alice_identity_blob.as_ref(); - // Packet fully authenticated. - let s_enc_start = HEADER_SIZE; - let s_auth_start = s_enc_start + P384_PUBLIC_KEY_SIZE; - let p_enc_start = s_auth_start + AES_GCM_TAG_SIZE; - let p_auth_start = p_enc_start + payload.len(); - let p_auth_end = p_auth_start + AES_GCM_TAG_SIZE; - let message3_len = p_auth_end; - - message3[s_enc_start..s_auth_start].copy_from_slice(self.0.static_keypair.public_key_bytes()); - let noise_h_ee1peekem1pskps = encrypt_and_hash::( - sha512, - &noise_k_eseeekem1psk, - &noise_h_ee1peekem1pskp, - PACKET_TYPE_NOISE_XK_PATTERN_3, - 1, - &mut message3[s_enc_start..p_enc_start], - ); - drop(noise_k_eseeekem1psk); - // Noise process pattern3 se token. - let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern3 payload token. - message3[p_enc_start..p_auth_start].copy_from_slice(payload); - let noise_h_ee1peekem1pskpsp = encrypt_and_hash::( - sha512, - &noise_k_eseeekem1pskse, - &noise_h_ee1peekem1pskps, - PACKET_TYPE_NOISE_XK_PATTERN_3, - 0, - &mut message3[p_enc_start..p_auth_end], - ); - drop(noise_k_eseeekem1pskse); - // Alice finished Noise XKhfs+psk2 handshake. - // Transition offer state machine to the NoiseXKPattern3 state. - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); - let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(chain_len + 1).unwrap()); - - let ratchet_to_preserve = &state.ratchet_states[ratchet_i]; - let result = app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&new_ratchet_state, ratchet_to_preserve], - current_time, - ); - if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); - } - - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); - - let local_key_id = handshake_state.local_key_id; - drop(state); - let mut state = session.state.write().unwrap(); - session - .kex_send_cipher - .lock() - .unwrap() - .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); - session - .kex_receive_cipher - .lock() - .unwrap() - .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); - state.ratchet_states[1] = state.ratchet_states[ratchet_i].clone(); - state.ratchet_states[0] = new_ratchet_state; - - state.cipher_states[0].replace(SessionKey::new( - hmac, - noise_ck, - local_key_id, - remote_key_id, - INIT_COUNTER, - false, - )); - debug_assert!(state.cipher_states[1].is_none()); - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - handshake_state.next_retry_time = - AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); - handshake_state.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); - handshake_state.offer = NoiseXKAliceHandshakeState::NoiseXKPattern3 { - noise_message: message3, - noise_message_len: p_auth_end, - }; - } - drop(state); - drop(kex_lock); - - if let Some((mut send, mut mtu)) = send_to(&session) { - mtu = mtu.max(MIN_TRANSPORT_MTU); - send_with_fragmentation( - &mut send, - mtu, - &mut message3[..message3_len], - PACKET_TYPE_NOISE_XK_PATTERN_3, - Some(remote_key_id), - 0, - Some(&session.header_send_cipher), - ); - } - app.event_log(LogEvent::ReceiveValidXK2(&session), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); - } - } - } - } - // Bob failed authentication so we must restart our offer according to Noise. - // We restart the offer instead of dropping the session to defend against DOS. - drop(state); - let mut state = session.state.write().unwrap(); - let ratchet_state = state.ratchet_states.clone(); - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if !handshake_state.reinitialize( - &session, - &ratchet_state, - &mut self.0.session_map.write().unwrap(), - &mut self.0.rng.lock().unwrap(), - current_time, - ) { - session.expire() - } - } - drop(state); - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } else { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - } else { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - } - PACKET_TYPE_NOISE_XK_PATTERN_3 => { - // Alice (remote) --> Bob (local) - // -> s, se - let message = &mut message[..message_size]; - app.event_log(LogEvent::ReceiveUncheckedXK3, current_time); - - if session.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if message.len() < NoiseXKPattern3::MIN_SIZE || message.len() > NoiseXKPattern3::MAX_SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - // The code above guarantees to us that each `incoming` handshake state that reaches - // this point will be strictly unique, even for the same remote peer. - // This property is strictly necessary to prevent catastrophic nonce reuse due to - // two session being created with the same set of keys. - let handshake_state = incoming.ok_or(byzantine_fault!(FaultType::UnknownLocalKeyId, false))?; - let s_enc_start = HEADER_SIZE; - - let s_auth_start = s_enc_start + P384_PUBLIC_KEY_SIZE; - let p_enc_start = s_auth_start + AES_GCM_TAG_SIZE; - let p_auth_end = message.len(); - let p_auth_start = p_auth_end - AES_GCM_TAG_SIZE; - - if !(p_enc_start <= p_auth_start) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - // Do not read from the message before this point, otherwise an array out of bounds - // error is possible. - // Noise process pattern3 s token. - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let (is_auth, noise_h_ee1peekem1pskps) = decrypt_and_hash::( - sha512, - &handshake_state.noise_k_eseeekem1psk, - &handshake_state.noise_h_ee1peekem1pskp, - packet_type, - 1, - &mut message[s_enc_start..p_enc_start], - ); - if !is_auth { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - // Noise process pattern3 se token. - let mut noise_se = Secret::new(); - if let Some(remote_s_public_key) = - from_bytes_agreement::(&message[s_enc_start..s_auth_start], &handshake_state.noise_e_secret, noise_se.as_mut()) - { - let mut noise_ck = handshake_state.noise_ck_eseeekem1psk.clone(); - let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern3 payload. - let (is_auth, noise_h_ee1peekem1pskpsp) = decrypt_and_hash::( - sha512, - &noise_k_eseeekem1pskse, - &noise_h_ee1peekem1pskps, - packet_type, - 0, - &mut message[p_enc_start..p_auth_end], - ); - drop(noise_k_eseeekem1pskse); - if !is_auth { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - // Bob finished Noise XKhfs+psk2 handshake. - let header_send_cipher = Application::PrpEnc::new(handshake_state.header_send_key.as_ref()); - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); - let mut send_reject = || { - // We just used a counter with this key, but we are not storing - // the fact we used it in memory. This is currently ok because the - // handshake is being dropped, so nonce reuse can't happen. - let (mut fragment, len) = encrypt_control( - &mut Application::AeadEnc::new(kex_key_b2a.as_ref()), - &header_send_cipher, - PACKET_TYPE_SESSION_REJECTED, - INIT_COUNTER, - handshake_state.remote_key_id.get(), - &[], - ); - send_unassociated_reply(&mut fragment[..len]); - }; - - let (responder_disallows_downgrade, responder_silently_rejects) = check_accept_session( - &remote_s_public_key, - &message[p_enc_start..p_auth_start], - handshake_state.ratchet_state.chain_len(), - ); - if let Some((responder_disallows_downgrade, application_data)) = responder_disallows_downgrade { - let result = app.restore_by_identity(&remote_s_public_key, &application_data, current_time); - match result { - Ok(true_ratchet_states) => { - let mut has_match = false; - for rs in &true_ratchet_states { - if !rs.is_null() { - has_match |= &handshake_state.ratchet_state == rs; - } - } - if !has_match { - if !responder_disallows_downgrade && handshake_state.ratchet_state.is_empty() { - // TODO: add some kind of warning callback or signal. - } else { - if !responder_silently_rejects { - send_reject(); - } - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - } - - let mut noise_kk_ss = Secret::new(); - if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); - let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); - - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); - // We must make sure the ratchet key is saved before we transition. - let new_ratchet_state = - RatchetState::new_nonempty(rk, rf, NonZeroU64::new(handshake_state.ratchet_state.chain_len() + 1).unwrap()); - let result = app.save_ratchet_state( - &remote_s_public_key, - &application_data, - [&true_ratchet_states[0], &true_ratchet_states[1]], - [&new_ratchet_state, &RatchetState::Null], - current_time, - ); - if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); - } - - let mut session_queue = self.0.session_queue.lock().unwrap(); - let queue_idx = session_queue.reserve_index(); - let session = Arc::new(Session { - context: Arc::downgrade(&self.0), - queue_idx, - application_data, - remote_static_key: remote_s_public_key, - send_counter: AtomicU64::new(INIT_COUNTER), - session_has_expired: AtomicBool::new(false), - counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - state_machine_lock: Mutex::new(()), - state: RwLock::new(SessionMutableState { - ratchet_states: [new_ratchet_state.clone(), RatchetState::Null], - cipher_states: [ - Some(SessionKey::new( - hmac, - noise_ck, - handshake_state.local_key_id, - handshake_state.remote_key_id, - INIT_COUNTER, - true, - )), - None, - ], - current_key: 0, - outgoing_offer: KeyConfirm { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - }, - }), - header_receive_cipher: Application::PrpDec::new(handshake_state.header_receive_key.as_ref()), - header_send_cipher, - kex_send_cipher: Mutex::new(Some(Application::AeadEnc::new(kex_key_b2a.as_ref()))), - kex_receive_cipher: Mutex::new(Some(Application::AeadDec::new(kex_key_a2b.as_ref()))), - noise_kk_ss, - noise_kk_local_init_h, - noise_kk_remote_init_h, - defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - was_bob: true, - }); - let timer = Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); - session_queue.push_reserved(queue_idx, Arc::downgrade(&session), timer); - drop(session_queue); - // There is the miniscule possibility this key id is already - // in use, in which case we have to drop this session like - // nothing ever happened. - let mut session_map = self.0.session_map.write().unwrap(); - if let std::collections::hash_map::Entry::Vacant(e) = session_map.entry(handshake_state.local_key_id) { - e.insert((Arc::downgrade(&session), false)); - drop(session_map); - let _ = - session.send_control(&session.state.read().unwrap(), send_unassociated_reply, PACKET_TYPE_KEY_CONFIRM, &[]); - - app.event_log(LogEvent::ReceiveValidXK3(&session.application_data), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::NewSession)); - } else { - // This can occur if we accidentally generate a key id collision. - // There is an extremely short amount of time during which - // another session can steal this session's id, we'll have to - // restart the handshake in this case. - return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); - } - } - Err(e) => { - return Err(ReceiveError::RatchetIoError(e)); - } - } - } else { - if !responder_silently_rejects { - send_reject(); - } - return Ok(ReceiveResult::Rejected); - } - } else { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - } - _ => return Err(byzantine_fault!(FaultType::InvalidPacket, false)), - } - } - /// Helper function for sending the empty string over the session. Useful for keep-alives. - /// - /// * `session` - The session to send to - /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a - /// slice of `data` - /// * `current_time` - Current time in milliseconds - pub fn send_empty(&self, session: &Arc>, send: impl FnMut(&mut [u8]) -> bool, current_time: i64) -> Result<(), SendError> { - self.send(session, send, &mut [0u8; MIN_TRANSPORT_MTU], &[], current_time) } /// Send data over the session. /// @@ -1644,7 +437,7 @@ impl Context { /// * `current_time` - Current time in milliseconds pub fn send( &self, - session: &Arc>, + session: &Arc>, mut send: impl FnMut(&mut [u8]) -> bool, mtu_sized_buffer: &mut [u8], mut data: &[u8], @@ -1710,983 +503,229 @@ impl Context { } Ok(()) } - /// Update the challenge window, returning true if the challenge is still valid. - fn check_challenge_window(&self, counter: u64) -> bool { - let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.load(Ordering::Relaxed); - prev_counter < counter - } - /// Update the challenge window, returning true if the challenge is still valid. - fn update_challenge_window(&self, counter: u64) -> bool { - let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); - prev_counter < counter - } -} -/// Initiate the rekeying protocol. This session will now begin attempting to rekey this session -/// with its peer, if it was not already. -fn initiate_rekey( - context: &Arc>, - session: &Arc>, - send: impl FnOnce(&mut [u8]) -> bool, - current_time: i64, -) -> Result { - let mut message = [0u8; NoiseKKPattern1or2::SIZE]; - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); - // We may only attempt to rekey if we are not already doing so. - match &state.outgoing_offer { - OfferStateMachine::Normal { .. } => (), - _ => return Err(()), - } - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - // Start of Noise KKpsk0 pattern1. - // Noise process pattern1 psk0 token. - let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_states[0].key().unwrap()); - let noise_h_psk = mix_hash(sha512, &session.noise_kk_local_init_h, &noise_temp_h); - // Noise process pattern1 e token. - let noise_e_secret = Application::KeyPair::generate(&mut context.rng.lock().unwrap()); - let noise_h_pske = mix_hash(sha512, &noise_h_psk, noise_e_secret.public_key_bytes()); - noise_ck.mix_key(hmac, noise_e_secret.public_key_bytes()); + /// Perform periodic background service and cleanup tasks. + /// + /// This returns the number of milliseconds until it should be called again. The caller should + /// try to satisfy this but small variations in timing of up to +/- a second or two are not + /// a problem. + /// + /// * `send_to` - Function to get a sender and an MTU to send something over an active session + /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced + /// with remote peers (although both of these properties would help reliability slightly). + /// Used to determine if any current handshakes should be resent or timed-out, or if a session + /// should rekey. + pub fn service bool>( + &self, + app: &App, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + current_time: i64, + ) -> i64 { + let retry_next = current_time.saturating_add(App::RETRY_INTERVAL_MS); + let mut next_service_time = 2 * App::RETRY_INTERVAL_MS; - let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); - noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); - // Noise process pattern1 es token. - let mut noise_es = Secret::new(); - if !noise_e_secret.agree(&session.remote_static_key, noise_es.as_mut()) { - return Err(()); - } - noise_ck.mix_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 ss token. - let noise_k_pskesss = noise_ck.mix_key_initialize_key(hmac, session.noise_kk_ss.as_ref()); - // Noise process pattern1 payload token. - let mut session_map = context.session_map.write().unwrap(); - let new_key_id = generate_key_id(&session_map, &mut context.rng.lock().unwrap()); - let next_key_index = state.current_key ^ 1; - session_map.insert(new_key_id, (Arc::downgrade(session), next_key_index > 0)); - drop(session_map); - - let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); - noise_pattern1.key_id = new_key_id.get().to_ne_bytes(); - let noise_h_pskep = encrypt_and_hash::( - sha512, - &noise_k_pskesss, - &noise_h_pske, - PACKET_TYPE_NOISE_KK_PATTERN_1, - 0, - &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - drop(noise_k_pskesss); - - drop(state); - let mut state = session.state.write().unwrap(); - state.outgoing_offer = OfferStateMachine::NoiseKKPattern1 { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - new_key_id, - noise_e_secret, - noise_message: message.clone(), - noise_h_pskep, - noise_ck: noise_ck.clone(), - }; - drop(state); - drop(kex_lock); - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_NOISE_KK_PATTERN_1, &message); - Ok(current_time.saturating_add(Application::RETRY_INTERVAL_MS)) -} -fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mut [u8]) -> bool>( - context: &Context, - session: Arc>, - app: &Application, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, - packet_type: u8, - counter: u64, - fragment: &mut [u8], - current_time: i64, -) -> Result, ReceiveError> { - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); - let mut c = session.kex_receive_cipher.lock().unwrap(); - let message = decrypt_control( - c.as_mut().ok_or(byzantine_fault!(FaultType::OutOfSequence, false))?, - packet_type, - counter, - fragment, - )?; - drop(c); - session.update_receive_window(counter); - use OfferStateMachine::*; - return match packet_type { - PACKET_TYPE_SESSION_REJECTED => { - if let NoiseXKPattern1or3(_) = &state.outgoing_offer { - drop(state); - let mut state = session.state.write().unwrap(); - state.outgoing_offer = OfferStateMachine::Null; - drop(state); - drop(kex_lock); - Ok(ReceiveResult::Session(session, SessionEvent::Rejected)) - } else { - // This can occur naturally because of control packet resends. - Err(byzantine_fault!(FaultType::OutOfSequence, true)) + let mut session_queue = self.0.session_queue.lock().unwrap(); + // This update system takes heavy advantage of the fact that sessions only need to be updated + // either roughly every second or roughly every hour. That big gap allows for minor optimizations. + // If the gap changes (unlikely) this code may need to be rewritten. + while let Some((session, timer, queue_idx)) = session_queue.peek() { + if timer.0 >= current_time { + next_service_time = next_service_time.min(timer.0 - current_time); + break; } - } - PACKET_TYPE_KEY_CONFIRM => { - drop(state); - app.event_log(LogEvent::ReceiveValidKeyConfirm(&session), current_time); - let mut state = session.state.write().unwrap(); - // We only want to stop sending NoiseKKPattern2 offers when the latest derived - // key is confirmed. And we only want to do that once. - let (used_latest_key, try_delete, ret) = match &state.outgoing_offer { - NoiseKKPattern2 { .. } => (true, true, SessionEvent::Control), - NoiseXKPattern1or3(handshake_state) => { - if let NoiseXKAliceHandshakeState::NoiseXKPattern3 { .. } = &handshake_state.offer { - (true, true, SessionEvent::Established) - } else { - (false, false, SessionEvent::Control) - } + let session = match session.upgrade() { + Some(s) => s, + _ => { + session_queue.remove(queue_idx); + continue; } - Null => (false, false, SessionEvent::Control), - _ => (true, false, SessionEvent::Control), }; - if try_delete { - let result = if !state.ratchet_states[1].is_null() { - app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&state.ratchet_states[0], &RatchetState::Null], - current_time, - ) - } else { - Ok(()) - }; - if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); - } - if let NoiseKKPattern2 { kex_send_key, .. } = &state.outgoing_offer { - session - .kex_send_cipher - .lock() - .unwrap() - .replace(Application::AeadEnc::new(kex_send_key.as_ref())); - } - state.ratchet_states[1] = RatchetState::Null; - state.current_key ^= 1; - state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); - } - drop(state); - drop(kex_lock); - if used_latest_key { - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_ACK, &[]); - } - } - Ok(ReceiveResult::Session(session, ret)) - } - PACKET_TYPE_ACK => { - if let KeyConfirm { .. } = &state.outgoing_offer { - drop(state); - app.event_log(LogEvent::ReceiveValidAck(&session), current_time); - let mut state = session.state.write().unwrap(); - // Check if we should end any current offers and transition back to Normal state - state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); - drop(kex_lock); - drop(state); - Ok(ReceiveResult::Session(session, SessionEvent::Control)) - } else { - // This can occur naturally because of control packet resends. - Err(byzantine_fault!(FaultType::OutOfSequence, true)) - } - } - PACKET_TYPE_NOISE_KK_PATTERN_1 => { - app.event_log(LogEvent::ReceiveUncheckedKK1, current_time); - let message = &mut message[..NoiseKKPattern1or2::SIZE]; - let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - // We need the following operation to be atomic with the change of offer type - let (should_rekey_as_bob, chosen_id) = match &state.outgoing_offer { - // Check rekey rate limits. - Normal { .. } => (true, None), - // In the following situation, both parties are in state NoiseKKPattern1, - // we need to deterministically allow only one of them to transition to - // NoiseKKPattern2. - NoiseKKPattern1 { new_key_id, .. } => (session.was_bob, Some(*new_key_id)), - _ => (false, None), - }; - if !should_rekey_as_bob { - // This can be triggered if both parties attempt rekeying simultaneously, or if the - // remote party sent us a duplicate rekey request. - // The code above handles this case and only lets one party through to rekeying. - drop(state); - drop(kex_lock); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); - } - // Noise process pattern1 psk0 token. - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_states[0].key().unwrap()); - let noise_h_psk = mix_hash(sha512, &session.noise_kk_remote_init_h, &noise_temp_h); - // Noise process pattern1 e token. - // Get public key validation out of the way early - let mut noise_es = Secret::new(); - let mut noise_ee = Secret::new(); - let mut noise_se = Secret::new(); - if let Some(alice_e) = from_bytes_agreement::(&noise_pattern1.noise_e, &context.0.static_keypair, noise_es.as_mut()) { - let bob_e_secret = Application::KeyPair::generate(&mut context.0.rng.lock().unwrap()); - if bob_e_secret.agree(&alice_e, noise_ee.as_mut()) && bob_e_secret.agree(&session.remote_static_key, noise_se.as_mut()) { - let noise_h_pske = mix_hash(sha512, &noise_h_psk, alice_e.as_bytes()); - noise_ck.mix_key(hmac, alice_e.as_bytes()); - // Noise process pattern1 es token. - noise_ck.mix_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 ss token. - let noise_k_pskesss = noise_ck.mix_key_initialize_key(hmac, session.noise_kk_ss.as_ref()); - - // Noise process pattern1 payload. - let (is_auth, noise_h_pskep) = decrypt_and_hash::( - sha512, - &noise_k_pskesss, - &noise_h_pske, - packet_type, - 0, - &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.key_id))) { - // Alice fully authenticated. - // Start of Noise KKpsk0 pattern2. - // Noise process pattern2 e token. - let noise_h_pskepe = mix_hash(sha512, &noise_h_pskep, bob_e_secret.public_key_bytes()); - noise_ck.mix_key(hmac, bob_e_secret.public_key_bytes()); - // Noise process pattern2 ee token. - noise_ck.mix_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 se token. - let noise_k_pskessseese = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern2 payload. - let mut message2 = [0u8; NoiseKKPattern1or2::SIZE]; - let noise_pattern2: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message2); - noise_pattern2.noise_e = *bob_e_secret.public_key_bytes(); - let mut session_map = context.0.session_map.write().unwrap(); - // If we already generated a new key id mapping reuse it. - let new_key_id = chosen_id.unwrap_or_else(|| generate_key_id(&session_map, &mut context.0.rng.lock().unwrap())); - noise_pattern2.key_id = new_key_id.get().to_ne_bytes(); - - let noise_h_pskepep = encrypt_and_hash::( - sha512, - &noise_k_pskessseese, - &noise_h_pskepe, - PACKET_TYPE_NOISE_KK_PATTERN_2, - 0, - &mut message2[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - drop(noise_k_pskessseese); - // Bob finished Noise KKpsk0 handshake. - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); - let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); - let result = app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&new_ratchet_state, &state.ratchet_states[0]], - current_time, - ); - if let Err(e) = result { - drop(state); - drop(kex_lock); - return Err(ReceiveError::RatchetIoError(e)); - } - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); - // The new "Bob" doesn't know yet if Alice has received the new key, so the - // new key is recorded as the "alt" (key_index ^ 1) but the current key is - // not advanced yet. - let next_key_index = state.current_key ^ 1; - session_map.insert(new_key_id, (Arc::downgrade(&session), next_key_index > 0)); - if let Some(pre_id) = state.cipher_states[next_key_index].as_ref().map(|k| k.local_key_id) { - session_map.remove(&pre_id); - } - drop(session_map); + let state = session.state.read().unwrap(); + let next_timer = match &state.outgoing_offer { + Normal { timeout, .. } => { + if *timeout <= current_time { drop(state); - let mut state = session.state.write().unwrap(); - let current_counter = session.send_counter.load(Ordering::Relaxed); - session - .kex_receive_cipher - .lock() - .unwrap() - .replace(Application::AeadDec::new(kex_key_a2b.as_ref())); - state.ratchet_states[1] = state.ratchet_states[0].clone(); - state.ratchet_states[0] = new_ratchet_state.clone(); - - state.cipher_states[next_key_index].replace(SessionKey::new( - hmac, - noise_ck, - new_key_id, - remote_key_id, - current_counter, - true, - )); - let timer = current_time.saturating_add(Application::RETRY_INTERVAL_MS); - state.outgoing_offer = NoiseKKPattern2 { - next_retry_time: AtomicI64::new(timer), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - noise_message: message2, - kex_send_key: kex_key_b2a.clone(), - }; - drop(state); - drop(kex_lock); - context.0.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timer)); - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_NOISE_KK_PATTERN_2, &message2); + let result = initiate_rekey(&self.0, &session, send, current_time); + if result.is_ok() { + app.event_log(LogEvent::ServiceKKStart(&session), current_time); + } + result.unwrap_or(retry_next) + } else { + retry_next } - app.event_log(LogEvent::ReceiveValidKK1(&session), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); + } else { + *timeout } } - } - Err(byzantine_fault!(FaultType::FailedAuthentication, false)) - } - PACKET_TYPE_NOISE_KK_PATTERN_2 => { - app.event_log(LogEvent::ReceiveUncheckedKK2, current_time); - let message = &mut message[..NoiseKKPattern1or2::SIZE]; - let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - - if let NoiseKKPattern1 { new_key_id, noise_e_secret, noise_ck, noise_h_pskep, .. } = &state.outgoing_offer { - // Noise process pattern2 e token. - let mut noise_ee = Secret::new(); - let mut noise_se = Secret::new(); - if let Some(bob_e) = from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) { - if context.0.static_keypair.agree(&bob_e, noise_se.as_mut()) { - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let mut noise_ck = noise_ck.clone(); - let noise_h_pskepe = mix_hash(sha512, noise_h_pskep, bob_e.as_bytes()); - noise_ck.mix_key(hmac, bob_e.as_bytes()); - // Noise process pattern2 ee token. - noise_ck.mix_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 se token. - let noise_k_pskessseese = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern2 payload. - let (is_auth, noise_h_pskepep) = decrypt_and_hash::( - sha512, - &noise_k_pskessseese, - &noise_h_pskepe, - packet_type, - 0, - &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern2.key_id))) { - // Bob fully authenticated. - // Alice finished Noise KKpsk0 handshake. - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); - let new_ratchet_state = - RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); - let result = app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&new_ratchet_state, &RatchetState::Null], - current_time, - ); - if let Err(e) = result { - drop(state); - drop(kex_lock); - return Err(ReceiveError::RatchetIoError(e)); - } - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); - - let new_key_id = *new_key_id; + // If there's an outstanding attempt to open a session, retransmit this + // periodically in case the initial packet doesn't make it. + NoiseXKPattern1or3(handshake_state) => { + if let Some(ts) = process_timer(&handshake_state.next_retry_time, App::RETRY_INTERVAL_MS, current_time) { + ts + } else { + // We have to eventually time out NoiseXKPattern3 because of unreliable network conditions. + if handshake_state.timeout <= current_time { drop(state); + let _kex_lock = session.state_machine_lock.lock().unwrap(); let mut state = session.state.write().unwrap(); - let next_key_index = state.current_key ^ 1; - state.current_key = next_key_index; - if let Some(key) = state.cipher_states[next_key_index].as_ref() { - context.0.session_map.write().unwrap().remove(&key.local_key_id); + let ratchet_state = state.ratchet_states.clone(); + // Since we dropped the lock we must re-check if we are in the correct state. + if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { + if handshake_state.timeout <= current_time { + app.event_log(LogEvent::ServiceXKTimeout(&session), current_time); + handshake_state.reinitialize( + &session, + &ratchet_state, + &mut self.0.session_map.write().unwrap(), + &mut self.0.rng.lock().unwrap(), + current_time, + ); + } } - session - .kex_receive_cipher - .lock() - .unwrap() - .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); - session - .kex_send_cipher - .lock() - .unwrap() - .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); - state.ratchet_states[1] = RatchetState::Null; - state.ratchet_states[0] = new_ratchet_state.clone(); - - state.cipher_states[next_key_index].replace(SessionKey::new( - hmac, - noise_ck, - new_key_id, - remote_key_id, - session.send_counter.load(Ordering::Relaxed), - false, - )); - state.outgoing_offer = KeyConfirm { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - }; - drop(state); - drop(kex_lock); - // Let Bob know we got the key. - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_KEY_CONFIRM, &[]); + } else if let Some((mut send, mut mtu)) = send_to(&session) { + mtu = mtu.max(MIN_TRANSPORT_MTU); + match &handshake_state.offer { + NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } => { + app.event_log(LogEvent::ServiceXK1Resend(&session), current_time); + // We are in state NoiseXKPattern1 so resend noise_pattern1. + send_with_fragmentation( + &mut send, + mtu, + &mut noise_message.clone()[..*noise_message_len], + PACKET_TYPE_NOISE_XK_PATTERN_1, + None, + *message_id, + None::<&App::PrpEnc>, + ); + } + NoiseXKAliceHandshakeState::NoiseXKPattern3 { noise_message, noise_message_len, .. } => { + app.event_log(LogEvent::ServiceXK3Resend(&session), current_time); + send_with_fragmentation( + &mut send, + mtu, + &mut noise_message.clone()[..*noise_message_len], + PACKET_TYPE_NOISE_XK_PATTERN_3, + state.cipher_states[0].as_ref().map(|k| k.remote_key_id), + 0, + Some(&session.header_send_cipher), + ); + } } - app.event_log(LogEvent::ReceiveValidKK2(&session), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); } + retry_next } } - // Bob failed authentication so according to Noise we must terminate this - // handshake. - // This should not happen in practice since this packet will have already passed - // authentication under the current key. - session.expire(); - Err(byzantine_fault!(FaultType::FailedAuthentication, false)) - } else { - drop(state); - drop(kex_lock); - Ok(ReceiveResult::Session(session, SessionEvent::Control)) - } + NoiseKKPattern1 { next_retry_time, timeout, noise_message, .. } | NoiseKKPattern2 { next_retry_time, timeout, noise_message, .. } => { + if let Some(ts) = process_timer(next_retry_time, App::RETRY_INTERVAL_MS, current_time) { + ts + } else { + if *timeout <= current_time { + app.event_log(LogEvent::ServiceKKTimeout(&session), current_time); + next_retry_time.store(i64::MAX, Ordering::Relaxed); + drop(state); + session.expire_inner(&self.0, &mut session_queue); + } else { + let packet_type = if let NoiseKKPattern1 { .. } = &state.outgoing_offer { + app.event_log(LogEvent::ServiceKK1Resend(&session), current_time); + PACKET_TYPE_NOISE_KK_PATTERN_1 + } else { + app.event_log(LogEvent::ServiceKK2Resend(&session), current_time); + PACKET_TYPE_NOISE_KK_PATTERN_2 + }; + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&state, send, packet_type, noise_message); + } + } + retry_next + } + } + KeyConfirm { next_retry_time, timeout, .. } => { + if let Some(ts) = process_timer(next_retry_time, App::RETRY_INTERVAL_MS, current_time) { + ts + } else { + if *timeout <= current_time { + app.event_log(LogEvent::ServiceKeyConfirmTimeout(&session), current_time); + next_retry_time.store(i64::MAX, Ordering::Relaxed); + drop(state); + session.expire_inner(&self.0, &mut session_queue); + } else { + app.event_log(LogEvent::ServiceKeyConfirmResend(&session), current_time); + if let Some((send, _)) = send_to(&session) { + let _ = session.send_control(&state, send, PACKET_TYPE_KEY_CONFIRM, &[]); + } + } + retry_next + } + } + Null => retry_next, + }; + session_queue.change_priority(queue_idx, Reverse(next_timer)); } - _ => Err(byzantine_fault!(FaultType::InvalidPacket, false)), - }; + drop(session_queue); + + self.0 + .unassociated_defrag_cache + .lock() + .unwrap() + .check_for_expiry(App::INITIAL_OFFER_TIMEOUT_MS, current_time); + self.0.unassociated_handshake_states.service(current_time); + + next_service_time + } } impl Session { - /// This can only fail with `MaxKeyLifetimeExceeded` or `SessionNotEstablished`. - fn send_control( - &self, - state: &SessionMutableState, - send: impl FnOnce(&mut [u8]) -> bool, - packet_type: u8, - packet: &[u8], - ) -> Result<(), SendError> { - let key = state.cipher_states[state.current_key].as_ref().ok_or(SendError::SessionNotEstablished)?; - let counter = self.get_next_outgoing_counter()?; - let mut c = self.kex_send_cipher.lock().unwrap(); - let (mut fragment, len) = encrypt_control( - c.as_mut().ok_or(SendError::SessionNotEstablished)?, - &self.header_send_cipher, - packet_type, - counter, - key.remote_key_id.get(), - packet, - ); - send(&mut fragment[..len]); - Ok(()) - } - /// Check whether this session is established. - pub fn established(&self) -> bool { - let state = self.state.read().unwrap(); - !matches!(&state.outgoing_offer, OfferStateMachine::NoiseXKPattern1or3(_) | OfferStateMachine::Null) - } - /// The static public key of the remote peer. - pub fn remote_s_public_key(&self) -> &Application::PublicKey { - &self.remote_static_key - } - /// The current ratchet state of this session. - /// The returned values are sensitive and should be securely erased before being dropped. - pub fn ratchet_states(&self) -> [RatchetState; 2] { - let state = self.state.read().unwrap(); - state.ratchet_states.clone() - } + /// + ///// The current ratchet state of this session. + ///// The returned values are sensitive and should be securely erased before being dropped. + //pub fn ratchet_states(&self) -> [RatchetState; 2] { + // let state = self.state.read().unwrap(); + // state.ratchet_states.clone() + //} /// The current ratchet count of this session. - pub fn ratchet_count(&self) -> u64 { - self.state.read().unwrap().ratchet_states[0].chain_len() - } + //pub fn ratchet_count(&self) -> u64 { + // self.state.read().unwrap(). + //} /// Mark a session as expired. This will make it impossible for this session to successfully /// receive or send data or control packets. It is recommended to simply `drop` the session /// instead, but this can provide some reassurance in complex shared ownership situations. - pub fn expire(&self) { - if let Some(context) = self.context.upgrade() { - self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); - } + //pub fn expire(&self) { + // if let Some(context) = self.context.upgrade() { + // self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); + // } + //} + //fn expire_inner( + // &self, + // context: &Arc>, + // session_queue: &mut IndexedBinaryHeap>, Reverse>, + //) { + // // Prevent this session from being updated. + // session_queue.remove(self.queue_idx); + // self.session_has_expired.store(true, Ordering::Relaxed); + // let _kex_lock = self.state_machine_lock.lock().unwrap(); + // let mut state = self.state.write().unwrap(); + // let mut session_map = context.session_map.write().unwrap(); + // for key in &state.cipher_states { + // if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { + // session_map.remove(&pre_id); + // } + // } + // use OfferStateMachine::*; + // match &state.outgoing_offer { + // NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), + // NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), + // _ => None, + // }; + // state.outgoing_offer = OfferStateMachine::Null; + //} + /// Check whether this session is established. + pub fn established(&self) -> bool { + let state = self.state.read().unwrap(); + !matches!(&state.beta, ZetaAutomata::A1(_) | ZetaAutomata::A3 {..} | ZetaAutomata::Null) } - fn expire_inner( - &self, - context: &Arc>, - session_queue: &mut IndexedBinaryHeap>, Reverse>, - ) { - // Prevent this session from being updated. - session_queue.remove(self.queue_idx); - self.session_has_expired.store(true, Ordering::Relaxed); - let _kex_lock = self.state_machine_lock.lock().unwrap(); - let mut state = self.state.write().unwrap(); - let mut session_map = context.session_map.write().unwrap(); - for key in &state.cipher_states { - if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { - session_map.remove(&pre_id); - } - } - use OfferStateMachine::*; - match &state.outgoing_offer { - NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), - NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), - _ => None, - }; - state.outgoing_offer = OfferStateMachine::Null; - } - - /// Get the next outgoing counter value. - fn get_next_outgoing_counter(&self) -> Result { - if self.session_has_expired.load(Ordering::Relaxed) { - Err(SendError::SessionExpired) - } else { - let counter = self.send_counter.fetch_add(1, Ordering::Relaxed); - if counter > THREAD_SAFE_COUNTER_HARD_EXPIRE { - // Because this thread sets the flag itself it will never be able to increment the - // counter again. - // For that reason the other atomic orderings can be `Relaxed`. - self.session_has_expired.store(true, Ordering::SeqCst) - } - Ok(counter) - } - } - /// Check the receive window without mutating state. - fn check_receive_window(&self, counter: u64) -> bool { - let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.load(Ordering::Relaxed); - prev_counter < counter && counter.wrapping_sub(prev_counter) <= COUNTER_WINDOW_MAX_SKIP_AHEAD - } - /// Update the receive window, returning true if the packet is still valid. - /// This should only be called after the packet is authenticated. - fn update_receive_window(&self, counter: u64) -> bool { - let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); - prev_counter < counter && counter.wrapping_sub(prev_counter) <= COUNTER_WINDOW_MAX_SKIP_AHEAD + /// The static public key of the remote peer. + pub fn remote_static_key(&self) -> &Application::PublicKey { + &self.s_remote } } -impl Drop for Session { - fn drop(&mut self) { - if let Some(context) = self.context.upgrade() { - self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); - } - } -} - -impl NoiseXKAliceHandshake { - /// Can only fail with `OpenError::InvalidPublicKey` because of remote_s_public_key. - /// Corresponds to Noise `Initialize`. - fn initialize( - local_key_id: NonZeroU32, - remote_s_public_key: &Application::PublicKey, - ratchet_state: &[RatchetState; 2], - rng: &mut Application::Rng, - ) -> Result< - ( - NoiseXKAliceHandshakeState, - Secret, - Secret, - ), - OpenError, - > { - let mut message = [0u8; NoiseXKPattern1::MAX_SIZE]; - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - // Start of Noise XKhfs+psk2 pattern1. - let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); - let noise_e_secret = Application::KeyPair::generate(rng); - let noise_e1_secret = pqc_kyber::keypair(rng); - noise_pattern1.alice_key_id = local_key_id.get().to_ne_bytes(); - noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); - noise_pattern1.noise_e1 = noise_e1_secret.public; - // Noise process prologue. - let noise_h = mix_hash( - sha512, - &INITIAL_H, - &message[NoiseXKPattern1::PROLOGUE_START..NoiseXKPattern1::PROLOGUE_END], - ); - let noise_h = mix_hash(sha512, &noise_h, remote_s_public_key.as_bytes()); - // Noise process pattern1 e token. - let mut noise_ck = SymmetricState::new(INITIAL_H); - let noise_h_e = mix_hash(sha512, &noise_h, noise_e_secret.public_key_bytes()); - noise_ck.mix_key(hmac, noise_e_secret.public_key_bytes()); - // Noise process pattern1 es token. - let mut noise_es = Secret::new(); - if !noise_e_secret.agree(remote_s_public_key, noise_es.as_mut()) { - return Err(OpenError::InvalidPublicKey); - } - let noise_k_es = noise_ck.mix_key_initialize_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 e1 token. - let noise_h_ee1 = encrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_e, - PACKET_TYPE_NOISE_XK_PATTERN_1, - 0, - &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], - ); - // Noise process pattern1 payload. - let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); - let mut idx = 0; - for rs in ratchet_state { - if let Some(rf) = rs.fingerprint() { - let next_idx = idx + RATCHET_SIZE; - noise_pattern1.payload[idx..next_idx].copy_from_slice(rf); - idx = next_idx; - } - } - let p_auth_end = NoiseXKPattern1::P_ENC_START + idx + AES_GCM_TAG_SIZE; - let noise_message_len = p_auth_end + ChallengeResponse::SIZE; - - let noise_h_ee1p = encrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_ee1, - PACKET_TYPE_NOISE_XK_PATTERN_1, - 1, - &mut message[NoiseXKPattern1::P_ENC_START..p_auth_end], - ); - drop(noise_k_es); - let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); - let message_id = u64::from_be_bytes(message[p_auth_end - 8..p_auth_end].try_into().unwrap()); - - message[noise_message_len - CHALLENGE_POW_SIZE..noise_message_len].copy_from_slice(&rng.next_u64().to_ne_bytes()); - Ok(( - NoiseXKAliceHandshakeState::NoiseXKPattern1 { - noise_h_ee1p, - noise_e_secret, - noise_e1_secret: Secret(noise_e1_secret.secret), - noise_ck_es: noise_ck, - noise_message_len, - noise_message: message, - message_id, - }, - header_a2b_key, - header_b2a_key, - )) - } - /// Should not fail unless Bob's public key is adversarial. - fn reinitialize( - &mut self, - session: &Arc>, - ratchet_state: &[RatchetState; 2], - session_map: &mut HashMap>, bool)>, - rng: &mut Application::Rng, - current_time: i64, - ) -> bool { - let local_key_id = generate_key_id(session_map, rng); - if let Ok((offer, a2b_header_key, b2a_header_key)) = Self::initialize(local_key_id, &session.remote_static_key, ratchet_state, rng) { - self.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); - session_map.remove(&self.local_key_id); - session_map.insert(local_key_id, (Arc::downgrade(session), false)); - self.local_key_id = local_key_id; - self.offer = offer; - session.header_send_cipher.reset(a2b_header_key.as_ref()); - session.header_receive_cipher.reset(b2a_header_key.as_ref()); - true - } else { - false - } - } -} - -/// Create the normal state of the offer state machine, with the correct timestamps. -fn new_normal_state(rand: u64, current_time: i64) -> OfferStateMachine { - OfferStateMachine::Normal { - timeout: current_time - .saturating_add(Application::REKEY_AFTER_TIME_MS) - .saturating_sub(rand as i64 % Application::REKEY_AFTER_TIME_MAX_JITTER_MS), - } -} -/// Get a timestamp of when this timer should trigger next, or None if it should trigger now. -fn process_timer(timer: &AtomicI64, wait_time: i64, current_time: i64) -> Option { - let ts = timer.load(Ordering::Relaxed); - if ts <= current_time && timer.fetch_max(ts.saturating_add(wait_time), Ordering::Relaxed) == ts { - None - } else { - Some(ts) - } -} - -/// Corresponds to Noise `EncryptAndHash`. -fn encrypt_and_hash( - sha512: &mut Application::Hash, - noise_k: &Secret, - noise_h: &[u8; HASHLEN], - packet_type: u8, - noise_k_uses: u64, - message: &mut [u8], -) -> [u8; HASHLEN] { - let auth_start = message.len() - AES_GCM_TAG_SIZE; - let mut gcm = Application::AeadEnc::new(noise_k.as_ref()); - // Encrypt and add authentication tag. - gcm.set_iv(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); - gcm.set_aad(noise_h); - if auth_start > 0 { - gcm.encrypt_in_place(&mut message[..auth_start]); - } - gcm.finish_encrypt((&mut message[auth_start..]).try_into().unwrap()); - mix_hash(sha512, noise_h, message) -} -/// Corresponds to Noise `DecryptAndHash`. -fn decrypt_and_hash( - sha512: &mut Application::Hash, - noise_k: &Secret, - noise_h: &[u8; HASHLEN], - packet_type: u8, - noise_k_uses: u64, - message: &mut [u8], -) -> (bool, [u8; HASHLEN]) { - let auth_start = message.len() - AES_GCM_TAG_SIZE; - let noise_h_c = mix_hash(sha512, noise_h, message); - let mut gcm = Application::AeadDec::new(noise_k.as_ref()); - gcm.set_iv(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); - gcm.set_aad(noise_h); - if auth_start > 0 { - gcm.decrypt_in_place(&mut message[..auth_start]); - } - (gcm.finish_decrypt((&message[auth_start..]).try_into().unwrap()), noise_h_c) -} -/// Encrypt a standardized control packet. -fn encrypt_control( - c: &mut impl AesGcmEnc, - header_cipher: &impl AesEnc, - packet_type: u8, - counter: u64, - remote_key_id: u32, - packet: &[u8], -) -> ([u8; CONTROL_PACKET_MAX_SIZE], usize) { - let mut fragment = [0u8; CONTROL_PACKET_MAX_SIZE]; - let fragment_len = packet.len() + HEADER_SIZE + AES_GCM_TAG_SIZE; - - c.set_iv(&create_message_nonce(packet_type, counter)); - if !packet.is_empty() { - fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE].copy_from_slice(packet); - c.encrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); - } - c.finish_encrypt((&mut fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]).try_into().unwrap()); - set_packet_header(&mut fragment, 1, 0, packet_type, remote_key_id, counter); - header_cipher.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); - (fragment, fragment_len) -} -fn decrypt_control<'a, IoError>( - c: &mut impl AesGcmDec, - packet_type: u8, - counter: u64, - fragment: &'a mut [u8], -) -> Result<&'a mut [u8], ReceiveError> { - let fragment_len = fragment.len(); - if !(CONTROL_PACKET_MIN_SIZE..=CONTROL_PACKET_MAX_SIZE).contains(&fragment_len) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - c.set_iv(&create_message_nonce(packet_type, counter)); - c.decrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); - if !c.finish_decrypt((&fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]).try_into().unwrap()) { - // This can occur naturally if one of the remote peers resent a - // control packet that got delayed and arrived out of order. - return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); - } - Ok(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]) -} - -fn set_packet_header(packet: &mut [u8], fragment_count: u8, fragment_no: u8, packet_type: u8, remote_key_id: u32, counter_or_id: u64) { - debug_assert!(packet.len() >= MIN_PACKET_SIZE); - debug_assert!(fragment_count > 0); - debug_assert!(fragment_count <= MAX_FRAGMENTS as u8); - debug_assert!(fragment_no < MAX_FRAGMENTS as u8); - debug_assert_eq!((packet_type << 1) >> 1, packet_type); - // [0..4] recipient key id - // -- start AES(ck_es * h_e_e1_p) encrypted block -- - // [4] fragment count (1..255) - // [5] fragment number (0..254) - // [6] reserved zero - // -- start of AES-GCM Nonce -- - // [7] packet type - // [8..16] 64-bit counter or packet id (big endian) - packet[4..16].copy_from_slice(&create_message_nonce(packet_type, counter_or_id)); - packet[0..4].copy_from_slice(&remote_key_id.to_ne_bytes()); - packet[4] = fragment_count; - packet[5] = fragment_no; - packet[6] = 0; -} -/// Create a 96-bit AES-GCM nonce. -/// -/// The primary information that we want to be contained here is the counter and the -/// packet type. The former makes this unique and the latter's inclusion authenticates -/// it as effectively AAD. Other elements of the header are either not authenticated, -/// like fragmentation info, or their authentication is implied via key exchange like -/// the key id. -fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] { - let mut ret = [0u8; AES_GCM_IV_SIZE]; - ret[3] = packet_type; - // Noise requires a big endian counter at the end of the Nonce - ret[4..].copy_from_slice(&counter.to_be_bytes()); - ret -} -/// returns `(fragment_count, fragment_no, packet_type, counter, header_nonce)`. -fn parse_packet_header(packet: &[u8]) -> (u8, u8, u8, u64, [u8; 10]) { - let header_nonce = packet[6..16].try_into().unwrap(); - let counter = packet[8..16].try_into().unwrap(); - // We intentionally ignore the version number for future revisions. - (packet[4], packet[5], packet[7], u64::from_be_bytes(counter), header_nonce) -} - -/// Break a packet into fragments and send them all. -/// -/// The contents of packet[] are mangled during this operation, so it should be discarded after. -/// This is only used for key exchange and control packets. For data packets this is done inline -/// for better performance with encryption and fragmentation happening at the same time. -fn send_with_fragmentation( - send: &mut impl FnMut(&mut [u8]) -> bool, - mtu: usize, - packet: &mut [u8], - packet_type: u8, - remote_key_id: Option, - counter_or_id: u64, - header_cipher: Option<&impl AesEnc>, -) -> bool { - let packet_len = packet.len(); - let fragment_count = (packet_len.saturating_add(mtu - 1)) / mtu; // integer ceiling divide - debug_assert!(fragment_count <= MAX_FRAGMENTS); - let mut fragment_start = 0; - let mut fragment_end = packet_len.min(mtu); - let mut fragment_no = 0; - loop { - let fragment = &mut packet[fragment_start..fragment_end]; - set_packet_header( - fragment, - fragment_count as u8, - fragment_no as u8, - packet_type, - remote_key_id.map_or(0, |n| n.get()), - counter_or_id, - ); - if let Some(hcc) = header_cipher { - hcc.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); - } - if !send(fragment) { - return false; - } - fragment_no += 1; - if fragment_no < fragment_count { - fragment_start = fragment_end - HEADER_SIZE; - fragment_end = (fragment_start.saturating_add(mtu)).min(packet_len); - } else { - break; - } - } - true -} - -/// Assemble a series of fragments into a buffer and return the length of the assembled packet in -/// bytes. -/// -/// This is also only used for key exchange and control packets. For data packets decryption and -/// assembly happen in one pass for better performance. -fn assemble_fragments_into(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result> { - let mut l = 0; - for i in 0..fragments.len() { - let mut ff = fragments[i].as_ref(); - if i > 0 { - ff = &ff[HEADER_SIZE..]; - } - let j = l + ff.len(); - if j > d.len() { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - d[l..j].copy_from_slice(ff); - l = j; - } - Ok(l) -} -/// Generate a random local key id that is currently unused. -fn generate_key_id( - session_map: &HashMap>, bool)>, - rng: &mut Application::Rng, -) -> NonZeroU32 { - loop { - if let Some(local_key_id) = NonZeroU32::new(rng.next_u32()) { - if !session_map.contains_key(&local_key_id) { - return local_key_id; - } - } - } -} - -impl SessionKey { - fn new( - hmac: &mut Application::HmacHash, - ck: SymmetricState, - local_key_id: NonZeroU32, - remote_key_id: NonZeroU32, - current_counter: u64, - is_bob: bool, - ) -> Self { - let (b2a, a2b) = ck.split(hmac); - let (receive_key, send_key) = if is_bob { - (&a2b, &b2a) - } else { - (&b2a, &a2b) - }; - let send_cipher_pool = std::array::from_fn(|_| Mutex::new(Application::AeadEnc::new(send_key.as_ref()))); - let receive_cipher_pool = std::array::from_fn(|_| Mutex::new(Application::AeadDec::new(receive_key.as_ref()))); - Self { - local_key_id, - remote_key_id, - send_cipher_pool, - receive_cipher_pool, - rekey_at_counter: current_counter.saturating_add(Application::REKEY_AFTER_USES), - expire_at_counter: current_counter.saturating_add(Application::EXPIRE_AFTER_USES), - } - } - - fn get_send_cipher(&self, counter: u64) -> Result, SendError> { - if counter < self.expire_at_counter { - Ok(self.send_cipher_pool[(counter as usize) % self.send_cipher_pool.len()].lock().unwrap()) - } else { - Err(SendError::SessionExpired) - } - } - - fn get_receive_cipher(&self, counter: u64) -> MutexGuard { - let idx = (counter as usize) % self.receive_cipher_pool.len(); - self.receive_cipher_pool[idx].lock().unwrap() - } -} - -/// MixHash to update 'h' during negotiation. -fn mix_hash(hasher: &mut impl Sha512, h: &[u8; HASHLEN], m: &[u8]) -> [u8; HASHLEN] { - let mut output = [0u8; HASHLEN]; - hasher.reset(); - hasher.update(h); - hasher.update(m); - hasher.finish(&mut output); - output -} -/// Check if the proof of work attached to the first message contains the correct number of leading -/// zeros. -fn verify_pow(hasher: &mut Application::Hash, response: &[u8]) -> bool { - if Application::PROOF_OF_WORK_BIT_DIFFICULTY == 0 { - return true; - } - hasher.reset(); - hasher.update(response); - let mut output = [0u8; HASHLEN]; - hasher.finish(&mut output); - let n = u32::from_be_bytes(output[..4].try_into().unwrap()); - n.leading_zeros() >= Application::PROOF_OF_WORK_BIT_DIFFICULTY -} -fn from_bytes_agreement( - public: &[u8], - private: &Application::KeyPair, - output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE], -) -> Option { - Application::PublicKey::from_bytes(public.try_into().unwrap()).and_then(|e| private.agree(&e, output).then_some(e)) -} From 8ba0697337efdf4e5eee9e9e498c217944c40268 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 7 Aug 2023 20:04:07 -0400 Subject: [PATCH 57/91] first compile --- src/crypto/aes.rs | 2 +- src/frag_cache.rs | 26 +- src/fragged.rs | 37 +-- src/proto.rs | 2 + src/result.rs | 10 +- src/symmetric_state.rs | 2 +- src/zeta.rs | 738 ++++++++++++++++++++++++++--------------- src/zssp.rs | 607 ++++++++++++++++++--------------- 8 files changed, 827 insertions(+), 597 deletions(-) diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index baec450..2e5f36a 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -47,7 +47,7 @@ pub trait AesGcmEncContext { } pub trait AesGcmDecContext { - fn decrypt(&mut self, input: &[u8], output: &mut [u8]); + fn decrypt_in_place(&mut self, data: &mut [u8]); #[must_use] fn finish(&mut self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; diff --git a/src/frag_cache.rs b/src/frag_cache.rs index f0c9c27..fab7a78 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -152,24 +152,12 @@ impl UnassociatedFragCache { self.frags[frag_idx].write(fragment); if entry.fragment_have == 1u64.wrapping_shl(fragment_count as u32) - 1 { - ret_assembled.empty(); - ret_assembled.1 = fragment_count as usize; + debug_assert!(ret_assembled.is_empty()); let start_idx = entry.frags_idx as usize; - // This is a ring buffer copy into ret_assembled. - // The fragments are moved into the `ret_assembled` container and returned. - // That container will drop them when it is dropped. - if start_idx + ret_assembled.1 <= self.frags.len() { - // Copy does not occur at the buffer's boundary - unsafe { - std::ptr::copy_nonoverlapping(&self.frags[start_idx], &mut ret_assembled.0[0], ret_assembled.1); - } - } else { - // Copy does occur at the buffer's boundary - let first_chunk_size = self.frags.len() - start_idx; - let second_chunk_size = ret_assembled.1 - first_chunk_size; - unsafe { - std::ptr::copy_nonoverlapping(&self.frags[start_idx], &mut ret_assembled.0[0], first_chunk_size); - std::ptr::copy_nonoverlapping(&self.frags[0], &mut ret_assembled.0[first_chunk_size], second_chunk_size); + unsafe { + for i in start_idx..start_idx + fragment_count { + + ret_assembled.push(self.frags[i % self.frags.len()].assume_init_read()) } } self.invalidate::(idx); @@ -267,7 +255,7 @@ fn test_cache() { } in_progress.push((i, fragment_count as u8, packet)); } else { - assembled.empty(); + assembled.clear(); let drop = xorshift64_random() as usize % (2 * fragment_count); for j in 0..fragment_count { if drop != j { @@ -297,7 +285,7 @@ fn test_cache() { for _ in 0..((xorshift64_random() as usize % packet.len()) + 1) { let (no, fragment) = packet.swap_remove(xorshift64_random() as usize % packet.len()); - assembled.empty(); + assembled.clear(); let mut nonce = [0; 12]; nonce[..4].copy_from_slice(&id.to_be_bytes()); cache.assemble(&nonce, 0, fragment.len(), fragment, no as usize, fragment_count as usize, 1000, time, &mut assembled); diff --git a/src/fragged.rs b/src/fragged.rs index 145b5d8..5cb1a49 100644 --- a/src/fragged.rs +++ b/src/fragged.rs @@ -6,40 +6,13 @@ * https://www.zerotier.com/ */ +use arrayvec::ArrayVec; use std::mem::{needs_drop, zeroed, MaybeUninit}; -use std::ptr::slice_from_raw_parts; use crate::crypto::aes::AES_GCM_IV_SIZE; use crate::proto::{MAX_FRAGMENTS, NONCE_SIZE_DIFF}; -pub(crate) struct Assembled(pub(crate) [MaybeUninit; MAX_FRAGMENTS], pub(crate) usize); - -impl Assembled { - pub(crate) fn new() -> Self { - Self(unsafe { MaybeUninit::<[MaybeUninit<_>; MAX_FRAGMENTS]>::uninit().assume_init() }, 0) - } - pub(crate) fn is_empty(&self) -> bool { - self.1 == 0 - } - pub(crate) fn empty(&mut self) { - for i in 0..self.1 { - unsafe { - self.0.get_unchecked_mut(i).assume_init_drop(); - } - } - self.1 = 0; - } -} -impl AsRef<[Fragment]> for Assembled { - fn as_ref(&self) -> &[Fragment] { - unsafe { &*slice_from_raw_parts(self.0.as_ptr().cast::(), self.1) } - } -} -impl Drop for Assembled { - fn drop(&mut self) { - self.empty() - } -} +pub type Assembled = ArrayVec; /// Fast packet defragmenter pub struct Fragged { @@ -94,10 +67,10 @@ impl Fragged { // Setting 'have' to 0 resets the state of this object, and the fragments // are effectively moved into the Assembled<> container and returned. That // container will drop them when it is dropped. - ret_assembled.empty(); - ret_assembled.1 = fragment_count as usize; unsafe { - std::ptr::copy_nonoverlapping(&self.frags[0], &mut ret_assembled.0[0], ret_assembled.1); + for i in 0..fragment_count { + ret_assembled.push(self.frags[i].assume_init_read()); + } } } } diff --git a/src/proto.rs b/src/proto.rs index 3f11bb1..c11f88f 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -23,6 +23,8 @@ pub(crate) const POW_START: usize = COUNTER_SIZE + MAC_SIZE; pub(crate) const CHALLENGE_SIZE: usize = COUNTER_SIZE + MAC_SIZE + POW_SIZE; pub(crate) const DIFFICULTY: u32 = 13; +pub(crate) const HEADERED_CHALLENGE_SIZE: usize = CHALLENGE_SIZE + HEADER_SIZE + KID_SIZE; + /* Fragmentation constants */ /* Header: diff --git a/src/result.rs b/src/result.rs index 4710492..c070bc9 100644 --- a/src/result.rs +++ b/src/result.rs @@ -61,8 +61,8 @@ pub enum FaultType { } /// An error that occurred during the receipt of a given packet. -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub enum ReceiveError { +#[derive(Debug)] +pub enum ReceiveError { /// A type of fault that can occur because a remote peer sent us a bad packet. /// Such packets will be ignored by ZSSP but a user of ZSSP might want to log /// them for debugging or tracing. @@ -104,7 +104,9 @@ pub enum ReceiveError { /// One of the ratchet saving or lookup functions returned an error, so the packet had to be /// dropped. - RatchetIoError(IoError), + StorageError(StorageError), + + IoError(std::io::Error), } macro_rules! byzantine_fault { @@ -158,7 +160,7 @@ pub enum SessionEvent { /// This return value cannot occur after a session is fully established. Rejected, /// The received packet was valid and a data payload was decoded and authenticated. - Data(Vec), + Data, /// The received packet was some authentic protocol control packet. No action needs to be taken. Control, } diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index f2e2a22..591fdb4 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -152,7 +152,7 @@ impl SymmetricState { } /// Corresponds to Noise `EncryptAndHash`. #[must_use] - pub fn encrypt_and_hash_in_place(&mut self, hash: &mut App::Hash, iv: [u8; AES_GCM_IV_SIZE], data: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE] { + pub fn encrypt_and_hash_in_place(&mut self, hash: &mut App::Hash, iv: [u8; AES_GCM_IV_SIZE], data: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE] { let tag = App::Aead::encrypt_in_place(&self.k, &iv, &self.h, data); hash.update(&self.h); hash.update(data); diff --git a/src/zeta.rs b/src/zeta.rs index 9203426..8475cf0 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -2,10 +2,11 @@ use arrayvec::ArrayVec; use rand_core::RngCore; use std::cmp::Reverse; use std::collections::HashMap; +use std::io::Write; use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; -use std::sync::atomic::{AtomicU64, Ordering, AtomicBool}; -use std::sync::{Arc, Mutex, Weak, RwLock}; +use std::sync::atomic::{AtomicU64, Ordering, AtomicBool, AtomicI64}; +use std::sync::{Arc, Mutex, Weak, RwLock, MutexGuard, RwLockWriteGuard}; use zeroize::Zeroizing; use crate::antireplay::Window; @@ -21,7 +22,7 @@ use crate::crypto::kyber1024::{Kyber1024PrivateKey, KYBER_PUBLIC_KEY_SIZE, KYBER use crate::indexed_heap::BinaryHeapIndex; use crate::proto::*; use crate::ratchet_state::RatchetState; -use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; +use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError, ReceiveOk}; use crate::symmetric_state::SymmetricState; use crate::fragged::Fragged; #[cfg(feature = "logging")] @@ -68,8 +69,8 @@ fn get_counter(session: &Session, state: &MutableSta } /// Corresponds to the Zeta State Machine found in Section 4.1. -pub(crate) struct Session { - //ctx: Weak>, +pub struct Session { + ctx: Weak>, /// An arbitrary application defined object associated with each session. pub session_data: App::SessionData, /// Is true if the local peer acted as Bob, the responder in the initial key exchange. @@ -85,7 +86,9 @@ pub(crate) struct Session { pub(crate) hk_send: App::PrpEnc, pub(crate) hk_recv: App::PrpDec, + /// `session_queue -> state_machine_lock -> state -> session_map` state_machine_lock: Mutex<()>, + /// `session_queue -> state_machine_lock -> state -> session_map` pub(crate) state: RwLock>, /// Pre-computed rekeying value. @@ -99,7 +102,7 @@ pub(crate) struct MutableState { key_index: bool, keys: [DuplexKey; 2], - resend_timer: i64, + resend_timer: AtomicI64, timeout_timer: i64, pub beta: ZetaAutomata, } @@ -190,7 +193,7 @@ impl SymmetricState { self.mix_key(hmac, &pub_key); e_secret } - fn read_e(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, i: &mut usize, packet: &[u8]) -> Option { + fn read_e(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, i: &mut usize, packet: &[u8]) -> Option { let j = *i + P384_PUBLIC_KEY_SIZE; let pub_key = &packet[*i..j]; self.mix_hash(hash, pub_key); @@ -227,9 +230,6 @@ impl MutableState { fn key_mut(&mut self, is_next: bool) -> &mut DuplexKey { &mut self.keys[(self.key_index ^ is_next) as usize] } - pub(crate) fn next_timer(&self) -> i64 { - self.timeout_timer.min(self.resend_timer) - } } fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_IV_SIZE]) { @@ -265,7 +265,8 @@ fn create_a1_state( let (e1_secret, e1_public) = App::Kem::generate(rng.lock().unwrap().deref_mut()); x1.extend(e1_public); x1.extend([0u8; AES_GCM_IV_SIZE]); - x1.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..])); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..]); + x1.extend(tag); // Process message pattern 1 payload. let i = x1.len(); if let Some(rf) = ratchet_state1.fingerprint() { @@ -274,7 +275,8 @@ fn create_a1_state( if let Some(Some(rf)) = ratchet_state2.map(|rs| rs.fingerprint()) { x1.try_extend_from_slice(rf).unwrap(); } - x1.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 1), &mut x1[i..])); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 1), &mut x1[i..]); + x1.extend(tag); let c = u64::from_be_bytes(x1[x1.len() - 8..].try_into().unwrap()); @@ -325,7 +327,9 @@ pub(crate) fn trans_to_a1( let current_time = app.time(); let queue_idx = session_queue.reserve_index(); - let mut session = Arc::new(Session { + let resend_timer = current_time + App::SETTINGS.resend_time as i64; + let session = Arc::new(Session { + ctx: Arc::downgrade(ctx), session_data, was_bob: false, queue_idx, @@ -340,7 +344,7 @@ pub(crate) fn trans_to_a1( key_creation_counter: 0, key_index: true, keys: [DuplexKey::default(), DuplexKey::default()], - resend_timer: current_time + App::SETTINGS.resend_time as i64, + resend_timer: AtomicI64::new(resend_timer), timeout_timer: current_time + App::SETTINGS.initial_offer_timeout as i64, beta: ZetaAutomata::A1(a1), }), @@ -349,14 +353,16 @@ pub(crate) fn trans_to_a1( hk_send: App::PrpEnc::new((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()), hk_recv: App::PrpDec::new((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()), }); - let mut state = session.state.write().unwrap(); - state.key_mut(true).recv.kid = Some(kid_recv); + { + let mut state = session.state.write().unwrap(); + state.key_mut(true).recv.kid = Some(kid_recv); + } session_map.insert(kid_recv, Arc::downgrade(&session)); session_queue.push_reserved( queue_idx, Arc::downgrade(&session), - Reverse(state.next_timer()), + Reverse(resend_timer), ); send(&mut x1, None); @@ -364,12 +370,12 @@ pub(crate) fn trans_to_a1( Ok(session) } /// Corresponds to Algorithm 13 found in Section 5. -pub(crate) fn respond_to_challenge(session: &mut Session, rng: &Mutex, challenge: &[u8; CHALLENGE_SIZE]) { +pub(crate) fn respond_to_challenge(ctx: &Arc>, session: &Session, challenge: &[u8; CHALLENGE_SIZE]) { let mut state = session.state.write().unwrap(); if let ZetaAutomata::A1(a1) = &mut state.beta { let response_start = a1.x1.len() - CHALLENGE_SIZE; respond_to_challenge_in_place::( - rng.lock().unwrap().deref_mut(), + ctx.rng.lock().unwrap().deref_mut(), challenge, (&mut a1.x1[response_start..]).try_into().unwrap(), ); @@ -379,8 +385,7 @@ pub(crate) fn respond_to_challenge(session: &mut Session< pub(crate) fn received_x1_trans( app: &App, ctx: &ContextInner, - remote_address: &impl std::hash::Hash, - n: [u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_IV_SIZE], x1: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { @@ -393,12 +398,6 @@ pub(crate) fn received_x1_trans( return Err(byzantine_fault!(InvalidPacket, true)); } - if let Err(challenge) = ctx.challenge.process_hello::(remote_address, (&x1[x1.len() - CHALLENGE_SIZE..]).try_into().unwrap()) { - /// - - return Err(byzantine_fault!(FailedAuth, false)); - } - if &n[AES_GCM_IV_SIZE - 8..] != &x1[x1.len() - 8..] { return Err(byzantine_fault!(FailedAuth, true)); } @@ -442,7 +441,7 @@ pub(crate) fn received_x1_trans( ratchet_state = Some(rs); break; } - Err(e) => return Err(ReceiveError::RatchetIoError(e)), + Err(e) => return Err(ReceiveError::StorageError(e)), } i += RATCHET_SIZE; } @@ -470,7 +469,8 @@ pub(crate) fn received_x1_trans( let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); let ekem1 = App::Kem::encapsulate(ctx.rng.lock().unwrap().deref_mut(), (&x1[e1_start..e1_end]).try_into().unwrap(), &mut ekem1_secret).ok_or(byzantine_fault!(FailedAuth, true))?; x2.extend(ekem1); - x2.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..])); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..]); + x2.extend(tag); noise.mix_key(hmac, ekem1_secret.as_ref()); } // Process message pattern 2 psk2 token. @@ -480,7 +480,8 @@ pub(crate) fn received_x1_trans( let i = x2.len(); x2.extend(kid_recv.get().to_be_bytes()); - x2.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..])); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..]); + x2.extend(tag); let i = x2.len(); let mut c = 0u64.to_be_bytes(); @@ -515,8 +516,8 @@ pub(crate) fn received_x2_trans( ctx: &Arc>, session: &Arc>, kid: NonZeroU32, - n: [u8; AES_GCM_IV_SIZE], - mut x2: &[u8], + n: &[u8; AES_GCM_IV_SIZE], + x2: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { use FaultType::*; @@ -534,122 +535,128 @@ pub(crate) fn received_x2_trans( if Some(kid) != state.key_ref(true).recv.kid { return Err(byzantine_fault!(UnknownLocalKeyId, true)); } - let (_, c) = from_nonce(&n); + let (_, c) = from_nonce(n); if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD || &n[AES_GCM_IV_SIZE - 3..] != &x2[x2.len() - 3..] { return Err(byzantine_fault!(FailedAuth, true)); } let mut result = (|| { - if let ZetaAutomata::A1(a1) = &state.beta { - let mut noise = a1.noise.clone(); - let mut i = 0; - // Process message pattern 2 e token. - let e_remote = noise.read_e(hash, hmac, &mut i, &x2).ok_or(byzantine_fault!(FailedAuth, true))?; - // Process message pattern 2 ee token. - noise.mix_dh(hmac, &a1.e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; - // Process message pattern 2 ekem1 token. - let j = i + KYBER_CIPHERTEXT_SIZE; - let k = j + AES_GCM_TAG_SIZE; - let tag = x2[j..k].try_into().unwrap(); - if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..j], tag) { - return Err(byzantine_fault!(FailedAuth, true)); + let a1 = if let ZetaAutomata::A1(a1) = &state.beta { + a1 + } else { + return Err(byzantine_fault!(FailedAuth, true)); + }; + let mut noise = a1.noise.clone(); + let mut i = 0; + // Process message pattern 2 e token. + let e_remote = noise.read_e(hash, hmac, &mut i, &x2).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 2 ee token. + noise.mix_dh(hmac, &a1.e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 2 ekem1 token. + let j = i + KYBER_CIPHERTEXT_SIZE; + let k = j + AES_GCM_TAG_SIZE; + let tag = x2[j..k].try_into().unwrap(); + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..j], tag) { + return Err(byzantine_fault!(FailedAuth, true)); + } + let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); + if !a1.e1_secret.decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) { + return Err(byzantine_fault!(FailedAuth, true)); + } + noise.mix_key(hmac, ekem1_secret.as_ref()); + drop(ekem1_secret); + i = j; + // We attempt to decrypt the payload at most three times. First two times with + // the ratchet key Alice remembers, and final time with a ratchet + // key of zero if Alice allows ratchet downgrades. + // The following code is not constant time, meaning we leak to an + // attacker whether or not we downgraded. + // We don't currently consider this sensitive enough information to hide. + let j = i + KID_SIZE; + let k = j + AES_GCM_TAG_SIZE; + let payload: [u8; KID_SIZE] = x2[i..j].try_into().unwrap(); + let tag = x2[j..k].try_into().unwrap(); + // Check for which ratchet key Bob wants to use. + let mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState)> { + let mut noise = noise.clone(); + let mut payload = payload.clone(); + // Process message pattern 2 psk token. + noise.mix_key_and_hash(hash, hmac, ratchet_key); + // Process message pattern 2 payload. + if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut payload, tag) { + return None; } - let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); - if !a1.e1_secret.decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) { - return Err(byzantine_fault!(FailedAuth, true)); + NonZeroU32::new(u32::from_be_bytes(payload)).map(|kid2| (kid2, noise)) + }; + // Check first key. + let mut ratchet_i = 1; + let mut chain_len = state.ratchet_state1.chain_len; + let mut result = test_ratchet_key(state.ratchet_state1.key.as_ref()); + // Check second key. + if result.is_none() { + ratchet_i = 2; + if let Some(rs) = state.ratchet_state2.as_ref() { + chain_len = rs.chain_len; + result = test_ratchet_key(rs.key.as_ref()); } - noise.mix_key(hmac, ekem1_secret.as_ref()); - drop(ekem1_secret); - i = j; - // We attempt to decrypt the payload at most three times. First two times with - // the ratchet key Alice remembers, and final time with a ratchet - // key of zero if Alice allows ratchet downgrades. - // The following code is not constant time, meaning we leak to an - // attacker whether or not we downgraded. - // We don't currently consider this sensitive enough information to hide. - let j = i + KID_SIZE; - let k = j + AES_GCM_TAG_SIZE; - let payload: [u8; KID_SIZE] = x2[i..j].try_into().unwrap(); - let tag = x2[j..k].try_into().unwrap(); - // Check for which ratchet key Bob wants to use. - let mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState)> { - let mut noise = noise.clone(); - let mut payload = payload.clone(); - // Process message pattern 2 psk token. - noise.mix_key_and_hash(hash, hmac, ratchet_key); - // Process message pattern 2 payload. - if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut payload, tag) { - return None; - } - NonZeroU32::new(u32::from_be_bytes(payload)).map(|kid2| (kid2, noise)) - }; - // Check first key. - let mut ratchet_i = 1; - let mut chain_len = state.ratchet_state1.chain_len; - let mut result = test_ratchet_key(state.ratchet_state1.key.as_ref()); - // Check second key. - if result.is_none() { - ratchet_i = 2; - if let Some(rs) = state.ratchet_state2.as_ref() { - chain_len = rs.chain_len; - result = test_ratchet_key(rs.key.as_ref()); - } - } - // Check zero key. - if result.is_none() && !app.initiator_disallows_downgrade(session) { - chain_len = 0; - result = test_ratchet_key(&[0u8; RATCHET_SIZE]); - if result.is_some() { - // TODO: add some kind of warning callback or signal. - } + } + // Check zero key. + if result.is_none() && !app.initiator_disallows_downgrade(session) { + chain_len = 0; + result = test_ratchet_key(&[0u8; RATCHET_SIZE]); + if result.is_some() { + // TODO: add some kind of warning callback or signal. } + } - let (kid_send, mut noise) = result.ok_or(byzantine_fault!(FailedAuth, true))?; + let (kid_send, mut noise) = result.ok_or(byzantine_fault!(FailedAuth, true))?; - let mut x3 = ArrayVec::::new(); - x3.extend([0u8; HEADER_SIZE]); - // Process message pattern 3 s token. - let i = x3.len(); - x3.extend(ctx.s_secret.public_key_bytes()); - x3.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..])); - // Process message pattern 3 se token. - noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; - // Process message pattern 3 payload. - let i = x3.len(); - x3.try_extend_from_slice(&a1.identity).unwrap(); - x3.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), &mut x3[i..])); + let mut x3 = ArrayVec::::new(); + x3.extend([0u8; HEADER_SIZE]); + // Process message pattern 3 s token. + let i = x3.len(); + x3.extend(ctx.s_secret.public_key_bytes()); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..]); + x3.extend(tag); + // Process message pattern 3 se token. + noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + // Process message pattern 3 payload. + let i = x3.len(); + x3.try_extend_from_slice(&a1.identity).unwrap(); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0), &mut x3[i..]); + x3.extend(tag); - let new_ratchet_state = create_ratchet_state(hmac, &mut noise, chain_len); + let new_ratchet_state = create_ratchet_state(hmac, &mut noise, chain_len); - let (ratchet_to_preserve, ratchet_to_delete) = if ratchet_i == 1 { - (Some(&state.ratchet_state1), state.ratchet_state2.as_ref()) - } else { - (state.ratchet_state2.as_ref(), Some(&state.ratchet_state1)) - }; - let result = app.save_ratchet_state( - &session.s_remote, - &session.session_data, - RatchetUpdate { - state1: &new_ratchet_state, - state2: ratchet_to_preserve, - state1_was_just_added: true, - state_deleted1: ratchet_to_delete, - state_deleted2: None, - }, - ); - if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); - } + let (ratchet_to_preserve, ratchet_to_delete) = if ratchet_i == 1 { + (Some(&state.ratchet_state1), state.ratchet_state2.as_ref()) + } else { + (state.ratchet_state2.as_ref(), Some(&state.ratchet_state1)) + }; + let result = app.save_ratchet_state( + &session.s_remote, + &session.session_data, + RatchetUpdate { + state1: &new_ratchet_state, + state2: ratchet_to_preserve, + state1_was_just_added: true, + state_deleted1: ratchet_to_delete, + state_deleted2: None, + }, + ); + if let Err(e) = result { + return Err(ReceiveError::StorageError(e)); + } - let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); - let mut kek_send = Zeroizing::new([0u8; HASHLEN]); - let mut nk_recv = Zeroizing::new([0u8; HASHLEN]); - let mut nk_send = Zeroizing::new([0u8; HASHLEN]); - noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_recv, &mut kek_send); - noise.split(hmac, &mut nk_recv, &mut nk_send); - let nonce = to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0); + let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); + let mut kek_send = Zeroizing::new([0u8; HASHLEN]); + let mut nk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut nk_send = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_recv, &mut kek_send); + noise.split(hmac, &mut nk_recv, &mut nk_send); + let nonce = to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0); - //let identity = identity.clone(); - drop(state); + drop(state); + let resend_timer = { let mut state = session.state.write().unwrap(); state.key_mut(true).send.kid = Some(kid_send); @@ -660,18 +667,28 @@ pub(crate) fn received_x2_trans( state.ratchet_state1 = new_ratchet_state.clone(); let current_time = app.time(); state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); - state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + let resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.resend_timer = AtomicI64::new(resend_timer); state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; + let a1 = if let ZetaAutomata::A1(a1) = &state.beta { + a1 + } else { + // This return is unreachable. + return Err(byzantine_fault!(FailedAuth, true)); + }; state.beta = ZetaAutomata::A3 { identity: a1.identity.clone(), x3: x3.clone(), kid_send: kid_send.get(), nonce }; + resend_timer + }; + drop(kex_lock); + ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(resend_timer)); - Ok(x3) - } else { - Err(byzantine_fault!(FailedAuth, true)) - } + Ok(x3) })(); - match &mut result { - Err(ReceiveError::ByzantineFault { .. }) => process_timers(app, ctx, session, app.time(), true, false, send), - Ok(mut packet) => send(&mut packet, Some(&session.hk_send)), + match result { + Err(ReceiveError::ByzantineFault { .. }) => { + process_timers(app, ctx, session, app.time(), true, false, send); + } + Ok(ref mut packet) => send(packet, Some(&session.hk_send)), _ => {} } result.map(|_| ()) @@ -680,9 +697,9 @@ pub(crate) fn received_x2_trans( pub(crate) fn received_x3_trans( app: &App, ctx: &Arc>, - zeta: StateB2, + zeta: Arc>, kid: NonZeroU32, - mut x3: Vec, + x3: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result>, ReceiveError> { use FaultType::*; @@ -693,8 +710,8 @@ pub(crate) fn received_x3_trans( if kid != zeta.kid_recv { return Err(byzantine_fault!(UnknownLocalKeyId, true)); } - let mut hash = &mut App::Hash::new(); - let mut hmac = &mut App::HmacHash::new(); + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); let mut noise = zeta.noise.clone(); let mut i = 0; @@ -758,11 +775,12 @@ pub(crate) fn received_x3_trans( if !ctx.s_secret.agree(&s_remote, &mut noise_kk_ss) { return Err(byzantine_fault!(FailedAuth, true)); } + + let new_ratchet_state = create_ratchet_state(hmac, &mut noise, zeta.ratchet_state.chain_len); let mut nk_recv = Zeroizing::new([0u8; HASHLEN]); let mut nk_send = Zeroizing::new([0u8; HASHLEN]); noise.split(hmac, &mut nk_send, &mut nk_recv); - let new_ratchet_state = create_ratchet_state(hmac, &mut noise, zeta.ratchet_state.chain_len); // We must make sure the ratchet key is saved before we transition. let result = app.save_ratchet_state( &s_remote, @@ -776,7 +794,7 @@ pub(crate) fn received_x3_trans( }, ); if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); + return Err(ReceiveError::StorageError(e)); } let (session, current_time) = { @@ -791,7 +809,9 @@ pub(crate) fn received_x3_trans( let mut session_queue = ctx.session_queue.lock().unwrap(); let queue_idx = session_queue.reserve_index(); let current_time = app.time(); + let resend_timer = current_time + App::SETTINGS.resend_time as i64; let session = Arc::new(Session { + ctx: Arc::downgrade(ctx), session_data, was_bob: true, s_remote, @@ -804,7 +824,7 @@ pub(crate) fn received_x3_trans( key_creation_counter: c + 1, key_index: false, keys: [DuplexKey::default(), DuplexKey::default()], - resend_timer: current_time + App::SETTINGS.resend_time as i64, + resend_timer: AtomicI64::new(resend_timer), timeout_timer: current_time + App::SETTINGS.rekey_timeout as i64, beta: ZetaAutomata::S1, }), @@ -815,23 +835,25 @@ pub(crate) fn received_x3_trans( hk_send: App::PrpEnc::new(&zeta.hk_send), hk_recv: App::PrpDec::new(&zeta.hk_recv), }); + { + let mut state = session.state.write().unwrap(); + state.key_mut(false).replace_nk(&nk_send, &nk_recv); + state.key_mut(false).recv.kid = Some(zeta.kid_recv); + state.key_mut(false).recv.replace_kek(&kek_recv); + state.key_mut(false).send.kid = Some(zeta.kid_send); + state.key_mut(false).send.replace_kek(&kek_send); + } - let mut state = session.state.write().unwrap(); - state.key_mut(false).replace_nk(&nk_send, &nk_recv); - state.key_mut(false).recv.kid = Some(zeta.kid_recv); - state.key_mut(false).recv.replace_kek(&kek_recv); - state.key_mut(false).send.kid = Some(zeta.kid_send); - state.key_mut(false).send.replace_kek(&kek_send); - - session_queue.push_reserved(queue_idx, Arc::downgrade(&session), Reverse(state.next_timer())); + session_queue.push_reserved(queue_idx, Arc::downgrade(&session), Reverse(resend_timer)); entry.insert(Arc::downgrade(&session)); + (session, current_time) }; process_timers(app, ctx, &session, current_time, false, true, send); Ok(session) } - Err(e) => Err(ReceiveError::RatchetIoError(e)), + Err(e) => Err(ReceiveError::StorageError(e)), } } else { if !responder_silently_rejects { @@ -895,28 +917,31 @@ pub(crate) fn received_c1_trans( }, ); if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); + return Err(ReceiveError::StorageError(e)); } } drop(state); - let mut state_mut = session.state.write().unwrap(); - - state_mut.ratchet_state2 = None; - state_mut.key_index ^= true; - state_mut.timeout_timer = app.time() - + App::SETTINGS - .rekey_after_time - .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; - state_mut.resend_timer = i64::MAX; - state_mut.beta = ZetaAutomata::S2; - drop(state); + let timeout_timer = { + let mut state = session.state.write().unwrap(); + state.ratchet_state2 = None; + state.key_index ^= true; + state.timeout_timer = app.time() + + App::SETTINGS + .rekey_after_time + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + state.resend_timer = AtomicI64::new(i64::MAX); + state.beta = ZetaAutomata::S2; + state.timeout_timer + }; + drop(kex_lock); + ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timeout_timer)); state = session.state.read().unwrap(); } } let mut c2 = ArrayVec::::new(); c2.extend([0u8; HEADER_SIZE]); - let (c, should_rekey) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; + let (c, _) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; let nonce = to_nonce(PACKET_TYPE_ACK, c); let latest_confirmed_key = state.key_ref(false).send.kek.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; c2.extend(App::Aead::encrypt_in_place(latest_confirmed_key, &nonce, &[], &mut [])); @@ -963,14 +988,18 @@ pub(crate) fn received_c2_trans( return Err(byzantine_fault!(ExpiredCounter, true)); } drop(state); - let mut state = session.state.write().unwrap(); - - state.timeout_timer = app.time() - + App::SETTINGS - .rekey_after_time - .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; - state.resend_timer = i64::MAX; - state.beta = ZetaAutomata::S2; + let timeout_timer = { + let mut state = session.state.write().unwrap(); + state.timeout_timer = app.time() + + App::SETTINGS + .rekey_after_time + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + state.resend_timer = AtomicI64::new(i64::MAX); + state.beta = ZetaAutomata::S2; + state.timeout_timer + }; + drop(kex_lock); + ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timeout_timer)); Ok(()) } /// Corresponds to the trivial Transition Algorithm described for processing D packets found in @@ -1004,8 +1033,8 @@ pub(crate) fn received_d_trans( if !session.window.update(c) { return Err(byzantine_fault!(ExpiredCounter, true)); } - /// - //zeta.expire(); + drop(state); + session.expire_inner(kex_lock, session.state.write().unwrap()); Ok(()) } /// Corresponds to the timer rules of the Zeta State Machine found in Section 4.1 - Definition 3. @@ -1017,13 +1046,13 @@ pub(crate) fn process_timers( force_timeout: bool, force_resend: bool, send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) { +) -> Option { let kex_lock = session.state_machine_lock.lock().unwrap(); let mut state = session.state.read().unwrap(); if force_timeout || state.timeout_timer <= current_time { // Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. match &state.beta { - ZetaAutomata::Null => {} + ZetaAutomata::Null => None, ZetaAutomata::A1(_) | ZetaAutomata::A3 { .. } => { let identity = match &state.beta { ZetaAutomata::A1(a1) => &a1.identity, @@ -1037,8 +1066,8 @@ pub(crate) fn process_timers( } let new_kid_recv = remap(ctx, session, &state); - let mut hash = &mut App::Hash::new(); - let mut hmac = &mut App::HmacHash::new(); + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); if let Some(a1) = create_a1_state( hash, hmac, @@ -1055,20 +1084,24 @@ pub(crate) fn process_timers( let mut x1 = a1.x1.clone(); drop(state); - { + let resend_timer = { let mut state = session.state.write().unwrap(); session.hk_recv.reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); session.hk_send.reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); *state.key_mut(true) = DuplexKey::default(); state.key_mut(true).recv.kid = Some(new_kid_recv); - state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + let resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.resend_timer = AtomicI64::new(resend_timer); state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; state.beta = ZetaAutomata::A1(a1); - } + resend_timer + }; + drop(kex_lock); send(&mut x1, None); + Some(resend_timer) } else { - //zeta.expire(); + None } } ZetaAutomata::S2 => { @@ -1093,84 +1126,97 @@ pub(crate) fn process_timers( let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut k1); // Process message pattern 1 es token. if noise.mix_dh(hmac, &e_secret, &session.s_remote).is_none() { - //zeta.expire(); - return; + return None; } // Process message pattern 1 ss token. noise.mix_key(hmac, session.noise_kk_ss.as_ref()); // Process message pattern 1 payload. let i = k1.len(); k1.extend(new_kid_recv.get().to_be_bytes()); - k1.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..])); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..]); + k1.extend(tag); drop(state); - { + let resend_timer = { let mut state = session.state.write().unwrap(); state.key_mut(true).recv.kid = Some(new_kid_recv); state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + let resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.resend_timer = AtomicI64::new(resend_timer); state.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; - } + resend_timer + }; + drop(kex_lock); let state = session.state.read().unwrap(); - if let Some((c, should_rekey)) = get_counter(session, &state) { + if let Some((c, _)) = get_counter(session, &state) { let nonce = to_nonce(PACKET_TYPE_REKEY_INIT, c); - k1.extend(App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k1)); + let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k1); + k1.extend(tag); set_header(&mut k1, state.key_ref(false).send.kid.unwrap().get(), &nonce); send(&mut k1, Some(&session.hk_send)); } + Some(resend_timer) } ZetaAutomata::S1 { .. } => { log!(app, TimeoutKeyConfirm(session)); - //zeta.expire(); + None } ZetaAutomata::R1 { .. } => { log!(app, TimeoutK1(session)); - //zeta.expire(); + None } ZetaAutomata::R2 { .. } => { log!(app, TimeoutK2(session)); - //zeta.expire(); + None } } - } else if force_resend || state.resend_timer <= current_time { - // Corresponds to the resend timer rules found in Section 4.1 - Definition 3. - state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + } else { + let ts = state.resend_timer.load(Ordering::Relaxed); + let resend_next = current_time + App::SETTINGS.resend_time as i64; + if force_resend || (ts <= current_time && state.resend_timer.fetch_max(resend_next, Ordering::Relaxed) == ts) { + // Corresponds to the resend timer rules found in Section 4.1 - Definition 3. - let (packet_type, mut control_payload) = match &state.beta { - ZetaAutomata::Null => return, - ZetaAutomata::A1(a1) => { - log!(app, ResentX1(session)); - return send(&mut a1.x1.clone(), None); - } - ZetaAutomata::A3 { x3, .. } => { - log!(app, ResentX3(session)); - return send(&mut x3.clone(), Some(&session.hk_send)); - } - ZetaAutomata::S1 => { - log!(app, ResentKeyConfirm(session)); - let mut c1 = ArrayVec::new(); - c1.extend([0u8; HEADER_SIZE]); - (PACKET_TYPE_KEY_CONFIRM, c1) - } - ZetaAutomata::S2 => return, - ZetaAutomata::R1 { k1, .. } => { - log!(app, ResentK1(session)); - (PACKET_TYPE_REKEY_INIT, k1.clone()) - } - ZetaAutomata::R2 { k2, .. } => { - log!(app, ResentK2(session)); - (PACKET_TYPE_REKEY_COMPLETE, k2.clone()) - } - }; - if let Some((c, should_rekey)) = get_counter(session, &state) { - let nonce = to_nonce(packet_type, c); - let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut control_payload); - control_payload.extend(tag); - set_header(&mut control_payload, state.key_ref(false).send.kid.unwrap().get(), &nonce); + let (packet_type, mut control_payload) = match &state.beta { + ZetaAutomata::Null => return None, + ZetaAutomata::A1(a1) => { + log!(app, ResentX1(session)); + send(&mut a1.x1.clone(), None); + return Some(resend_next); + } + ZetaAutomata::A3 { x3, .. } => { + log!(app, ResentX3(session)); + send(&mut x3.clone(), Some(&session.hk_send)); + return Some(resend_next); + } + ZetaAutomata::S1 => { + log!(app, ResentKeyConfirm(session)); + let mut c1 = ArrayVec::new(); + c1.extend([0u8; HEADER_SIZE]); + (PACKET_TYPE_KEY_CONFIRM, c1) + } + ZetaAutomata::S2 => return Some(state.timeout_timer), + ZetaAutomata::R1 { k1, .. } => { + log!(app, ResentK1(session)); + (PACKET_TYPE_REKEY_INIT, k1.clone()) + } + ZetaAutomata::R2 { k2, .. } => { + log!(app, ResentK2(session)); + (PACKET_TYPE_REKEY_COMPLETE, k2.clone()) + } + }; + if let Some((c, _)) = get_counter(session, &state) { + let nonce = to_nonce(packet_type, c); + let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut control_payload); + control_payload.extend(tag); + set_header(&mut control_payload, state.key_ref(false).send.kid.unwrap().get(), &nonce); - send(&mut control_payload, Some(&session.hk_send)); + send(&mut control_payload, Some(&session.hk_send)); + } + Some(resend_next) + } else { + Some(ts) } } } @@ -1190,7 +1236,6 @@ pub(crate) fn received_k1_trans( app: &App, ctx: &Arc>, session: &Arc>, - s_secret: &App::KeyPair, kid: NonZeroU32, n: &[u8; AES_GCM_IV_SIZE], k1: &mut [u8], @@ -1225,7 +1270,7 @@ pub(crate) fn received_k1_trans( let i = k1.len() - AES_GCM_TAG_SIZE; let tag = k1[i..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k1[..i], tag) { + if !App::Aead::decrypt_in_place(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k1[..i], &tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); @@ -1236,17 +1281,17 @@ pub(crate) fn received_k1_trans( let result = (|| { let mut i = 0; let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_KK); - let mut hash = &mut App::Hash::new(); - let mut hmac = &mut App::HmacHash::new(); + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); // Noise process prologue. noise.mix_hash(hash, &session.s_remote.to_bytes()); - noise.mix_hash(hash, &s_secret.public_key_bytes()); + noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); // Process message pattern 1 psk0 token. noise.mix_key_and_hash(hash, hmac, state.ratchet_state1.key.as_ref()); // Process message pattern 1 e token. let e_remote = noise.read_e(hash, hmac, &mut i, &k1).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. - noise.mix_dh(hmac, s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 ss token. noise.mix_key(hmac, session.noise_kk_ss.as_ref()); // Process message pattern 1 payload. @@ -1265,12 +1310,13 @@ pub(crate) fn received_k1_trans( // Process message pattern 2 ee token. noise.mix_dh(hmac, &e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 se token. - noise.mix_dh(hmac, &s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 payload. let i = k2.len(); let new_kid_recv = remap(ctx, session, &state); k2.extend(new_kid_recv.get().to_be_bytes()); - k2.extend(noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..])); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..]); + k2.extend(tag); let new_ratchet_state = create_ratchet_state(hmac, &noise, state.ratchet_state1.chain_len); let result = app.save_ratchet_state( @@ -1285,7 +1331,7 @@ pub(crate) fn received_k1_trans( }, ); if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); + return Err(ReceiveError::StorageError(e)); } let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); @@ -1296,7 +1342,7 @@ pub(crate) fn received_k1_trans( noise.split(hmac, &mut nk_send, &mut nk_recv); drop(state); - { + let resend_timer = { let mut state = session.state.write().unwrap(); state.key_mut(true).replace_nk(&nk_send, &nk_recv); state.key_mut(true).send.kid = Some(kid_send); @@ -1307,24 +1353,28 @@ pub(crate) fn received_k1_trans( state.ratchet_state1 = new_ratchet_state.clone(); let current_time = app.time(); state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); + let resend_timer = current_time + App::SETTINGS.resend_time as i64; state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.resend_timer = AtomicI64::new(resend_timer); state.beta = ZetaAutomata::R2 { k2: k2.clone() }; - } - let mut state = session.state.read().unwrap(); + resend_timer + }; + drop(kex_lock); + ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(resend_timer)); + let state = session.state.read().unwrap(); - /// - let (c, should_rekey) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; + let (c, _) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; let nonce = to_nonce(PACKET_TYPE_REKEY_COMPLETE, c); - k2.extend(App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k2)); + let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k2); + k2.extend(tag); set_header(&mut k2, state.key_ref(false).send.kid.unwrap().get(), &nonce); send(&mut k2, Some(&session.hk_send)); Ok(()) })(); - /// + if matches!(result, Err(ReceiveError::ByzantineFault { .. })) { - //zeta.expire(); + session.expire(); } result } @@ -1335,7 +1385,7 @@ pub(crate) fn received_k2_trans( session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_IV_SIZE], - mut k2: &mut [u8], + k2: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { use FaultType::*; @@ -1358,7 +1408,7 @@ pub(crate) fn received_k2_trans( let i = k2.len() - AES_GCM_TAG_SIZE; let tag = k2[i..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k2[..i], tag) { + if !App::Aead::decrypt_in_place(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k2[..i], &tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); @@ -1369,8 +1419,8 @@ pub(crate) fn received_k2_trans( if let ZetaAutomata::R1 { noise, e_secret, .. } = &state.beta { let mut noise = noise.clone(); let mut i = 0; - let mut hash = &mut App::Hash::new(); - let mut hmac = &mut App::HmacHash::new(); + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); // Process message pattern 2 e token. let e_remote = noise.read_e(hash, hmac, &mut i, &k2).ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. @@ -1399,7 +1449,7 @@ pub(crate) fn received_k2_trans( }, ); if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); + return Err(ReceiveError::StorageError(e)); } let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); let mut kek_send = Zeroizing::new([0u8; HASHLEN]); @@ -1409,7 +1459,7 @@ pub(crate) fn received_k2_trans( noise.split(hmac, &mut nk_recv, &mut nk_send); drop(state); - let current_time = { + let (current_time, resend_timer) = { let mut state = session.state.write().unwrap(); state.key_mut(true).replace_nk(&nk_send, &nk_recv); state.key_mut(true).send.kid = Some(kid_send); @@ -1419,11 +1469,14 @@ pub(crate) fn received_k2_trans( state.key_index ^= true; let current_time = app.time(); state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); + let resend_timer = current_time + App::SETTINGS.resend_time as i64; state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - state.resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.resend_timer = AtomicI64::new(resend_timer); state.beta = ZetaAutomata::S1; - current_time + (current_time, resend_timer) }; + drop(kex_lock); + ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(resend_timer)); process_timers(app, ctx, session, current_time, false, true, send); Ok(()) @@ -1431,23 +1484,156 @@ pub(crate) fn received_k2_trans( unreachable!() } })(); + if matches!(result, Err(ReceiveError::ByzantineFault { .. })) { - //zeta.expire(); + session.expire(); } result } +/// Corresponds to Algorithm 10 found in Section 4.3. +pub(crate) fn receive_payload_in_place( + app: &App, + ctx: &Arc>, + session: &Arc>, + kid: NonZeroU32, + n: &[u8; AES_GCM_IV_SIZE], + fragments: &mut [App::IncomingPacketBuffer], + mut output_buffer: impl Write, +) -> Result<(), ReceiveError> { + use FaultType::*; -//impl Session { -// /// Mark a session as expired. This will make it impossible for this session to successfully -// /// receive or send data or control packets. It is recommended to simply `drop` the session -// /// instead, but this can provide some reassurance in complex shared ownership situations. -// pub fn expire(&mut self) { -// self.0.lock().unwrap().expire(); -// } -//} + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + let is_other = if Some(kid) == state.key_ref(true).recv.kid { + true + } else if Some(kid) == state.key_ref(false).recv.kid { + false + } else { + return Err(byzantine_fault!(OutOfSequence, true)); + }; -//impl Drop for Session { -// fn drop(&mut self) { -// self.expire(); -// } -//} + let mut cipher = state.key_ref(is_other).nk + .as_ref() + .ok_or(byzantine_fault!(OutOfSequence, true))?.start_dec(n); + + // NOTE: This only works because we check the size of every received fragment in the receive + // function, otherwise this could panic. + let mut i = 0; + while i + 1 < fragments.len() { + let fragment = &mut fragments[i].as_mut()[HEADER_SIZE..]; + cipher.decrypt_in_place(fragment); + i += 1; + } + let fragment = &mut fragments[i].as_mut()[HEADER_SIZE..]; + let tag_idx = fragment.len() - AES_GCM_IV_SIZE; + cipher.decrypt_in_place(&mut fragment[..tag_idx]); + if cipher.finish((&fragment[tag_idx..]).try_into().unwrap()) { + return Err(byzantine_fault!(FailedAuth, true)); + } + + let (_, c) = from_nonce(n); + if !session.window.update(c) { + // This error is marked as not happening naturally, but it could occur if something about + // the transport protocol is duplicating packets. + return Err(byzantine_fault!(ExpiredCounter, true)); + } + + drop(cipher); + for fragment in fragments { + let result = output_buffer.write(&fragment.as_ref()[HEADER_SIZE..]); + if let Err(e) = result { + return Err(ReceiveError::IoError(e)); + } + } + + Ok(()) +} + + +impl Drop for Session { + fn drop(&mut self) { + self.expire(); + } +} +impl Session { + /// Mark a session as expired. This will make it impossible for this session to successfully + /// receive or send data or control packets. It is recommended to simply `drop` the session + /// instead, but this can provide some reassurance in complex shared ownership situations. + pub fn expire(&self) { + self.expire_inner(self.state_machine_lock.lock().unwrap(), self.state.write().unwrap()); + } + pub(crate) fn expire_inner(&self, kex_lock: MutexGuard<'_, ()>, mut state: RwLockWriteGuard<'_, MutableState>) { + let mut kids_to_remove = None; + if !matches!(&state.beta, ZetaAutomata::Null) { + self.session_has_expired.store(true, Ordering::Relaxed); + kids_to_remove = Some([state.keys[0].recv.kid, state.keys[1].recv.kid]); + state.keys = [DuplexKey::default(), DuplexKey::default()]; + state.resend_timer = AtomicI64::new(i64::MAX); + state.timeout_timer = i64::MAX; + state.beta = ZetaAutomata::Null; + } + drop(state); + drop(kex_lock); + if let Some(kids_to_remove) = kids_to_remove { + if let Some(ctx) = self.ctx.upgrade() { + ctx.session_queue.lock().unwrap().remove(self.queue_idx); + let mut session_map = ctx.session_map.write().unwrap(); + for kid_recv in kids_to_remove.iter().flatten() { + session_map.remove(kid_recv); + } + } + } + } + /// + ///// The current ratchet state of this session. + ///// The returned values are sensitive and should be securely erased before being dropped. + //pub fn ratchet_states(&self) -> [RatchetState; 2] { + // let state = self.state.read().unwrap(); + // state.ratchet_states.clone() + //} + /// The current ratchet count of this session. + //pub fn ratchet_count(&self) -> u64 { + // self.state.read().unwrap(). + //} + /// Mark a session as expired. This will make it impossible for this session to successfully + /// receive or send data or control packets. It is recommended to simply `drop` the session + /// instead, but this can provide some reassurance in complex shared ownership situations. + //pub fn expire(&self) { + // if let Some(context) = self.context.upgrade() { + // self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); + // } + //} + //fn expire_inner( + // &self, + // context: &Arc>, + // session_queue: &mut IndexedBinaryHeap>, Reverse>, + //) { + // // Prevent this session from being updated. + // session_queue.remove(self.queue_idx); + // self.session_has_expired.store(true, Ordering::Relaxed); + // let _kex_lock = self.state_machine_lock.lock().unwrap(); + // let mut state = self.state.write().unwrap(); + // let mut session_map = context.session_map.write().unwrap(); + // for key in &state.cipher_states { + // if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { + // session_map.remove(&pre_id); + // } + // } + // use OfferStateMachine::*; + // match &state.outgoing_offer { + // NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), + // NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), + // _ => None, + // }; + // state.outgoing_offer = OfferStateMachine::Null; + //} + /// Check whether this session is established. + pub fn established(&self) -> bool { + let state = self.state.read().unwrap(); + !matches!(&state.beta, ZetaAutomata::A1(_) | ZetaAutomata::A3 {..} | ZetaAutomata::Null) + } + /// The static public key of the remote peer. + pub fn remote_static_key(&self) -> &App::PublicKey { + &self.s_remote + } +} diff --git a/src/zssp.rs b/src/zssp.rs index ef49568..5af962f 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -10,6 +10,7 @@ use std::cmp::Reverse; use std::collections::HashMap; +use std::io::Write; use std::hash::Hash; use std::num::{NonZeroU32, NonZeroU64}; use std::ops::DerefMut; @@ -60,16 +61,17 @@ impl Clone for Context { } pub(crate) type SessionMap = RwLock>>>; -pub(crate) struct ContextInner { +pub(crate) type SessionQueue = IndexedBinaryHeap>, Reverse>; +pub struct ContextInner { pub rng: Mutex, pub(crate) s_secret: App::KeyPair, - pub(crate) session_queue: Mutex>, Reverse>>, + /// `session_queue -> state_machine_lock -> state -> session_map` + pub(crate) session_queue: Mutex>, + /// `session_queue -> state_machine_lock -> state -> session_map` pub(crate) session_map: SessionMap, pub(crate) unassociated_defrag_cache: Mutex>, pub(crate) unassociated_handshake_states: UnassociatedHandshakeCache, - //pub(crate) b2_map: Mutex>>, - //hello_defrag: Mutex, pub(crate) challenge: ChallengeContext, } @@ -80,7 +82,7 @@ pub enum IncomingSessionAction { Drop, } -fn parse_fragment_header(incoming_fragment: &[u8]) -> Result<(usize, usize, [u8; AES_GCM_IV_SIZE]), ReceiveError> { +fn parse_fragment_header(incoming_fragment: &[u8]) -> Result<(usize, usize, [u8; AES_GCM_IV_SIZE]), ReceiveError> { let fragment_no = incoming_fragment[FRAGMENT_NO_IDX] as usize; let fragment_count = incoming_fragment[FRAGMENT_COUNT_IDX] as usize; if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS { @@ -92,6 +94,43 @@ fn parse_fragment_header(incoming_fragment: &[u8]) -> Res } +/// Fragments and sends the packet, destroying it in the process. +/// +/// Corresponds to the fragmentation algorithm described in Section 6. +fn send_with_fragmentation( + mut send: impl FnMut(&mut [u8]) -> bool, + mtu: usize, + headered_packet: &mut [u8], + hk_send: Option<&PrpEnc>, +) -> bool { + let payload_len = headered_packet.len() - HEADER_SIZE; + let payload_mtu = mtu - HEADER_SIZE; + debug_assert!(payload_mtu >= 4); + let fragment_count = payload_len.saturating_add(payload_mtu - 1) / payload_mtu; // Ceiling div. + let fragment_base_size = payload_len / fragment_count; + let fragment_size_remainder = payload_len % fragment_count; + + let mut i = HEADER_SIZE; + for fragment_no in 0..fragment_count { + let j = i + fragment_base_size + (fragment_no < fragment_size_remainder) as usize; + let fragment = &mut headered_packet[i - HEADER_SIZE..j]; + + fragment[FRAGMENT_NO_IDX] = fragment_no as u8; + fragment[FRAGMENT_COUNT_IDX] = fragment_count as u8; + + if let Some(hk_send) = hk_send { + hk_send.encrypt_in_place( + (&mut fragment[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap(), + ); + } + if !send(fragment) { + return false; + } + i = j; + } + true +} + impl Context { /// Create a new session context. pub fn new(static_secret_key: App::KeyPair, mut rng: App::Rng) -> Self { @@ -143,6 +182,9 @@ impl Context { static_remote_key, session_data, identity, + |packet, hk_send| { + send_with_fragmentation(send, mtu, packet, hk_send); + } ) } @@ -186,9 +228,8 @@ impl Context { mut send_unassociated_mtu: usize, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, remote_address: &impl Hash, - data_buf: &'a mut [u8], mut incoming_fragment_buf: App::IncomingPacketBuffer, - current_time: i64, + output_buffer: impl Write, ) -> Result, ReceiveError> { use crate::result::FaultType::*; let ctx = &self.0; @@ -198,21 +239,175 @@ impl Context { return Err(byzantine_fault!(FaultType::InvalidPacket, false)); } - // The first section parses the header and looks up relevant state information. If it's a DATA - // or NOP packet it gets handled right here, otherwise we pull out a set of variables and - // continue to the logic that handles KEX and session control packets. + let mut fragment_buffer = Assembled::new(); - let mut assembled_packet = Assembled::new(); // needs to outlive the block below - let mut incoming = None; - let (session, packet_type, fragments) = { - let kid_recv = incoming_fragment[0..KID_SIZE].try_into().unwrap(); - // `from_ne_bytes` because this id was generated locally. - if let Some(kid_recv) = NonZeroU32::new(u32::from_ne_bytes(kid_recv)) { - let session_map = self.0.session_map.read().unwrap(); - let session = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()); - if let Some(Some(session)) = session { - drop(session_map); - session.hk_recv.decrypt_in_place( + let kid_recv = incoming_fragment[0..KID_SIZE].try_into().unwrap(); + if let Some(kid_recv) = NonZeroU32::new(u32::from_be_bytes(kid_recv)) { + let session_map = self.0.session_map.read().unwrap(); + let session = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()); + if let Some(Some(session)) = session { + drop(session_map); + session.hk_recv.decrypt_in_place( + (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) + .try_into() + .unwrap(), + ); + + let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; + let (packet_type, incoming_counter) = from_nonce(&nonce); + + {//vrfy + if packet_type != PACKET_TYPE_DATA { + log!(app, ReceivedRawFragment(p, c, fragment_no, fragment_count)); + } + if packet_type == PACKET_TYPE_HANDSHAKE_RESPONSE { + if !matches!(&session.state.read().unwrap().beta, ZetaAutomata::A1(_)) { + // A resent handshake response from Bob may have arrived out of order, + // after we already received one. + return Err(byzantine_fault!(OutOfSequence, false)); + } + if incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD { + return Err(byzantine_fault!(ExpiredCounter, true)); + } + } else if PACKET_TYPE_USES_COUNTER_RANGE.contains(&packet_type) { + // For DOS resistant reply-protection we need to check that the given counter is + // in the window of valid counters immediately. + // But for packets larger than 1 fragment we can't actually record the + // counter as received until we've authenticated the packet. + // So we check the counter window twice, and only update it the second time + // after the packet has been authenticated. + if !session.window.check(incoming_counter) { + // This can occur naturally if packets arrive way out of order, or + // if they are duplicates. + // This can also be naturally triggered if Bob has just successfully + // received the first session key and is reject all of Alice's resends. + // This can also occur if a session was manually expired, but not + // dropped, and the remote party is still sending us data. + return Err(byzantine_fault!(ExpiredCounter, false)); + } + } else if packet_type == PACKET_TYPE_HANDSHAKE_COMPLETION { + // This can be triggered if Bob successfully received a session key and + // needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3. + return Err(byzantine_fault!(InvalidPacket, false)); + } else { + return Err(byzantine_fault!(InvalidPacket, true)); + } + } + + // Handle defragmentation. + let ret = if packet_type == PACKET_TYPE_DATA { + let fragments = if fragment_count > 1 { + let idx = incoming_counter as usize % session.defrag.len(); + session.defrag[idx].lock().unwrap().assemble( + &nonce, + incoming_fragment_buf, + fragment_no, + fragment_count, + &mut fragment_buffer, + ); + if fragment_buffer.is_empty() { + return Ok(ReceiveOk::Unassociated); + } else { + // We have not yet authenticated the sender so we do not report + // receiving a packet from them. + fragment_buffer.as_mut() + } + } else { + std::slice::from_mut(&mut incoming_fragment_buf) + }; + receive_payload_in_place(app, ctx, &session, kid_recv, &nonce, fragments, output_buffer)?; + SessionEvent::Data + } else { + let mut buffer = ArrayVec::::new(); + let assembled_packet = if fragment_count > 1 { + let idx = incoming_counter as usize % session.defrag.len(); + session.defrag[idx].lock().unwrap().assemble( + &nonce, + incoming_fragment_buf, + fragment_no, + fragment_count, + &mut fragment_buffer, + ); + if fragment_buffer.is_empty() { + return Ok(ReceiveOk::Unassociated); + } else { + for fragment in fragment_buffer.as_ref() { + buffer.try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]).map_err(|_| byzantine_fault!(InvalidPacket, true))?; + } + // We have not yet authenticated the sender so we do not report + // receiving a packet from them. + buffer.as_mut() + } + } else { + &mut incoming_fragment_buf.as_mut()[HEADER_SIZE..] + }; + + let send_associated = |packet: &mut [u8], hk_send: Option<&App::PrpEnc>| { + if let Some((send_fragment, mut mtu)) = send_to(&session) { + mtu = mtu.max(MIN_TRANSPORT_MTU); + send_with_fragmentation(send_fragment, mtu, packet, hk_send); + } + }; + match packet_type { + PACKET_TYPE_HANDSHAKE_RESPONSE => { + log!(app, ReceivedRawX2); + received_x2_trans( + app, + ctx, + &session, + kid_recv, + &nonce, + assembled_packet, + send_associated, + )?; + log!(app, X2IsAuthSentX3(&session)); + SessionEvent::Control + } + PACKET_TYPE_KEY_CONFIRM => { + log!(app, ReceivedRawKeyConfirm); + let result = + received_c1_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet,send_associated)?; + log!(app, KeyConfirmIsAuthSentAck(&session)); + if result { + SessionEvent::Established + } else { + SessionEvent::Control + } + } + PACKET_TYPE_ACK => { + log!(app, ReceivedRawAck); + received_c2_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet)?; + log!(app, AckIsAuth(&session)); + SessionEvent::Control + } + PACKET_TYPE_REKEY_INIT => { + log!(app, ReceivedRawK1); + received_k1_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet, send_associated)?; + log!(app, K1IsAuthSentK2(&session)); + SessionEvent::Control + } + PACKET_TYPE_REKEY_COMPLETE => { + log!(app, ReceivedRawK2); + received_k2_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet, send_associated)?; + log!(app, K2IsAuthSentKeyConfirm(&session)); + SessionEvent::Control + } + PACKET_TYPE_SESSION_REJECTED => { + log!(app, ReceivedRawD); + received_d_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet)?; + log!(app, DIsAuthClosedSession(&session)); + SessionEvent::Rejected + } + _ => return Err(byzantine_fault!(InvalidPacket, true)), // This is unreachable. + } + }; + Ok(ReceiveOk::Session(session, ret)) + } else { + drop(session_map); + // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 + let zeta = self.0.unassociated_handshake_states.get(kid_recv); + if let Some(zeta) = zeta { + App::PrpDec::new(&zeta.hk_recv).decrypt_in_place( (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) .try_into() .unwrap(), @@ -222,211 +417,150 @@ impl Context { let (packet_type, incoming_counter) = from_nonce(&nonce); {//vrfy - if packet_type != PACKET_TYPE_DATA { - log!(app, ReceivedRawFragment(p, c, fragment_no, fragment_count)); - } - if packet_type == PACKET_TYPE_HANDSHAKE_RESPONSE { - if !matches!(&session.state.read().unwrap().beta, ZetaAutomata::A1(_)) { - // A resent handshake response from Bob may have arrived out of order, - // after we already received one. - return Err(byzantine_fault!(OutOfSequence, false)); - } - if incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD { - return Err(byzantine_fault!(ExpiredCounter, true)); - } - } else if PACKET_TYPE_USES_COUNTER_RANGE.contains(&packet_type) { - // For DOS resistant reply-protection we need to check that the given counter is - // in the window of valid counters immediately. - // But for packets larger than 1 fragment we can't actually record the - // counter as received until we've authenticated the packet. - // So we check the counter window twice, and only update it the second time - // after the packet has been authenticated. - if !session.window.check(incoming_counter) { - // This can occur naturally if packets arrive way out of order, or - // if they are duplicates. - // This can also be naturally triggered if Bob has just successfully - // received the first session key and is reject all of Alice's resends. - // This can also occur if a session was manually expired, but not - // dropped, and the remote party is still sending us data. - return Err(byzantine_fault!(ExpiredCounter, false)); - } - } else if packet_type == PACKET_TYPE_HANDSHAKE_COMPLETION { - // This can be triggered if Bob successfully received a session key and - // needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3. - return Err(byzantine_fault!(InvalidPacket, false)); - } else { - return Err(byzantine_fault!(InvalidPacket, true)); + log!(app, ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count)); + if packet_type != PACKET_TYPE_HANDSHAKE_COMPLETION || incoming_counter != 0 { + return Err(byzantine_fault!(InvalidPacket, true)) } } - // Handle defragmentation. - let fragments = if fragment_count > 1 { - let idx = incoming_counter as usize % session.defrag.len(); - session.defrag[idx].lock().unwrap().assemble( + let mut buffer = ArrayVec::::new(); + let assembled_packet = if fragment_count > 1 { + zeta.defrag.lock().unwrap().assemble( &nonce, incoming_fragment_buf, fragment_no, fragment_count, - &mut assembled_packet, + &mut fragment_buffer ); - if assembled_packet.is_empty() { - // We have not yet authenticated the sender so we do not report - // receiving a packet from them. + if fragment_buffer.is_empty() { return Ok(ReceiveOk::Unassociated); } else { - assembled_packet.as_ref() + for fragment in fragment_buffer.as_ref() { + buffer.try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]).map_err(|_| byzantine_fault!(InvalidPacket, true))?; + } + buffer.as_mut() } } else { - std::slice::from_ref(&incoming_fragment_buf) + &mut incoming_fragment_buf.as_mut()[HEADER_SIZE..] }; - - match packet_type { - PACKET_TYPE_DATA => { - let state = session.state.read().unwrap(); - // The error here can occur because the other party is using a brand new - // session key that we have not received yet. - let key = state.cipher_states[key_index] - .as_ref() - .ok_or(byzantine_fault!(FaultType::OutOfSequence, true))?; - let mut c = key.get_receive_cipher(incoming_counter); - c.set_iv(&create_message_nonce(packet_type, incoming_counter)); - - let mut data_len = 0; - - // Decrypt fragments 0..N-1 where N is the number of fragments. - for f in fragments[..(fragments.len() - 1)].iter() { - let f: &[u8] = f.as_ref(); - debug_assert!(f.len() >= HEADER_SIZE); - let current_frag_data_start = data_len; - data_len += f.len() - HEADER_SIZE; - if data_len > data_buf.len() { - return Err(ReceiveError::DataBufferTooSmall); - } - c.decrypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); - } - - // Decrypt final fragment (or only fragment if not fragmented) - let current_frag_data_start = data_len; - let last_fragment = fragments.last().unwrap().as_ref(); - if last_fragment.len() < (HEADER_SIZE + AES_GCM_TAG_SIZE) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE); - if data_len > data_buf.len() { - return Err(ReceiveError::DataBufferTooSmall); - } - let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE; - c.decrypt(&last_fragment[HEADER_SIZE..payload_end], &mut data_buf[current_frag_data_start..data_len]); - - let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..].try_into().unwrap()); - drop(c); - drop(state); - - if !aead_authentication_ok { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - if !session.update_receive_window(incoming_counter) { - // This can be naturally triggered because Bob has just - // successfully received a session key and needs to reject - // all of Alice's resends. - // This can also occur naturally if some part of the outer - // system is duplicating the packets being sent to us. - // We are safely deduplicating them here. - return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); - } - // Packet fully authenticated - return Ok(ReceiveOk::Session(session, SessionEvent::Data(&mut data_buf[..data_len]))); - } - PACKET_TYPE_HANDSHAKE_RESPONSE => { - (Some(session), packet_type, fragments) - } - } - } else { - drop(session_map); - // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 - incoming = self.0.unassociated_handshake_states.get(kid_recv); - if let Some(incoming) = incoming.as_ref() { - App::PrpDec::new(&incoming.hk_recv).decrypt_in_place( - (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) - .try_into() - .unwrap(), - ); - - let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; - let (packet_type, incoming_counter) = from_nonce(&nonce); - - {//vrfy - log!(app, ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count)); - if packet_type != PACKET_TYPE_HANDSHAKE_COMPLETION || incoming_counter != 0 { - return Err(byzantine_fault!(InvalidPacket, true)) - } - } - - let fragments = if fragment_count > 1 { - incoming.defrag.lock().unwrap().assemble( - &nonce, - incoming_fragment_buf, - fragment_no, - fragment_count, - &mut assembled_packet, - ); - if !assembled_packet.is_empty() { - assembled_packet.as_ref() - } else { - return Ok(ReceiveOk::Unassociated); - } - } else { - std::slice::from_ref(&incoming_fragment_buf) - }; - // We must guarantee that this incoming handshake is processed once and only - // once. This prevents catastrophic nonce reuse caused by multithreading. - if self.0.unassociated_handshake_states.remove(kid_recv) { - (None, PACKET_TYPE_HANDSHAKE_COMPLETION, fragments) - } else { - return Ok(ReceiveOk::Unassociated); - } - } else { - // This can occur naturally because either Bob's incoming_sessions cache got - // full so Alice's incoming session was dropped, or the session this packet - // was for was dropped by the application. - return Err(byzantine_fault!(UnknownLocalKeyId, true)); - } - } - } else { - let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; - let (packet_type, incoming_counter) = from_nonce(&nonce); - - {//vrfy - log!(app, ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count)); - if packet_type != PACKET_TYPE_HANDSHAKE_HELLO && packet_type != PACKET_TYPE_CHALLENGE { - return Err(byzantine_fault!(InvalidPacket, true)) - } - } - - let fragments = if fragment_count > 1 { - self.0.unassociated_defrag_cache.lock().unwrap().assemble( - &nonce, - remote_address, - incoming_fragment.len() - HEADER_SIZE, - incoming_fragment_buf, - fragment_no, - fragment_count, - App::SETTINGS.resend_time as i64, - current_time, - &mut assembled_packet, - ); - if !assembled_packet.is_empty() { - assembled_packet.as_ref() - } else { + // We must guarantee that this incoming handshake is processed once and only + // once. This prevents catastrophic nonce reuse caused by multithreading. + if !self.0.unassociated_handshake_states.remove(kid_recv) { return Ok(ReceiveOk::Unassociated); } + + log!(app, ReceivedRawX3); + let session = received_x3_trans(app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { + send_with_fragmentation( + send_unassociated_reply, + send_unassociated_mtu, + packet, + hk_send, + ); + })?; + log!(app, X3IsAuthSentKeyConfirm(&session)); + Ok(ReceiveOk::Session(session, SessionEvent::NewSession)) } else { - std::array::from_ref(&incoming_fragment_buf) - }; - (None, packet_type, fragments) + // This can occur naturally because either Bob's incoming_sessions cache got + // full so Alice's incoming session was dropped, or the session this packet + // was for was dropped by the application. + return Err(byzantine_fault!(UnknownLocalKeyId, true)); + } } - }; + } else { + let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; + let (packet_type, _c) = from_nonce(&nonce); + + {//vrfy + log!(app, ReceivedRawFragment(packet_type, _c, frag_no, frag_count)); + if packet_type != PACKET_TYPE_HANDSHAKE_HELLO && packet_type != PACKET_TYPE_CHALLENGE { + return Err(byzantine_fault!(InvalidPacket, true)) + } + } + + let mut buffer = ArrayVec::::new(); + let assembled_packet = if fragment_count > 1 { + self.0.unassociated_defrag_cache.lock().unwrap().assemble( + &nonce, + remote_address, + incoming_fragment.len() - HEADER_SIZE, + incoming_fragment_buf, + fragment_no, + fragment_count, + App::SETTINGS.resend_time as i64, + app.time(), + &mut fragment_buffer + ); + if fragment_buffer.is_empty() { + return Ok(ReceiveOk::Unassociated); + } else { + for fragment in fragment_buffer.as_ref() { + buffer.try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]).map_err(|_| byzantine_fault!(InvalidPacket, true))?; + } + buffer.as_mut() + } + } else { + &mut incoming_fragment_buf.as_mut()[HEADER_SIZE..] + }; + + if packet_type == PACKET_TYPE_HANDSHAKE_HELLO { + log!(app, ReceivedRawX1); + + if !(HANDSHAKE_HELLO_CHALLENGE_MIN_SIZE..=HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE).contains(&assembled_packet.len()) { + return Err(byzantine_fault!(InvalidPacket, true)); + } + // Process recv challenge layer. + let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; + let result = ctx.challenge.process_hello::(remote_address, (&assembled_packet[challenge_start..]).try_into().unwrap()); + if let Err(challenge) = result { + log!(app, X1FailedChallengeSentNewChallenge); + let mut challenge_packet = ArrayVec::::new(); + challenge_packet.extend([0u8; HEADER_SIZE]); + challenge_packet.try_extend_from_slice(&assembled_packet[..KID_SIZE]).unwrap(); + challenge_packet.extend(challenge); + let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); + challenge_packet[FRAGMENT_COUNT_IDX] = 1; + challenge_packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce); + + send_unassociated_reply(&mut challenge_packet); + // If we issue a challenge the first hello packet will always fail. + return Err(byzantine_fault!(FailedAuth, false)); + } else { + log!(app, X1SucceededChallenge); + } + + // Process recv zeta layer. + received_x1_trans(app, ctx, &nonce, assembled_packet, |packet, hk_send| { + send_with_fragmentation( + send_unassociated_reply, + send_unassociated_mtu, + packet, + hk_send, + ); + })?; + log!(app, X1IsAuthSentX2); + + Ok(ReceiveOk::Unassociated) + } else if packet_type == PACKET_TYPE_CHALLENGE { + log!(app, ReceivedRawChallenge); + // Process recv challenge layer. + if assembled_packet.len() != KID_SIZE + CHALLENGE_SIZE { + return Err(byzantine_fault!(InvalidPacket, true)); + } + if let Some(kid_recv) = NonZeroU32::new(u32::from_be_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) { + if let Some(Some(session)) = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()) { + respond_to_challenge(ctx, &session, &assembled_packet[KID_SIZE..].try_into().unwrap()); + log!(app, ChallengeIsAuth(&session)); + return Ok(ReceiveOk::Unassociated); + } + } + Err(byzantine_fault!(UnknownLocalKeyId, true)) + } else { + Err(byzantine_fault!(InvalidPacket, true)) + } + } } + /* /// Send data over the session. /// /// * `session` - The session to send to @@ -672,60 +806,5 @@ impl Context { self.0.unassociated_handshake_states.service(current_time); next_service_time - } -} - -impl Session { - /// - ///// The current ratchet state of this session. - ///// The returned values are sensitive and should be securely erased before being dropped. - //pub fn ratchet_states(&self) -> [RatchetState; 2] { - // let state = self.state.read().unwrap(); - // state.ratchet_states.clone() - //} - /// The current ratchet count of this session. - //pub fn ratchet_count(&self) -> u64 { - // self.state.read().unwrap(). - //} - /// Mark a session as expired. This will make it impossible for this session to successfully - /// receive or send data or control packets. It is recommended to simply `drop` the session - /// instead, but this can provide some reassurance in complex shared ownership situations. - //pub fn expire(&self) { - // if let Some(context) = self.context.upgrade() { - // self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); - // } - //} - //fn expire_inner( - // &self, - // context: &Arc>, - // session_queue: &mut IndexedBinaryHeap>, Reverse>, - //) { - // // Prevent this session from being updated. - // session_queue.remove(self.queue_idx); - // self.session_has_expired.store(true, Ordering::Relaxed); - // let _kex_lock = self.state_machine_lock.lock().unwrap(); - // let mut state = self.state.write().unwrap(); - // let mut session_map = context.session_map.write().unwrap(); - // for key in &state.cipher_states { - // if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { - // session_map.remove(&pre_id); - // } - // } - // use OfferStateMachine::*; - // match &state.outgoing_offer { - // NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), - // NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), - // _ => None, - // }; - // state.outgoing_offer = OfferStateMachine::Null; - //} - /// Check whether this session is established. - pub fn established(&self) -> bool { - let state = self.state.read().unwrap(); - !matches!(&state.beta, ZetaAutomata::A1(_) | ZetaAutomata::A3 {..} | ZetaAutomata::Null) - } - /// The static public key of the remote peer. - pub fn remote_static_key(&self) -> &Application::PublicKey { - &self.s_remote - } + } */ } From ff80eeaf685fde99cfd037bc58013fbb88143968 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 7 Aug 2023 20:04:42 -0400 Subject: [PATCH 58/91] cargo fmt --- src/antireplay.rs | 2 +- src/applicationlayer.rs | 9 +- src/challenge.rs | 12 +- src/crypto/aes.rs | 24 +++- src/crypto/kyber1024.rs | 12 +- src/crypto/mod.rs | 2 +- src/crypto/sha512.rs | 1 - src/frag_cache.rs | 49 +++++-- src/indexed_heap.rs | 6 +- src/lib.rs | 10 +- src/log_event.rs | 11 +- src/proto.rs | 18 ++- src/symmetric_state.rs | 45 ++++-- src/zeta.rs | 307 +++++++++++++++++++++++++++++++--------- src/zssp.rs | 101 +++++++------ 15 files changed, 446 insertions(+), 163 deletions(-) diff --git a/src/antireplay.rs b/src/antireplay.rs index 80cd707..81bc5a0 100644 --- a/src/antireplay.rs +++ b/src/antireplay.rs @@ -1,6 +1,6 @@ use std::sync::atomic::{AtomicU64, Ordering}; -pub struct Window ([AtomicU64; L]); +pub struct Window([AtomicU64; L]); impl Window { pub fn new() -> Self { diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 2dcfc96..4d7fcfc 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -11,10 +11,10 @@ use crate::crypto::aes::{AesDec, AesEnc, HighThroughputAesGcmPool, LowThroughput use crate::crypto::kyber1024::Kyber1024PrivateKey; use crate::crypto::p384::{P384KeyPair, P384PublicKey}; use crate::crypto::rand_core::{CryptoRng, RngCore}; -use crate::crypto::sha512::{HmacSha512, HashSha512}; -use crate::RatchetState; +use crate::crypto::sha512::{HashSha512, HmacSha512}; use crate::proto::RATCHET_SIZE; use crate::zeta::Session; +use crate::RatchetState; //use crate::{log_event::LogEvent, Session}; /// A container for a vast majority of the dynamic settings within ZSSP, including all time-based settings. @@ -210,7 +210,10 @@ pub trait ApplicationLayer: Sized { /// If `RatchetAction::DowngradeRatchet` is returned we will attempt to convince Alice to downgrade /// to the empty ratchet key, restarting the ratchet chain. /// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped. - fn restore_by_fingerprint(&self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, Self::StorageError>; + fn restore_by_fingerprint( + &self, + ratchet_fingerprint: &[u8; RATCHET_SIZE], + ) -> Result, Self::StorageError>; /// Lookup the specific ratchet states based on the identity of the peer being communicated with. /// This function will be called whenever Alice attempts to open a session, or Bob attempts /// to verify Alice's identity. diff --git a/src/challenge.rs b/src/challenge.rs index c47af7b..11e4578 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -4,12 +4,15 @@ use std::sync::atomic::{AtomicU64, Ordering}; use rand_core::{CryptoRng, RngCore}; use crate::antireplay::Window; -use crate::crypto::{secure_eq, sha512::{HashSha512, SHA512_HASH_SIZE}}; +use crate::crypto::{ + secure_eq, + sha512::{HashSha512, SHA512_HASH_SIZE}, +}; use crate::proto::*; pub struct ChallengeContext { counter: AtomicU64, - antireplay_window: Window, + antireplay_window: Window, salt: [u8; SALT_SIZE], } @@ -57,7 +60,10 @@ impl ChallengeContext { ) -> Result<(), [u8; CHALLENGE_SIZE]> { let c = u64::from_be_bytes(response[..COUNTER_SIZE].try_into().unwrap()); let mut work_buf = [0u8; SHA512_HASH_SIZE]; - if self.antireplay_window.check(c) && secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac::(c, addr)) && verify_pow::(response, &mut work_buf) { + if self.antireplay_window.check(c) + && secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac::(c, addr)) + && verify_pow::(response, &mut work_buf) + { self.antireplay_window.update(c); Ok(()) } else { diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index 2e5f36a..9a4e242 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -39,7 +39,6 @@ pub trait AesDec: Send + Sync { fn decrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]); } - pub trait AesGcmEncContext { fn encrypt(&mut self, input: &[u8], output: &mut [u8]); @@ -54,8 +53,12 @@ pub trait AesGcmDecContext { } pub trait HighThroughputAesGcmPool: Send + Sync { - type EncContext<'a>: AesGcmEncContext where Self: 'a; - type DecContext<'a>: AesGcmDecContext where Self: 'a; + type EncContext<'a>: AesGcmEncContext + where + Self: 'a; + type DecContext<'a>: AesGcmDecContext + where + Self: 'a; fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self; @@ -64,7 +67,18 @@ pub trait HighThroughputAesGcmPool: Send + Sync { } pub trait LowThroughputAesGcm { - fn encrypt_in_place(key: &[u8; AES_256_KEY_SIZE], iv: &[u8; AES_GCM_IV_SIZE], aad: &[u8], data: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE]; + fn encrypt_in_place( + key: &[u8; AES_256_KEY_SIZE], + iv: &[u8; AES_GCM_IV_SIZE], + aad: &[u8], + data: &mut [u8], + ) -> [u8; AES_GCM_TAG_SIZE]; #[must_use] - fn decrypt_in_place(key: &[u8; AES_256_KEY_SIZE], iv: &[u8; AES_GCM_IV_SIZE], aad: &[u8], data: &mut [u8], tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; + fn decrypt_in_place( + key: &[u8; AES_256_KEY_SIZE], + iv: &[u8; AES_GCM_IV_SIZE], + aad: &[u8], + data: &mut [u8], + tag: &[u8; AES_GCM_TAG_SIZE], + ) -> bool; } diff --git a/src/crypto/kyber1024.rs b/src/crypto/kyber1024.rs index 5cccc30..74c9571 100644 --- a/src/crypto/kyber1024.rs +++ b/src/crypto/kyber1024.rs @@ -25,12 +25,20 @@ pub trait Kyber1024PrivateKey: Sized + Send + Sync { /// **CRITICAL**: This must return `None` if the given `public_key` is invalid in any way /// according to the Kyber1024 spec. #[must_use] - fn encapsulate(rng: &mut Rng, public_key: &[u8; KYBER_PUBLIC_KEY_SIZE], plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE]) -> Option<[u8; KYBER_CIPHERTEXT_SIZE]>; + fn encapsulate( + rng: &mut Rng, + public_key: &[u8; KYBER_PUBLIC_KEY_SIZE], + plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE], + ) -> Option<[u8; KYBER_CIPHERTEXT_SIZE]>; /// Decapsulate a Kyber1024 `ciphertext` received from the remote peer, retreiving /// the raw bytes of the original plaintext. This plaintext is immediately hashed and deleted. /// /// **CRITICAL**: This must return `None` if the given `ciphertext` is invalid in any way /// according to the Kyber1024 spec. #[must_use] - fn decapsulate(&self, ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE], plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE]) -> bool; + fn decapsulate( + &self, + ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE], + plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE], + ) -> bool; } diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 22dcb9a..f31b7f6 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,9 +1,9 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. pub mod aes; +pub mod kyber1024; pub mod p384; pub mod sha512; -pub mod kyber1024; // We re-export our dependencies so it is less of a headache for the implementor to use the same // exact version of them. diff --git a/src/crypto/sha512.rs b/src/crypto/sha512.rs index bfb4b0d..94178c1 100644 --- a/src/crypto/sha512.rs +++ b/src/crypto/sha512.rs @@ -13,7 +13,6 @@ pub trait HashSha512 { fn finish_and_reset(&mut self, output: &mut [u8; SHA512_HASH_SIZE]); } - /// Opaque HMAC-SHA-512 implementation. /// Does not need to be threadsafe. pub trait HmacSha512 { diff --git a/src/frag_cache.rs b/src/frag_cache.rs index fab7a78..e132f4c 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -67,7 +67,10 @@ impl UnassociatedFragCache { ret_assembled: &mut Assembled, ) { debug_assert!(MAX_FRAGMENTS < MAX_UNASSOCIATED_FRAGMENTS); - if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS || fragment_size > MAX_UNASSOCIATED_PACKET_SIZE { + if fragment_no >= fragment_count + || fragment_count > MAX_FRAGMENTS + || fragment_size > MAX_UNASSOCIATED_PACKET_SIZE + { return; } @@ -144,7 +147,10 @@ impl UnassociatedFragCache { let new_size = entry.packet_size + fragment_size as u32; let got = 1u64.wrapping_shl(fragment_no as u32); - if got & entry.fragment_have == 0 && fragment_count == entry.fragment_count as usize && new_size <= MAX_UNASSOCIATED_PACKET_SIZE as u32 { + if got & entry.fragment_have == 0 + && fragment_count == entry.fragment_count as usize + && new_size <= MAX_UNASSOCIATED_PACKET_SIZE as u32 + { entry.packet_size = new_size; entry.fragment_have |= got; @@ -156,7 +162,6 @@ impl UnassociatedFragCache { let start_idx = entry.frags_idx as usize; unsafe { for i in start_idx..start_idx + fragment_count { - ret_assembled.push(self.frags[i % self.frags.len()].assume_init_read()) } } @@ -263,13 +268,30 @@ fn test_cache() { // If the timeout is 1 we should be guaranteed to get our packet cached. let mut nonce = [0; 12]; nonce[..4].copy_from_slice(&i.to_be_bytes()); - cache.assemble(&nonce, 0, fragment.len(), fragment, j, fragment_count, 1, time, &mut assembled); + cache.assemble( + &nonce, + 0, + fragment.len(), + fragment, + j, + fragment_count, + 1, + time, + &mut assembled, + ); time += 1; } } if drop >= fragment_count { - assert!(!assembled.is_empty(), "Packet was dropped from the cache when it shouldn't have"); - assert_eq!(assembled.as_ref().len(), fragment_count, "Cache returned the wrong packet"); + assert!( + !assembled.is_empty(), + "Packet was dropped from the cache when it shouldn't have" + ); + assert_eq!( + assembled.as_ref().len(), + fragment_count, + "Cache returned the wrong packet" + ); for j in 0..fragment_count { assert_eq!(assembled.as_ref()[j][7], r, "Cache returned a corrupted packet"); } @@ -281,14 +303,25 @@ fn test_cache() { if in_progress.len() > 0 { let to_remain = (xorshift64_random() as usize % in_progress_fragments) + 16; while in_progress_fragments > to_remain { - let (id, fragment_count, mut packet) = in_progress.swap_remove(xorshift64_random() as usize % in_progress.len()); + let (id, fragment_count, mut packet) = + in_progress.swap_remove(xorshift64_random() as usize % in_progress.len()); for _ in 0..((xorshift64_random() as usize % packet.len()) + 1) { let (no, fragment) = packet.swap_remove(xorshift64_random() as usize % packet.len()); assembled.clear(); let mut nonce = [0; 12]; nonce[..4].copy_from_slice(&id.to_be_bytes()); - cache.assemble(&nonce, 0, fragment.len(), fragment, no as usize, fragment_count as usize, 1000, time, &mut assembled); + cache.assemble( + &nonce, + 0, + fragment.len(), + fragment, + no as usize, + fragment_count as usize, + 1000, + time, + &mut assembled, + ); time += 1; in_progress_fragments -= 1; diff --git a/src/indexed_heap.rs b/src/indexed_heap.rs index 70085dc..c83b8ca 100644 --- a/src/indexed_heap.rs +++ b/src/indexed_heap.rs @@ -52,7 +52,8 @@ impl IndexedBinaryHeap { let child0_idx = parent_idx * 2 + 1; let child1_idx = child0_idx + 1; if child0_idx < self.data.len() { - let largest_child = if child1_idx < self.data.len() && self.data[child1_idx].1 > self.data[child0_idx].1 { + let largest_child = if child1_idx < self.data.len() && self.data[child1_idx].1 > self.data[child0_idx].1 + { child1_idx } else { child0_idx @@ -155,7 +156,8 @@ impl IndexedBinaryHeap { .map(|data_idx| std::mem::replace(&mut self.data[data_idx].0, new_item)) } pub fn get(&self, idx: BinaryHeapIndex) -> Option<(&T, &P)> { - self.deref_index(idx).map(|data_idx| (&self.data[data_idx].0, &self.data[data_idx].1)) + self.deref_index(idx) + .map(|data_idx| (&self.data[data_idx].0, &self.data[data_idx].1)) } pub fn get_mut(&mut self, idx: BinaryHeapIndex) -> Option<(&mut T, &P)> { self.deref_index(idx).map(|data_idx| { diff --git a/src/lib.rs b/src/lib.rs index 61c0db5..44a6e59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,20 +7,20 @@ */ pub mod crypto; +mod antireplay; mod applicationlayer; -mod fragged; +mod challenge; mod frag_cache; +mod fragged; mod handshake_cache; mod indexed_heap; mod log_event; mod proto; mod ratchet_state; -mod symmetric_state; -mod antireplay; -mod challenge; pub mod result; -mod zssp; +mod symmetric_state; mod zeta; +mod zssp; //mod context; //pub mod error; diff --git a/src/log_event.rs b/src/log_event.rs index cf96367..8690bf9 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -7,7 +7,7 @@ */ use std::sync::Arc; -use crate::{ApplicationLayer, zeta::Session}; +use crate::{zeta::Session, ApplicationLayer}; /// ZSSP events that might be interesting to log or aggregate into metrics. pub enum LogEvent<'a, Application: ApplicationLayer> { @@ -51,9 +51,12 @@ impl<'a, Application: ApplicationLayer> std::fmt::Debug for LogEvent<'a, Applica ServiceKKTimeout(_) => write!(f, "ServiceKKTimeout"), ServiceKeyConfirmResend(_) => write!(f, "ServiceKeyConfirmResend"), ServiceKeyConfirmTimeout(_) => write!(f, "ServiceKeyConfirmTimeout"), - ReceiveUnassociatedFragment(arg0, arg1, arg2) => { - f.debug_tuple("ReceiveUnassociatedFragment").field(arg0).field(arg1).field(arg2).finish() - } + ReceiveUnassociatedFragment(arg0, arg1, arg2) => f + .debug_tuple("ReceiveUnassociatedFragment") + .field(arg0) + .field(arg1) + .field(arg2) + .finish(), ReceiveUncheckedXK1 => write!(f, "ReceiveUncheckedXK1"), ReceiveCheckXK1Challenge(arg0) => f.debug_tuple("ReceiveCheckXK1Challenge").field(arg0).finish(), ReceiveValidXK1 => write!(f, "ReceiveValidXK1"), diff --git a/src/proto.rs b/src/proto.rs index c11f88f..d7d5eaf 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -1,4 +1,9 @@ -use crate::crypto::{aes::{AES_GCM_TAG_SIZE, AES_GCM_IV_SIZE}, kyber1024::{KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE}, p384::P384_PUBLIC_KEY_SIZE, sha512::SHA512_HASH_SIZE}; +use crate::crypto::{ + aes::{AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}, + kyber1024::{KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE}, + p384::P384_PUBLIC_KEY_SIZE, + sha512::SHA512_HASH_SIZE, +}; /* Common constants */ @@ -75,7 +80,8 @@ pub(crate) const HASHLEN: usize = SHA512_HASH_SIZE; pub const RATCHET_SIZE: usize = 32; /// Initial value of 'h'. -pub(crate) const PROTOCOL_NAME_NOISE_XK: &[u8; HASHLEN] = b"Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; +pub(crate) const PROTOCOL_NAME_NOISE_XK: &[u8; HASHLEN] = + b"Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; /// Initial value of 'ck' for rekeying. pub(crate) const PROTOCOL_NAME_NOISE_KK: &[u8; HASHLEN] = b"Noise_KKpsk0_P384_AESGCM_SHA512\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; @@ -101,7 +107,7 @@ pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 1 << 24; /// When Bob issues a challenge to Alice to mitigate DDOS, Bob will only accept Alice's /// response once, and then its attached counter is added to the window. pub(crate) const CHALLENGE_COUNTER_WINDOW_MAX_OOO: usize = 32; -pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - 1<<16; +pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - 1 << 16; /* Packet constants */ @@ -117,7 +123,8 @@ pub(crate) const PACKET_TYPE_DATA: u8 = 8; pub(crate) const PACKET_TYPE_CHALLENGE: u8 = 9; pub(crate) const PACKET_TYPE_USES_COUNTER_RANGE: std::ops::Range = 3..9; -pub(crate) const HANDSHAKE_HELLO_MIN_SIZE: usize = KID_SIZE + P384_PUBLIC_KEY_SIZE + KYBER_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; +pub(crate) const HANDSHAKE_HELLO_MIN_SIZE: usize = + KID_SIZE + P384_PUBLIC_KEY_SIZE + KYBER_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; pub(crate) const HANDSHAKE_HELLO_MAX_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + RATCHET_SIZE; pub(crate) const HANDSHAKE_HELLO_CHALLENGE_MIN_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + CHALLENGE_SIZE; @@ -125,7 +132,8 @@ pub(crate) const HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE: usize = HANDSHAKE_HELLO_MAX pub(crate) const HEADERED_HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE: usize = HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE + HEADER_SIZE; -pub(crate) const HANDSHAKE_RESPONSE_SIZE: usize = P384_PUBLIC_KEY_SIZE + KYBER_CIPHERTEXT_SIZE + AES_GCM_TAG_SIZE + KID_SIZE + AES_GCM_TAG_SIZE; +pub(crate) const HANDSHAKE_RESPONSE_SIZE: usize = + P384_PUBLIC_KEY_SIZE + KYBER_CIPHERTEXT_SIZE + AES_GCM_TAG_SIZE + KID_SIZE + AES_GCM_TAG_SIZE; pub(crate) const HEADERED_HANDSHAKE_RESPONSE_SIZE: usize = HANDSHAKE_RESPONSE_SIZE + HEADER_SIZE; pub(crate) const HANDSHAKE_COMPLETION_MIN_SIZE: usize = P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + 0 + AES_GCM_TAG_SIZE; diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 591fdb4..e2f0e5c 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -3,10 +3,10 @@ use std::marker::PhantomData; use arrayvec::ArrayVec; use zeroize::Zeroizing; -use crate::crypto::aes::{LowThroughputAesGcm, HighThroughputAesGcmPool, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}; +use crate::crypto::aes::{HighThroughputAesGcmPool, LowThroughputAesGcm, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}; use crate::crypto::sha512::{HashSha512, HmacSha512}; -use crate::{applicationlayer::ApplicationLayer, crypto::aes::AES_256_KEY_SIZE}; use crate::proto::*; +use crate::{applicationlayer::ApplicationLayer, crypto::aes::AES_256_KEY_SIZE}; pub struct SymmetricState { k: Zeroizing<[u8; AES_256_KEY_SIZE]>, @@ -27,7 +27,6 @@ impl Clone for SymmetricState { } } - impl SymmetricState { /// HMAC-SHA512 key derivation based on KBKDF Counter Mode: /// https://csrc.nist.gov/publications/detail/sp/800-108/rev-1/final. @@ -93,7 +92,15 @@ impl SymmetricState { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_k = Zeroizing::new([0u8; HASHLEN]); - self.kbkdf(hmac, input_key_material, LABEL_KBKDF_CHAIN, 2, &mut next_ck, Some(&mut temp_k), None); + self.kbkdf( + hmac, + input_key_material, + LABEL_KBKDF_CHAIN, + 2, + &mut next_ck, + Some(&mut temp_k), + None, + ); *self.ck = *next_ck; self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); @@ -133,7 +140,12 @@ impl SymmetricState { self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } /// Corresponds to Noise `MixKeyAndHash`. - pub fn mix_key_and_hash_no_init(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + pub fn mix_key_and_hash_no_init( + &mut self, + hash: &mut App::Hash, + hmac: &mut App::HmacHash, + input_key_material: &[u8], + ) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_h = [0u8; HASHLEN]; @@ -152,7 +164,12 @@ impl SymmetricState { } /// Corresponds to Noise `EncryptAndHash`. #[must_use] - pub fn encrypt_and_hash_in_place(&mut self, hash: &mut App::Hash, iv: [u8; AES_GCM_IV_SIZE], data: &mut [u8]) -> [u8; AES_GCM_TAG_SIZE] { + pub fn encrypt_and_hash_in_place( + &mut self, + hash: &mut App::Hash, + iv: [u8; AES_GCM_IV_SIZE], + data: &mut [u8], + ) -> [u8; AES_GCM_TAG_SIZE] { let tag = App::Aead::encrypt_in_place(&self.k, &iv, &self.h, data); hash.update(&self.h); hash.update(data); @@ -162,7 +179,13 @@ impl SymmetricState { } /// Corresponds to Noise `DecryptAndHash`. #[must_use] - pub fn decrypt_and_hash_in_place(&mut self, hash: &mut App::Hash, iv: [u8; AES_GCM_IV_SIZE], data: &mut [u8], tag: [u8; AES_GCM_TAG_SIZE]) -> bool { + pub fn decrypt_and_hash_in_place( + &mut self, + hash: &mut App::Hash, + iv: [u8; AES_GCM_IV_SIZE], + data: &mut [u8], + tag: [u8; AES_GCM_TAG_SIZE], + ) -> bool { hash.update(&self.h); hash.update(data); hash.update(&tag); @@ -178,7 +201,13 @@ impl SymmetricState { /// is forward secrect and is cryptographically independent from all other produced keys. /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. - pub fn get_ask(&self, hmac: &mut App::HmacHash, label: &[u8; 4], key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { + pub fn get_ask( + &self, + hmac: &mut App::HmacHash, + label: &[u8; 4], + key1: &mut [u8; HASHLEN], + key2: &mut [u8; HASHLEN], + ) { self.kbkdf(hmac, &self.h, label, 2, key1, Some(key2), None); } /// Used for internally debugging a key exchange. diff --git a/src/zeta.rs b/src/zeta.rs index 8475cf0..f0b0c4a 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -5,26 +5,28 @@ use std::collections::HashMap; use std::io::Write; use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; -use std::sync::atomic::{AtomicU64, Ordering, AtomicBool, AtomicI64}; -use std::sync::{Arc, Mutex, Weak, RwLock, MutexGuard, RwLockWriteGuard}; +use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; +use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockWriteGuard, Weak}; use zeroize::Zeroizing; use crate::antireplay::Window; use crate::applicationlayer::ApplicationLayer; use crate::applicationlayer::RatchetUpdate; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; -use crate::zssp::{ContextInner, log}; +use crate::zssp::{log, ContextInner}; //use crate::context::{log, ContextInner, SessionMap}; use crate::crypto::aes::*; +use crate::crypto::kyber1024::{ + Kyber1024PrivateKey, KYBER_CIPHERTEXT_SIZE, KYBER_PLAINTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE, +}; use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; use crate::crypto::sha512::{HashSha512, HmacSha512}; -use crate::crypto::kyber1024::{Kyber1024PrivateKey, KYBER_PUBLIC_KEY_SIZE, KYBER_CIPHERTEXT_SIZE, KYBER_PLAINTEXT_SIZE}; +use crate::fragged::Fragged; use crate::indexed_heap::BinaryHeapIndex; use crate::proto::*; use crate::ratchet_state::RatchetState; -use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError, ReceiveOk}; +use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError}; use crate::symmetric_state::SymmetricState; -use crate::fragged::Fragged; #[cfg(feature = "logging")] use crate::LogEvent::*; @@ -50,11 +52,19 @@ pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { let c_start = n.len() - 8; (n[c_start - 1], u64::from_be_bytes(n[c_start..].try_into().unwrap())) } -fn create_ratchet_state(hmac: &mut App::HmacHash, noise: &SymmetricState, pre_chain_len: u64) -> RatchetState { +fn create_ratchet_state( + hmac: &mut App::HmacHash, + noise: &SymmetricState, + pre_chain_len: u64, +) -> RatchetState { let mut rk = Zeroizing::new([0u8; HASHLEN]); let mut rf = Zeroizing::new([0u8; HASHLEN]); noise.get_ask(hmac, LABEL_RATCHET_STATE, &mut rk, &mut rf); - RatchetState::new(Zeroizing::new(rk[..RATCHET_SIZE].try_into().unwrap()), Zeroizing::new(rf[..RATCHET_SIZE].try_into().unwrap()), pre_chain_len + 1) + RatchetState::new( + Zeroizing::new(rk[..RATCHET_SIZE].try_into().unwrap()), + Zeroizing::new(rf[..RATCHET_SIZE].try_into().unwrap()), + pre_chain_len + 1, + ) } fn get_counter(session: &Session, state: &MutableState) -> Option<(u64, bool)> { if session.session_has_expired.load(Ordering::Relaxed) { @@ -131,7 +141,10 @@ impl Default for DuplexKey { } impl DuplexKey { fn replace_nk(&mut self, nk_send: &[u8; HASHLEN], nk_recv: &[u8; HASHLEN]) { - self.nk = Some(App::AeadPool::new((&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap())) + self.nk = Some(App::AeadPool::new( + (&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), + (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap(), + )) } } @@ -144,7 +157,9 @@ impl Keys { fn replace_kek(&mut self, kek: &[u8; HASHLEN]) { // We want to give rust the best chance of implementing this in a way that does // not leak the key on the stack. - self.kek.get_or_insert(Zeroizing::new([0u8; AES_256_KEY_SIZE])).copy_from_slice(&kek[..AES_256_KEY_SIZE]); + self.kek + .get_or_insert(Zeroizing::new([0u8; AES_256_KEY_SIZE])) + .copy_from_slice(&kek[..AES_256_KEY_SIZE]); } } @@ -185,7 +200,13 @@ pub(crate) enum ZetaAutomata { } impl SymmetricState { - fn write_e(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, rng: &Mutex, packet: &mut ArrayVec) -> App::KeyPair { + fn write_e( + &mut self, + hash: &mut App::Hash, + hmac: &mut App::HmacHash, + rng: &Mutex, + packet: &mut ArrayVec, + ) -> App::KeyPair { let e_secret = App::KeyPair::generate(rng.lock().unwrap().deref_mut()); let pub_key = e_secret.public_key_bytes(); packet.extend(pub_key); @@ -193,7 +214,13 @@ impl SymmetricState { self.mix_key(hmac, &pub_key); e_secret } - fn read_e(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, i: &mut usize, packet: &[u8]) -> Option { + fn read_e( + &mut self, + hash: &mut App::Hash, + hmac: &mut App::HmacHash, + i: &mut usize, + packet: &[u8], + ) -> Option { let j = *i + P384_PUBLIC_KEY_SIZE; let pub_key = &packet[*i..j]; self.mix_hash(hash, pub_key); @@ -238,7 +265,9 @@ fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_IV_SIZE]) { } fn create_a1_state( - hash: &mut App::Hash, hmac: &mut App::HmacHash, rng: &Mutex, + hash: &mut App::Hash, + hmac: &mut App::HmacHash, + rng: &Mutex, s_remote: &App::PublicKey, kid_recv: NonZeroU32, ratchet_state1: &RatchetState, @@ -312,11 +341,21 @@ pub(crate) fn trans_to_a1( let hash = &mut App::Hash::new(); let hmac = &mut App::HmacHash::new(); - let a1 = create_a1_state(hash, hmac, &ctx.rng, &s_remote, kid_recv, &ratchet_state1, ratchet_state2.as_ref(), identity).ok_or(OpenError::InvalidPublicKey)?; + let a1 = create_a1_state( + hash, + hmac, + &ctx.rng, + &s_remote, + kid_recv, + &ratchet_state1, + ratchet_state2.as_ref(), + identity, + ) + .ok_or(OpenError::InvalidPublicKey)?; let mut noise_kk_ss = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if !ctx.s_secret.agree(&s_remote, &mut noise_kk_ss) { - return Err(OpenError::InvalidPublicKey) + return Err(OpenError::InvalidPublicKey); } let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); @@ -359,18 +398,18 @@ pub(crate) fn trans_to_a1( } session_map.insert(kid_recv, Arc::downgrade(&session)); - session_queue.push_reserved( - queue_idx, - Arc::downgrade(&session), - Reverse(resend_timer), - ); + session_queue.push_reserved(queue_idx, Arc::downgrade(&session), Reverse(resend_timer)); send(&mut x1, None); Ok(session) } /// Corresponds to Algorithm 13 found in Section 5. -pub(crate) fn respond_to_challenge(ctx: &Arc>, session: &Session, challenge: &[u8; CHALLENGE_SIZE]) { +pub(crate) fn respond_to_challenge( + ctx: &Arc>, + session: &Session, + challenge: &[u8; CHALLENGE_SIZE], +) { let mut state = session.state.write().unwrap(); if let ZetaAutomata::A1(a1) = &mut state.beta { let response_start = a1.x1.len() - CHALLENGE_SIZE; @@ -408,13 +447,18 @@ pub(crate) fn received_x1_trans( // Noise process prologue. let j = i + KID_SIZE; noise.mix_hash(hash, &x1[i..j]); - let kid_send = NonZeroU32::new(u32::from_be_bytes(x1[i..j].try_into().unwrap())).ok_or(byzantine_fault!(InvalidPacket, true))?; + let kid_send = NonZeroU32::new(u32::from_be_bytes(x1[i..j].try_into().unwrap())) + .ok_or(byzantine_fault!(InvalidPacket, true))?; noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); i = j; // Process message pattern 1 e token. - let e_remote = noise.read_e(hash, hmac, &mut i, &x1).ok_or(byzantine_fault!(FailedAuth, true))?; + let e_remote = noise + .read_e(hash, hmac, &mut i, &x1) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. - noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &ctx.s_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 e1 token. let j = i + KYBER_PUBLIC_KEY_SIZE; let k = i + AES_GCM_TAG_SIZE; @@ -462,12 +506,19 @@ pub(crate) fn received_x1_trans( // Process message pattern 2 e token. let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut x2); // Process message pattern 2 ee token. - noise.mix_dh(hmac, &e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &e_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ekem1 token. { let i = x2.len(); let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); - let ekem1 = App::Kem::encapsulate(ctx.rng.lock().unwrap().deref_mut(), (&x1[e1_start..e1_end]).try_into().unwrap(), &mut ekem1_secret).ok_or(byzantine_fault!(FailedAuth, true))?; + let ekem1 = App::Kem::encapsulate( + ctx.rng.lock().unwrap().deref_mut(), + (&x1[e1_start..e1_end]).try_into().unwrap(), + &mut ekem1_secret, + ) + .ok_or(byzantine_fault!(FailedAuth, true))?; x2.extend(ekem1); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..]); x2.extend(tag); @@ -476,7 +527,10 @@ pub(crate) fn received_x1_trans( // Process message pattern 2 psk2 token. noise.mix_key_and_hash(hash, hmac, ratchet_state.key.as_ref()); // Process message pattern 2 payload. - let kid_recv = gen_kid(ctx.session_map.read().unwrap().deref(), ctx.rng.lock().unwrap().deref_mut()); + let kid_recv = gen_kid( + ctx.session_map.read().unwrap().deref(), + ctx.rng.lock().unwrap().deref_mut(), + ); let i = x2.len(); x2.extend(kid_recv.get().to_be_bytes()); @@ -504,10 +558,13 @@ pub(crate) fn received_x1_trans( noise, defrag: Mutex::new(Fragged::new()), }), - app.time() + app.time(), ); - send(&mut x2, Some(&App::PrpEnc::new(&hk_send[..AES_256_KEY_SIZE].try_into().unwrap()))); + send( + &mut x2, + Some(&App::PrpEnc::new(&hk_send[..AES_256_KEY_SIZE].try_into().unwrap())), + ); Ok(()) } /// Corresponds to Transition Algorithm 3 found in Section 4.3. @@ -548,9 +605,13 @@ pub(crate) fn received_x2_trans( let mut noise = a1.noise.clone(); let mut i = 0; // Process message pattern 2 e token. - let e_remote = noise.read_e(hash, hmac, &mut i, &x2).ok_or(byzantine_fault!(FailedAuth, true))?; + let e_remote = noise + .read_e(hash, hmac, &mut i, &x2) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. - noise.mix_dh(hmac, &a1.e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &a1.e_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ekem1 token. let j = i + KYBER_CIPHERTEXT_SIZE; let k = j + AES_GCM_TAG_SIZE; @@ -559,7 +620,10 @@ pub(crate) fn received_x2_trans( return Err(byzantine_fault!(FailedAuth, true)); } let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); - if !a1.e1_secret.decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) { + if !a1 + .e1_secret + .decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) + { return Err(byzantine_fault!(FailedAuth, true)); } noise.mix_key(hmac, ekem1_secret.as_ref()); @@ -618,7 +682,9 @@ pub(crate) fn received_x2_trans( let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..]); x3.extend(tag); // Process message pattern 3 se token. - noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &ctx.s_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 3 payload. let i = x3.len(); x3.try_extend_from_slice(&a1.identity).unwrap(); @@ -676,11 +742,19 @@ pub(crate) fn received_x2_trans( // This return is unreachable. return Err(byzantine_fault!(FailedAuth, true)); }; - state.beta = ZetaAutomata::A3 { identity: a1.identity.clone(), x3: x3.clone(), kid_send: kid_send.get(), nonce }; + state.beta = ZetaAutomata::A3 { + identity: a1.identity.clone(), + x3: x3.clone(), + kid_send: kid_send.get(), + nonce, + }; resend_timer }; drop(kex_lock); - ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(resend_timer)); + ctx.session_queue + .lock() + .unwrap() + .change_priority(session.queue_idx, Reverse(resend_timer)); Ok(x3) })(); @@ -722,10 +796,13 @@ pub(crate) fn received_x3_trans( if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 1), &mut x3[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let s_remote = App::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()).ok_or(byzantine_fault!(FailedAuth, true))?; + let s_remote = + App::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()).ok_or(byzantine_fault!(FailedAuth, true))?; i = k; // Process message pattern 3 se token. - noise.mix_dh(hmac, &zeta.e_secret, &s_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &zeta.e_secret, &s_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 3 payload. let k = x3.len(); let j = k - AES_GCM_TAG_SIZE; @@ -752,7 +829,12 @@ pub(crate) fn received_x3_trans( let mut d = ArrayVec::::new(); d.extend([0u8; HEADER_SIZE]); let nonce = to_nonce(PACKET_TYPE_SESSION_REJECTED, c); - d.extend(App::Aead::encrypt_in_place((&kek_send[..AES_256_KEY_SIZE]).try_into().unwrap(), &nonce, &[], &mut [])); + d.extend(App::Aead::encrypt_in_place( + (&kek_send[..AES_256_KEY_SIZE]).try_into().unwrap(), + &nonce, + &[], + &mut [], + )); set_header(&mut d, zeta.kid_send.get(), &nonce); d }; @@ -891,7 +973,12 @@ pub(crate) fn received_c1_trans( return Err(byzantine_fault!(OutOfSequence, false)); }; - let specified_key = state.key_ref(is_other).recv.kek.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; + let specified_key = state + .key_ref(is_other) + .recv + .kek + .as_ref() + .ok_or(byzantine_fault!(OutOfSequence, true))?; let tag = c1[..].try_into().unwrap(); if !App::Aead::decrypt_in_place(specified_key, n, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); @@ -928,13 +1015,17 @@ pub(crate) fn received_c1_trans( state.timeout_timer = app.time() + App::SETTINGS .rekey_after_time - .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) + as i64; state.resend_timer = AtomicI64::new(i64::MAX); state.beta = ZetaAutomata::S2; state.timeout_timer }; drop(kex_lock); - ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timeout_timer)); + ctx.session_queue + .lock() + .unwrap() + .change_priority(session.queue_idx, Reverse(timeout_timer)); state = session.state.read().unwrap(); } } @@ -943,9 +1034,18 @@ pub(crate) fn received_c1_trans( c2.extend([0u8; HEADER_SIZE]); let (c, _) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; let nonce = to_nonce(PACKET_TYPE_ACK, c); - let latest_confirmed_key = state.key_ref(false).send.kek.as_ref().ok_or(byzantine_fault!(OutOfSequence, true))?; + let latest_confirmed_key = state + .key_ref(false) + .send + .kek + .as_ref() + .ok_or(byzantine_fault!(OutOfSequence, true))?; c2.extend(App::Aead::encrypt_in_place(latest_confirmed_key, &nonce, &[], &mut [])); - let kid_send = state.key_ref(false).send.kid.ok_or(byzantine_fault!(OutOfSequence, false))?; + let kid_send = state + .key_ref(false) + .send + .kid + .ok_or(byzantine_fault!(OutOfSequence, false))?; set_header(&mut c2, kid_send.get(), &nonce); send(&mut c2, Some(&session.hk_send)); @@ -993,13 +1093,17 @@ pub(crate) fn received_c2_trans( state.timeout_timer = app.time() + App::SETTINGS .rekey_after_time - .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) as i64; + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) + as i64; state.resend_timer = AtomicI64::new(i64::MAX); state.beta = ZetaAutomata::S2; state.timeout_timer }; drop(kex_lock); - ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timeout_timer)); + ctx.session_queue + .lock() + .unwrap() + .change_priority(session.queue_idx, Reverse(timeout_timer)); Ok(()) } /// Corresponds to the trivial Transition Algorithm described for processing D packets found in @@ -1086,8 +1190,12 @@ pub(crate) fn process_timers( drop(state); let resend_timer = { let mut state = session.state.write().unwrap(); - session.hk_recv.reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); - session.hk_send.reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); + session + .hk_recv + .reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); + session + .hk_send + .reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); *state.key_mut(true) = DuplexKey::default(); state.key_mut(true).recv.kid = Some(new_kid_recv); let resend_timer = current_time + App::SETTINGS.resend_time as i64; @@ -1151,7 +1259,12 @@ pub(crate) fn process_timers( if let Some((c, _)) = get_counter(session, &state) { let nonce = to_nonce(PACKET_TYPE_REKEY_INIT, c); - let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k1); + let tag = App::Aead::encrypt_in_place( + state.key_ref(false).send.kek.as_ref().unwrap(), + &nonce, + &[], + &mut k1, + ); k1.extend(tag); set_header(&mut k1, state.key_ref(false).send.kid.unwrap().get(), &nonce); @@ -1208,9 +1321,18 @@ pub(crate) fn process_timers( }; if let Some((c, _)) = get_counter(session, &state) { let nonce = to_nonce(packet_type, c); - let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut control_payload); + let tag = App::Aead::encrypt_in_place( + state.key_ref(false).send.kek.as_ref().unwrap(), + &nonce, + &[], + &mut control_payload, + ); control_payload.extend(tag); - set_header(&mut control_payload, state.key_ref(false).send.kid.unwrap().get(), &nonce); + set_header( + &mut control_payload, + state.key_ref(false).send.kid.unwrap().get(), + &nonce, + ); send(&mut control_payload, Some(&session.hk_send)); } @@ -1220,7 +1342,11 @@ pub(crate) fn process_timers( } } } -fn remap(ctx: &Arc>, session: &Arc>, state: &MutableState) -> NonZeroU32 { +fn remap( + ctx: &Arc>, + session: &Arc>, + state: &MutableState, +) -> NonZeroU32 { let mut session_map = ctx.session_map.write().unwrap(); let weak = if let Some(Some(weak)) = state.key_ref(true).recv.kid.as_ref().map(|kid| session_map.remove(kid)) { weak @@ -1270,7 +1396,13 @@ pub(crate) fn received_k1_trans( let i = k1.len() - AES_GCM_TAG_SIZE; let tag = k1[i..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k1[..i], &tag) { + if !App::Aead::decrypt_in_place( + state.key_ref(false).recv.kek.as_ref().unwrap(), + n, + &[], + &mut k1[..i], + &tag, + ) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); @@ -1289,9 +1421,13 @@ pub(crate) fn received_k1_trans( // Process message pattern 1 psk0 token. noise.mix_key_and_hash(hash, hmac, state.ratchet_state1.key.as_ref()); // Process message pattern 1 e token. - let e_remote = noise.read_e(hash, hmac, &mut i, &k1).ok_or(byzantine_fault!(FailedAuth, true))?; + let e_remote = noise + .read_e(hash, hmac, &mut i, &k1) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. - noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &ctx.s_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 ss token. noise.mix_key(hmac, session.noise_kk_ss.as_ref()); // Process message pattern 1 payload. @@ -1301,16 +1437,21 @@ pub(crate) fn received_k1_trans( if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let kid_send = NonZeroU32::new(u32::from_be_bytes(k1[i..j].try_into().unwrap())).ok_or(byzantine_fault!(FailedAuth, true))?; + let kid_send = NonZeroU32::new(u32::from_be_bytes(k1[i..j].try_into().unwrap())) + .ok_or(byzantine_fault!(FailedAuth, true))?; let mut k2 = ArrayVec::::new(); k2.extend([0u8; HEADER_SIZE]); // Process message pattern 2 e token. let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut k2); // Process message pattern 2 ee token. - noise.mix_dh(hmac, &e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &e_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 se token. - noise.mix_dh(hmac, &ctx.s_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, &ctx.s_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 payload. let i = k2.len(); let new_kid_recv = remap(ctx, session, &state); @@ -1360,7 +1501,10 @@ pub(crate) fn received_k1_trans( resend_timer }; drop(kex_lock); - ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(resend_timer)); + ctx.session_queue + .lock() + .unwrap() + .change_priority(session.queue_idx, Reverse(resend_timer)); let state = session.state.read().unwrap(); let (c, _) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; @@ -1408,7 +1552,13 @@ pub(crate) fn received_k2_trans( let i = k2.len() - AES_GCM_TAG_SIZE; let tag = k2[i..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut k2[..i], &tag) { + if !App::Aead::decrypt_in_place( + state.key_ref(false).recv.kek.as_ref().unwrap(), + n, + &[], + &mut k2[..i], + &tag, + ) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); @@ -1422,11 +1572,17 @@ pub(crate) fn received_k2_trans( let hash = &mut App::Hash::new(); let hmac = &mut App::HmacHash::new(); // Process message pattern 2 e token. - let e_remote = noise.read_e(hash, hmac, &mut i, &k2).ok_or(byzantine_fault!(FailedAuth, true))?; + let e_remote = noise + .read_e(hash, hmac, &mut i, &k2) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. - noise.mix_dh(hmac, e_secret, &e_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, e_secret, &e_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 se token. - noise.mix_dh(hmac, e_secret, &session.s_remote).ok_or(byzantine_fault!(FailedAuth, true))?; + noise + .mix_dh(hmac, e_secret, &session.s_remote) + .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 payload. let j = i + KID_SIZE; let k = j + AES_GCM_TAG_SIZE; @@ -1434,7 +1590,8 @@ pub(crate) fn received_k2_trans( if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let kid_send = NonZeroU32::new(u32::from_be_bytes(k2[i..j].try_into().unwrap())).ok_or(byzantine_fault!(InvalidPacket, true))?; + let kid_send = NonZeroU32::new(u32::from_be_bytes(k2[i..j].try_into().unwrap())) + .ok_or(byzantine_fault!(InvalidPacket, true))?; let new_ratchet_state = create_ratchet_state(hmac, &noise, state.ratchet_state1.chain_len); let result = app.save_ratchet_state( @@ -1476,7 +1633,10 @@ pub(crate) fn received_k2_trans( (current_time, resend_timer) }; drop(kex_lock); - ctx.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(resend_timer)); + ctx.session_queue + .lock() + .unwrap() + .change_priority(session.queue_idx, Reverse(resend_timer)); process_timers(app, ctx, session, current_time, false, true, send); Ok(()) @@ -1512,9 +1672,12 @@ pub(crate) fn receive_payload_in_place( return Err(byzantine_fault!(OutOfSequence, true)); }; - let mut cipher = state.key_ref(is_other).nk + let mut cipher = state + .key_ref(is_other) + .nk .as_ref() - .ok_or(byzantine_fault!(OutOfSequence, true))?.start_dec(n); + .ok_or(byzantine_fault!(OutOfSequence, true))? + .start_dec(n); // NOTE: This only works because we check the size of every received fragment in the receive // function, otherwise this could panic. @@ -1549,7 +1712,6 @@ pub(crate) fn receive_payload_in_place( Ok(()) } - impl Drop for Session { fn drop(&mut self) { self.expire(); @@ -1562,7 +1724,11 @@ impl Session { pub fn expire(&self) { self.expire_inner(self.state_machine_lock.lock().unwrap(), self.state.write().unwrap()); } - pub(crate) fn expire_inner(&self, kex_lock: MutexGuard<'_, ()>, mut state: RwLockWriteGuard<'_, MutableState>) { + pub(crate) fn expire_inner( + &self, + kex_lock: MutexGuard<'_, ()>, + mut state: RwLockWriteGuard<'_, MutableState>, + ) { let mut kids_to_remove = None; if !matches!(&state.beta, ZetaAutomata::Null) { self.session_has_expired.store(true, Ordering::Relaxed); @@ -1630,7 +1796,10 @@ impl Session { /// Check whether this session is established. pub fn established(&self) -> bool { let state = self.state.read().unwrap(); - !matches!(&state.beta, ZetaAutomata::A1(_) | ZetaAutomata::A3 {..} | ZetaAutomata::Null) + !matches!( + &state.beta, + ZetaAutomata::A1(_) | ZetaAutomata::A3 { .. } | ZetaAutomata::Null + ) } /// The static public key of the remote peer. pub fn remote_static_key(&self) -> &App::PublicKey { diff --git a/src/zssp.rs b/src/zssp.rs index 5af962f..fac5118 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -10,8 +10,8 @@ use std::cmp::Reverse; use std::collections::HashMap; -use std::io::Write; use std::hash::Hash; +use std::io::Write; use std::num::{NonZeroU32, NonZeroU64}; use std::ops::DerefMut; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; @@ -20,21 +20,21 @@ use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; use arrayvec::ArrayVec; use zeroize::Zeroizing; -use crate::zeta::*; use crate::challenge::ChallengeContext; -use crate::crypto::aes::{AesDec, AesEnc, AES_256_KEY_SIZE, AES_GCM_TAG_SIZE, AES_GCM_IV_SIZE}; +use crate::crypto::aes::{AesDec, AesEnc, AES_256_KEY_SIZE, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}; use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; use crate::crypto::rand_core::RngCore; -use crate::crypto::sha512::{HmacSha512, HashSha512}; +use crate::crypto::sha512::{HashSha512, HmacSha512}; +use crate::zeta::*; -use crate::result::{FaultType, OpenError, ReceiveError, SendError, ReceiveOk, byzantine_fault, SessionEvent}; use crate::frag_cache::UnassociatedFragCache; use crate::fragged::{Assembled, Fragged}; use crate::handshake_cache::UnassociatedHandshakeCache; use crate::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; use crate::log_event::LogEvent; use crate::proto::*; +use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; use crate::symmetric_state::SymmetricState; use crate::{applicationlayer::*, RatchetState}; @@ -82,7 +82,9 @@ pub enum IncomingSessionAction { Drop, } -fn parse_fragment_header(incoming_fragment: &[u8]) -> Result<(usize, usize, [u8; AES_GCM_IV_SIZE]), ReceiveError> { +fn parse_fragment_header( + incoming_fragment: &[u8], +) -> Result<(usize, usize, [u8; AES_GCM_IV_SIZE]), ReceiveError> { let fragment_no = incoming_fragment[FRAGMENT_NO_IDX] as usize; let fragment_count = incoming_fragment[FRAGMENT_COUNT_IDX] as usize; if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS { @@ -93,7 +95,6 @@ fn parse_fragment_header(incoming_fragment: &[u8]) -> Result<(usiz Ok((fragment_no, fragment_count, nonce)) } - /// Fragments and sends the packet, destroying it in the process. /// /// Corresponds to the fragmentation algorithm described in Section 6. @@ -119,9 +120,7 @@ fn send_with_fragmentation( fragment[FRAGMENT_COUNT_IDX] = fragment_count as u8; if let Some(hk_send) = hk_send { - hk_send.encrypt_in_place( - (&mut fragment[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap(), - ); + hk_send.encrypt_in_place((&mut fragment[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap()); } if !send(fragment) { return false; @@ -184,7 +183,7 @@ impl Context { identity, |packet, hk_send| { send_with_fragmentation(send, mtu, packet, hk_send); - } + }, ) } @@ -256,7 +255,8 @@ impl Context { let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; let (packet_type, incoming_counter) = from_nonce(&nonce); - {//vrfy + { + //vrfy if packet_type != PACKET_TYPE_DATA { log!(app, ReceivedRawFragment(p, c, fragment_no, fragment_count)); } @@ -332,7 +332,9 @@ impl Context { return Ok(ReceiveOk::Unassociated); } else { for fragment in fragment_buffer.as_ref() { - buffer.try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]).map_err(|_| byzantine_fault!(InvalidPacket, true))?; + buffer + .try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]) + .map_err(|_| byzantine_fault!(InvalidPacket, true))?; } // We have not yet authenticated the sender so we do not report // receiving a packet from them. @@ -351,7 +353,13 @@ impl Context { match packet_type { PACKET_TYPE_HANDSHAKE_RESPONSE => { log!(app, ReceivedRawX2); - received_x2_trans( + received_x2_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet, send_associated)?; + log!(app, X2IsAuthSentX3(&session)); + SessionEvent::Control + } + PACKET_TYPE_KEY_CONFIRM => { + log!(app, ReceivedRawKeyConfirm); + let result = received_c1_trans( app, ctx, &session, @@ -360,13 +368,6 @@ impl Context { assembled_packet, send_associated, )?; - log!(app, X2IsAuthSentX3(&session)); - SessionEvent::Control - } - PACKET_TYPE_KEY_CONFIRM => { - log!(app, ReceivedRawKeyConfirm); - let result = - received_c1_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet,send_associated)?; log!(app, KeyConfirmIsAuthSentAck(&session)); if result { SessionEvent::Established @@ -416,10 +417,14 @@ impl Context { let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; let (packet_type, incoming_counter) = from_nonce(&nonce); - {//vrfy - log!(app, ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count)); + { + //vrfy + log!( + app, + ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count) + ); if packet_type != PACKET_TYPE_HANDSHAKE_COMPLETION || incoming_counter != 0 { - return Err(byzantine_fault!(InvalidPacket, true)) + return Err(byzantine_fault!(InvalidPacket, true)); } } @@ -430,13 +435,15 @@ impl Context { incoming_fragment_buf, fragment_no, fragment_count, - &mut fragment_buffer + &mut fragment_buffer, ); if fragment_buffer.is_empty() { return Ok(ReceiveOk::Unassociated); } else { for fragment in fragment_buffer.as_ref() { - buffer.try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]).map_err(|_| byzantine_fault!(InvalidPacket, true))?; + buffer + .try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]) + .map_err(|_| byzantine_fault!(InvalidPacket, true))?; } buffer.as_mut() } @@ -451,12 +458,7 @@ impl Context { log!(app, ReceivedRawX3); let session = received_x3_trans(app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { - send_with_fragmentation( - send_unassociated_reply, - send_unassociated_mtu, - packet, - hk_send, - ); + send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); })?; log!(app, X3IsAuthSentKeyConfirm(&session)); Ok(ReceiveOk::Session(session, SessionEvent::NewSession)) @@ -471,10 +473,11 @@ impl Context { let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; let (packet_type, _c) = from_nonce(&nonce); - {//vrfy + { + //vrfy log!(app, ReceivedRawFragment(packet_type, _c, frag_no, frag_count)); if packet_type != PACKET_TYPE_HANDSHAKE_HELLO && packet_type != PACKET_TYPE_CHALLENGE { - return Err(byzantine_fault!(InvalidPacket, true)) + return Err(byzantine_fault!(InvalidPacket, true)); } } @@ -489,13 +492,15 @@ impl Context { fragment_count, App::SETTINGS.resend_time as i64, app.time(), - &mut fragment_buffer + &mut fragment_buffer, ); if fragment_buffer.is_empty() { return Ok(ReceiveOk::Unassociated); } else { for fragment in fragment_buffer.as_ref() { - buffer.try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]).map_err(|_| byzantine_fault!(InvalidPacket, true))?; + buffer + .try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]) + .map_err(|_| byzantine_fault!(InvalidPacket, true))?; } buffer.as_mut() } @@ -506,17 +511,24 @@ impl Context { if packet_type == PACKET_TYPE_HANDSHAKE_HELLO { log!(app, ReceivedRawX1); - if !(HANDSHAKE_HELLO_CHALLENGE_MIN_SIZE..=HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE).contains(&assembled_packet.len()) { + if !(HANDSHAKE_HELLO_CHALLENGE_MIN_SIZE..=HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE) + .contains(&assembled_packet.len()) + { return Err(byzantine_fault!(InvalidPacket, true)); } // Process recv challenge layer. let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; - let result = ctx.challenge.process_hello::(remote_address, (&assembled_packet[challenge_start..]).try_into().unwrap()); + let result = ctx.challenge.process_hello::( + remote_address, + (&assembled_packet[challenge_start..]).try_into().unwrap(), + ); if let Err(challenge) = result { log!(app, X1FailedChallengeSentNewChallenge); let mut challenge_packet = ArrayVec::::new(); challenge_packet.extend([0u8; HEADER_SIZE]); - challenge_packet.try_extend_from_slice(&assembled_packet[..KID_SIZE]).unwrap(); + challenge_packet + .try_extend_from_slice(&assembled_packet[..KID_SIZE]) + .unwrap(); challenge_packet.extend(challenge); let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); challenge_packet[FRAGMENT_COUNT_IDX] = 1; @@ -531,12 +543,7 @@ impl Context { // Process recv zeta layer. received_x1_trans(app, ctx, &nonce, assembled_packet, |packet, hk_send| { - send_with_fragmentation( - send_unassociated_reply, - send_unassociated_mtu, - packet, - hk_send, - ); + send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); })?; log!(app, X1IsAuthSentX2); @@ -547,7 +554,9 @@ impl Context { if assembled_packet.len() != KID_SIZE + CHALLENGE_SIZE { return Err(byzantine_fault!(InvalidPacket, true)); } - if let Some(kid_recv) = NonZeroU32::new(u32::from_be_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) { + if let Some(kid_recv) = + NonZeroU32::new(u32::from_be_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) + { if let Some(Some(session)) = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()) { respond_to_challenge(ctx, &session, &assembled_packet[KID_SIZE..].try_into().unwrap()); log!(app, ChallengeIsAuth(&session)); From 2a7784b5226bce33fdb6abdac33dca80ce6891b1 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 7 Aug 2023 23:07:16 -0400 Subject: [PATCH 59/91] cargo fmt --- src/applicationlayer.rs | 2 +- src/handshake_cache.rs | 2 +- src/lib.rs | 16 +-- src/log_event.rs | 2 +- src/proto.rs | 1 - src/zeta.rs | 125 +++++++++++++++++----- src/zssp.rs | 227 ++++++---------------------------------- 7 files changed, 142 insertions(+), 233 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index 4d7fcfc..f078770 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -14,7 +14,7 @@ use crate::crypto::rand_core::{CryptoRng, RngCore}; use crate::crypto::sha512::{HashSha512, HmacSha512}; use crate::proto::RATCHET_SIZE; use crate::zeta::Session; -use crate::RatchetState; +use crate::ratchet_state::RatchetState; //use crate::{log_event::LogEvent, Session}; /// A container for a vast majority of the dynamic settings within ZSSP, including all time-based settings. diff --git a/src/handshake_cache.rs b/src/handshake_cache.rs index 8ce3852..1344412 100644 --- a/src/handshake_cache.rs +++ b/src/handshake_cache.rs @@ -11,7 +11,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; use crate::zeta::StateB2; -use crate::{proto::MAX_UNASSOCIATED_HANDSHAKE_STATES, ApplicationLayer}; +use crate::{proto::MAX_UNASSOCIATED_HANDSHAKE_STATES, applicationlayer::ApplicationLayer}; pub(crate) struct UnassociatedHandshakeCache { has_pending: AtomicBool, // Allowed to be falsely positive diff --git a/src/lib.rs b/src/lib.rs index 44a6e59..358dc61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,24 +8,24 @@ pub mod crypto; mod antireplay; -mod applicationlayer; +pub mod applicationlayer; mod challenge; mod frag_cache; mod fragged; mod handshake_cache; mod indexed_heap; -mod log_event; -mod proto; -mod ratchet_state; +pub mod log_event; +pub mod proto; +pub mod ratchet_state; pub mod result; mod symmetric_state; -mod zeta; -mod zssp; +pub mod zeta; +pub mod zssp; //mod context; //pub mod error; -pub use crate::applicationlayer::ApplicationLayer; +//pub use crate::applicationlayer::ApplicationLayer; //pub use crate::log_event::LogEvent; //pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_SIZE}; -pub use crate::ratchet_state::RatchetState; +//pub use crate::ratchet_state::RatchetState; //pub use crate::zssp::{Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/log_event.rs b/src/log_event.rs index 8690bf9..43fdc32 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -7,7 +7,7 @@ */ use std::sync::Arc; -use crate::{zeta::Session, ApplicationLayer}; +use crate::{zeta::Session, applicationlayer::ApplicationLayer}; /// ZSSP events that might be interesting to log or aggregate into metrics. pub enum LogEvent<'a, Application: ApplicationLayer> { diff --git a/src/proto.rs b/src/proto.rs index d7d5eaf..91ea301 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -92,7 +92,6 @@ pub(crate) const LABEL_RATCHET_STATE: &[u8; 4] = b"ASKR"; pub(crate) const LABEL_HEADER_KEY: &[u8; 4] = b"ASKH"; pub(crate) const LABEL_KEX_KEY: &[u8; 4] = b"ASKK"; -pub(crate) const INIT_COUNTER: u64 = 0; pub(crate) const EXPIRE_AFTER_USES: u64 = 1 << 32 - 1; /// Determines the number of counters a session will remember. If a counter arrives over /// this amount out of order relative to other received counters, it is likely to be diff --git a/src/zeta.rs b/src/zeta.rs index f0b0c4a..79e7eba 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -6,14 +6,14 @@ use std::io::Write; use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; -use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockWriteGuard, Weak}; +use std::sync::{Arc, Mutex, RwLock, Weak}; use zeroize::Zeroizing; use crate::antireplay::Window; use crate::applicationlayer::ApplicationLayer; use crate::applicationlayer::RatchetUpdate; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; -use crate::zssp::{log, ContextInner}; +use crate::zssp::{log, ContextInner, SessionQueue}; //use crate::context::{log, ContextInner, SessionMap}; use crate::crypto::aes::*; use crate::crypto::kyber1024::{ @@ -25,7 +25,7 @@ use crate::fragged::Fragged; use crate::indexed_heap::BinaryHeapIndex; use crate::proto::*; use crate::ratchet_state::RatchetState; -use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError}; +use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; use crate::symmetric_state::SymmetricState; #[cfg(feature = "logging")] use crate::LogEvent::*; @@ -71,8 +71,12 @@ fn get_counter(session: &Session, state: &MutableSta None } else { let c = session.send_counter.fetch_add(1, Ordering::Relaxed); + if c > state.key_creation_counter + EXPIRE_AFTER_USES { + session.session_has_expired.store(true, Ordering::SeqCst); + return None; + } if c > THREAD_SAFE_COUNTER_HARD_EXPIRE { - session.session_has_expired.store(true, Ordering::SeqCst) + session.session_has_expired.store(true, Ordering::SeqCst); } Some((c, c > state.key_creation_counter + App::SETTINGS.rekey_after_key_uses)) } @@ -1109,8 +1113,6 @@ pub(crate) fn received_c2_trans( /// Corresponds to the trivial Transition Algorithm described for processing D packets found in /// Section 4.3. pub(crate) fn received_d_trans( - app: &App, - ctx: &Arc>, session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_IV_SIZE], @@ -1137,8 +1139,10 @@ pub(crate) fn received_d_trans( if !session.window.update(c) { return Err(byzantine_fault!(ExpiredCounter, true)); } + drop(state); - session.expire_inner(kex_lock, session.state.write().unwrap()); + drop(kex_lock); + session.expire(); Ok(()) } /// Corresponds to the timer rules of the Zeta State Machine found in Section 4.1 - Definition 3. @@ -1152,7 +1156,7 @@ pub(crate) fn process_timers( send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Option { let kex_lock = session.state_machine_lock.lock().unwrap(); - let mut state = session.state.read().unwrap(); + let state = session.state.read().unwrap(); if force_timeout || state.timeout_timer <= current_time { // Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. match &state.beta { @@ -1221,8 +1225,8 @@ pub(crate) fn process_timers( // ... // -> psk, e, es, ss let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); - let mut hash = &mut App::Hash::new(); - let mut hmac = &mut App::HmacHash::new(); + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); let mut k1 = ArrayVec::::new(); k1.extend([0u8; HEADER_SIZE]); // Noise process prologue. @@ -1650,10 +1654,77 @@ pub(crate) fn received_k2_trans( } result } +/// Corresponds to Algorithm 9 found in Section 4.3. +pub(crate) fn send_payload( + ctx: &Arc>, + session: &Arc>, + payload: &[u8], + mut send: impl FnMut(&[u8]) -> bool, + mtu_sized_buffer: &mut [u8], +) -> Result<(), SendError> { + use SendError::*; + let mtu = mtu_sized_buffer.len(); + if mtu < MIN_TRANSPORT_MTU { + return Err(InvalidParameter); + } + + let state = session.state.read().unwrap(); + if matches!(&state.beta, ZetaAutomata::Null) { + return Err(SessionExpired); + } + if !matches!( + &state.beta, + ZetaAutomata::S1 | ZetaAutomata::S2 | ZetaAutomata::R1 { .. } | ZetaAutomata::R2 { .. } + ) { + return Err(SessionNotEstablished); + } + let (c, should_rekey) = get_counter(session, &state).ok_or(SessionExpired)?; + let nonce = to_nonce(PACKET_TYPE_DATA, c); + + let key = state.key_ref(false); + let mut cipher = key.nk.as_ref().unwrap().start_enc(&nonce); + + let payload_mtu = mtu - HEADER_SIZE; + debug_assert!(payload_mtu >= 4); + let fragment_count = payload.len().saturating_add(payload_mtu - 1) / payload_mtu; // Ceiling div. + let fragment_base_size = payload.len() / fragment_count; + let fragment_size_remainder = payload.len() % fragment_count; + + mtu_sized_buffer[..KID_SIZE].copy_from_slice(&key.send.kid.unwrap().get().to_be_bytes()); + mtu_sized_buffer[FRAGMENT_COUNT_IDX] = fragment_count as u8; + mtu_sized_buffer[PACKET_NONCE_START..].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); + + let mut i = 0; + for fragment_no in 0..fragment_count { + let fragment_len = fragment_base_size + (fragment_no < fragment_size_remainder) as usize; + let j = i + fragment_len; + + mtu_sized_buffer[FRAGMENT_NO_IDX] = fragment_no as u8; + cipher.encrypt(&payload[i..j], &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + fragment_len]); + + session.hk_send.encrypt_in_place((&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap()); + + if !send(&mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { + return Ok(()); + } + i = j; + } + drop(cipher); + drop(state); + + if should_rekey { + let mut state = session.state.write().unwrap(); + state.timeout_timer = i64::MIN; + drop(state); + ctx.session_queue + .lock() + .unwrap() + .change_priority(session.queue_idx, Reverse(i64::MIN)); + } + Ok(()) +} /// Corresponds to Algorithm 10 found in Section 4.3. pub(crate) fn receive_payload_in_place( - app: &App, - ctx: &Arc>, session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_IV_SIZE], @@ -1662,7 +1733,6 @@ pub(crate) fn receive_payload_in_place( ) -> Result<(), ReceiveError> { use FaultType::*; - let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); let is_other = if Some(kid) == state.key_ref(true).recv.kid { true @@ -1722,13 +1792,20 @@ impl Session { /// receive or send data or control packets. It is recommended to simply `drop` the session /// instead, but this can provide some reassurance in complex shared ownership situations. pub fn expire(&self) { - self.expire_inner(self.state_machine_lock.lock().unwrap(), self.state.write().unwrap()); + if let Some(ctx) = self.ctx.upgrade() { + self.expire_inner(Some(&ctx), Some(&mut ctx.session_queue.lock().unwrap())); + } else { + self.expire_inner(None, None); + } } + /// Allows us to expire sessions with the correct locking order, preventing deadlock. pub(crate) fn expire_inner( &self, - kex_lock: MutexGuard<'_, ()>, - mut state: RwLockWriteGuard<'_, MutableState>, + ctx: Option<&Arc>>, + session_queue: Option<&mut SessionQueue>, ) { + let _kex_lock = self.state_machine_lock.lock().unwrap(); + let mut state = self.state.write().unwrap(); let mut kids_to_remove = None; if !matches!(&state.beta, ZetaAutomata::Null) { self.session_has_expired.store(true, Ordering::Relaxed); @@ -1738,15 +1815,13 @@ impl Session { state.timeout_timer = i64::MAX; state.beta = ZetaAutomata::Null; } - drop(state); - drop(kex_lock); - if let Some(kids_to_remove) = kids_to_remove { - if let Some(ctx) = self.ctx.upgrade() { - ctx.session_queue.lock().unwrap().remove(self.queue_idx); - let mut session_map = ctx.session_map.write().unwrap(); - for kid_recv in kids_to_remove.iter().flatten() { - session_map.remove(kid_recv); - } + if let Some(session_queue) = session_queue { + session_queue.remove(self.queue_idx); + } + if let (Some(ctx), Some(kids_to_remove)) = (ctx, kids_to_remove) { + let mut session_map = ctx.session_map.write().unwrap(); + for kid_recv in kids_to_remove.iter().flatten() { + session_map.remove(kid_recv); } } } diff --git a/src/zssp.rs b/src/zssp.rs index fac5118..b0ba9f8 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -36,7 +36,7 @@ use crate::log_event::LogEvent; use crate::proto::*; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; use crate::symmetric_state::SymmetricState; -use crate::{applicationlayer::*, RatchetState}; +use crate::{applicationlayer::*, ratchet_state::RatchetState}; /// Macro to turn off logging at compile time. macro_rules! log { @@ -315,7 +315,7 @@ impl Context { } else { std::slice::from_mut(&mut incoming_fragment_buf) }; - receive_payload_in_place(app, ctx, &session, kid_recv, &nonce, fragments, output_buffer)?; + receive_payload_in_place(&session, kid_recv, &nonce, fragments, output_buffer)?; SessionEvent::Data } else { let mut buffer = ArrayVec::::new(); @@ -395,7 +395,7 @@ impl Context { } PACKET_TYPE_SESSION_REJECTED => { log!(app, ReceivedRawD); - received_d_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet)?; + received_d_trans(&session, kid_recv, &nonce, assembled_packet)?; log!(app, DIsAuthClosedSession(&session)); SessionEvent::Rejected } @@ -569,7 +569,6 @@ impl Context { } } } - /* /// Send data over the session. /// /// * `session` - The session to send to @@ -581,70 +580,11 @@ impl Context { pub fn send( &self, session: &Arc>, - mut send: impl FnMut(&mut [u8]) -> bool, + send: impl FnMut(&[u8]) -> bool, mtu_sized_buffer: &mut [u8], - mut data: &[u8], - current_time: i64, + data: &[u8], ) -> Result<(), SendError> { - if mtu_sized_buffer.len() < MIN_TRANSPORT_MTU { - return Err(SendError::InvalidParameter); - } - let state = session.state.read().unwrap(); - let key = state.cipher_states[state.current_key].as_ref().ok_or(SendError::SessionNotEstablished)?; - let counter = session.get_next_outgoing_counter()?; - - let mut c = key.get_send_cipher(counter)?; - c.set_iv(&create_message_nonce(PACKET_TYPE_DATA, counter)); - - let fragment_max_chunk_size = mtu_sized_buffer.len() - HEADER_SIZE; - let fragment_count = (data.len() + AES_GCM_TAG_SIZE + (fragment_max_chunk_size - 1)) / fragment_max_chunk_size; - if fragment_count > MAX_FRAGMENTS { - return Err(SendError::DataTooLarge); - } - let last_fragment_no = fragment_count - 1; - - for fragment_no in 0..fragment_count { - let chunk_size = fragment_max_chunk_size.min(data.len()); - let mut fragment_size = chunk_size + HEADER_SIZE; - - set_packet_header( - mtu_sized_buffer, - fragment_count as u8, - fragment_no as u8, - PACKET_TYPE_DATA, - key.remote_key_id.get(), - counter, - ); - - c.encrypt(&data[..chunk_size], &mut mtu_sized_buffer[HEADER_SIZE..fragment_size]); - data = &data[chunk_size..]; - - if fragment_no == last_fragment_no { - debug_assert!(data.is_empty()); - let tagged_fragment_size = fragment_size + AES_GCM_TAG_SIZE; - c.finish_encrypt((&mut mtu_sized_buffer[fragment_size..tagged_fragment_size]).try_into().unwrap()); - fragment_size = tagged_fragment_size; - } - - session.header_send_cipher.encrypt_in_place( - (&mut mtu_sized_buffer[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) - .try_into() - .unwrap(), - ); - if !send(&mut mtu_sized_buffer[..fragment_size]) { - break; - } - } - drop(c); - if counter >= key.rekey_at_counter { - if let OfferStateMachine::Normal { .. } = &state.outgoing_offer { - drop(state); - if let Ok(timer) = initiate_rekey(&self.0, session, send, current_time) { - self.0.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timer)); - } - } - } - Ok(()) + send_payload(&self.0, session, data, send, mtu_sized_buffer) } /// Perform periodic background service and cleanup tasks. @@ -660,20 +600,19 @@ impl Context { /// should rekey. pub fn service bool>( &self, - app: &App, + app: App, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, current_time: i64, ) -> i64 { - let retry_next = current_time.saturating_add(App::RETRY_INTERVAL_MS); - let mut next_service_time = 2 * App::RETRY_INTERVAL_MS; - - let mut session_queue = self.0.session_queue.lock().unwrap(); + let ctx = &self.0; + let mut session_queue = ctx.session_queue.lock().unwrap(); + let mut next_service_time = current_time + App::SETTINGS.fragment_assembly_timeout as i64; // This update system takes heavy advantage of the fact that sessions only need to be updated // either roughly every second or roughly every hour. That big gap allows for minor optimizations. // If the gap changes (unlikely) this code may need to be rewritten. - while let Some((session, timer, queue_idx)) = session_queue.peek() { - if timer.0 >= current_time { - next_service_time = next_service_time.min(timer.0 - current_time); + while let Some((session, Reverse(timer), queue_idx)) = session_queue.peek() { + if *timer >= current_time { + next_service_time = next_service_time.min(*timer); break; } let session = match session.upgrade() { @@ -683,127 +622,23 @@ impl Context { continue; } }; - let state = session.state.read().unwrap(); - let next_timer = match &state.outgoing_offer { - Normal { timeout, .. } => { - if *timeout <= current_time { - drop(state); - if let Some((send, _)) = send_to(&session) { - let result = initiate_rekey(&self.0, &session, send, current_time); - if result.is_ok() { - app.event_log(LogEvent::ServiceKKStart(&session), current_time); - } - result.unwrap_or(retry_next) - } else { - retry_next - } - } else { - *timeout - } + let result = process_timers(&app, ctx, &session, current_time, false, false, |packet, hk_send| { + if let Some((send_fragment, mut mtu)) = send_to(&session) { + mtu = mtu.max(MIN_TRANSPORT_MTU); + send_with_fragmentation( + send_fragment, + mtu, + packet, + hk_send + ); } - // If there's an outstanding attempt to open a session, retransmit this - // periodically in case the initial packet doesn't make it. - NoiseXKPattern1or3(handshake_state) => { - if let Some(ts) = process_timer(&handshake_state.next_retry_time, App::RETRY_INTERVAL_MS, current_time) { - ts - } else { - // We have to eventually time out NoiseXKPattern3 because of unreliable network conditions. - if handshake_state.timeout <= current_time { - drop(state); - let _kex_lock = session.state_machine_lock.lock().unwrap(); - let mut state = session.state.write().unwrap(); - let ratchet_state = state.ratchet_states.clone(); - // Since we dropped the lock we must re-check if we are in the correct state. - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if handshake_state.timeout <= current_time { - app.event_log(LogEvent::ServiceXKTimeout(&session), current_time); - handshake_state.reinitialize( - &session, - &ratchet_state, - &mut self.0.session_map.write().unwrap(), - &mut self.0.rng.lock().unwrap(), - current_time, - ); - } - } - } else if let Some((mut send, mut mtu)) = send_to(&session) { - mtu = mtu.max(MIN_TRANSPORT_MTU); - match &handshake_state.offer { - NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } => { - app.event_log(LogEvent::ServiceXK1Resend(&session), current_time); - // We are in state NoiseXKPattern1 so resend noise_pattern1. - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_1, - None, - *message_id, - None::<&App::PrpEnc>, - ); - } - NoiseXKAliceHandshakeState::NoiseXKPattern3 { noise_message, noise_message_len, .. } => { - app.event_log(LogEvent::ServiceXK3Resend(&session), current_time); - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_3, - state.cipher_states[0].as_ref().map(|k| k.remote_key_id), - 0, - Some(&session.header_send_cipher), - ); - } - } - } - retry_next - } - } - NoiseKKPattern1 { next_retry_time, timeout, noise_message, .. } | NoiseKKPattern2 { next_retry_time, timeout, noise_message, .. } => { - if let Some(ts) = process_timer(next_retry_time, App::RETRY_INTERVAL_MS, current_time) { - ts - } else { - if *timeout <= current_time { - app.event_log(LogEvent::ServiceKKTimeout(&session), current_time); - next_retry_time.store(i64::MAX, Ordering::Relaxed); - drop(state); - session.expire_inner(&self.0, &mut session_queue); - } else { - let packet_type = if let NoiseKKPattern1 { .. } = &state.outgoing_offer { - app.event_log(LogEvent::ServiceKK1Resend(&session), current_time); - PACKET_TYPE_NOISE_KK_PATTERN_1 - } else { - app.event_log(LogEvent::ServiceKK2Resend(&session), current_time); - PACKET_TYPE_NOISE_KK_PATTERN_2 - }; - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&state, send, packet_type, noise_message); - } - } - retry_next - } - } - KeyConfirm { next_retry_time, timeout, .. } => { - if let Some(ts) = process_timer(next_retry_time, App::RETRY_INTERVAL_MS, current_time) { - ts - } else { - if *timeout <= current_time { - app.event_log(LogEvent::ServiceKeyConfirmTimeout(&session), current_time); - next_retry_time.store(i64::MAX, Ordering::Relaxed); - drop(state); - session.expire_inner(&self.0, &mut session_queue); - } else { - app.event_log(LogEvent::ServiceKeyConfirmResend(&session), current_time); - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&state, send, PACKET_TYPE_KEY_CONFIRM, &[]); - } - } - retry_next - } - } - Null => retry_next, - }; - session_queue.change_priority(queue_idx, Reverse(next_timer)); + }); + if let Some(next_timer) = result { + next_service_time = next_service_time.min(next_timer); + session_queue.change_priority(queue_idx, Reverse(next_timer)); + } else { + session.expire_inner(Some(ctx), Some(&mut session_queue)); + } } drop(session_queue); @@ -811,9 +646,9 @@ impl Context { .unassociated_defrag_cache .lock() .unwrap() - .check_for_expiry(App::INITIAL_OFFER_TIMEOUT_MS, current_time); + .check_for_expiry(App::SETTINGS.fragment_assembly_timeout as i64, current_time); self.0.unassociated_handshake_states.service(current_time); next_service_time - } */ + } } From df2c589d09f04213b819b39b8f8fd204720e36e4 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 08:37:16 -0400 Subject: [PATCH 60/91] refactored --- src/{applicationlayer.rs => application.rs} | 73 +- src/challenge.rs | 5 +- src/context.rs | 436 --- src/crypto/mod.rs | 16 +- src/error.rs | 110 - src/frag_cache.rs | 2 +- src/fragged.rs | 2 +- src/handshake_cache.rs | 2 +- src/lib.rs | 2 +- src/log_event.rs | 2 +- src/proto.rs | 7 +- src/proto_old.rs | 296 -- src/ratchet_state.rs | 147 +- src/ratchet_state_old.rs | 62 - src/result.rs | 2 +- src/symmetric_state.rs | 6 +- src/symmetric_state_old.rs | 156 -- src/zeta.rs | 116 +- src/zssp copy.rs | 2704 ------------------- src/zssp.rs | 9 +- 20 files changed, 240 insertions(+), 3915 deletions(-) rename src/{applicationlayer.rs => application.rs} (82%) delete mode 100644 src/context.rs delete mode 100644 src/error.rs delete mode 100644 src/proto_old.rs delete mode 100644 src/ratchet_state_old.rs delete mode 100644 src/symmetric_state_old.rs delete mode 100644 src/zssp copy.rs diff --git a/src/applicationlayer.rs b/src/application.rs similarity index 82% rename from src/applicationlayer.rs rename to src/application.rs index f078770..0a28af9 100644 --- a/src/applicationlayer.rs +++ b/src/application.rs @@ -1,21 +1,12 @@ use std::sync::Arc; +use rand_core::{CryptoRng, RngCore}; -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ -use crate::crypto::aes::{AesDec, AesEnc, HighThroughputAesGcmPool, LowThroughputAesGcm}; -use crate::crypto::kyber1024::Kyber1024PrivateKey; -use crate::crypto::p384::{P384KeyPair, P384PublicKey}; -use crate::crypto::rand_core::{CryptoRng, RngCore}; -use crate::crypto::sha512::{HashSha512, HmacSha512}; -use crate::proto::RATCHET_SIZE; +use crate::crypto::*; use crate::zeta::Session; use crate::ratchet_state::RatchetState; -//use crate::{log_event::LogEvent, Session}; + +pub use crate::proto::RATCHET_SIZE; +pub use crate::ratchet_state::*; /// A container for a vast majority of the dynamic settings within ZSSP, including all time-based settings. /// If the user wishes to measure time in units other than milliseconds for some reason, then they can @@ -194,22 +185,17 @@ pub trait ApplicationLayer: Sized { /// Function to accept sessions after final negotiation. /// The second argument is the identity that the remote peer sent us. The application /// must verify this identity is associated with the remote peer's static key. - /// To prevent desync, if this function returns (Some(_), _), no other open session with the - /// same remote peer must exist. Drop or call expire on any pre-existing sessions before returning. + /// To prevent desync, if this function specifies that we should connect, no other open session + /// with the same remote peer must exist. Drop or call expire on any pre-existing sessions + /// before returning. fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> AcceptAction; /// Lookup a specific ratchet state based on its ratchet fingerprint. /// This function will be called whenever Alice attempts to connect to us with a non-empty /// ratchet fingerprint. /// - /// If the ratchet key was found, the function should return `RestoreAction::RestoreRatchet`. This will - /// cause us to connect to Alice using the returned ratchet number and ratchet key. - /// - /// If the ratchet key could not be found, the application may choose between returning - /// `RatchetAction::DowngradeRatchet` or `RatchetAction::FailAuthentication`. - /// If `RatchetAction::DowngradeRatchet` is returned we will attempt to convince Alice to downgrade - /// to the empty ratchet key, restarting the ratchet chain. - /// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped. + /// If a ratchet state with a matching fingerprint could not be found, this function should + /// return `Ok(None)`. fn restore_by_fingerprint( &self, ratchet_fingerprint: &[u8; RATCHET_SIZE], @@ -232,17 +218,10 @@ pub trait ApplicationLayer: Sized { &self, remote_static_key: &Self::PublicKey, session_data: &Self::SessionData, - ) -> Result<(RatchetState, Option), Self::StorageError>; - /// Atomically save `current_state1` and `current_state2` so that them and only them can be - /// restored with `restore_by_identity` and `restore_by_fingerprint` through a system restart. - /// Theses should overwrite the previous ratchet states 1 and 2 saved to storage. - /// - /// `state_added` will be equal to the brand new ratchet state that was added in this update, - /// or `None` if there is not a new ratchet state this update. `state_deleted1` and - /// `state_deleted2` will be equal to any ratchet states that are to be deleted and overwritten - /// as a result of this update, or `None` if there is not one to be deleted. - /// `state_added` will always have a non-empty (`Some()`) ratchet fingerprint, and it will - /// always be equal to `current_state1`. + ) -> Result, Self::StorageError>; + /// Atomically commit the update specified by `update_data` to storage, or return an error if + /// the update could not be made. + /// The implementor is free to choose how to apply these updates to storage. /// /// If this returns `Err(IoError)`, the packet which triggered this function to be called will be /// dropped, and no session state will be mutated, preserving synchronization. The remote peer @@ -254,8 +233,7 @@ pub trait ApplicationLayer: Sized { /// fix is to reset both ratchet keys to empty. /// /// This function may also save state to volatile storage, in which case all peers which connect - /// to us will have to allow downgrade, i.e. `initiator_disallows_downgrade` returns false - /// and/or `check_accept_session` returns `(Some(true, _), _)`. + /// to us will have to allow downgrade across the board. /// Otherwise, when we restart, we will not be allowed to reconnect. fn save_ratchet_state( &self, @@ -271,16 +249,21 @@ pub trait ApplicationLayer: Sized { fn event_log(&self, event: LogEvent<'_, Self>); } -pub struct RatchetUpdate<'a> { - pub state1: &'a RatchetState, - pub state2: Option<&'a RatchetState>, - pub state1_was_just_added: bool, - pub state_deleted1: Option<&'a RatchetState>, - pub state_deleted2: Option<&'a RatchetState>, -} - +/// A collection of fields specifying how to complete the key exchange with a specific remote peer, +/// used by Bob, the responder, at the very last stage of the key exchange. +/// +/// Corresponds to the *Accept* callback of Transition Algorithm 4. pub struct AcceptAction { + /// The data object to be attached to the session if we successfully connect. + /// If this field is None then we will not connect to this remote peer. pub session_data: Option, + /// Whether or not we will accept a connection with the remote peer when they do not have a + /// ratchet key that we think they should have. pub responder_disallows_downgrade: bool, + /// Whether or not to send an explicit rejection packet to the remote peer if we do not create + /// a session with them. + /// + /// This field will not be used if `session_data` is `Some` and the remote peer passes all other + /// authentication checks. pub responder_silently_rejects: bool, } diff --git a/src/challenge.rs b/src/challenge.rs index 11e4578..c63c916 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -4,10 +4,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; use rand_core::{CryptoRng, RngCore}; use crate::antireplay::Window; -use crate::crypto::{ - secure_eq, - sha512::{HashSha512, SHA512_HASH_SIZE}, -}; +use crate::crypto::*; use crate::proto::*; pub struct ChallengeContext { diff --git a/src/context.rs b/src/context.rs deleted file mode 100644 index 2320365..0000000 --- a/src/context.rs +++ /dev/null @@ -1,436 +0,0 @@ -use rand_core::RngCore; -use std::cmp::Reverse; -use std::collections::hash_map::Entry; -use std::collections::HashMap; -use std::hash::Hash; -use std::num::NonZeroU32; -use std::sync::{Arc, Mutex, Weak, RwLock}; - -use crate::applicationlayer::ApplicationLayer; -use crate::crypto::aes::{AES_256_KEY_SIZE, AES_GCM_IV_SIZE}; -use crate::frag_cache::UnassociatedFragCache; -use crate::handshake_cache::UnassociatedHandshakeCache; -use crate::indexed_heap::IndexedBinaryHeap; -//use crate::fragmentation::{send_with_fragmentation, DefragBuffer}; -use crate::proto::*; -use crate::result::{byzantine_fault, ReceiveError, ReceiveOk, SendError, SessionEvent}; -use crate::zeta::*; -#[cfg(feature = "logging")] -use crate::LogEvent::*; -use crate::{challenge::ChallengeContext, result::OpenError}; - -/// Macro to turn off logging at compile time. -macro_rules! log { - ($app:expr, $event:expr) => { - #[cfg(feature = "logging")] - $app.event_log($event); - }; -} -pub(crate) use log; - -/// Session context for local application. -/// -/// Each application using ZSSP must create an instance of this to own sessions and -/// defragment incoming packets that are not yet associated with a session. -/// -/// Internally this is just a clonable Arc, so it can be safely shared with multiple threads. -pub struct Context(Arc>); -impl Clone for Context { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -pub(crate) type SessionMap = RwLock>>>; - -pub(crate) struct ContextInner { - pub(crate) rng: Mutex, - pub(crate) s_secret: App::KeyPair, - pub(crate) session_queue: Mutex>, Reverse>>, - pub(crate) session_map: SessionMap, - unassociated_defrag_cache: Mutex>, - unassociated_handshake_states: UnassociatedHandshakeCache, - //pub(crate) b2_map: Mutex>>, - - //hello_defrag: Mutex, - challenge: ChallengeContext, -} - -/// Corresponds to Figure 10 found in Section 4.3. -fn to_aes_nonce(pn: &[u8; PACKET_NONCE_SIZE]) -> [u8; AES_GCM_IV_SIZE] { - let mut an = [0u8; AES_GCM_IV_SIZE]; - an[2..].copy_from_slice(pn); - an -} -/// Corresponds to Figure 14 found near Section 6. -fn to_packet_nonce(n: &[u8; AES_GCM_IV_SIZE]) -> &[u8; PACKET_NONCE_SIZE] { - (&n[n.len() - PACKET_NONCE_SIZE..]).try_into().unwrap() -} - -impl Context { - /// Create a new session context. - pub fn new(static_secret_key: App::KeyPair, mut rng: App::Rng) -> Self { - let challenge = ChallengeContext::new(&mut rng); - Self(Arc::new(ContextInner { - rng: Mutex::new(rng), - s_secret: static_secret_key, - session_map: RwLock::new(HashMap::new()), - challenge, - session_queue: Mutex::new(IndexedBinaryHeap::new()), - unassociated_defrag_cache: Mutex::new(UnassociatedFragCache::new()), - unassociated_handshake_states: UnassociatedHandshakeCache::new(), - })) - } - /// Create a new session and send initial packet(s) to other side. - /// - /// This will return SendError::DataTooLarge if the combined size of the metadata and the local - /// static public blob (as retrieved from the application layer) exceed MAX_INIT_PAYLOAD_SIZE. - /// - /// * `app` - Application layer instance - /// * `send` - Function to be called to send one or more initial packets to the remote being - /// contacted - /// * `mtu` - MTU for initial packets - /// * `static_remote_key` - Remote side's static public NIST P-384 key - /// * `session_data` - Arbitrary data meaningful to the application to include with session - /// object - /// * `identity` - Payload to be sent to Bob that contains the information necessary - /// for the upper protocol to authenticate and approve of Alice's identity - pub fn open( - &self, - app: App, - send: impl FnMut(Vec) -> bool, - mut mtu: usize, - static_remote_key: App::PublicKey, - session_data: App::SessionData, - identity: Vec, - ) -> Result>, OpenError> { - mtu = mtu.max(MIN_TRANSPORT_MTU); - let ctx = &self.0; - - // Process zeta layer. - trans_to_a1( - app, - &ctx, - static_remote_key, - session_data, - identity, - |Packet(kid, nonce, payload): &Packet| { - // Process fragmentation layer. - send_with_fragmentation::(send, mtu, *kid, to_packet_nonce(&nonce), payload, None); - }, - ) - } - - /// Receive, authenticate, decrypt, and process a physical wire packet. - /// - /// * `app` - Interface to application using ZSSP - /// * `send_unassociated_reply` - Function to send reply packets directly when no session exists - /// * `send_unassociated_mtu` - MTU for unassociated replies - /// * `send_to` - Function to get senders for existing sessions, permitting MTU and path lookup - /// * `remote_address` - Whatever the remote address is, as long as you can Hash it - /// * `raw_fragment` - Buffer containing incoming wire packet - pub fn receive<'a, SendFn: FnMut(Vec) -> bool>( - &self, - app: App, - send_unassociated_reply: impl FnMut(Vec) -> bool, - mut send_unassociated_mtu: usize, - send_to: impl FnOnce(&Arc>) -> Option<(SendFn, usize)>, - remote_address: &impl Hash, - raw_fragment: Vec, - ) -> Result, ReceiveError> { - use crate::result::FaultType::*; - send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); - let ctx = &self.0; - - // Multiplex session. - let kid_recv = u32::from_be_bytes(raw_fragment[..KID_SIZE].try_into().unwrap()); - if let Some(kid_recv) = NonZeroU32::new(kid_recv) { - let session = ctx.session_map.lock().unwrap().get(&kid_recv).map(|r| r.upgrade()); - if let Some(Some(session)) = session { - // Process recv fragmentation layer. - let mut zeta = session.0.lock().unwrap(); - let result = zeta.defrag.received_fragment::(raw_fragment, app.time(), |n, frag_no, frag_count| { - let (p, c) = from_nonce(n); - if p != PACKET_TYPE_DATA { - log!(app, ReceivedRawFragment(p, c, frag_no, frag_count)); - } - if p == PACKET_TYPE_HANDSHAKE_RESPONSE { - if !matches!(&zeta.beta, ZetaAutomata::A1(_)) { - // A resent handshake response from Bob may have arrived out of order, - // after we already received one. - return Err(byzantine_fault!(OutOfSequence, false)); - } - if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD { - return Err(byzantine_fault!(ExpiredCounter, true)); - } - Ok(()) - } else if PACKET_TYPE_USES_COUNTER_RANGE.contains(&p) { - if !zeta.check_counter_window(c) { - // The counter window has finite memory and so will occasionally give - // false positives on very out-of-order packets. - return Err(byzantine_fault!(ExpiredCounter, false)); - } - Ok(()) - } else if p == PACKET_TYPE_HANDSHAKE_COMPLETION { - // The handshake completion packet could have been resent. - return Err(byzantine_fault!(InvalidPacket, false)); - } else { - return Err(byzantine_fault!(InvalidPacket, true)); - } - })?; - if let Some((pn, mut assembled_packet)) = result { - // Process recv zeta layer. - let send_associated = |Packet(kid, nonce, payload): &Packet, hk: Option<&[u8; AES_256_KEY_SIZE]>| { - if let Some((send_fragment, mut mtu)) = send_to(&session) { - mtu = mtu.max(MIN_TRANSPORT_MTU); - send_with_fragmentation::(send_fragment, mtu, *kid, to_packet_nonce(&nonce), payload, hk); - } - }; - - let (p, _) = from_nonce(&pn); - let ret = match p { - PACKET_TYPE_DATA => { - received_payload_in_place(&mut zeta, kid_recv, to_aes_nonce(&pn), &mut assembled_packet)?; - SessionEvent::Data(assembled_packet) - } - PACKET_TYPE_HANDSHAKE_RESPONSE => { - log!(app, ReceivedRawX2); - received_x2_trans( - &mut zeta, - &session, - &app, - &ctx, - kid_recv, - to_aes_nonce(&pn), - assembled_packet, - send_associated, - )?; - log!(app, X2IsAuthSentX3(&session)); - SessionEvent::Control - } - PACKET_TYPE_KEY_CONFIRM => { - log!(app, ReceivedRawKeyConfirm); - let result = - received_c1_trans(&mut zeta, &app, &ctx.rng, kid_recv, to_aes_nonce(&pn), assembled_packet, send_associated)?; - log!(app, KeyConfirmIsAuthSentAck(&session)); - if result { - SessionEvent::Established - } else { - SessionEvent::Control - } - } - PACKET_TYPE_ACK => { - log!(app, ReceivedRawAck); - received_c2_trans(&mut zeta, &app, &ctx.rng, kid_recv, to_aes_nonce(&pn), assembled_packet)?; - log!(app, AckIsAuth(&session)); - SessionEvent::Control - } - PACKET_TYPE_REKEY_INIT => { - log!(app, ReceivedRawK1); - received_k1_trans( - &mut zeta, - &session, - &app, - &ctx.rng, - &ctx.session_map, - &ctx.s_secret, - kid_recv, - to_aes_nonce(&pn), - assembled_packet, - send_associated, - )?; - log!(app, K1IsAuthSentK2(&session)); - SessionEvent::Control - } - PACKET_TYPE_REKEY_COMPLETE => { - log!(app, ReceivedRawK2); - received_k2_trans(&mut zeta, &app, kid_recv, to_aes_nonce(&pn), assembled_packet, send_associated)?; - log!(app, K2IsAuthSentKeyConfirm(&session)); - SessionEvent::Control - } - PACKET_TYPE_SESSION_REJECTED => { - log!(app, ReceivedRawD); - received_d_trans(&mut zeta, kid_recv, to_aes_nonce(&pn), assembled_packet)?; - log!(app, DIsAuthClosedSession(&session)); - SessionEvent::Rejected - } - _ => return Err(byzantine_fault!(InvalidPacket, true)), // This is unreachable. - }; - drop(zeta); - Ok(ReceiveOk::Session(session, ret)) - } else { - Ok(ReceiveOk::Unassociated) - } - } else { - let mut b2_map = ctx.b2_map.lock().unwrap(); - if let Entry::Occupied(mut entry) = b2_map.entry(kid_recv) { - let zeta = entry.get_mut(); - // Process recv fragmentation layer. - let result = zeta.defrag.received_fragment::(raw_fragment, app.time(), |n, frag_no, frag_count| { - let (p, c) = from_nonce(n); - log!(app, ReceivedRawFragment(p, c, frag_no, frag_count)); - if p == PACKET_TYPE_HANDSHAKE_COMPLETION && c == 0 { - Ok(()) - } else { - Err(byzantine_fault!(InvalidPacket, true)) - } - })?; - if let Some((_, assembled_packet)) = result { - log!(app, ReceivedRawX3); - let zeta = entry.remove(); - let session = received_x3_trans(zeta, &app, ctx, kid_recv, assembled_packet, |Packet(kid, nonce, payload), hk| { - send_with_fragmentation::( - send_unassociated_reply, - send_unassociated_mtu, - *kid, - to_packet_nonce(&nonce), - payload, - hk, - ); - })?; - log!(app, X3IsAuthSentKeyConfirm(&session)); - Ok(ReceiveOk::Session(session, SessionEvent::NewSession)) - } else { - Ok(ReceiveOk::Unassociated) - } - } else { - // When sessions are added or dropped or packets arrive extremely delayed it is - // possible to receive no longer recognized key ids. - Err(byzantine_fault!(UnknownLocalKeyId, false)) - } - } - } else { - // Process recv fragmentation layer. - let result = ctx - .hello_defrag - .lock() - .unwrap() - .received_fragment::(raw_fragment, app.time(), |n, frag_no, frag_count| { - let (p, c) = from_nonce(n); - log!(app, ReceivedRawFragment(p, c, frag_no, frag_count)); - if p == PACKET_TYPE_HANDSHAKE_HELLO || p == PACKET_TYPE_CHALLENGE { - Ok(()) - } else { - Err(byzantine_fault!(InvalidPacket, true)) - } - })?; - if let Some((n, mut assembled_packet)) = result { - let (p, _) = from_nonce(&n); - if p == PACKET_TYPE_HANDSHAKE_HELLO { - log!(app, ReceivedRawX1); - // Process recv challenge layer. - let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; - let result = ctx - .challenge - .lock() - .unwrap() - .process_hello::(remote_address, (&assembled_packet[challenge_start..]).try_into().unwrap()); - if let Err(challenge) = result { - log!(app, X1FailedChallengeSentNewChallenge); - let mut challenge_packet = Vec::new(); - challenge_packet.extend(&assembled_packet[..KID_SIZE]); - challenge_packet.extend(&challenge); - let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); - send_with_fragmentation::( - send_unassociated_reply, - send_unassociated_mtu, - 0, - to_packet_nonce(&nonce), - &challenge_packet, - None, - ); - // If we issue a challenge the first hello packet will always fail. - return Err(byzantine_fault!(FailedAuth, false)); - } else if let Ok(true) = result { - log!(app, X1SucceededChallenge); - } - assembled_packet.truncate(challenge_start); - - // Process recv zeta layer. - received_x1_trans(&app, &ctx, to_aes_nonce(&n), assembled_packet, |Packet(kid, nonce, payload), hk| { - send_with_fragmentation::( - send_unassociated_reply, - send_unassociated_mtu, - *kid, - to_packet_nonce(&nonce), - payload, - Some(hk), - ); - })?; - log!(app, X1IsAuthSentX2); - Ok(ReceiveOk::Unassociated) - } else if p == PACKET_TYPE_CHALLENGE { - log!(app, ReceivedRawChallenge); - // Process recv challenge layer. - if assembled_packet.len() != KID_SIZE + CHALLENGE_SIZE { - return Err(byzantine_fault!(InvalidPacket, true)); - } - if let Some(kid_recv) = NonZeroU32::new(u32::from_be_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) { - if let Some(Some(session)) = ctx.session_map.lock().unwrap().get(&kid_recv).map(|r| r.upgrade()) { - let mut zeta = session.0.lock().unwrap(); - respond_to_challenge(&mut zeta, &ctx.rng, &assembled_packet[KID_SIZE..].try_into().unwrap()); - log!(app, ChallengeIsAuth(&session)); - return Ok(ReceiveOk::Unassociated); - } - } - Err(byzantine_fault!(UnknownLocalKeyId, true)) - } else { - Err(byzantine_fault!(InvalidPacket, true)) - } - } else { - Ok(ReceiveOk::Unassociated) - } - } - } - - /// Send data over the session. - /// - /// * `session` - The session to send to - /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a - /// slice of `data` - /// * `mtu` - The MTU of the link, all packets passed to `send` will be at most `mtu` in length - /// * `payload` - Data to send - pub fn send(&self, session: &Arc>, send: impl FnMut(Vec) -> bool, mut mtu: usize, payload: Vec) -> Result<(), SendError> { - mtu = mtu.max(MIN_TRANSPORT_MTU); - let mut zeta = session.0.lock().unwrap(); - send_payload(&mut zeta, payload, |Packet(kid, nonce, payload), hk| { - send_with_fragmentation::(send, mtu, *kid, to_packet_nonce(nonce), &payload, hk); - }) - } - - /// Perform periodic background service and cleanup tasks. - /// - /// This returns the number of milliseconds until it should be called again. The caller should - /// try to satisfy this but small variations in timing of up to a few seconds are not - /// a problem. - /// - /// * `send_to` - Function to get a sender and an MTU to send something over an active session - pub fn service) -> bool>(&self, app: App, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>) -> i64 { - let ctx = &self.0; - let sessions = ctx.sessions.lock().unwrap(); - let current_time = app.time(); - let mut next_timer = i64::MAX; - for (_, session) in sessions.iter() { - if let Some(session) = session.upgrade() { - let mut zeta = session.0.lock().unwrap(); - service( - &mut zeta, - &session, - ctx, - &app, - current_time, - |Packet(kid, nonce, payload): &Packet, hk| { - if let Some((send_fragment, mut mtu)) = send_to(&session) { - mtu = mtu.max(MIN_TRANSPORT_MTU); - send_with_fragmentation::(send_fragment, mtu, *kid, to_packet_nonce(&nonce), payload, hk); - } - }, - ); - next_timer = next_timer.min(zeta.next_timer()); - zeta.defrag.service::(current_time); - } - } - ctx.hello_defrag.lock().unwrap().service::(current_time); - (App::SETTINGS.resend_time as i64).min(next_timer - current_time) - } -} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index f31b7f6..00ca2aa 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,13 +1,17 @@ -// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +mod aes; +pub use self::aes::*; -pub mod aes; -pub mod kyber1024; -pub mod p384; -pub mod sha512; +mod p384; +pub use self::p384::*; + +mod sha512; +pub use sha512::*; + +mod kyber1024; +pub use kyber1024::*; // We re-export our dependencies so it is less of a headache for the implementor to use the same // exact version of them. -pub use pqc_kyber; pub use rand_core; /// Constant time byte slice equality. diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 63c31c0..0000000 --- a/src/error.rs +++ /dev/null @@ -1,110 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -#[derive(Debug, PartialEq, Eq)] -pub enum OpenError { - /// An invalid parameter was supplied to the function. - InvalidPublicKey, - - /// Local identity blob is too large to send, even with fragmentation. - DataTooLarge, - - RatchetIoError(IoError), -} - -#[derive(Debug, PartialEq, Eq)] -pub enum SendError { - /// An invalid parameter was supplied to the function. - InvalidParameter, - - /// The session has been marked as expired and refuses to send data. - /// Several components of ZSSP can cause this to occur, but the most likely situation to be seen - /// in practice is where rekeying repeatedly fails due to exceedingly bad network conditions. - /// - /// The associated session will no longer send or receive data and must be immediately dropped. - SessionExpired, - - /// Attempt to send using a session without a shared symmetric key. - /// The caller should wait until the handshake has completed. - SessionNotEstablished, - - /// Data object is too large to send, even with fragmentation. - DataTooLarge, -} - -/// A type of fault occurred because we received a bad packet. -/// -/// An unauthenticated attacker can intentionally trigger any of these, so it is best to -/// treat these as raw user input that needs to be sanitize. -#[derive(Debug, PartialEq, Eq)] -pub enum FaultType { - /// The received packet was addressed to an unrecognized local session. - UnknownLocalKeyId, - - /// The received packet from the remote peer was not well formed. - InvalidPacket, - - /// Packet failed one or more authentication (MAC) checks. - FailedAuthentication, - - /// Packet counter was repeated or outside window of allowed counter values. - ExpiredCounter, - - /// Packet contained protocol control parameters that are disallowed at this point in - /// time by ZSSP. - OutOfSequence, -} - -#[derive(Debug, PartialEq, Eq)] -pub enum ReceiveError { - /// A type of fault that can occur because a remote peer sent us a bad packet. - /// Such packets will be ignored by ZSSP but a user of ZSSP might want to log - /// them for debugging or tracing. - /// - /// Because an unauthenticated remote peer can force these to occur with specific - /// contained information, it is recommended in production to either drop these - /// immediately, or log them safely to a local output stream and then drop them. - ByzantineFault { - /// The type of fault that has occurred. Be cautious if you choose to read this - /// value, as an attacker has control over it. - error: FaultType, - /// Some byzantine faults within ZSSP are naturally occurring, i.e. they can occur - /// between two well behaved and trusted parties executing the protocol. - /// This boolean is true if this is one of these faults. If you go to the file and - /// line number specified by this error you will find a comment describing - /// how and why exactly this fault can occur naturally. - /// - /// Faults that can occur because the underlying communication medium is lossy and - /// sequentially inconsistent (as in UDP) are considered naturally occurring. - /// However ZSSP considers faults that occur because data integrity has not been - /// persevered (i.e. bits have been flipped) to be unnatural. - /// ZSSP also considers collisions of what are supposed to be uniform random - /// numbers to be unnatural. - is_naturally_occurring: bool, - /// The file of this implementation of ZSSP from which this error was generated. - file: &'static str, - /// The line number of this implementation of ZSSP from which this error was - /// generated. As such this number uniquely identifies each possible fault that - /// can occur during ZSSP. Advanced user can use this information to debug more - /// complicated usages of ZSSP. - line: u32, - }, - - /// The caller supplied data buffer is too small to receive data from the remote peer. - /// An attacker can cause this to occur, so users should place a hard upper limit on - /// how large their supplied data buffers can be. - DataBufferTooSmall, - - /// Rekeying failed and session secret has reached its hard usage count limit. - /// The associated session will no longer function and has to be dropped. - MaxKeyLifetimeExceeded, - - /// One of the ratchet saving or lookup functions returned an error, so the packet had to be - /// dropped. - RatchetIoError(IoError), -} diff --git a/src/frag_cache.rs b/src/frag_cache.rs index e132f4c..cbe95df 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -10,7 +10,7 @@ use std::collections::hash_map::RandomState; use std::hash::{BuildHasher, Hash, Hasher}; use std::mem::MaybeUninit; -use crate::crypto::aes::AES_GCM_IV_SIZE; +use crate::crypto::AES_GCM_IV_SIZE; use crate::fragged::Assembled; use crate::proto::{MAX_FRAGMENTS, MAX_UNASSOCIATED_FRAGMENTS, MAX_UNASSOCIATED_PACKETS, MAX_UNASSOCIATED_PACKET_SIZE}; diff --git a/src/fragged.rs b/src/fragged.rs index 5cb1a49..38a0e18 100644 --- a/src/fragged.rs +++ b/src/fragged.rs @@ -9,7 +9,7 @@ use arrayvec::ArrayVec; use std::mem::{needs_drop, zeroed, MaybeUninit}; -use crate::crypto::aes::AES_GCM_IV_SIZE; +use crate::crypto::AES_GCM_IV_SIZE; use crate::proto::{MAX_FRAGMENTS, NONCE_SIZE_DIFF}; pub type Assembled = ArrayVec; diff --git a/src/handshake_cache.rs b/src/handshake_cache.rs index 1344412..f5cd728 100644 --- a/src/handshake_cache.rs +++ b/src/handshake_cache.rs @@ -11,7 +11,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; use crate::zeta::StateB2; -use crate::{proto::MAX_UNASSOCIATED_HANDSHAKE_STATES, applicationlayer::ApplicationLayer}; +use crate::{proto::MAX_UNASSOCIATED_HANDSHAKE_STATES, application::ApplicationLayer}; pub(crate) struct UnassociatedHandshakeCache { has_pending: AtomicBool, // Allowed to be falsely positive diff --git a/src/lib.rs b/src/lib.rs index 358dc61..bd202c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ pub mod crypto; mod antireplay; -pub mod applicationlayer; +pub mod application; mod challenge; mod frag_cache; mod fragged; diff --git a/src/log_event.rs b/src/log_event.rs index 43fdc32..a160ad9 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -7,7 +7,7 @@ */ use std::sync::Arc; -use crate::{zeta::Session, applicationlayer::ApplicationLayer}; +use crate::{zeta::Session, application::ApplicationLayer}; /// ZSSP events that might be interesting to log or aggregate into metrics. pub enum LogEvent<'a, Application: ApplicationLayer> { diff --git a/src/proto.rs b/src/proto.rs index 91ea301..8c1d252 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -1,9 +1,4 @@ -use crate::crypto::{ - aes::{AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}, - kyber1024::{KYBER_CIPHERTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE}, - p384::P384_PUBLIC_KEY_SIZE, - sha512::SHA512_HASH_SIZE, -}; +use crate::crypto::*; /* Common constants */ diff --git a/src/proto_old.rs b/src/proto_old.rs deleted file mode 100644 index 2f83f3e..0000000 --- a/src/proto_old.rs +++ /dev/null @@ -1,296 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::hash::Hasher; -use std::mem::size_of; - -use crate::crypto::aes::AES_GCM_TAG_SIZE; -use crate::crypto::p384::P384_PUBLIC_KEY_SIZE; -use crate::crypto::pqc_kyber::{KYBER_CIPHERTEXTBYTES, KYBER_PUBLICKEYBYTES}; -use crate::crypto::sha512::{HashSha512, SHA512_HASH_SIZE}; -use hex_literal::hex; - -/// Minimum size of a valid physical ZSSP packet of any type. Anything smaller is discarded. -pub const MIN_PACKET_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; - -/// Minimum physical MTU for ZSSP to function. -pub const MIN_TRANSPORT_MTU: usize = 128; - -pub const RATCHET_SIZE: usize = 32; - -/// The application has the ability to attach a data payload to Alice's handshake. -/// It will be the first payload Bob receives from Alice. -/// The application also must attach a static public identity to their handshake. -/// The combined size of both in bytes must be at most this value. -/// -/// If not ZSSP will return `OpenError::DataTooLarge` and refuse to create a session object. -pub const MAX_IDENTITY_BLOB_SIZE: usize = NoiseXKPattern3::MAX_SIZE - NoiseXKPattern3::MIN_SIZE; - -/// Initial value of 'h'. -/// echo -n 'Noise_XKhfs+psk2_P384+Kyber1024_AESGCM_SHA512' | shasum -a 512 -pub(crate) const INITIAL_H: [u8; SHA512_HASH_SIZE] = - hex!("cd1f422196a5a614e24392cf34dcbf340ee61ad6ee6834274ff35fd42a7a5c44d04a045101555548a291778dd036b93ae21005a26c003213f57a5df9fb17f745"); -/// Initial value of 'ck' for rekeying. -/// echo -n 'Noise_KKpsk0_P384_AESGCM_SHA512' | shasum -a 512 -pub(crate) const INITIAL_H_REKEY: [u8; SHA512_HASH_SIZE] = - hex!("daeedd651ac9c5173f2eaaff996beebac6f3f1bfe9a70bb1cc54fa1fb2bf46260d71a3c4fb4d4ee36f654c31773a8a15e5d5be974a0668dc7db70f4e13ed172e"); - -pub(crate) const SESSION_ID_SIZE: usize = 4; - -pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_1: u8 = 0; -pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_2: u8 = 1; -pub(crate) const PACKET_TYPE_NOISE_XK_PATTERN_3: u8 = 2; -pub(crate) const PACKET_TYPE_KEY_CONFIRM: u8 = 3; -pub(crate) const PACKET_TYPE_ACK: u8 = 4; -pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_1: u8 = 5; -pub(crate) const PACKET_TYPE_NOISE_KK_PATTERN_2: u8 = 6; -pub(crate) const PACKET_TYPE_SESSION_REJECTED: u8 = 7; -pub(crate) const PACKET_TYPE_DATA: u8 = 8; -pub(crate) const PACKET_TYPE_BOB_DOS_CHALLENGE: u8 = 9; -pub(crate) const PACKET_TYPE_RANGE_TRANSPORT: std::ops::Range = 3..9; - -/// Noise asks that the counter be initialized to 0 but for out of order reasons we have -/// to start it at 1. -/// Since with unreliable transport the first counter could always end up dropped this is -/// functionally equivalent to initializing to 0. -pub(crate) const INIT_COUNTER: u64 = 0; -pub(crate) const LABEL_RATCHET_STATE: u8 = b'R'; -pub(crate) const LABEL_HEADER_KEY: u8 = b'H'; -pub(crate) const LABEL_KEX_KEY: u8 = b'K'; - -/// Size of keys used during derivation, mixing, etc. -pub(crate) const HASHLEN: usize = SHA512_HASH_SIZE; - -pub(crate) const HEADER_SIZE: usize = 16; -pub(crate) const HEADER_PROTECT_ENC_START: usize = 4; -pub(crate) const HEADER_PROTECT_ENC_END: usize = 20; -pub(crate) const CHALLENGE_COUNTER_SIZE: usize = 8; -pub(crate) const CHALLENGE_MAC_SIZE: usize = 16; -pub(crate) const CHALLENGE_POW_SIZE: usize = 8; -pub(crate) const CHALLENGE_SALT_SIZE: usize = 32; - -pub(crate) const MAX_NOISE_HANDSHAKE_SIZE: usize = MAX_FRAGMENTS * MIN_TRANSPORT_MTU; -pub(crate) const CONTROL_PACKET_MAX_SIZE: usize = HEADER_SIZE + NoiseKKPattern1or2::SIZE + AES_GCM_TAG_SIZE; -pub(crate) const CONTROL_PACKET_MIN_SIZE: usize = HEADER_SIZE + AES_GCM_TAG_SIZE; - -/// Determines the number of counters a session will remember. If a counter arrives over -/// this amount out of order relative to other received counters, it is likely to be -/// rejected on the basis that the session can't remember if this counter was replayed. -/// Increasing this value makes a session consume more memory. -pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 64; -/// Maximum number of counter steps that the counter is allowed to skip ahead. -/// This cannot be changed away from 2^24 without changing the header nonce handling code. -pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 16777216; -/// Similar to `COUNTER_WINDOW_MAX_OOO`, except this governs the receive context challenge -/// counter rather than the session counter. -/// When Bob issues a challenge to Alice to mitigate DDOS, Bob will only accept Alice's -/// response once, and then its attached counter is added to the window. -pub(crate) const CHALLENGE_COUNTER_WINDOW_MAX_OOO: usize = 32; -/// We hard-expire the Noise counter long before we reach u64::MAX because of the ABA problem. -/// Over (1<<16) threads would have to attempt to increment the counter at the same time -/// to overflow it. -/// Having (1<<16) threads active at the same time would crash basically any system. -pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - (1 << 16); - -/// Maximum number of fragments a single packet may be split into. If a packet cannot fit -/// into this number of fragments it will be dropped. -pub(crate) const MAX_FRAGMENTS: usize = 48; // hard protocol max: 63 -/// Maximum window over which session packets may be reordered to be defragmented and -/// reassembled. Out of order fragments may be dropped in favor of newer fragments. -/// Increasing this value makes a session consume more significantly more memory. -pub(crate) const SESSION_MAX_FRAGMENTS_OOO: usize = 32; - -/// The maximum number of unassociated packets that a receive context will cache. -/// Additional packets will either be dropped or cause a different packet to be dropped -/// from the cache. -/// Larger values consume more memory but provide better reliability and DDOS resistance. -pub(crate) const MAX_UNASSOCIATED_PACKETS: usize = 32; -/// The maximum number of fragments of unassociated packets that a receive context will -/// cache. -/// All unassociated fragments share the same buffer, when it fills up additional -/// fragments will be dropped or cause other fragments to be dropped from the cache. -/// Larger values consume more memory but provide better reliability and DDOS resistance. -pub(crate) const MAX_UNASSOCIATED_FRAGMENTS: usize = 32 * 32; -/// The maximum number of `NoiseXKBobHandshakeState` that a receive context will cache. -/// These are extremely large and since Alice has not been authenticated we put a hard -/// limit to how many we cache. -/// Larger values consume more memory but provide better reliability and DDOS resistance. -pub(crate) const MAX_UNASSOCIATED_HANDSHAKE_STATES: usize = 32; - -/// The maximum size a packet that is not associated to a session may be. -/// Excludes the size of headers for fragmentation. -pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = NoiseXKPattern1::MAX_SIZE - HEADER_SIZE; - -/* -XKhfs+psk2: - <- s - ... - -> e, es, e1 - <- e, ee, ekem1, psk - -> s, se -*/ -/* -KKpsk0: - -> s - <- s - ... - -> psk, e, es, ss - <- e, ee, se -*/ -/* -Header: - [0..4] recipient key id --- start AES(ck_es * h_e_e1_p) encrypted block -- - [4] fragment count (1..255) - [5] fragment number (0..254) - [6] reserved zero --- start AES-GCM Nonce -- - [7] packet type - [8..16] 64-bit counter or packet id -*/ -/// The first packet in Noise_XK exchange containing Alice's ephemeral keys, key id, -/// and a random symmetric key to protect header fragmentation fields for this session. -#[repr(C, packed)] -pub(crate) struct NoiseXKPattern1 { - pub header: [u8; HEADER_SIZE], - /// -- start prologue -- - pub alice_key_id: [u8; SESSION_ID_SIZE], - /// -- end prologue -- - pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], - /// -- start AES-GCM(k_es) encrypted section - pub noise_e1: [u8; KYBER_PUBLICKEYBYTES], - /// -- end encrypted section - pub e1_gcm_tag: [u8; AES_GCM_TAG_SIZE], - pub payload: [u8; RATCHET_SIZE + RATCHET_SIZE + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE], -} - -#[repr(C, packed)] -pub(crate) struct ChallengeResponse { - pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE], - pub challenge_mac: [u8; CHALLENGE_MAC_SIZE], - pub challenge_pow: [u8; CHALLENGE_POW_SIZE], -} - -impl NoiseXKPattern1 { - pub const PROLOGUE_START: usize = HEADER_SIZE; - pub const PROLOGUE_END: usize = Self::PROLOGUE_START + SESSION_ID_SIZE; - pub const E1_ENC_START: usize = Self::PROLOGUE_END + P384_PUBLIC_KEY_SIZE; - pub const E1_AUTH_START: usize = Self::E1_ENC_START + KYBER_PUBLICKEYBYTES; - pub const P_ENC_START: usize = Self::E1_AUTH_START + AES_GCM_TAG_SIZE; - - pub const MIN_SIZE: usize = Self::P_ENC_START + AES_GCM_TAG_SIZE + ChallengeResponse::SIZE; - pub const MAX_SIZE: usize = Self::MIN_SIZE + RATCHET_SIZE + RATCHET_SIZE; -} -impl ChallengeResponse { - pub const SIZE: usize = CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; -} - -#[repr(C, packed)] -pub(crate) struct BobDOSChallenge { - pub header: [u8; HEADER_SIZE], - pub alice_key_id: [u8; SESSION_ID_SIZE], - pub challenge_counter: [u8; CHALLENGE_COUNTER_SIZE], - pub challenge_mac: [u8; CHALLENGE_MAC_SIZE], - pub prior_challenge_pow: [u8; CHALLENGE_POW_SIZE], -} - -impl BobDOSChallenge { - pub const SIZE: usize = HEADER_SIZE + SESSION_ID_SIZE + CHALLENGE_COUNTER_SIZE + CHALLENGE_MAC_SIZE + CHALLENGE_POW_SIZE; -} - -/// The response to NoiseXKPattern1 containing Bob's ephemeral keys. -#[repr(C, packed)] -pub(crate) struct NoiseXKPattern2 { - pub header: [u8; HEADER_SIZE], - pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], - /// -- start AES-GCM(k_es_ee) encrypted section - pub noise_ekem1: [u8; KYBER_CIPHERTEXTBYTES], - /// -- end encrypted section - pub ekem1_gcm_tag: [u8; AES_GCM_TAG_SIZE], - /// -- start AES-GCM(k_es_ee_ekem1_psk) encrypted section - pub bob_key_id: [u8; SESSION_ID_SIZE], - /// -- end encrypted section - pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], -} - -impl NoiseXKPattern2 { - pub const EKEM1_ENC_START: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE; - pub const EKEM1_AUTH_START: usize = Self::EKEM1_ENC_START + KYBER_CIPHERTEXTBYTES; - pub const P_ENC_START: usize = Self::EKEM1_AUTH_START + AES_GCM_TAG_SIZE; - pub const P_AUTH_START: usize = Self::P_ENC_START + SESSION_ID_SIZE; - pub const P_AUTH_END: usize = Self::P_AUTH_START + AES_GCM_TAG_SIZE; - pub const SIZE: usize = Self::P_AUTH_END; -} - -/// Alice's final response containing her identity (she already knows Bob's) and meta-data. -/// While Alice's response does match what is described in this struct, -/// this struct is unused because it would contain variable length fields. -/// It is present here for documentation purposes. -#[repr(C, packed)] -pub(crate) struct NoiseXKPattern3 { - pub header: [u8; HEADER_SIZE], - /// -- start AES-GCM(k_es_ee_ekem1_psk) encrypted section - pub noise_s: [u8; P384_PUBLIC_KEY_SIZE], - /// -- end encrypted section - pub s_gcm_tag: [u8; AES_GCM_TAG_SIZE], - /// -- start AES-GCM(k_es_ee_ekem1_psk_se) encrypted section - pub alice_blob: [u8; 0], - /// -- end encrypted section - pub p_gcm_tag: [u8; AES_GCM_TAG_SIZE], -} -impl NoiseXKPattern3 { - pub const MIN_SIZE: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; - pub const MAX_SIZE: usize = MAX_NOISE_HANDSHAKE_SIZE; -} - -#[repr(C, packed)] -pub(crate) struct NoiseKKPattern1or2 { - pub header: [u8; HEADER_SIZE], - pub noise_e: [u8; P384_PUBLIC_KEY_SIZE], - pub key_id: [u8; SESSION_ID_SIZE], - pub gcm_tag: [u8; AES_GCM_TAG_SIZE], - pub kek_tag: [u8; AES_GCM_TAG_SIZE], -} -impl NoiseKKPattern1or2 { - pub const ENC_START: usize = HEADER_SIZE + P384_PUBLIC_KEY_SIZE; - pub const AUTH_START: usize = Self::ENC_START + SESSION_ID_SIZE; - pub const AUTH_END: usize = Self::AUTH_START + AES_GCM_TAG_SIZE; - pub const SIZE: usize = Self::AUTH_END + AES_GCM_TAG_SIZE; -} - -// Annotate only these structs as being compatible with byte_array_as_proto_buffer(). These structs -// are packed flat buffers containing only byte or byte array fields, making them safe to treat -// this way even on architectures that require type size aligned access. -pub(crate) trait ProtocolFlatBuffer {} -impl ProtocolFlatBuffer for NoiseXKPattern1 {} -impl ProtocolFlatBuffer for NoiseXKPattern2 {} -impl ProtocolFlatBuffer for NoiseKKPattern1or2 {} -impl ProtocolFlatBuffer for BobDOSChallenge {} -impl ProtocolFlatBuffer for ChallengeResponse {} - -#[inline(always)] -pub(crate) fn byte_array_as_proto_buffer(b: &[u8]) -> &B { - assert!(b.len() >= size_of::()); - unsafe { &*b.as_ptr().cast() } -} - -#[inline(always)] -pub(crate) fn byte_array_as_proto_buffer_mut(b: &mut [u8]) -> &mut B { - assert!(b.len() >= size_of::()); - unsafe { &mut *b.as_mut_ptr().cast() } -} -/// Trick rust into letting us use a hasher that returns more than 64 bits. -pub(crate) struct ShaHasher<'a, ShaImpl: HashSha512>(pub &'a mut ShaImpl); -impl<'a, ShaImpl: HashSha512> Hasher for ShaHasher<'a, ShaImpl> { - fn finish(&self) -> u64 { - panic!() - } - fn write(&mut self, bytes: &[u8]) { - self.0.update(bytes) - } -} diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index 378eb78..d90298b 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -1,10 +1,15 @@ +use arrayvec::ArrayVec; use zeroize::Zeroizing; -use crate::crypto::secure_eq; +use crate::crypto::*; use crate::proto::*; /// A ratchet key and fingerprint, /// along with the length of the ratchet chain the keys were derived from. /// +/// Implements constant time equality. +/// The hash implementation only uses the ratchet fingerprint. +/// Any operation involving the ratchet key must take constant time. +/// /// Corresponds to the Ratchet Key and Ratchet Fingerprint described in Section 3. #[derive(Clone, Eq)] pub struct RatchetState { @@ -14,13 +19,19 @@ pub struct RatchetState { } impl PartialEq for RatchetState { fn eq(&self, other: &Self) -> bool { - secure_eq(&self.key, &other.key) - & (self.chain_len == other.chain_len) - & match (self.fingerprint.as_ref(), other.fingerprint.as_ref()) { - (Some(rf1), Some(rf2)) => secure_eq(rf1, rf2), - (None, None) => true, - _ => false, - } + let ret = match (self.fingerprint.as_ref(), other.fingerprint.as_ref()) { + (Some(rf1), Some(rf2)) => secure_eq(rf1, rf2), + (None, None) => true, + _ => false, + }; + ret & secure_eq(&self.key, &other.key) & (self.chain_len == other.chain_len) + } +} +impl std::hash::Hash for RatchetState { + fn hash(&self, state: &mut H) { + if let Some(rf) = &self.fingerprint { + state.write_u64(u64::from_ne_bytes(rf[..8].try_into().unwrap())) + } } } impl RatchetState { @@ -41,29 +52,23 @@ impl RatchetState { chain_len: 0, } } - //pub fn new_from_otp(otp: &[u8]) -> RatchetState { - // let mut buffer = Vec::new(); - // buffer.push(1); - // buffer.extend(LABEL_OTP_TO_RATCHET); - // buffer.push(0x00); - // buffer.extend((2u16 * 512u16).to_be_bytes()); - // let r1 = Hmac::hmac(otp, &buffer); - // buffer[0] = 2; - // let r2 = Hmac::hmac(otp, &buffer); - // Self::new( - // Zeroizing::new(r1[..RATCHET_SIZE].try_into().unwrap()), - // Zeroizing::new(r2[..RATCHET_SIZE].try_into().unwrap()), - // 1, - // ) - //} + pub fn new_from_otp(otp: &[u8]) -> RatchetState { + let mut buffer = ArrayVec::::new(); + buffer.push(1); + buffer.extend(*LABEL_OTP_TO_RATCHET); + buffer.push(0x00); + buffer.extend((1024u16).to_be_bytes()); - pub fn new_initial_states() -> (RatchetState, Option) { - (RatchetState::empty(), None) + let mut hmac = Hmac::new(); + let mut output = Zeroizing::new([0u8; HASHLEN]); + hmac.hash(otp, &buffer, &mut output); + let rk = Zeroizing::new(output[..RATCHET_SIZE].try_into().unwrap()); + buffer[0] = 2; + hmac.hash(otp, &buffer, &mut output); + let rf = Zeroizing::new(output[..RATCHET_SIZE].try_into().unwrap()); + + Self::new(rk, rf, 1) } - //pub fn new_otp_states(otp: &[u8]) -> (RatchetState, Option) { - // (RatchetState::new_from_otp::(otp), None) - //} - pub fn is_empty(&self) -> bool { self.fingerprint.is_none() } @@ -74,3 +79,87 @@ impl RatchetState { self.fingerprint.as_deref() } } +impl Default for RatchetState { + fn default() -> Self { + Self::empty() + } +} + +/// A pair of ratchet states. +/// It is expected that an instance of this object will be saved to a storage device per-peer, +/// and be restore-able via the `ApplicationLayer` trait. +/// +/// This corresponds to the possible values of abstract variables `rf` and `rk` found in Section 4.3. +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct RatchetStates { + pub state1: RatchetState, + pub state2: Option, +} +impl RatchetStates { + pub fn new(state1: RatchetState, state2: Option) -> Self { + Self { state1, state2 } + } + pub fn new_initial_states() -> Self { + Self { state1: RatchetState::empty(), state2: None } + } + pub fn new_otp_states(otp: &[u8]) -> Self { + Self { + state1: RatchetState::new_from_otp::(otp), + state2: None, + } + } +} +impl Default for RatchetStates { + fn default() -> Self { + Self::new_initial_states() + } +} + +/// A set of references to ratchet states specifying how a remote peer's persistent +/// storage should be updated. +/// +/// There should be only up to two ratchet states saved to storage at a time per peer. +/// Every time a new ratchet state is generated, a previous ratchet state will be deleted. +/// +/// These are sensitive values should they ought to be securely stored. +#[derive(Clone)] +pub struct RatchetUpdate<'a> { + /// The ratchet key and fingerprint to store in the first slot. + pub state1: &'a RatchetState, + /// The ratchet key and fingerprint to store in the second slot. + pub state2: Option<&'a RatchetState>, + /// Whether `state1` is a brand new ratchet state, or if it was previously saved. + pub state1_was_just_added: bool, + /// A previous ratchet key and fingerprint that now must be deleted from storage. + /// This will have been a previously given value of `state1` or `state2`. + pub deleted_state1: Option<&'a RatchetState>, + /// A previous ratchet key and fingerprint that now must be deleted from storage. + /// It is extremely rare that this field is occupied. + pub deleted_state2: Option<&'a RatchetState>, +} +impl<'a> RatchetUpdate<'a> { + pub fn to_states(&self) -> RatchetStates { + RatchetStates::new(self.state1.clone(), self.state2.cloned()) + } + pub fn added_fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { + if self.state1_was_just_added { + self.state1.fingerprint() + } else { + None + } + } + pub fn deleted_fingerprint1(&self) -> Option<&[u8; RATCHET_SIZE]> { + if let Some(rs) = &self.deleted_state1 { + rs.fingerprint() + } else { + None + } + } + pub fn deleted_fingerprint2(&self) -> Option<&[u8; RATCHET_SIZE]> { + if let Some(rs) = &self.deleted_state2 { + rs.fingerprint() + } else { + None + } + } +} diff --git a/src/ratchet_state_old.rs b/src/ratchet_state_old.rs deleted file mode 100644 index 73519e6..0000000 --- a/src/ratchet_state_old.rs +++ /dev/null @@ -1,62 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::num::NonZeroU64; - -#[derive(Clone, PartialEq, Eq)] -pub enum RatchetState { - Null, - Empty, - NonEmpty(NonEmptyRatchetState), -} -use RatchetState::*; -use zeroize::Zeroizing; - -use crate::proto::RATCHET_SIZE; -impl RatchetState { - pub fn new_nonempty(key: Zeroizing<[u8; RATCHET_SIZE]>, fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, chain_len: NonZeroU64) -> Self { - NonEmpty(NonEmptyRatchetState { key, fingerprint, chain_len }) - } - pub fn new_initial_states() -> [RatchetState; 2] { - [RatchetState::Empty, RatchetState::Null] - } - pub fn is_null(&self) -> bool { - matches!(self, Null) - } - pub fn is_empty(&self) -> bool { - matches!(self, Empty) - } - pub fn nonempty(&self) -> Option<&NonEmptyRatchetState> { - match self { - NonEmpty(rs) => Some(rs), - _ => None, - } - } - pub fn chain_len(&self) -> u64 { - self.nonempty().map_or(0, |rs| rs.chain_len.get()) - } - //pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { - // self.nonempty().map(|rs| rs.fingerprint.as_ref()) - //} - //pub fn key(&self) -> Option<&[u8; RATCHET_SIZE]> { - // const ZERO_KEY: [u8; RATCHET_SIZE] = [0u8; RATCHET_SIZE]; - // match self { - // Null => None, - // Empty => Some(&ZERO_KEY), - // NonEmpty(rs) => Some(rs.key.as_ref()), - // } - //} -} -/// A ratchet key and fingerprint, -/// along with the length of the ratchet chain the keys were derived from. -#[derive(Clone, PartialEq, Eq)] -pub struct NonEmptyRatchetState { - pub key: Zeroizing<[u8; RATCHET_SIZE]>, - pub fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, - pub chain_len: NonZeroU64, -} diff --git a/src/result.rs b/src/result.rs index c070bc9..15b023d 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::applicationlayer::ApplicationLayer; +use crate::application::ApplicationLayer; use crate::zeta::Session; /// An error that can occur when attempting to open a session. diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index e2f0e5c..a780b72 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -1,12 +1,10 @@ use std::marker::PhantomData; -use arrayvec::ArrayVec; use zeroize::Zeroizing; -use crate::crypto::aes::{HighThroughputAesGcmPool, LowThroughputAesGcm, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}; -use crate::crypto::sha512::{HashSha512, HmacSha512}; +use crate::crypto::*; use crate::proto::*; -use crate::{applicationlayer::ApplicationLayer, crypto::aes::AES_256_KEY_SIZE}; +use crate::application::ApplicationLayer; pub struct SymmetricState { k: Zeroizing<[u8; AES_256_KEY_SIZE]>, diff --git a/src/symmetric_state_old.rs b/src/symmetric_state_old.rs deleted file mode 100644 index 264fc45..0000000 --- a/src/symmetric_state_old.rs +++ /dev/null @@ -1,156 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ -use crate::crypto::aes::AES_256_KEY_SIZE; -use crate::crypto::sha512::HmacSha512; - -use crate::proto::NOISE_HASHLEN; - -#[derive(Clone)] -pub(crate) struct SymmetricState { - chaining_key: Secret, - token_counter: u8, -} - -impl SymmetricState { - pub(crate) fn new(h: [u8; NOISE_HASHLEN]) -> Self { - Self { chaining_key: Secret(h), token_counter: b'P' } - } - /// Corresponds to Noise `MixKey`. - pub(crate) fn mix_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) { - let mut next_ck = Secret::new(); - - self.kbkdf(hm, input_key_material, self.label(), 2, next_ck.as_mut(), None, None); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - // We don't need a key at this step of Noise, so generating that key and calling - // `InitializeKey` would be completely pointless. - } - /// Corresponds to Noise `MixKey` followed by `InitializeKey`. - pub(crate) fn mix_key_initialize_key(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> Secret { - let mut next_ck = Secret::new(); - let mut temp_k = [0u8; NOISE_HASHLEN]; - - self.kbkdf(hm, input_key_material, self.label(), 2, next_ck.as_mut(), Some(&mut temp_k), None); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE]) - } - /// Corresponds to Noise `MixKeyAndHash`. - pub(crate) fn mix_key_and_hash(&mut self, hm: &mut impl HmacSha512, input_key_material: &[u8]) -> [u8; NOISE_HASHLEN] { - let mut next_ck = Secret::new(); - let mut temp_h = [0u8; NOISE_HASHLEN]; - - self.kbkdf(hm, input_key_material, self.label(), 3, next_ck.as_mut(), Some(&mut temp_h), None); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - temp_h - } - /// Corresponds to Noise `MixKeyAndHash` followed by `InitializeKey`. - pub(crate) fn mix_key_and_hash_initialize_key( - &mut self, - hm: &mut impl HmacSha512, - input_key_material: &[u8], - ) -> ([u8; NOISE_HASHLEN], Secret) { - let mut next_ck = Secret::new(); - let mut temp_h = [0u8; NOISE_HASHLEN]; - let mut temp_k = [0u8; NOISE_HASHLEN]; - - self.kbkdf( - hm, - input_key_material, - self.label(), - 3, - next_ck.as_mut(), - Some(&mut temp_h), - Some(&mut temp_k), - ); - self.token_counter += 1; - - self.chaining_key.overwrite(&next_ck); - (temp_h, Secret::from_bytes_then_delete(&mut temp_k[..AES_256_KEY_SIZE])) - } - /// Get an additional symmetric key (ASK) that is a collision resistant hash of the transcript, - /// is forward secrect and is cryptographically independent from all other produced keys. - /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. - /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. - pub(crate) fn get_ask2( - &self, - hm: &mut impl HmacSha512, - label: u8, - noise_h: &[u8; NOISE_HASHLEN], - ) -> (Secret, Secret) { - let mut temp_k1 = [0u8; NOISE_HASHLEN]; - let mut temp_k2 = [0u8; NOISE_HASHLEN]; - self.kbkdf(hm, noise_h, [b'A', b'S', b'K', label], 2, &mut temp_k1, Some(&mut temp_k2), None); - ( - Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]), - Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), - ) - } - /// Corresponds to Noise `Split`. - pub(crate) fn split(self, hm: &mut impl HmacSha512) -> (Secret, Secret) { - let mut temp_k1 = [0u8; NOISE_HASHLEN]; - let mut temp_k2 = [0u8; NOISE_HASHLEN]; - self.kbkdf(hm, &[], self.label(), 2, &mut temp_k1, Some(&mut temp_k2), None); - // Normally KBKDF would not truncate to derive the correct length of AES keys, - // but Noise specifies that the AES keys be truncated from NOISE_HASHLEN to AES_256_KEY_SIZE. - ( - Secret::from_bytes_then_delete(&mut temp_k1[..AES_256_KEY_SIZE]), - Secret::from_bytes_then_delete(&mut temp_k2[..AES_256_KEY_SIZE]), - ) - } - fn label(&self) -> [u8; 4] { - [b'Z', b'S', b'S', self.token_counter] - } - /// HMAC-SHA512 key derivation based on KBKDF Counter Mode: - /// https://csrc.nist.gov/publications/detail/sp/800-108/rev-1/final. - /// Cryptographically this isn't meaningfully different from - /// `HKDF(self.chaining_key, input_key_material)` but this is how NIST rolls. - /// These are the values we have assigned to the 4 variables involved in their KDF: - /// * K_IN = `input_key_material` - /// * Label = `label` - /// * Context = `self.chaining_key` - /// * L = `num_outputs*512u16` - /// We have intentionally made every input small and fixed size to avoid unnecessary complexity - /// and data representation ambiguity. - fn kbkdf( - &self, - hm: &mut impl HmacSha512, - input_key_material: &[u8], - label: [u8; 4], - num_outputs: u16, - output1: &mut [u8; NOISE_HASHLEN], - output2: Option<&mut [u8; NOISE_HASHLEN]>, - output3: Option<&mut [u8; NOISE_HASHLEN]>, - ) { - let l = &(num_outputs * 512u16).to_be_bytes(); - - hm.reset(input_key_material); - hm.update(&[1, label[0], label[1], label[2], label[3], 0x00]); - hm.update(self.chaining_key.as_ref()); - hm.update(l); - hm.finish(output1); - if let Some(output2) = output2 { - hm.reset(input_key_material); - hm.update(&[2, label[0], label[1], label[2], label[3], 0x00]); - hm.update(self.chaining_key.as_ref()); - hm.update(l); - hm.finish(output2); - } - if let Some(output3) = output3 { - hm.reset(input_key_material); - hm.update(&[3, label[0], label[1], label[2], label[3], 0x00]); - hm.update(self.chaining_key.as_ref()); - hm.update(l); - hm.finish(output3); - } - } -} diff --git a/src/zeta.rs b/src/zeta.rs index 79e7eba..0278fb7 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -10,21 +10,14 @@ use std::sync::{Arc, Mutex, RwLock, Weak}; use zeroize::Zeroizing; use crate::antireplay::Window; -use crate::applicationlayer::ApplicationLayer; -use crate::applicationlayer::RatchetUpdate; +use crate::application::*; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; use crate::zssp::{log, ContextInner, SessionQueue}; -//use crate::context::{log, ContextInner, SessionMap}; -use crate::crypto::aes::*; -use crate::crypto::kyber1024::{ - Kyber1024PrivateKey, KYBER_CIPHERTEXT_SIZE, KYBER_PLAINTEXT_SIZE, KYBER_PUBLIC_KEY_SIZE, -}; -use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; -use crate::crypto::sha512::{HashSha512, HmacSha512}; +use crate::crypto::*; use crate::fragged::Fragged; use crate::indexed_heap::BinaryHeapIndex; use crate::proto::*; -use crate::ratchet_state::RatchetState; +use crate::ratchet_state::{RatchetState, RatchetStates}; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; use crate::symmetric_state::SymmetricState; #[cfg(feature = "logging")] @@ -167,10 +160,6 @@ impl Keys { } } -/// Corresponds to the tuple of values the Transition Algorithms send to the remote peer in Section 4.3. -//#[derive(Clone)] -//pub(crate) struct Packet(pub u32, pub [u8; AES_GCM_IV_SIZE], pub Vec); - /// Corresponds to State A_1 of the Zeta State Machine found in Section 4.1. #[derive(Clone)] pub(crate) struct StateA1 { @@ -181,16 +170,17 @@ pub(crate) struct StateA1 { x1: ArrayVec, } +pub(crate) struct StateA3 { + identity: ArrayVec, + x3: ArrayVec, +} + + /// Corresponds to the ZKE Automata found in Section 4.1 - Definition 2. pub(crate) enum ZetaAutomata { Null, A1(Box>), - A3 { - identity: ArrayVec, - kid_send: u32, - nonce: [u8; AES_GCM_IV_SIZE], - x3: ArrayVec, - }, + A3(Box), S1, S2, R1 { @@ -232,6 +222,34 @@ impl SymmetricState { *i = j; App::PublicKey::from_bytes((pub_key).try_into().unwrap()) } + fn write_e_no_init( + &mut self, + hash: &mut App::Hash, + hmac: &mut App::HmacHash, + rng: &Mutex, + packet: &mut ArrayVec, + ) -> App::KeyPair { + let e_secret = App::KeyPair::generate(rng.lock().unwrap().deref_mut()); + let pub_key = e_secret.public_key_bytes(); + packet.extend(pub_key); + self.mix_hash(hash, &pub_key); + self.mix_key_no_init(hmac, &pub_key); + e_secret + } + fn read_e_no_init( + &mut self, + hash: &mut App::Hash, + hmac: &mut App::HmacHash, + i: &mut usize, + packet: &[u8], + ) -> Option { + let j = *i + P384_PUBLIC_KEY_SIZE; + let pub_key = &packet[*i..j]; + self.mix_hash(hash, pub_key); + self.mix_key_no_init(hmac, pub_key); + *i = j; + App::PublicKey::from_bytes((pub_key).try_into().unwrap()) + } fn mix_dh(&mut self, hmac: &mut App::HmacHash, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { @@ -241,6 +259,15 @@ impl SymmetricState { None } } + fn mix_dh_no_init(&mut self, hmac: &mut App::HmacHash, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { + let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); + if secret.agree(&remote, &mut ecdh_secret) { + self.mix_key_no_init(hmac, ecdh_secret.as_ref()); + Some(()) + } else { + None + } + } } /// Generate a random local key id that is currently unused. @@ -335,9 +362,9 @@ pub(crate) fn trans_to_a1( identity: &[u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result>, OpenError> { - let (ratchet_state1, ratchet_state2) = app + let RatchetStates{state1, state2} = app .restore_by_identity(&s_remote, &session_data) - .map_err(|e| OpenError::RatchetIoError(e))?; + .map_err(|e| OpenError::RatchetIoError(e))?.unwrap_or_default(); let mut session_queue = ctx.session_queue.lock().unwrap(); let mut session_map = ctx.session_map.write().unwrap(); @@ -351,8 +378,8 @@ pub(crate) fn trans_to_a1( &ctx.rng, &s_remote, kid_recv, - &ratchet_state1, - ratchet_state2.as_ref(), + &state1, + state2.as_ref(), identity, ) .ok_or(OpenError::InvalidPublicKey)?; @@ -382,8 +409,8 @@ pub(crate) fn trans_to_a1( window: Window::new(), state_machine_lock: Mutex::new(()), state: RwLock::new(MutableState { - ratchet_state1: ratchet_state1.clone(), - ratchet_state2: ratchet_state2.clone(), + ratchet_state1: state1.clone(), + ratchet_state2: state2.clone(), key_creation_counter: 0, key_index: true, keys: [DuplexKey::default(), DuplexKey::default()], @@ -709,8 +736,8 @@ pub(crate) fn received_x2_trans( state1: &new_ratchet_state, state2: ratchet_to_preserve, state1_was_just_added: true, - state_deleted1: ratchet_to_delete, - state_deleted2: None, + deleted_state1: ratchet_to_delete, + deleted_state2: None, }, ); if let Err(e) = result { @@ -746,12 +773,10 @@ pub(crate) fn received_x2_trans( // This return is unreachable. return Err(byzantine_fault!(FailedAuth, true)); }; - state.beta = ZetaAutomata::A3 { + state.beta = ZetaAutomata::A3(Box::new(StateA3 { identity: a1.identity.clone(), x3: x3.clone(), - kid_send: kid_send.get(), - nonce, - }; + })); resend_timer }; drop(kex_lock); @@ -845,8 +870,9 @@ pub(crate) fn received_x3_trans( if let Some(session_data) = session_data { let result = app.restore_by_identity(&s_remote, &session_data); match result { - Ok((ratchet_state1, ratchet_state2)) => { - if (&zeta.ratchet_state != &ratchet_state1) & (Some(&zeta.ratchet_state) != ratchet_state2.as_ref()) { + Ok(rss) => { + let RatchetStates { state1, state2 } = rss.unwrap_or_default(); + if (&zeta.ratchet_state != &state1) & (Some(&zeta.ratchet_state) != state2.as_ref()) { if !responder_disallows_downgrade && zeta.ratchet_state.fingerprint().is_none() { // TODO: add some kind of warning callback or signal. } else { @@ -875,8 +901,8 @@ pub(crate) fn received_x3_trans( state1: &new_ratchet_state, state2: None, state1_was_just_added: true, - state_deleted1: Some(&ratchet_state1), - state_deleted2: ratchet_state2.as_ref(), + deleted_state1: Some(&state1), + deleted_state2: state2.as_ref(), }, ); if let Err(e) = result { @@ -1003,8 +1029,8 @@ pub(crate) fn received_c1_trans( state1: &state.ratchet_state1, state2: None, state1_was_just_added: false, - state_deleted1: state.ratchet_state2.as_ref(), - state_deleted2: None, + deleted_state1: state.ratchet_state2.as_ref(), + deleted_state2: None, }, ); if let Err(e) = result { @@ -1164,7 +1190,7 @@ pub(crate) fn process_timers( ZetaAutomata::A1(_) | ZetaAutomata::A3 { .. } => { let identity = match &state.beta { ZetaAutomata::A1(a1) => &a1.identity, - ZetaAutomata::A3 { identity, .. } => identity, + ZetaAutomata::A3(a3) => &a3.identity, _ => unreachable!(), }; if matches!(&state.beta, ZetaAutomata::A1(_)) { @@ -1302,9 +1328,9 @@ pub(crate) fn process_timers( send(&mut a1.x1.clone(), None); return Some(resend_next); } - ZetaAutomata::A3 { x3, .. } => { + ZetaAutomata::A3(a3) => { log!(app, ResentX3(session)); - send(&mut x3.clone(), Some(&session.hk_send)); + send(&mut a3.x3.clone(), Some(&session.hk_send)); return Some(resend_next); } ZetaAutomata::S1 => { @@ -1471,8 +1497,8 @@ pub(crate) fn received_k1_trans( state1: &new_ratchet_state, state2: Some(&state.ratchet_state1), state1_was_just_added: true, - state_deleted1: state.ratchet_state2.as_ref(), - state_deleted2: None, + deleted_state1: state.ratchet_state2.as_ref(), + deleted_state2: None, }, ); if let Err(e) = result { @@ -1605,8 +1631,8 @@ pub(crate) fn received_k2_trans( state1: &new_ratchet_state, state2: None, state1_was_just_added: true, - state_deleted1: Some(&state.ratchet_state1), - state_deleted2: state.ratchet_state2.as_ref(), + deleted_state1: Some(&state.ratchet_state1), + deleted_state2: state.ratchet_state2.as_ref(), }, ); if let Err(e) = result { diff --git a/src/zssp copy.rs b/src/zssp copy.rs deleted file mode 100644 index 413c38d..0000000 --- a/src/zssp copy.rs +++ /dev/null @@ -1,2704 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public -* License, v. 2.0. If a copy of the MPL was not distributed with this -* file, You can obtain one at https://mozilla.org/MPL/2.0/. -* -* (c) ZeroTier, Inc. -* https://www.zerotier.com/ -*/ -// ZSSP: ZeroTier Secure Session Protocol -// FIPS compliant Noise_XK with Jedi powers (Kyber1024) and built-in attack-resistant large payload (fragmentation) support. - -use std::cmp::Reverse; -use std::collections::HashMap; -use std::hash::Hash; -use std::num::{NonZeroU32, NonZeroU64}; -use std::ops::DerefMut; -use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; -use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; - -use crate::crypto::aes::{AesDec, AesEnc}; -use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; -use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; -use crate::crypto::rand_core::RngCore; -use crate::crypto::sha512::{HmacSha512, HashSha512}; - -use crate::error::{FaultType, OpenError, ReceiveError, SendError}; -use crate::frag_cache::UnassociatedFragCache; -use crate::fragged::{Assembled, Fragged}; -use crate::handshake_cache::UnassociatedHandshakeCache; -use crate::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; -use crate::log_event::LogEvent; -use crate::proto::*; -use crate::symmetric_state::SymmetricState; -use crate::{applicationlayer::*, RatchetState}; - -/// Session context for local application. -/// -/// Each application using ZSSP must create an instance of this to own sessions and -/// defragment incoming packets that are not yet associated with a session. -/// -/// Internally this is just a clonable Arc, so it can be safely shared with multiple threads. -pub struct Context(pub Arc>); -impl Clone for Context { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} -pub struct ContextInner { - static_keypair: Application::KeyPair, - unassociated_defrag_cache: Mutex>, - unassociated_handshake_states: UnassociatedHandshakeCache, - /// `session_queue -> state_machine_lock -> state -> session_map` - session_queue: Mutex>, Reverse>>, - session_map: RwLock>, bool)>>, - challenge_counter: AtomicU64, - challenge_antireplay_window: [AtomicU64; CHALLENGE_COUNTER_WINDOW_MAX_OOO], - challenge_salt: [u8; CHALLENGE_SALT_SIZE], - rng: Mutex, -} - -/// Result generated by the context packet receive function, with possible payloads. -pub enum ReceiveResult<'b, Application: ApplicationLayer> { - /// Packet superficially appeared valid but is not associated with a session yet. - /// This can occur because the packet was only a fragment of a larger packet, - /// or if it was a control packet that does not go through full Noise authentication. - Unassociated, - /// Packet was authentic and belongs to this specific session. - Session(Arc>, SessionEvent<'b>), - /// Packet was a part of a handshake, and while it superficially appeared valid the application - /// explicitly rejected it. - /// Relates to callbacks `check_allow_incoming_session`, `hello_requires_recognized_ratchet` - /// and `check_accept_session`. - Rejected, -} - -#[derive(Debug, PartialEq, Eq)] -pub enum SessionEvent<'b> { - /// The received packet was valid, and it contained the necessary keys to fully establish a new - /// session with Alice, the handshake initiator. - /// - /// If the session Arc returned is dropped, the session with this peer will be immediately - /// terminated. Save the session Arc to some long lived datastructure to keep it alive. - NewSession, - /// When Alice calls `Context::open`, a session will be created, but Bob will not yet have - /// received this session. They will have to successfully complete a handshake first. - /// - /// Alice will receive this return value when the received packet confirms both parties - /// have completed the initial handshake and now have a shared session with each other. - /// If according to the upper protocol, Bob is the first party to send data, it is possible for - /// Alice to start receiving data from Bob before this value is returned. - /// - /// This return value can only occur once per session, only for session objects that were - /// created with `Context::open`. - Established, - /// Bob explicitly refused to establish a session with Alice, and sent us an error code. - /// The application should immediately drop this session as Bob will not allow us to connect. - /// - /// This return value cannot occur after a session is fully established. - Rejected, - /// The received packet was valid and a data payload was decoded and authenticated. - Data(&'b mut [u8]), - /// The received packet was some authentic protocol control packet. No action needs to be taken. - Control, -} - -#[derive(Debug, PartialEq, Eq)] -pub enum IncomingSessionAction { - Allow, - Challenge, - Drop, -} - -/// ZeroTier Secure Session Protocol (ZSSP) Session -/// -/// A FIPS/NIST compliant variant of Noise_XK with hybrid Kyber1024 PQ data forward secrecy. -pub struct Session { - /// An arbitrary application defined object associated with each session. - pub application_data: Application::SessionData, - /// Is true if the local peer acted as Bob, the responder in the initial key exchange. - pub was_bob: bool, - /// The receive context associated with this session, - /// only this context can receive messages from the remote peer. - context: Weak>, - /// Handle into the session queue for changing the update timer. - queue_idx: BinaryHeapIndex, - - remote_static_key: Application::PublicKey, - send_counter: AtomicU64, - /// This bool signals to all threads to stop incrementing the counter and instead error out. - session_has_expired: AtomicBool, - /// The following is a ring buffer of previously seen counter values, where we use the counter's - /// value as the index of the head of the ring buffer. - counter_antireplay_window: [AtomicU64; COUNTER_WINDOW_MAX_OOO], - /// Enforces atomicity of state machine transitions. - /// There is a standard locking sequence, - /// it goes `session_queue -> state_machine_lock -> state -> session_map`. - /// Any lock can be skipped but they must be locked in that order. - state_machine_lock: Mutex<()>, - state: RwLock>, - defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], - header_send_cipher: Application::PrpEnc, - header_receive_cipher: Application::PrpDec, - kex_send_cipher: Mutex>, - kex_receive_cipher: Mutex>, - /// Pre-computed rekeying values. - noise_kk_ss: Secret, - noise_kk_local_init_h: [u8; HASHLEN], - noise_kk_remote_init_h: [u8; HASHLEN], -} -/// `AesGcm` is not threadsafe, but it is threadsafe when inside a `Mutex`. -unsafe impl Send for Session {} -unsafe impl Sync for Session {} - -/// Session state may only be mutated during atomic transitions of the offer state machine. -struct SessionMutableState { - ratchet_states: [RatchetState; 2], - /// For OOO rekeying reliability we allow our version of Noise CipherState to hold the last two - /// session keys, instead of just the most recent one. - cipher_states: [Option>; 2], - /// This is the index of `noise_cipher_state` that contains the most recent key. - /// It will be attached to fragment headers to help with OOO transport. - current_key: usize, - /// This defines the exact state of the offer state machine we are in. - outgoing_offer: OfferStateMachine, -} - -/// These offer enums form a state machine. -/// Documented below are the only legal transitions for this state machine. -/// A session is initialized with an `outgoing_offer` of either NoiseXKPattern1 or Normal. -enum OfferStateMachine { - Normal { - timeout: i64, - }, // -> NoiseKKPattern1, NoiseKKPattern2 - /// This state uses a lot of memory so we put it on the heap. - NoiseXKPattern1or3(Box>), // -> Normal - NoiseKKPattern1 { - next_retry_time: AtomicI64, - timeout: i64, - new_key_id: NonZeroU32, - noise_e_secret: Application::KeyPair, - noise_message: [u8; NoiseKKPattern1or2::SIZE], - noise_ck: SymmetricState, - noise_h_pskep: [u8; HASHLEN], - }, // -> NoiseKKPattern2, KeyConfirm - NoiseKKPattern2 { - next_retry_time: AtomicI64, - timeout: i64, - noise_message: [u8; NoiseKKPattern1or2::SIZE], - kex_send_key: Secret, - }, // -> Normal - KeyConfirm { - next_retry_time: AtomicI64, - timeout: i64, - }, // -> Normal - Null, -} - -pub(crate) struct NoiseXKBobHandshakeState { - /// Can never be Null. - ratchet_state: RatchetState, - remote_key_id: NonZeroU32, - local_key_id: NonZeroU32, - header_receive_key: Secret, - header_send_key: Secret, - noise_h_ee1peekem1pskp: [u8; HASHLEN], - noise_e_secret: Application::KeyPair, - noise_ck_eseeekem1psk: SymmetricState, - noise_k_eseeekem1psk: Secret, - noise_pattern3_defrag: Mutex>, -} - -struct NoiseXKAliceHandshake { - next_retry_time: AtomicI64, - timeout: i64, - /// A secure random number put in the header of Alice's fragments to identify them. - /// If a DDOS attacker could guess this they could block Alice starting the handshake. - local_key_id: NonZeroU32, - alice_identity_blob: Application::LocalIdentityBlob, - offer: NoiseXKAliceHandshakeState, -} - -enum NoiseXKAliceHandshakeState { - NoiseXKPattern1 { - noise_h_ee1p: [u8; HASHLEN], - noise_e_secret: Application::KeyPair, - noise_e1_secret: Secret, - noise_ck_es: SymmetricState, - /// ZSSP assumes an unreliable, out-of-order physical transport environment, so for that - /// reason we have to resend key offers. - noise_message: [u8; NoiseXKPattern1::MAX_SIZE], - noise_message_len: usize, - message_id: u64, - }, - NoiseXKPattern3 { - noise_message: [u8; NoiseXKPattern3::MAX_SIZE], - noise_message_len: usize, - }, -} - -struct SessionKey { - remote_key_id: NonZeroU32, - local_key_id: NonZeroU32, - /// Pool of reusable sending ciphers. - receive_cipher_pool: [Mutex; 8], - /// Pool of reusable receiving ciphers. - send_cipher_pool: [Mutex; 8], - /// Rekey at or after this counter. - rekey_at_counter: u64, - /// Hard error when this counter value is reached or exceeded. - expire_at_counter: u64, -} - -macro_rules! byzantine_fault { - ($name:expr, $is_natural:ident) => { - ReceiveError::ByzantineFault { - file: file!(), - line: line!(), - error: $name, - is_naturally_occurring: $is_natural, - } - }; -} - -impl Context { - /// Create a new session context. - pub fn new(static_keypair: Application::KeyPair, mut rng: Application::Rng) -> Self { - debug_assert!(Application::REKEY_AFTER_TIME_MAX_JITTER_MS > 0, "Invalid protocol constant"); - let mut challenge_salt = [0u8; CHALLENGE_SALT_SIZE]; - rng.fill_bytes(&mut challenge_salt); - Self(Arc::new(ContextInner { - static_keypair, - unassociated_defrag_cache: Mutex::new(UnassociatedFragCache::new()), - unassociated_handshake_states: UnassociatedHandshakeCache::new(), - session_map: RwLock::new(HashMap::new()), - session_queue: Mutex::new(IndexedBinaryHeap::new()), - challenge_counter: AtomicU64::new(INIT_COUNTER), - challenge_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - challenge_salt, - rng: Mutex::new(rng), - })) - } - - /// Perform periodic background service and cleanup tasks. - /// - /// This returns the number of milliseconds until it should be called again. The caller should - /// try to satisfy this but small variations in timing of up to +/- a second or two are not - /// a problem. - /// - /// * `send_to` - Function to get a sender and an MTU to send something over an active session - /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced - /// with remote peers (although both of these properties would help reliability slightly). - /// Used to determine if any current handshakes should be resent or timed-out, or if a session - /// should rekey. - pub fn service bool>( - &self, - app: &Application, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, - current_time: i64, - ) -> i64 { - let retry_next = current_time.saturating_add(Application::RETRY_INTERVAL_MS); - let mut next_service_time = 2 * Application::RETRY_INTERVAL_MS; - - let mut session_queue = self.0.session_queue.lock().unwrap(); - // This update system takes heavy advantage of the fact that sessions only need to be updated - // either roughly every second or roughly every hour. That big gap allows for minor optimizations. - // If the gap changes (unlikely) this code may need to be rewritten. - while let Some((session, timer, queue_idx)) = session_queue.peek() { - if timer.0 >= current_time { - next_service_time = next_service_time.min(timer.0 - current_time); - break; - } - let session = match session.upgrade() { - Some(s) => s, - _ => { - session_queue.remove(queue_idx); - continue; - } - }; - let state = session.state.read().unwrap(); - use OfferStateMachine::*; - let next_timer = match &state.outgoing_offer { - Normal { timeout, .. } => { - if *timeout <= current_time { - drop(state); - if let Some((send, _)) = send_to(&session) { - let result = initiate_rekey(&self.0, &session, send, current_time); - if result.is_ok() { - app.event_log(LogEvent::ServiceKKStart(&session), current_time); - } - result.unwrap_or(retry_next) - } else { - retry_next - } - } else { - *timeout - } - } - // If there's an outstanding attempt to open a session, retransmit this - // periodically in case the initial packet doesn't make it. - NoiseXKPattern1or3(handshake_state) => { - if let Some(ts) = process_timer(&handshake_state.next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { - ts - } else { - // We have to eventually time out NoiseXKPattern3 because of unreliable network conditions. - if handshake_state.timeout <= current_time { - drop(state); - let _kex_lock = session.state_machine_lock.lock().unwrap(); - let mut state = session.state.write().unwrap(); - let ratchet_state = state.ratchet_states.clone(); - // Since we dropped the lock we must re-check if we are in the correct state. - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if handshake_state.timeout <= current_time { - app.event_log(LogEvent::ServiceXKTimeout(&session), current_time); - handshake_state.reinitialize( - &session, - &ratchet_state, - &mut self.0.session_map.write().unwrap(), - &mut self.0.rng.lock().unwrap(), - current_time, - ); - } - } - } else if let Some((mut send, mut mtu)) = send_to(&session) { - mtu = mtu.max(MIN_TRANSPORT_MTU); - match &handshake_state.offer { - NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } => { - app.event_log(LogEvent::ServiceXK1Resend(&session), current_time); - // We are in state NoiseXKPattern1 so resend noise_pattern1. - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_1, - None, - *message_id, - None::<&Application::PrpEnc>, - ); - } - NoiseXKAliceHandshakeState::NoiseXKPattern3 { noise_message, noise_message_len, .. } => { - app.event_log(LogEvent::ServiceXK3Resend(&session), current_time); - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_3, - state.cipher_states[0].as_ref().map(|k| k.remote_key_id), - 0, - Some(&session.header_send_cipher), - ); - } - } - } - retry_next - } - } - NoiseKKPattern1 { next_retry_time, timeout, noise_message, .. } | NoiseKKPattern2 { next_retry_time, timeout, noise_message, .. } => { - if let Some(ts) = process_timer(next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { - ts - } else { - if *timeout <= current_time { - app.event_log(LogEvent::ServiceKKTimeout(&session), current_time); - next_retry_time.store(i64::MAX, Ordering::Relaxed); - drop(state); - session.expire_inner(&self.0, &mut session_queue); - } else { - let packet_type = if let NoiseKKPattern1 { .. } = &state.outgoing_offer { - app.event_log(LogEvent::ServiceKK1Resend(&session), current_time); - PACKET_TYPE_NOISE_KK_PATTERN_1 - } else { - app.event_log(LogEvent::ServiceKK2Resend(&session), current_time); - PACKET_TYPE_NOISE_KK_PATTERN_2 - }; - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&state, send, packet_type, noise_message); - } - } - retry_next - } - } - KeyConfirm { next_retry_time, timeout, .. } => { - if let Some(ts) = process_timer(next_retry_time, Application::RETRY_INTERVAL_MS, current_time) { - ts - } else { - if *timeout <= current_time { - app.event_log(LogEvent::ServiceKeyConfirmTimeout(&session), current_time); - next_retry_time.store(i64::MAX, Ordering::Relaxed); - drop(state); - session.expire_inner(&self.0, &mut session_queue); - } else { - app.event_log(LogEvent::ServiceKeyConfirmResend(&session), current_time); - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&state, send, PACKET_TYPE_KEY_CONFIRM, &[]); - } - } - retry_next - } - } - Null => retry_next, - }; - session_queue.change_priority(queue_idx, Reverse(next_timer)); - } - drop(session_queue); - - self.0 - .unassociated_defrag_cache - .lock() - .unwrap() - .check_for_expiry(Application::INITIAL_OFFER_TIMEOUT_MS, current_time); - self.0.unassociated_handshake_states.service(current_time); - - next_service_time - } - - /// Create a new session and send initial packet(s) to other side. - /// - /// This will return SendError::DataTooLarge if the combined size of the metadata and the local - /// static public blob (as retrieved from the application layer) exceed MAX_INIT_PAYLOAD_SIZE. - /// - /// * `app` - Application layer instance - /// * `send` - Function to be called to send one or more initial packets to the remote being - /// contacted - /// * `mtu` - MTU for initial packets - /// * `remote_static_key` - Remote side's static public NIST P-384 key - /// * `application_data` - Arbitrary data meaningful to the application to include with session - /// object - /// * `ratchet_state` - The last saved and confirmed ratchet state associated with this remote - /// peer, or None if we do not have one. - /// * `local_identity_blob` - Payload to be sent to Bob that contains the information necessary - /// for the upper protocol to authenticate and approve of Alice's identity. - /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced - /// with the remote peer. Used to determine when this offer should be resent. - pub fn open( - &self, - app: &Application, - mut send: impl FnMut(&mut [u8]) -> bool, - mut mtu: usize, - remote_static_key: Application::PublicKey, - application_data: Application::SessionData, - local_identity_blob: Application::LocalIdentityBlob, - current_time: i64, - ) -> Result>, OpenError> { - mtu = mtu.max(MIN_TRANSPORT_MTU); - if local_identity_blob.as_ref().len() > MAX_IDENTITY_BLOB_SIZE { - return Err(OpenError::DataTooLarge); - } - let result = app.restore_by_identity(&remote_static_key, &application_data, current_time); - match result { - Ok(ratchet_states) => { - let sha512 = &mut Application::Hash::new(); - - let mut noise_kk_ss = Secret::new(); - if !self.0.static_keypair.agree(&remote_static_key, noise_kk_ss.as_mut()) { - return Err(OpenError::InvalidPublicKey); - } - let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); - let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_static_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_static_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); - - let mut session_queue = self.0.session_queue.lock().unwrap(); - let mut session_map = self.0.session_map.write().unwrap(); - let local_key_id = generate_key_id(&session_map, &mut self.0.rng.lock().unwrap()); - // Begin Noise XKhfs+psk2. - let (offer, a2b_header_key, b2a_header_key) = NoiseXKAliceHandshake::::initialize( - local_key_id, - &remote_static_key, - &ratchet_states, - &mut self.0.rng.lock().unwrap(), - )?; - let handshake_state = Box::new(NoiseXKAliceHandshake { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS), - local_key_id, - alice_identity_blob: local_identity_blob, - offer, - }); - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, message_id, .. } = &handshake_state.offer { - send_with_fragmentation( - &mut send, - mtu, - &mut noise_message.clone()[..*noise_message_len], - PACKET_TYPE_NOISE_XK_PATTERN_1, - None, - *message_id, - None::<&Application::PrpEnc>, - ); - } - - let queue_idx = session_queue.reserve_index(); - let session = Arc::new(Session { - context: Arc::downgrade(&self.0), - queue_idx, - application_data, - remote_static_key, - send_counter: AtomicU64::new(INIT_COUNTER), - session_has_expired: AtomicBool::new(false), - counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - state_machine_lock: Mutex::new(()), - state: RwLock::new(SessionMutableState { - ratchet_states: ratchet_states.clone(), - cipher_states: [None, None], - // Points at 1 until the first key is confirmed. - current_key: 1, - outgoing_offer: OfferStateMachine::NoiseXKPattern1or3(handshake_state), - }), - header_send_cipher: Application::PrpEnc::new(a2b_header_key.as_ref()), - header_receive_cipher: Application::PrpDec::new(b2a_header_key.as_ref()), - kex_receive_cipher: Mutex::new(None), - kex_send_cipher: Mutex::new(None), - noise_kk_ss: noise_kk_ss.clone(), - noise_kk_local_init_h, - noise_kk_remote_init_h, - defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - was_bob: false, - }); - session_map.insert(local_key_id, (Arc::downgrade(&session), false)); - session_queue.push_reserved( - queue_idx, - Arc::downgrade(&session), - Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - ); - - Ok(session) - } - Err(e) => Err(OpenError::RatchetIoError(e)), - } - } - - /// Receive, authenticate, decrypt, and process a physical wire packet. - /// - /// The check_allow_incoming_session function is called when an initial Noise_XK init message is - /// received. This is before anything is known about the caller. A return value of true proceeds - /// with negotiation. False drops the packet and ignores the inbound attempt. - /// - /// The check_accept_session function is called at the end of negotiation for an incoming - /// session with the caller's static public blob. It must return the P-384 static public key - /// extracted from the supplied blob and application data. A return of Some() accepts the - /// session and will always result in a new session ReceiveResult being returned. - /// - /// * `app` - Interface to application using ZSSP - /// * `check_allow_incoming_session` - Function to call to check whether an unidentified new - /// session should be accepted - /// * `check_accept_session` - Function to accept sessions after final negotiation. - /// The second argument is the identity blob that the remote peer sent us. The application - /// must verify this identity is associated with the remote peer's static key. - /// The third argument is the ratchet chain length, or ratchet count. - /// To prevent desync, if this function returns (Some(_), _), no other open session with the - /// same remote peer must exist. - /// * `send_unassociated_reply` - Function to send reply packets directly when no session exists - /// * `send_unassociated_mtu` - MTU for unassociated replies - /// * `send_to` - Function to get senders for existing sessions, permitting MTU and path lookup - /// * `remote_address` - Whatever the remote address is, as long as you can Hash it - /// * `data_buf` - Buffer to receive decrypted and authenticated object data (an error is - /// returned if too small) - /// * `incoming_physical_packet_buf` - Buffer containing incoming wire packet - /// (receive() takes ownership) - /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced - /// with the remote peer. Used to check the state of local offers we may currently have or want - /// to put in-flight. - pub fn receive<'a, SendFn: FnMut(&mut [u8]) -> bool>( - &self, - app: &Application, - check_allow_incoming_session: impl FnOnce() -> IncomingSessionAction, - check_accept_session: impl FnOnce(&Application::PublicKey, &[u8], u64) -> (Option<(bool, Application::SessionData)>, bool), - mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, - mut send_unassociated_mtu: usize, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, - remote_address: &impl Hash, - data_buf: &'a mut [u8], - mut incoming_physical_packet_buf: Application::IncomingPacketBuffer, - current_time: i64, - ) -> Result, ReceiveError> { - send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); - let incoming_physical_packet: &mut [u8] = incoming_physical_packet_buf.as_mut(); - let incoming_physical_packet_len = incoming_physical_packet.len(); - if incoming_physical_packet_len < MIN_PACKET_SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - // The first section parses the header and looks up relevant state information. If it's a DATA - // or NOP packet it gets handled right here, otherwise we pull out a set of variables and - // continue to the logic that handles KEX and session control packets. - - let mut assembled_packet = Assembled::new(); // needs to outlive the block below - let mut incoming = None; - let (session, packet_type, fragments) = { - let local_key_id = incoming_physical_packet[0..SESSION_ID_SIZE].try_into().unwrap(); - // `from_ne_bytes` because this id was generated locally. - if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(local_key_id)) { - let session_map = self.0.session_map.read().unwrap(); - if let Some((Some(session), key_index)) = session_map.get(&local_key_id).map(|r| (r.0.upgrade(), r.1 as usize)) { - drop(session_map); - session.header_receive_cipher.decrypt_in_place( - (&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) - .try_into() - .unwrap(), - ); - let (fragment_count, fragment_no, packet_type, incoming_counter, header_nonce) = parse_packet_header(incoming_physical_packet); - // Handle replay protection. - if PACKET_TYPE_RANGE_TRANSPORT.contains(&packet_type) { - // For DOS resistant reply-protection we need to check that the given counter is - // in the window of valid counters immediately. - // But for packets larger than 1 fragment we can't actually record the - // counter as received until we've authenticated the packet. - // So we check the counter window twice, and only update it the second time - // after the packet has been authenticated. - if !session.check_receive_window(incoming_counter) { - // This can occur naturally if packets arrive way out of order, or - // if they are duplicates. - // This can also be naturally triggered if Bob has just successfully - // received the first session key and is reject all of Alice's resends. - // This can also occur if a session was manually expired, but not - // dropped, and the remote party is still sending us data. - return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); - } - if packet_type != PACKET_TYPE_DATA { - // This is a control packet. - if fragment_count != 1 || fragment_no > 0 { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - return receive_control_fragment( - self, - session, - app, - send_to, - packet_type, - incoming_counter, - incoming_physical_packet_buf.as_mut(), - current_time, - ); - } - } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_2 { - // We need to reject fragments marked with this type if they are sent out - // of sequence, since an attacker is able to replay them. - match &session.state.read().unwrap().outgoing_offer { - OfferStateMachine::NoiseXKPattern1or3(handshake_state) => match &handshake_state.offer { - NoiseXKAliceHandshakeState::NoiseXKPattern1 { .. } => { - if incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - } - // This error can occur naturally if Bob's initial reply to Alice had a - // resend that was delayed massively and arrived out of order. - _ => return Err(byzantine_fault!(FaultType::OutOfSequence, true)), - }, - _ => return Err(byzantine_fault!(FaultType::OutOfSequence, false)), - }; - } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_3 { - // This can be triggered if Bob successfully received a session key and - // needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3. - return Err(byzantine_fault!(FaultType::OutOfSequence, true)); - } else { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - // Handle defragmentation. - let fragments = if fragment_count > 1 { - let idx = incoming_counter as usize % session.defrag.len(); - session.defrag[idx].lock().unwrap().assemble( - header_nonce, - incoming_physical_packet_buf, - fragment_no, - fragment_count, - &mut assembled_packet, - ); - if assembled_packet.is_empty() { - // We have not yet authenticated the sender so we do not report - // receiving a packet from them. - return Ok(ReceiveResult::Unassociated); - } else { - assembled_packet.as_ref() - } - } else { - std::array::from_ref(&incoming_physical_packet_buf) - }; - // Handle DATA in the fastest path when we have a session. - if packet_type == PACKET_TYPE_DATA { - let state = session.state.read().unwrap(); - // The error here can occur because the other party is using a brand new - // session key that we have not received yet. - let key = state.cipher_states[key_index] - .as_ref() - .ok_or(byzantine_fault!(FaultType::OutOfSequence, true))?; - let mut c = key.get_receive_cipher(incoming_counter); - c.set_iv(&create_message_nonce(packet_type, incoming_counter)); - - let mut data_len = 0; - - // Decrypt fragments 0..N-1 where N is the number of fragments. - for f in fragments[..(fragments.len() - 1)].iter() { - let f: &[u8] = f.as_ref(); - debug_assert!(f.len() >= HEADER_SIZE); - let current_frag_data_start = data_len; - data_len += f.len() - HEADER_SIZE; - if data_len > data_buf.len() { - return Err(ReceiveError::DataBufferTooSmall); - } - c.decrypt(&f[HEADER_SIZE..], &mut data_buf[current_frag_data_start..data_len]); - } - - // Decrypt final fragment (or only fragment if not fragmented) - let current_frag_data_start = data_len; - let last_fragment = fragments.last().unwrap().as_ref(); - if last_fragment.len() < (HEADER_SIZE + AES_GCM_TAG_SIZE) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - data_len += last_fragment.len() - (HEADER_SIZE + AES_GCM_TAG_SIZE); - if data_len > data_buf.len() { - return Err(ReceiveError::DataBufferTooSmall); - } - let payload_end = last_fragment.len() - AES_GCM_TAG_SIZE; - c.decrypt(&last_fragment[HEADER_SIZE..payload_end], &mut data_buf[current_frag_data_start..data_len]); - - let aead_authentication_ok = c.finish_decrypt(&last_fragment[payload_end..].try_into().unwrap()); - drop(c); - drop(state); - - if !aead_authentication_ok { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - if !session.update_receive_window(incoming_counter) { - // This can be naturally triggered because Bob has just - // successfully received a session key and needs to reject - // all of Alice's resends. - // This can also occur naturally if some part of the outer - // system is duplicating the packets being sent to us. - // We are safely deduplicating them here. - return Err(byzantine_fault!(FaultType::ExpiredCounter, true)); - } - // Packet fully authenticated - return Ok(ReceiveResult::Session(session, SessionEvent::Data(&mut data_buf[..data_len]))); - } else if packet_type == PACKET_TYPE_NOISE_XK_PATTERN_2 { - (Some(session), packet_type, fragments) - } else { - unreachable!() - } - } else { - drop(session_map); - // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 - incoming = self.0.unassociated_handshake_states.get(local_key_id); - if let Some(incoming) = incoming.as_ref() { - Application::PrpDec::new(incoming.header_receive_key.as_ref()).decrypt_in_place( - (&mut incoming_physical_packet[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) - .try_into() - .unwrap(), - ); - let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(incoming_physical_packet); - app.event_log( - LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), - current_time, - ); - if packet_type != PACKET_TYPE_NOISE_XK_PATTERN_3 { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - let fragments = if fragment_count > 1 { - incoming.noise_pattern3_defrag.lock().unwrap().assemble( - header_nonce, - incoming_physical_packet_buf, - fragment_no, - fragment_count, - &mut assembled_packet, - ); - if !assembled_packet.is_empty() { - assembled_packet.as_ref() - } else { - return Ok(ReceiveResult::Unassociated); - } - } else { - std::array::from_ref(&incoming_physical_packet_buf) - }; - // We must guarantee that this incoming handshake is processed once and only - // once. This prevents catastrophic nonce reuse caused by multithreading. - if self.0.unassociated_handshake_states.remove(local_key_id) { - (None, PACKET_TYPE_NOISE_XK_PATTERN_3, fragments) - } else { - return Ok(ReceiveResult::Unassociated); - } - } else { - // This can occur naturally because either Bob's incoming_sessions cache got - // full so Alice's incoming session was dropped, or the session this packet - // was for was dropped by the application. - return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); - } - } - } else { - let (fragment_count, fragment_no, packet_type, _, header_nonce) = parse_packet_header(incoming_physical_packet); - app.event_log( - LogEvent::ReceiveUnassociatedFragment(fragment_count, fragment_no, packet_type), - current_time, - ); - if packet_type != PACKET_TYPE_NOISE_XK_PATTERN_1 && packet_type != PACKET_TYPE_BOB_DOS_CHALLENGE { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - let fragments = if fragment_count > 1 { - self.0.unassociated_defrag_cache.lock().unwrap().assemble( - header_nonce, - remote_address, - incoming_physical_packet_len - HEADER_SIZE, - incoming_physical_packet_buf, - fragment_no, - fragment_count, - Application::RETRY_INTERVAL_MS, - current_time, - &mut assembled_packet, - ); - if !assembled_packet.is_empty() { - assembled_packet.as_ref() - } else { - return Ok(ReceiveResult::Unassociated); - } - } else { - std::array::from_ref(&incoming_physical_packet_buf) - }; - (None, packet_type, fragments) - } - }; - - debug_assert!(!fragments.is_empty()); - debug_assert!(incoming.is_none() || session.is_none()); - - let message = &mut [0u8; MAX_NOISE_HANDSHAKE_SIZE]; - let message_size = assemble_fragments_into::(fragments, message)?; - if message_size < MIN_PACKET_SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - use OfferStateMachine::*; - match packet_type { - PACKET_TYPE_NOISE_XK_PATTERN_1 => { - // Alice (remote) --> Bob (local) - // -> e, es, e1 - app.event_log(LogEvent::ReceiveUncheckedXK1, current_time); - - if session.is_some() || incoming.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if !(NoiseXKPattern1::MIN_SIZE..=NoiseXKPattern1::MAX_SIZE).contains(&message_size) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - // The message id must be the first 8 bytes of the gcm tag. - // This forces the message id to be authenticated along with the entire message. - let p_auth_end = message_size - ChallengeResponse::SIZE; - if message[8..16] != message[p_auth_end - 8..p_auth_end] { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - let p_size = p_auth_end - NoiseXKPattern1::P_ENC_START - AES_GCM_TAG_SIZE; - let total_ratchet_fingerprints = p_size / RATCHET_SIZE; - if p_size % RATCHET_SIZE != 0 || total_ratchet_fingerprints > 2 { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); - if let Some(remote_key_id) = NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.alice_key_id)) { - let sha512 = &mut Application::Hash::new(); - // Let application filter incoming connection attempts by whatever criteria it wants. - // This should ideally prevent ZSSP from wasting time on DDOS attacks. - match check_allow_incoming_session() { - IncomingSessionAction::Allow => {} - IncomingSessionAction::Challenge => { - let response: &ChallengeResponse = byte_array_as_proto_buffer(&message[p_auth_end..message_size]); - let counter = u64::from_be_bytes(response.challenge_counter.try_into().unwrap()); - - sha512.reset(); - let mut hasher = ShaHasher(sha512); - let mut output = [0u8; HASHLEN]; - hasher.0.update(&response.challenge_counter); - remote_address.hash(&mut hasher); - hasher.0.update(&self.0.challenge_salt); - hasher.0.finish(&mut output); - let is_valid = self.check_challenge_window(counter) - && secure_eq(&output[..CHALLENGE_MAC_SIZE], &response.challenge_mac) - && verify_pow::(hasher.0, &message[p_auth_end..message_size]) - && self.update_challenge_window(counter); - app.event_log(LogEvent::ReceiveCheckXK1Challenge(is_valid), current_time); - if !is_valid { - // Alice failed the challenge so issue them a new challenge. - let mut challenge_buffer = [0u8; BobDOSChallenge::SIZE]; - let challenge: &mut BobDOSChallenge = byte_array_as_proto_buffer_mut(&mut challenge_buffer); - challenge.alice_key_id = remote_key_id.get().to_ne_bytes(); - // We attach a monotonically increasing counter value to the challenge - // so it cannot be replayed. - let counter = self.0.challenge_counter.fetch_add(1, Ordering::Relaxed); - challenge.challenge_counter = counter.to_be_bytes(); - - hasher.0.reset(); - hasher.0.update(&counter.to_be_bytes()); - remote_address.hash(&mut hasher); - hasher.0.update(&self.0.challenge_salt); - hasher.0.finish(&mut output); - challenge.challenge_mac.copy_from_slice(&output[..CHALLENGE_MAC_SIZE]); - challenge.prior_challenge_pow = response.challenge_pow; - // We haven't decrypted any of Alice's packet so we don't know the - // header protection cipher. - // For DOS resistance Alice will not accept unencrypted headers directly - // into their session defrag buffer, so we have to send them this reply - // through their incoming sessions cache. - send_with_fragmentation( - &mut send_unassociated_reply, - send_unassociated_mtu, - &mut challenge_buffer, - PACKET_TYPE_BOB_DOS_CHALLENGE, - None, - self.0.rng.lock().unwrap().next_u64(), - None::<&Application::PrpEnc>, - ); - return Ok(ReceiveResult::Unassociated); - } - // Alice succeeded at the challenge so continue to decryption. - } - IncomingSessionAction::Drop => return Ok(ReceiveResult::Rejected), - } - - // Noise process handshake prologue. - let noise_h = mix_hash( - sha512, - &INITIAL_H, - &message[NoiseXKPattern1::PROLOGUE_START..NoiseXKPattern1::PROLOGUE_END], - ); - let noise_h = mix_hash(sha512, &noise_h, self.0.static_keypair.public_key_bytes()); - // Noise process pattern1 e token. - let mut noise_ck = SymmetricState::new(INITIAL_H); - let hmac = &mut Application::HmacHash::new(); - let mut noise_es = Secret::new(); - let noise_e_pattern1 = from_bytes_agreement::(&noise_pattern1.noise_e, &self.0.static_keypair, noise_es.as_mut()) - .ok_or(byzantine_fault!(FaultType::FailedAuthentication, false))?; - let noise_h_e = mix_hash(sha512, &noise_h, &noise_pattern1.noise_e); - noise_ck.mix_key(hmac, &noise_pattern1.noise_e); - // Noise process pattern1 es token. - let noise_k_es = noise_ck.mix_key_initialize_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 e1 token. - let (is_auth, noise_h_ee1) = decrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_e, - packet_type, - 0, - &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], - ); - if !is_auth { - // This could occur naturally if Alice's ApplicationLayer is dynamically - // changing their mtu, which in bad network conditions could clobber their - // resent KEX packet. - // Or maybe Alice randomly generated the same temporary id twice in a row. - // Since these situations are super unlikely to occur we still mark this error - // as unnatural. - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - // Noise process pattern1 payload. - let (is_auth, noise_h_ee1p) = decrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_ee1, - packet_type, - 1, - &mut message[NoiseXKPattern1::P_ENC_START..p_auth_end], - ); - drop(noise_k_es); - if !is_auth { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); - // Get ratchet key. - let noise_pattern1: &NoiseXKPattern1 = byte_array_as_proto_buffer(message); - let mut ratchet_state = RatchetState::Null; - for i in 0..total_ratchet_fingerprints { - match app.restore_by_fingerprint( - (&noise_pattern1.payload[i * RATCHET_SIZE..(i + 1) * RATCHET_SIZE]).try_into().unwrap(), - current_time, - ) { - Ok(RatchetState::Null) | Ok(RatchetState::Empty) => {} - Ok(rs) => { - ratchet_state = rs; - break; - } - Err(e) => return Err(ReceiveError::RatchetIoError(e)), - } - } - if ratchet_state.is_null() { - if app.hello_requires_recognized_ratchet(current_time) { - return Ok(ReceiveResult::Rejected); - } - ratchet_state = RatchetState::Empty; - } - - // Start of Noise XKhfs+psk2 pattern2. - let mut message2 = [0u8; NoiseXKPattern2::SIZE]; - let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); - // Noise process pattern2 e token. - let noise_e_pattern2_secret = Application::KeyPair::generate(&mut self.0.rng.lock().unwrap()); - noise_pattern2.noise_e = *noise_e_pattern2_secret.public_key_bytes(); - let noise_h_ee1pe = mix_hash(sha512, &noise_h_ee1p, &noise_pattern2.noise_e); - noise_ck.mix_key(hmac, &noise_pattern2.noise_e); - // Noise process pattern2 ee token. - let mut noise_ee = Secret::new(); - if !noise_e_pattern2_secret.agree(&noise_e_pattern1, noise_ee.as_mut()) { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - let noise_k_esee = noise_ck.mix_key_initialize_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 ekem1 token. - let (noise_ekem1, noise_ekem1_secret) = pqc_kyber::encapsulate(&noise_pattern1.noise_e1, self.0.rng.lock().unwrap().deref_mut()) - .map_err(|_| byzantine_fault!(FaultType::FailedAuthentication, false)) - .map(|(ct, ekem1)| (ct, Secret(ekem1)))?; - // Alice fully authenticated. - noise_pattern2.noise_ekem1 = noise_ekem1; - let noise_h_ee1peekem1 = encrypt_and_hash::( - sha512, - &noise_k_esee, - &noise_h_ee1pe, - PACKET_TYPE_NOISE_XK_PATTERN_2, - 0, - &mut message2[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], - ); - drop(noise_k_esee); - noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); - drop(noise_ekem1_secret); - // Noise process pattern2 psk token. - let ratchet_key = ratchet_state.key().unwrap(); - let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); - let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); - // Noise process pattern2 payload. - // We try to prevent the id we generate from colliding with another session but - // because we might have handshakes in flight it's impossible to 100% prevent. - // In those exceedingly rare cases we have to drop Alice's session and start over. - let local_key_id = generate_key_id(&self.0.session_map.read().unwrap(), &mut self.0.rng.lock().unwrap()); - let noise_pattern2: &mut NoiseXKPattern2 = byte_array_as_proto_buffer_mut(&mut message2); - noise_pattern2.bob_key_id = local_key_id.get().to_ne_bytes(); - - let noise_h_ee1peekem1pskp = encrypt_and_hash::( - sha512, - &noise_k_eseeekem1psk, - &noise_h_ee1peekem1psk, - PACKET_TYPE_NOISE_XK_PATTERN_2, - 0, - &mut message2[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END], - ); - - app.event_log(LogEvent::ReceiveValidXK1, current_time); - let handshake = Arc::new(NoiseXKBobHandshakeState { - local_key_id, - remote_key_id, - ratchet_state, - noise_h_ee1peekem1pskp, - noise_ck_eseeekem1psk: noise_ck.clone(), - noise_k_eseeekem1psk: noise_k_eseeekem1psk.clone(), - noise_e_secret: noise_e_pattern2_secret, - header_receive_key: header_a2b_key.clone(), - header_send_key: header_b2a_key.clone(), - noise_pattern3_defrag: Mutex::new(Fragged::new()), - }); - self.0.unassociated_handshake_states.insert(local_key_id, handshake, current_time); - - // We put a copy of the gcm tag in the header so Alice can tell this packet apart - // from any other pattern 1 packet we send, without having to make Bob maintain state. - let mut pattern2_id = 0u64.to_ne_bytes(); - pattern2_id[5] = message2[NoiseXKPattern2::P_AUTH_END - 3]; - pattern2_id[6] = message2[NoiseXKPattern2::P_AUTH_END - 2]; - pattern2_id[7] = message2[NoiseXKPattern2::P_AUTH_END - 1]; - send_with_fragmentation( - &mut send_unassociated_reply, - send_unassociated_mtu, - &mut message2, - PACKET_TYPE_NOISE_XK_PATTERN_2, - Some(remote_key_id), - u64::from_be_bytes(pattern2_id), - Some(&Application::PrpEnc::new(header_b2a_key.first_n::())), - ); - - return Ok(ReceiveResult::Unassociated); - } else { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - } - PACKET_TYPE_BOB_DOS_CHALLENGE => { - let message = &mut message[..message_size]; - app.event_log(LogEvent::ReceiveUncheckedDOSChallenge, current_time); - - // We expect Bob to only send this to us through our unassociated defrag cache. - if incoming.is_some() || session.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if message.len() != BobDOSChallenge::SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - let challenge: &BobDOSChallenge = byte_array_as_proto_buffer(message); - - if let Some(local_key_id) = NonZeroU32::new(u32::from_ne_bytes(challenge.alice_key_id)) { - if let Some(session) = self.0.session_map.read().unwrap().get(&local_key_id).and_then(|s| s.0.upgrade()) { - // We don't need to hold the kex lock because we are not transitioning state. - let mut state = session.state.write().unwrap(); - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { noise_message, noise_message_len, .. } = &mut handshake_state.offer { - let response_raw = &mut noise_message[*noise_message_len - ChallengeResponse::SIZE..]; - - let response: &mut ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); - // Only people who know what Alice's prior pow was can convince us to - // compute a new pow. - if challenge.prior_challenge_pow != response.challenge_pow { - // This can occur if Bob sends us multiple challenges and they - // arrive OOO. - return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); - } - response.challenge_counter.copy_from_slice(&challenge.challenge_counter); - response.challenge_mac.copy_from_slice(&challenge.challenge_mac); - let mut pow = self.0.rng.lock().unwrap().next_u64(); - let sha512 = &mut Application::Hash::new(); - loop { - let response: &mut ChallengeResponse = byte_array_as_proto_buffer_mut(response_raw); - response.challenge_pow.copy_from_slice(&pow.to_be_bytes()); - if verify_pow::(sha512, response_raw) { - break; - } - pow = pow.wrapping_add(1); - } - - app.event_log(LogEvent::ReceiveValidDOSChallenge(&session), current_time); - return Ok(ReceiveResult::Unassociated); - } else { - // This could happen if Bob challenges Alice, but their challenge packet - // gets massively delayed. - return Err(byzantine_fault!(FaultType::OutOfSequence, true)); - } - } else { - // This could happen if Bob challenges Alice, but their challenge packet - // gets massively delayed. - return Err(byzantine_fault!(FaultType::OutOfSequence, true)); - } - } else { - // This can occur naturally if Alice's session was dropped. - return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); - } - } else { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - } - PACKET_TYPE_NOISE_XK_PATTERN_2 => { - // Bob (remote) --> Alice (local) - // <- e, ee, ekem1, psk - let message = &mut message[..message_size]; - app.event_log(LogEvent::ReceiveUncheckedXK2, current_time); - - if incoming.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if message.len() != NoiseXKPattern2::SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - let session = session.ok_or(byzantine_fault!(FaultType::UnknownLocalKeyId, false))?; - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); - - if let NoiseXKPattern1or3(handshake_state) = &state.outgoing_offer { - if let NoiseXKAliceHandshakeState::NoiseXKPattern1 { - noise_h_ee1p, noise_e_secret, noise_e1_secret, noise_ck_es, .. - } = &handshake_state.offer - { - let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); - // Authenticate header counter. - if noise_pattern2.header[13..16] != noise_pattern2.p_gcm_tag[13..16] { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - - // Noise process pattern2 e token. - let mut noise_ee = Secret::new(); - if let Some(noise_e_pattern2) = - from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) - { - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let mut noise_ck = noise_ck_es.clone(); - let noise_h_ee1pe = mix_hash(sha512, noise_h_ee1p, noise_e_pattern2.as_bytes()); - noise_ck.mix_key(hmac, noise_e_pattern2.as_bytes()); - // Noise process pattern2 ee token. - let noise_k_esee = noise_ck.mix_key_initialize_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 ekem1 token. - let (is_auth, noise_h_ee1peekem1) = decrypt_and_hash::( - sha512, - &noise_k_esee, - &noise_h_ee1pe, - packet_type, - 0, - &mut message[NoiseXKPattern2::EKEM1_ENC_START..NoiseXKPattern2::P_ENC_START], - ); - let noise_pattern2: &NoiseXKPattern2 = byte_array_as_proto_buffer(message); - let noise_ekem1_secret = pqc_kyber::decapsulate(&noise_pattern2.noise_ekem1, noise_e1_secret.as_ref()).map(Secret); - if let Some(Ok(noise_ekem1_secret)) = is_auth.then_some(noise_ekem1_secret) { - noise_ck.mix_key(hmac, noise_ekem1_secret.as_ref()); - drop(noise_ekem1_secret); - - // We attempt to decrypt the payload at most three times. First two times with - // the ratchet key Alice remembers, and final time with a ratchet - // key of zero if Alice allows ratchet downgrades. - // The following code is not constant time, meaning we leak to an - // attacker whether or not we downgraded. - // We don't currently consider this sensitive enough information to hide. - let mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState, Secret, [u8; 64])> { - // Check for which ratchet key Bob wants to use. - let mut noise_ck = noise_ck.clone(); - let mut payload = [0u8; NoiseXKPattern2::P_AUTH_END - NoiseXKPattern2::P_ENC_START]; - payload.copy_from_slice(&message[NoiseXKPattern2::P_ENC_START..NoiseXKPattern2::P_AUTH_END]); - // Noise process pattern2 psk token. - let (temp_h, noise_k_eseeekem1psk) = noise_ck.mix_key_and_hash_initialize_key(hmac, ratchet_key); - let noise_h_ee1peekem1psk = mix_hash(sha512, &noise_h_ee1peekem1, &temp_h); - // Noise process pattern2 payload. - let (is_auth, noise_h_ee1peekem1pskp) = decrypt_and_hash::( - sha512, - &noise_k_eseeekem1psk, - &noise_h_ee1peekem1psk, - packet_type, - 0, - &mut payload, - ); - if is_auth { - let key_id = NonZeroU32::new(u32::from_ne_bytes( - (&payload[..NoiseXKPattern2::P_AUTH_START - NoiseXKPattern2::P_ENC_START]) - .try_into() - .unwrap(), - )); - key_id.map(|kid| (kid, noise_ck, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) - } else { - None - } - }; - // Check first key. - let mut ratchet_i = 0; - let mut result = None; - let mut chain_len = 0; - if let Some(key) = state.ratchet_states[0].key() { - chain_len = state.ratchet_states[0].chain_len(); - result = test_ratchet_key(key); - } - // Check second key. - if result.is_none() { - ratchet_i = 1; - if let Some(key) = state.ratchet_states[1].key() { - chain_len = state.ratchet_states[1].chain_len(); - result = test_ratchet_key(key); - } - } - // Check zero key. - if result.is_none() && !app.initiator_disallows_downgrade(&session, current_time) { - chain_len = 0; - result = test_ratchet_key(&[0u8; RATCHET_SIZE]); - if result.is_some() { - // TODO: add some kind of warning callback or signal. - } - } - - if let Some((remote_key_id, mut noise_ck, noise_k_eseeekem1psk, noise_h_ee1peekem1pskp)) = result { - // Start of Noise XKhfs+psk2 pattern3. - let mut message3 = [0u8; NoiseXKPattern3::MAX_SIZE]; - // Noise process pattern3 s token. - let mut noise_se = Secret::new(); - if self.0.static_keypair.agree(&noise_e_pattern2, noise_se.as_mut()) { - let payload = handshake_state.alice_identity_blob.as_ref(); - // Packet fully authenticated. - let s_enc_start = HEADER_SIZE; - let s_auth_start = s_enc_start + P384_PUBLIC_KEY_SIZE; - let p_enc_start = s_auth_start + AES_GCM_TAG_SIZE; - let p_auth_start = p_enc_start + payload.len(); - let p_auth_end = p_auth_start + AES_GCM_TAG_SIZE; - let message3_len = p_auth_end; - - message3[s_enc_start..s_auth_start].copy_from_slice(self.0.static_keypair.public_key_bytes()); - let noise_h_ee1peekem1pskps = encrypt_and_hash::( - sha512, - &noise_k_eseeekem1psk, - &noise_h_ee1peekem1pskp, - PACKET_TYPE_NOISE_XK_PATTERN_3, - 1, - &mut message3[s_enc_start..p_enc_start], - ); - drop(noise_k_eseeekem1psk); - // Noise process pattern3 se token. - let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern3 payload token. - message3[p_enc_start..p_auth_start].copy_from_slice(payload); - let noise_h_ee1peekem1pskpsp = encrypt_and_hash::( - sha512, - &noise_k_eseeekem1pskse, - &noise_h_ee1peekem1pskps, - PACKET_TYPE_NOISE_XK_PATTERN_3, - 0, - &mut message3[p_enc_start..p_auth_end], - ); - drop(noise_k_eseeekem1pskse); - // Alice finished Noise XKhfs+psk2 handshake. - // Transition offer state machine to the NoiseXKPattern3 state. - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); - let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(chain_len + 1).unwrap()); - - let ratchet_to_preserve = &state.ratchet_states[ratchet_i]; - let result = app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&new_ratchet_state, ratchet_to_preserve], - current_time, - ); - if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); - } - - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); - - let local_key_id = handshake_state.local_key_id; - drop(state); - let mut state = session.state.write().unwrap(); - session - .kex_send_cipher - .lock() - .unwrap() - .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); - session - .kex_receive_cipher - .lock() - .unwrap() - .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); - state.ratchet_states[1] = state.ratchet_states[ratchet_i].clone(); - state.ratchet_states[0] = new_ratchet_state; - - state.cipher_states[0].replace(SessionKey::new( - hmac, - noise_ck, - local_key_id, - remote_key_id, - INIT_COUNTER, - false, - )); - debug_assert!(state.cipher_states[1].is_none()); - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - handshake_state.next_retry_time = - AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); - handshake_state.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); - handshake_state.offer = NoiseXKAliceHandshakeState::NoiseXKPattern3 { - noise_message: message3, - noise_message_len: p_auth_end, - }; - } - drop(state); - drop(kex_lock); - - if let Some((mut send, mut mtu)) = send_to(&session) { - mtu = mtu.max(MIN_TRANSPORT_MTU); - send_with_fragmentation( - &mut send, - mtu, - &mut message3[..message3_len], - PACKET_TYPE_NOISE_XK_PATTERN_3, - Some(remote_key_id), - 0, - Some(&session.header_send_cipher), - ); - } - app.event_log(LogEvent::ReceiveValidXK2(&session), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); - } - } - } - } - // Bob failed authentication so we must restart our offer according to Noise. - // We restart the offer instead of dropping the session to defend against DOS. - drop(state); - let mut state = session.state.write().unwrap(); - let ratchet_state = state.ratchet_states.clone(); - if let NoiseXKPattern1or3(handshake_state) = &mut state.outgoing_offer { - if !handshake_state.reinitialize( - &session, - &ratchet_state, - &mut self.0.session_map.write().unwrap(), - &mut self.0.rng.lock().unwrap(), - current_time, - ) { - session.expire() - } - } - drop(state); - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } else { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - } else { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - } - PACKET_TYPE_NOISE_XK_PATTERN_3 => { - // Alice (remote) --> Bob (local) - // -> s, se - let message = &mut message[..message_size]; - app.event_log(LogEvent::ReceiveUncheckedXK3, current_time); - - if session.is_some() { - return Err(byzantine_fault!(FaultType::OutOfSequence, false)); - } - if message.len() < NoiseXKPattern3::MIN_SIZE || message.len() > NoiseXKPattern3::MAX_SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - // The code above guarantees to us that each `incoming` handshake state that reaches - // this point will be strictly unique, even for the same remote peer. - // This property is strictly necessary to prevent catastrophic nonce reuse due to - // two session being created with the same set of keys. - let handshake_state = incoming.ok_or(byzantine_fault!(FaultType::UnknownLocalKeyId, false))?; - let s_enc_start = HEADER_SIZE; - - let s_auth_start = s_enc_start + P384_PUBLIC_KEY_SIZE; - let p_enc_start = s_auth_start + AES_GCM_TAG_SIZE; - let p_auth_end = message.len(); - let p_auth_start = p_auth_end - AES_GCM_TAG_SIZE; - - if !(p_enc_start <= p_auth_start) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - // Do not read from the message before this point, otherwise an array out of bounds - // error is possible. - // Noise process pattern3 s token. - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let (is_auth, noise_h_ee1peekem1pskps) = decrypt_and_hash::( - sha512, - &handshake_state.noise_k_eseeekem1psk, - &handshake_state.noise_h_ee1peekem1pskp, - packet_type, - 1, - &mut message[s_enc_start..p_enc_start], - ); - if !is_auth { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - // Noise process pattern3 se token. - let mut noise_se = Secret::new(); - if let Some(remote_s_public_key) = - from_bytes_agreement::(&message[s_enc_start..s_auth_start], &handshake_state.noise_e_secret, noise_se.as_mut()) - { - let mut noise_ck = handshake_state.noise_ck_eseeekem1psk.clone(); - let noise_k_eseeekem1pskse = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern3 payload. - let (is_auth, noise_h_ee1peekem1pskpsp) = decrypt_and_hash::( - sha512, - &noise_k_eseeekem1pskse, - &noise_h_ee1peekem1pskps, - packet_type, - 0, - &mut message[p_enc_start..p_auth_end], - ); - drop(noise_k_eseeekem1pskse); - if !is_auth { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - // Bob finished Noise XKhfs+psk2 handshake. - let header_send_cipher = Application::PrpEnc::new(handshake_state.header_send_key.as_ref()); - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_ee1peekem1pskpsp); - let mut send_reject = || { - // We just used a counter with this key, but we are not storing - // the fact we used it in memory. This is currently ok because the - // handshake is being dropped, so nonce reuse can't happen. - let (mut fragment, len) = encrypt_control( - &mut Application::AeadEnc::new(kex_key_b2a.as_ref()), - &header_send_cipher, - PACKET_TYPE_SESSION_REJECTED, - INIT_COUNTER, - handshake_state.remote_key_id.get(), - &[], - ); - send_unassociated_reply(&mut fragment[..len]); - }; - - let (responder_disallows_downgrade, responder_silently_rejects) = check_accept_session( - &remote_s_public_key, - &message[p_enc_start..p_auth_start], - handshake_state.ratchet_state.chain_len(), - ); - if let Some((responder_disallows_downgrade, application_data)) = responder_disallows_downgrade { - let result = app.restore_by_identity(&remote_s_public_key, &application_data, current_time); - match result { - Ok(true_ratchet_states) => { - let mut has_match = false; - for rs in &true_ratchet_states { - if !rs.is_null() { - has_match |= &handshake_state.ratchet_state == rs; - } - } - if !has_match { - if !responder_disallows_downgrade && handshake_state.ratchet_state.is_empty() { - // TODO: add some kind of warning callback or signal. - } else { - if !responder_silently_rejects { - send_reject(); - } - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - } - - let mut noise_kk_ss = Secret::new(); - if !self.0.static_keypair.agree(&remote_s_public_key, noise_kk_ss.as_mut()) { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - let noise_kk_local_init_h = mix_hash(sha512, &INITIAL_H_REKEY, self.0.static_keypair.public_key_bytes()); - let noise_kk_local_init_h = mix_hash(sha512, &noise_kk_local_init_h, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &INITIAL_H_REKEY, remote_s_public_key.as_bytes()); - let noise_kk_remote_init_h = mix_hash(sha512, &noise_kk_remote_init_h, self.0.static_keypair.public_key_bytes()); - - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_ee1peekem1pskpsp); - // We must make sure the ratchet key is saved before we transition. - let new_ratchet_state = - RatchetState::new_nonempty(rk, rf, NonZeroU64::new(handshake_state.ratchet_state.chain_len() + 1).unwrap()); - let result = app.save_ratchet_state( - &remote_s_public_key, - &application_data, - [&true_ratchet_states[0], &true_ratchet_states[1]], - [&new_ratchet_state, &RatchetState::Null], - current_time, - ); - if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); - } - - let mut session_queue = self.0.session_queue.lock().unwrap(); - let queue_idx = session_queue.reserve_index(); - let session = Arc::new(Session { - context: Arc::downgrade(&self.0), - queue_idx, - application_data, - remote_static_key: remote_s_public_key, - send_counter: AtomicU64::new(INIT_COUNTER), - session_has_expired: AtomicBool::new(false), - counter_antireplay_window: std::array::from_fn(|_| AtomicU64::new(0)), - state_machine_lock: Mutex::new(()), - state: RwLock::new(SessionMutableState { - ratchet_states: [new_ratchet_state.clone(), RatchetState::Null], - cipher_states: [ - Some(SessionKey::new( - hmac, - noise_ck, - handshake_state.local_key_id, - handshake_state.remote_key_id, - INIT_COUNTER, - true, - )), - None, - ], - current_key: 0, - outgoing_offer: KeyConfirm { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - }, - }), - header_receive_cipher: Application::PrpDec::new(handshake_state.header_receive_key.as_ref()), - header_send_cipher, - kex_send_cipher: Mutex::new(Some(Application::AeadEnc::new(kex_key_b2a.as_ref()))), - kex_receive_cipher: Mutex::new(Some(Application::AeadDec::new(kex_key_a2b.as_ref()))), - noise_kk_ss, - noise_kk_local_init_h, - noise_kk_remote_init_h, - defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - was_bob: true, - }); - let timer = Reverse(current_time.saturating_add(Application::RETRY_INTERVAL_MS)); - session_queue.push_reserved(queue_idx, Arc::downgrade(&session), timer); - drop(session_queue); - // There is the miniscule possibility this key id is already - // in use, in which case we have to drop this session like - // nothing ever happened. - let mut session_map = self.0.session_map.write().unwrap(); - if let std::collections::hash_map::Entry::Vacant(e) = session_map.entry(handshake_state.local_key_id) { - e.insert((Arc::downgrade(&session), false)); - drop(session_map); - let _ = - session.send_control(&session.state.read().unwrap(), send_unassociated_reply, PACKET_TYPE_KEY_CONFIRM, &[]); - - app.event_log(LogEvent::ReceiveValidXK3(&session.application_data), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::NewSession)); - } else { - // This can occur if we accidentally generate a key id collision. - // There is an extremely short amount of time during which - // another session can steal this session's id, we'll have to - // restart the handshake in this case. - return Err(byzantine_fault!(FaultType::UnknownLocalKeyId, true)); - } - } - Err(e) => { - return Err(ReceiveError::RatchetIoError(e)); - } - } - } else { - if !responder_silently_rejects { - send_reject(); - } - return Ok(ReceiveResult::Rejected); - } - } else { - return Err(byzantine_fault!(FaultType::FailedAuthentication, false)); - } - } - _ => return Err(byzantine_fault!(FaultType::InvalidPacket, false)), - } - } - /// Helper function for sending the empty string over the session. Useful for keep-alives. - /// - /// * `session` - The session to send to - /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a - /// slice of `data` - /// * `current_time` - Current time in milliseconds - pub fn send_empty(&self, session: &Arc>, send: impl FnMut(&mut [u8]) -> bool, current_time: i64) -> Result<(), SendError> { - self.send(session, send, &mut [0u8; MIN_TRANSPORT_MTU], &[], current_time) - } - /// Send data over the session. - /// - /// * `session` - The session to send to - /// * `send` - Function to call to send physical packet(s); the buffer passed to `send` is a - /// slice of `data` - /// * `mtu_sized_buffer` - A writable work buffer whose size equals the MTU - /// * `data` - Data to send - /// * `current_time` - Current time in milliseconds - pub fn send( - &self, - session: &Arc>, - mut send: impl FnMut(&mut [u8]) -> bool, - mtu_sized_buffer: &mut [u8], - mut data: &[u8], - current_time: i64, - ) -> Result<(), SendError> { - if mtu_sized_buffer.len() < MIN_TRANSPORT_MTU { - return Err(SendError::InvalidParameter); - } - let state = session.state.read().unwrap(); - let key = state.cipher_states[state.current_key].as_ref().ok_or(SendError::SessionNotEstablished)?; - let counter = session.get_next_outgoing_counter()?; - - let mut c = key.get_send_cipher(counter)?; - c.set_iv(&create_message_nonce(PACKET_TYPE_DATA, counter)); - - let fragment_max_chunk_size = mtu_sized_buffer.len() - HEADER_SIZE; - let fragment_count = (data.len() + AES_GCM_TAG_SIZE + (fragment_max_chunk_size - 1)) / fragment_max_chunk_size; - if fragment_count > MAX_FRAGMENTS { - return Err(SendError::DataTooLarge); - } - let last_fragment_no = fragment_count - 1; - - for fragment_no in 0..fragment_count { - let chunk_size = fragment_max_chunk_size.min(data.len()); - let mut fragment_size = chunk_size + HEADER_SIZE; - - set_packet_header( - mtu_sized_buffer, - fragment_count as u8, - fragment_no as u8, - PACKET_TYPE_DATA, - key.remote_key_id.get(), - counter, - ); - - c.encrypt(&data[..chunk_size], &mut mtu_sized_buffer[HEADER_SIZE..fragment_size]); - data = &data[chunk_size..]; - - if fragment_no == last_fragment_no { - debug_assert!(data.is_empty()); - let tagged_fragment_size = fragment_size + AES_GCM_TAG_SIZE; - c.finish_encrypt((&mut mtu_sized_buffer[fragment_size..tagged_fragment_size]).try_into().unwrap()); - fragment_size = tagged_fragment_size; - } - - session.header_send_cipher.encrypt_in_place( - (&mut mtu_sized_buffer[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]) - .try_into() - .unwrap(), - ); - if !send(&mut mtu_sized_buffer[..fragment_size]) { - break; - } - } - drop(c); - if counter >= key.rekey_at_counter { - if let OfferStateMachine::Normal { .. } = &state.outgoing_offer { - drop(state); - if let Ok(timer) = initiate_rekey(&self.0, session, send, current_time) { - self.0.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timer)); - } - } - } - Ok(()) - } - /// Update the challenge window, returning true if the challenge is still valid. - fn check_challenge_window(&self, counter: u64) -> bool { - let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.load(Ordering::Relaxed); - prev_counter < counter - } - /// Update the challenge window, returning true if the challenge is still valid. - fn update_challenge_window(&self, counter: u64) -> bool { - let slot = &self.0.challenge_antireplay_window[(counter as usize) % self.0.challenge_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); - prev_counter < counter - } -} -/// Initiate the rekeying protocol. This session will now begin attempting to rekey this session -/// with its peer, if it was not already. -fn initiate_rekey( - context: &Arc>, - session: &Arc>, - send: impl FnOnce(&mut [u8]) -> bool, - current_time: i64, -) -> Result { - let mut message = [0u8; NoiseKKPattern1or2::SIZE]; - - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); - // We may only attempt to rekey if we are not already doing so. - match &state.outgoing_offer { - OfferStateMachine::Normal { .. } => (), - _ => return Err(()), - } - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - // Start of Noise KKpsk0 pattern1. - // Noise process pattern1 psk0 token. - let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_states[0].key().unwrap()); - let noise_h_psk = mix_hash(sha512, &session.noise_kk_local_init_h, &noise_temp_h); - // Noise process pattern1 e token. - let noise_e_secret = Application::KeyPair::generate(&mut context.rng.lock().unwrap()); - let noise_h_pske = mix_hash(sha512, &noise_h_psk, noise_e_secret.public_key_bytes()); - noise_ck.mix_key(hmac, noise_e_secret.public_key_bytes()); - - let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); - noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); - // Noise process pattern1 es token. - let mut noise_es = Secret::new(); - if !noise_e_secret.agree(&session.remote_static_key, noise_es.as_mut()) { - return Err(()); - } - noise_ck.mix_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 ss token. - let noise_k_pskesss = noise_ck.mix_key_initialize_key(hmac, session.noise_kk_ss.as_ref()); - // Noise process pattern1 payload token. - let mut session_map = context.session_map.write().unwrap(); - let new_key_id = generate_key_id(&session_map, &mut context.rng.lock().unwrap()); - let next_key_index = state.current_key ^ 1; - session_map.insert(new_key_id, (Arc::downgrade(session), next_key_index > 0)); - drop(session_map); - - let noise_pattern1: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message); - noise_pattern1.key_id = new_key_id.get().to_ne_bytes(); - let noise_h_pskep = encrypt_and_hash::( - sha512, - &noise_k_pskesss, - &noise_h_pske, - PACKET_TYPE_NOISE_KK_PATTERN_1, - 0, - &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - drop(noise_k_pskesss); - - drop(state); - let mut state = session.state.write().unwrap(); - state.outgoing_offer = OfferStateMachine::NoiseKKPattern1 { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - new_key_id, - noise_e_secret, - noise_message: message.clone(), - noise_h_pskep, - noise_ck: noise_ck.clone(), - }; - drop(state); - drop(kex_lock); - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_NOISE_KK_PATTERN_1, &message); - Ok(current_time.saturating_add(Application::RETRY_INTERVAL_MS)) -} -fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mut [u8]) -> bool>( - context: &Context, - session: Arc>, - app: &Application, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, - packet_type: u8, - counter: u64, - fragment: &mut [u8], - current_time: i64, -) -> Result, ReceiveError> { - let kex_lock = session.state_machine_lock.lock().unwrap(); - let state = session.state.read().unwrap(); - let mut c = session.kex_receive_cipher.lock().unwrap(); - let message = decrypt_control( - c.as_mut().ok_or(byzantine_fault!(FaultType::OutOfSequence, false))?, - packet_type, - counter, - fragment, - )?; - drop(c); - session.update_receive_window(counter); - use OfferStateMachine::*; - return match packet_type { - PACKET_TYPE_SESSION_REJECTED => { - if let NoiseXKPattern1or3(_) = &state.outgoing_offer { - drop(state); - let mut state = session.state.write().unwrap(); - state.outgoing_offer = OfferStateMachine::Null; - drop(state); - drop(kex_lock); - Ok(ReceiveResult::Session(session, SessionEvent::Rejected)) - } else { - // This can occur naturally because of control packet resends. - Err(byzantine_fault!(FaultType::OutOfSequence, true)) - } - } - PACKET_TYPE_KEY_CONFIRM => { - drop(state); - app.event_log(LogEvent::ReceiveValidKeyConfirm(&session), current_time); - let mut state = session.state.write().unwrap(); - // We only want to stop sending NoiseKKPattern2 offers when the latest derived - // key is confirmed. And we only want to do that once. - let (used_latest_key, try_delete, ret) = match &state.outgoing_offer { - NoiseKKPattern2 { .. } => (true, true, SessionEvent::Control), - NoiseXKPattern1or3(handshake_state) => { - if let NoiseXKAliceHandshakeState::NoiseXKPattern3 { .. } = &handshake_state.offer { - (true, true, SessionEvent::Established) - } else { - (false, false, SessionEvent::Control) - } - } - Null => (false, false, SessionEvent::Control), - _ => (true, false, SessionEvent::Control), - }; - if try_delete { - let result = if !state.ratchet_states[1].is_null() { - app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&state.ratchet_states[0], &RatchetState::Null], - current_time, - ) - } else { - Ok(()) - }; - if let Err(e) = result { - return Err(ReceiveError::RatchetIoError(e)); - } - if let NoiseKKPattern2 { kex_send_key, .. } = &state.outgoing_offer { - session - .kex_send_cipher - .lock() - .unwrap() - .replace(Application::AeadEnc::new(kex_send_key.as_ref())); - } - state.ratchet_states[1] = RatchetState::Null; - state.current_key ^= 1; - state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); - } - drop(state); - drop(kex_lock); - if used_latest_key { - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_ACK, &[]); - } - } - Ok(ReceiveResult::Session(session, ret)) - } - PACKET_TYPE_ACK => { - if let KeyConfirm { .. } = &state.outgoing_offer { - drop(state); - app.event_log(LogEvent::ReceiveValidAck(&session), current_time); - let mut state = session.state.write().unwrap(); - // Check if we should end any current offers and transition back to Normal state - state.outgoing_offer = new_normal_state(context.0.rng.lock().unwrap().next_u64(), current_time); - drop(kex_lock); - drop(state); - Ok(ReceiveResult::Session(session, SessionEvent::Control)) - } else { - // This can occur naturally because of control packet resends. - Err(byzantine_fault!(FaultType::OutOfSequence, true)) - } - } - PACKET_TYPE_NOISE_KK_PATTERN_1 => { - app.event_log(LogEvent::ReceiveUncheckedKK1, current_time); - let message = &mut message[..NoiseKKPattern1or2::SIZE]; - let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - // We need the following operation to be atomic with the change of offer type - let (should_rekey_as_bob, chosen_id) = match &state.outgoing_offer { - // Check rekey rate limits. - Normal { .. } => (true, None), - // In the following situation, both parties are in state NoiseKKPattern1, - // we need to deterministically allow only one of them to transition to - // NoiseKKPattern2. - NoiseKKPattern1 { new_key_id, .. } => (session.was_bob, Some(*new_key_id)), - _ => (false, None), - }; - if !should_rekey_as_bob { - // This can be triggered if both parties attempt rekeying simultaneously, or if the - // remote party sent us a duplicate rekey request. - // The code above handles this case and only lets one party through to rekeying. - drop(state); - drop(kex_lock); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); - } - // Noise process pattern1 psk0 token. - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let mut noise_ck = SymmetricState::new(INITIAL_H_REKEY); - let noise_temp_h = noise_ck.mix_key_and_hash(hmac, state.ratchet_states[0].key().unwrap()); - let noise_h_psk = mix_hash(sha512, &session.noise_kk_remote_init_h, &noise_temp_h); - // Noise process pattern1 e token. - // Get public key validation out of the way early - let mut noise_es = Secret::new(); - let mut noise_ee = Secret::new(); - let mut noise_se = Secret::new(); - if let Some(alice_e) = from_bytes_agreement::(&noise_pattern1.noise_e, &context.0.static_keypair, noise_es.as_mut()) { - let bob_e_secret = Application::KeyPair::generate(&mut context.0.rng.lock().unwrap()); - if bob_e_secret.agree(&alice_e, noise_ee.as_mut()) && bob_e_secret.agree(&session.remote_static_key, noise_se.as_mut()) { - let noise_h_pske = mix_hash(sha512, &noise_h_psk, alice_e.as_bytes()); - noise_ck.mix_key(hmac, alice_e.as_bytes()); - // Noise process pattern1 es token. - noise_ck.mix_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 ss token. - let noise_k_pskesss = noise_ck.mix_key_initialize_key(hmac, session.noise_kk_ss.as_ref()); - - // Noise process pattern1 payload. - let (is_auth, noise_h_pskep) = decrypt_and_hash::( - sha512, - &noise_k_pskesss, - &noise_h_pske, - packet_type, - 0, - &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - let noise_pattern1: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern1.key_id))) { - // Alice fully authenticated. - // Start of Noise KKpsk0 pattern2. - // Noise process pattern2 e token. - let noise_h_pskepe = mix_hash(sha512, &noise_h_pskep, bob_e_secret.public_key_bytes()); - noise_ck.mix_key(hmac, bob_e_secret.public_key_bytes()); - // Noise process pattern2 ee token. - noise_ck.mix_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 se token. - let noise_k_pskessseese = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern2 payload. - let mut message2 = [0u8; NoiseKKPattern1or2::SIZE]; - let noise_pattern2: &mut NoiseKKPattern1or2 = byte_array_as_proto_buffer_mut(&mut message2); - noise_pattern2.noise_e = *bob_e_secret.public_key_bytes(); - let mut session_map = context.0.session_map.write().unwrap(); - // If we already generated a new key id mapping reuse it. - let new_key_id = chosen_id.unwrap_or_else(|| generate_key_id(&session_map, &mut context.0.rng.lock().unwrap())); - noise_pattern2.key_id = new_key_id.get().to_ne_bytes(); - - let noise_h_pskepep = encrypt_and_hash::( - sha512, - &noise_k_pskessseese, - &noise_h_pskepe, - PACKET_TYPE_NOISE_KK_PATTERN_2, - 0, - &mut message2[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - drop(noise_k_pskessseese); - // Bob finished Noise KKpsk0 handshake. - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); - let new_ratchet_state = RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); - let result = app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&new_ratchet_state, &state.ratchet_states[0]], - current_time, - ); - if let Err(e) = result { - drop(state); - drop(kex_lock); - return Err(ReceiveError::RatchetIoError(e)); - } - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); - // The new "Bob" doesn't know yet if Alice has received the new key, so the - // new key is recorded as the "alt" (key_index ^ 1) but the current key is - // not advanced yet. - let next_key_index = state.current_key ^ 1; - session_map.insert(new_key_id, (Arc::downgrade(&session), next_key_index > 0)); - if let Some(pre_id) = state.cipher_states[next_key_index].as_ref().map(|k| k.local_key_id) { - session_map.remove(&pre_id); - } - drop(session_map); - drop(state); - let mut state = session.state.write().unwrap(); - let current_counter = session.send_counter.load(Ordering::Relaxed); - session - .kex_receive_cipher - .lock() - .unwrap() - .replace(Application::AeadDec::new(kex_key_a2b.as_ref())); - state.ratchet_states[1] = state.ratchet_states[0].clone(); - state.ratchet_states[0] = new_ratchet_state.clone(); - - state.cipher_states[next_key_index].replace(SessionKey::new( - hmac, - noise_ck, - new_key_id, - remote_key_id, - current_counter, - true, - )); - let timer = current_time.saturating_add(Application::RETRY_INTERVAL_MS); - state.outgoing_offer = NoiseKKPattern2 { - next_retry_time: AtomicI64::new(timer), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - noise_message: message2, - kex_send_key: kex_key_b2a.clone(), - }; - drop(state); - drop(kex_lock); - context.0.session_queue.lock().unwrap().change_priority(session.queue_idx, Reverse(timer)); - - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_NOISE_KK_PATTERN_2, &message2); - } - app.event_log(LogEvent::ReceiveValidKK1(&session), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); - } - } - } - Err(byzantine_fault!(FaultType::FailedAuthentication, false)) - } - PACKET_TYPE_NOISE_KK_PATTERN_2 => { - app.event_log(LogEvent::ReceiveUncheckedKK2, current_time); - let message = &mut message[..NoiseKKPattern1or2::SIZE]; - let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - - if let NoiseKKPattern1 { new_key_id, noise_e_secret, noise_ck, noise_h_pskep, .. } = &state.outgoing_offer { - // Noise process pattern2 e token. - let mut noise_ee = Secret::new(); - let mut noise_se = Secret::new(); - if let Some(bob_e) = from_bytes_agreement::(&noise_pattern2.noise_e, noise_e_secret, noise_ee.as_mut()) { - if context.0.static_keypair.agree(&bob_e, noise_se.as_mut()) { - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - let mut noise_ck = noise_ck.clone(); - let noise_h_pskepe = mix_hash(sha512, noise_h_pskep, bob_e.as_bytes()); - noise_ck.mix_key(hmac, bob_e.as_bytes()); - // Noise process pattern2 ee token. - noise_ck.mix_key(hmac, noise_ee.as_ref()); - drop(noise_ee); - // Noise process pattern2 se token. - let noise_k_pskessseese = noise_ck.mix_key_initialize_key(hmac, noise_se.as_ref()); - drop(noise_se); - // Noise process pattern2 payload. - let (is_auth, noise_h_pskepep) = decrypt_and_hash::( - sha512, - &noise_k_pskessseese, - &noise_h_pskepe, - packet_type, - 0, - &mut message[NoiseKKPattern1or2::ENC_START..NoiseKKPattern1or2::AUTH_END], - ); - let noise_pattern2: &NoiseKKPattern1or2 = byte_array_as_proto_buffer(message); - if let (true, Some(remote_key_id)) = (is_auth, NonZeroU32::new(u32::from_ne_bytes(noise_pattern2.key_id))) { - // Bob fully authenticated. - // Alice finished Noise KKpsk0 handshake. - let (rk, rf) = noise_ck.get_ask2(hmac, LABEL_RATCHET_STATE, &noise_h_pskepep); - let new_ratchet_state = - RatchetState::new_nonempty(rk, rf, NonZeroU64::new(state.ratchet_states[0].chain_len() + 1).unwrap()); - let result = app.save_ratchet_state( - &session.remote_static_key, - &session.application_data, - [&state.ratchet_states[0], &state.ratchet_states[1]], - [&new_ratchet_state, &RatchetState::Null], - current_time, - ); - if let Err(e) = result { - drop(state); - drop(kex_lock); - return Err(ReceiveError::RatchetIoError(e)); - } - let (kex_key_b2a, kex_key_a2b) = noise_ck.get_ask2(hmac, LABEL_KEX_KEY, &noise_h_pskepep); - - let new_key_id = *new_key_id; - drop(state); - let mut state = session.state.write().unwrap(); - let next_key_index = state.current_key ^ 1; - state.current_key = next_key_index; - if let Some(key) = state.cipher_states[next_key_index].as_ref() { - context.0.session_map.write().unwrap().remove(&key.local_key_id); - } - session - .kex_receive_cipher - .lock() - .unwrap() - .replace(Application::AeadDec::new(kex_key_b2a.as_ref())); - session - .kex_send_cipher - .lock() - .unwrap() - .replace(Application::AeadEnc::new(kex_key_a2b.as_ref())); - state.ratchet_states[1] = RatchetState::Null; - state.ratchet_states[0] = new_ratchet_state.clone(); - - state.cipher_states[next_key_index].replace(SessionKey::new( - hmac, - noise_ck, - new_key_id, - remote_key_id, - session.send_counter.load(Ordering::Relaxed), - false, - )); - state.outgoing_offer = KeyConfirm { - next_retry_time: AtomicI64::new(current_time.saturating_add(Application::RETRY_INTERVAL_MS)), - timeout: current_time.saturating_add(Application::EXPIRATION_TIMEOUT_MS), - }; - drop(state); - drop(kex_lock); - // Let Bob know we got the key. - if let Some((send, _)) = send_to(&session) { - let _ = session.send_control(&session.state.read().unwrap(), send, PACKET_TYPE_KEY_CONFIRM, &[]); - } - app.event_log(LogEvent::ReceiveValidKK2(&session), current_time); - return Ok(ReceiveResult::Session(session, SessionEvent::Control)); - } - } - } - // Bob failed authentication so according to Noise we must terminate this - // handshake. - // This should not happen in practice since this packet will have already passed - // authentication under the current key. - session.expire(); - Err(byzantine_fault!(FaultType::FailedAuthentication, false)) - } else { - drop(state); - drop(kex_lock); - Ok(ReceiveResult::Session(session, SessionEvent::Control)) - } - } - _ => Err(byzantine_fault!(FaultType::InvalidPacket, false)), - }; -} - -impl Session { - /// This can only fail with `MaxKeyLifetimeExceeded` or `SessionNotEstablished`. - fn send_control( - &self, - state: &SessionMutableState, - send: impl FnOnce(&mut [u8]) -> bool, - packet_type: u8, - packet: &[u8], - ) -> Result<(), SendError> { - let key = state.cipher_states[state.current_key].as_ref().ok_or(SendError::SessionNotEstablished)?; - let counter = self.get_next_outgoing_counter()?; - let mut c = self.kex_send_cipher.lock().unwrap(); - let (mut fragment, len) = encrypt_control( - c.as_mut().ok_or(SendError::SessionNotEstablished)?, - &self.header_send_cipher, - packet_type, - counter, - key.remote_key_id.get(), - packet, - ); - send(&mut fragment[..len]); - Ok(()) - } - /// Check whether this session is established. - pub fn established(&self) -> bool { - let state = self.state.read().unwrap(); - !matches!(&state.outgoing_offer, OfferStateMachine::NoiseXKPattern1or3(_) | OfferStateMachine::Null) - } - /// The static public key of the remote peer. - pub fn remote_s_public_key(&self) -> &Application::PublicKey { - &self.remote_static_key - } - /// The current ratchet state of this session. - /// The returned values are sensitive and should be securely erased before being dropped. - pub fn ratchet_states(&self) -> [RatchetState; 2] { - let state = self.state.read().unwrap(); - state.ratchet_states.clone() - } - /// The current ratchet count of this session. - pub fn ratchet_count(&self) -> u64 { - self.state.read().unwrap().ratchet_states[0].chain_len() - } - /// Mark a session as expired. This will make it impossible for this session to successfully - /// receive or send data or control packets. It is recommended to simply `drop` the session - /// instead, but this can provide some reassurance in complex shared ownership situations. - pub fn expire(&self) { - if let Some(context) = self.context.upgrade() { - self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); - } - } - fn expire_inner( - &self, - context: &Arc>, - session_queue: &mut IndexedBinaryHeap>, Reverse>, - ) { - // Prevent this session from being updated. - session_queue.remove(self.queue_idx); - self.session_has_expired.store(true, Ordering::Relaxed); - let _kex_lock = self.state_machine_lock.lock().unwrap(); - let mut state = self.state.write().unwrap(); - let mut session_map = context.session_map.write().unwrap(); - for key in &state.cipher_states { - if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { - session_map.remove(&pre_id); - } - } - use OfferStateMachine::*; - match &state.outgoing_offer { - NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), - NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), - _ => None, - }; - state.outgoing_offer = OfferStateMachine::Null; - } - - /// Get the next outgoing counter value. - fn get_next_outgoing_counter(&self) -> Result { - if self.session_has_expired.load(Ordering::Relaxed) { - Err(SendError::SessionExpired) - } else { - let counter = self.send_counter.fetch_add(1, Ordering::Relaxed); - if counter > THREAD_SAFE_COUNTER_HARD_EXPIRE { - // Because this thread sets the flag itself it will never be able to increment the - // counter again. - // For that reason the other atomic orderings can be `Relaxed`. - self.session_has_expired.store(true, Ordering::SeqCst) - } - Ok(counter) - } - } - /// Check the receive window without mutating state. - fn check_receive_window(&self, counter: u64) -> bool { - let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.load(Ordering::Relaxed); - prev_counter < counter && counter.wrapping_sub(prev_counter) <= COUNTER_WINDOW_MAX_SKIP_AHEAD - } - /// Update the receive window, returning true if the packet is still valid. - /// This should only be called after the packet is authenticated. - fn update_receive_window(&self, counter: u64) -> bool { - let slot = &self.counter_antireplay_window[(counter as usize) % self.counter_antireplay_window.len()]; - let counter = counter.wrapping_add(1); - let prev_counter = slot.fetch_max(counter, Ordering::Relaxed); - prev_counter < counter && counter.wrapping_sub(prev_counter) <= COUNTER_WINDOW_MAX_SKIP_AHEAD - } -} -impl Drop for Session { - fn drop(&mut self) { - if let Some(context) = self.context.upgrade() { - self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); - } - } -} - -impl NoiseXKAliceHandshake { - /// Can only fail with `OpenError::InvalidPublicKey` because of remote_s_public_key. - /// Corresponds to Noise `Initialize`. - fn initialize( - local_key_id: NonZeroU32, - remote_s_public_key: &Application::PublicKey, - ratchet_state: &[RatchetState; 2], - rng: &mut Application::Rng, - ) -> Result< - ( - NoiseXKAliceHandshakeState, - Secret, - Secret, - ), - OpenError, - > { - let mut message = [0u8; NoiseXKPattern1::MAX_SIZE]; - let sha512 = &mut Application::Hash::new(); - let hmac = &mut Application::HmacHash::new(); - // Start of Noise XKhfs+psk2 pattern1. - let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); - let noise_e_secret = Application::KeyPair::generate(rng); - let noise_e1_secret = pqc_kyber::keypair(rng); - noise_pattern1.alice_key_id = local_key_id.get().to_ne_bytes(); - noise_pattern1.noise_e = *noise_e_secret.public_key_bytes(); - noise_pattern1.noise_e1 = noise_e1_secret.public; - // Noise process prologue. - let noise_h = mix_hash( - sha512, - &INITIAL_H, - &message[NoiseXKPattern1::PROLOGUE_START..NoiseXKPattern1::PROLOGUE_END], - ); - let noise_h = mix_hash(sha512, &noise_h, remote_s_public_key.as_bytes()); - // Noise process pattern1 e token. - let mut noise_ck = SymmetricState::new(INITIAL_H); - let noise_h_e = mix_hash(sha512, &noise_h, noise_e_secret.public_key_bytes()); - noise_ck.mix_key(hmac, noise_e_secret.public_key_bytes()); - // Noise process pattern1 es token. - let mut noise_es = Secret::new(); - if !noise_e_secret.agree(remote_s_public_key, noise_es.as_mut()) { - return Err(OpenError::InvalidPublicKey); - } - let noise_k_es = noise_ck.mix_key_initialize_key(hmac, noise_es.as_ref()); - drop(noise_es); - // Noise process pattern1 e1 token. - let noise_h_ee1 = encrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_e, - PACKET_TYPE_NOISE_XK_PATTERN_1, - 0, - &mut message[NoiseXKPattern1::E1_ENC_START..NoiseXKPattern1::P_ENC_START], - ); - // Noise process pattern1 payload. - let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(&mut message); - let mut idx = 0; - for rs in ratchet_state { - if let Some(rf) = rs.fingerprint() { - let next_idx = idx + RATCHET_SIZE; - noise_pattern1.payload[idx..next_idx].copy_from_slice(rf); - idx = next_idx; - } - } - let p_auth_end = NoiseXKPattern1::P_ENC_START + idx + AES_GCM_TAG_SIZE; - let noise_message_len = p_auth_end + ChallengeResponse::SIZE; - - let noise_h_ee1p = encrypt_and_hash::( - sha512, - &noise_k_es, - &noise_h_ee1, - PACKET_TYPE_NOISE_XK_PATTERN_1, - 1, - &mut message[NoiseXKPattern1::P_ENC_START..p_auth_end], - ); - drop(noise_k_es); - let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); - let message_id = u64::from_be_bytes(message[p_auth_end - 8..p_auth_end].try_into().unwrap()); - - message[noise_message_len - CHALLENGE_POW_SIZE..noise_message_len].copy_from_slice(&rng.next_u64().to_ne_bytes()); - Ok(( - NoiseXKAliceHandshakeState::NoiseXKPattern1 { - noise_h_ee1p, - noise_e_secret, - noise_e1_secret: Secret(noise_e1_secret.secret), - noise_ck_es: noise_ck, - noise_message_len, - noise_message: message, - message_id, - }, - header_a2b_key, - header_b2a_key, - )) - } - /// Should not fail unless Bob's public key is adversarial. - fn reinitialize( - &mut self, - session: &Arc>, - ratchet_state: &[RatchetState; 2], - session_map: &mut HashMap>, bool)>, - rng: &mut Application::Rng, - current_time: i64, - ) -> bool { - let local_key_id = generate_key_id(session_map, rng); - if let Ok((offer, a2b_header_key, b2a_header_key)) = Self::initialize(local_key_id, &session.remote_static_key, ratchet_state, rng) { - self.timeout = current_time.saturating_add(Application::INITIAL_OFFER_TIMEOUT_MS); - session_map.remove(&self.local_key_id); - session_map.insert(local_key_id, (Arc::downgrade(session), false)); - self.local_key_id = local_key_id; - self.offer = offer; - session.header_send_cipher.reset(a2b_header_key.as_ref()); - session.header_receive_cipher.reset(b2a_header_key.as_ref()); - true - } else { - false - } - } -} - -/// Create the normal state of the offer state machine, with the correct timestamps. -fn new_normal_state(rand: u64, current_time: i64) -> OfferStateMachine { - OfferStateMachine::Normal { - timeout: current_time - .saturating_add(Application::REKEY_AFTER_TIME_MS) - .saturating_sub(rand as i64 % Application::REKEY_AFTER_TIME_MAX_JITTER_MS), - } -} -/// Get a timestamp of when this timer should trigger next, or None if it should trigger now. -fn process_timer(timer: &AtomicI64, wait_time: i64, current_time: i64) -> Option { - let ts = timer.load(Ordering::Relaxed); - if ts <= current_time && timer.fetch_max(ts.saturating_add(wait_time), Ordering::Relaxed) == ts { - None - } else { - Some(ts) - } -} - -/// Corresponds to Noise `EncryptAndHash`. -fn encrypt_and_hash( - sha512: &mut Application::Hash, - noise_k: &Secret, - noise_h: &[u8; HASHLEN], - packet_type: u8, - noise_k_uses: u64, - message: &mut [u8], -) -> [u8; HASHLEN] { - let auth_start = message.len() - AES_GCM_TAG_SIZE; - let mut gcm = Application::AeadEnc::new(noise_k.as_ref()); - // Encrypt and add authentication tag. - gcm.set_iv(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); - gcm.set_aad(noise_h); - if auth_start > 0 { - gcm.encrypt_in_place(&mut message[..auth_start]); - } - gcm.finish_encrypt((&mut message[auth_start..]).try_into().unwrap()); - mix_hash(sha512, noise_h, message) -} -/// Corresponds to Noise `DecryptAndHash`. -fn decrypt_and_hash( - sha512: &mut Application::Hash, - noise_k: &Secret, - noise_h: &[u8; HASHLEN], - packet_type: u8, - noise_k_uses: u64, - message: &mut [u8], -) -> (bool, [u8; HASHLEN]) { - let auth_start = message.len() - AES_GCM_TAG_SIZE; - let noise_h_c = mix_hash(sha512, noise_h, message); - let mut gcm = Application::AeadDec::new(noise_k.as_ref()); - gcm.set_iv(&create_message_nonce(packet_type, INIT_COUNTER + noise_k_uses)); - gcm.set_aad(noise_h); - if auth_start > 0 { - gcm.decrypt_in_place(&mut message[..auth_start]); - } - (gcm.finish_decrypt((&message[auth_start..]).try_into().unwrap()), noise_h_c) -} -/// Encrypt a standardized control packet. -fn encrypt_control( - c: &mut impl AesGcmEnc, - header_cipher: &impl AesEnc, - packet_type: u8, - counter: u64, - remote_key_id: u32, - packet: &[u8], -) -> ([u8; CONTROL_PACKET_MAX_SIZE], usize) { - let mut fragment = [0u8; CONTROL_PACKET_MAX_SIZE]; - let fragment_len = packet.len() + HEADER_SIZE + AES_GCM_TAG_SIZE; - - c.set_iv(&create_message_nonce(packet_type, counter)); - if !packet.is_empty() { - fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE].copy_from_slice(packet); - c.encrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); - } - c.finish_encrypt((&mut fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]).try_into().unwrap()); - set_packet_header(&mut fragment, 1, 0, packet_type, remote_key_id, counter); - header_cipher.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); - (fragment, fragment_len) -} -fn decrypt_control<'a, IoError>( - c: &mut impl AesGcmDec, - packet_type: u8, - counter: u64, - fragment: &'a mut [u8], -) -> Result<&'a mut [u8], ReceiveError> { - let fragment_len = fragment.len(); - if !(CONTROL_PACKET_MIN_SIZE..=CONTROL_PACKET_MAX_SIZE).contains(&fragment_len) { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - c.set_iv(&create_message_nonce(packet_type, counter)); - c.decrypt_in_place(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]); - if !c.finish_decrypt((&fragment[fragment_len - AES_GCM_TAG_SIZE..fragment_len]).try_into().unwrap()) { - // This can occur naturally if one of the remote peers resent a - // control packet that got delayed and arrived out of order. - return Err(byzantine_fault!(FaultType::FailedAuthentication, true)); - } - Ok(&mut fragment[HEADER_SIZE..fragment_len - AES_GCM_TAG_SIZE]) -} - -fn set_packet_header(packet: &mut [u8], fragment_count: u8, fragment_no: u8, packet_type: u8, remote_key_id: u32, counter_or_id: u64) { - debug_assert!(packet.len() >= MIN_PACKET_SIZE); - debug_assert!(fragment_count > 0); - debug_assert!(fragment_count <= MAX_FRAGMENTS as u8); - debug_assert!(fragment_no < MAX_FRAGMENTS as u8); - debug_assert_eq!((packet_type << 1) >> 1, packet_type); - // [0..4] recipient key id - // -- start AES(ck_es * h_e_e1_p) encrypted block -- - // [4] fragment count (1..255) - // [5] fragment number (0..254) - // [6] reserved zero - // -- start of AES-GCM Nonce -- - // [7] packet type - // [8..16] 64-bit counter or packet id (big endian) - packet[4..16].copy_from_slice(&create_message_nonce(packet_type, counter_or_id)); - packet[0..4].copy_from_slice(&remote_key_id.to_ne_bytes()); - packet[4] = fragment_count; - packet[5] = fragment_no; - packet[6] = 0; -} -/// Create a 96-bit AES-GCM nonce. -/// -/// The primary information that we want to be contained here is the counter and the -/// packet type. The former makes this unique and the latter's inclusion authenticates -/// it as effectively AAD. Other elements of the header are either not authenticated, -/// like fragmentation info, or their authentication is implied via key exchange like -/// the key id. -fn create_message_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] { - let mut ret = [0u8; AES_GCM_IV_SIZE]; - ret[3] = packet_type; - // Noise requires a big endian counter at the end of the Nonce - ret[4..].copy_from_slice(&counter.to_be_bytes()); - ret -} -/// returns `(fragment_count, fragment_no, packet_type, counter, header_nonce)`. -fn parse_packet_header(packet: &[u8]) -> (u8, u8, u8, u64, [u8; 10]) { - let header_nonce = packet[6..16].try_into().unwrap(); - let counter = packet[8..16].try_into().unwrap(); - // We intentionally ignore the version number for future revisions. - (packet[4], packet[5], packet[7], u64::from_be_bytes(counter), header_nonce) -} - -/// Break a packet into fragments and send them all. -/// -/// The contents of packet[] are mangled during this operation, so it should be discarded after. -/// This is only used for key exchange and control packets. For data packets this is done inline -/// for better performance with encryption and fragmentation happening at the same time. -fn send_with_fragmentation( - send: &mut impl FnMut(&mut [u8]) -> bool, - mtu: usize, - packet: &mut [u8], - packet_type: u8, - remote_key_id: Option, - counter_or_id: u64, - header_cipher: Option<&impl AesEnc>, -) -> bool { - let packet_len = packet.len(); - let fragment_count = (packet_len.saturating_add(mtu - 1)) / mtu; // integer ceiling divide - debug_assert!(fragment_count <= MAX_FRAGMENTS); - let mut fragment_start = 0; - let mut fragment_end = packet_len.min(mtu); - let mut fragment_no = 0; - loop { - let fragment = &mut packet[fragment_start..fragment_end]; - set_packet_header( - fragment, - fragment_count as u8, - fragment_no as u8, - packet_type, - remote_key_id.map_or(0, |n| n.get()), - counter_or_id, - ); - if let Some(hcc) = header_cipher { - hcc.encrypt_in_place((&mut fragment[HEADER_PROTECT_ENC_START..HEADER_PROTECT_ENC_END]).try_into().unwrap()); - } - if !send(fragment) { - return false; - } - fragment_no += 1; - if fragment_no < fragment_count { - fragment_start = fragment_end - HEADER_SIZE; - fragment_end = (fragment_start.saturating_add(mtu)).min(packet_len); - } else { - break; - } - } - true -} - -/// Assemble a series of fragments into a buffer and return the length of the assembled packet in -/// bytes. -/// -/// This is also only used for key exchange and control packets. For data packets decryption and -/// assembly happen in one pass for better performance. -fn assemble_fragments_into(fragments: &[A::IncomingPacketBuffer], d: &mut [u8]) -> Result> { - let mut l = 0; - for i in 0..fragments.len() { - let mut ff = fragments[i].as_ref(); - if i > 0 { - ff = &ff[HEADER_SIZE..]; - } - let j = l + ff.len(); - if j > d.len() { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); - } - d[l..j].copy_from_slice(ff); - l = j; - } - Ok(l) -} -/// Generate a random local key id that is currently unused. -fn generate_key_id( - session_map: &HashMap>, bool)>, - rng: &mut Application::Rng, -) -> NonZeroU32 { - loop { - if let Some(local_key_id) = NonZeroU32::new(rng.next_u32()) { - if !session_map.contains_key(&local_key_id) { - return local_key_id; - } - } - } -} - -impl SessionKey { - fn new( - hmac: &mut Application::HmacHash, - ck: SymmetricState, - local_key_id: NonZeroU32, - remote_key_id: NonZeroU32, - current_counter: u64, - is_bob: bool, - ) -> Self { - let (b2a, a2b) = ck.split(hmac); - let (receive_key, send_key) = if is_bob { - (&a2b, &b2a) - } else { - (&b2a, &a2b) - }; - let send_cipher_pool = std::array::from_fn(|_| Mutex::new(Application::AeadEnc::new(send_key.as_ref()))); - let receive_cipher_pool = std::array::from_fn(|_| Mutex::new(Application::AeadDec::new(receive_key.as_ref()))); - Self { - local_key_id, - remote_key_id, - send_cipher_pool, - receive_cipher_pool, - rekey_at_counter: current_counter.saturating_add(Application::REKEY_AFTER_USES), - expire_at_counter: current_counter.saturating_add(Application::EXPIRE_AFTER_USES), - } - } - - fn get_send_cipher(&self, counter: u64) -> Result, SendError> { - if counter < self.expire_at_counter { - Ok(self.send_cipher_pool[(counter as usize) % self.send_cipher_pool.len()].lock().unwrap()) - } else { - Err(SendError::SessionExpired) - } - } - - fn get_receive_cipher(&self, counter: u64) -> MutexGuard { - let idx = (counter as usize) % self.receive_cipher_pool.len(); - self.receive_cipher_pool[idx].lock().unwrap() - } -} - -/// MixHash to update 'h' during negotiation. -fn mix_hash(hasher: &mut impl Sha512, h: &[u8; HASHLEN], m: &[u8]) -> [u8; HASHLEN] { - let mut output = [0u8; HASHLEN]; - hasher.reset(); - hasher.update(h); - hasher.update(m); - hasher.finish(&mut output); - output -} -/// Check if the proof of work attached to the first message contains the correct number of leading -/// zeros. -fn verify_pow(hasher: &mut Application::Hash, response: &[u8]) -> bool { - if Application::PROOF_OF_WORK_BIT_DIFFICULTY == 0 { - return true; - } - hasher.reset(); - hasher.update(response); - let mut output = [0u8; HASHLEN]; - hasher.finish(&mut output); - let n = u32::from_be_bytes(output[..4].try_into().unwrap()); - n.leading_zeros() >= Application::PROOF_OF_WORK_BIT_DIFFICULTY -} -fn from_bytes_agreement( - public: &[u8], - private: &Application::KeyPair, - output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE], -) -> Option { - Application::PublicKey::from_bytes(public.try_into().unwrap()).and_then(|e| private.agree(&e, output).then_some(e)) -} diff --git a/src/zssp.rs b/src/zssp.rs index b0ba9f8..ff7eed5 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -21,11 +21,8 @@ use arrayvec::ArrayVec; use zeroize::Zeroizing; use crate::challenge::ChallengeContext; -use crate::crypto::aes::{AesDec, AesEnc, AES_256_KEY_SIZE, AES_GCM_IV_SIZE, AES_GCM_TAG_SIZE}; -use crate::crypto::p384::{P384KeyPair, P384PublicKey, P384_ECDH_SHARED_SECRET_SIZE, P384_PUBLIC_KEY_SIZE}; -use crate::crypto::pqc_kyber::KYBER_SECRETKEYBYTES; -use crate::crypto::rand_core::RngCore; -use crate::crypto::sha512::{HashSha512, HmacSha512}; +use crate::crypto::*; +use rand_core::RngCore; use crate::zeta::*; use crate::frag_cache::UnassociatedFragCache; @@ -36,7 +33,7 @@ use crate::log_event::LogEvent; use crate::proto::*; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; use crate::symmetric_state::SymmetricState; -use crate::{applicationlayer::*, ratchet_state::RatchetState}; +use crate::application::*; /// Macro to turn off logging at compile time. macro_rules! log { From d4c07cd7a3271511f3c54890dd6c98ed807f9d8f Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 09:20:16 -0400 Subject: [PATCH 61/91] reorganized --- src/application.rs | 12 +- src/challenge.rs | 2 +- src/handshake_cache.rs | 2 +- src/log_event.rs | 2 +- src/result.rs | 1 + src/symmetric_state.rs | 2 +- src/zeta.rs | 412 +++++++++++++++++++++-------------------- src/zssp.rs | 124 +++++++------ 8 files changed, 293 insertions(+), 264 deletions(-) diff --git a/src/application.rs b/src/application.rs index 0a28af9..705cdad 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,9 +1,9 @@ -use std::sync::Arc; use rand_core::{CryptoRng, RngCore}; +use std::sync::Arc; use crate::crypto::*; -use crate::zeta::Session; use crate::ratchet_state::RatchetState; +use crate::zeta::Session; pub use crate::proto::RATCHET_SIZE; pub use crate::ratchet_state::*; @@ -156,6 +156,7 @@ pub trait ApplicationLayer: Sized { /// should rekey. fn time(&self) -> i64; + fn incoming_session(&self) -> IncomingSessionAction; /// This function will be called whenever Alice's initial Hello packet contains the empty ratchet /// fingerprint. Brand new peers will always connect to Bob with the empty ratchet, but from /// then on they should be using non-empty ratchet states. @@ -249,6 +250,13 @@ pub trait ApplicationLayer: Sized { fn event_log(&self, event: LogEvent<'_, Self>); } +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum IncomingSessionAction { + Allow, + Challenge, + Drop, +} + /// A collection of fields specifying how to complete the key exchange with a specific remote peer, /// used by Bob, the responder, at the very last stage of the key exchange. /// diff --git a/src/challenge.rs b/src/challenge.rs index c63c916..cc4de21 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -9,7 +9,7 @@ use crate::proto::*; pub struct ChallengeContext { counter: AtomicU64, - antireplay_window: Window, + antireplay_window: Window, salt: [u8; SALT_SIZE], } diff --git a/src/handshake_cache.rs b/src/handshake_cache.rs index f5cd728..75b4aa4 100644 --- a/src/handshake_cache.rs +++ b/src/handshake_cache.rs @@ -11,7 +11,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; use crate::zeta::StateB2; -use crate::{proto::MAX_UNASSOCIATED_HANDSHAKE_STATES, application::ApplicationLayer}; +use crate::{application::ApplicationLayer, proto::MAX_UNASSOCIATED_HANDSHAKE_STATES}; pub(crate) struct UnassociatedHandshakeCache { has_pending: AtomicBool, // Allowed to be falsely positive diff --git a/src/log_event.rs b/src/log_event.rs index a160ad9..1cb6522 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -7,7 +7,7 @@ */ use std::sync::Arc; -use crate::{zeta::Session, application::ApplicationLayer}; +use crate::{application::ApplicationLayer, zeta::Session}; /// ZSSP events that might be interesting to log or aggregate into metrics. pub enum LogEvent<'a, Application: ApplicationLayer> { diff --git a/src/result.rs b/src/result.rs index 15b023d..f78e035 100644 --- a/src/result.rs +++ b/src/result.rs @@ -102,6 +102,7 @@ pub enum ReceiveError { /// The associated session will no longer function and has to be dropped. MaxKeyLifetimeExceeded, + Rejected, /// One of the ratchet saving or lookup functions returned an error, so the packet had to be /// dropped. StorageError(StorageError), diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index a780b72..7349d63 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -2,9 +2,9 @@ use std::marker::PhantomData; use zeroize::Zeroizing; +use crate::application::ApplicationLayer; use crate::crypto::*; use crate::proto::*; -use crate::application::ApplicationLayer; pub struct SymmetricState { k: Zeroizing<[u8; AES_256_KEY_SIZE]>, diff --git a/src/zeta.rs b/src/zeta.rs index 0278fb7..957faeb 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -6,13 +6,12 @@ use std::io::Write; use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; -use std::sync::{Arc, Mutex, RwLock, Weak}; +use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, Weak}; use zeroize::Zeroizing; use crate::antireplay::Window; use crate::application::*; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; -use crate::zssp::{log, ContextInner, SessionQueue}; use crate::crypto::*; use crate::fragged::Fragged; use crate::indexed_heap::BinaryHeapIndex; @@ -20,6 +19,7 @@ use crate::proto::*; use crate::ratchet_state::{RatchetState, RatchetStates}; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; use crate::symmetric_state::SymmetricState; +use crate::zssp::{log, ContextInner, SessionQueue}; #[cfg(feature = "logging")] use crate::LogEvent::*; @@ -175,7 +175,6 @@ pub(crate) struct StateA3 { x3: ArrayVec, } - /// Corresponds to the ZKE Automata found in Section 4.1 - Definition 2. pub(crate) enum ZetaAutomata { Null, @@ -259,7 +258,12 @@ impl SymmetricState { None } } - fn mix_dh_no_init(&mut self, hmac: &mut App::HmacHash, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { + fn mix_dh_no_init( + &mut self, + hmac: &mut App::HmacHash, + secret: &App::KeyPair, + remote: &App::PublicKey, + ) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { self.mix_key_no_init(hmac, ecdh_secret.as_ref()); @@ -362,9 +366,10 @@ pub(crate) fn trans_to_a1( identity: &[u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result>, OpenError> { - let RatchetStates{state1, state2} = app + let RatchetStates { state1, state2 } = app .restore_by_identity(&s_remote, &session_data) - .map_err(|e| OpenError::RatchetIoError(e))?.unwrap_or_default(); + .map_err(|e| OpenError::RatchetIoError(e))? + .unwrap_or_default(); let mut session_queue = ctx.session_queue.lock().unwrap(); let mut session_map = ctx.session_map.write().unwrap(); @@ -750,12 +755,13 @@ pub(crate) fn received_x2_trans( let mut nk_send = Zeroizing::new([0u8; HASHLEN]); noise.get_ask(hmac, LABEL_KEX_KEY, &mut kek_recv, &mut kek_send); noise.split(hmac, &mut nk_recv, &mut nk_send); + let nonce = to_nonce(PACKET_TYPE_HANDSHAKE_COMPLETION, 0); + set_header(&mut x3, kid_send.get(), &nonce); drop(state); let resend_timer = { let mut state = session.state.write().unwrap(); - state.key_mut(true).send.kid = Some(kid_send); state.key_mut(true).send.replace_kek(&kek_send); state.key_mut(true).recv.replace_kek(&kek_recv); @@ -773,10 +779,7 @@ pub(crate) fn received_x2_trans( // This return is unreachable. return Err(byzantine_fault!(FailedAuth, true)); }; - state.beta = ZetaAutomata::A3(Box::new(StateA3 { - identity: a1.identity.clone(), - x3: x3.clone(), - })); + state.beta = ZetaAutomata::A3(Box::new(StateA3 { identity: a1.identity.clone(), x3: x3.clone() })); resend_timer }; drop(kex_lock); @@ -787,15 +790,40 @@ pub(crate) fn received_x2_trans( Ok(x3) })(); + match result { Err(ReceiveError::ByzantineFault { .. }) => { - process_timers(app, ctx, session, app.time(), true, false, send); + let kex_lock = session.state_machine_lock.lock().unwrap(); + let state = session.state.read().unwrap(); + timeout_trans(app, ctx, session, kex_lock, state, app.time(), send); } Ok(ref mut packet) => send(packet, Some(&session.hk_send)), _ => {} } result.map(|_| ()) } +fn send_control( + session: &Arc>, + state: &MutableState, + packet_type: u8, + mut payload: ArrayVec, + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), +) -> bool { + if let Some((c, _)) = get_counter(session, &state) { + if let (Some(kek), Some(kid)) = (state.key_ref(false).send.kek.as_ref(), state.key_ref(false).send.kid) { + let nonce = to_nonce(packet_type, c); + let tag = App::Aead::encrypt_in_place(kek, &nonce, &[], &mut payload[HEADER_SIZE..]); + payload.extend(tag); + set_header(&mut payload, kid.get(), &nonce); + send(&mut payload, Some(&session.hk_send)); + true + } else { + false + } + } else { + false + } +} /// Corresponds to Transition Algorithm 4 found in Section 4.3. pub(crate) fn received_x3_trans( app: &App, @@ -909,7 +937,7 @@ pub(crate) fn received_x3_trans( return Err(ReceiveError::StorageError(e)); } - let (session, current_time) = { + let session = { let mut session_map = ctx.session_map.write().unwrap(); use std::collections::hash_map::Entry::*; let entry = match session_map.entry(zeta.kid_recv) { @@ -959,9 +987,13 @@ pub(crate) fn received_x3_trans( session_queue.push_reserved(queue_idx, Arc::downgrade(&session), Reverse(resend_timer)); entry.insert(Arc::downgrade(&session)); - (session, current_time) + session }; - process_timers(app, ctx, &session, current_time, false, true, send); + let state = session.state.read().unwrap(); + let mut c1 = ArrayVec::::new(); + c1.extend([0u8; HEADER_SIZE]); + send_control(&session, &state, PACKET_TYPE_KEY_CONFIRM, c1, send); + drop(state); Ok(session) } @@ -969,9 +1001,9 @@ pub(crate) fn received_x3_trans( } } else { if !responder_silently_rejects { - //send(&create_reject(), Some(&zeta.hk_send)) + send(&mut create_reject(), Some(&App::PrpEnc::new(&zeta.hk_send))) } - Err(byzantine_fault!(FailedAuth, true)) + Err(ReceiveError::Rejected) } } /// Corresponds to Transition Algorithm 5 found in Section 4.3. @@ -1003,12 +1035,8 @@ pub(crate) fn received_c1_trans( return Err(byzantine_fault!(OutOfSequence, false)); }; - let specified_key = state - .key_ref(is_other) - .recv - .kek - .as_ref() - .ok_or(byzantine_fault!(OutOfSequence, true))?; + let specified_key = state.key_ref(is_other).recv.kek.as_ref(); + let specified_key = specified_key.ok_or(byzantine_fault!(OutOfSequence, true))?; let tag = c1[..].try_into().unwrap(); if !App::Aead::decrypt_in_place(specified_key, n, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); @@ -1060,25 +1088,12 @@ pub(crate) fn received_c1_trans( } } - let mut c2 = ArrayVec::::new(); + let mut c2 = ArrayVec::::new(); c2.extend([0u8; HEADER_SIZE]); - let (c, _) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; - let nonce = to_nonce(PACKET_TYPE_ACK, c); - let latest_confirmed_key = state - .key_ref(false) - .send - .kek - .as_ref() - .ok_or(byzantine_fault!(OutOfSequence, true))?; - c2.extend(App::Aead::encrypt_in_place(latest_confirmed_key, &nonce, &[], &mut [])); - let kid_send = state - .key_ref(false) - .send - .kid - .ok_or(byzantine_fault!(OutOfSequence, false))?; - set_header(&mut c2, kid_send.get(), &nonce); + if !send_control(session, &state, PACKET_TYPE_ACK, c2, send) { + return Err(byzantine_fault!(OutOfSequence, true)); + } - send(&mut c2, Some(&session.hk_send)); Ok(just_establised) } /// Corresponds to the trivial Transition Algorithm described for processing C_2 packets found in @@ -1171,157 +1186,155 @@ pub(crate) fn received_d_trans( session.expire(); Ok(()) } +// Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. +fn timeout_trans( + app: &App, + ctx: &Arc>, + session: &Arc>, + kex_lock: MutexGuard<'_, ()>, + state: RwLockReadGuard<'_, MutableState>, + current_time: i64, + send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), +) -> Option { + match &state.beta { + ZetaAutomata::Null => None, + ZetaAutomata::A1(_) | ZetaAutomata::A3 { .. } => { + let identity = match &state.beta { + ZetaAutomata::A1(a1) => &a1.identity, + ZetaAutomata::A3(a3) => &a3.identity, + _ => unreachable!(), + }; + if matches!(&state.beta, ZetaAutomata::A1(_)) { + log!(app, TimeoutX1(session)); + } else { + log!(app, TimeoutX3(session)); + } + let new_kid_recv = remap(ctx, session, &state); + + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); + if let Some(a1) = create_a1_state( + hash, + hmac, + &ctx.rng, + &session.s_remote, + new_kid_recv, + &state.ratchet_state1, + state.ratchet_state2.as_ref(), + identity, + ) { + let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); + let mut hk_send = Zeroizing::new([0u8; HASHLEN]); + a1.noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); + let mut x1 = a1.x1.clone(); + + drop(state); + let resend_timer = { + let mut state = session.state.write().unwrap(); + session + .hk_recv + .reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); + session + .hk_send + .reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); + *state.key_mut(true) = DuplexKey::default(); + state.key_mut(true).recv.kid = Some(new_kid_recv); + let resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.resend_timer = AtomicI64::new(resend_timer); + state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; + state.beta = ZetaAutomata::A1(a1); + resend_timer + }; + drop(kex_lock); + + send(&mut x1, None); + Some(resend_timer) + } else { + None + } + } + ZetaAutomata::S2 => { + // Corresponds to Transition Algorithm 6 found in Section 4.3. + log!(app, StartedRekeyingSentK1(session)); + let new_kid_recv = remap(ctx, session, &state); + // -> s + // <- s + // ... + // -> psk, e, es, ss + let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); + let hash = &mut App::Hash::new(); + let hmac = &mut App::HmacHash::new(); + let mut k1 = ArrayVec::::new(); + k1.extend([0u8; HEADER_SIZE]); + // Noise process prologue. + noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); + noise.mix_hash(hash, &session.s_remote.to_bytes()); + // Process message pattern 1 psk0 token. + noise.mix_key_and_hash(hash, hmac, state.ratchet_state1.key.as_ref()); + // Process message pattern 1 e token. + let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut k1); + // Process message pattern 1 es token. + if noise.mix_dh(hmac, &e_secret, &session.s_remote).is_none() { + return None; + } + // Process message pattern 1 ss token. + noise.mix_key(hmac, session.noise_kk_ss.as_ref()); + // Process message pattern 1 payload. + let i = k1.len(); + k1.extend(new_kid_recv.get().to_be_bytes()); + let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..]); + k1.extend(tag); + + drop(state); + let resend_timer = { + let mut state = session.state.write().unwrap(); + state.key_mut(true).recv.kid = Some(new_kid_recv); + state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; + let resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.resend_timer = AtomicI64::new(resend_timer); + state.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; + resend_timer + }; + drop(kex_lock); + let state = session.state.read().unwrap(); + + send_control(session, &state, PACKET_TYPE_REKEY_INIT, k1, send); + Some(resend_timer) + } + ZetaAutomata::S1 { .. } => { + log!(app, TimeoutKeyConfirm(session)); + None + } + ZetaAutomata::R1 { .. } => { + log!(app, TimeoutK1(session)); + None + } + ZetaAutomata::R2 { .. } => { + log!(app, TimeoutK2(session)); + None + } + } +} /// Corresponds to the timer rules of the Zeta State Machine found in Section 4.1 - Definition 3. pub(crate) fn process_timers( app: &App, ctx: &Arc>, session: &Arc>, current_time: i64, - force_timeout: bool, - force_resend: bool, send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Option { let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); - if force_timeout || state.timeout_timer <= current_time { + if state.timeout_timer <= current_time { // Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. - match &state.beta { - ZetaAutomata::Null => None, - ZetaAutomata::A1(_) | ZetaAutomata::A3 { .. } => { - let identity = match &state.beta { - ZetaAutomata::A1(a1) => &a1.identity, - ZetaAutomata::A3(a3) => &a3.identity, - _ => unreachable!(), - }; - if matches!(&state.beta, ZetaAutomata::A1(_)) { - log!(app, TimeoutX1(session)); - } else { - log!(app, TimeoutX3(session)); - } - let new_kid_recv = remap(ctx, session, &state); - - let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); - if let Some(a1) = create_a1_state( - hash, - hmac, - &ctx.rng, - &session.s_remote, - new_kid_recv, - &state.ratchet_state1, - state.ratchet_state2.as_ref(), - identity, - ) { - let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); - let mut hk_send = Zeroizing::new([0u8; HASHLEN]); - a1.noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); - let mut x1 = a1.x1.clone(); - - drop(state); - let resend_timer = { - let mut state = session.state.write().unwrap(); - session - .hk_recv - .reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); - session - .hk_send - .reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); - *state.key_mut(true) = DuplexKey::default(); - state.key_mut(true).recv.kid = Some(new_kid_recv); - let resend_timer = current_time + App::SETTINGS.resend_time as i64; - state.resend_timer = AtomicI64::new(resend_timer); - state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; - state.beta = ZetaAutomata::A1(a1); - resend_timer - }; - drop(kex_lock); - - send(&mut x1, None); - Some(resend_timer) - } else { - None - } - } - ZetaAutomata::S2 => { - // Corresponds to Transition Algorithm 6 found in Section 4.3. - log!(app, StartedRekeyingSentK1(session)); - let new_kid_recv = remap(ctx, session, &state); - // -> s - // <- s - // ... - // -> psk, e, es, ss - let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); - let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); - let mut k1 = ArrayVec::::new(); - k1.extend([0u8; HEADER_SIZE]); - // Noise process prologue. - noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); - noise.mix_hash(hash, &session.s_remote.to_bytes()); - // Process message pattern 1 psk0 token. - noise.mix_key_and_hash(hash, hmac, state.ratchet_state1.key.as_ref()); - // Process message pattern 1 e token. - let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut k1); - // Process message pattern 1 es token. - if noise.mix_dh(hmac, &e_secret, &session.s_remote).is_none() { - return None; - } - // Process message pattern 1 ss token. - noise.mix_key(hmac, session.noise_kk_ss.as_ref()); - // Process message pattern 1 payload. - let i = k1.len(); - k1.extend(new_kid_recv.get().to_be_bytes()); - let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..]); - k1.extend(tag); - - drop(state); - let resend_timer = { - let mut state = session.state.write().unwrap(); - state.key_mut(true).recv.kid = Some(new_kid_recv); - state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - let resend_timer = current_time + App::SETTINGS.resend_time as i64; - state.resend_timer = AtomicI64::new(resend_timer); - state.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; - resend_timer - }; - drop(kex_lock); - let state = session.state.read().unwrap(); - - if let Some((c, _)) = get_counter(session, &state) { - let nonce = to_nonce(PACKET_TYPE_REKEY_INIT, c); - let tag = App::Aead::encrypt_in_place( - state.key_ref(false).send.kek.as_ref().unwrap(), - &nonce, - &[], - &mut k1, - ); - k1.extend(tag); - set_header(&mut k1, state.key_ref(false).send.kid.unwrap().get(), &nonce); - - send(&mut k1, Some(&session.hk_send)); - } - Some(resend_timer) - } - ZetaAutomata::S1 { .. } => { - log!(app, TimeoutKeyConfirm(session)); - None - } - ZetaAutomata::R1 { .. } => { - log!(app, TimeoutK1(session)); - None - } - ZetaAutomata::R2 { .. } => { - log!(app, TimeoutK2(session)); - None - } - } + timeout_trans(app, ctx, session, kex_lock, state, current_time, send) } else { let ts = state.resend_timer.load(Ordering::Relaxed); let resend_next = current_time + App::SETTINGS.resend_time as i64; - if force_resend || (ts <= current_time && state.resend_timer.fetch_max(resend_next, Ordering::Relaxed) == ts) { + if ts <= current_time && state.resend_timer.fetch_max(resend_next, Ordering::Relaxed) == ts { // Corresponds to the resend timer rules found in Section 4.1 - Definition 3. - let (packet_type, mut control_payload) = match &state.beta { + let (packet_type, control_payload) = match &state.beta { ZetaAutomata::Null => return None, ZetaAutomata::A1(a1) => { log!(app, ResentX1(session)); @@ -1349,23 +1362,8 @@ pub(crate) fn process_timers( (PACKET_TYPE_REKEY_COMPLETE, k2.clone()) } }; - if let Some((c, _)) = get_counter(session, &state) { - let nonce = to_nonce(packet_type, c); - let tag = App::Aead::encrypt_in_place( - state.key_ref(false).send.kek.as_ref().unwrap(), - &nonce, - &[], - &mut control_payload, - ); - control_payload.extend(tag); - set_header( - &mut control_payload, - state.key_ref(false).send.kid.unwrap().get(), - &nonce, - ); - send(&mut control_payload, Some(&session.hk_send)); - } + send_control(session, &state, packet_type, control_payload, send); Some(resend_next) } else { Some(ts) @@ -1537,13 +1535,10 @@ pub(crate) fn received_k1_trans( .change_priority(session.queue_idx, Reverse(resend_timer)); let state = session.state.read().unwrap(); - let (c, _) = get_counter(session, &state).ok_or(byzantine_fault!(ExpiredCounter, false))?; - let nonce = to_nonce(PACKET_TYPE_REKEY_COMPLETE, c); - let tag = App::Aead::encrypt_in_place(state.key_ref(false).send.kek.as_ref().unwrap(), &nonce, &[], &mut k2); - k2.extend(tag); - set_header(&mut k2, state.key_ref(false).send.kid.unwrap().get(), &nonce); + if !send_control(session, &state, PACKET_TYPE_REKEY_COMPLETE, k2, send) { + return Err(byzantine_fault!(OutOfSequence, true)); + } - send(&mut k2, Some(&session.hk_send)); Ok(()) })(); @@ -1646,7 +1641,7 @@ pub(crate) fn received_k2_trans( noise.split(hmac, &mut nk_recv, &mut nk_send); drop(state); - let (current_time, resend_timer) = { + let resend_timer = { let mut state = session.state.write().unwrap(); state.key_mut(true).replace_nk(&nk_send, &nk_recv); state.key_mut(true).send.kid = Some(kid_send); @@ -1660,14 +1655,20 @@ pub(crate) fn received_k2_trans( state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; state.resend_timer = AtomicI64::new(resend_timer); state.beta = ZetaAutomata::S1; - (current_time, resend_timer) + resend_timer }; drop(kex_lock); ctx.session_queue .lock() .unwrap() .change_priority(session.queue_idx, Reverse(resend_timer)); - process_timers(app, ctx, session, current_time, false, true, send); + let state = session.state.read().unwrap(); + + let mut c1 = ArrayVec::::new(); + c1.extend([0u8; HEADER_SIZE]); + if !send_control(&session, &state, PACKET_TYPE_KEY_CONFIRM, c1, send) { + return Err(byzantine_fault!(OutOfSequence, true)); + } Ok(()) } else { @@ -1726,9 +1727,16 @@ pub(crate) fn send_payload( let j = i + fragment_len; mtu_sized_buffer[FRAGMENT_NO_IDX] = fragment_no as u8; - cipher.encrypt(&payload[i..j], &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + fragment_len]); + cipher.encrypt( + &payload[i..j], + &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + fragment_len], + ); - session.hk_send.encrypt_in_place((&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap()); + session.hk_send.encrypt_in_place( + (&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]) + .try_into() + .unwrap(), + ); if !send(&mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { return Ok(()); diff --git a/src/zssp.rs b/src/zssp.rs index ff7eed5..6df421b 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -12,28 +12,24 @@ use std::cmp::Reverse; use std::collections::HashMap; use std::hash::Hash; use std::io::Write; -use std::num::{NonZeroU32, NonZeroU64}; -use std::ops::DerefMut; -use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; -use std::sync::{Arc, Mutex, MutexGuard, RwLock, Weak}; +use std::num::NonZeroU32; +use std::sync::{Arc, Mutex, RwLock, Weak}; use arrayvec::ArrayVec; -use zeroize::Zeroizing; +use rand_core::RngCore; use crate::challenge::ChallengeContext; use crate::crypto::*; -use rand_core::RngCore; use crate::zeta::*; +use crate::application::*; use crate::frag_cache::UnassociatedFragCache; -use crate::fragged::{Assembled, Fragged}; +use crate::fragged::Assembled; use crate::handshake_cache::UnassociatedHandshakeCache; -use crate::indexed_heap::{BinaryHeapIndex, IndexedBinaryHeap}; +use crate::indexed_heap::IndexedBinaryHeap; use crate::log_event::LogEvent; use crate::proto::*; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; -use crate::symmetric_state::SymmetricState; -use crate::application::*; /// Macro to turn off logging at compile time. macro_rules! log { @@ -72,13 +68,6 @@ pub struct ContextInner { pub(crate) challenge: ChallengeContext, } -#[derive(Debug, PartialEq, Eq)] -pub enum IncomingSessionAction { - Allow, - Challenge, - Drop, -} - fn parse_fragment_header( incoming_fragment: &[u8], ) -> Result<(usize, usize, [u8; AES_GCM_IV_SIZE]), ReceiveError> { @@ -161,7 +150,7 @@ impl Context { pub fn open( &self, app: App, - mut send: impl FnMut(&mut [u8]) -> bool, + send: impl FnMut(&mut [u8]) -> bool, mut mtu: usize, static_remote_key: App::PublicKey, session_data: App::SessionData, @@ -217,9 +206,7 @@ impl Context { /// to put in-flight. pub fn receive<'a, SendFn: FnMut(&mut [u8]) -> bool>( &self, - app: &App, - check_allow_incoming_session: impl FnOnce() -> IncomingSessionAction, - check_accept_session: impl FnOnce(&App::PublicKey, &[u8], u64) -> (Option<(bool, App::SessionData)>, bool), + app: App, mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, mut send_unassociated_mtu: usize, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, @@ -350,14 +337,22 @@ impl Context { match packet_type { PACKET_TYPE_HANDSHAKE_RESPONSE => { log!(app, ReceivedRawX2); - received_x2_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet, send_associated)?; + received_x2_trans( + &app, + ctx, + &session, + kid_recv, + &nonce, + assembled_packet, + send_associated, + )?; log!(app, X2IsAuthSentX3(&session)); SessionEvent::Control } PACKET_TYPE_KEY_CONFIRM => { log!(app, ReceivedRawKeyConfirm); let result = received_c1_trans( - app, + &app, ctx, &session, kid_recv, @@ -374,19 +369,35 @@ impl Context { } PACKET_TYPE_ACK => { log!(app, ReceivedRawAck); - received_c2_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet)?; + received_c2_trans(&app, ctx, &session, kid_recv, &nonce, assembled_packet)?; log!(app, AckIsAuth(&session)); SessionEvent::Control } PACKET_TYPE_REKEY_INIT => { log!(app, ReceivedRawK1); - received_k1_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet, send_associated)?; + received_k1_trans( + &app, + ctx, + &session, + kid_recv, + &nonce, + assembled_packet, + send_associated, + )?; log!(app, K1IsAuthSentK2(&session)); SessionEvent::Control } PACKET_TYPE_REKEY_COMPLETE => { log!(app, ReceivedRawK2); - received_k2_trans(app, ctx, &session, kid_recv, &nonce, assembled_packet, send_associated)?; + received_k2_trans( + &app, + ctx, + &session, + kid_recv, + &nonce, + assembled_packet, + send_associated, + )?; log!(app, K2IsAuthSentKeyConfirm(&session)); SessionEvent::Control } @@ -454,7 +465,7 @@ impl Context { } log!(app, ReceivedRawX3); - let session = received_x3_trans(app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { + let session = received_x3_trans(&app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); })?; log!(app, X3IsAuthSentKeyConfirm(&session)); @@ -514,32 +525,38 @@ impl Context { return Err(byzantine_fault!(InvalidPacket, true)); } // Process recv challenge layer. - let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; - let result = ctx.challenge.process_hello::( - remote_address, - (&assembled_packet[challenge_start..]).try_into().unwrap(), - ); - if let Err(challenge) = result { - log!(app, X1FailedChallengeSentNewChallenge); - let mut challenge_packet = ArrayVec::::new(); - challenge_packet.extend([0u8; HEADER_SIZE]); - challenge_packet - .try_extend_from_slice(&assembled_packet[..KID_SIZE]) - .unwrap(); - challenge_packet.extend(challenge); - let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); - challenge_packet[FRAGMENT_COUNT_IDX] = 1; - challenge_packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce); + match app.incoming_session() { + IncomingSessionAction::Allow => {} + IncomingSessionAction::Challenge => { + let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; + let result = ctx.challenge.process_hello::( + remote_address, + (&assembled_packet[challenge_start..]).try_into().unwrap(), + ); + if let Err(challenge) = result { + log!(app, X1FailedChallengeSentNewChallenge); + let mut challenge_packet = ArrayVec::::new(); + challenge_packet.extend([0u8; HEADER_SIZE]); + challenge_packet + .try_extend_from_slice(&assembled_packet[..KID_SIZE]) + .unwrap(); + challenge_packet.extend(challenge); + let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); + challenge_packet[FRAGMENT_COUNT_IDX] = 1; + challenge_packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce); - send_unassociated_reply(&mut challenge_packet); - // If we issue a challenge the first hello packet will always fail. - return Err(byzantine_fault!(FailedAuth, false)); - } else { - log!(app, X1SucceededChallenge); + send_unassociated_reply(&mut challenge_packet); + // If we issue a challenge the first hello packet will always fail. + return Err(byzantine_fault!(FailedAuth, false)); + } else { + log!(app, X1SucceededChallenge); + } + } + IncomingSessionAction::Drop => return Err(ReceiveError::Rejected), } // Process recv zeta layer. - received_x1_trans(app, ctx, &nonce, assembled_packet, |packet, hk_send| { + received_x1_trans(&app, ctx, &nonce, assembled_packet, |packet, hk_send| { send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); })?; log!(app, X1IsAuthSentX2); @@ -619,15 +636,10 @@ impl Context { continue; } }; - let result = process_timers(&app, ctx, &session, current_time, false, false, |packet, hk_send| { + let result = process_timers(&app, ctx, &session, current_time, |packet, hk_send| { if let Some((send_fragment, mut mtu)) = send_to(&session) { mtu = mtu.max(MIN_TRANSPORT_MTU); - send_with_fragmentation( - send_fragment, - mtu, - packet, - hk_send - ); + send_with_fragmentation(send_fragment, mtu, packet, hk_send); } }); if let Some(next_timer) = result { From ccea68abe64c0633d96061e2edfb9a400555e34b Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 11:05:01 -0400 Subject: [PATCH 62/91] refactor --- Cargo.lock | 238 +++++++++++++++++++++++++++++++++++ Cargo.toml | 18 ++- src/application.rs | 2 +- src/crypto/aes.rs | 14 +-- src/crypto/mod.rs | 1 + src/crypto/sha512.rs | 4 + src/crypto_impl/kyber1024.rs | 51 ++++++++ src/crypto_impl/mod.rs | 21 ++++ src/crypto_impl/p384_impl.rs | 39 ++++++ src/crypto_impl/sha512.rs | 34 +++++ src/frag_cache.rs | 4 +- src/fragged.rs | 4 +- src/lib.rs | 7 +- src/log_event.rs | 137 ++++++++++---------- src/proto.rs | 2 +- src/symmetric_state.rs | 4 +- src/zeta.rs | 62 +++++---- src/zssp.rs | 36 +++--- 18 files changed, 549 insertions(+), 129 deletions(-) create mode 100644 src/crypto_impl/kyber1024.rs create mode 100644 src/crypto_impl/mod.rs create mode 100644 src/crypto_impl/p384_impl.rs create mode 100644 src/crypto_impl/sha512.rs diff --git a/Cargo.lock b/Cargo.lock index e0caaa4..81b8afa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,181 @@ dependencies = [ "zeroize", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-oid" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "elliptic-curve", + "primeorder", +] + [[package]] name = "pqc_kyber" version = "0.6.0" @@ -20,11 +195,71 @@ dependencies = [ "rand_core", ] +[[package]] +name = "primeorder" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zeroize" @@ -37,7 +272,10 @@ name = "zssp" version = "0.0.3" dependencies = [ "arrayvec", + "hmac", + "p384", "pqc_kyber", "rand_core", + "sha2", "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index ca0967b..9b02171 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,21 @@ path = "src/lib.rs" doc = true [dependencies] -pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber1024", "std"] } -rand_core = "0.6.4" +rand_core = { version = "0.6.4" } zeroize = { version = "1.6.0" } arrayvec = { version = "0.7.4", default-features = false, features = ["std", "zeroize"] } + +pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber1024", "std"], optional = true } +p384 = { version = "0.13.0", default-features = false, features = ["ecdh"], optional = true } +sha2 = { version = "0.10.7", default-features = false, optional = true } +hmac = { version = "0.12.1", default-features = false, optional = true } + +[features] +default = ["debug", "p384", "hmac", "pqc_kyber"] +sha2 = ["dep:sha2"] +hmac = ["dep:hmac", "sha2"] +logging = [] +debug = ["logging"] + +[dev-dependencies] +rand_core = { version = "0.6.4", features = ["getrandom"] } diff --git a/src/application.rs b/src/application.rs index 705cdad..48dc269 100644 --- a/src/application.rs +++ b/src/application.rs @@ -247,7 +247,7 @@ pub trait ApplicationLayer: Sized { /// These are provided for debugging, logging or metrics purposes, and must be used for /// nothing else. Do not base protocol-level decisions upon the events passed to this function. #[cfg(feature = "logging")] - fn event_log(&self, event: LogEvent<'_, Self>); + fn event_log(&self, event: crate::LogEvent<'_, Self>); } #[derive(Debug, PartialEq, Eq, Clone)] diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index 9a4e242..8368e08 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -3,7 +3,7 @@ pub const AES_256_KEY_SIZE: usize = 32; pub const AES_256_BLOCK_SIZE: usize = 16; pub const AES_GCM_TAG_SIZE: usize = 16; -pub const AES_GCM_IV_SIZE: usize = 12; +pub const AES_GCM_NONCE_SIZE: usize = 12; /// A trait for encrypting individual blocks of plaintext using AES-256. /// It is used for header authentication, for which we have a standard model proof that our @@ -15,7 +15,7 @@ pub trait AesEnc: Send + Sync { /// Change the encryption key to `key` so that all future encryption is performed with it. /// This function is very rarely called so it does not have to be particularly efficient. - fn reset(&self, key: &[u8; AES_256_KEY_SIZE]); + fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]); /// Decrypt the given `block` of plaintext directly using the AES block cipher /// (i.e. AES-256 in zero-padding ECB mode). @@ -31,7 +31,7 @@ pub trait AesDec: Send + Sync { /// Change the decryption key to `key` so that all future decryption is performed with it. /// This function is very rarely called so it does not have to be particularly efficient. - fn reset(&self, key: &[u8; AES_256_KEY_SIZE]); + fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]); /// Decrypt the given `block` of ciphertext directly using the AES 256 block cipher /// (i.e. AES-256 in zero-padding ECB mode). @@ -62,21 +62,21 @@ pub trait HighThroughputAesGcmPool: Send + Sync { fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self; - fn start_enc<'a>(&'a self, iv: &[u8; AES_GCM_IV_SIZE]) -> Self::EncContext<'a>; - fn start_dec<'a>(&'a self, iv: &[u8; AES_GCM_IV_SIZE]) -> Self::DecContext<'a>; + fn start_enc<'a>(&'a self, iv: &[u8; AES_GCM_NONCE_SIZE]) -> Self::EncContext<'a>; + fn start_dec<'a>(&'a self, iv: &[u8; AES_GCM_NONCE_SIZE]) -> Self::DecContext<'a>; } pub trait LowThroughputAesGcm { fn encrypt_in_place( key: &[u8; AES_256_KEY_SIZE], - iv: &[u8; AES_GCM_IV_SIZE], + iv: &[u8; AES_GCM_NONCE_SIZE], aad: &[u8], data: &mut [u8], ) -> [u8; AES_GCM_TAG_SIZE]; #[must_use] fn decrypt_in_place( key: &[u8; AES_256_KEY_SIZE], - iv: &[u8; AES_GCM_IV_SIZE], + iv: &[u8; AES_GCM_NONCE_SIZE], aad: &[u8], data: &mut [u8], tag: &[u8; AES_GCM_TAG_SIZE], diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 00ca2aa..16279f0 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -13,6 +13,7 @@ pub use kyber1024::*; // We re-export our dependencies so it is less of a headache for the implementor to use the same // exact version of them. pub use rand_core; +pub use zeroize; /// Constant time byte slice equality. pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { diff --git a/src/crypto/sha512.rs b/src/crypto/sha512.rs index 94178c1..32a0a82 100644 --- a/src/crypto/sha512.rs +++ b/src/crypto/sha512.rs @@ -17,6 +17,10 @@ pub trait HashSha512 { /// Does not need to be threadsafe. pub trait HmacSha512 { /// Allocate space on the stack or heap for repeated Hmac invocations. + /// + /// Many FIPS compliant libraries, namely OpenSSL, require initializing an Hmac context on the + /// heap before operating on it. + /// If you are using a more sane library feel free to make this return an empty type. fn new() -> Self; /// Pure function for computing a single HMAC Hash. Repeat invocations of this function should /// have no effect on each other. diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs new file mode 100644 index 0000000..d2db360 --- /dev/null +++ b/src/crypto_impl/kyber1024.rs @@ -0,0 +1,51 @@ +use rand_core::{CryptoRng, RngCore}; +use zeroize::Zeroizing; + +use crate::crypto::*; + +/// A wrapper for a buffer the size of a pqc_kyber secret key. +/// The crate `pqc_kyber` is low level and operates directly on buffers of bytes. +pub type RustKyber1024PrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; +impl Kyber1024PrivateKey for RustKyber1024PrivateKey { + fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) { + let keypair = pqc_kyber::keypair(rng); + (Zeroizing::new(keypair.secret), keypair.public) + } + + fn encapsulate( + rng: &mut Rng, + public_key: &[u8; KYBER_PUBLIC_KEY_SIZE], + plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE], + ) -> Option<[u8; KYBER_CIPHERTEXT_SIZE]> { + let ret; + (ret, *plaintext_out) = pqc_kyber::encapsulate(public_key, rng).ok()?; + Some(ret) + } + + fn decapsulate( + &self, + ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE], + plaintext_out: &mut [u8; KYBER_PLAINTEXT_SIZE], + ) -> bool { + if let Ok(result) = pqc_kyber::decapsulate(ciphertext, self.as_ref()) { + *plaintext_out = result; + true + } else { + false + } + } +} + +//impl Kyber1024PrivateKey for RustKyber1024PrivateKey { +// fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) { +// } + +// fn encapsulate( +// rng: &mut Rng, +// public_key: &[u8; KYBER_PUBLIC_KEY_SIZE], +// ) -> Option<([u8; KYBER_CIPHERTEXT_SIZE], [u8; KYBER_PLAINTEXT_SIZE])> { +// } + +// fn decapsulate(&self, ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE]) -> Option<[u8; KYBER_PLAINTEXT_SIZE]> { +// } +//} diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs new file mode 100644 index 0000000..e24f28d --- /dev/null +++ b/src/crypto_impl/mod.rs @@ -0,0 +1,21 @@ +#[cfg(feature = "pqc_kyber")] +mod kyber1024; +#[cfg(feature = "pqc_kyber")] +pub use kyber1024::*; +#[cfg(feature = "p384")] +mod p384_impl; +#[cfg(feature = "p384")] +pub use p384_impl::*; +#[cfg(feature = "sha2")] +mod sha512; +#[cfg(feature = "sha2")] +pub use sha512::*; + +#[cfg(feature = "hmac")] +pub use hmac; +#[cfg(feature = "p384")] +pub use p384; +#[cfg(feature = "pqc_kyber")] +pub use pqc_kyber; +#[cfg(feature = "sha2")] +pub use sha2; diff --git a/src/crypto_impl/p384_impl.rs b/src/crypto_impl/p384_impl.rs new file mode 100644 index 0000000..d65028a --- /dev/null +++ b/src/crypto_impl/p384_impl.rs @@ -0,0 +1,39 @@ +use p384::{ecdh::EphemeralSecret, CompressedPoint, PublicKey}; +use rand_core::{CryptoRng, RngCore}; + +use crate::crypto::*; + +pub type RustP384PublicKey = PublicKey; +impl P384PublicKey for PublicKey { + fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option { + PublicKey::from_sec1_bytes(raw_key).ok() + } + + fn to_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE] { + let k = CompressedPoint::from(self); + k.as_slice().try_into().unwrap() + } +} + +pub type RustP384KeyPair = EphemeralSecret; +impl P384KeyPair for RustP384KeyPair { + type PublicKey = PublicKey; + + fn generate(rng: &mut Rng) -> Self { + EphemeralSecret::random(rng) + } + + fn public_key_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE] { + CompressedPoint::from(self.public_key()).as_slice().try_into().unwrap() + } + + fn agree(&self, public_key: &Self::PublicKey, output: &mut [u8; P384_ECDH_SHARED_SECRET_SIZE]) -> bool { + *output = self + .diffie_hellman(public_key) + .raw_secret_bytes() + .as_slice() + .try_into() + .unwrap(); + true + } +} diff --git a/src/crypto_impl/sha512.rs b/src/crypto_impl/sha512.rs new file mode 100644 index 0000000..10dc3b5 --- /dev/null +++ b/src/crypto_impl/sha512.rs @@ -0,0 +1,34 @@ +use hmac::{Hmac, Mac}; +use sha2::{Digest, Sha512}; + +use crate::crypto::*; + +pub type RustSha512 = Sha512; +impl HashSha512 for RustSha512 { + fn new() -> Self { + Digest::new() + } + + fn update(&mut self, data: &[u8]) { + Digest::update(self, data) + } + + fn finish_and_reset(&mut self, output: &mut [u8; SHA512_HASH_SIZE]) { + let mut hasher = Digest::new(); + std::mem::swap(self, &mut hasher); + *output = hasher.finalize().into(); + } +} + +pub struct RustHmac; +impl HmacSha512 for RustHmac { + fn new() -> Self { + RustHmac + } + + fn hash(&mut self, key: &[u8], full_input: &[u8], output: &mut [u8; SHA512_HASH_SIZE]) { + let mut hm = Hmac::::new_from_slice(key).unwrap(); + hm.update(full_input); + *output = hm.finalize().into_bytes().into() + } +} diff --git a/src/frag_cache.rs b/src/frag_cache.rs index cbe95df..2423cda 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -10,7 +10,7 @@ use std::collections::hash_map::RandomState; use std::hash::{BuildHasher, Hash, Hasher}; use std::mem::MaybeUninit; -use crate::crypto::AES_GCM_IV_SIZE; +use crate::crypto::AES_GCM_NONCE_SIZE; use crate::fragged::Assembled; use crate::proto::{MAX_FRAGMENTS, MAX_UNASSOCIATED_FRAGMENTS, MAX_UNASSOCIATED_PACKETS, MAX_UNASSOCIATED_PACKET_SIZE}; @@ -56,7 +56,7 @@ impl UnassociatedFragCache { /// Will check that aad is the same for all fragments. pub(crate) fn assemble( &mut self, - nonce: &[u8; AES_GCM_IV_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], remote_address: impl Hash, fragment_size: usize, fragment: Fragment, diff --git a/src/fragged.rs b/src/fragged.rs index 38a0e18..54b90d9 100644 --- a/src/fragged.rs +++ b/src/fragged.rs @@ -9,7 +9,7 @@ use arrayvec::ArrayVec; use std::mem::{needs_drop, zeroed, MaybeUninit}; -use crate::crypto::AES_GCM_IV_SIZE; +use crate::crypto::AES_GCM_NONCE_SIZE; use crate::proto::{MAX_FRAGMENTS, NONCE_SIZE_DIFF}; pub type Assembled = ArrayVec; @@ -37,7 +37,7 @@ impl Fragged { /// Will check that aad is the same for all fragments. pub(crate) fn assemble( &mut self, - nonce: &[u8; AES_GCM_IV_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], fragment: Fragment, fragment_no: usize, fragment_count: usize, diff --git a/src/lib.rs b/src/lib.rs index bd202c4..36c2fcd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ * https://www.zerotier.com/ */ pub mod crypto; +pub mod crypto_impl; mod antireplay; pub mod application; @@ -14,14 +15,16 @@ mod frag_cache; mod fragged; mod handshake_cache; mod indexed_heap; -pub mod log_event; + +mod log_event; +pub use log_event::*; + pub mod proto; pub mod ratchet_state; pub mod result; mod symmetric_state; pub mod zeta; pub mod zssp; -//mod context; //pub mod error; //pub use crate::applicationlayer::ApplicationLayer; diff --git a/src/log_event.rs b/src/log_event.rs index 1cb6522..283ae3a 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -1,77 +1,88 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ use std::sync::Arc; -use crate::{application::ApplicationLayer, zeta::Session}; +use crate::application::ApplicationLayer; +use crate::zeta::Session; /// ZSSP events that might be interesting to log or aggregate into metrics. -pub enum LogEvent<'a, Application: ApplicationLayer> { - ServiceXK1Resend(&'a Arc>), - ServiceXK3Resend(&'a Arc>), - ServiceXKTimeout(&'a Arc>), - ServiceKKStart(&'a Arc>), - ServiceKK1Resend(&'a Arc>), - ServiceKK2Resend(&'a Arc>), - ServiceKKTimeout(&'a Arc>), - ServiceKeyConfirmResend(&'a Arc>), - ServiceKeyConfirmTimeout(&'a Arc>), - /// `(fragment_count, fragment_no, packet_type)` - ReceiveUnassociatedFragment(u8, u8, u8), - ReceiveUncheckedXK1, - ReceiveCheckXK1Challenge(bool), - ReceiveValidXK1, - ReceiveUncheckedDOSChallenge, - ReceiveValidDOSChallenge(&'a Arc>), - ReceiveUncheckedXK2, - ReceiveValidXK2(&'a Arc>), - ReceiveUncheckedXK3, - ReceiveValidXK3(&'a Application::SessionData), - ReceiveUncheckedKK1, - ReceiveValidKK1(&'a Arc>), - ReceiveUncheckedKK2, - ReceiveValidKK2(&'a Arc>), - ReceiveValidKeyConfirm(&'a Arc>), - ReceiveValidAck(&'a Arc>), +pub enum LogEvent<'a, App: ApplicationLayer> { + ResentX1(&'a Arc>), + TimeoutX1(&'a Arc>), + TimeoutX2, + ResentX3(&'a Arc>), + TimeoutX3(&'a Arc>), + ResentKeyConfirm(&'a Arc>), + TimeoutKeyConfirm(&'a Arc>), + StartedRekeyingSentK1(&'a Arc>), + ResentK1(&'a Arc>), + TimeoutK1(&'a Arc>), + ResentK2(&'a Arc>), + TimeoutK2(&'a Arc>), + /// `(packet_type, packet_counter, fragment_no, fragment_count)` + ReceivedRawFragment(u8, u64, usize, usize), + ReceivedRawX1, + X1FailedChallengeSentNewChallenge, + X1SucceededChallenge, + X1IsAuthSentX2, + ReceivedRawChallenge, + ChallengeIsAuth(&'a Arc>), + ReceivedRawX2, + X2IsAuthSentX3(&'a Arc>), + ReceivedRawX3, + X3IsAuthSentKeyConfirm(&'a Arc>), + ReceivedRawKeyConfirm, + KeyConfirmIsAuthSentAck(&'a Arc>), + ReceivedRawAck, + AckIsAuth(&'a Arc>), + ReceivedRawK1, + K1IsAuthSentK2(&'a Arc>), + ReceivedRawK2, + K2IsAuthSentKeyConfirm(&'a Arc>), + ReceivedRawD, + DIsAuthClosedSession(&'a Arc>), } -impl<'a, Application: ApplicationLayer> std::fmt::Debug for LogEvent<'a, Application> { + +impl<'a, App: ApplicationLayer> std::fmt::Debug for LogEvent<'a, App> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use LogEvent::*; match self { - ServiceXK1Resend(_) => write!(f, "ServiceXK1Resend"), - ServiceXK3Resend(_) => write!(f, "ServiceXK3Resend"), - ServiceXKTimeout(_) => write!(f, "ServiceXKTimeout"), - ServiceKKStart(_) => write!(f, "ServiceKKStart"), - ServiceKK1Resend(_) => write!(f, "ServiceKK1Resend"), - ServiceKK2Resend(_) => write!(f, "ServiceKK2Resend"), - ServiceKKTimeout(_) => write!(f, "ServiceKKTimeout"), - ServiceKeyConfirmResend(_) => write!(f, "ServiceKeyConfirmResend"), - ServiceKeyConfirmTimeout(_) => write!(f, "ServiceKeyConfirmTimeout"), - ReceiveUnassociatedFragment(arg0, arg1, arg2) => f - .debug_tuple("ReceiveUnassociatedFragment") + Self::ResentX1(_) => f.debug_tuple("ResentX1").finish(), + Self::TimeoutX1(_) => f.debug_tuple("TimeoutX1").finish(), + Self::TimeoutX2 => write!(f, "TimeoutX2"), + Self::ResentX3(_) => f.debug_tuple("ResentX3").finish(), + Self::TimeoutX3(_) => f.debug_tuple("TimeoutX3").finish(), + Self::ResentKeyConfirm(_) => f.debug_tuple("ResentKeyConfirm").finish(), + Self::TimeoutKeyConfirm(_) => f.debug_tuple("TimeoutKeyConfirm").finish(), + Self::StartedRekeyingSentK1(_) => f.debug_tuple("StartedRekeyingSentK1").finish(), + Self::ResentK1(_) => f.debug_tuple("ResentK1").finish(), + Self::TimeoutK1(_) => f.debug_tuple("TimeoutK1").finish(), + Self::ResentK2(_) => f.debug_tuple("ResentK2").finish(), + Self::TimeoutK2(_) => f.debug_tuple("TimeoutK2").finish(), + Self::ReceivedRawFragment(arg0, arg1, arg2, arg3) => f + .debug_tuple("ReceivedRawFragment") .field(arg0) .field(arg1) .field(arg2) + .field(arg3) .finish(), - ReceiveUncheckedXK1 => write!(f, "ReceiveUncheckedXK1"), - ReceiveCheckXK1Challenge(arg0) => f.debug_tuple("ReceiveCheckXK1Challenge").field(arg0).finish(), - ReceiveValidXK1 => write!(f, "ReceiveValidXK1"), - ReceiveUncheckedDOSChallenge => write!(f, "ReceiveUncheckedDOSChallenge"), - ReceiveValidDOSChallenge(_) => write!(f, "ReceiveValidDOSChallenge"), - ReceiveUncheckedXK2 => write!(f, "ReceiveUncheckedXK2"), - ReceiveValidXK2(_) => write!(f, "ReceiveValidXK2"), - ReceiveUncheckedXK3 => write!(f, "ReceiveUncheckedXK3"), - ReceiveValidXK3(_) => write!(f, "ReceiveValidXK3"), - ReceiveUncheckedKK1 => write!(f, "ReceiveUncheckedKK1"), - ReceiveValidKK1(_) => write!(f, "ReceiveValidKK1"), - ReceiveUncheckedKK2 => write!(f, "ReceiveUncheckedKK2"), - ReceiveValidKK2(_) => write!(f, "ReceiveValidKK2"), - ReceiveValidKeyConfirm(_) => write!(f, "ReceiveValidKeyConfirm"), - ReceiveValidAck(_) => write!(f, "ReceiveValidAck"), + Self::ReceivedRawX1 => write!(f, "ReceivedRawX1"), + Self::X1FailedChallengeSentNewChallenge => write!(f, "X1FailedChallengeSentNewChallenge"), + Self::X1SucceededChallenge => write!(f, "X1SucceededChallenge"), + Self::X1IsAuthSentX2 => write!(f, "X1IsAuthSentX2"), + Self::ReceivedRawChallenge => write!(f, "ReceivedRawChallenge"), + Self::ChallengeIsAuth(_) => f.debug_tuple("ChallengeIsAuth").finish(), + Self::ReceivedRawX2 => write!(f, "ReceivedRawX2"), + Self::X2IsAuthSentX3(_) => f.debug_tuple("X2IsAuthSentX3").finish(), + Self::ReceivedRawX3 => write!(f, "ReceivedRawX3"), + Self::X3IsAuthSentKeyConfirm(_) => f.debug_tuple("X3IsAuthSentKeyConfirm").finish(), + Self::ReceivedRawKeyConfirm => write!(f, "ReceivedRawKeyConfirm"), + Self::KeyConfirmIsAuthSentAck(_) => f.debug_tuple("KeyConfirmIsAuthSentAck").finish(), + Self::ReceivedRawAck => write!(f, "ReceivedRawAck"), + Self::AckIsAuth(_) => f.debug_tuple("AckIsAuth").finish(), + Self::ReceivedRawK1 => write!(f, "ReceivedRawK1"), + Self::K1IsAuthSentK2(_) => f.debug_tuple("K1IsAuthSentK2").finish(), + Self::ReceivedRawK2 => write!(f, "ReceivedRawK2"), + Self::K2IsAuthSentKeyConfirm(_) => f.debug_tuple("K2IsAuthSentKeyConfirm").finish(), + Self::ReceivedRawD => write!(f, "ReceivedRawD"), + Self::DIsAuthClosedSession(_) => f.debug_tuple("DIsAuthClosedSession").finish(), } } } diff --git a/src/proto.rs b/src/proto.rs index 8c1d252..74ae800 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -51,7 +51,7 @@ pub(crate) const FRAGMENT_COUNT_IDX: usize = 5; /// into this number of fragments it will be dropped. pub(crate) const MAX_FRAGMENTS: usize = 48; -pub(crate) const NONCE_SIZE_DIFF: usize = AES_GCM_IV_SIZE - PACKET_NONCE_SIZE; +pub(crate) const NONCE_SIZE_DIFF: usize = AES_GCM_NONCE_SIZE - PACKET_NONCE_SIZE; /* Key exchange constants */ /* diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 7349d63..6f23da2 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -165,7 +165,7 @@ impl SymmetricState { pub fn encrypt_and_hash_in_place( &mut self, hash: &mut App::Hash, - iv: [u8; AES_GCM_IV_SIZE], + iv: [u8; AES_GCM_NONCE_SIZE], data: &mut [u8], ) -> [u8; AES_GCM_TAG_SIZE] { let tag = App::Aead::encrypt_in_place(&self.k, &iv, &self.h, data); @@ -180,7 +180,7 @@ impl SymmetricState { pub fn decrypt_and_hash_in_place( &mut self, hash: &mut App::Hash, - iv: [u8; AES_GCM_IV_SIZE], + iv: [u8; AES_GCM_NONCE_SIZE], data: &mut [u8], tag: [u8; AES_GCM_TAG_SIZE], ) -> bool { diff --git a/src/zeta.rs b/src/zeta.rs index 957faeb..3b71ec4 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -32,8 +32,8 @@ use crate::LogEvent::*; /// the key id. /// /// Corresponds to Figure 10 found in Section 4.3. -pub(crate) fn to_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_IV_SIZE] { - let mut ret = [0u8; AES_GCM_IV_SIZE]; +pub(crate) fn to_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_NONCE_SIZE] { + let mut ret = [0u8; AES_GCM_NONCE_SIZE]; ret[3] = packet_type; // Noise requires a big endian counter at the end of the Nonce ret[4..].copy_from_slice(&counter.to_be_bytes()); @@ -90,8 +90,6 @@ pub struct Session { pub window: Window, pub(crate) defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], - pub(crate) hk_send: App::PrpEnc, - pub(crate) hk_recv: App::PrpDec, /// `session_queue -> state_machine_lock -> state -> session_map` state_machine_lock: Mutex<()>, @@ -105,13 +103,15 @@ pub(crate) struct MutableState { ratchet_state1: RatchetState, ratchet_state2: Option, + pub(crate) hk_send: App::PrpEnc, + pub(crate) hk_recv: App::PrpDec, key_creation_counter: u64, key_index: bool, keys: [DuplexKey; 2], resend_timer: AtomicI64, timeout_timer: i64, - pub beta: ZetaAutomata, + pub(crate) beta: ZetaAutomata, } /// Corresponds to State B_2 of the Zeta State Machine found in Section 4.1 - Definition 3. @@ -294,7 +294,7 @@ impl MutableState { } } -fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_IV_SIZE]) { +fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_NONCE_SIZE]) { packet[..KID_SIZE].copy_from_slice(&kid_send.to_be_bytes()); packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); } @@ -328,7 +328,7 @@ fn create_a1_state( let i = x1.len(); let (e1_secret, e1_public) = App::Kem::generate(rng.lock().unwrap().deref_mut()); x1.extend(e1_public); - x1.extend([0u8; AES_GCM_IV_SIZE]); + x1.extend([0u8; AES_GCM_NONCE_SIZE]); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..]); x1.extend(tag); // Process message pattern 1 payload. @@ -416,6 +416,8 @@ pub(crate) fn trans_to_a1( state: RwLock::new(MutableState { ratchet_state1: state1.clone(), ratchet_state2: state2.clone(), + hk_send: App::PrpEnc::new((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()), + hk_recv: App::PrpDec::new((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()), key_creation_counter: 0, key_index: true, keys: [DuplexKey::default(), DuplexKey::default()], @@ -425,8 +427,6 @@ pub(crate) fn trans_to_a1( }), noise_kk_ss: noise_kk_ss.clone(), defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - hk_send: App::PrpEnc::new((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()), - hk_recv: App::PrpDec::new((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()), }); { let mut state = session.state.write().unwrap(); @@ -460,7 +460,7 @@ pub(crate) fn respond_to_challenge( pub(crate) fn received_x1_trans( app: &App, ctx: &ContextInner, - n: &[u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_NONCE_SIZE], x1: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { @@ -473,7 +473,7 @@ pub(crate) fn received_x1_trans( return Err(byzantine_fault!(InvalidPacket, true)); } - if &n[AES_GCM_IV_SIZE - 8..] != &x1[x1.len() - 8..] { + if &n[AES_GCM_NONCE_SIZE - 8..] != &x1[x1.len() - 8..] { return Err(byzantine_fault!(FailedAuth, true)); } let hash = &mut App::Hash::new(); @@ -609,7 +609,7 @@ pub(crate) fn received_x2_trans( ctx: &Arc>, session: &Arc>, kid: NonZeroU32, - n: &[u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_NONCE_SIZE], x2: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { @@ -629,7 +629,7 @@ pub(crate) fn received_x2_trans( return Err(byzantine_fault!(UnknownLocalKeyId, true)); } let (_, c) = from_nonce(n); - if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD || &n[AES_GCM_IV_SIZE - 3..] != &x2[x2.len() - 3..] { + if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD || &n[AES_GCM_NONCE_SIZE - 3..] != &x2[x2.len() - 3..] { return Err(byzantine_fault!(FailedAuth, true)); } let mut result = (|| { @@ -797,7 +797,7 @@ pub(crate) fn received_x2_trans( let state = session.state.read().unwrap(); timeout_trans(app, ctx, session, kex_lock, state, app.time(), send); } - Ok(ref mut packet) => send(packet, Some(&session.hk_send)), + Ok(ref mut packet) => send(packet, Some(&session.state.read().unwrap().hk_send)), _ => {} } result.map(|_| ()) @@ -815,7 +815,7 @@ fn send_control( let tag = App::Aead::encrypt_in_place(kek, &nonce, &[], &mut payload[HEADER_SIZE..]); payload.extend(tag); set_header(&mut payload, kid.get(), &nonce); - send(&mut payload, Some(&session.hk_send)); + send(&mut payload, Some(&state.hk_send)); true } else { false @@ -961,6 +961,8 @@ pub(crate) fn received_x3_trans( state: RwLock::new(MutableState { ratchet_state1: new_ratchet_state.clone(), ratchet_state2: None, + hk_send: App::PrpEnc::new(&zeta.hk_send), + hk_recv: App::PrpDec::new(&zeta.hk_recv), key_creation_counter: c + 1, key_index: false, keys: [DuplexKey::default(), DuplexKey::default()], @@ -972,8 +974,6 @@ pub(crate) fn received_x3_trans( queue_idx, noise_kk_ss: noise_kk_ss.clone(), defrag: std::array::from_fn(|_| Mutex::new(Fragged::new())), - hk_send: App::PrpEnc::new(&zeta.hk_send), - hk_recv: App::PrpDec::new(&zeta.hk_recv), }); { let mut state = session.state.write().unwrap(); @@ -1012,7 +1012,7 @@ pub(crate) fn received_c1_trans( ctx: &Arc>, session: &Arc>, kid: NonZeroU32, - n: &[u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_NONCE_SIZE], c1: &[u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result> { @@ -1103,7 +1103,7 @@ pub(crate) fn received_c2_trans( ctx: &Arc>, session: &Arc>, kid: NonZeroU32, - n: &[u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_NONCE_SIZE], c2: &[u8], ) -> Result<(), ReceiveError> { use FaultType::*; @@ -1156,7 +1156,7 @@ pub(crate) fn received_c2_trans( pub(crate) fn received_d_trans( session: &Arc>, kid: NonZeroU32, - n: &[u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_NONCE_SIZE], d: &[u8], ) -> Result<(), ReceiveError> { use FaultType::*; @@ -1231,12 +1231,8 @@ fn timeout_trans( drop(state); let resend_timer = { let mut state = session.state.write().unwrap(); - session - .hk_recv - .reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); - session - .hk_send - .reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); + state.hk_recv.reset((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()); + state.hk_send.reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); *state.key_mut(true) = DuplexKey::default(); state.key_mut(true).recv.kid = Some(new_kid_recv); let resend_timer = current_time + App::SETTINGS.resend_time as i64; @@ -1343,7 +1339,7 @@ pub(crate) fn process_timers( } ZetaAutomata::A3(a3) => { log!(app, ResentX3(session)); - send(&mut a3.x3.clone(), Some(&session.hk_send)); + send(&mut a3.x3.clone(), Some(&state.hk_send)); return Some(resend_next); } ZetaAutomata::S1 => { @@ -1391,7 +1387,7 @@ pub(crate) fn received_k1_trans( ctx: &Arc>, session: &Arc>, kid: NonZeroU32, - n: &[u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_NONCE_SIZE], k1: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { @@ -1553,7 +1549,7 @@ pub(crate) fn received_k2_trans( ctx: &Arc>, session: &Arc>, kid: NonZeroU32, - n: &[u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_NONCE_SIZE], k2: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), ) -> Result<(), ReceiveError> { @@ -1732,7 +1728,7 @@ pub(crate) fn send_payload( &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + fragment_len], ); - session.hk_send.encrypt_in_place( + state.hk_send.encrypt_in_place( (&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]) .try_into() .unwrap(), @@ -1760,14 +1756,14 @@ pub(crate) fn send_payload( /// Corresponds to Algorithm 10 found in Section 4.3. pub(crate) fn receive_payload_in_place( session: &Arc>, + state: RwLockReadGuard<'_, MutableState>, kid: NonZeroU32, - n: &[u8; AES_GCM_IV_SIZE], + n: &[u8; AES_GCM_NONCE_SIZE], fragments: &mut [App::IncomingPacketBuffer], mut output_buffer: impl Write, ) -> Result<(), ReceiveError> { use FaultType::*; - let state = session.state.read().unwrap(); let is_other = if Some(kid) == state.key_ref(true).recv.kid { true } else if Some(kid) == state.key_ref(false).recv.kid { @@ -1792,7 +1788,7 @@ pub(crate) fn receive_payload_in_place( i += 1; } let fragment = &mut fragments[i].as_mut()[HEADER_SIZE..]; - let tag_idx = fragment.len() - AES_GCM_IV_SIZE; + let tag_idx = fragment.len() - AES_GCM_NONCE_SIZE; cipher.decrypt_in_place(&mut fragment[..tag_idx]); if cipher.finish((&fragment[tag_idx..]).try_into().unwrap()) { return Err(byzantine_fault!(FailedAuth, true)); diff --git a/src/zssp.rs b/src/zssp.rs index 6df421b..52e4361 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -27,9 +27,10 @@ use crate::frag_cache::UnassociatedFragCache; use crate::fragged::Assembled; use crate::handshake_cache::UnassociatedHandshakeCache; use crate::indexed_heap::IndexedBinaryHeap; -use crate::log_event::LogEvent; use crate::proto::*; use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; +#[cfg(feature = "logging")] +use crate::LogEvent::*; /// Macro to turn off logging at compile time. macro_rules! log { @@ -70,13 +71,13 @@ pub struct ContextInner { fn parse_fragment_header( incoming_fragment: &[u8], -) -> Result<(usize, usize, [u8; AES_GCM_IV_SIZE]), ReceiveError> { +) -> Result<(usize, usize, [u8; AES_GCM_NONCE_SIZE]), ReceiveError> { let fragment_no = incoming_fragment[FRAGMENT_NO_IDX] as usize; let fragment_count = incoming_fragment[FRAGMENT_COUNT_IDX] as usize; if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS { return Err(byzantine_fault!(FaultType::InvalidPacket, true)); } - let mut nonce = [0u8; AES_GCM_IV_SIZE]; + let mut nonce = [0u8; AES_GCM_NONCE_SIZE]; nonce[2..].copy_from_slice(&incoming_fragment[PACKET_NONCE_START..HEADER_SIZE]); Ok((fragment_no, fragment_count, nonce)) } @@ -230,7 +231,8 @@ impl Context { let session = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()); if let Some(Some(session)) = session { drop(session_map); - session.hk_recv.decrypt_in_place( + let state = session.state.read().unwrap(); + state.hk_recv.decrypt_in_place( (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) .try_into() .unwrap(), @@ -238,14 +240,17 @@ impl Context { let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; let (packet_type, incoming_counter) = from_nonce(&nonce); + if packet_type != PACKET_TYPE_DATA { + log!( + app, + ReceivedRawFragment(packet_type, incoming_counter, fragment_no, fragment_count) + ); + } { //vrfy - if packet_type != PACKET_TYPE_DATA { - log!(app, ReceivedRawFragment(p, c, fragment_no, fragment_count)); - } if packet_type == PACKET_TYPE_HANDSHAKE_RESPONSE { - if !matches!(&session.state.read().unwrap().beta, ZetaAutomata::A1(_)) { + if !matches!(&state.beta, ZetaAutomata::A1(_)) { // A resent handshake response from Bob may have arrived out of order, // after we already received one. return Err(byzantine_fault!(OutOfSequence, false)); @@ -299,9 +304,12 @@ impl Context { } else { std::slice::from_mut(&mut incoming_fragment_buf) }; - receive_payload_in_place(&session, kid_recv, &nonce, fragments, output_buffer)?; + + receive_payload_in_place(&session, state, kid_recv, &nonce, fragments, output_buffer)?; + SessionEvent::Data } else { + drop(state); let mut buffer = ArrayVec::::new(); let assembled_packet = if fragment_count > 1 { let idx = incoming_counter as usize % session.defrag.len(); @@ -424,13 +432,13 @@ impl Context { let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; let (packet_type, incoming_counter) = from_nonce(&nonce); + log!( + app, + ReceivedRawFragment(packet_type, incoming_counter, fragment_no, fragment_count) + ); { //vrfy - log!( - app, - ReceivedRawFragment(packet_type, incoming_counter, frag_no, frag_count) - ); if packet_type != PACKET_TYPE_HANDSHAKE_COMPLETION || incoming_counter != 0 { return Err(byzantine_fault!(InvalidPacket, true)); } @@ -480,10 +488,10 @@ impl Context { } else { let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; let (packet_type, _c) = from_nonce(&nonce); + log!(app, ReceivedRawFragment(packet_type, _c, fragment_no, fragment_count)); { //vrfy - log!(app, ReceivedRawFragment(packet_type, _c, frag_no, frag_count)); if packet_type != PACKET_TYPE_HANDSHAKE_HELLO && packet_type != PACKET_TYPE_CHALLENGE { return Err(byzantine_fault!(InvalidPacket, true)); } From e7f0af7ab802876616b63c7af249ca1ef33a5b8b Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 11:11:15 -0400 Subject: [PATCH 63/91] renamed traits --- src/application.rs | 8 ++++---- src/challenge.rs | 36 ++++++++++++++++++------------------ src/crypto/aes.rs | 4 ++-- src/crypto/sha512.rs | 4 ++-- src/crypto_impl/sha512.rs | 4 ++-- src/ratchet_state.rs | 4 ++-- src/symmetric_state.rs | 14 +++++++------- src/zeta.rs | 37 +++++++++++++++++++------------------ src/zssp.rs | 8 +++++--- 9 files changed, 61 insertions(+), 58 deletions(-) diff --git a/src/application.rs b/src/application.rs index 48dc269..dd48bae 100644 --- a/src/application.rs +++ b/src/application.rs @@ -102,11 +102,11 @@ pub trait ApplicationLayer: Sized { /// The implementation of AES-256 Encryption that ZSSP should use. /// /// FIPS compliance requires use of a FIPS certified implementation. - type PrpEnc: AesEnc; + type PrpEnc: Aes256Enc; /// The implementation of AES-256 Decryption that ZSSP should use. /// /// FIPS compliance requires use of a FIPS certified implementation. - type PrpDec: AesDec; + type PrpDec: Aes256Dec; type Aead: LowThroughputAesGcm; type AeadPool: HighThroughputAesGcmPool; @@ -114,8 +114,8 @@ pub trait ApplicationLayer: Sized { /// The implementation of SHA-512 that ZSSP should use. /// /// FIPS compliance requires use of a FIPS certified implementation. - type Hash: HashSha512; - type HmacHash: HmacSha512; + type Hash: Sha512Hash; + type Hmac: Sha512Hmac; /// The implementation of P-384 public keys that ZSSP should use. /// /// FIPS compliance requires a FIPS certified implementation. diff --git a/src/challenge.rs b/src/challenge.rs index cc4de21..faabbce 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -14,14 +14,15 @@ pub struct ChallengeContext { } /// Corresponds to Algorithm 11 found in Section 5. -pub fn gen_null_response(rng: &mut Rng) -> [u8; CHALLENGE_SIZE] { +pub fn gen_null_response(rng: &mut impl RngCore) -> [u8; CHALLENGE_SIZE] { let mut response = [0u8; CHALLENGE_SIZE]; response[POW_START..].copy_from_slice(&rng.next_u64().to_be_bytes()); response } /// Corresponds to Algorithm 13 found in Section 5. -pub fn respond_to_challenge_in_place( - rng: &mut Rng, +pub fn respond_to_challenge_in_place( + rng: &mut impl RngCore, + hash: &mut impl Sha512Hash, challenge: &[u8; CHALLENGE_SIZE], pre_response: &mut [u8; CHALLENGE_SIZE], ) { @@ -31,7 +32,7 @@ pub fn respond_to_challenge_in_place let mut work_buf = [0u8; SHA512_HASH_SIZE]; loop { pre_response[POW_START..].copy_from_slice(&pow.to_be_bytes()); - if verify_pow::(pre_response, &mut work_buf) { + if verify_pow(hash, pre_response, &mut work_buf) { return; } pow = pow.wrapping_add(1); @@ -50,16 +51,17 @@ impl ChallengeContext { } } /// Corresponds to Algorithm 12 found in Section 5. - pub fn process_hello( + pub fn process_hello( &self, + hash: &mut impl Sha512Hash, addr: &impl std::hash::Hash, response: &[u8; CHALLENGE_SIZE], ) -> Result<(), [u8; CHALLENGE_SIZE]> { let c = u64::from_be_bytes(response[..COUNTER_SIZE].try_into().unwrap()); let mut work_buf = [0u8; SHA512_HASH_SIZE]; if self.antireplay_window.check(c) - && secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac::(c, addr)) - && verify_pow::(response, &mut work_buf) + && secure_eq(&response[COUNTER_SIZE..POW_START], &self.create_mac(hash, c, addr)) + && verify_pow(hash, response, &mut work_buf) { self.antireplay_window.update(c); Ok(()) @@ -67,28 +69,27 @@ impl ChallengeContext { let mut challenge = [0u8; CHALLENGE_SIZE]; let d = self.counter.fetch_add(1, Ordering::Relaxed); challenge[..COUNTER_SIZE].copy_from_slice(&d.to_be_bytes()); - challenge[COUNTER_SIZE..POW_START].copy_from_slice(&self.create_mac::(d, addr)); + challenge[COUNTER_SIZE..POW_START].copy_from_slice(&self.create_mac(hash, d, addr)); challenge[POW_START..].copy_from_slice(&response[POW_START..]); Err(challenge) } } - fn create_mac(&self, c: u64, addr: &impl std::hash::Hash) -> [u8; MAC_SIZE] { - let mut h = Hash::new(); - let mut hasher = ShaHasher(&mut h); + fn create_mac(&self, hash: &mut impl Sha512Hash, c: u64, addr: &impl std::hash::Hash) -> [u8; MAC_SIZE] { + let mut hasher = ShaHasher(hash); hasher.write(&c.to_be_bytes()); addr.hash(&mut hasher); hasher.write(&self.salt); drop(hasher); let mut mac = [0u8; SHA512_HASH_SIZE]; - h.finish_and_reset(&mut mac); + hash.finish_and_reset(&mut mac); mac[..MAC_SIZE].try_into().unwrap() } } /// Trick rust into letting us use a hasher that returns more than 64 bits. -struct ShaHasher<'a, ShaImpl: HashSha512>(&'a mut ShaImpl); -impl<'a, ShaImpl: HashSha512> Hasher for ShaHasher<'a, ShaImpl> { +struct ShaHasher<'a, ShaImpl: Sha512Hash>(&'a mut ShaImpl); +impl<'a, ShaImpl: Sha512Hash> Hasher for ShaHasher<'a, ShaImpl> { fn finish(&self) -> u64 { unimplemented!() } @@ -99,10 +100,9 @@ impl<'a, ShaImpl: HashSha512> Hasher for ShaHasher<'a, ShaImpl> { /// Check if the proof of work attached to the first message contains the correct number of leading /// zeros. -fn verify_pow(response: &[u8], work_buf: &mut [u8; SHA512_HASH_SIZE]) -> bool { - let mut hasher = Hash::new(); - hasher.update(response); - hasher.finish_and_reset(work_buf); +fn verify_pow(hash: &mut impl Sha512Hash, response: &[u8], work_buf: &mut [u8; SHA512_HASH_SIZE]) -> bool { + hash.update(response); + hash.finish_and_reset(work_buf); let n = u32::from_be_bytes(work_buf[..4].try_into().unwrap()); n.leading_zeros() >= DIFFICULTY } diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index 8368e08..a06b85d 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -10,7 +10,7 @@ pub const AES_GCM_NONCE_SIZE: usize = 12; /// algorithm is secure. /// /// Instances must securely delete their keys when dropped or reset. -pub trait AesEnc: Send + Sync { +pub trait Aes256Enc: Send + Sync { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self; /// Change the encryption key to `key` so that all future encryption is performed with it. @@ -26,7 +26,7 @@ pub trait AesEnc: Send + Sync { /// A trait for decrypting individual blocks of plaintext using AES-256. /// /// Instances must securely delete their keys when dropped or reset. -pub trait AesDec: Send + Sync { +pub trait Aes256Dec: Send + Sync { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self; /// Change the decryption key to `key` so that all future decryption is performed with it. diff --git a/src/crypto/sha512.rs b/src/crypto/sha512.rs index 32a0a82..4e064d5 100644 --- a/src/crypto/sha512.rs +++ b/src/crypto/sha512.rs @@ -3,7 +3,7 @@ pub const SHA512_HASH_SIZE: usize = 64; /// A SHA-512 implementation. -pub trait HashSha512 { +pub trait Sha512Hash { /// Create a new instance of SHA-512 for streaming data to. fn new() -> Self; /// Update the instance of SHA-512 with input `data`. @@ -15,7 +15,7 @@ pub trait HashSha512 { /// Opaque HMAC-SHA-512 implementation. /// Does not need to be threadsafe. -pub trait HmacSha512 { +pub trait Sha512Hmac { /// Allocate space on the stack or heap for repeated Hmac invocations. /// /// Many FIPS compliant libraries, namely OpenSSL, require initializing an Hmac context on the diff --git a/src/crypto_impl/sha512.rs b/src/crypto_impl/sha512.rs index 10dc3b5..3b72b46 100644 --- a/src/crypto_impl/sha512.rs +++ b/src/crypto_impl/sha512.rs @@ -4,7 +4,7 @@ use sha2::{Digest, Sha512}; use crate::crypto::*; pub type RustSha512 = Sha512; -impl HashSha512 for RustSha512 { +impl Sha512Hash for RustSha512 { fn new() -> Self { Digest::new() } @@ -21,7 +21,7 @@ impl HashSha512 for RustSha512 { } pub struct RustHmac; -impl HmacSha512 for RustHmac { +impl Sha512Hmac for RustHmac { fn new() -> Self { RustHmac } diff --git a/src/ratchet_state.rs b/src/ratchet_state.rs index d90298b..72a4ca9 100644 --- a/src/ratchet_state.rs +++ b/src/ratchet_state.rs @@ -52,7 +52,7 @@ impl RatchetState { chain_len: 0, } } - pub fn new_from_otp(otp: &[u8]) -> RatchetState { + pub fn new_from_otp(otp: &[u8]) -> RatchetState { let mut buffer = ArrayVec::::new(); buffer.push(1); buffer.extend(*LABEL_OTP_TO_RATCHET); @@ -102,7 +102,7 @@ impl RatchetStates { pub fn new_initial_states() -> Self { Self { state1: RatchetState::empty(), state2: None } } - pub fn new_otp_states(otp: &[u8]) -> Self { + pub fn new_otp_states(otp: &[u8]) -> Self { Self { state1: RatchetState::new_from_otp::(otp), state2: None, diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 6f23da2..6a58549 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -40,7 +40,7 @@ impl SymmetricState { /// Corresponds to Noise `HKDF`. fn kbkdf( &self, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, input_key_material: &[u8], label: &[u8; 4], num_outputs: u16, @@ -86,7 +86,7 @@ impl SymmetricState { } } /// Corresponds to Noise `MixKey`. - pub fn mix_key(&mut self, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + pub fn mix_key(&mut self, hmac: &mut App::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_k = Zeroizing::new([0u8; HASHLEN]); @@ -104,7 +104,7 @@ impl SymmetricState { self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } /// Corresponds to Noise `MixKey`. - pub fn mix_key_no_init(&mut self, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + pub fn mix_key_no_init(&mut self, hmac: &mut App::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); self.kbkdf(hmac, input_key_material, LABEL_KBKDF_CHAIN, 2, &mut next_ck, None, None); @@ -118,7 +118,7 @@ impl SymmetricState { hash.finish_and_reset(&mut self.h); } /// Corresponds to Noise `MixKeyAndHash`. - pub fn mix_key_and_hash(&mut self, hash: &mut App::Hash, hmac: &mut App::HmacHash, input_key_material: &[u8]) { + pub fn mix_key_and_hash(&mut self, hash: &mut App::Hash, hmac: &mut App::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_h = [0u8; HASHLEN]; let mut temp_k = Zeroizing::new([0u8; HASHLEN]); @@ -141,7 +141,7 @@ impl SymmetricState { pub fn mix_key_and_hash_no_init( &mut self, hash: &mut App::Hash, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, input_key_material: &[u8], ) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); @@ -192,7 +192,7 @@ impl SymmetricState { is_auth } /// Corresponds to Noise `Split`. - pub fn split(self, hmac: &mut App::HmacHash, key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { + pub fn split(self, hmac: &mut App::Hmac, key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { self.kbkdf(hmac, &[], LABEL_KBKDF_CHAIN, 2, key1, Some(key2), None); } /// Get an additional symmetric key (ASK) that is a collision resistant hash of the transcript, @@ -201,7 +201,7 @@ impl SymmetricState { /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. pub fn get_ask( &self, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, label: &[u8; 4], key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN], diff --git a/src/zeta.rs b/src/zeta.rs index 3b71ec4..3819c25 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -46,7 +46,7 @@ pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { (n[c_start - 1], u64::from_be_bytes(n[c_start..].try_into().unwrap())) } fn create_ratchet_state( - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, noise: &SymmetricState, pre_chain_len: u64, ) -> RatchetState { @@ -196,7 +196,7 @@ impl SymmetricState { fn write_e( &mut self, hash: &mut App::Hash, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, rng: &Mutex, packet: &mut ArrayVec, ) -> App::KeyPair { @@ -210,7 +210,7 @@ impl SymmetricState { fn read_e( &mut self, hash: &mut App::Hash, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, i: &mut usize, packet: &[u8], ) -> Option { @@ -224,7 +224,7 @@ impl SymmetricState { fn write_e_no_init( &mut self, hash: &mut App::Hash, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, rng: &Mutex, packet: &mut ArrayVec, ) -> App::KeyPair { @@ -238,7 +238,7 @@ impl SymmetricState { fn read_e_no_init( &mut self, hash: &mut App::Hash, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, i: &mut usize, packet: &[u8], ) -> Option { @@ -249,7 +249,7 @@ impl SymmetricState { *i = j; App::PublicKey::from_bytes((pub_key).try_into().unwrap()) } - fn mix_dh(&mut self, hmac: &mut App::HmacHash, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { + fn mix_dh(&mut self, hmac: &mut App::Hmac, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { self.mix_key(hmac, ecdh_secret.as_ref()); @@ -260,7 +260,7 @@ impl SymmetricState { } fn mix_dh_no_init( &mut self, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, secret: &App::KeyPair, remote: &App::PublicKey, ) -> Option<()> { @@ -301,7 +301,7 @@ fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_NONCE_SIZE] fn create_a1_state( hash: &mut App::Hash, - hmac: &mut App::HmacHash, + hmac: &mut App::Hmac, rng: &Mutex, s_remote: &App::PublicKey, kid_recv: NonZeroU32, @@ -376,7 +376,7 @@ pub(crate) fn trans_to_a1( let kid_recv = gen_kid(session_map.deref(), ctx.rng.lock().unwrap().deref_mut()); let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); let a1 = create_a1_state( hash, hmac, @@ -449,8 +449,9 @@ pub(crate) fn respond_to_challenge( let mut state = session.state.write().unwrap(); if let ZetaAutomata::A1(a1) = &mut state.beta { let response_start = a1.x1.len() - CHALLENGE_SIZE; - respond_to_challenge_in_place::( + respond_to_challenge_in_place( ctx.rng.lock().unwrap().deref_mut(), + &mut App::Hash::new(), challenge, (&mut a1.x1[response_start..]).try_into().unwrap(), ); @@ -460,6 +461,7 @@ pub(crate) fn respond_to_challenge( pub(crate) fn received_x1_trans( app: &App, ctx: &ContextInner, + hash: &mut App::Hash, n: &[u8; AES_GCM_NONCE_SIZE], x1: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), @@ -476,8 +478,7 @@ pub(crate) fn received_x1_trans( if &n[AES_GCM_NONCE_SIZE - 8..] != &x1[x1.len() - 8..] { return Err(byzantine_fault!(FailedAuth, true)); } - let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); let mut i = 0; // Noise process prologue. @@ -623,7 +624,7 @@ pub(crate) fn received_x2_trans( let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); if Some(kid) != state.key_ref(true).recv.kid { return Err(byzantine_fault!(UnknownLocalKeyId, true)); @@ -842,7 +843,7 @@ pub(crate) fn received_x3_trans( return Err(byzantine_fault!(UnknownLocalKeyId, true)); } let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); let mut noise = zeta.noise.clone(); let mut i = 0; @@ -1212,7 +1213,7 @@ fn timeout_trans( let new_kid_recv = remap(ctx, session, &state); let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); if let Some(a1) = create_a1_state( hash, hmac, @@ -1259,7 +1260,7 @@ fn timeout_trans( // -> psk, e, es, ss let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); let mut k1 = ArrayVec::::new(); k1.extend([0u8; HEADER_SIZE]); // Noise process prologue. @@ -1438,7 +1439,7 @@ pub(crate) fn received_k1_trans( let mut i = 0; let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_KK); let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); // Noise process prologue. noise.mix_hash(hash, &session.s_remote.to_bytes()); noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); @@ -1591,7 +1592,7 @@ pub(crate) fn received_k2_trans( let mut noise = noise.clone(); let mut i = 0; let hash = &mut App::Hash::new(); - let hmac = &mut App::HmacHash::new(); + let hmac = &mut App::Hmac::new(); // Process message pattern 2 e token. let e_remote = noise .read_e(hash, hmac, &mut i, &k2) diff --git a/src/zssp.rs b/src/zssp.rs index 52e4361..78a7a30 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -85,7 +85,7 @@ fn parse_fragment_header( /// Fragments and sends the packet, destroying it in the process. /// /// Corresponds to the fragmentation algorithm described in Section 6. -fn send_with_fragmentation( +fn send_with_fragmentation( mut send: impl FnMut(&mut [u8]) -> bool, mtu: usize, headered_packet: &mut [u8], @@ -533,11 +533,13 @@ impl Context { return Err(byzantine_fault!(InvalidPacket, true)); } // Process recv challenge layer. + let hash = &mut App::Hash::new(); match app.incoming_session() { IncomingSessionAction::Allow => {} IncomingSessionAction::Challenge => { let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; - let result = ctx.challenge.process_hello::( + let result = ctx.challenge.process_hello( + hash, remote_address, (&assembled_packet[challenge_start..]).try_into().unwrap(), ); @@ -564,7 +566,7 @@ impl Context { } // Process recv zeta layer. - received_x1_trans(&app, ctx, &nonce, assembled_packet, |packet, hk_send| { + received_x1_trans(&app, ctx, hash, &nonce, assembled_packet, |packet, hk_send| { send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); })?; log!(app, X1IsAuthSentX2); From a423f87554cd031ad63d25c7d74ecf3823a70e91 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 12:51:53 -0400 Subject: [PATCH 64/91] added openssl-sys --- Cargo.lock | 34 +++ Cargo.toml | 1 + examples/basic_test.rs | 398 +++++++++++++++++++++++++++++++++++ src/application.rs | 5 - src/crypto/aes.rs | 8 +- src/crypto_impl/kyber1024.rs | 14 -- src/crypto_impl/mod.rs | 3 + src/crypto_impl/openssl.rs | 284 +++++++++++++++++++++++++ src/crypto_impl/p384_impl.rs | 8 +- src/crypto_impl/sha512.rs | 10 +- src/lib.rs | 10 +- src/symmetric_state.rs | 15 +- src/zeta.rs | 76 ++----- src/zssp.rs | 13 +- 14 files changed, 766 insertions(+), 113 deletions(-) create mode 100644 examples/basic_test.rs create mode 100644 src/crypto_impl/openssl.rs diff --git a/Cargo.lock b/Cargo.lock index 81b8afa..50f595c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "cc" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +dependencies = [ + "libc", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -176,6 +185,18 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "openssl-sys" +version = "0.9.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "p384" version = "0.13.0" @@ -186,6 +207,12 @@ dependencies = [ "primeorder", ] +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "pqc_kyber" version = "0.6.0" @@ -249,6 +276,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -273,6 +306,7 @@ version = "0.0.3" dependencies = [ "arrayvec", "hmac", + "openssl-sys", "p384", "pqc_kyber", "rand_core", diff --git a/Cargo.toml b/Cargo.toml index 9b02171..e291b16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber102 p384 = { version = "0.13.0", default-features = false, features = ["ecdh"], optional = true } sha2 = { version = "0.10.7", default-features = false, optional = true } hmac = { version = "0.12.1", default-features = false, optional = true } +openssl-sys = { version = "0.9.91", default-features = false } [features] default = ["debug", "p384", "hmac", "pqc_kyber"] diff --git a/examples/basic_test.rs b/examples/basic_test.rs new file mode 100644 index 0000000..71fde87 --- /dev/null +++ b/examples/basic_test.rs @@ -0,0 +1,398 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use std::collections::HashMap; +use std::iter::ExactSizeIterator; +use std::str::FromStr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{mpsc, Arc, Mutex}; +use std::thread; +use std::time::{Duration, Instant}; + +use rand_core::OsRng; +use rand_core::RngCore; + +use zssp::application::{ + AcceptAction, ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate, Settings, RATCHET_SIZE, IncomingSessionAction, +}; +use zssp::crypto::P384KeyPair; +use zssp::crypto_impl::*; +use zssp::Session; +use zssp::result::ReceiveError; + +const TEST_MTU: usize = 1500; + +struct TestApplication { + time: Instant, + name: &'static str, + ratchets: Mutex, +} + +struct Ratchets { + rf_map: HashMap<[u8; RATCHET_SIZE], RatchetState>, + peer_map: HashMap, +} +impl Ratchets { + fn new() -> Self { + Self { rf_map: HashMap::new(), peer_map: HashMap::new() } + } +} + +#[allow(unused)] +impl ApplicationLayer for &TestApplication { + const SETTINGS: Settings = Settings { + initial_offer_timeout: Settings::INITIAL_OFFER_TIMEOUT_MS, + rekey_timeout: 60 * 1000, + rekey_after_time: 3000, + rekey_time_max_jitter: 1000, + rekey_after_key_uses: Settings::REKEY_AFTER_KEY_USES, + resend_time: 250, + fragment_assembly_timeout: Settings::FRAGMENT_ASSEMBLY_TIMEOUT_MS, + }; + + type Rng = OsRng; + type PrpEnc = Aes256OpenSSLEnc; + type PrpDec = Aes256OpenSSLDec; + type Aead = AesGcmOpenSSL; + type AeadPool = AesGcmOpenSSLPool; + type Hash = Sha512Crate; + type Hmac = HmacSha512Crate; + type PublicKey = P384CratePublicKey; + type KeyPair = P384CrateKeyPair; + type Kem = RustKyber1024PrivateKey; + + type StorageError = std::convert::Infallible; + type SessionData = u128; + + type IncomingPacketBuffer = Vec; + + + fn incoming_session(&self) -> IncomingSessionAction { + IncomingSessionAction::Allow + } + + fn hello_requires_recognized_ratchet(&self) -> bool { + false + } + + fn initiator_disallows_downgrade(&self, session: &Arc>) -> bool { + true + } + + fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> AcceptAction { + AcceptAction { + session_data: Some(1), + responder_disallows_downgrade: true, + responder_silently_rejects: false, + } + } + + fn restore_by_fingerprint( + &self, + ratchet_fingerprint: &[u8; RATCHET_SIZE], + ) -> Result, Self::StorageError> { + let ratchets = self.ratchets.lock().unwrap(); + Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned()) + } + + fn restore_by_identity( + &self, + remote_static_key: &Self::PublicKey, + session_data: &Self::SessionData, + ) -> Result, Self::StorageError> { + let ratchets = self.ratchets.lock().unwrap(); + Ok(ratchets.peer_map.get(session_data).cloned()) + } + + fn save_ratchet_state( + &self, + remote_static_key: &Self::PublicKey, + session_data: &Self::SessionData, + update_data: RatchetUpdate<'_>, + ) -> Result<(), Self::StorageError> { + let mut ratchets = self.ratchets.lock().unwrap(); + ratchets.peer_map.insert(*session_data, update_data.to_states()); + + if let Some(rf) = update_data.added_fingerprint() { + ratchets.rf_map.insert(*rf, update_data.state1.clone()); + println!("[{}] new ratchet #{}", self.name, update_data.state1.chain_len); + } + if let Some(rf) = update_data.deleted_fingerprint1() { + ratchets.rf_map.remove(rf); + } + if let Some(rf) = update_data.deleted_fingerprint2() { + ratchets.rf_map.remove(rf); + } + Ok(()) + } + + fn time(&self) -> i64 { + self.time.elapsed().as_millis() as i64 + } + + fn event_log(&self, event: zssp::LogEvent) { + println!(">[{}] {:?}", self.name, event); + } +} + +fn alice_main( + run: &AtomicBool, + packet_success_rate: u32, + alice_app: &TestApplication, + alice_out: mpsc::SyncSender>, + alice_in: mpsc::Receiver>, + recursive_out: mpsc::SyncSender>, + alice_keypair: P384CrateKeyPair, + bob_pubkey: P384CratePublicKey, +) { + let startup_time = std::time::Instant::now(); + let context = zssp::Context::<&TestApplication>::new(alice_keypair, OsRng); + let mut next_service = startup_time.elapsed().as_millis() as i64 + 500; + let test_data = [1u8; TEST_MTU * 10]; + let mut up = false; + let mut alice_session = None; + + while run.load(Ordering::Relaxed) { + if alice_session.is_none() { + up = false; + alice_session = Some( + context + .open( + alice_app, + |b| alice_out.send(b.to_vec()).is_ok(), + TEST_MTU, + bob_pubkey.clone(), + 0, + &[], + ) + .unwrap(), + ); + println!("[alice] opening session"); + } + let current_time = startup_time.elapsed().as_millis() as i64; + loop { + let pkt = alice_in.try_recv(); + if let Ok(pkt) = pkt { + if OsRng.next_u32() <= packet_success_rate { + use zssp::result::ReceiveOk::*; + use zssp::result::SessionEvent::*; + let mut output_data = Vec::new(); + match context.receive( + alice_app, + |b| alice_out.send(b.to_vec()).is_ok(), + TEST_MTU, + |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)), + &0, + pkt, + &mut output_data + ) { + Ok(Unassociated) => { + //println!("[alice] ok"); + } + Ok(Session(_, event)) => match event { + Established => { + up = true; + } + Data => { + assert!(!output_data.is_empty()); + //println!("[alice] received {}", data.len()); + } + NewSession => panic!(), + Rejected => panic!(), + Control => (), + }, + Err(e) => { + println!("[alice] ERROR {:?}", e); + if let ReceiveError::ByzantineFault { unnatural, .. } = e { + assert!(!unnatural) + } + } + } + } else if OsRng.next_u32() | 1 > 0 { + let _ = recursive_out.send(pkt); + } + } else { + break; + } + } + + if up { + context + .send( + alice_session.as_ref().unwrap(), + |b| alice_out.send(b.to_vec()).is_ok(), + &mut [0u8; TEST_MTU], + &test_data[..1400 + ((OsRng.next_u64() as usize) % (test_data.len() - 1400))], + ) + .unwrap(); + } else { + thread::sleep(Duration::from_millis(10)); + } + // TODO: we need to more comprehensively test if re-opening the session works + if OsRng.next_u32() <= ((u32::MAX as f64) * 0.000005) as u32 { + alice_session = None; + } + + if current_time >= next_service { + next_service = + current_time + context.service(alice_app, |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU))); + } + } +} + +fn bob_main( + run: &AtomicBool, + packet_success_rate: u32, + bob_app: &TestApplication, + bob_out: mpsc::SyncSender>, + bob_in: mpsc::Receiver>, + recursive_out: mpsc::SyncSender>, + bob_keypair: P384CrateKeyPair, +) { + let startup_time = std::time::Instant::now(); + let context = zssp::Context::<&TestApplication>::new(bob_keypair, OsRng); + let mut last_speed_metric = startup_time.elapsed().as_millis() as i64; + let mut next_service = last_speed_metric + 500; + let mut transferred = 0u64; + + let mut bob_session = None; + + while run.load(Ordering::Relaxed) { + let pkt = bob_in.recv_timeout(Duration::from_millis(100)); + let current_time = startup_time.elapsed().as_millis() as i64; + + if let Ok(pkt) = pkt { + if OsRng.next_u32() <= packet_success_rate { + use zssp::result::ReceiveOk::*; + use zssp::result::SessionEvent::*; + let mut output_data = Vec::new(); + match context.receive( + bob_app, + |b| bob_out.send(b.to_vec()).is_ok(), + TEST_MTU, + |_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)), + &0, + pkt, + &mut output_data + ) { + Ok(Unassociated) => {} + Ok(Session(s, event)) => match event { + NewSession => { + println!("[bob] new session, took {}s", current_time as f32 / 1000.0); + let _ = bob_session.replace(s); + } + Data => { + assert!(!output_data.is_empty()); + //println!("[bob] received {}", output_data.len()); + transferred += output_data.len() as u64 * 2; // *2 because we are also sending this many bytes back + context.send(&s, |b| bob_out.send(b.to_vec()).is_ok(), &mut [0u8; TEST_MTU], &output_data).unwrap(); + } + Established => panic!(), + Rejected => panic!(), + Control => (), + }, + Err(e) => { + println!("[bob] ERROR {:?}", e); + if let ReceiveError::ByzantineFault { unnatural, .. } = e { + assert!(!unnatural) + } + } + } + } else if OsRng.next_u32() | 1 > 0 { + let _ = recursive_out.try_send(pkt); + } + } + + let speed_metric_elapsed = current_time - last_speed_metric; + if speed_metric_elapsed >= 10000 { + last_speed_metric = current_time; + println!( + "[bob] throughput: {} MiB/sec (combined input and output)", + ((transferred as f64) / 1048576.0) / ((speed_metric_elapsed as f64) / 1000.0) + ); + transferred = 0; + } + + if current_time >= next_service { + next_service = current_time + context.service(bob_app, |_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU))); + } + } +} + +fn core(time: u64, packet_success_rate: u32) { + let run = &AtomicBool::new(true); + + let alice_keypair = P384CrateKeyPair::generate(&mut OsRng); + let alice_app = TestApplication { + time: Instant::now(), + name: "alice", + ratchets: Mutex::new(Ratchets::new()), + }; + let bob_keypair = P384CrateKeyPair::generate(&mut OsRng); + let bob_pubkey = bob_keypair.public_key(); + let bob_app = TestApplication { + time: Instant::now(), + name: "bob", + ratchets: Mutex::new(Ratchets::new()), + }; + + let (alice_out, bob_in) = mpsc::sync_channel::>(256); + let (bob_out, alice_in) = mpsc::sync_channel::>(256); + + thread::scope(|ts| { + { + let alice_out = alice_out.clone(); + let bob_out = bob_out.clone(); + ts.spawn(move || { + alice_main( + run, + packet_success_rate, + &alice_app, + alice_out, + alice_in, + bob_out, + alice_keypair, + bob_pubkey, + ) + }); + } + ts.spawn(move || { + bob_main( + run, + packet_success_rate, + &bob_app, + bob_out, + bob_in, + alice_out, + bob_keypair, + ) + }); + + thread::sleep(Duration::from_secs(time)); + + run.store(false, Ordering::SeqCst); + println!("finished"); + }); +} + +fn main() { + let args = std::env::args(); + let packet_success_rate = if args.len() <= 1 { + let default_success_rate = 1.0; + ((u32::MAX as f64) * default_success_rate) as u32 + } else { + ((u32::MAX as f64) * f64::from_str(args.last().unwrap().as_str()).unwrap()) as u32 + }; + + core(60 * 60, packet_success_rate) +} + +#[test] +fn test_main() { + core(2, u32::MAX / 2) +} diff --git a/src/application.rs b/src/application.rs index dd48bae..d05aeb8 100644 --- a/src/application.rs +++ b/src/application.rs @@ -2,7 +2,6 @@ use rand_core::{CryptoRng, RngCore}; use std::sync::Arc; use crate::crypto::*; -use crate::ratchet_state::RatchetState; use crate::zeta::Session; pub use crate::proto::RATCHET_SIZE; @@ -145,10 +144,6 @@ pub trait ApplicationLayer: Sized { /// hold these for a short period of time when assembling fragmented packets on the receive /// path. type IncomingPacketBuffer: AsRef<[u8]> + AsMut<[u8]>; - /// Data type for giving ZSSP temporary ownership of a buffer containing the local party's - /// identity. - /// It will be dropped as soon as the session is established. - type LocalIdentityBlob: AsRef<[u8]>; /// Should return the current time in milliseconds. Does not have to be monotonic, nor synced /// with remote peers (although both of these properties would help reliability slightly). diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index a06b85d..9935564 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -62,21 +62,21 @@ pub trait HighThroughputAesGcmPool: Send + Sync { fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self; - fn start_enc<'a>(&'a self, iv: &[u8; AES_GCM_NONCE_SIZE]) -> Self::EncContext<'a>; - fn start_dec<'a>(&'a self, iv: &[u8; AES_GCM_NONCE_SIZE]) -> Self::DecContext<'a>; + fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> Self::EncContext<'a>; + fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> Self::DecContext<'a>; } pub trait LowThroughputAesGcm { fn encrypt_in_place( key: &[u8; AES_256_KEY_SIZE], - iv: &[u8; AES_GCM_NONCE_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], aad: &[u8], data: &mut [u8], ) -> [u8; AES_GCM_TAG_SIZE]; #[must_use] fn decrypt_in_place( key: &[u8; AES_256_KEY_SIZE], - iv: &[u8; AES_GCM_NONCE_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], aad: &[u8], data: &mut [u8], tag: &[u8; AES_GCM_TAG_SIZE], diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs index d2db360..feffd5f 100644 --- a/src/crypto_impl/kyber1024.rs +++ b/src/crypto_impl/kyber1024.rs @@ -35,17 +35,3 @@ impl Kyber1024PrivateKey for RustKyber1024Private } } } - -//impl Kyber1024PrivateKey for RustKyber1024PrivateKey { -// fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) { -// } - -// fn encapsulate( -// rng: &mut Rng, -// public_key: &[u8; KYBER_PUBLIC_KEY_SIZE], -// ) -> Option<([u8; KYBER_CIPHERTEXT_SIZE], [u8; KYBER_PLAINTEXT_SIZE])> { -// } - -// fn decapsulate(&self, ciphertext: &[u8; KYBER_CIPHERTEXT_SIZE]) -> Option<[u8; KYBER_PLAINTEXT_SIZE]> { -// } -//} diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs index e24f28d..d414a06 100644 --- a/src/crypto_impl/mod.rs +++ b/src/crypto_impl/mod.rs @@ -11,6 +11,9 @@ mod sha512; #[cfg(feature = "sha2")] pub use sha512::*; +mod openssl; +pub use openssl::*; + #[cfg(feature = "hmac")] pub use hmac; #[cfg(feature = "p384")] diff --git a/src/crypto_impl/openssl.rs b/src/crypto_impl/openssl.rs new file mode 100644 index 0000000..fb17bf9 --- /dev/null +++ b/src/crypto_impl/openssl.rs @@ -0,0 +1,284 @@ +use std::{ + ptr::{self, NonNull}, + sync::Mutex, +}; + +use openssl_sys::*; +use zeroize::Zeroizing; + +use crate::crypto::*; + +struct CipherCtx(NonNull); +impl Drop for CipherCtx { + fn drop(&mut self) { + unsafe { + EVP_CIPHER_CTX_free(self.0.as_ptr()); + } + } +} +impl CipherCtx { + /// Creates a new context. + pub fn new() -> Option { + unsafe { Some(CipherCtx(NonNull::new(EVP_CIPHER_CTX_new())?)) } + } + + pub unsafe fn cipher_init( + &self, + t: *const openssl_sys::EVP_CIPHER, + key: *const u8, + iv: *const u8, + ) -> bool { + let evp_f = if ENCRYPT { + EVP_EncryptInit_ex + } else { + EVP_DecryptInit_ex + }; + + // OpenSSL will usually leak a static amount of memory per cipher given here. + evp_f(self.0.as_ptr(), t, ptr::null_mut(), key, iv) > 0 + } + + pub unsafe fn update(&self, input: &[u8], output: *mut u8) -> bool { + let evp_f = if ENCRYPT { + EVP_EncryptUpdate + } else { + EVP_DecryptUpdate + }; + + let mut outlen = 0; + + evp_f( + self.0.as_ptr(), + output, + &mut outlen, + input.as_ptr(), + input.len() as c_int, + ) > 0 + } + + pub unsafe fn finalize(&self) -> bool { + let evp_f = if ENCRYPT { + EVP_EncryptFinal_ex + } else { + EVP_DecryptFinal_ex + }; + let mut outl = 0; + + evp_f(self.0.as_ptr(), ptr::null_mut(), &mut outl) > 0 + } + + pub unsafe fn get_tag(&self, tag: &mut [u8]) -> bool { + EVP_CIPHER_CTX_ctrl( + self.0.as_ptr(), + openssl_sys::EVP_CTRL_GCM_GET_TAG, + tag.len() as c_int, + tag.as_mut_ptr() as *mut _, + ) > 0 + } + + /// Sets the authentication tag for verification during decryption. + #[allow(unused)] + pub unsafe fn set_tag(&self, tag: &[u8]) -> bool { + EVP_CIPHER_CTX_ctrl( + self.0.as_ptr(), + openssl_sys::EVP_CTRL_GCM_SET_TAG, + tag.len() as c_int, + tag.as_ptr() as *mut _, + ) > 0 + } + pub fn as_ptr(&self) -> *mut openssl_sys::EVP_CIPHER_CTX { + self.0.as_ptr() + } +} + +pub struct Aes256OpenSSLEnc(Mutex); +unsafe impl Send for Aes256OpenSSLEnc {} +unsafe impl Sync for Aes256OpenSSLEnc {} + +impl Aes256Enc for Aes256OpenSSLEnc { + fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self { + let ctx = CipherCtx::new().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_ecb(); + assert!(ctx.cipher_init::(t, key.as_ptr(), ptr::null())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + } + Self(Mutex::new(ctx)) + } + + fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]) { + let ctx = self.0.lock().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_ecb(); + assert!(ctx.cipher_init::(t, key.as_ptr(), ptr::null())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + } + } + + fn encrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]) { + let ptr = block.as_mut_ptr(); + let ctx = self.0.lock().unwrap(); + unsafe { assert!(ctx.update::(block, ptr)) } + } +} +pub struct Aes256OpenSSLDec(Mutex); +unsafe impl Send for Aes256OpenSSLDec {} +unsafe impl Sync for Aes256OpenSSLDec {} + +impl Aes256Dec for Aes256OpenSSLDec { + fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self { + let ctx = CipherCtx::new().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_ecb(); + assert!(ctx.cipher_init::(t, key.as_ptr(), ptr::null())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + } + Self(Mutex::new(ctx)) + } + + fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]) { + let ctx = self.0.lock().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_ecb(); + assert!(ctx.cipher_init::(t, key.as_ptr(), ptr::null())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + } + } + + fn decrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]) { + let ptr = block.as_mut_ptr(); + let ctx = self.0.lock().unwrap(); + unsafe { assert!(ctx.update::(block, ptr)) } + } +} + +pub struct AesGcmOpenSSLEnc(CipherCtx); +impl AesGcmEncContext for AesGcmOpenSSLEnc { + fn encrypt(&mut self, input: &[u8], output: &mut [u8]) { + unsafe { assert!(self.0.update::(input, output.as_mut_ptr())) }; + } + + fn finish(&mut self) -> [u8; AES_GCM_TAG_SIZE] { + let mut output = [0u8; AES_GCM_TAG_SIZE]; + unsafe { + assert!(self.0.finalize::()); + assert!(self.0.get_tag(&mut output)); + } + output + } +} + +pub struct AesGcmOpenSSLDec(CipherCtx); +impl AesGcmDecContext for AesGcmOpenSSLDec { + fn decrypt_in_place(&mut self, data: &mut [u8]) { + let p = data.as_mut_ptr(); + unsafe { assert!(self.0.update::(data, p)) }; + } + + fn finish(&mut self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool { + unsafe { self.0.set_tag(tag) && self.0.finalize::() } + } +} + +pub struct AesGcmOpenSSLPool(Zeroizing<[u8; AES_256_KEY_SIZE]>, Zeroizing<[u8; AES_256_KEY_SIZE]>); +impl HighThroughputAesGcmPool for AesGcmOpenSSLPool { + type EncContext<'a> = AesGcmOpenSSLEnc; + + type DecContext<'a> = AesGcmOpenSSLDec; + + fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self { + AesGcmOpenSSLPool(Zeroizing::new(*encrypt_key), Zeroizing::new(*decrypt_key)) + } + + fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> AesGcmOpenSSLEnc { + let ctx = CipherCtx::new().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_gcm(); + assert!(ctx.cipher_init::(t, self.0.as_ptr(), nonce.as_ptr())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + } + AesGcmOpenSSLEnc(ctx) + } + + fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> AesGcmOpenSSLDec { + let ctx = CipherCtx::new().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_gcm(); + assert!(ctx.cipher_init::(t, self.0.as_ptr(), nonce.as_ptr())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + } + AesGcmOpenSSLDec(ctx) + } +} + +pub struct AesGcmOpenSSL; +impl LowThroughputAesGcm for AesGcmOpenSSL { + fn encrypt_in_place( + key: &[u8; AES_256_KEY_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], + aad: &[u8], + data: &mut [u8], + ) -> [u8; AES_GCM_TAG_SIZE] { + let mut output = [0u8; AES_GCM_TAG_SIZE]; + let ctx = CipherCtx::new().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_gcm(); + assert!(ctx.cipher_init::(t, key.as_ptr(), nonce.as_ptr())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + + assert!(ctx.update::(aad, ptr::null_mut())); + let p = data.as_mut_ptr(); + assert!(ctx.update::(data, p)); + + assert!(ctx.finalize::()); + assert!(ctx.get_tag(&mut output)); + } + output + } + + fn decrypt_in_place( + key: &[u8; AES_256_KEY_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], + aad: &[u8], + data: &mut [u8], + tag: &[u8; AES_GCM_TAG_SIZE], + ) -> bool { + let ctx = CipherCtx::new().unwrap(); + unsafe { + let t = openssl_sys::EVP_aes_256_gcm(); + assert!(ctx.cipher_init::(t, key.as_ptr(), nonce.as_ptr())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + + assert!(ctx.update::(aad, ptr::null_mut())); + let p = data.as_mut_ptr(); + assert!(ctx.update::(data, p)); + + ctx.set_tag(tag) && ctx.finalize::() + } + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn aes_128_ecb() { + let key = [1u8; 16]; + let ctx = CipherCtx::new().unwrap(); + unsafe { + assert!(ctx.cipher_init::(openssl_sys::EVP_aes_128_ecb(), key.as_ptr(), ptr::null())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + assert_eq!(openssl_sys::EVP_CIPHER_CTX_get_block_size(ctx.as_ptr()) as usize, 16); + + let origin = [2u8; 16]; + let mut val = origin; + let p = val.as_mut_ptr(); + + assert!(ctx.update::(&val, p)); + assert!(ctx.cipher_init::(ptr::null(), key.as_ptr(), ptr::null())); + assert!(ctx.update::(&val, p)); + + assert_eq!(val, origin); + } + } +} diff --git a/src/crypto_impl/p384_impl.rs b/src/crypto_impl/p384_impl.rs index d65028a..77c0938 100644 --- a/src/crypto_impl/p384_impl.rs +++ b/src/crypto_impl/p384_impl.rs @@ -3,8 +3,8 @@ use rand_core::{CryptoRng, RngCore}; use crate::crypto::*; -pub type RustP384PublicKey = PublicKey; -impl P384PublicKey for PublicKey { +pub type P384CratePublicKey = PublicKey; +impl P384PublicKey for P384CratePublicKey { fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option { PublicKey::from_sec1_bytes(raw_key).ok() } @@ -15,8 +15,8 @@ impl P384PublicKey for PublicKey { } } -pub type RustP384KeyPair = EphemeralSecret; -impl P384KeyPair for RustP384KeyPair { +pub type P384CrateKeyPair = EphemeralSecret; +impl P384KeyPair for P384CrateKeyPair { type PublicKey = PublicKey; fn generate(rng: &mut Rng) -> Self { diff --git a/src/crypto_impl/sha512.rs b/src/crypto_impl/sha512.rs index 3b72b46..ba932f8 100644 --- a/src/crypto_impl/sha512.rs +++ b/src/crypto_impl/sha512.rs @@ -3,8 +3,8 @@ use sha2::{Digest, Sha512}; use crate::crypto::*; -pub type RustSha512 = Sha512; -impl Sha512Hash for RustSha512 { +pub type Sha512Crate = Sha512; +impl Sha512Hash for Sha512Crate { fn new() -> Self { Digest::new() } @@ -20,10 +20,10 @@ impl Sha512Hash for RustSha512 { } } -pub struct RustHmac; -impl Sha512Hmac for RustHmac { +pub struct HmacSha512Crate; +impl Sha512Hmac for HmacSha512Crate { fn new() -> Self { - RustHmac + HmacSha512Crate } fn hash(&mut self, key: &[u8], full_input: &[u8], output: &mut [u8; SHA512_HASH_SIZE]) { diff --git a/src/lib.rs b/src/lib.rs index 36c2fcd..626e58c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,15 +20,17 @@ mod log_event; pub use log_event::*; pub mod proto; -pub mod ratchet_state; +mod ratchet_state; pub mod result; mod symmetric_state; -pub mod zeta; -pub mod zssp; +mod zeta; +mod zssp; +pub use zeta::*; +pub use zssp::*; //pub mod error; //pub use crate::applicationlayer::ApplicationLayer; //pub use crate::log_event::LogEvent; -//pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_SIZE}; +//pub use crate::proto::{IDENTITY_MAX_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU}; //pub use crate::ratchet_state::RatchetState; //pub use crate::zssp::{Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 6a58549..71cf4d0 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -138,12 +138,7 @@ impl SymmetricState { self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } /// Corresponds to Noise `MixKeyAndHash`. - pub fn mix_key_and_hash_no_init( - &mut self, - hash: &mut App::Hash, - hmac: &mut App::Hmac, - input_key_material: &[u8], - ) { + pub fn mix_key_and_hash_no_init(&mut self, hash: &mut App::Hash, hmac: &mut App::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_h = [0u8; HASHLEN]; @@ -199,13 +194,7 @@ impl SymmetricState { /// is forward secrect and is cryptographically independent from all other produced keys. /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. - pub fn get_ask( - &self, - hmac: &mut App::Hmac, - label: &[u8; 4], - key1: &mut [u8; HASHLEN], - key2: &mut [u8; HASHLEN], - ) { + pub fn get_ask(&self, hmac: &mut App::Hmac, label: &[u8; 4], key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { self.kbkdf(hmac, &self.h, label, 2, key1, Some(key2), None); } /// Used for internally debugging a key exchange. diff --git a/src/zeta.rs b/src/zeta.rs index 3819c25..07f7c74 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -64,13 +64,13 @@ fn get_counter(session: &Session, state: &MutableSta None } else { let c = session.send_counter.fetch_add(1, Ordering::Relaxed); + if c > THREAD_SAFE_COUNTER_HARD_EXPIRE { + session.session_has_expired.store(true, Ordering::SeqCst); + } if c > state.key_creation_counter + EXPIRE_AFTER_USES { session.session_has_expired.store(true, Ordering::SeqCst); return None; } - if c > THREAD_SAFE_COUNTER_HARD_EXPIRE { - session.session_has_expired.store(true, Ordering::SeqCst); - } Some((c, c > state.key_creation_counter + App::SETTINGS.rekey_after_key_uses)) } } @@ -258,12 +258,7 @@ impl SymmetricState { None } } - fn mix_dh_no_init( - &mut self, - hmac: &mut App::Hmac, - secret: &App::KeyPair, - remote: &App::PublicKey, - ) -> Option<()> { + fn mix_dh_no_init(&mut self, hmac: &mut App::Hmac, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { self.mix_key_no_init(hmac, ecdh_secret.as_ref()); @@ -1765,20 +1760,15 @@ pub(crate) fn receive_payload_in_place( ) -> Result<(), ReceiveError> { use FaultType::*; - let is_other = if Some(kid) == state.key_ref(true).recv.kid { - true - } else if Some(kid) == state.key_ref(false).recv.kid { - false + let specified_key = if Some(kid) == state.keys[0].recv.kid { + state.keys[0].nk.as_ref() + } else if Some(kid) == state.keys[1].recv.kid { + state.keys[1].nk.as_ref() } else { return Err(byzantine_fault!(OutOfSequence, true)); }; - let mut cipher = state - .key_ref(is_other) - .nk - .as_ref() - .ok_or(byzantine_fault!(OutOfSequence, true))? - .start_dec(n); + let mut cipher = specified_key.ok_or(byzantine_fault!(OutOfSequence, true))?.start_dec(n); // NOTE: This only works because we check the size of every received fragment in the receive // function, otherwise this could panic. @@ -1794,6 +1784,7 @@ pub(crate) fn receive_payload_in_place( if cipher.finish((&fragment[tag_idx..]).try_into().unwrap()) { return Err(byzantine_fault!(FailedAuth, true)); } + drop(cipher); let (_, c) = from_nonce(n); if !session.window.update(c) { @@ -1802,7 +1793,6 @@ pub(crate) fn receive_payload_in_place( return Err(byzantine_fault!(ExpiredCounter, true)); } - drop(cipher); for fragment in fragments { let result = output_buffer.write(&fragment.as_ref()[HEADER_SIZE..]); if let Err(e) = result { @@ -1859,46 +1849,14 @@ impl Session { /// ///// The current ratchet state of this session. ///// The returned values are sensitive and should be securely erased before being dropped. - //pub fn ratchet_states(&self) -> [RatchetState; 2] { - // let state = self.state.read().unwrap(); - // state.ratchet_states.clone() - //} + pub fn ratchet_states(&self) -> RatchetStates { + let state = self.state.read().unwrap(); + RatchetStates::new(state.ratchet_state1.clone(), state.ratchet_state2.clone()) + } /// The current ratchet count of this session. - //pub fn ratchet_count(&self) -> u64 { - // self.state.read().unwrap(). - //} - /// Mark a session as expired. This will make it impossible for this session to successfully - /// receive or send data or control packets. It is recommended to simply `drop` the session - /// instead, but this can provide some reassurance in complex shared ownership situations. - //pub fn expire(&self) { - // if let Some(context) = self.context.upgrade() { - // self.expire_inner(&context, &mut context.session_queue.lock().unwrap()); - // } - //} - //fn expire_inner( - // &self, - // context: &Arc>, - // session_queue: &mut IndexedBinaryHeap>, Reverse>, - //) { - // // Prevent this session from being updated. - // session_queue.remove(self.queue_idx); - // self.session_has_expired.store(true, Ordering::Relaxed); - // let _kex_lock = self.state_machine_lock.lock().unwrap(); - // let mut state = self.state.write().unwrap(); - // let mut session_map = context.session_map.write().unwrap(); - // for key in &state.cipher_states { - // if let Some(pre_id) = key.as_ref().map(|k| k.local_key_id) { - // session_map.remove(&pre_id); - // } - // } - // use OfferStateMachine::*; - // match &state.outgoing_offer { - // NoiseXKPattern1or3(handshake_state) => session_map.remove(&handshake_state.local_key_id), - // NoiseKKPattern1 { new_key_id, .. } => session_map.remove(new_key_id), - // _ => None, - // }; - // state.outgoing_offer = OfferStateMachine::Null; - //} + pub fn ratchet_count(&self) -> u64 { + self.state.read().unwrap().ratchet_state1.chain_len + } /// Check whether this session is established. pub fn established(&self) -> bool { let state = self.state.read().unwrap(); diff --git a/src/zssp.rs b/src/zssp.rs index 78a7a30..29b90b3 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -98,13 +98,16 @@ fn send_with_fragmentation( let fragment_base_size = payload_len / fragment_count; let fragment_size_remainder = payload_len % fragment_count; + let mut header: [u8; HEADER_SIZE] = headered_packet[..HEADER_SIZE].try_into().unwrap(); + header[FRAGMENT_COUNT_IDX] = fragment_count as u8; + let mut i = HEADER_SIZE; for fragment_no in 0..fragment_count { let j = i + fragment_base_size + (fragment_no < fragment_size_remainder) as usize; let fragment = &mut headered_packet[i - HEADER_SIZE..j]; - fragment[FRAGMENT_NO_IDX] = fragment_no as u8; - fragment[FRAGMENT_COUNT_IDX] = fragment_count as u8; + header[FRAGMENT_NO_IDX] = fragment_no as u8; + fragment[..HEADER_SIZE].copy_from_slice(&header); if let Some(hk_send) = hk_send { hk_send.encrypt_in_place((&mut fragment[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap()); @@ -482,7 +485,7 @@ impl Context { // This can occur naturally because either Bob's incoming_sessions cache got // full so Alice's incoming session was dropped, or the session this packet // was for was dropped by the application. - return Err(byzantine_fault!(UnknownLocalKeyId, true)); + return Err(byzantine_fault!(UnknownLocalKeyId, false)); } } } else { @@ -626,10 +629,10 @@ impl Context { &self, app: App, mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, - current_time: i64, ) -> i64 { let ctx = &self.0; let mut session_queue = ctx.session_queue.lock().unwrap(); + let current_time = app.time(); let mut next_service_time = current_time + App::SETTINGS.fragment_assembly_timeout as i64; // This update system takes heavy advantage of the fact that sessions only need to be updated // either roughly every second or roughly every hour. That big gap allows for minor optimizations. @@ -668,6 +671,6 @@ impl Context { .check_for_expiry(App::SETTINGS.fragment_assembly_timeout as i64, current_time); self.0.unassociated_handshake_states.service(current_time); - next_service_time + next_service_time - current_time } } From 8e8aa4fa63695982ac1f3f44df9feb6cbcecf152 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 14:40:04 -0400 Subject: [PATCH 65/91] got it working --- examples/basic_test.rs | 32 ++++++++++----- src/crypto/aes.rs | 4 +- src/crypto_impl/openssl.rs | 26 +++++++------ src/proto.rs | 6 +-- src/zeta.rs | 80 +++++++++++++++++++++++++------------- src/zssp.rs | 17 +++++--- 6 files changed, 108 insertions(+), 57 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 71fde87..e1d1a35 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -18,12 +18,13 @@ use rand_core::OsRng; use rand_core::RngCore; use zssp::application::{ - AcceptAction, ApplicationLayer, RatchetState, RatchetStates, RatchetUpdate, Settings, RATCHET_SIZE, IncomingSessionAction, + AcceptAction, ApplicationLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, Settings, + RATCHET_SIZE, }; use zssp::crypto::P384KeyPair; use zssp::crypto_impl::*; -use zssp::Session; use zssp::result::ReceiveError; +use zssp::Session; const TEST_MTU: usize = 1500; @@ -71,7 +72,6 @@ impl ApplicationLayer for &TestApplication { type IncomingPacketBuffer = Vec; - fn incoming_session(&self) -> IncomingSessionAction { IncomingSessionAction::Allow } @@ -189,7 +189,7 @@ fn alice_main( |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)), &0, pkt, - &mut output_data + &mut output_data, ) { Ok(Unassociated) => { //println!("[alice] ok"); @@ -234,13 +234,15 @@ fn alice_main( thread::sleep(Duration::from_millis(10)); } // TODO: we need to more comprehensively test if re-opening the session works - if OsRng.next_u32() <= ((u32::MAX as f64) * 0.000005) as u32 { + if OsRng.next_u32() <= ((u32::MAX as f64) * 0.0000005) as u32 { alice_session = None; } if current_time >= next_service { - next_service = - current_time + context.service(alice_app, |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU))); + next_service = current_time + + context.service(alice_app, |_| { + Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)) + }); } } } @@ -278,7 +280,7 @@ fn bob_main( |_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)), &0, pkt, - &mut output_data + &mut output_data, ) { Ok(Unassociated) => {} Ok(Session(s, event)) => match event { @@ -290,7 +292,14 @@ fn bob_main( assert!(!output_data.is_empty()); //println!("[bob] received {}", output_data.len()); transferred += output_data.len() as u64 * 2; // *2 because we are also sending this many bytes back - context.send(&s, |b| bob_out.send(b.to_vec()).is_ok(), &mut [0u8; TEST_MTU], &output_data).unwrap(); + context + .send( + &s, + |b| bob_out.send(b.to_vec()).is_ok(), + &mut [0u8; TEST_MTU], + &output_data, + ) + .unwrap(); } Established => panic!(), Rejected => panic!(), @@ -319,7 +328,10 @@ fn bob_main( } if current_time >= next_service { - next_service = current_time + context.service(bob_app, |_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU))); + next_service = current_time + + context.service(bob_app, |_| { + Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)) + }); } } } diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index 9935564..e0e3752 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -42,14 +42,14 @@ pub trait Aes256Dec: Send + Sync { pub trait AesGcmEncContext { fn encrypt(&mut self, input: &[u8], output: &mut [u8]); - fn finish(&mut self) -> [u8; AES_GCM_TAG_SIZE]; + fn finish(self) -> [u8; AES_GCM_TAG_SIZE]; } pub trait AesGcmDecContext { fn decrypt_in_place(&mut self, data: &mut [u8]); #[must_use] - fn finish(&mut self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; + fn finish(self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; } pub trait HighThroughputAesGcmPool: Send + Sync { diff --git a/src/crypto_impl/openssl.rs b/src/crypto_impl/openssl.rs index fb17bf9..596352f 100644 --- a/src/crypto_impl/openssl.rs +++ b/src/crypto_impl/openssl.rs @@ -75,8 +75,6 @@ impl CipherCtx { tag.as_mut_ptr() as *mut _, ) > 0 } - - /// Sets the authentication tag for verification during decryption. #[allow(unused)] pub unsafe fn set_tag(&self, tag: &[u8]) -> bool { EVP_CIPHER_CTX_ctrl( @@ -158,7 +156,7 @@ impl AesGcmEncContext for AesGcmOpenSSLEnc { unsafe { assert!(self.0.update::(input, output.as_mut_ptr())) }; } - fn finish(&mut self) -> [u8; AES_GCM_TAG_SIZE] { + fn finish(self) -> [u8; AES_GCM_TAG_SIZE] { let mut output = [0u8; AES_GCM_TAG_SIZE]; unsafe { assert!(self.0.finalize::()); @@ -175,26 +173,32 @@ impl AesGcmDecContext for AesGcmOpenSSLDec { unsafe { assert!(self.0.update::(data, p)) }; } - fn finish(&mut self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool { + fn finish(self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool { unsafe { self.0.set_tag(tag) && self.0.finalize::() } } } -pub struct AesGcmOpenSSLPool(Zeroizing<[u8; AES_256_KEY_SIZE]>, Zeroizing<[u8; AES_256_KEY_SIZE]>); +pub struct AesGcmOpenSSLPool { + enc_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, + dec_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, +} impl HighThroughputAesGcmPool for AesGcmOpenSSLPool { type EncContext<'a> = AesGcmOpenSSLEnc; type DecContext<'a> = AesGcmOpenSSLDec; fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self { - AesGcmOpenSSLPool(Zeroizing::new(*encrypt_key), Zeroizing::new(*decrypt_key)) + AesGcmOpenSSLPool { + enc_key: Zeroizing::new(*encrypt_key), + dec_key: Zeroizing::new(*decrypt_key), + } } fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> AesGcmOpenSSLEnc { let ctx = CipherCtx::new().unwrap(); unsafe { let t = openssl_sys::EVP_aes_256_gcm(); - assert!(ctx.cipher_init::(t, self.0.as_ptr(), nonce.as_ptr())); + assert!(ctx.cipher_init::(t, self.enc_key.as_ptr(), nonce.as_ptr())); openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); } AesGcmOpenSSLEnc(ctx) @@ -204,7 +208,7 @@ impl HighThroughputAesGcmPool for AesGcmOpenSSLPool { let ctx = CipherCtx::new().unwrap(); unsafe { let t = openssl_sys::EVP_aes_256_gcm(); - assert!(ctx.cipher_init::(t, self.0.as_ptr(), nonce.as_ptr())); + assert!(ctx.cipher_init::(t, self.dec_key.as_ptr(), nonce.as_ptr())); openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); } AesGcmOpenSSLDec(ctx) @@ -246,12 +250,12 @@ impl LowThroughputAesGcm for AesGcmOpenSSL { let ctx = CipherCtx::new().unwrap(); unsafe { let t = openssl_sys::EVP_aes_256_gcm(); - assert!(ctx.cipher_init::(t, key.as_ptr(), nonce.as_ptr())); + assert!(ctx.cipher_init::(t, key.as_ptr(), nonce.as_ptr())); openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); - assert!(ctx.update::(aad, ptr::null_mut())); + assert!(ctx.update::(aad, ptr::null_mut())); let p = data.as_mut_ptr(); - assert!(ctx.update::(data, p)); + assert!(ctx.update::(data, p)); ctx.set_tag(tag) && ctx.finalize::() } diff --git a/src/proto.rs b/src/proto.rs index 74ae800..8d20ca5 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -119,7 +119,7 @@ pub(crate) const PACKET_TYPE_USES_COUNTER_RANGE: std::ops::Range = 3..9; pub(crate) const HANDSHAKE_HELLO_MIN_SIZE: usize = KID_SIZE + P384_PUBLIC_KEY_SIZE + KYBER_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; -pub(crate) const HANDSHAKE_HELLO_MAX_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + RATCHET_SIZE; +pub(crate) const HANDSHAKE_HELLO_MAX_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + RATCHET_SIZE + RATCHET_SIZE; pub(crate) const HANDSHAKE_HELLO_CHALLENGE_MIN_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + CHALLENGE_SIZE; pub(crate) const HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE: usize = HANDSHAKE_HELLO_MAX_SIZE + CHALLENGE_SIZE; @@ -145,7 +145,7 @@ pub(crate) const SESSION_REJECTED_SIZE: usize = AES_GCM_TAG_SIZE; pub(crate) const HEADERED_SESSION_REJECTED_SIZE: usize = SESSION_REJECTED_SIZE + HEADER_SIZE; pub(crate) const REKEY_SIZE: usize = P384_PUBLIC_KEY_SIZE + KID_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; -pub(crate) const HEADERED_REKEY_SIZE: usize = P384_PUBLIC_KEY_SIZE + KID_SIZE + AES_GCM_TAG_SIZE + AES_GCM_TAG_SIZE; +pub(crate) const HEADERED_REKEY_SIZE: usize = REKEY_SIZE + HEADER_SIZE; /// The application has the ability to attach a data payload to Alice's handshake. /// It will be the first payload Bob receives from Alice. @@ -177,6 +177,6 @@ pub(crate) const MAX_UNASSOCIATED_FRAGMENTS: usize = 32 * 32; /// The maximum size a packet that is not associated to a session may be. /// Excludes the size of headers for fragmentation. -pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = HANDSHAKE_HELLO_MAX_SIZE; +pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE; pub(crate) const SESSION_MAX_FRAGMENTS_OOO: usize = 64; diff --git a/src/zeta.rs b/src/zeta.rs index 07f7c74..8bba2b6 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -185,10 +185,10 @@ pub(crate) enum ZetaAutomata { R1 { noise: SymmetricState, e_secret: App::KeyPair, - k1: ArrayVec, + k1: ArrayVec, }, R2 { - k2: ArrayVec, + k2: ArrayVec, }, } @@ -323,7 +323,6 @@ fn create_a1_state( let i = x1.len(); let (e1_secret, e1_public) = App::Kem::generate(rng.lock().unwrap().deref_mut()); x1.extend(e1_public); - x1.extend([0u8; AES_GCM_NONCE_SIZE]); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..]); x1.extend(tag); // Process message pattern 1 payload. @@ -466,7 +465,7 @@ pub(crate) fn received_x1_trans( // ... // -> e, es, e1 // <- e, ee, ekem1, psk - if !(HANDSHAKE_HELLO_CHALLENGE_MIN_SIZE..=HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE).contains(&x1.len()) { + if !(HANDSHAKE_HELLO_MIN_SIZE..=HANDSHAKE_HELLO_MAX_SIZE).contains(&x1.len()) { return Err(byzantine_fault!(InvalidPacket, true)); } @@ -493,14 +492,14 @@ pub(crate) fn received_x1_trans( .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 e1 token. let j = i + KYBER_PUBLIC_KEY_SIZE; - let k = i + AES_GCM_TAG_SIZE; + let k = j + AES_GCM_TAG_SIZE; let tag = x1[j..k].try_into().unwrap(); if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let e1_start = i; let e1_end = j; - i = j; + i = k; // Process message pattern 1 payload. let k = x1.len(); let j = k - AES_GCM_TAG_SIZE; @@ -529,9 +528,10 @@ pub(crate) fn received_x1_trans( } RatchetState::empty() }; + let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); let mut hk_send = Zeroizing::new([0u8; HASHLEN]); - noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_recv, &mut hk_send); + noise.get_ask(hmac, LABEL_HEADER_KEY, &mut hk_send, &mut hk_recv); let mut x2 = ArrayVec::::new(); x2.extend([0u8; HEADER_SIZE]); @@ -660,7 +660,7 @@ pub(crate) fn received_x2_trans( } noise.mix_key(hmac, ekem1_secret.as_ref()); drop(ekem1_secret); - i = j; + i = k; // We attempt to decrypt the payload at most three times. First two times with // the ratchet key Alice remembers, and final time with a ratchet // key of zero if Alice allows ratchet downgrades. @@ -1705,19 +1705,22 @@ pub(crate) fn send_payload( let payload_mtu = mtu - HEADER_SIZE; debug_assert!(payload_mtu >= 4); - let fragment_count = payload.len().saturating_add(payload_mtu - 1) / payload_mtu; // Ceiling div. - let fragment_base_size = payload.len() / fragment_count; - let fragment_size_remainder = payload.len() % fragment_count; + let tagged_payload_len = payload.len() + AES_GCM_TAG_SIZE; + let fragment_count = tagged_payload_len.saturating_add(payload_mtu - 1) / payload_mtu; // Ceiling div. + let fragment_base_size = tagged_payload_len / fragment_count; + let fragment_size_remainder = tagged_payload_len % fragment_count; - mtu_sized_buffer[..KID_SIZE].copy_from_slice(&key.send.kid.unwrap().get().to_be_bytes()); - mtu_sized_buffer[FRAGMENT_COUNT_IDX] = fragment_count as u8; - mtu_sized_buffer[PACKET_NONCE_START..].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); + let mut header = [0u8; HEADER_SIZE]; + header[..KID_SIZE].copy_from_slice(&key.send.kid.unwrap().get().to_be_bytes()); + header[FRAGMENT_COUNT_IDX] = fragment_count as u8; + header[PACKET_NONCE_START..].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); let mut i = 0; - for fragment_no in 0..fragment_count { + for fragment_no in 0..fragment_count - 1 { let fragment_len = fragment_base_size + (fragment_no < fragment_size_remainder) as usize; let j = i + fragment_len; + mtu_sized_buffer[..HEADER_SIZE].copy_from_slice(&header); mtu_sized_buffer[FRAGMENT_NO_IDX] = fragment_no as u8; cipher.encrypt( &payload[i..j], @@ -1735,7 +1738,29 @@ pub(crate) fn send_payload( } i = j; } - drop(cipher); + let fragment_no = fragment_count - 1; + let payload_rem = payload.len() - i; + let fragment_len = payload_rem + AES_GCM_TAG_SIZE; + debug_assert_eq!(fragment_len, fragment_base_size); + + mtu_sized_buffer[..HEADER_SIZE].copy_from_slice(&header); + mtu_sized_buffer[FRAGMENT_NO_IDX] = fragment_no as u8; + cipher.encrypt( + &payload[i..], + &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + payload_rem], + ); + mtu_sized_buffer[HEADER_SIZE + payload_rem..HEADER_SIZE + fragment_len].copy_from_slice(&cipher.finish()); + + state.hk_send.encrypt_in_place( + (&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]) + .try_into() + .unwrap(), + ); + + if !send(&mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { + return Ok(()); + } + drop(state); if should_rekey { @@ -1754,11 +1779,12 @@ pub(crate) fn receive_payload_in_place( session: &Arc>, state: RwLockReadGuard<'_, MutableState>, kid: NonZeroU32, - n: &[u8; AES_GCM_NONCE_SIZE], + nonce: &[u8; AES_GCM_NONCE_SIZE], fragments: &mut [App::IncomingPacketBuffer], mut output_buffer: impl Write, ) -> Result<(), ReceiveError> { use FaultType::*; + debug_assert!(!fragments.is_empty()); let specified_key = if Some(kid) == state.keys[0].recv.kid { state.keys[0].nk.as_ref() @@ -1768,25 +1794,27 @@ pub(crate) fn receive_payload_in_place( return Err(byzantine_fault!(OutOfSequence, true)); }; - let mut cipher = specified_key.ok_or(byzantine_fault!(OutOfSequence, true))?.start_dec(n); + let mut cipher = specified_key + .ok_or(byzantine_fault!(OutOfSequence, true))? + .start_dec(nonce); + let (_, c) = from_nonce(nonce); // NOTE: This only works because we check the size of every received fragment in the receive // function, otherwise this could panic. - let mut i = 0; - while i + 1 < fragments.len() { + for i in 0..fragments.len() - 1 { let fragment = &mut fragments[i].as_mut()[HEADER_SIZE..]; + debug_assert!(fragment.len() >= AES_GCM_TAG_SIZE); cipher.decrypt_in_place(fragment); - i += 1; } - let fragment = &mut fragments[i].as_mut()[HEADER_SIZE..]; - let tag_idx = fragment.len() - AES_GCM_NONCE_SIZE; + let fragment = &mut fragments[fragments.len() - 1].as_mut()[HEADER_SIZE..]; + debug_assert!(fragment.len() >= AES_GCM_TAG_SIZE); + let tag_idx = fragment.len() - AES_GCM_TAG_SIZE; cipher.decrypt_in_place(&mut fragment[..tag_idx]); - if cipher.finish((&fragment[tag_idx..]).try_into().unwrap()) { + + if !cipher.finish((&fragment[tag_idx..]).try_into().unwrap()) { return Err(byzantine_fault!(FailedAuth, true)); } - drop(cipher); - let (_, c) = from_nonce(n); if !session.window.update(c) { // This error is marked as not happening naturally, but it could occur if something about // the transport protocol is duplicating packets. diff --git a/src/zssp.rs b/src/zssp.rs index 29b90b3..17c24fe 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -106,8 +106,8 @@ fn send_with_fragmentation( let j = i + fragment_base_size + (fragment_no < fragment_size_remainder) as usize; let fragment = &mut headered_packet[i - HEADER_SIZE..j]; - header[FRAGMENT_NO_IDX] = fragment_no as u8; fragment[..HEADER_SIZE].copy_from_slice(&header); + fragment[FRAGMENT_NO_IDX] = fragment_no as u8; if let Some(hk_send) = hk_send { hk_send.encrypt_in_place((&mut fragment[HEADER_AUTH_START..HEADER_AUTH_END]).try_into().unwrap()); @@ -536,11 +536,11 @@ impl Context { return Err(byzantine_fault!(InvalidPacket, true)); } // Process recv challenge layer. + let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; let hash = &mut App::Hash::new(); match app.incoming_session() { IncomingSessionAction::Allow => {} IncomingSessionAction::Challenge => { - let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; let result = ctx.challenge.process_hello( hash, remote_address, @@ -569,9 +569,16 @@ impl Context { } // Process recv zeta layer. - received_x1_trans(&app, ctx, hash, &nonce, assembled_packet, |packet, hk_send| { - send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); - })?; + received_x1_trans( + &app, + ctx, + hash, + &nonce, + &mut assembled_packet[..challenge_start], + |packet, hk_send| { + send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); + }, + )?; log!(app, X1IsAuthSentX2); Ok(ReceiveOk::Unassociated) From de04653f606ead4947fe11b5fbdd2b6fb7efef0d Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 14:52:47 -0400 Subject: [PATCH 66/91] refactored dependencies --- Cargo.toml | 7 +++---- examples/basic_test.rs | 8 -------- src/crypto/mod.rs | 1 + src/crypto_impl/mod.rs | 23 ++++++++++++++--------- src/frag_cache.rs | 8 -------- src/fragged.rs | 8 -------- src/handshake_cache.rs | 8 -------- src/lib.rs | 17 +++++------------ src/zssp.rs | 10 ---------- 9 files changed, 23 insertions(+), 67 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e291b16..48a75dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,12 +19,11 @@ pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber102 p384 = { version = "0.13.0", default-features = false, features = ["ecdh"], optional = true } sha2 = { version = "0.10.7", default-features = false, optional = true } hmac = { version = "0.12.1", default-features = false, optional = true } -openssl-sys = { version = "0.9.91", default-features = false } +openssl-sys = { version = "0.9.91", default-features = false, optional = true } [features] -default = ["debug", "p384", "hmac", "pqc_kyber"] -sha2 = ["dep:sha2"] -hmac = ["dep:hmac", "sha2"] +default = ["debug", "p384", "sha2", "pqc_kyber", "openssl-sys"] +sha2 = ["dep:sha2", "dep:hmac"] logging = [] debug = ["logging"] diff --git a/examples/basic_test.rs b/examples/basic_test.rs index e1d1a35..8e45b0e 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -1,11 +1,3 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - use std::collections::HashMap; use std::iter::ExactSizeIterator; use std::str::FromStr; diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 16279f0..0bd3beb 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -14,6 +14,7 @@ pub use kyber1024::*; // exact version of them. pub use rand_core; pub use zeroize; +pub use arrayvec; /// Constant time byte slice equality. pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs index d414a06..cdd11cd 100644 --- a/src/crypto_impl/mod.rs +++ b/src/crypto_impl/mod.rs @@ -2,23 +2,28 @@ mod kyber1024; #[cfg(feature = "pqc_kyber")] pub use kyber1024::*; +#[cfg(feature = "pqc_kyber")] +pub use pqc_kyber; + #[cfg(feature = "p384")] mod p384_impl; #[cfg(feature = "p384")] pub use p384_impl::*; +#[cfg(feature = "p384")] +pub use p384; + #[cfg(feature = "sha2")] mod sha512; #[cfg(feature = "sha2")] pub use sha512::*; - -mod openssl; -pub use openssl::*; - -#[cfg(feature = "hmac")] +#[cfg(feature = "sha2")] pub use hmac; -#[cfg(feature = "p384")] -pub use p384; -#[cfg(feature = "pqc_kyber")] -pub use pqc_kyber; #[cfg(feature = "sha2")] pub use sha2; + +#[cfg(feature = "openssl-sys")] +mod openssl; +#[cfg(feature = "openssl-sys")] +pub use openssl::*; +#[cfg(feature = "openssl-sys")] +pub use openssl_sys; diff --git a/src/frag_cache.rs b/src/frag_cache.rs index 2423cda..e9f15b9 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -1,11 +1,3 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - use std::collections::hash_map::RandomState; use std::hash::{BuildHasher, Hash, Hasher}; use std::mem::MaybeUninit; diff --git a/src/fragged.rs b/src/fragged.rs index 54b90d9..b3c293a 100644 --- a/src/fragged.rs +++ b/src/fragged.rs @@ -1,11 +1,3 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - use arrayvec::ArrayVec; use std::mem::{needs_drop, zeroed, MaybeUninit}; diff --git a/src/handshake_cache.rs b/src/handshake_cache.rs index 75b4aa4..3939cc7 100644 --- a/src/handshake_cache.rs +++ b/src/handshake_cache.rs @@ -1,11 +1,3 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - use std::num::NonZeroU32; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; diff --git a/src/lib.rs b/src/lib.rs index 626e58c..cb8e8c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,28 +9,21 @@ pub mod crypto; pub mod crypto_impl; mod antireplay; -pub mod application; mod challenge; mod frag_cache; mod fragged; mod handshake_cache; mod indexed_heap; - mod log_event; -pub use log_event::*; - -pub mod proto; mod ratchet_state; -pub mod result; mod symmetric_state; mod zeta; mod zssp; +pub mod proto; +pub mod result; +pub mod application; + +pub use log_event::*; pub use zeta::*; pub use zssp::*; -//pub mod error; -//pub use crate::applicationlayer::ApplicationLayer; -//pub use crate::log_event::LogEvent; -//pub use crate::proto::{IDENTITY_MAX_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU}; -//pub use crate::ratchet_state::RatchetState; -//pub use crate::zssp::{Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/zssp.rs b/src/zssp.rs index 17c24fe..dd0961c 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -1,13 +1,3 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public -* License, v. 2.0. If a copy of the MPL was not distributed with this -* file, You can obtain one at https://mozilla.org/MPL/2.0/. -* -* (c) ZeroTier, Inc. -* https://www.zerotier.com/ -*/ -// ZSSP: ZeroTier Secure Session Protocol -// FIPS compliant Noise_XK with Jedi powers (Kyber1024) and built-in attack-resistant large payload (fragmentation) support. - use std::cmp::Reverse; use std::collections::HashMap; use std::hash::Hash; From 597e5b29b43499d315fba949b3ddf2ce0ebc3a59 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 15:48:59 -0400 Subject: [PATCH 67/91] fixed wrong constant bug --- examples/basic_test.rs | 12 +++++++----- src/proto.rs | 2 +- src/zssp.rs | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 8e45b0e..537cc02 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -132,6 +132,7 @@ impl ApplicationLayer for &TestApplication { } } +#[allow(unused)] fn alice_main( run: &AtomicBool, packet_success_rate: u32, @@ -205,8 +206,8 @@ fn alice_main( } } } - } else if OsRng.next_u32() | 1 > 0 { - let _ = recursive_out.send(pkt); + //} else if OsRng.next_u32() | 1 > 0 { + // let _ = recursive_out.send(pkt); } } else { break; @@ -239,6 +240,7 @@ fn alice_main( } } +#[allow(unused)] fn bob_main( run: &AtomicBool, packet_success_rate: u32, @@ -304,8 +306,8 @@ fn bob_main( } } } - } else if OsRng.next_u32() | 1 > 0 { - let _ = recursive_out.try_send(pkt); + //} else if OsRng.next_u32() | 1 > 0 { + // let _ = recursive_out.try_send(pkt); } } @@ -387,7 +389,7 @@ fn core(time: u64, packet_success_rate: u32) { fn main() { let args = std::env::args(); let packet_success_rate = if args.len() <= 1 { - let default_success_rate = 1.0; + let default_success_rate = 0.5; ((u32::MAX as f64) * default_success_rate) as u32 } else { ((u32::MAX as f64) * f64::from_str(args.last().unwrap().as_str()).unwrap()) as u32 diff --git a/src/proto.rs b/src/proto.rs index 8d20ca5..e5a9249 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -92,7 +92,7 @@ pub(crate) const EXPIRE_AFTER_USES: u64 = 1 << 32 - 1; /// this amount out of order relative to other received counters, it is likely to be /// rejected on the basis that the session can't remember if this counter was replayed. /// Increasing this value makes a session consume more memory. -pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 64; +pub(crate) const COUNTER_WINDOW_MAX_OOO: usize = 128; /// Maximum number of counter steps that the counter is allowed to skip ahead. /// This cannot be changed away from 2^24 without changing the header nonce handling code. pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 1 << 24; diff --git a/src/zssp.rs b/src/zssp.rs index dd0961c..95a2398 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -490,7 +490,7 @@ impl Context { } } - let mut buffer = ArrayVec::::new(); + let mut buffer = ArrayVec::::new(); let assembled_packet = if fragment_count > 1 { self.0.unassociated_defrag_cache.lock().unwrap().assemble( &nonce, From 23941c979765311e5831d9613987bdc6cf3a7381 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 8 Aug 2023 16:03:46 -0400 Subject: [PATCH 68/91] removed some key inits --- examples/basic_test.rs | 2 +- src/zeta.rs | 60 +++++++++++------------------------------- 2 files changed, 17 insertions(+), 45 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 537cc02..7befb32 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -389,7 +389,7 @@ fn core(time: u64, packet_success_rate: u32) { fn main() { let args = std::env::args(); let packet_success_rate = if args.len() <= 1 { - let default_success_rate = 0.5; + let default_success_rate = 1.0; ((u32::MAX as f64) * default_success_rate) as u32 } else { ((u32::MAX as f64) * f64::from_str(args.last().unwrap().as_str()).unwrap()) as u32 diff --git a/src/zeta.rs b/src/zeta.rs index 8bba2b6..5649a59 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -193,34 +193,6 @@ pub(crate) enum ZetaAutomata { } impl SymmetricState { - fn write_e( - &mut self, - hash: &mut App::Hash, - hmac: &mut App::Hmac, - rng: &Mutex, - packet: &mut ArrayVec, - ) -> App::KeyPair { - let e_secret = App::KeyPair::generate(rng.lock().unwrap().deref_mut()); - let pub_key = e_secret.public_key_bytes(); - packet.extend(pub_key); - self.mix_hash(hash, &pub_key); - self.mix_key(hmac, &pub_key); - e_secret - } - fn read_e( - &mut self, - hash: &mut App::Hash, - hmac: &mut App::Hmac, - i: &mut usize, - packet: &[u8], - ) -> Option { - let j = *i + P384_PUBLIC_KEY_SIZE; - let pub_key = &packet[*i..j]; - self.mix_hash(hash, pub_key); - self.mix_key(hmac, pub_key); - *i = j; - App::PublicKey::from_bytes((pub_key).try_into().unwrap()) - } fn write_e_no_init( &mut self, hash: &mut App::Hash, @@ -316,7 +288,7 @@ fn create_a1_state( noise.mix_hash(hash, &kid); noise.mix_hash(hash, &s_remote.to_bytes()); // Process message pattern 1 e token. - let e_secret = noise.write_e(hash, hmac, rng, &mut x1); + let e_secret = noise.write_e_no_init(hash, hmac, rng, &mut x1); // Process message pattern 1 es token. noise.mix_dh(hmac, &e_secret, s_remote)?; // Process message pattern 1 e1 token. @@ -484,7 +456,7 @@ pub(crate) fn received_x1_trans( i = j; // Process message pattern 1 e token. let e_remote = noise - .read_e(hash, hmac, &mut i, &x1) + .read_e_no_init(hash, hmac, &mut i, &x1) .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. noise @@ -536,7 +508,7 @@ pub(crate) fn received_x1_trans( let mut x2 = ArrayVec::::new(); x2.extend([0u8; HEADER_SIZE]); // Process message pattern 2 e token. - let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut x2); + let e_secret = noise.write_e_no_init(hash, hmac, &ctx.rng, &mut x2); // Process message pattern 2 ee token. noise .mix_dh(hmac, &e_secret, &e_remote) @@ -554,7 +526,7 @@ pub(crate) fn received_x1_trans( x2.extend(ekem1); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..]); x2.extend(tag); - noise.mix_key(hmac, ekem1_secret.as_ref()); + noise.mix_key_no_init(hmac, ekem1_secret.as_ref()); } // Process message pattern 2 psk2 token. noise.mix_key_and_hash(hash, hmac, ratchet_state.key.as_ref()); @@ -638,7 +610,7 @@ pub(crate) fn received_x2_trans( let mut i = 0; // Process message pattern 2 e token. let e_remote = noise - .read_e(hash, hmac, &mut i, &x2) + .read_e_no_init(hash, hmac, &mut i, &x2) .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. noise @@ -658,7 +630,7 @@ pub(crate) fn received_x2_trans( { return Err(byzantine_fault!(FailedAuth, true)); } - noise.mix_key(hmac, ekem1_secret.as_ref()); + noise.mix_key_no_init(hmac, ekem1_secret.as_ref()); drop(ekem1_secret); i = k; // We attempt to decrypt the payload at most three times. First two times with @@ -1262,11 +1234,11 @@ fn timeout_trans( noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); noise.mix_hash(hash, &session.s_remote.to_bytes()); // Process message pattern 1 psk0 token. - noise.mix_key_and_hash(hash, hmac, state.ratchet_state1.key.as_ref()); + noise.mix_key_and_hash_no_init(hash, hmac, state.ratchet_state1.key.as_ref()); // Process message pattern 1 e token. - let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut k1); + let e_secret = noise.write_e_no_init(hash, hmac, &ctx.rng, &mut k1); // Process message pattern 1 es token. - if noise.mix_dh(hmac, &e_secret, &session.s_remote).is_none() { + if noise.mix_dh_no_init(hmac, &e_secret, &session.s_remote).is_none() { return None; } // Process message pattern 1 ss token. @@ -1439,14 +1411,14 @@ pub(crate) fn received_k1_trans( noise.mix_hash(hash, &session.s_remote.to_bytes()); noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); // Process message pattern 1 psk0 token. - noise.mix_key_and_hash(hash, hmac, state.ratchet_state1.key.as_ref()); + noise.mix_key_and_hash_no_init(hash, hmac, state.ratchet_state1.key.as_ref()); // Process message pattern 1 e token. let e_remote = noise - .read_e(hash, hmac, &mut i, &k1) + .read_e_no_init(hash, hmac, &mut i, &k1) .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 es token. noise - .mix_dh(hmac, &ctx.s_secret, &e_remote) + .mix_dh_no_init(hmac, &ctx.s_secret, &e_remote) .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 1 ss token. noise.mix_key(hmac, session.noise_kk_ss.as_ref()); @@ -1463,10 +1435,10 @@ pub(crate) fn received_k1_trans( let mut k2 = ArrayVec::::new(); k2.extend([0u8; HEADER_SIZE]); // Process message pattern 2 e token. - let e_secret = noise.write_e(hash, hmac, &ctx.rng, &mut k2); + let e_secret = noise.write_e_no_init(hash, hmac, &ctx.rng, &mut k2); // Process message pattern 2 ee token. noise - .mix_dh(hmac, &e_secret, &e_remote) + .mix_dh_no_init(hmac, &e_secret, &e_remote) .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 se token. noise @@ -1590,11 +1562,11 @@ pub(crate) fn received_k2_trans( let hmac = &mut App::Hmac::new(); // Process message pattern 2 e token. let e_remote = noise - .read_e(hash, hmac, &mut i, &k2) + .read_e_no_init(hash, hmac, &mut i, &k2) .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 ee token. noise - .mix_dh(hmac, e_secret, &e_remote) + .mix_dh_no_init(hmac, e_secret, &e_remote) .ok_or(byzantine_fault!(FailedAuth, true))?; // Process message pattern 2 se token. noise From 5cd2d8f45f8705646bd529915ef50f5a3cf0386f Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 9 Aug 2023 12:07:59 -0400 Subject: [PATCH 69/91] added downgrade warning --- examples/basic_test.rs | 16 +++++++--------- src/crypto/aes.rs | 12 ++++++++---- src/crypto/mod.rs | 2 +- src/crypto_impl/mod.rs | 8 ++++---- src/lib.rs | 8 ++++---- src/result.rs | 8 ++++++-- src/zeta.rs | 42 +++++++++++++++++++++--------------------- src/zssp.rs | 30 +++++++++++++++++++++--------- 8 files changed, 72 insertions(+), 54 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 7befb32..3bbf1f0 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -195,9 +195,8 @@ fn alice_main( assert!(!output_data.is_empty()); //println!("[alice] received {}", data.len()); } - NewSession => panic!(), - Rejected => panic!(), Control => (), + _ => panic!(), }, Err(e) => { println!("[alice] ERROR {:?}", e); @@ -206,8 +205,8 @@ fn alice_main( } } } - //} else if OsRng.next_u32() | 1 > 0 { - // let _ = recursive_out.send(pkt); + //} else if OsRng.next_u32() | 1 > 0 { + // let _ = recursive_out.send(pkt); } } else { break; @@ -278,7 +277,7 @@ fn bob_main( ) { Ok(Unassociated) => {} Ok(Session(s, event)) => match event { - NewSession => { + NewSession | NewDowngradedSession => { println!("[bob] new session, took {}s", current_time as f32 / 1000.0); let _ = bob_session.replace(s); } @@ -295,9 +294,8 @@ fn bob_main( ) .unwrap(); } - Established => panic!(), - Rejected => panic!(), Control => (), + _ => panic!(), }, Err(e) => { println!("[bob] ERROR {:?}", e); @@ -306,8 +304,8 @@ fn bob_main( } } } - //} else if OsRng.next_u32() | 1 > 0 { - // let _ = recursive_out.try_send(pkt); + //} else if OsRng.next_u32() | 1 > 0 { + // let _ = recursive_out.try_send(pkt); } } diff --git a/src/crypto/aes.rs b/src/crypto/aes.rs index e0e3752..7921c30 100644 --- a/src/crypto/aes.rs +++ b/src/crypto/aes.rs @@ -10,12 +10,14 @@ pub const AES_GCM_NONCE_SIZE: usize = 12; /// algorithm is secure. /// /// Instances must securely delete their keys when dropped or reset. -pub trait Aes256Enc: Send + Sync { +pub trait Aes256Enc: Sized + Send + Sync { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self; /// Change the encryption key to `key` so that all future encryption is performed with it. /// This function is very rarely called so it does not have to be particularly efficient. - fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]); + fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]) { + *self = Self::new(key); + } /// Decrypt the given `block` of plaintext directly using the AES block cipher /// (i.e. AES-256 in zero-padding ECB mode). @@ -26,12 +28,14 @@ pub trait Aes256Enc: Send + Sync { /// A trait for decrypting individual blocks of plaintext using AES-256. /// /// Instances must securely delete their keys when dropped or reset. -pub trait Aes256Dec: Send + Sync { +pub trait Aes256Dec: Sized + Send + Sync { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self; /// Change the decryption key to `key` so that all future decryption is performed with it. /// This function is very rarely called so it does not have to be particularly efficient. - fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]); + fn reset(&mut self, key: &[u8; AES_256_KEY_SIZE]) { + *self = Self::new(key); + } /// Decrypt the given `block` of ciphertext directly using the AES 256 block cipher /// (i.e. AES-256 in zero-padding ECB mode). diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 0bd3beb..2b6ea78 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -12,9 +12,9 @@ pub use kyber1024::*; // We re-export our dependencies so it is less of a headache for the implementor to use the same // exact version of them. +pub use arrayvec; pub use rand_core; pub use zeroize; -pub use arrayvec; /// Constant time byte slice equality. pub fn secure_eq + ?Sized, B: AsRef<[u8]> + ?Sized>(a: &A, b: &B) -> bool { diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs index cdd11cd..d481ff4 100644 --- a/src/crypto_impl/mod.rs +++ b/src/crypto_impl/mod.rs @@ -8,18 +8,18 @@ pub use pqc_kyber; #[cfg(feature = "p384")] mod p384_impl; #[cfg(feature = "p384")] -pub use p384_impl::*; -#[cfg(feature = "p384")] pub use p384; +#[cfg(feature = "p384")] +pub use p384_impl::*; #[cfg(feature = "sha2")] mod sha512; #[cfg(feature = "sha2")] -pub use sha512::*; -#[cfg(feature = "sha2")] pub use hmac; #[cfg(feature = "sha2")] pub use sha2; +#[cfg(feature = "sha2")] +pub use sha512::*; #[cfg(feature = "openssl-sys")] mod openssl; diff --git a/src/lib.rs b/src/lib.rs index cb8e8c0..caf7a4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,10 +20,10 @@ mod symmetric_state; mod zeta; mod zssp; +pub mod application; pub mod proto; pub mod result; -pub mod application; -pub use log_event::*; -pub use zeta::*; -pub use zssp::*; +pub use crate::log_event::*; +pub use crate::zeta::*; +pub use crate::zssp::*; diff --git a/src/result.rs b/src/result.rs index f78e035..8c19d85 100644 --- a/src/result.rs +++ b/src/result.rs @@ -19,13 +19,15 @@ pub enum OpenError { /// Depending on the error type trying again may not work. #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub enum SendError { - /// An invalid parameter was supplied to the function. - InvalidParameter, + /// An invalid mtu was supplied to the function. The MTU can be no smaller than 128 bytes. + MtuTooSmall, /// The session has been marked as expired and refuses to send data. /// Several components of ZSSP can cause this to occur, but the most likely situation to be seen /// in practice is where rekeying repeatedly fails due to exceedingly bad network conditions. /// + /// The user can also explicitly cause this to occur by manually calling `expire` on a session. + /// /// The associated session will no longer send or receive data and must be immediately dropped. SessionExpired, @@ -144,6 +146,7 @@ pub enum SessionEvent { /// If the session Arc returned is dropped, the session with this peer will be immediately /// terminated. Save the session Arc to some long lived datastructure to keep it alive. NewSession, + NewDowngradedSession, /// When Alice calls `Context::open`, a session will be created, but Bob will not yet have /// received this session. They will have to successfully complete a handshake first. /// @@ -164,4 +167,5 @@ pub enum SessionEvent { Data, /// The received packet was some authentic protocol control packet. No action needs to be taken. Control, + DowngradedRatchetKey, } diff --git a/src/zeta.rs b/src/zeta.rs index 5649a59..3b077fb 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -580,7 +580,7 @@ pub(crate) fn received_x2_trans( n: &[u8; AES_GCM_NONCE_SIZE], x2: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result<(), ReceiveError> { +) -> Result> { use FaultType::*; // <- e, ee, ekem1, psk // -> s, se @@ -600,6 +600,7 @@ pub(crate) fn received_x2_trans( if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD || &n[AES_GCM_NONCE_SIZE - 3..] != &x2[x2.len() - 3..] { return Err(byzantine_fault!(FailedAuth, true)); } + let mut should_warn_missing_ratchet = false; let mut result = (|| { let a1 = if let ZetaAutomata::A1(a1) = &state.beta { a1 @@ -672,7 +673,7 @@ pub(crate) fn received_x2_trans( chain_len = 0; result = test_ratchet_key(&[0u8; RATCHET_SIZE]); if result.is_some() { - // TODO: add some kind of warning callback or signal. + should_warn_missing_ratchet = true; } } @@ -768,7 +769,7 @@ pub(crate) fn received_x2_trans( Ok(ref mut packet) => send(packet, Some(&session.state.read().unwrap().hk_send)), _ => {} } - result.map(|_| ()) + result.map(|_| should_warn_missing_ratchet) } fn send_control( session: &Arc>, @@ -800,7 +801,7 @@ pub(crate) fn received_x3_trans( kid: NonZeroU32, x3: &mut [u8], send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result>, ReceiveError> { +) -> Result<(Arc>, bool), ReceiveError> { use FaultType::*; // -> s, se if x3.len() < HANDSHAKE_COMPLETION_MIN_SIZE { @@ -868,9 +869,11 @@ pub(crate) fn received_x3_trans( match result { Ok(rss) => { let RatchetStates { state1, state2 } = rss.unwrap_or_default(); + let mut should_warn_missing_ratchet = false; + if (&zeta.ratchet_state != &state1) & (Some(&zeta.ratchet_state) != state2.as_ref()) { if !responder_disallows_downgrade && zeta.ratchet_state.fingerprint().is_none() { - // TODO: add some kind of warning callback or signal. + should_warn_missing_ratchet = true; } else { if !responder_silently_rejects { send(&mut create_reject(), Some(&App::PrpEnc::new(&zeta.hk_send))) @@ -963,7 +966,7 @@ pub(crate) fn received_x3_trans( send_control(&session, &state, PACKET_TYPE_KEY_CONFIRM, c1, send); drop(state); - Ok(session) + Ok((session, should_warn_missing_ratchet)) } Err(e) => Err(ReceiveError::StorageError(e)), } @@ -1650,30 +1653,27 @@ pub(crate) fn send_payload( ctx: &Arc>, session: &Arc>, payload: &[u8], - mut send: impl FnMut(&[u8]) -> bool, + mut send: impl FnMut(&mut [u8]) -> bool, mtu_sized_buffer: &mut [u8], ) -> Result<(), SendError> { use SendError::*; let mtu = mtu_sized_buffer.len(); if mtu < MIN_TRANSPORT_MTU { - return Err(InvalidParameter); + return Err(MtuTooSmall); } let state = session.state.read().unwrap(); - if matches!(&state.beta, ZetaAutomata::Null) { - return Err(SessionExpired); - } - if !matches!( - &state.beta, - ZetaAutomata::S1 | ZetaAutomata::S2 | ZetaAutomata::R1 { .. } | ZetaAutomata::R2 { .. } - ) { - return Err(SessionNotEstablished); - } let (c, should_rekey) = get_counter(session, &state).ok_or(SessionExpired)?; let nonce = to_nonce(PACKET_TYPE_DATA, c); let key = state.key_ref(false); - let mut cipher = key.nk.as_ref().unwrap().start_enc(&nonce); + let kid_send = key.send.kid.ok_or(SessionNotEstablished)?.get().to_be_bytes(); + let mut cipher = key.nk.as_ref().ok_or(SessionNotEstablished)?.start_enc(&nonce); + + debug_assert!(matches!( + &state.beta, + ZetaAutomata::S1 | ZetaAutomata::S2 | ZetaAutomata::R1 { .. } | ZetaAutomata::R2 { .. } + )); let payload_mtu = mtu - HEADER_SIZE; debug_assert!(payload_mtu >= 4); @@ -1683,7 +1683,7 @@ pub(crate) fn send_payload( let fragment_size_remainder = tagged_payload_len % fragment_count; let mut header = [0u8; HEADER_SIZE]; - header[..KID_SIZE].copy_from_slice(&key.send.kid.unwrap().get().to_be_bytes()); + header[..KID_SIZE].copy_from_slice(&kid_send); header[FRAGMENT_COUNT_IDX] = fragment_count as u8; header[PACKET_NONCE_START..].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); @@ -1705,7 +1705,7 @@ pub(crate) fn send_payload( .unwrap(), ); - if !send(&mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { + if !send(&mut mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { return Ok(()); } i = j; @@ -1729,7 +1729,7 @@ pub(crate) fn send_payload( .unwrap(), ); - if !send(&mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { + if !send(&mut mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { return Ok(()); } diff --git a/src/zssp.rs b/src/zssp.rs index 95a2398..92dbd63 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -338,7 +338,7 @@ impl Context { match packet_type { PACKET_TYPE_HANDSHAKE_RESPONSE => { log!(app, ReceivedRawX2); - received_x2_trans( + let should_warn_missing_ratchet = received_x2_trans( &app, ctx, &session, @@ -348,11 +348,15 @@ impl Context { send_associated, )?; log!(app, X2IsAuthSentX3(&session)); - SessionEvent::Control + if should_warn_missing_ratchet { + SessionEvent::DowngradedRatchetKey + } else { + SessionEvent::Control + } } PACKET_TYPE_KEY_CONFIRM => { log!(app, ReceivedRawKeyConfirm); - let result = received_c1_trans( + let just_established = received_c1_trans( &app, ctx, &session, @@ -362,7 +366,7 @@ impl Context { send_associated, )?; log!(app, KeyConfirmIsAuthSentAck(&session)); - if result { + if just_established { SessionEvent::Established } else { SessionEvent::Control @@ -466,11 +470,19 @@ impl Context { } log!(app, ReceivedRawX3); - let session = received_x3_trans(&app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { - send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); - })?; + let (session, should_warn_missing_ratchet) = + received_x3_trans(&app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { + send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); + })?; log!(app, X3IsAuthSentKeyConfirm(&session)); - Ok(ReceiveOk::Session(session, SessionEvent::NewSession)) + Ok(ReceiveOk::Session( + session, + if should_warn_missing_ratchet { + SessionEvent::NewDowngradedSession + } else { + SessionEvent::NewSession + }, + )) } else { // This can occur naturally because either Bob's incoming_sessions cache got // full so Alice's incoming session was dropped, or the session this packet @@ -604,7 +616,7 @@ impl Context { pub fn send( &self, session: &Arc>, - send: impl FnMut(&[u8]) -> bool, + send: impl FnMut(&mut [u8]) -> bool, mtu_sized_buffer: &mut [u8], data: &[u8], ) -> Result<(), SendError> { From ac9d97f0c3b7610a11de52de281f11fba51ad094 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 9 Aug 2023 12:08:16 -0400 Subject: [PATCH 70/91] switched kids to ne bytes --- src/challenge.rs | 4 +- src/crypto/p384.rs | 4 +- src/zeta.rs | 227 +++++++++++++++++++++++---------------------- src/zssp.rs | 7 +- 4 files changed, 120 insertions(+), 122 deletions(-) diff --git a/src/challenge.rs b/src/challenge.rs index faabbce..09b14d4 100644 --- a/src/challenge.rs +++ b/src/challenge.rs @@ -16,7 +16,7 @@ pub struct ChallengeContext { /// Corresponds to Algorithm 11 found in Section 5. pub fn gen_null_response(rng: &mut impl RngCore) -> [u8; CHALLENGE_SIZE] { let mut response = [0u8; CHALLENGE_SIZE]; - response[POW_START..].copy_from_slice(&rng.next_u64().to_be_bytes()); + response[POW_START..].copy_from_slice(&rng.next_u64().to_ne_bytes()); response } /// Corresponds to Algorithm 13 found in Section 5. @@ -31,7 +31,7 @@ pub fn respond_to_challenge_in_place( let mut pow = rng.next_u64(); let mut work_buf = [0u8; SHA512_HASH_SIZE]; loop { - pre_response[POW_START..].copy_from_slice(&pow.to_be_bytes()); + pre_response[POW_START..].copy_from_slice(&pow.to_ne_bytes()); if verify_pow(hash, pre_response, &mut work_buf) { return; } diff --git a/src/crypto/p384.rs b/src/crypto/p384.rs index 647ac93..67f780a 100644 --- a/src/crypto/p384.rs +++ b/src/crypto/p384.rs @@ -35,9 +35,9 @@ pub trait P384KeyPair { /// This must output the compressed SEC1 NIST encoding of P-384 public keys. fn public_key_bytes(&self) -> [u8; P384_PUBLIC_KEY_SIZE]; - /// Perform ECDH key agreement, writing the raw (un-hashed!) ECDH secret to `output`. + /// Perform ECDH key agreement, writing the raw (un-hashed!) ECDH secret to `ecdh_out`. /// - /// **CRITICAL**: This function must return `None` if key agreement between this private key and + /// **CRITICAL**: This function must return `false` if key agreement between this private key and /// the input `public_key` key would result in an invalid, non-standard or predictable ECDH secret. /// Please refer to the NIST spec for P-384 ECDH key agreement, or better yet use a peer reviewed /// library that has already implemented this correctly. diff --git a/src/zeta.rs b/src/zeta.rs index 3b077fb..f824eb0 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -23,58 +23,6 @@ use crate::zssp::{log, ContextInner, SessionQueue}; #[cfg(feature = "logging")] use crate::LogEvent::*; -/// Create a 96-bit AES-GCM nonce. -/// -/// The primary information that we want to be contained here is the counter and the -/// packet type. The former makes this unique and the latter's inclusion authenticates -/// it as effectively AAD. Other elements of the header are either not authenticated, -/// like fragmentation info, or their authentication is implied via key exchange like -/// the key id. -/// -/// Corresponds to Figure 10 found in Section 4.3. -pub(crate) fn to_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_NONCE_SIZE] { - let mut ret = [0u8; AES_GCM_NONCE_SIZE]; - ret[3] = packet_type; - // Noise requires a big endian counter at the end of the Nonce - ret[4..].copy_from_slice(&counter.to_be_bytes()); - ret -} -/// Corresponds to Figure 10 and Figure 14 found in Section 4.3. -pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { - assert!(n.len() >= PACKET_NONCE_SIZE); - let c_start = n.len() - 8; - (n[c_start - 1], u64::from_be_bytes(n[c_start..].try_into().unwrap())) -} -fn create_ratchet_state( - hmac: &mut App::Hmac, - noise: &SymmetricState, - pre_chain_len: u64, -) -> RatchetState { - let mut rk = Zeroizing::new([0u8; HASHLEN]); - let mut rf = Zeroizing::new([0u8; HASHLEN]); - noise.get_ask(hmac, LABEL_RATCHET_STATE, &mut rk, &mut rf); - RatchetState::new( - Zeroizing::new(rk[..RATCHET_SIZE].try_into().unwrap()), - Zeroizing::new(rf[..RATCHET_SIZE].try_into().unwrap()), - pre_chain_len + 1, - ) -} -fn get_counter(session: &Session, state: &MutableState) -> Option<(u64, bool)> { - if session.session_has_expired.load(Ordering::Relaxed) { - None - } else { - let c = session.send_counter.fetch_add(1, Ordering::Relaxed); - if c > THREAD_SAFE_COUNTER_HARD_EXPIRE { - session.session_has_expired.store(true, Ordering::SeqCst); - } - if c > state.key_creation_counter + EXPIRE_AFTER_USES { - session.session_has_expired.store(true, Ordering::SeqCst); - return None; - } - Some((c, c > state.key_creation_counter + App::SETTINGS.rekey_after_key_uses)) - } -} - /// Corresponds to the Zeta State Machine found in Section 4.1. pub struct Session { ctx: Weak>, @@ -131,34 +79,12 @@ pub(crate) struct DuplexKey { recv: Keys, nk: Option, } -impl Default for DuplexKey { - fn default() -> Self { - Self { send: Default::default(), recv: Default::default(), nk: None } - } -} -impl DuplexKey { - fn replace_nk(&mut self, nk_send: &[u8; HASHLEN], nk_recv: &[u8; HASHLEN]) { - self.nk = Some(App::AeadPool::new( - (&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), - (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap(), - )) - } -} #[derive(Default)] pub(crate) struct Keys { kek: Option>, kid: Option, } -impl Keys { - fn replace_kek(&mut self, kek: &[u8; HASHLEN]) { - // We want to give rust the best chance of implementing this in a way that does - // not leak the key on the stack. - self.kek - .get_or_insert(Zeroizing::new([0u8; AES_256_KEY_SIZE])) - .copy_from_slice(&kek[..AES_256_KEY_SIZE]); - } -} /// Corresponds to State A_1 of the Zeta State Machine found in Section 4.1. #[derive(Clone)] @@ -192,6 +118,38 @@ pub(crate) enum ZetaAutomata { }, } +impl Default for DuplexKey { + fn default() -> Self { + Self { send: Default::default(), recv: Default::default(), nk: None } + } +} +impl DuplexKey { + fn replace_nk(&mut self, nk_send: &[u8; HASHLEN], nk_recv: &[u8; HASHLEN]) { + self.nk = Some(App::AeadPool::new( + (&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), + (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap(), + )) + } +} +impl Keys { + fn replace_kek(&mut self, kek: &[u8; HASHLEN]) { + // We want to give rust the best chance of implementing this in a way that does + // not leak the key on the stack. + self.kek + .get_or_insert(Zeroizing::new([0u8; AES_256_KEY_SIZE])) + .copy_from_slice(&kek[..AES_256_KEY_SIZE]); + } +} + +impl MutableState { + fn key_ref(&self, is_next: bool) -> &DuplexKey { + &self.keys[(self.key_index ^ is_next) as usize] + } + fn key_mut(&mut self, is_next: bool) -> &mut DuplexKey { + &mut self.keys[(self.key_index ^ is_next) as usize] + } +} + impl SymmetricState { fn write_e_no_init( &mut self, @@ -241,6 +199,62 @@ impl SymmetricState { } } +/// Create a 96-bit AES-GCM nonce. +/// +/// The primary information that we want to be contained here is the counter and the +/// packet type. The former makes this unique and the latter's inclusion authenticates +/// it as effectively AAD. Other elements of the header are either not authenticated, +/// like fragmentation info, or their authentication is implied via key exchange like +/// the key id. +/// +/// Corresponds to Figure 10 found in Section 4.3. +pub(crate) fn to_nonce(packet_type: u8, counter: u64) -> [u8; AES_GCM_NONCE_SIZE] { + let mut ret = [0u8; AES_GCM_NONCE_SIZE]; + ret[3] = packet_type; + // Noise requires a big endian counter at the end of the Nonce + ret[4..].copy_from_slice(&counter.to_be_bytes()); + ret +} +/// Corresponds to Figure 10 and Figure 14 found in Section 4.3. +pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { + assert!(n.len() >= PACKET_NONCE_SIZE); + let c_start = n.len() - 8; + (n[c_start - 1], u64::from_be_bytes(n[c_start..].try_into().unwrap())) +} +fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_NONCE_SIZE]) { + packet[..KID_SIZE].copy_from_slice(&kid_send.to_ne_bytes()); + packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); +} +fn create_ratchet_state( + hmac: &mut App::Hmac, + noise: &SymmetricState, + pre_chain_len: u64, +) -> RatchetState { + let mut rk = Zeroizing::new([0u8; HASHLEN]); + let mut rf = Zeroizing::new([0u8; HASHLEN]); + noise.get_ask(hmac, LABEL_RATCHET_STATE, &mut rk, &mut rf); + RatchetState::new( + Zeroizing::new(rk[..RATCHET_SIZE].try_into().unwrap()), + Zeroizing::new(rf[..RATCHET_SIZE].try_into().unwrap()), + pre_chain_len + 1, + ) +} +fn get_counter(session: &Session, state: &MutableState) -> Option<(u64, bool)> { + if session.session_has_expired.load(Ordering::Relaxed) { + None + } else { + let c = session.send_counter.fetch_add(1, Ordering::Relaxed); + if c > THREAD_SAFE_COUNTER_HARD_EXPIRE { + session.session_has_expired.store(true, Ordering::SeqCst); + } + if c > state.key_creation_counter + EXPIRE_AFTER_USES { + session.session_has_expired.store(true, Ordering::SeqCst); + return None; + } + Some((c, c > state.key_creation_counter + App::SETTINGS.rekey_after_key_uses)) + } +} + /// Generate a random local key id that is currently unused. fn gen_kid(session_map: &HashMap, rng: &mut impl RngCore) -> NonZeroU32 { loop { @@ -251,19 +265,20 @@ fn gen_kid(session_map: &HashMap, rng: &mut impl RngCore) -> N } } } - -impl MutableState { - fn key_ref(&self, is_next: bool) -> &DuplexKey { - &self.keys[(self.key_index ^ is_next) as usize] - } - fn key_mut(&mut self, is_next: bool) -> &mut DuplexKey { - &mut self.keys[(self.key_index ^ is_next) as usize] - } -} - -fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_NONCE_SIZE]) { - packet[..KID_SIZE].copy_from_slice(&kid_send.to_be_bytes()); - packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); +fn remap( + ctx: &Arc>, + session: &Arc>, + state: &MutableState, +) -> NonZeroU32 { + let mut session_map = ctx.session_map.write().unwrap(); + let weak = if let Some(Some(weak)) = state.key_ref(true).recv.kid.as_ref().map(|kid| session_map.remove(kid)) { + weak + } else { + Arc::downgrade(&session) + }; + let new_kid_recv = gen_kid(session_map.deref(), ctx.rng.lock().unwrap().deref_mut()); + session_map.insert(new_kid_recv, weak); + new_kid_recv } fn create_a1_state( @@ -283,7 +298,7 @@ fn create_a1_state( let mut x1 = ArrayVec::::new(); x1.extend([0u8; HEADER_SIZE]); // Noise process prologue. - let kid = kid_recv.get().to_be_bytes(); + let kid = kid_recv.get().to_ne_bytes(); x1.extend(kid); noise.mix_hash(hash, &kid); noise.mix_hash(hash, &s_remote.to_bytes()); @@ -450,7 +465,7 @@ pub(crate) fn received_x1_trans( // Noise process prologue. let j = i + KID_SIZE; noise.mix_hash(hash, &x1[i..j]); - let kid_send = NonZeroU32::new(u32::from_be_bytes(x1[i..j].try_into().unwrap())) + let kid_send = NonZeroU32::new(u32::from_ne_bytes(x1[i..j].try_into().unwrap())) .ok_or(byzantine_fault!(InvalidPacket, true))?; noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); i = j; @@ -537,12 +552,12 @@ pub(crate) fn received_x1_trans( ); let i = x2.len(); - x2.extend(kid_recv.get().to_be_bytes()); + x2.extend(kid_recv.get().to_ne_bytes()); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..]); x2.extend(tag); let i = x2.len(); - let mut c = 0u64.to_be_bytes(); + let mut c = [0u8; 8]; c[5] = x2[i - 3]; c[6] = x2[i - 2]; c[7] = x2[i - 1]; @@ -654,7 +669,7 @@ pub(crate) fn received_x2_trans( if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut payload, tag) { return None; } - NonZeroU32::new(u32::from_be_bytes(payload)).map(|kid2| (kid2, noise)) + NonZeroU32::new(u32::from_ne_bytes(payload)).map(|kid2| (kid2, noise)) }; // Check first key. let mut ratchet_i = 1; @@ -1248,7 +1263,7 @@ fn timeout_trans( noise.mix_key(hmac, session.noise_kk_ss.as_ref()); // Process message pattern 1 payload. let i = k1.len(); - k1.extend(new_kid_recv.get().to_be_bytes()); + k1.extend(new_kid_recv.get().to_ne_bytes()); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..]); k1.extend(tag); @@ -1337,21 +1352,6 @@ pub(crate) fn process_timers( } } } -fn remap( - ctx: &Arc>, - session: &Arc>, - state: &MutableState, -) -> NonZeroU32 { - let mut session_map = ctx.session_map.write().unwrap(); - let weak = if let Some(Some(weak)) = state.key_ref(true).recv.kid.as_ref().map(|kid| session_map.remove(kid)) { - weak - } else { - Arc::downgrade(&session) - }; - let new_kid_recv = gen_kid(session_map.deref(), ctx.rng.lock().unwrap().deref_mut()); - session_map.insert(new_kid_recv, weak); - new_kid_recv -} /// Corresponds to Transition Algorithm 7 found in Section 4.3. pub(crate) fn received_k1_trans( app: &App, @@ -1432,7 +1432,7 @@ pub(crate) fn received_k1_trans( if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_INIT, 0), &mut k1[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let kid_send = NonZeroU32::new(u32::from_be_bytes(k1[i..j].try_into().unwrap())) + let kid_send = NonZeroU32::new(u32::from_ne_bytes(k1[i..j].try_into().unwrap())) .ok_or(byzantine_fault!(FailedAuth, true))?; let mut k2 = ArrayVec::::new(); @@ -1450,7 +1450,7 @@ pub(crate) fn received_k1_trans( // Process message pattern 2 payload. let i = k2.len(); let new_kid_recv = remap(ctx, session, &state); - k2.extend(new_kid_recv.get().to_be_bytes()); + k2.extend(new_kid_recv.get().to_ne_bytes()); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..]); k2.extend(tag); @@ -1582,7 +1582,7 @@ pub(crate) fn received_k2_trans( if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_REKEY_COMPLETE, 0), &mut k2[i..j], tag) { return Err(byzantine_fault!(FailedAuth, true)); } - let kid_send = NonZeroU32::new(u32::from_be_bytes(k2[i..j].try_into().unwrap())) + let kid_send = NonZeroU32::new(u32::from_ne_bytes(k2[i..j].try_into().unwrap())) .ok_or(byzantine_fault!(InvalidPacket, true))?; let new_ratchet_state = create_ratchet_state(hmac, &noise, state.ratchet_state1.chain_len); @@ -1667,7 +1667,7 @@ pub(crate) fn send_payload( let nonce = to_nonce(PACKET_TYPE_DATA, c); let key = state.key_ref(false); - let kid_send = key.send.kid.ok_or(SessionNotEstablished)?.get().to_be_bytes(); + let kid_send = key.send.kid.ok_or(SessionNotEstablished)?.get().to_ne_bytes(); let mut cipher = key.nk.as_ref().ok_or(SessionNotEstablished)?.start_enc(&nonce); debug_assert!(matches!( @@ -1763,7 +1763,8 @@ pub(crate) fn receive_payload_in_place( } else if Some(kid) == state.keys[1].recv.kid { state.keys[1].nk.as_ref() } else { - return Err(byzantine_fault!(OutOfSequence, true)); + // Should be unreachable unless we are leaking kids somewhere. + return Err(byzantine_fault!(UnknownLocalKeyId, true)); }; let mut cipher = specified_key diff --git a/src/zssp.rs b/src/zssp.rs index 92dbd63..0acc741 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -219,11 +219,9 @@ impl Context { let mut fragment_buffer = Assembled::new(); let kid_recv = incoming_fragment[0..KID_SIZE].try_into().unwrap(); - if let Some(kid_recv) = NonZeroU32::new(u32::from_be_bytes(kid_recv)) { - let session_map = self.0.session_map.read().unwrap(); + if let Some(kid_recv) = NonZeroU32::new(u32::from_ne_bytes(kid_recv)) { let session = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()); if let Some(Some(session)) = session { - drop(session_map); let state = session.state.read().unwrap(); state.hk_recv.decrypt_in_place( (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) @@ -417,7 +415,6 @@ impl Context { }; Ok(ReceiveOk::Session(session, ret)) } else { - drop(session_map); // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 let zeta = self.0.unassociated_handshake_states.get(kid_recv); if let Some(zeta) = zeta { @@ -591,7 +588,7 @@ impl Context { return Err(byzantine_fault!(InvalidPacket, true)); } if let Some(kid_recv) = - NonZeroU32::new(u32::from_be_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) + NonZeroU32::new(u32::from_ne_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) { if let Some(Some(session)) = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()) { respond_to_challenge(ctx, &session, &assembled_packet[KID_SIZE..].try_into().unwrap()); From 9d9091f6556e78404fe0644da3953f2258c0eaa6 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 11:05:33 -0400 Subject: [PATCH 71/91] refactored crypto --- examples/basic_test.rs | 47 ++--- src/application.rs | 45 ++--- src/handshake_cache.rs | 10 +- src/log_event.rs | 44 ++--- src/result.rs | 14 +- src/symmetric_state.rs | 34 ++-- src/zeta.rs | 421 +++++++++++++++++++++-------------------- src/zssp.rs | 84 ++++---- 8 files changed, 352 insertions(+), 347 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 3bbf1f0..f90a764 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -10,8 +10,8 @@ use rand_core::OsRng; use rand_core::RngCore; use zssp::application::{ - AcceptAction, ApplicationLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, Settings, - RATCHET_SIZE, + AcceptAction, CryptoLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, Settings, + RATCHET_SIZE, ApplicationLayer, }; use zssp::crypto::P384KeyPair; use zssp::crypto_impl::*; @@ -37,7 +37,7 @@ impl Ratchets { } #[allow(unused)] -impl ApplicationLayer for &TestApplication { +impl CryptoLayer for TestApplication { const SETTINGS: Settings = Settings { initial_offer_timeout: Settings::INITIAL_OFFER_TIMEOUT_MS, rekey_timeout: 60 * 1000, @@ -59,24 +59,27 @@ impl ApplicationLayer for &TestApplication { type KeyPair = P384CrateKeyPair; type Kem = RustKyber1024PrivateKey; - type StorageError = std::convert::Infallible; type SessionData = u128; type IncomingPacketBuffer = Vec; +} +#[allow(unused)] +impl ApplicationLayer for &TestApplication { + type Crypto = TestApplication; - fn incoming_session(&self) -> IncomingSessionAction { + fn incoming_session(&mut self) -> IncomingSessionAction { IncomingSessionAction::Allow } - fn hello_requires_recognized_ratchet(&self) -> bool { + fn hello_requires_recognized_ratchet(&mut self) -> bool { false } - fn initiator_disallows_downgrade(&self, session: &Arc>) -> bool { + fn initiator_disallows_downgrade(&mut self, session: &Arc>) -> bool { true } - fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> AcceptAction { + fn check_accept_session(&mut self, remote_static_key: &P384CratePublicKey, identity: &[u8]) -> AcceptAction { AcceptAction { session_data: Some(1), responder_disallows_downgrade: true, @@ -85,28 +88,28 @@ impl ApplicationLayer for &TestApplication { } fn restore_by_fingerprint( - &self, + &mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE], - ) -> Result, Self::StorageError> { + ) -> Result, ()> { let ratchets = self.ratchets.lock().unwrap(); Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned()) } fn restore_by_identity( - &self, - remote_static_key: &Self::PublicKey, - session_data: &Self::SessionData, - ) -> Result, Self::StorageError> { + &mut self, + remote_static_key: &P384CratePublicKey, + session_data: &u128, + ) -> Result, ()> { let ratchets = self.ratchets.lock().unwrap(); Ok(ratchets.peer_map.get(session_data).cloned()) } fn save_ratchet_state( - &self, - remote_static_key: &Self::PublicKey, - session_data: &Self::SessionData, + &mut self, + remote_static_key: &P384CratePublicKey, + session_data: &u128, update_data: RatchetUpdate<'_>, - ) -> Result<(), Self::StorageError> { + ) -> Result<(), ()> { let mut ratchets = self.ratchets.lock().unwrap(); ratchets.peer_map.insert(*session_data, update_data.to_states()); @@ -123,11 +126,11 @@ impl ApplicationLayer for &TestApplication { Ok(()) } - fn time(&self) -> i64 { + fn time(&mut self) -> i64 { self.time.elapsed().as_millis() as i64 } - fn event_log(&self, event: zssp::LogEvent) { + fn event_log(&mut self, event: zssp::LogEvent) { println!(">[{}] {:?}", self.name, event); } } @@ -144,7 +147,7 @@ fn alice_main( bob_pubkey: P384CratePublicKey, ) { let startup_time = std::time::Instant::now(); - let context = zssp::Context::<&TestApplication>::new(alice_keypair, OsRng); + let context = zssp::Context::::new(alice_keypair, OsRng); let mut next_service = startup_time.elapsed().as_millis() as i64 + 500; let test_data = [1u8; TEST_MTU * 10]; let mut up = false; @@ -250,7 +253,7 @@ fn bob_main( bob_keypair: P384CrateKeyPair, ) { let startup_time = std::time::Instant::now(); - let context = zssp::Context::<&TestApplication>::new(bob_keypair, OsRng); + let context = zssp::Context::::new(bob_keypair, OsRng); let mut last_speed_metric = startup_time.elapsed().as_millis() as i64; let mut next_service = last_speed_metric + 500; let mut transferred = 0u64; diff --git a/src/application.rs b/src/application.rs index d05aeb8..05bb201 100644 --- a/src/application.rs +++ b/src/application.rs @@ -90,7 +90,7 @@ impl Default for Settings { /// and negotiation timeout behavior. Both sides of a ZSSP session **must** have these constants /// set to the same values. Changing these constants is generally discouraged unless you know /// what you are doing. -pub trait ApplicationLayer: Sized { +pub trait CryptoLayer: Sized { /// These are constants that can be redefined from their defaults to change rekey /// and negotiation timeout behavior. If two sides of a ZSSP session have different constants, /// the protocol will tend to default to the smaller constants. @@ -129,9 +129,6 @@ pub trait ApplicationLayer: Sized { /// for ZSSP to achieve FIPS compliance. type Kem: Kyber1024PrivateKey; - /// A user-defined error returned when the `ApplicationLayer` fails to access persistent storage - /// for a peer's ratchet states. - type StorageError: std::error::Error; /// Type for arbitrary opaque object for use by the application that is attached to /// each session. @@ -144,14 +141,18 @@ pub trait ApplicationLayer: Sized { /// hold these for a short period of time when assembling fragmented packets on the receive /// path. type IncomingPacketBuffer: AsRef<[u8]> + AsMut<[u8]>; +} + +pub trait ApplicationLayer: Sized { + type Crypto: CryptoLayer; /// Should return the current time in milliseconds. Does not have to be monotonic, nor synced /// with remote peers (although both of these properties would help reliability slightly). /// Used to determine if any current handshakes should be resent or timed-out, or if a session /// should rekey. - fn time(&self) -> i64; + fn time(&mut self) -> i64; - fn incoming_session(&self) -> IncomingSessionAction; + fn incoming_session(&mut self) -> IncomingSessionAction; /// This function will be called whenever Alice's initial Hello packet contains the empty ratchet /// fingerprint. Brand new peers will always connect to Bob with the empty ratchet, but from /// then on they should be using non-empty ratchet states. @@ -161,7 +162,7 @@ pub trait ApplicationLayer: Sized { /// If this function is configured to always return true, it means peers will not be able to /// connect to us unless they had a prior-established ratchet key with us. This is the best way /// for the paranoid to enforce a manual allow-list. - fn hello_requires_recognized_ratchet(&self) -> bool; + fn hello_requires_recognized_ratchet(&mut self) -> bool; /// This function is called if we, as Alice, attempted to open a session with Bob using a /// non-empty ratchet key, but Bob does not have this ratchet key and wants to downgrade /// to the zero ratchet key. @@ -177,14 +178,14 @@ pub trait ApplicationLayer: Sized { /// least one party is misconfigured and got their ratchet keys corrupted or lost, or Bob has /// been compromised and is being impersonated. An attacker must at least have Bob's private /// static key to be able to ask Alice to downgrade. - fn initiator_disallows_downgrade(&self, session: &Arc>) -> bool; + fn initiator_disallows_downgrade(&mut self, session: &Arc>) -> bool; /// Function to accept sessions after final negotiation. /// The second argument is the identity that the remote peer sent us. The application /// must verify this identity is associated with the remote peer's static key. /// To prevent desync, if this function specifies that we should connect, no other open session /// with the same remote peer must exist. Drop or call expire on any pre-existing sessions /// before returning. - fn check_accept_session(&self, remote_static_key: &Self::PublicKey, identity: &[u8]) -> AcceptAction; + fn check_accept_session(&mut self, remote_static_key: &::PublicKey, identity: &[u8]) -> AcceptAction; /// Lookup a specific ratchet state based on its ratchet fingerprint. /// This function will be called whenever Alice attempts to connect to us with a non-empty @@ -193,9 +194,9 @@ pub trait ApplicationLayer: Sized { /// If a ratchet state with a matching fingerprint could not be found, this function should /// return `Ok(None)`. fn restore_by_fingerprint( - &self, + &mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE], - ) -> Result, Self::StorageError>; + ) -> Result, ()>; /// Lookup the specific ratchet states based on the identity of the peer being communicated with. /// This function will be called whenever Alice attempts to open a session, or Bob attempts /// to verify Alice's identity. @@ -211,10 +212,10 @@ pub trait ApplicationLayer: Sized { /// Filtering peers should be done by the caller to `Context::open` as well as by the /// function `ApplicationLayer::check_accept_session`. fn restore_by_identity( - &self, - remote_static_key: &Self::PublicKey, - session_data: &Self::SessionData, - ) -> Result, Self::StorageError>; + &mut self, + remote_static_key: &::PublicKey, + session_data: &::SessionData, + ) -> Result, ()>; /// Atomically commit the update specified by `update_data` to storage, or return an error if /// the update could not be made. /// The implementor is free to choose how to apply these updates to storage. @@ -232,17 +233,17 @@ pub trait ApplicationLayer: Sized { /// to us will have to allow downgrade across the board. /// Otherwise, when we restart, we will not be allowed to reconnect. fn save_ratchet_state( - &self, - remote_static_key: &Self::PublicKey, - session_data: &Self::SessionData, + &mut self, + remote_static_key: &::PublicKey, + session_data: &::SessionData, update_data: RatchetUpdate<'_>, - ) -> Result<(), Self::StorageError>; + ) -> Result<(), ()>; /// Receives a stream of events that occur during an execution of ZSSP. /// These are provided for debugging, logging or metrics purposes, and must be used for /// nothing else. Do not base protocol-level decisions upon the events passed to this function. #[cfg(feature = "logging")] - fn event_log(&self, event: crate::LogEvent<'_, Self>); + fn event_log(&mut self, event: crate::LogEvent<'_, Self::Crypto>); } #[derive(Debug, PartialEq, Eq, Clone)] @@ -256,10 +257,10 @@ pub enum IncomingSessionAction { /// used by Bob, the responder, at the very last stage of the key exchange. /// /// Corresponds to the *Accept* callback of Transition Algorithm 4. -pub struct AcceptAction { +pub struct AcceptAction { /// The data object to be attached to the session if we successfully connect. /// If this field is None then we will not connect to this remote peer. - pub session_data: Option, + pub session_data: Option, /// Whether or not we will accept a connection with the remote peer when they do not have a /// ratchet key that we think they should have. pub responder_disallows_downgrade: bool, diff --git a/src/handshake_cache.rs b/src/handshake_cache.rs index 3939cc7..1425a9b 100644 --- a/src/handshake_cache.rs +++ b/src/handshake_cache.rs @@ -3,23 +3,23 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, RwLock}; use crate::zeta::StateB2; -use crate::{application::ApplicationLayer, proto::MAX_UNASSOCIATED_HANDSHAKE_STATES}; +use crate::{application::CryptoLayer, proto::MAX_UNASSOCIATED_HANDSHAKE_STATES}; -pub(crate) struct UnassociatedHandshakeCache { +pub(crate) struct UnassociatedHandshakeCache { has_pending: AtomicBool, // Allowed to be falsely positive cache: RwLock>, } /// SoA format -struct CacheInner { +struct CacheInner { local_ids: [Option; MAX_UNASSOCIATED_HANDSHAKE_STATES], timeouts: [i64; MAX_UNASSOCIATED_HANDSHAKE_STATES], - handshakes: [Option>>; MAX_UNASSOCIATED_HANDSHAKE_STATES], + handshakes: [Option>>; MAX_UNASSOCIATED_HANDSHAKE_STATES], } /// Linear-search cache for capping the memory consumption of handshake data. /// Designed specifically to have short and simple code that clearly bounds above /// memory consumption. -impl UnassociatedHandshakeCache { +impl UnassociatedHandshakeCache { pub(crate) fn new() -> Self { Self { has_pending: AtomicBool::new(false), diff --git a/src/log_event.rs b/src/log_event.rs index 283ae3a..3671abd 100644 --- a/src/log_event.rs +++ b/src/log_event.rs @@ -1,22 +1,22 @@ use std::sync::Arc; -use crate::application::ApplicationLayer; +use crate::application::CryptoLayer; use crate::zeta::Session; /// ZSSP events that might be interesting to log or aggregate into metrics. -pub enum LogEvent<'a, App: ApplicationLayer> { - ResentX1(&'a Arc>), - TimeoutX1(&'a Arc>), +pub enum LogEvent<'a, Crypto: CryptoLayer> { + ResentX1(&'a Arc>), + TimeoutX1(&'a Arc>), TimeoutX2, - ResentX3(&'a Arc>), - TimeoutX3(&'a Arc>), - ResentKeyConfirm(&'a Arc>), - TimeoutKeyConfirm(&'a Arc>), - StartedRekeyingSentK1(&'a Arc>), - ResentK1(&'a Arc>), - TimeoutK1(&'a Arc>), - ResentK2(&'a Arc>), - TimeoutK2(&'a Arc>), + ResentX3(&'a Arc>), + TimeoutX3(&'a Arc>), + ResentKeyConfirm(&'a Arc>), + TimeoutKeyConfirm(&'a Arc>), + StartedRekeyingSentK1(&'a Arc>), + ResentK1(&'a Arc>), + TimeoutK1(&'a Arc>), + ResentK2(&'a Arc>), + TimeoutK2(&'a Arc>), /// `(packet_type, packet_counter, fragment_no, fragment_count)` ReceivedRawFragment(u8, u64, usize, usize), ReceivedRawX1, @@ -24,24 +24,24 @@ pub enum LogEvent<'a, App: ApplicationLayer> { X1SucceededChallenge, X1IsAuthSentX2, ReceivedRawChallenge, - ChallengeIsAuth(&'a Arc>), + ChallengeIsAuth(&'a Arc>), ReceivedRawX2, - X2IsAuthSentX3(&'a Arc>), + X2IsAuthSentX3(&'a Arc>), ReceivedRawX3, - X3IsAuthSentKeyConfirm(&'a Arc>), + X3IsAuthSentKeyConfirm(&'a Arc>), ReceivedRawKeyConfirm, - KeyConfirmIsAuthSentAck(&'a Arc>), + KeyConfirmIsAuthSentAck(&'a Arc>), ReceivedRawAck, - AckIsAuth(&'a Arc>), + AckIsAuth(&'a Arc>), ReceivedRawK1, - K1IsAuthSentK2(&'a Arc>), + K1IsAuthSentK2(&'a Arc>), ReceivedRawK2, - K2IsAuthSentKeyConfirm(&'a Arc>), + K2IsAuthSentKeyConfirm(&'a Arc>), ReceivedRawD, - DIsAuthClosedSession(&'a Arc>), + DIsAuthClosedSession(&'a Arc>), } -impl<'a, App: ApplicationLayer> std::fmt::Debug for LogEvent<'a, App> { +impl<'a, Crypto: CryptoLayer> std::fmt::Debug for LogEvent<'a, Crypto> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::ResentX1(_) => f.debug_tuple("ResentX1").finish(), diff --git a/src/result.rs b/src/result.rs index 8c19d85..a972597 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,18 +1,18 @@ use std::sync::Arc; -use crate::application::ApplicationLayer; +use crate::application::CryptoLayer; use crate::zeta::Session; /// An error that can occur when attempting to open a session. /// Depending on the error type trying again may not work. #[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub enum OpenError { +pub enum OpenError { /// An invalid parameter was supplied to the function. InvalidPublicKey, IdentityTooLarge, - RatchetIoError(IoError), + RatchetStorageError, } /// An error that can occur when attempting to send data over a session. @@ -64,7 +64,7 @@ pub enum FaultType { /// An error that occurred during the receipt of a given packet. #[derive(Debug)] -pub enum ReceiveError { +pub enum ReceiveError { /// A type of fault that can occur because a remote peer sent us a bad packet. /// Such packets will be ignored by ZSSP but a user of ZSSP might want to log /// them for debugging or tracing. @@ -107,7 +107,7 @@ pub enum ReceiveError { Rejected, /// One of the ratchet saving or lookup functions returned an error, so the packet had to be /// dropped. - StorageError(StorageError), + RatchetStorageError, IoError(std::io::Error), } @@ -128,13 +128,13 @@ pub(crate) use byzantine_fault; /// Result generated by the context packet receive function, with possible payloads. #[derive(Clone)] -pub enum ReceiveOk { +pub enum ReceiveOk { /// Packet superficially appeared valid but is not associated with a session yet. /// This can occur because the packet was only a fragment of a larger packet, /// or if it was a control packet that does not go through full Noise authentication. Unassociated, /// Packet was authentic and belongs to this specific session. - Session(Arc>, SessionEvent), + Session(Arc>, SessionEvent), } /// Something that can occur to an associated session when a packet is received successfully, /// including receiving a payload of decrypted, authenticated data. diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 71cf4d0..5981e3c 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -2,19 +2,19 @@ use std::marker::PhantomData; use zeroize::Zeroizing; -use crate::application::ApplicationLayer; +use crate::application::CryptoLayer; use crate::crypto::*; use crate::proto::*; -pub struct SymmetricState { +pub struct SymmetricState { k: Zeroizing<[u8; AES_256_KEY_SIZE]>, ck: Zeroizing<[u8; HASHLEN]>, h: [u8; HASHLEN], /// If anyone knows a better way to get rid of the "parameter `App` is never used" error please /// let me know. - _app: PhantomData App::SessionData>, + _app: PhantomData Crypto::SessionData>, } -impl Clone for SymmetricState { +impl Clone for SymmetricState { fn clone(&self) -> Self { Self { k: self.k.clone(), @@ -25,7 +25,7 @@ impl Clone for SymmetricState { } } -impl SymmetricState { +impl SymmetricState { /// HMAC-SHA512 key derivation based on KBKDF Counter Mode: /// https://csrc.nist.gov/publications/detail/sp/800-108/rev-1/final. /// Cryptographically this isn't meaningfully different from @@ -40,7 +40,7 @@ impl SymmetricState { /// Corresponds to Noise `HKDF`. fn kbkdf( &self, - hmac: &mut App::Hmac, + hmac: &mut Crypto::Hmac, input_key_material: &[u8], label: &[u8; 4], num_outputs: u16, @@ -86,7 +86,7 @@ impl SymmetricState { } } /// Corresponds to Noise `MixKey`. - pub fn mix_key(&mut self, hmac: &mut App::Hmac, input_key_material: &[u8]) { + pub fn mix_key(&mut self, hmac: &mut Crypto::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_k = Zeroizing::new([0u8; HASHLEN]); @@ -104,7 +104,7 @@ impl SymmetricState { self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } /// Corresponds to Noise `MixKey`. - pub fn mix_key_no_init(&mut self, hmac: &mut App::Hmac, input_key_material: &[u8]) { + pub fn mix_key_no_init(&mut self, hmac: &mut Crypto::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); self.kbkdf(hmac, input_key_material, LABEL_KBKDF_CHAIN, 2, &mut next_ck, None, None); @@ -112,13 +112,13 @@ impl SymmetricState { *self.ck = *next_ck; } /// Corresponds to Noise `MixHash`. - pub fn mix_hash(&mut self, hash: &mut App::Hash, data: &[u8]) { + pub fn mix_hash(&mut self, hash: &mut Crypto::Hash, data: &[u8]) { hash.update(&self.h); hash.update(data); hash.finish_and_reset(&mut self.h); } /// Corresponds to Noise `MixKeyAndHash`. - pub fn mix_key_and_hash(&mut self, hash: &mut App::Hash, hmac: &mut App::Hmac, input_key_material: &[u8]) { + pub fn mix_key_and_hash(&mut self, hash: &mut Crypto::Hash, hmac: &mut Crypto::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_h = [0u8; HASHLEN]; let mut temp_k = Zeroizing::new([0u8; HASHLEN]); @@ -138,7 +138,7 @@ impl SymmetricState { self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } /// Corresponds to Noise `MixKeyAndHash`. - pub fn mix_key_and_hash_no_init(&mut self, hash: &mut App::Hash, hmac: &mut App::Hmac, input_key_material: &[u8]) { + pub fn mix_key_and_hash_no_init(&mut self, hash: &mut Crypto::Hash, hmac: &mut Crypto::Hmac, input_key_material: &[u8]) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_h = [0u8; HASHLEN]; @@ -159,11 +159,11 @@ impl SymmetricState { #[must_use] pub fn encrypt_and_hash_in_place( &mut self, - hash: &mut App::Hash, + hash: &mut Crypto::Hash, iv: [u8; AES_GCM_NONCE_SIZE], data: &mut [u8], ) -> [u8; AES_GCM_TAG_SIZE] { - let tag = App::Aead::encrypt_in_place(&self.k, &iv, &self.h, data); + let tag = Crypto::Aead::encrypt_in_place(&self.k, &iv, &self.h, data); hash.update(&self.h); hash.update(data); hash.update(&tag); @@ -174,7 +174,7 @@ impl SymmetricState { #[must_use] pub fn decrypt_and_hash_in_place( &mut self, - hash: &mut App::Hash, + hash: &mut Crypto::Hash, iv: [u8; AES_GCM_NONCE_SIZE], data: &mut [u8], tag: [u8; AES_GCM_TAG_SIZE], @@ -182,19 +182,19 @@ impl SymmetricState { hash.update(&self.h); hash.update(data); hash.update(&tag); - let is_auth = App::Aead::decrypt_in_place(&self.k, &iv, &self.h, data, tag.as_ref().try_into().unwrap()); + let is_auth = Crypto::Aead::decrypt_in_place(&self.k, &iv, &self.h, data, tag.as_ref().try_into().unwrap()); hash.finish_and_reset(&mut self.h); is_auth } /// Corresponds to Noise `Split`. - pub fn split(self, hmac: &mut App::Hmac, key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { + pub fn split(self, hmac: &mut Crypto::Hmac, key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { self.kbkdf(hmac, &[], LABEL_KBKDF_CHAIN, 2, key1, Some(key2), None); } /// Get an additional symmetric key (ASK) that is a collision resistant hash of the transcript, /// is forward secrect and is cryptographically independent from all other produced keys. /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. - pub fn get_ask(&self, hmac: &mut App::Hmac, label: &[u8; 4], key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { + pub fn get_ask(&self, hmac: &mut Crypto::Hmac, label: &[u8; 4], key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { self.kbkdf(hmac, &self.h, label, 2, key1, Some(key2), None); } /// Used for internally debugging a key exchange. diff --git a/src/zeta.rs b/src/zeta.rs index f824eb0..24c3dbf 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -24,60 +24,60 @@ use crate::zssp::{log, ContextInner, SessionQueue}; use crate::LogEvent::*; /// Corresponds to the Zeta State Machine found in Section 4.1. -pub struct Session { - ctx: Weak>, +pub struct Session { + ctx: Weak>, /// An arbitrary application defined object associated with each session. - pub session_data: App::SessionData, + pub session_data: Crypto::SessionData, /// Is true if the local peer acted as Bob, the responder in the initial key exchange. pub was_bob: bool, queue_idx: BinaryHeapIndex, - pub(crate) s_remote: App::PublicKey, + pub(crate) s_remote: Crypto::PublicKey, send_counter: AtomicU64, session_has_expired: AtomicBool, pub window: Window, - pub(crate) defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], + pub(crate) defrag: [Mutex>; SESSION_MAX_FRAGMENTS_OOO], /// `session_queue -> state_machine_lock -> state -> session_map` state_machine_lock: Mutex<()>, /// `session_queue -> state_machine_lock -> state -> session_map` - pub(crate) state: RwLock>, + pub(crate) state: RwLock>, /// Pre-computed rekeying value. noise_kk_ss: Zeroizing<[u8; P384_ECDH_SHARED_SECRET_SIZE]>, } -pub(crate) struct MutableState { +pub(crate) struct MutableState { ratchet_state1: RatchetState, ratchet_state2: Option, - pub(crate) hk_send: App::PrpEnc, - pub(crate) hk_recv: App::PrpDec, + pub(crate) hk_send: Crypto::PrpEnc, + pub(crate) hk_recv: Crypto::PrpDec, key_creation_counter: u64, key_index: bool, - keys: [DuplexKey; 2], + keys: [DuplexKey; 2], resend_timer: AtomicI64, timeout_timer: i64, - pub(crate) beta: ZetaAutomata, + pub(crate) beta: ZetaAutomata, } /// Corresponds to State B_2 of the Zeta State Machine found in Section 4.1 - Definition 3. -pub(crate) struct StateB2 { +pub(crate) struct StateB2 { ratchet_state: RatchetState, kid_send: NonZeroU32, pub kid_recv: NonZeroU32, pub hk_send: Zeroizing<[u8; AES_256_KEY_SIZE]>, pub hk_recv: Zeroizing<[u8; AES_256_KEY_SIZE]>, - e_secret: App::KeyPair, - noise: SymmetricState, - pub defrag: Mutex>, + e_secret: Crypto::KeyPair, + noise: SymmetricState, + pub defrag: Mutex>, } -pub(crate) struct DuplexKey { +pub(crate) struct DuplexKey { send: Keys, recv: Keys, - nk: Option, + nk: Option, } #[derive(Default)] @@ -88,10 +88,10 @@ pub(crate) struct Keys { /// Corresponds to State A_1 of the Zeta State Machine found in Section 4.1. #[derive(Clone)] -pub(crate) struct StateA1 { - noise: SymmetricState, - e_secret: App::KeyPair, - e1_secret: App::Kem, +pub(crate) struct StateA1 { + noise: SymmetricState, + e_secret: Crypto::KeyPair, + e1_secret: Crypto::Kem, identity: ArrayVec, x1: ArrayVec, } @@ -102,15 +102,15 @@ pub(crate) struct StateA3 { } /// Corresponds to the ZKE Automata found in Section 4.1 - Definition 2. -pub(crate) enum ZetaAutomata { +pub(crate) enum ZetaAutomata { Null, - A1(Box>), + A1(Box>), A3(Box), S1, S2, R1 { - noise: SymmetricState, - e_secret: App::KeyPair, + noise: SymmetricState, + e_secret: Crypto::KeyPair, k1: ArrayVec, }, R2 { @@ -118,14 +118,14 @@ pub(crate) enum ZetaAutomata { }, } -impl Default for DuplexKey { +impl Default for DuplexKey { fn default() -> Self { Self { send: Default::default(), recv: Default::default(), nk: None } } } -impl DuplexKey { +impl DuplexKey { fn replace_nk(&mut self, nk_send: &[u8; HASHLEN], nk_recv: &[u8; HASHLEN]) { - self.nk = Some(App::AeadPool::new( + self.nk = Some(Crypto::AeadPool::new( (&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap(), )) @@ -141,24 +141,24 @@ impl Keys { } } -impl MutableState { - fn key_ref(&self, is_next: bool) -> &DuplexKey { +impl MutableState { + fn key_ref(&self, is_next: bool) -> &DuplexKey { &self.keys[(self.key_index ^ is_next) as usize] } - fn key_mut(&mut self, is_next: bool) -> &mut DuplexKey { + fn key_mut(&mut self, is_next: bool) -> &mut DuplexKey { &mut self.keys[(self.key_index ^ is_next) as usize] } } -impl SymmetricState { +impl SymmetricState { fn write_e_no_init( &mut self, - hash: &mut App::Hash, - hmac: &mut App::Hmac, - rng: &Mutex, + hash: &mut Crypto::Hash, + hmac: &mut Crypto::Hmac, + rng: &Mutex, packet: &mut ArrayVec, - ) -> App::KeyPair { - let e_secret = App::KeyPair::generate(rng.lock().unwrap().deref_mut()); + ) -> Crypto::KeyPair { + let e_secret = Crypto::KeyPair::generate(rng.lock().unwrap().deref_mut()); let pub_key = e_secret.public_key_bytes(); packet.extend(pub_key); self.mix_hash(hash, &pub_key); @@ -167,19 +167,19 @@ impl SymmetricState { } fn read_e_no_init( &mut self, - hash: &mut App::Hash, - hmac: &mut App::Hmac, + hash: &mut Crypto::Hash, + hmac: &mut Crypto::Hmac, i: &mut usize, packet: &[u8], - ) -> Option { + ) -> Option { let j = *i + P384_PUBLIC_KEY_SIZE; let pub_key = &packet[*i..j]; self.mix_hash(hash, pub_key); self.mix_key_no_init(hmac, pub_key); *i = j; - App::PublicKey::from_bytes((pub_key).try_into().unwrap()) + Crypto::PublicKey::from_bytes((pub_key).try_into().unwrap()) } - fn mix_dh(&mut self, hmac: &mut App::Hmac, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { + fn mix_dh(&mut self, hmac: &mut Crypto::Hmac, secret: &Crypto::KeyPair, remote: &Crypto::PublicKey) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { self.mix_key(hmac, ecdh_secret.as_ref()); @@ -188,7 +188,7 @@ impl SymmetricState { None } } - fn mix_dh_no_init(&mut self, hmac: &mut App::Hmac, secret: &App::KeyPair, remote: &App::PublicKey) -> Option<()> { + fn mix_dh_no_init(&mut self, hmac: &mut Crypto::Hmac, secret: &Crypto::KeyPair, remote: &Crypto::PublicKey) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { self.mix_key_no_init(hmac, ecdh_secret.as_ref()); @@ -225,9 +225,9 @@ fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_NONCE_SIZE] packet[..KID_SIZE].copy_from_slice(&kid_send.to_ne_bytes()); packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); } -fn create_ratchet_state( - hmac: &mut App::Hmac, - noise: &SymmetricState, +fn create_ratchet_state( + hmac: &mut Crypto::Hmac, + noise: &SymmetricState, pre_chain_len: u64, ) -> RatchetState { let mut rk = Zeroizing::new([0u8; HASHLEN]); @@ -239,7 +239,7 @@ fn create_ratchet_state( pre_chain_len + 1, ) } -fn get_counter(session: &Session, state: &MutableState) -> Option<(u64, bool)> { +fn get_counter(session: &Session, state: &MutableState) -> Option<(u64, bool)> { if session.session_has_expired.load(Ordering::Relaxed) { None } else { @@ -251,7 +251,7 @@ fn get_counter(session: &Session, state: &MutableSta session.session_has_expired.store(true, Ordering::SeqCst); return None; } - Some((c, c > state.key_creation_counter + App::SETTINGS.rekey_after_key_uses)) + Some((c, c > state.key_creation_counter + Crypto::SETTINGS.rekey_after_key_uses)) } } @@ -265,10 +265,10 @@ fn gen_kid(session_map: &HashMap, rng: &mut impl RngCore) -> N } } } -fn remap( - ctx: &Arc>, - session: &Arc>, - state: &MutableState, +fn remap( + ctx: &Arc>, + session: &Arc>, + state: &MutableState, ) -> NonZeroU32 { let mut session_map = ctx.session_map.write().unwrap(); let weak = if let Some(Some(weak)) = state.key_ref(true).recv.kid.as_ref().map(|kid| session_map.remove(kid)) { @@ -281,20 +281,20 @@ fn remap( new_kid_recv } -fn create_a1_state( - hash: &mut App::Hash, - hmac: &mut App::Hmac, - rng: &Mutex, - s_remote: &App::PublicKey, +fn create_a1_state( + hash: &mut Crypto::Hash, + hmac: &mut Crypto::Hmac, + rng: &Mutex, + s_remote: &Crypto::PublicKey, kid_recv: NonZeroU32, ratchet_state1: &RatchetState, ratchet_state2: Option<&RatchetState>, identity: &[u8], -) -> Option>> { +) -> Option>> { // <- s // ... // -> e, es, e1 - let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); + let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); let mut x1 = ArrayVec::::new(); x1.extend([0u8; HEADER_SIZE]); // Noise process prologue. @@ -308,7 +308,7 @@ fn create_a1_state( noise.mix_dh(hmac, &e_secret, s_remote)?; // Process message pattern 1 e1 token. let i = x1.len(); - let (e1_secret, e1_public) = App::Kem::generate(rng.lock().unwrap().deref_mut()); + let (e1_secret, e1_public) = Crypto::Kem::generate(rng.lock().unwrap().deref_mut()); x1.extend(e1_public); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..]); x1.extend(tag); @@ -339,25 +339,25 @@ fn create_a1_state( })) } /// Corresponds to Transition Algorithm 1 found in Section 4.3. -pub(crate) fn trans_to_a1( - app: App, - ctx: &Arc>, - s_remote: App::PublicKey, - session_data: App::SessionData, +pub(crate) fn trans_to_a1>( + mut app: App, + ctx: &Arc>, + s_remote: Crypto::PublicKey, + session_data: Crypto::SessionData, identity: &[u8], - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result>, OpenError> { + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), +) -> Result>, OpenError> { let RatchetStates { state1, state2 } = app .restore_by_identity(&s_remote, &session_data) - .map_err(|e| OpenError::RatchetIoError(e))? + .map_err(|_| OpenError::RatchetStorageError)? .unwrap_or_default(); let mut session_queue = ctx.session_queue.lock().unwrap(); let mut session_map = ctx.session_map.write().unwrap(); let kid_recv = gen_kid(session_map.deref(), ctx.rng.lock().unwrap().deref_mut()); - let hash = &mut App::Hash::new(); - let hmac = &mut App::Hmac::new(); + let hash = &mut Crypto::Hash::new(); + let hmac = &mut Crypto::Hmac::new(); let a1 = create_a1_state( hash, hmac, @@ -383,7 +383,7 @@ pub(crate) fn trans_to_a1( let current_time = app.time(); let queue_idx = session_queue.reserve_index(); - let resend_timer = current_time + App::SETTINGS.resend_time as i64; + let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; let session = Arc::new(Session { ctx: Arc::downgrade(ctx), session_data, @@ -397,13 +397,13 @@ pub(crate) fn trans_to_a1( state: RwLock::new(MutableState { ratchet_state1: state1.clone(), ratchet_state2: state2.clone(), - hk_send: App::PrpEnc::new((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()), - hk_recv: App::PrpDec::new((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()), + hk_send: Crypto::PrpEnc::new((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()), + hk_recv: Crypto::PrpDec::new((&hk_recv[..AES_256_KEY_SIZE]).try_into().unwrap()), key_creation_counter: 0, key_index: true, keys: [DuplexKey::default(), DuplexKey::default()], resend_timer: AtomicI64::new(resend_timer), - timeout_timer: current_time + App::SETTINGS.initial_offer_timeout as i64, + timeout_timer: current_time + Crypto::SETTINGS.initial_offer_timeout as i64, beta: ZetaAutomata::A1(a1), }), noise_kk_ss: noise_kk_ss.clone(), @@ -422,9 +422,9 @@ pub(crate) fn trans_to_a1( Ok(session) } /// Corresponds to Algorithm 13 found in Section 5. -pub(crate) fn respond_to_challenge( - ctx: &Arc>, - session: &Session, +pub(crate) fn respond_to_challenge( + ctx: &Arc>, + session: &Session, challenge: &[u8; CHALLENGE_SIZE], ) { let mut state = session.state.write().unwrap(); @@ -432,21 +432,21 @@ pub(crate) fn respond_to_challenge( let response_start = a1.x1.len() - CHALLENGE_SIZE; respond_to_challenge_in_place( ctx.rng.lock().unwrap().deref_mut(), - &mut App::Hash::new(), + &mut Crypto::Hash::new(), challenge, (&mut a1.x1[response_start..]).try_into().unwrap(), ); } } /// Corresponds to Transition Algorithm 2 found in Section 4.3. -pub(crate) fn received_x1_trans( - app: &App, - ctx: &ContextInner, - hash: &mut App::Hash, +pub(crate) fn received_x1_trans>( + app: &mut App, + ctx: &ContextInner, + hash: &mut Crypto::Hash, n: &[u8; AES_GCM_NONCE_SIZE], x1: &mut [u8], - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result<(), ReceiveError> { + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), +) -> Result<(), ReceiveError> { use FaultType::*; // <- s // ... @@ -459,8 +459,8 @@ pub(crate) fn received_x1_trans( if &n[AES_GCM_NONCE_SIZE - 8..] != &x1[x1.len() - 8..] { return Err(byzantine_fault!(FailedAuth, true)); } - let hmac = &mut App::Hmac::new(); - let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); + let hmac = &mut Crypto::Hmac::new(); + let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); let mut i = 0; // Noise process prologue. let j = i + KID_SIZE; @@ -503,7 +503,7 @@ pub(crate) fn received_x1_trans( ratchet_state = Some(rs); break; } - Err(e) => return Err(ReceiveError::StorageError(e)), + Err(_) => return Err(ReceiveError::RatchetStorageError), } i += RATCHET_SIZE; } @@ -532,7 +532,7 @@ pub(crate) fn received_x1_trans( { let i = x2.len(); let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); - let ekem1 = App::Kem::encapsulate( + let ekem1 = Crypto::Kem::encapsulate( ctx.rng.lock().unwrap().deref_mut(), (&x1[e1_start..e1_end]).try_into().unwrap(), &mut ekem1_secret, @@ -582,20 +582,20 @@ pub(crate) fn received_x1_trans( send( &mut x2, - Some(&App::PrpEnc::new(&hk_send[..AES_256_KEY_SIZE].try_into().unwrap())), + Some(&Crypto::PrpEnc::new(&hk_send[..AES_256_KEY_SIZE].try_into().unwrap())), ); Ok(()) } /// Corresponds to Transition Algorithm 3 found in Section 4.3. -pub(crate) fn received_x2_trans( - app: &App, - ctx: &Arc>, - session: &Arc>, +pub(crate) fn received_x2_trans>( + app: &mut App, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_NONCE_SIZE], x2: &mut [u8], - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result> { + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), +) -> Result { use FaultType::*; // <- e, ee, ekem1, psk // -> s, se @@ -605,8 +605,8 @@ pub(crate) fn received_x2_trans( let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); - let hash = &mut App::Hash::new(); - let hmac = &mut App::Hmac::new(); + let hash = &mut Crypto::Hash::new(); + let hmac = &mut Crypto::Hmac::new(); if Some(kid) != state.key_ref(true).recv.kid { return Err(byzantine_fault!(UnknownLocalKeyId, true)); @@ -660,7 +660,7 @@ pub(crate) fn received_x2_trans( let payload: [u8; KID_SIZE] = x2[i..j].try_into().unwrap(); let tag = x2[j..k].try_into().unwrap(); // Check for which ratchet key Bob wants to use. - let mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState)> { + let mut test_ratchet_key = |ratchet_key| -> Option<(NonZeroU32, SymmetricState)> { let mut noise = noise.clone(); let mut payload = payload.clone(); // Process message pattern 2 psk token. @@ -729,8 +729,8 @@ pub(crate) fn received_x2_trans( deleted_state2: None, }, ); - if let Err(e) = result { - return Err(ReceiveError::StorageError(e)); + if let Err(()) = result { + return Err(ReceiveError::RatchetStorageError); } let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); @@ -754,9 +754,9 @@ pub(crate) fn received_x2_trans( state.ratchet_state1 = new_ratchet_state.clone(); let current_time = app.time(); state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); - let resend_timer = current_time + App::SETTINGS.resend_time as i64; + let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; state.resend_timer = AtomicI64::new(resend_timer); - state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; + state.timeout_timer = current_time + Crypto::SETTINGS.initial_offer_timeout as i64; let a1 = if let ZetaAutomata::A1(a1) = &state.beta { a1 } else { @@ -779,24 +779,25 @@ pub(crate) fn received_x2_trans( Err(ReceiveError::ByzantineFault { .. }) => { let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); - timeout_trans(app, ctx, session, kex_lock, state, app.time(), send); + let current_time = app.time(); + timeout_trans(app, ctx, session, kex_lock, state, current_time, send); } Ok(ref mut packet) => send(packet, Some(&session.state.read().unwrap().hk_send)), _ => {} } result.map(|_| should_warn_missing_ratchet) } -fn send_control( - session: &Arc>, - state: &MutableState, +fn send_control( + session: &Arc>, + state: &MutableState, packet_type: u8, mut payload: ArrayVec, - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), ) -> bool { if let Some((c, _)) = get_counter(session, &state) { if let (Some(kek), Some(kid)) = (state.key_ref(false).send.kek.as_ref(), state.key_ref(false).send.kid) { let nonce = to_nonce(packet_type, c); - let tag = App::Aead::encrypt_in_place(kek, &nonce, &[], &mut payload[HEADER_SIZE..]); + let tag = Crypto::Aead::encrypt_in_place(kek, &nonce, &[], &mut payload[HEADER_SIZE..]); payload.extend(tag); set_header(&mut payload, kid.get(), &nonce); send(&mut payload, Some(&state.hk_send)); @@ -809,14 +810,14 @@ fn send_control( } } /// Corresponds to Transition Algorithm 4 found in Section 4.3. -pub(crate) fn received_x3_trans( - app: &App, - ctx: &Arc>, - zeta: Arc>, +pub(crate) fn received_x3_trans>( + app: &mut App, + ctx: &Arc>, + zeta: Arc>, kid: NonZeroU32, x3: &mut [u8], - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result<(Arc>, bool), ReceiveError> { + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), +) -> Result<(Arc>, bool), ReceiveError> { use FaultType::*; // -> s, se if x3.len() < HANDSHAKE_COMPLETION_MIN_SIZE { @@ -825,8 +826,8 @@ pub(crate) fn received_x3_trans( if kid != zeta.kid_recv { return Err(byzantine_fault!(UnknownLocalKeyId, true)); } - let hash = &mut App::Hash::new(); - let hmac = &mut App::Hmac::new(); + let hash = &mut Crypto::Hash::new(); + let hmac = &mut Crypto::Hmac::new(); let mut noise = zeta.noise.clone(); let mut i = 0; @@ -838,7 +839,7 @@ pub(crate) fn received_x3_trans( return Err(byzantine_fault!(FailedAuth, true)); } let s_remote = - App::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()).ok_or(byzantine_fault!(FailedAuth, true))?; + Crypto::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()).ok_or(byzantine_fault!(FailedAuth, true))?; i = k; // Process message pattern 3 se token. noise @@ -870,7 +871,7 @@ pub(crate) fn received_x3_trans( let mut d = ArrayVec::::new(); d.extend([0u8; HEADER_SIZE]); let nonce = to_nonce(PACKET_TYPE_SESSION_REJECTED, c); - d.extend(App::Aead::encrypt_in_place( + d.extend(Crypto::Aead::encrypt_in_place( (&kek_send[..AES_256_KEY_SIZE]).try_into().unwrap(), &nonce, &[], @@ -891,7 +892,7 @@ pub(crate) fn received_x3_trans( should_warn_missing_ratchet = true; } else { if !responder_silently_rejects { - send(&mut create_reject(), Some(&App::PrpEnc::new(&zeta.hk_send))) + send(&mut create_reject(), Some(&Crypto::PrpEnc::new(&zeta.hk_send))) } return Err(byzantine_fault!(FailedAuth, true)); } @@ -919,8 +920,8 @@ pub(crate) fn received_x3_trans( deleted_state2: state2.as_ref(), }, ); - if let Err(e) = result { - return Err(ReceiveError::StorageError(e)); + if let Err(()) = result { + return Err(ReceiveError::RatchetStorageError); } let session = { @@ -935,7 +936,7 @@ pub(crate) fn received_x3_trans( let mut session_queue = ctx.session_queue.lock().unwrap(); let queue_idx = session_queue.reserve_index(); let current_time = app.time(); - let resend_timer = current_time + App::SETTINGS.resend_time as i64; + let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; let session = Arc::new(Session { ctx: Arc::downgrade(ctx), session_data, @@ -947,13 +948,13 @@ pub(crate) fn received_x3_trans( state: RwLock::new(MutableState { ratchet_state1: new_ratchet_state.clone(), ratchet_state2: None, - hk_send: App::PrpEnc::new(&zeta.hk_send), - hk_recv: App::PrpDec::new(&zeta.hk_recv), + hk_send: Crypto::PrpEnc::new(&zeta.hk_send), + hk_recv: Crypto::PrpDec::new(&zeta.hk_recv), key_creation_counter: c + 1, key_index: false, keys: [DuplexKey::default(), DuplexKey::default()], resend_timer: AtomicI64::new(resend_timer), - timeout_timer: current_time + App::SETTINGS.rekey_timeout as i64, + timeout_timer: current_time + Crypto::SETTINGS.rekey_timeout as i64, beta: ZetaAutomata::S1, }), window: Window::new(), @@ -983,25 +984,25 @@ pub(crate) fn received_x3_trans( Ok((session, should_warn_missing_ratchet)) } - Err(e) => Err(ReceiveError::StorageError(e)), + Err(()) => Err(ReceiveError::RatchetStorageError), } } else { if !responder_silently_rejects { - send(&mut create_reject(), Some(&App::PrpEnc::new(&zeta.hk_send))) + send(&mut create_reject(), Some(&Crypto::PrpEnc::new(&zeta.hk_send))) } Err(ReceiveError::Rejected) } } /// Corresponds to Transition Algorithm 5 found in Section 4.3. -pub(crate) fn received_c1_trans( - app: &App, - ctx: &Arc>, - session: &Arc>, +pub(crate) fn received_c1_trans>( + app: &mut App, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_NONCE_SIZE], c1: &[u8], - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result> { + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), +) -> Result { use FaultType::*; if c1.len() != KEY_CONFIRMATION_SIZE { @@ -1024,7 +1025,7 @@ pub(crate) fn received_c1_trans( let specified_key = state.key_ref(is_other).recv.kek.as_ref(); let specified_key = specified_key.ok_or(byzantine_fault!(OutOfSequence, true))?; let tag = c1[..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(specified_key, n, &[], &mut [], tag) { + if !Crypto::Aead::decrypt_in_place(specified_key, n, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); @@ -1047,8 +1048,8 @@ pub(crate) fn received_c1_trans( deleted_state2: None, }, ); - if let Err(e) = result { - return Err(ReceiveError::StorageError(e)); + if let Err(()) = result { + return Err(ReceiveError::RatchetStorageError); } } drop(state); @@ -1057,9 +1058,9 @@ pub(crate) fn received_c1_trans( state.ratchet_state2 = None; state.key_index ^= true; state.timeout_timer = app.time() - + App::SETTINGS + + Crypto::SETTINGS .rekey_after_time - .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % Crypto::SETTINGS.rekey_time_max_jitter) as i64; state.resend_timer = AtomicI64::new(i64::MAX); state.beta = ZetaAutomata::S2; @@ -1084,14 +1085,14 @@ pub(crate) fn received_c1_trans( } /// Corresponds to the trivial Transition Algorithm described for processing C_2 packets found in /// Section 4.3. -pub(crate) fn received_c2_trans( - app: &App, - ctx: &Arc>, - session: &Arc>, +pub(crate) fn received_c2_trans>( + app: &mut App, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_NONCE_SIZE], c2: &[u8], -) -> Result<(), ReceiveError> { +) -> Result<(), ReceiveError> { use FaultType::*; if c2.len() != ACKNOWLEDGEMENT_SIZE { @@ -1111,7 +1112,7 @@ pub(crate) fn received_c2_trans( } let tag = c2[..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut [], tag) { + if !Crypto::Aead::decrypt_in_place(state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); @@ -1122,9 +1123,9 @@ pub(crate) fn received_c2_trans( let timeout_timer = { let mut state = session.state.write().unwrap(); state.timeout_timer = app.time() - + App::SETTINGS + + Crypto::SETTINGS .rekey_after_time - .saturating_sub(ctx.rng.lock().unwrap().next_u64() % App::SETTINGS.rekey_time_max_jitter) + .saturating_sub(ctx.rng.lock().unwrap().next_u64() % Crypto::SETTINGS.rekey_time_max_jitter) as i64; state.resend_timer = AtomicI64::new(i64::MAX); state.beta = ZetaAutomata::S2; @@ -1139,12 +1140,12 @@ pub(crate) fn received_c2_trans( } /// Corresponds to the trivial Transition Algorithm described for processing D packets found in /// Section 4.3. -pub(crate) fn received_d_trans( - session: &Arc>, +pub(crate) fn received_d_trans( + session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_NONCE_SIZE], d: &[u8], -) -> Result<(), ReceiveError> { +) -> Result<(), ReceiveError> { use FaultType::*; if d.len() != SESSION_REJECTED_SIZE { @@ -1159,7 +1160,7 @@ pub(crate) fn received_d_trans( } let tag = d[..].try_into().unwrap(); - if !App::Aead::decrypt_in_place(state.key_ref(true).recv.kek.as_ref().unwrap(), n, &[], &mut [], tag) { + if !Crypto::Aead::decrypt_in_place(state.key_ref(true).recv.kek.as_ref().unwrap(), n, &[], &mut [], tag) { return Err(byzantine_fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); @@ -1173,14 +1174,14 @@ pub(crate) fn received_d_trans( Ok(()) } // Corresponds to the timeout timer Transition Algorithm described in Section 4.1 - Definition 3. -fn timeout_trans( - app: &App, - ctx: &Arc>, - session: &Arc>, +fn timeout_trans>( + app: &mut App, + ctx: &Arc>, + session: &Arc>, kex_lock: MutexGuard<'_, ()>, - state: RwLockReadGuard<'_, MutableState>, + state: RwLockReadGuard<'_, MutableState>, current_time: i64, - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), ) -> Option { match &state.beta { ZetaAutomata::Null => None, @@ -1197,8 +1198,8 @@ fn timeout_trans( } let new_kid_recv = remap(ctx, session, &state); - let hash = &mut App::Hash::new(); - let hmac = &mut App::Hmac::new(); + let hash = &mut Crypto::Hash::new(); + let hmac = &mut Crypto::Hmac::new(); if let Some(a1) = create_a1_state( hash, hmac, @@ -1221,9 +1222,9 @@ fn timeout_trans( state.hk_send.reset((&hk_send[..AES_256_KEY_SIZE]).try_into().unwrap()); *state.key_mut(true) = DuplexKey::default(); state.key_mut(true).recv.kid = Some(new_kid_recv); - let resend_timer = current_time + App::SETTINGS.resend_time as i64; + let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; state.resend_timer = AtomicI64::new(resend_timer); - state.timeout_timer = current_time + App::SETTINGS.initial_offer_timeout as i64; + state.timeout_timer = current_time + Crypto::SETTINGS.initial_offer_timeout as i64; state.beta = ZetaAutomata::A1(a1); resend_timer }; @@ -1244,8 +1245,8 @@ fn timeout_trans( // ... // -> psk, e, es, ss let mut noise = SymmetricState::initialize(PROTOCOL_NAME_NOISE_KK); - let hash = &mut App::Hash::new(); - let hmac = &mut App::Hmac::new(); + let hash = &mut Crypto::Hash::new(); + let hmac = &mut Crypto::Hmac::new(); let mut k1 = ArrayVec::::new(); k1.extend([0u8; HEADER_SIZE]); // Noise process prologue. @@ -1271,8 +1272,8 @@ fn timeout_trans( let resend_timer = { let mut state = session.state.write().unwrap(); state.key_mut(true).recv.kid = Some(new_kid_recv); - state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; - let resend_timer = current_time + App::SETTINGS.resend_time as i64; + state.timeout_timer = current_time + Crypto::SETTINGS.rekey_timeout as i64; + let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; state.resend_timer = AtomicI64::new(resend_timer); state.beta = ZetaAutomata::R1 { noise, e_secret, k1: k1.clone() }; resend_timer @@ -1298,12 +1299,12 @@ fn timeout_trans( } } /// Corresponds to the timer rules of the Zeta State Machine found in Section 4.1 - Definition 3. -pub(crate) fn process_timers( - app: &App, - ctx: &Arc>, - session: &Arc>, +pub(crate) fn process_timers>( + app: &mut App, + ctx: &Arc>, + session: &Arc>, current_time: i64, - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), ) -> Option { let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); @@ -1312,7 +1313,7 @@ pub(crate) fn process_timers( timeout_trans(app, ctx, session, kex_lock, state, current_time, send) } else { let ts = state.resend_timer.load(Ordering::Relaxed); - let resend_next = current_time + App::SETTINGS.resend_time as i64; + let resend_next = current_time + Crypto::SETTINGS.resend_time as i64; if ts <= current_time && state.resend_timer.fetch_max(resend_next, Ordering::Relaxed) == ts { // Corresponds to the resend timer rules found in Section 4.1 - Definition 3. @@ -1353,15 +1354,15 @@ pub(crate) fn process_timers( } } /// Corresponds to Transition Algorithm 7 found in Section 4.3. -pub(crate) fn received_k1_trans( - app: &App, - ctx: &Arc>, - session: &Arc>, +pub(crate) fn received_k1_trans>( + app: &mut App, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_NONCE_SIZE], k1: &mut [u8], - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result<(), ReceiveError> { + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), +) -> Result<(), ReceiveError> { use FaultType::*; // -> s // <- s @@ -1391,7 +1392,7 @@ pub(crate) fn received_k1_trans( let i = k1.len() - AES_GCM_TAG_SIZE; let tag = k1[i..].try_into().unwrap(); - if !App::Aead::decrypt_in_place( + if !Crypto::Aead::decrypt_in_place( state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], @@ -1407,9 +1408,9 @@ pub(crate) fn received_k1_trans( let result = (|| { let mut i = 0; - let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_KK); - let hash = &mut App::Hash::new(); - let hmac = &mut App::Hmac::new(); + let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_KK); + let hash = &mut Crypto::Hash::new(); + let hmac = &mut Crypto::Hmac::new(); // Noise process prologue. noise.mix_hash(hash, &session.s_remote.to_bytes()); noise.mix_hash(hash, &ctx.s_secret.public_key_bytes()); @@ -1466,8 +1467,8 @@ pub(crate) fn received_k1_trans( deleted_state2: None, }, ); - if let Err(e) = result { - return Err(ReceiveError::StorageError(e)); + if let Err(()) = result { + return Err(ReceiveError::RatchetStorageError); } let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); @@ -1489,8 +1490,8 @@ pub(crate) fn received_k1_trans( state.ratchet_state1 = new_ratchet_state.clone(); let current_time = app.time(); state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); - let resend_timer = current_time + App::SETTINGS.resend_time as i64; - state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; + let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; + state.timeout_timer = current_time + Crypto::SETTINGS.rekey_timeout as i64; state.resend_timer = AtomicI64::new(resend_timer); state.beta = ZetaAutomata::R2 { k2: k2.clone() }; resend_timer @@ -1515,15 +1516,15 @@ pub(crate) fn received_k1_trans( result } /// Corresponds to Transition Algorithm 8 found in Section 4.3. -pub(crate) fn received_k2_trans( - app: &App, - ctx: &Arc>, - session: &Arc>, +pub(crate) fn received_k2_trans>( + app: &mut App, + ctx: &Arc>, + session: &Arc>, kid: NonZeroU32, n: &[u8; AES_GCM_NONCE_SIZE], k2: &mut [u8], - send: impl FnOnce(&mut [u8], Option<&App::PrpEnc>), -) -> Result<(), ReceiveError> { + send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), +) -> Result<(), ReceiveError> { use FaultType::*; // <- e, ee, se if k2.len() != REKEY_SIZE { @@ -1544,7 +1545,7 @@ pub(crate) fn received_k2_trans( let i = k2.len() - AES_GCM_TAG_SIZE; let tag = k2[i..].try_into().unwrap(); - if !App::Aead::decrypt_in_place( + if !Crypto::Aead::decrypt_in_place( state.key_ref(false).recv.kek.as_ref().unwrap(), n, &[], @@ -1561,8 +1562,8 @@ pub(crate) fn received_k2_trans( if let ZetaAutomata::R1 { noise, e_secret, .. } = &state.beta { let mut noise = noise.clone(); let mut i = 0; - let hash = &mut App::Hash::new(); - let hmac = &mut App::Hmac::new(); + let hash = &mut Crypto::Hash::new(); + let hmac = &mut Crypto::Hmac::new(); // Process message pattern 2 e token. let e_remote = noise .read_e_no_init(hash, hmac, &mut i, &k2) @@ -1597,8 +1598,8 @@ pub(crate) fn received_k2_trans( deleted_state2: state.ratchet_state2.as_ref(), }, ); - if let Err(e) = result { - return Err(ReceiveError::StorageError(e)); + if let Err(()) = result { + return Err(ReceiveError::RatchetStorageError); } let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); let mut kek_send = Zeroizing::new([0u8; HASHLEN]); @@ -1618,8 +1619,8 @@ pub(crate) fn received_k2_trans( state.key_index ^= true; let current_time = app.time(); state.key_creation_counter = session.send_counter.load(Ordering::Relaxed); - let resend_timer = current_time + App::SETTINGS.resend_time as i64; - state.timeout_timer = current_time + App::SETTINGS.rekey_timeout as i64; + let resend_timer = current_time + Crypto::SETTINGS.resend_time as i64; + state.timeout_timer = current_time + Crypto::SETTINGS.rekey_timeout as i64; state.resend_timer = AtomicI64::new(resend_timer); state.beta = ZetaAutomata::S1; resend_timer @@ -1649,9 +1650,9 @@ pub(crate) fn received_k2_trans( result } /// Corresponds to Algorithm 9 found in Section 4.3. -pub(crate) fn send_payload( - ctx: &Arc>, - session: &Arc>, +pub(crate) fn send_payload( + ctx: &Arc>, + session: &Arc>, payload: &[u8], mut send: impl FnMut(&mut [u8]) -> bool, mtu_sized_buffer: &mut [u8], @@ -1747,14 +1748,14 @@ pub(crate) fn send_payload( Ok(()) } /// Corresponds to Algorithm 10 found in Section 4.3. -pub(crate) fn receive_payload_in_place( - session: &Arc>, - state: RwLockReadGuard<'_, MutableState>, +pub(crate) fn receive_payload_in_place( + session: &Arc>, + state: RwLockReadGuard<'_, MutableState>, kid: NonZeroU32, nonce: &[u8; AES_GCM_NONCE_SIZE], - fragments: &mut [App::IncomingPacketBuffer], + fragments: &mut [Crypto::IncomingPacketBuffer], mut output_buffer: impl Write, -) -> Result<(), ReceiveError> { +) -> Result<(), ReceiveError> { use FaultType::*; debug_assert!(!fragments.is_empty()); @@ -1804,12 +1805,12 @@ pub(crate) fn receive_payload_in_place( Ok(()) } -impl Drop for Session { +impl Drop for Session { fn drop(&mut self) { self.expire(); } } -impl Session { +impl Session { /// Mark a session as expired. This will make it impossible for this session to successfully /// receive or send data or control packets. It is recommended to simply `drop` the session /// instead, but this can provide some reassurance in complex shared ownership situations. @@ -1823,8 +1824,8 @@ impl Session { /// Allows us to expire sessions with the correct locking order, preventing deadlock. pub(crate) fn expire_inner( &self, - ctx: Option<&Arc>>, - session_queue: Option<&mut SessionQueue>, + ctx: Option<&Arc>>, + session_queue: Option<&mut SessionQueue>, ) { let _kex_lock = self.state_machine_lock.lock().unwrap(); let mut state = self.state.write().unwrap(); @@ -1867,7 +1868,7 @@ impl Session { ) } /// The static public key of the remote peer. - pub fn remote_static_key(&self) -> &App::PublicKey { + pub fn remote_static_key(&self) -> &Crypto::PublicKey { &self.s_remote } } diff --git a/src/zssp.rs b/src/zssp.rs index 0acc741..16a40e5 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -37,31 +37,31 @@ pub(crate) use log; /// defragment incoming packets that are not yet associated with a session. /// /// Internally this is just a clonable Arc, so it can be safely shared with multiple threads. -pub struct Context(pub Arc>); -impl Clone for Context { +pub struct Context(pub Arc>); +impl Clone for Context { fn clone(&self) -> Self { Self(self.0.clone()) } } -pub(crate) type SessionMap = RwLock>>>; -pub(crate) type SessionQueue = IndexedBinaryHeap>, Reverse>; -pub struct ContextInner { - pub rng: Mutex, - pub(crate) s_secret: App::KeyPair, +pub(crate) type SessionMap = RwLock>>>; +pub(crate) type SessionQueue = IndexedBinaryHeap>, Reverse>; +pub struct ContextInner { + pub rng: Mutex, + pub(crate) s_secret: Crypto::KeyPair, /// `session_queue -> state_machine_lock -> state -> session_map` - pub(crate) session_queue: Mutex>, + pub(crate) session_queue: Mutex>, /// `session_queue -> state_machine_lock -> state -> session_map` - pub(crate) session_map: SessionMap, - pub(crate) unassociated_defrag_cache: Mutex>, - pub(crate) unassociated_handshake_states: UnassociatedHandshakeCache, + pub(crate) session_map: SessionMap, + pub(crate) unassociated_defrag_cache: Mutex>, + pub(crate) unassociated_handshake_states: UnassociatedHandshakeCache, pub(crate) challenge: ChallengeContext, } -fn parse_fragment_header( +fn parse_fragment_header( incoming_fragment: &[u8], -) -> Result<(usize, usize, [u8; AES_GCM_NONCE_SIZE]), ReceiveError> { +) -> Result<(usize, usize, [u8; AES_GCM_NONCE_SIZE]), ReceiveError> { let fragment_no = incoming_fragment[FRAGMENT_NO_IDX] as usize; let fragment_count = incoming_fragment[FRAGMENT_COUNT_IDX] as usize; if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS { @@ -110,9 +110,9 @@ fn send_with_fragmentation( true } -impl Context { +impl Context { /// Create a new session context. - pub fn new(static_secret_key: App::KeyPair, mut rng: App::Rng) -> Self { + pub fn new(static_secret_key: Crypto::KeyPair, mut rng: Crypto::Rng) -> Self { let challenge = ChallengeContext::new(&mut rng); Self(Arc::new(ContextInner { rng: Mutex::new(rng), @@ -141,15 +141,15 @@ impl Context { /// peer, or None if we do not have one. /// * `local_identity_blob` - Payload to be sent to Bob that contains the information necessary /// for the upper protocol to authenticate and approve of Alice's identity. - pub fn open( + pub fn open>( &self, app: App, send: impl FnMut(&mut [u8]) -> bool, mut mtu: usize, - static_remote_key: App::PublicKey, - session_data: App::SessionData, + static_remote_key: Crypto::PublicKey, + session_data: Crypto::SessionData, identity: &[u8], - ) -> Result>, OpenError> { + ) -> Result>, OpenError> { mtu = mtu.max(MIN_TRANSPORT_MTU); if identity.len() > IDENTITY_MAX_SIZE { return Err(OpenError::IdentityTooLarge); @@ -198,16 +198,16 @@ impl Context { /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced /// with the remote peer. Used to check the state of local offers we may currently have or want /// to put in-flight. - pub fn receive<'a, SendFn: FnMut(&mut [u8]) -> bool>( + pub fn receive<'a, App: ApplicationLayer, SendFn: FnMut(&mut [u8]) -> bool>( &self, - app: App, + mut app: App, mut send_unassociated_reply: impl FnMut(&mut [u8]) -> bool, mut send_unassociated_mtu: usize, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, remote_address: &impl Hash, - mut incoming_fragment_buf: App::IncomingPacketBuffer, + mut incoming_fragment_buf: Crypto::IncomingPacketBuffer, output_buffer: impl Write, - ) -> Result, ReceiveError> { + ) -> Result, ReceiveError> { use crate::result::FaultType::*; let ctx = &self.0; send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); @@ -327,7 +327,7 @@ impl Context { &mut incoming_fragment_buf.as_mut()[HEADER_SIZE..] }; - let send_associated = |packet: &mut [u8], hk_send: Option<&App::PrpEnc>| { + let send_associated = |packet: &mut [u8], hk_send: Option<&Crypto::PrpEnc>| { if let Some((send_fragment, mut mtu)) = send_to(&session) { mtu = mtu.max(MIN_TRANSPORT_MTU); send_with_fragmentation(send_fragment, mtu, packet, hk_send); @@ -337,7 +337,7 @@ impl Context { PACKET_TYPE_HANDSHAKE_RESPONSE => { log!(app, ReceivedRawX2); let should_warn_missing_ratchet = received_x2_trans( - &app, + &mut app, ctx, &session, kid_recv, @@ -355,7 +355,7 @@ impl Context { PACKET_TYPE_KEY_CONFIRM => { log!(app, ReceivedRawKeyConfirm); let just_established = received_c1_trans( - &app, + &mut app, ctx, &session, kid_recv, @@ -372,14 +372,14 @@ impl Context { } PACKET_TYPE_ACK => { log!(app, ReceivedRawAck); - received_c2_trans(&app, ctx, &session, kid_recv, &nonce, assembled_packet)?; + received_c2_trans(&mut app, ctx, &session, kid_recv, &nonce, assembled_packet)?; log!(app, AckIsAuth(&session)); SessionEvent::Control } PACKET_TYPE_REKEY_INIT => { log!(app, ReceivedRawK1); received_k1_trans( - &app, + &mut app, ctx, &session, kid_recv, @@ -393,7 +393,7 @@ impl Context { PACKET_TYPE_REKEY_COMPLETE => { log!(app, ReceivedRawK2); received_k2_trans( - &app, + &mut app, ctx, &session, kid_recv, @@ -418,7 +418,7 @@ impl Context { // Check for and handle PACKET_TYPE_ALICE_NOISE_XK_PATTERN_3 let zeta = self.0.unassociated_handshake_states.get(kid_recv); if let Some(zeta) = zeta { - App::PrpDec::new(&zeta.hk_recv).decrypt_in_place( + Crypto::PrpDec::new(&zeta.hk_recv).decrypt_in_place( (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) .try_into() .unwrap(), @@ -468,7 +468,7 @@ impl Context { log!(app, ReceivedRawX3); let (session, should_warn_missing_ratchet) = - received_x3_trans(&app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { + received_x3_trans(&mut app, ctx, zeta, kid_recv, assembled_packet, |packet, hk_send| { send_with_fragmentation(send_unassociated_reply, send_unassociated_mtu, packet, hk_send); })?; log!(app, X3IsAuthSentKeyConfirm(&session)); @@ -508,7 +508,7 @@ impl Context { incoming_fragment_buf, fragment_no, fragment_count, - App::SETTINGS.resend_time as i64, + Crypto::SETTINGS.resend_time as i64, app.time(), &mut fragment_buffer, ); @@ -536,7 +536,7 @@ impl Context { } // Process recv challenge layer. let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; - let hash = &mut App::Hash::new(); + let hash = &mut Crypto::Hash::new(); match app.incoming_session() { IncomingSessionAction::Allow => {} IncomingSessionAction::Challenge => { @@ -569,7 +569,7 @@ impl Context { // Process recv zeta layer. received_x1_trans( - &app, + &mut app, ctx, hash, &nonce, @@ -612,7 +612,7 @@ impl Context { /// * `current_time` - Current time in milliseconds pub fn send( &self, - session: &Arc>, + session: &Arc>, send: impl FnMut(&mut [u8]) -> bool, mtu_sized_buffer: &mut [u8], data: &[u8], @@ -631,15 +631,15 @@ impl Context { /// with remote peers (although both of these properties would help reliability slightly). /// Used to determine if any current handshakes should be resent or timed-out, or if a session /// should rekey. - pub fn service bool>( + pub fn service, SendFn: FnMut(&mut [u8]) -> bool>( &self, - app: App, - mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, + mut app: App, + mut send_to: impl FnMut(&Arc>) -> Option<(SendFn, usize)>, ) -> i64 { let ctx = &self.0; let mut session_queue = ctx.session_queue.lock().unwrap(); let current_time = app.time(); - let mut next_service_time = current_time + App::SETTINGS.fragment_assembly_timeout as i64; + let mut next_service_time = current_time + Crypto::SETTINGS.fragment_assembly_timeout as i64; // This update system takes heavy advantage of the fact that sessions only need to be updated // either roughly every second or roughly every hour. That big gap allows for minor optimizations. // If the gap changes (unlikely) this code may need to be rewritten. @@ -655,7 +655,7 @@ impl Context { continue; } }; - let result = process_timers(&app, ctx, &session, current_time, |packet, hk_send| { + let result = process_timers(&mut app, ctx, &session, current_time, |packet, hk_send| { if let Some((send_fragment, mut mtu)) = send_to(&session) { mtu = mtu.max(MIN_TRANSPORT_MTU); send_with_fragmentation(send_fragment, mtu, packet, hk_send); @@ -674,7 +674,7 @@ impl Context { .unassociated_defrag_cache .lock() .unwrap() - .check_for_expiry(App::SETTINGS.fragment_assembly_timeout as i64, current_time); + .check_for_expiry(Crypto::SETTINGS.fragment_assembly_timeout as i64, current_time); self.0.unassociated_handshake_states.service(current_time); next_service_time - current_time From a22898d3486d7e639846dd09eb15edf27107c875 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 11:20:17 -0400 Subject: [PATCH 72/91] refortmatted --- examples/basic_test.rs | 15 +++---- src/application.rs | 12 +++--- src/symmetric_state.rs | 15 ++++++- src/zeta.rs | 91 ++++++++++++++++-------------------------- src/zssp.rs | 11 ++--- 5 files changed, 65 insertions(+), 79 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index f90a764..9d9bdc8 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -10,8 +10,8 @@ use rand_core::OsRng; use rand_core::RngCore; use zssp::application::{ - AcceptAction, CryptoLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, Settings, - RATCHET_SIZE, ApplicationLayer, + AcceptAction, ApplicationLayer, CryptoLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, + Settings, RATCHET_SIZE, }; use zssp::crypto::P384KeyPair; use zssp::crypto_impl::*; @@ -79,7 +79,11 @@ impl ApplicationLayer for &TestApplication { true } - fn check_accept_session(&mut self, remote_static_key: &P384CratePublicKey, identity: &[u8]) -> AcceptAction { + fn check_accept_session( + &mut self, + remote_static_key: &P384CratePublicKey, + identity: &[u8], + ) -> AcceptAction { AcceptAction { session_data: Some(1), responder_disallows_downgrade: true, @@ -87,10 +91,7 @@ impl ApplicationLayer for &TestApplication { } } - fn restore_by_fingerprint( - &mut self, - ratchet_fingerprint: &[u8; RATCHET_SIZE], - ) -> Result, ()> { + fn restore_by_fingerprint(&mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, ()> { let ratchets = self.ratchets.lock().unwrap(); Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned()) } diff --git a/src/application.rs b/src/application.rs index 05bb201..fe94c9f 100644 --- a/src/application.rs +++ b/src/application.rs @@ -129,7 +129,6 @@ pub trait CryptoLayer: Sized { /// for ZSSP to achieve FIPS compliance. type Kem: Kyber1024PrivateKey; - /// Type for arbitrary opaque object for use by the application that is attached to /// each session. type SessionData; @@ -185,7 +184,11 @@ pub trait ApplicationLayer: Sized { /// To prevent desync, if this function specifies that we should connect, no other open session /// with the same remote peer must exist. Drop or call expire on any pre-existing sessions /// before returning. - fn check_accept_session(&mut self, remote_static_key: &::PublicKey, identity: &[u8]) -> AcceptAction; + fn check_accept_session( + &mut self, + remote_static_key: &::PublicKey, + identity: &[u8], + ) -> AcceptAction; /// Lookup a specific ratchet state based on its ratchet fingerprint. /// This function will be called whenever Alice attempts to connect to us with a non-empty @@ -193,10 +196,7 @@ pub trait ApplicationLayer: Sized { /// /// If a ratchet state with a matching fingerprint could not be found, this function should /// return `Ok(None)`. - fn restore_by_fingerprint( - &mut self, - ratchet_fingerprint: &[u8; RATCHET_SIZE], - ) -> Result, ()>; + fn restore_by_fingerprint(&mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, ()>; /// Lookup the specific ratchet states based on the identity of the peer being communicated with. /// This function will be called whenever Alice attempts to open a session, or Bob attempts /// to verify Alice's identity. diff --git a/src/symmetric_state.rs b/src/symmetric_state.rs index 5981e3c..c9ea5a8 100644 --- a/src/symmetric_state.rs +++ b/src/symmetric_state.rs @@ -138,7 +138,12 @@ impl SymmetricState { self.k.clone_from_slice(&temp_k[..AES_256_KEY_SIZE]); } /// Corresponds to Noise `MixKeyAndHash`. - pub fn mix_key_and_hash_no_init(&mut self, hash: &mut Crypto::Hash, hmac: &mut Crypto::Hmac, input_key_material: &[u8]) { + pub fn mix_key_and_hash_no_init( + &mut self, + hash: &mut Crypto::Hash, + hmac: &mut Crypto::Hmac, + input_key_material: &[u8], + ) { let mut next_ck = Zeroizing::new([0u8; HASHLEN]); let mut temp_h = [0u8; HASHLEN]; @@ -194,7 +199,13 @@ impl SymmetricState { /// is forward secrect and is cryptographically independent from all other produced keys. /// Based on Noise's unstable ASK mechanism, using KBKDF instead of HKDF. /// https://github.com/noiseprotocol/noise_wiki/wiki/Additional-Symmetric-Keys. - pub fn get_ask(&self, hmac: &mut Crypto::Hmac, label: &[u8; 4], key1: &mut [u8; HASHLEN], key2: &mut [u8; HASHLEN]) { + pub fn get_ask( + &self, + hmac: &mut Crypto::Hmac, + label: &[u8; 4], + key1: &mut [u8; HASHLEN], + key2: &mut [u8; HASHLEN], + ) { self.kbkdf(hmac, &self.h, label, 2, key1, Some(key2), None); } /// Used for internally debugging a key exchange. diff --git a/src/zeta.rs b/src/zeta.rs index 24c3dbf..068f964 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -188,7 +188,12 @@ impl SymmetricState { None } } - fn mix_dh_no_init(&mut self, hmac: &mut Crypto::Hmac, secret: &Crypto::KeyPair, remote: &Crypto::PublicKey) -> Option<()> { + fn mix_dh_no_init( + &mut self, + hmac: &mut Crypto::Hmac, + secret: &Crypto::KeyPair, + remote: &Crypto::PublicKey, + ) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { self.mix_key_no_init(hmac, ecdh_secret.as_ref()); @@ -251,7 +256,8 @@ fn get_counter(session: &Session, state: &MutableSt session.session_has_expired.store(true, Ordering::SeqCst); return None; } - Some((c, c > state.key_creation_counter + Crypto::SETTINGS.rekey_after_key_uses)) + let rekey_at = state.key_creation_counter + Crypto::SETTINGS.rekey_after_key_uses; + Some((c, c > rekey_at)) } } @@ -330,13 +336,8 @@ fn create_a1_state( set_header(&mut x1, 0, &to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, c)); - Some(Box::new(StateA1 { - noise, - e_secret, - e1_secret, - identity: identity.try_into().unwrap(), - x1, - })) + let identity = identity.try_into().unwrap(); + Some(Box::new(StateA1 { noise, e_secret, e1_secret, identity, x1 })) } /// Corresponds to Transition Algorithm 1 found in Section 4.3. pub(crate) fn trans_to_a1>( @@ -430,12 +431,9 @@ pub(crate) fn respond_to_challenge( let mut state = session.state.write().unwrap(); if let ZetaAutomata::A1(a1) = &mut state.beta { let response_start = a1.x1.len() - CHALLENGE_SIZE; - respond_to_challenge_in_place( - ctx.rng.lock().unwrap().deref_mut(), - &mut Crypto::Hash::new(), - challenge, - (&mut a1.x1[response_start..]).try_into().unwrap(), - ); + let mut rng = ctx.rng.lock().unwrap(); + let response = (&mut a1.x1[response_start..]).try_into().unwrap(); + respond_to_challenge_in_place(rng.deref_mut(), &mut Crypto::Hash::new(), challenge, response); } } /// Corresponds to Transition Algorithm 2 found in Section 4.3. @@ -718,7 +716,7 @@ pub(crate) fn received_x2_trans::new(); d.extend([0u8; HEADER_SIZE]); let nonce = to_nonce(PACKET_TYPE_SESSION_REJECTED, c); - d.extend(Crypto::Aead::encrypt_in_place( - (&kek_send[..AES_256_KEY_SIZE]).try_into().unwrap(), - &nonce, - &[], - &mut [], - )); + let kek_send = (&kek_send[..AES_256_KEY_SIZE]).try_into().unwrap(); + d.extend(Crypto::Aead::encrypt_in_place(kek_send, &nonce, &[], &mut [])); set_header(&mut d, zeta.kid_send.get(), &nonce); d }; @@ -909,7 +901,7 @@ pub(crate) fn received_x3_trans { pub(crate) challenge: ChallengeContext, } -fn parse_fragment_header( - incoming_fragment: &[u8], -) -> Result<(usize, usize, [u8; AES_GCM_NONCE_SIZE]), ReceiveError> { +fn parse_fragment_header(incoming_fragment: &[u8]) -> Result<(usize, usize, [u8; AES_GCM_NONCE_SIZE]), ReceiveError> { let fragment_no = incoming_fragment[FRAGMENT_NO_IDX] as usize; let fragment_count = incoming_fragment[FRAGMENT_COUNT_IDX] as usize; if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS { @@ -223,11 +221,8 @@ impl Context { let session = ctx.session_map.read().unwrap().get(&kid_recv).map(|r| r.upgrade()); if let Some(Some(session)) = session { let state = session.state.read().unwrap(); - state.hk_recv.decrypt_in_place( - (&mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]) - .try_into() - .unwrap(), - ); + let header_auth = &mut incoming_fragment[HEADER_AUTH_START..HEADER_AUTH_END]; + state.hk_recv.decrypt_in_place(header_auth.try_into().unwrap()); let (fragment_no, fragment_count, nonce) = parse_fragment_header(incoming_fragment)?; let (packet_type, incoming_counter) = from_nonce(&nonce); From 9de883f39303b3d0d331c56bc4a0905e3837061e Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 11:27:28 -0400 Subject: [PATCH 73/91] reformatted --- src/result.rs | 4 +- src/zeta.rs | 171 +++++++++++++++++++++++++------------------------- src/zssp.rs | 40 ++++++------ 3 files changed, 107 insertions(+), 108 deletions(-) diff --git a/src/result.rs b/src/result.rs index a972597..126b903 100644 --- a/src/result.rs +++ b/src/result.rs @@ -112,7 +112,7 @@ pub enum ReceiveError { IoError(std::io::Error), } -macro_rules! byzantine_fault { +macro_rules! fault { ($name:expr, $unnatural:ident) => { ReceiveError::ByzantineFault { #[cfg(feature = "debug")] @@ -124,7 +124,7 @@ macro_rules! byzantine_fault { } }; } -pub(crate) use byzantine_fault; +pub(crate) use fault; /// Result generated by the context packet receive function, with possible payloads. #[derive(Clone)] diff --git a/src/zeta.rs b/src/zeta.rs index 068f964..2eb7f5a 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -17,7 +17,7 @@ use crate::fragged::Fragged; use crate::indexed_heap::BinaryHeapIndex; use crate::proto::*; use crate::ratchet_state::{RatchetState, RatchetStates}; -use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; +use crate::result::{fault, FaultType, OpenError, ReceiveError, SendError}; use crate::symmetric_state::SymmetricState; use crate::zssp::{log, ContextInner, SessionQueue}; #[cfg(feature = "logging")] @@ -125,19 +125,17 @@ impl Default for DuplexKey { } impl DuplexKey { fn replace_nk(&mut self, nk_send: &[u8; HASHLEN], nk_recv: &[u8; HASHLEN]) { - self.nk = Some(Crypto::AeadPool::new( - (&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(), - (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap(), - )) + let nk_send = (&nk_send[..AES_256_KEY_SIZE]).try_into().unwrap(); + let nk_recv = (&nk_recv[..AES_256_KEY_SIZE]).try_into().unwrap(); + self.nk = Some(Crypto::AeadPool::new(nk_send, nk_recv)) } } impl Keys { fn replace_kek(&mut self, kek: &[u8; HASHLEN]) { // We want to give rust the best chance of implementing this in a way that does // not leak the key on the stack. - self.kek - .get_or_insert(Zeroizing::new([0u8; AES_256_KEY_SIZE])) - .copy_from_slice(&kek[..AES_256_KEY_SIZE]); + let old_kek = self.kek.get_or_insert(Zeroizing::new([0u8; AES_256_KEY_SIZE])); + old_kek.copy_from_slice(&kek[..AES_256_KEY_SIZE]); } } @@ -151,6 +149,7 @@ impl MutableState { } impl SymmetricState { + #[must_use] fn write_e_no_init( &mut self, hash: &mut Crypto::Hash, @@ -165,6 +164,7 @@ impl SymmetricState { self.mix_key_no_init(hmac, &pub_key); e_secret } + #[must_use] fn read_e_no_init( &mut self, hash: &mut Crypto::Hash, @@ -179,6 +179,7 @@ impl SymmetricState { *i = j; Crypto::PublicKey::from_bytes((pub_key).try_into().unwrap()) } + #[must_use] fn mix_dh(&mut self, hmac: &mut Crypto::Hmac, secret: &Crypto::KeyPair, remote: &Crypto::PublicKey) -> Option<()> { let mut ecdh_secret = Zeroizing::new([0u8; P384_ECDH_SHARED_SECRET_SIZE]); if secret.agree(&remote, &mut ecdh_secret) { @@ -188,6 +189,7 @@ impl SymmetricState { None } } + #[must_use] fn mix_dh_no_init( &mut self, hmac: &mut Crypto::Hmac, @@ -451,11 +453,11 @@ pub(crate) fn received_x1_trans e, es, e1 // <- e, ee, ekem1, psk if !(HANDSHAKE_HELLO_MIN_SIZE..=HANDSHAKE_HELLO_MAX_SIZE).contains(&x1.len()) { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } if &n[AES_GCM_NONCE_SIZE - 8..] != &x1[x1.len() - 8..] { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let hmac = &mut Crypto::Hmac::new(); let mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); @@ -463,24 +465,24 @@ pub(crate) fn received_x1_trans s, se if HANDSHAKE_RESPONSE_SIZE != x2.len() { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } let kex_lock = session.state_machine_lock.lock().unwrap(); @@ -607,42 +609,42 @@ pub(crate) fn received_x2_trans= COUNTER_WINDOW_MAX_SKIP_AHEAD || &n[AES_GCM_NONCE_SIZE - 3..] != &x2[x2.len() - 3..] { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let mut should_warn_missing_ratchet = false; let mut result = (|| { let a1 = if let ZetaAutomata::A1(a1) = &state.beta { a1 } else { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); }; let mut noise = a1.noise.clone(); let mut i = 0; // Process message pattern 2 e token. let e_remote = noise .read_e_no_init(hash, hmac, &mut i, &x2) - .ok_or(byzantine_fault!(FailedAuth, true))?; + .ok_or(fault!(FailedAuth, true))?; // Process message pattern 2 ee token. noise .mix_dh(hmac, &a1.e_secret, &e_remote) - .ok_or(byzantine_fault!(FailedAuth, true))?; + .ok_or(fault!(FailedAuth, true))?; // Process message pattern 2 ekem1 token. let j = i + KYBER_CIPHERTEXT_SIZE; let k = j + AES_GCM_TAG_SIZE; let tag = x2[j..k].try_into().unwrap(); if !noise.decrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..j], tag) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let mut ekem1_secret = Zeroizing::new([0u8; KYBER_PLAINTEXT_SIZE]); if !a1 .e1_secret .decapsulate((&x2[i..j]).try_into().unwrap(), &mut ekem1_secret) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } noise.mix_key_no_init(hmac, ekem1_secret.as_ref()); drop(ekem1_secret); @@ -690,7 +692,7 @@ pub(crate) fn received_x2_trans::new(); x3.extend([0u8; HEADER_SIZE]); @@ -702,7 +704,7 @@ pub(crate) fn received_x2_trans s, se if x3.len() < HANDSHAKE_COMPLETION_MIN_SIZE { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } if kid != zeta.kid_recv { - return Err(byzantine_fault!(UnknownLocalKeyId, true)); + return Err(fault!(UnknownLocalKeyId, true)); } let hash = &mut Crypto::Hash::new(); let hmac = &mut Crypto::Hmac::new(); @@ -832,21 +834,20 @@ pub(crate) fn received_x3_trans return Err(byzantine_fault!(OutOfSequence, false)), + Occupied(_) => return Err(fault!(OutOfSequence, false)), Vacant(entry) => entry, }; let mut session_queue = ctx.session_queue.lock().unwrap(); @@ -996,7 +997,7 @@ pub(crate) fn received_c1_trans::new(); c2.extend([0u8; HEADER_SIZE]); if !send_control(session, &state, PACKET_TYPE_ACK, c2, send) { - return Err(byzantine_fault!(OutOfSequence, true)); + return Err(fault!(OutOfSequence, true)); } Ok(just_establised) @@ -1081,7 +1082,7 @@ pub(crate) fn received_c2_trans( use FaultType::*; if d.len() != SESSION_REJECTED_SIZE { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } let kex_lock = session.state_machine_lock.lock().unwrap(); let state = session.state.read().unwrap(); if Some(kid) != state.key_ref(true).recv.kid || !matches!(&state.beta, ZetaAutomata::A3 { .. }) { - return Err(byzantine_fault!(OutOfSequence, true)); + return Err(fault!(OutOfSequence, true)); } let tag = d[..].try_into().unwrap(); if !Crypto::Aead::decrypt_in_place(state.key_ref(true).recv.kek.as_ref().unwrap(), n, &[], &mut [], tag) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let (_, c) = from_nonce(n); if !session.window.update(c) { - return Err(byzantine_fault!(ExpiredCounter, true)); + return Err(fault!(ExpiredCounter, true)); } drop(state); @@ -1352,7 +1353,7 @@ pub(crate) fn received_k1_trans psk, e, es, ss // <- e, ee, se if k1.len() != REKEY_SIZE { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } let kex_lock = session.state_machine_lock.lock().unwrap(); @@ -1360,7 +1361,7 @@ pub(crate) fn received_k1_trans true, @@ -1369,7 +1370,7 @@ pub(crate) fn received_k1_trans::new(); k2.extend([0u8; HEADER_SIZE]); @@ -1425,11 +1426,11 @@ pub(crate) fn received_k1_trans::new(); c1.extend([0u8; HEADER_SIZE]); if !send_control(&session, &state, PACKET_TYPE_KEY_CONFIRM, c1, send) { - return Err(byzantine_fault!(OutOfSequence, true)); + return Err(fault!(OutOfSequence, true)); } Ok(()) @@ -1744,12 +1745,10 @@ pub(crate) fn receive_payload_in_place( state.keys[1].nk.as_ref() } else { // Should be unreachable unless we are leaking kids somewhere. - return Err(byzantine_fault!(UnknownLocalKeyId, true)); + return Err(fault!(UnknownLocalKeyId, true)); }; - let mut cipher = specified_key - .ok_or(byzantine_fault!(OutOfSequence, true))? - .start_dec(nonce); + let mut cipher = specified_key.ok_or(fault!(OutOfSequence, true))?.start_dec(nonce); let (_, c) = from_nonce(nonce); // NOTE: This only works because we check the size of every received fragment in the receive @@ -1765,13 +1764,13 @@ pub(crate) fn receive_payload_in_place( cipher.decrypt_in_place(&mut fragment[..tag_idx]); if !cipher.finish((&fragment[tag_idx..]).try_into().unwrap()) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } if !session.window.update(c) { // This error is marked as not happening naturally, but it could occur if something about // the transport protocol is duplicating packets. - return Err(byzantine_fault!(ExpiredCounter, true)); + return Err(fault!(ExpiredCounter, true)); } for fragment in fragments { diff --git a/src/zssp.rs b/src/zssp.rs index 03cfd22..7ba3b41 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -18,7 +18,7 @@ use crate::fragged::Assembled; use crate::handshake_cache::UnassociatedHandshakeCache; use crate::indexed_heap::IndexedBinaryHeap; use crate::proto::*; -use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; +use crate::result::{fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; #[cfg(feature = "logging")] use crate::LogEvent::*; @@ -63,7 +63,7 @@ fn parse_fragment_header(incoming_fragment: &[u8]) -> Result<(usize, usize, [u8; let fragment_no = incoming_fragment[FRAGMENT_NO_IDX] as usize; let fragment_count = incoming_fragment[FRAGMENT_COUNT_IDX] as usize; if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS { - return Err(byzantine_fault!(FaultType::InvalidPacket, true)); + return Err(fault!(FaultType::InvalidPacket, true)); } let mut nonce = [0u8; AES_GCM_NONCE_SIZE]; nonce[2..].copy_from_slice(&incoming_fragment[PACKET_NONCE_START..HEADER_SIZE]); @@ -211,7 +211,7 @@ impl Context { send_unassociated_mtu = send_unassociated_mtu.max(MIN_TRANSPORT_MTU); let incoming_fragment: &mut [u8] = incoming_fragment_buf.as_mut(); if incoming_fragment.len() < MIN_PACKET_SIZE { - return Err(byzantine_fault!(FaultType::InvalidPacket, false)); + return Err(fault!(FaultType::InvalidPacket, false)); } let mut fragment_buffer = Assembled::new(); @@ -239,10 +239,10 @@ impl Context { if !matches!(&state.beta, ZetaAutomata::A1(_)) { // A resent handshake response from Bob may have arrived out of order, // after we already received one. - return Err(byzantine_fault!(OutOfSequence, false)); + return Err(fault!(OutOfSequence, false)); } if incoming_counter >= COUNTER_WINDOW_MAX_SKIP_AHEAD { - return Err(byzantine_fault!(ExpiredCounter, true)); + return Err(fault!(ExpiredCounter, true)); } } else if PACKET_TYPE_USES_COUNTER_RANGE.contains(&packet_type) { // For DOS resistant reply-protection we need to check that the given counter is @@ -258,14 +258,14 @@ impl Context { // received the first session key and is reject all of Alice's resends. // This can also occur if a session was manually expired, but not // dropped, and the remote party is still sending us data. - return Err(byzantine_fault!(ExpiredCounter, false)); + return Err(fault!(ExpiredCounter, false)); } } else if packet_type == PACKET_TYPE_HANDSHAKE_COMPLETION { // This can be triggered if Bob successfully received a session key and // needs to reject all of Alice's resends of PACKET_TYPE_NOISE_XK_PATTERN_3. - return Err(byzantine_fault!(InvalidPacket, false)); + return Err(fault!(InvalidPacket, false)); } else { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } } @@ -312,7 +312,7 @@ impl Context { for fragment in fragment_buffer.as_ref() { buffer .try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]) - .map_err(|_| byzantine_fault!(InvalidPacket, true))?; + .map_err(|_| fault!(InvalidPacket, true))?; } // We have not yet authenticated the sender so we do not report // receiving a packet from them. @@ -405,7 +405,7 @@ impl Context { log!(app, DIsAuthClosedSession(&session)); SessionEvent::Rejected } - _ => return Err(byzantine_fault!(InvalidPacket, true)), // This is unreachable. + _ => return Err(fault!(InvalidPacket, true)), // This is unreachable. } }; Ok(ReceiveOk::Session(session, ret)) @@ -429,7 +429,7 @@ impl Context { { //vrfy if packet_type != PACKET_TYPE_HANDSHAKE_COMPLETION || incoming_counter != 0 { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } } @@ -448,7 +448,7 @@ impl Context { for fragment in fragment_buffer.as_ref() { buffer .try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]) - .map_err(|_| byzantine_fault!(InvalidPacket, true))?; + .map_err(|_| fault!(InvalidPacket, true))?; } buffer.as_mut() } @@ -479,7 +479,7 @@ impl Context { // This can occur naturally because either Bob's incoming_sessions cache got // full so Alice's incoming session was dropped, or the session this packet // was for was dropped by the application. - return Err(byzantine_fault!(UnknownLocalKeyId, false)); + return Err(fault!(UnknownLocalKeyId, false)); } } } else { @@ -490,7 +490,7 @@ impl Context { { //vrfy if packet_type != PACKET_TYPE_HANDSHAKE_HELLO && packet_type != PACKET_TYPE_CHALLENGE { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } } @@ -513,7 +513,7 @@ impl Context { for fragment in fragment_buffer.as_ref() { buffer .try_extend_from_slice(&fragment.as_ref()[HEADER_SIZE..]) - .map_err(|_| byzantine_fault!(InvalidPacket, true))?; + .map_err(|_| fault!(InvalidPacket, true))?; } buffer.as_mut() } @@ -527,7 +527,7 @@ impl Context { if !(HANDSHAKE_HELLO_CHALLENGE_MIN_SIZE..=HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE) .contains(&assembled_packet.len()) { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } // Process recv challenge layer. let challenge_start = assembled_packet.len() - CHALLENGE_SIZE; @@ -554,7 +554,7 @@ impl Context { send_unassociated_reply(&mut challenge_packet); // If we issue a challenge the first hello packet will always fail. - return Err(byzantine_fault!(FailedAuth, false)); + return Err(fault!(FailedAuth, false)); } else { log!(app, X1SucceededChallenge); } @@ -580,7 +580,7 @@ impl Context { log!(app, ReceivedRawChallenge); // Process recv challenge layer. if assembled_packet.len() != KID_SIZE + CHALLENGE_SIZE { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } if let Some(kid_recv) = NonZeroU32::new(u32::from_ne_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) @@ -591,9 +591,9 @@ impl Context { return Ok(ReceiveOk::Unassociated); } } - Err(byzantine_fault!(UnknownLocalKeyId, true)) + Err(fault!(UnknownLocalKeyId, true)) } else { - Err(byzantine_fault!(InvalidPacket, true)) + Err(fault!(InvalidPacket, true)) } } } From 77d2d0f4f1cb8a91bee7d2575480faff84b0dc66 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 12:02:57 -0400 Subject: [PATCH 74/91] reformatted imports --- src/zeta.rs | 5 +++-- src/zssp.rs | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/zeta.rs b/src/zeta.rs index 2eb7f5a..59abe4c 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -1,5 +1,3 @@ -use arrayvec::ArrayVec; -use rand_core::RngCore; use std::cmp::Reverse; use std::collections::HashMap; use std::io::Write; @@ -7,6 +5,9 @@ use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, Weak}; + +use arrayvec::ArrayVec; +use rand_core::RngCore; use zeroize::Zeroizing; use crate::antireplay::Window; diff --git a/src/zssp.rs b/src/zssp.rs index 7ba3b41..7190c3a 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -11,7 +11,6 @@ use rand_core::RngCore; use crate::challenge::ChallengeContext; use crate::crypto::*; use crate::zeta::*; - use crate::application::*; use crate::frag_cache::UnassociatedFragCache; use crate::fragged::Assembled; From 91c00f318b1d2a2286ff4832ebfdb77b131c5cc0 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 12:25:33 -0400 Subject: [PATCH 75/91] added benchmark --- examples/basic_test.rs | 2 +- examples/benchmark.rs | 314 +++++++++++++++++++++++++++++++++++++++++ src/application.rs | 3 +- src/zeta.rs | 2 +- src/zssp.rs | 7 +- 5 files changed, 322 insertions(+), 6 deletions(-) create mode 100644 examples/benchmark.rs diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 9d9bdc8..11e5d0b 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -68,7 +68,7 @@ impl ApplicationLayer for &TestApplication { type Crypto = TestApplication; fn incoming_session(&mut self) -> IncomingSessionAction { - IncomingSessionAction::Allow + IncomingSessionAction::Challenge } fn hello_requires_recognized_ratchet(&mut self) -> bool { diff --git a/examples/benchmark.rs b/examples/benchmark.rs new file mode 100644 index 0000000..52ce563 --- /dev/null +++ b/examples/benchmark.rs @@ -0,0 +1,314 @@ +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{mpsc, Arc}; +use std::thread; +use std::time::{Duration, Instant}; + +use arrayvec::ArrayVec; +use rand_core::OsRng; +use rand_core::RngCore; + +use zssp::application::{ + AcceptAction, ApplicationLayer, CryptoLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, + RATCHET_SIZE, +}; +use zssp::crypto::P384KeyPair; +use zssp::crypto_impl::*; +use zssp::result::ReceiveError; +use zssp::Session; + +const TEST_MTU: usize = 1500; + +struct TestApplication { + time: Instant, +} + +#[allow(unused)] +impl CryptoLayer for TestApplication { + type Rng = OsRng; + type PrpEnc = Aes256OpenSSLEnc; + type PrpDec = Aes256OpenSSLDec; + type Aead = AesGcmOpenSSL; + type AeadPool = AesGcmOpenSSLPool; + type Hash = Sha512Crate; + type Hmac = HmacSha512Crate; + type PublicKey = P384CratePublicKey; + type KeyPair = P384CrateKeyPair; + type Kem = RustKyber1024PrivateKey; + + type SessionData = (); + + type IncomingPacketBuffer = Vec; +} +#[allow(unused)] +impl ApplicationLayer for &TestApplication { + type Crypto = TestApplication; + + fn incoming_session(&mut self) -> IncomingSessionAction { + IncomingSessionAction::Allow + } + + fn hello_requires_recognized_ratchet(&mut self) -> bool { + false + } + + fn initiator_disallows_downgrade(&mut self, session: &Arc>) -> bool { + false + } + + fn check_accept_session( + &mut self, + remote_static_key: &P384CratePublicKey, + identity: &[u8], + ) -> AcceptAction { + AcceptAction { + session_data: Some(()), + responder_disallows_downgrade: true, + responder_silently_rejects: false, + } + } + + fn restore_by_fingerprint(&mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE]) -> Result, ()> { + Ok(None) + } + + fn restore_by_identity( + &mut self, + remote_static_key: &P384CratePublicKey, + session_data: &(), + ) -> Result, ()> { + Ok(None) + } + + fn save_ratchet_state( + &mut self, + remote_static_key: &P384CratePublicKey, + session_data: &(), + update_data: RatchetUpdate<'_>, + ) -> Result<(), ()> { + Ok(()) + } + + fn time(&mut self) -> i64 { + self.time.elapsed().as_millis() as i64 + } +} + +#[allow(unused)] +fn alice_main( + run: &AtomicBool, + alice_app: &TestApplication, + alice_out: mpsc::SyncSender>, + alice_in: mpsc::Receiver>, + alice_keypair: P384CrateKeyPair, + bob_pubkey: P384CratePublicKey, +) { + let startup_time = std::time::Instant::now(); + let context = zssp::Context::::new(alice_keypair, OsRng); + let mut next_service = startup_time.elapsed().as_millis() as i64 + 500; + let test_data = [1u8; TEST_MTU * 10]; + let mut up = false; + + let alice_session = Some( + context + .open( + alice_app, + |b| alice_out.send(b.to_vec()).is_ok(), + TEST_MTU, + bob_pubkey.clone(), + (), + &[], + ) + .unwrap(), + ); + println!("[alice] opening session"); + while run.load(Ordering::Relaxed) { + let current_time = startup_time.elapsed().as_millis() as i64; + loop { + let pkt = alice_in.try_recv(); + if let Ok(pkt) = pkt { + use zssp::result::ReceiveOk::*; + use zssp::result::SessionEvent::*; + let mut output_data = Vec::new(); + match context.receive( + alice_app, + |b| alice_out.send(b.to_vec()).is_ok(), + TEST_MTU, + |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)), + &0, + pkt, + &mut output_data, + ) { + Ok(Unassociated) => { + //println!("[alice] ok"); + } + Ok(Session(_, event)) => match event { + Established => { + up = true; + } + Data => { + assert!(!output_data.is_empty()); + //println!("[alice] received {}", data.len()); + } + Control => (), + _ => panic!(), + }, + Err(e) => { + println!("[alice] ERROR {:?}", e); + if let ReceiveError::ByzantineFault { unnatural, .. } = e { + assert!(!unnatural) + } + } + } + } else { + break; + } + } + + if up { + context + .send( + alice_session.as_ref().unwrap(), + |b| alice_out.send(b.to_vec()).is_ok(), + &mut [0u8; TEST_MTU], + &test_data[..1400 + ((OsRng.next_u64() as usize) % (test_data.len() - 1400))], + ) + .unwrap(); + } else { + thread::sleep(Duration::from_millis(10)); + } + + if current_time >= next_service { + next_service = current_time + + context.service(alice_app, |_| { + Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)) + }); + } + } +} + +#[allow(unused)] +fn bob_main( + run: &AtomicBool, + bob_app: &TestApplication, + bob_out: mpsc::SyncSender>, + bob_in: mpsc::Receiver>, + bob_keypair: P384CrateKeyPair, +) { + let startup_time = std::time::Instant::now(); + let context = zssp::Context::::new(bob_keypair, OsRng); + let mut last_speed_metric = startup_time.elapsed().as_millis() as i64; + let mut next_service = last_speed_metric + 500; + let mut transferred = 0u64; + let mut output_data = ArrayVec::::new(); + + let mut bob_session = None; + + while run.load(Ordering::Relaxed) { + let pkt = bob_in.recv_timeout(Duration::from_millis(100)); + let current_time = startup_time.elapsed().as_millis() as i64; + + if let Ok(pkt) = pkt { + use zssp::result::ReceiveOk::*; + use zssp::result::SessionEvent::*; + match context.receive( + bob_app, + |b| bob_out.send(b.to_vec()).is_ok(), + TEST_MTU, + |_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)), + &0, + pkt, + &mut output_data, + ) { + Ok(Unassociated) => {} + Ok(Session(s, event)) => match event { + NewSession | NewDowngradedSession => { + println!("[bob] new session, took {}s", current_time as f32 / 1000.0); + let _ = bob_session.replace(s); + } + Data => { + assert!(!output_data.is_empty()); + //println!("[bob] received {}", output_data.len()); + transferred += output_data.len() as u64 * 2; // *2 because we are also sending this many bytes back + context + .send( + &s, + |b| bob_out.send(b.to_vec()).is_ok(), + &mut [0u8; TEST_MTU], + &output_data, + ) + .unwrap(); + } + Control => (), + _ => panic!(), + }, + Err(e) => { + println!("[bob] ERROR {:?}", e); + if let ReceiveError::ByzantineFault { unnatural, .. } = e { + assert!(!unnatural) + } + } + } + } + + let speed_metric_elapsed = current_time - last_speed_metric; + if speed_metric_elapsed >= 10000 { + last_speed_metric = current_time; + println!( + "[bob] throughput: {} MiB/sec (combined input and output)", + ((transferred as f64) / 1048576.0) / ((speed_metric_elapsed as f64) / 1000.0) + ); + transferred = 0; + } + + if current_time >= next_service { + next_service = current_time + + context.service(bob_app, |_| { + Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)) + }); + } + } +} + +fn core(time: u64) { + let run = &AtomicBool::new(true); + + let alice_keypair = P384CrateKeyPair::generate(&mut OsRng); + let alice_app = TestApplication { time: Instant::now()}; + let bob_keypair = P384CrateKeyPair::generate(&mut OsRng); + let bob_pubkey = bob_keypair.public_key(); + let bob_app = TestApplication { time: Instant::now() }; + + let (alice_out, bob_in) = mpsc::sync_channel::>(256); + let (bob_out, alice_in) = mpsc::sync_channel::>(256); + + thread::scope(|ts| { + { + let alice_out = alice_out.clone(); + ts.spawn(move || { + alice_main( + run, + &alice_app, + alice_out, + alice_in, + alice_keypair, + bob_pubkey, + ) + }); + } + ts.spawn(move || bob_main(run, &bob_app, bob_out, bob_in, bob_keypair)); + + thread::sleep(Duration::from_secs(time)); + + run.store(false, Ordering::SeqCst); + println!("finished"); + }); +} + +fn main() { + core(60 * 60) +} + +#[test] +fn test_main() { + core(2) +} diff --git a/src/application.rs b/src/application.rs index fe94c9f..3246920 100644 --- a/src/application.rs +++ b/src/application.rs @@ -243,7 +243,8 @@ pub trait ApplicationLayer: Sized { /// These are provided for debugging, logging or metrics purposes, and must be used for /// nothing else. Do not base protocol-level decisions upon the events passed to this function. #[cfg(feature = "logging")] - fn event_log(&mut self, event: crate::LogEvent<'_, Self::Crypto>); + #[allow(unused)] + fn event_log(&mut self, event: crate::LogEvent<'_, Self::Crypto>) {} } #[derive(Debug, PartialEq, Eq, Clone)] diff --git a/src/zeta.rs b/src/zeta.rs index 59abe4c..f6d3cb7 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -229,7 +229,7 @@ pub(crate) fn from_nonce(n: &[u8]) -> (u8, u64) { let c_start = n.len() - 8; (n[c_start - 1], u64::from_be_bytes(n[c_start..].try_into().unwrap())) } -fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_NONCE_SIZE]) { +pub(crate) fn set_header(packet: &mut [u8], kid_send: u32, nonce: &[u8; AES_GCM_NONCE_SIZE]) { packet[..KID_SIZE].copy_from_slice(&kid_send.to_ne_bytes()); packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[NONCE_SIZE_DIFF..]); } diff --git a/src/zssp.rs b/src/zssp.rs index 7190c3a..2996c0c 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -8,16 +8,16 @@ use std::sync::{Arc, Mutex, RwLock, Weak}; use arrayvec::ArrayVec; use rand_core::RngCore; +use crate::application::*; use crate::challenge::ChallengeContext; use crate::crypto::*; -use crate::zeta::*; -use crate::application::*; use crate::frag_cache::UnassociatedFragCache; use crate::fragged::Assembled; use crate::handshake_cache::UnassociatedHandshakeCache; use crate::indexed_heap::IndexedBinaryHeap; use crate::proto::*; use crate::result::{fault, FaultType, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; +use crate::zeta::*; #[cfg(feature = "logging")] use crate::LogEvent::*; @@ -549,7 +549,8 @@ impl Context { challenge_packet.extend(challenge); let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); challenge_packet[FRAGMENT_COUNT_IDX] = 1; - challenge_packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce); + challenge_packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[..PACKET_NONCE_SIZE]); + set_header(&mut challenge_packet, 0, &nonce); send_unassociated_reply(&mut challenge_packet); // If we issue a challenge the first hello packet will always fail. From ad8272bf4acb2b12458d9b88ed4dfaa36af13624 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 13:07:03 -0400 Subject: [PATCH 76/91] fixed benchmark --- examples/benchmark.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/benchmark.rs b/examples/benchmark.rs index 52ce563..0defb8d 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -107,6 +107,7 @@ fn alice_main( let mut next_service = startup_time.elapsed().as_millis() as i64 + 500; let test_data = [1u8; TEST_MTU * 10]; let mut up = false; + let mut output_data = ArrayVec::::new(); let alice_session = Some( context @@ -128,7 +129,7 @@ fn alice_main( if let Ok(pkt) = pkt { use zssp::result::ReceiveOk::*; use zssp::result::SessionEvent::*; - let mut output_data = Vec::new(); + output_data.clear(); match context.receive( alice_app, |b| alice_out.send(b.to_vec()).is_ok(), @@ -210,6 +211,7 @@ fn bob_main( if let Ok(pkt) = pkt { use zssp::result::ReceiveOk::*; use zssp::result::SessionEvent::*; + output_data.clear(); match context.receive( bob_app, |b| bob_out.send(b.to_vec()).is_ok(), From 9f4cd4bfcddd7e0597459ae3d0861595349dfd9e Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 13:28:21 -0400 Subject: [PATCH 77/91] removed extra lines --- examples/benchmark.rs | 13 ++----------- src/zeta.rs | 7 ++----- src/zssp.rs | 3 ++- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/examples/benchmark.rs b/examples/benchmark.rs index 0defb8d..b8b9e4e 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -275,7 +275,7 @@ fn core(time: u64) { let run = &AtomicBool::new(true); let alice_keypair = P384CrateKeyPair::generate(&mut OsRng); - let alice_app = TestApplication { time: Instant::now()}; + let alice_app = TestApplication { time: Instant::now() }; let bob_keypair = P384CrateKeyPair::generate(&mut OsRng); let bob_pubkey = bob_keypair.public_key(); let bob_app = TestApplication { time: Instant::now() }; @@ -286,16 +286,7 @@ fn core(time: u64) { thread::scope(|ts| { { let alice_out = alice_out.clone(); - ts.spawn(move || { - alice_main( - run, - &alice_app, - alice_out, - alice_in, - alice_keypair, - bob_pubkey, - ) - }); + ts.spawn(move || alice_main(run, &alice_app, alice_out, alice_in, alice_keypair, bob_pubkey)); } ts.spawn(move || bob_main(run, &bob_app, bob_out, bob_in, bob_keypair)); diff --git a/src/zeta.rs b/src/zeta.rs index f6d3cb7..b2d9b52 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -1705,11 +1705,8 @@ pub(crate) fn send_payload( ); mtu_sized_buffer[HEADER_SIZE + payload_rem..HEADER_SIZE + fragment_len].copy_from_slice(&cipher.finish()); - state.hk_send.encrypt_in_place( - (&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]) - .try_into() - .unwrap(), - ); + let header_auth = &mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]; + state.hk_send.encrypt_in_place(header_auth.try_into().unwrap()); if !send(&mut mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { return Ok(()); diff --git a/src/zssp.rs b/src/zssp.rs index 2996c0c..27b24a6 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -549,7 +549,8 @@ impl Context { challenge_packet.extend(challenge); let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); challenge_packet[FRAGMENT_COUNT_IDX] = 1; - challenge_packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[..PACKET_NONCE_SIZE]); + challenge_packet[PACKET_NONCE_START..HEADER_SIZE] + .copy_from_slice(&nonce[..PACKET_NONCE_SIZE]); set_header(&mut challenge_packet, 0, &nonce); send_unassociated_reply(&mut challenge_packet); From ea46c2bced38b7ccbaff987676b67536a66d2ba4 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 16:21:21 -0400 Subject: [PATCH 78/91] added profiler --- .gitignore | 1 + Cargo.toml | 3 + examples/benchmark.rs | 15 +- flamegraph.svg | 491 ++++++++++++++++++++++++++++++++++++++++++ src/zeta.rs | 26 +-- src/zssp.rs | 3 +- 6 files changed, 508 insertions(+), 31 deletions(-) create mode 100644 flamegraph.svg diff --git a/.gitignore b/.gitignore index ea8c4bf..790f4b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +./perf* diff --git a/Cargo.toml b/Cargo.toml index 48a75dc..80513a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ name = "zssp" path = "src/lib.rs" doc = true +[profile.bench] +debug = true + [dependencies] rand_core = { version = "0.6.4" } zeroize = { version = "1.6.0" } diff --git a/examples/benchmark.rs b/examples/benchmark.rs index 0defb8d..b2ec3fb 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -275,7 +275,7 @@ fn core(time: u64) { let run = &AtomicBool::new(true); let alice_keypair = P384CrateKeyPair::generate(&mut OsRng); - let alice_app = TestApplication { time: Instant::now()}; + let alice_app = TestApplication { time: Instant::now() }; let bob_keypair = P384CrateKeyPair::generate(&mut OsRng); let bob_pubkey = bob_keypair.public_key(); let bob_app = TestApplication { time: Instant::now() }; @@ -286,16 +286,7 @@ fn core(time: u64) { thread::scope(|ts| { { let alice_out = alice_out.clone(); - ts.spawn(move || { - alice_main( - run, - &alice_app, - alice_out, - alice_in, - alice_keypair, - bob_pubkey, - ) - }); + ts.spawn(move || alice_main(run, &alice_app, alice_out, alice_in, alice_keypair, bob_pubkey)); } ts.spawn(move || bob_main(run, &bob_app, bob_out, bob_in, bob_keypair)); @@ -307,7 +298,7 @@ fn core(time: u64) { } fn main() { - core(60 * 60) + core(20) } #[test] diff --git a/flamegraph.svg b/flamegraph.svg new file mode 100644 index 0000000..8075ab4 --- /dev/null +++ b/flamegraph.svg @@ -0,0 +1,491 @@ +Flame Graph Reset ZoomSearch <zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (13 samples, 0.03%)zssp::crypto_impl::openssl::CipherCtx::update (5 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (35 samples, 0.09%)zssp::crypto_impl::openssl::CipherCtx::update (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (71 samples, 0.18%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (8 samples, 0.02%)CRYPTO_THREAD_read_lock (7 samples, 0.02%)CRYPTO_gcm128_decrypt (8 samples, 0.02%)CRYPTO_gcm128_decrypt_ctr32 (21 samples, 0.05%)CRYPTO_gcm128_encrypt (10 samples, 0.03%)CRYPTO_gcm128_encrypt_ctr32 (16 samples, 0.04%)CRYPTO_gcm128_finish (4 samples, 0.01%)CRYPTO_gcm128_init (9 samples, 0.02%)CRYPTO_gcm128_tag (5 samples, 0.01%)CRYPTO_zalloc (25 samples, 0.06%)EVP_CIPHER_CTX_ctrl (5 samples, 0.01%)EVP_CIPHER_CTX_reset (7 samples, 0.02%)EVP_CIPHER_CTX_set_padding (12 samples, 0.03%)EVP_CIPHER_fetch (6 samples, 0.02%)EVP_CIPHER_free (17 samples, 0.04%)EVP_DecryptUpdate (4 samples, 0.01%)EVP_EncryptFinal_ex (6 samples, 0.02%)OPENSSL_LH_retrieve (10 samples, 0.03%)OPENSSL_strnlen (5 samples, 0.01%)OSSL_PARAM_locate (121 samples, 0.31%)OSSL_PARAM_set_uint64 (4 samples, 0.01%)[libc.so.6] (60 samples, 0.15%)[libcrypto.so.3] (289 samples, 0.74%)cfree (13 samples, 0.03%)malloc (31 samples, 0.08%)pthread_rwlock_rdlock (5 samples, 0.01%)pthread_rwlock_unlock (7 samples, 0.02%)std::sync::mpmc::Sender<T>::send (9 samples, 0.02%)<std::sync::mpmc::select::Token as core::default::Default>::default (11 samples, 0.03%)std::sync::mpmc::array::Channel<T>::start_send (11 samples, 0.03%)std::sync::mpmc::array::Channel<T>::send (45 samples, 0.12%)std::sync::mpmc::array::Channel<T>::write (7 samples, 0.02%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::try_recv (19 samples, 0.05%)std::sync::mpmc::utils::Backoff::new (11 samples, 0.03%)core::sync::atomic::AtomicBool::load (10 samples, 0.03%)core::sync::atomic::atomic_load (10 samples, 0.03%)std::sync::mpmc::waker::SyncWaker::notify (11 samples, 0.03%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (7 samples, 0.02%)std::time::Instant::elapsed (7 samples, 0.02%)syscall (10 samples, 0.03%)__entry_text_start (6 samples, 0.02%)[anon] (1,024 samples, 2.62%)[a..zssp::zeta::receive_payload_in_place (25 samples, 0.06%)std::io::impls::<impl std::io::Write for &mut W>::write (7 samples, 0.02%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (7 samples, 0.02%)core::intrinsics::copy_nonoverlapping (7 samples, 0.02%)<std::sync::mpmc::zero::ZeroToken as core::default::Default>::default (4 samples, 0.01%)EVP_DecryptUpdate (4 samples, 0.01%)[libc.so.6] (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (8 samples, 0.02%)std::sync::mpmc::array::Channel<T>::start_recv (9 samples, 0.02%)[benchmark] (54 samples, 0.14%)zssp::zssp::Context<Crypto>::receive (17 samples, 0.04%)CRYPTO_THREAD_read_lock (17 samples, 0.04%)CRYPTO_THREAD_run_once (4 samples, 0.01%)CRYPTO_THREAD_unlock (19 samples, 0.05%)CRYPTO_gcm128_finish (14 samples, 0.04%)CRYPTO_gcm128_setiv (7 samples, 0.02%)CRYPTO_get_ex_data (10 samples, 0.03%)OPENSSL_sk_value (4 samples, 0.01%)OSSL_PARAM_construct_size_t (5 samples, 0.01%)[libc.so.6] (17 samples, 0.04%)[libcrypto.so.3] (145 samples, 0.37%)pthread_getspecific (7 samples, 0.02%)pthread_rwlock_rdlock (22 samples, 0.06%)[libcrypto.so.3] (316 samples, 0.81%)pthread_rwlock_unlock (22 samples, 0.06%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (14 samples, 0.04%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (14 samples, 0.04%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (80 samples, 0.20%)zssp::crypto_impl::openssl::CipherCtx::update (10 samples, 0.03%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (8 samples, 0.02%)zssp::crypto_impl::openssl::CipherCtx::update (8 samples, 0.02%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (7 samples, 0.02%)CRYPTO_THREAD_read_lock (6 samples, 0.02%)CRYPTO_THREAD_unlock (11 samples, 0.03%)CRYPTO_gcm128_decrypt (10 samples, 0.03%)CRYPTO_gcm128_decrypt_ctr32 (22 samples, 0.06%)CRYPTO_gcm128_encrypt (15 samples, 0.04%)CRYPTO_gcm128_encrypt_ctr32 (28 samples, 0.07%)CRYPTO_strndup (5 samples, 0.01%)EVP_CIPHER_CTX_ctrl (10 samples, 0.03%)EVP_CIPHER_CTX_get_iv_length (4 samples, 0.01%)EVP_CIPHER_get_block_size (9 samples, 0.02%)EVP_DecryptUpdate (54 samples, 0.14%)EVP_EncryptUpdate (45 samples, 0.12%)OPENSSL_LH_retrieve (8 samples, 0.02%)OPENSSL_init_crypto (4 samples, 0.01%)OSSL_PARAM_locate (18 samples, 0.05%)[[vdso]] (8 samples, 0.02%)[benchmark] (13 samples, 0.03%)EVP_DecryptUpdate (13 samples, 0.03%)[libc.so.6] (88 samples, 0.23%)[libcrypto.so.3] (322 samples, 0.82%)__bss_start (11 samples, 0.03%)[libcrypto.so.3] (11 samples, 0.03%)__entry_text_start (39 samples, 0.10%)_copy_to_iter (4 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)_copy_to_iter (37 samples, 0.09%)copyout (29 samples, 0.07%)asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)raw_irqentry_exit_cond_resched (10 samples, 0.03%)preempt_schedule_irq (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_pmu_nop_void (5 samples, 0.01%)__memcpy (6 samples, 0.02%)chacha_block_generic (225 samples, 0.58%)chacha_permute (202 samples, 0.52%)__x64_sys_getrandom (367 samples, 0.94%)get_random_bytes_user (354 samples, 0.91%)crng_make_state (276 samples, 0.71%)crng_fast_key_erasure (253 samples, 0.65%)exit_to_user_mode_prepare (4 samples, 0.01%)exit_to_user_mode_prepare (13 samples, 0.03%)fpregs_assert_state_consistent (5 samples, 0.01%)do_syscall_64 (396 samples, 1.01%)syscall_exit_to_user_mode (21 samples, 0.05%)entry_SYSCALL_64_after_hwframe (403 samples, 1.03%)<rand_core::os::OsRng as rand_core::RngCore>::next_u64 (485 samples, 1.24%)rand_core::impls::next_u64_via_fill (485 samples, 1.24%)<rand_core::os::OsRng as rand_core::RngCore>::fill_bytes (485 samples, 1.24%)<rand_core::os::OsRng as rand_core::RngCore>::try_fill_bytes (485 samples, 1.24%)getrandom::getrandom (485 samples, 1.24%)getrandom::getrandom_uninit (485 samples, 1.24%)getrandom::imp::getrandom_inner (484 samples, 1.24%)getrandom::util_libc::sys_fill_exact (484 samples, 1.24%)getrandom::imp::getrandom_inner::_{{closure}} (482 samples, 1.23%)getrandom::imp::getrandom (482 samples, 1.23%)syscall (482 samples, 1.23%)syscall_return_via_sysret (11 samples, 0.03%)[libc.so.6] (18 samples, 0.05%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (7 samples, 0.02%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (7 samples, 0.02%)core::sync::atomic::AtomicUsize::fetch_sub (6 samples, 0.02%)core::sync::atomic::atomic_sub (6 samples, 0.02%)_ZN3std4sync4mpmc5array16Channel$LT$T$GT$10start_recv17h7800ca29c64cb868E.llvm.12455019271255371362 (7 samples, 0.02%)core::result::Result<T,E>::map_err (5 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (15 samples, 0.04%)std::sync::mpmc::array::Channel<T>::read (17 samples, 0.04%)core::slice::<impl [T]>::get_unchecked (8 samples, 0.02%)<usize as core::slice::index::SliceIndex<[T]>>::get_unchecked (8 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::add (8 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::offset (8 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (47 samples, 0.12%)core::sync::atomic::atomic_compare_exchange_weak (47 samples, 0.12%)core::sync::atomic::AtomicUsize::load (143 samples, 0.37%)core::sync::atomic::atomic_load (143 samples, 0.37%)core::sync::atomic::fence (16 samples, 0.04%)std::sync::mpsc::Receiver<T>::try_recv (299 samples, 0.77%)std::sync::mpmc::Receiver<T>::try_recv (299 samples, 0.77%)std::sync::mpmc::array::Channel<T>::try_recv (287 samples, 0.73%)std::sync::mpmc::array::Channel<T>::start_recv (248 samples, 0.63%)<std::time::Instant as core::ops::arith::Sub>::sub (6 samples, 0.02%)std::time::Instant::duration_since (6 samples, 0.02%)std::time::Instant::checked_duration_since (5 samples, 0.01%)std::sys::unix::time::inner::Instant::checked_sub_instant (5 samples, 0.01%)std::sys::unix::time::Timespec::sub_timespec (5 samples, 0.01%)std::time::Instant::elapsed (28 samples, 0.07%)std::time::Instant::now (22 samples, 0.06%)std::sys::unix::time::inner::Instant::now (22 samples, 0.06%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (22 samples, 0.06%)clock_gettime (21 samples, 0.05%)[[vdso]] (21 samples, 0.05%)[[vdso]] (17 samples, 0.04%)<T as core::convert::TryInto<U>>::try_into (386 samples, 0.99%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (386 samples, 0.99%)core::result::Result<T,E>::map (386 samples, 0.99%)<std::sync::mpmc::zero::ZeroToken as core::default::Default>::default (4 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (61 samples, 0.16%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (61 samples, 0.16%)std::sys::unix::locks::futex_mutex::Mutex::unlock (58 samples, 0.15%)core::sync::atomic::AtomicU32::swap (56 samples, 0.14%)core::sync::atomic::atomic_swap (56 samples, 0.14%)std::sync::mutex::Mutex<T>::lock (67 samples, 0.17%)std::sys::unix::locks::futex_mutex::Mutex::lock (66 samples, 0.17%)core::sync::atomic::AtomicU32::compare_exchange (65 samples, 0.17%)core::sync::atomic::atomic_compare_exchange (65 samples, 0.17%)EVP_CIPHER_CTX_get_block_size (4 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (362 samples, 0.93%)zssp::crypto_impl::openssl::CipherCtx::update (228 samples, 0.58%)EVP_DecryptUpdate (221 samples, 0.57%)[libcrypto.so.3] (203 samples, 0.52%)[libcrypto.so.3] (148 samples, 0.38%)[libcrypto.so.3] (137 samples, 0.35%)__rust_probestack (8 samples, 0.02%)alloc::sync::Weak<T>::upgrade::_{{closure}} (5 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (54 samples, 0.14%)core::sync::atomic::atomic_compare_exchange_weak (54 samples, 0.14%)alloc::sync::Weak<T>::upgrade (78 samples, 0.20%)core::sync::atomic::AtomicUsize::fetch_update (72 samples, 0.18%)core::sync::atomic::AtomicUsize::load (9 samples, 0.02%)core::sync::atomic::atomic_load (9 samples, 0.02%)core::option::Option<T>::map (82 samples, 0.21%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (82 samples, 0.21%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (59 samples, 0.15%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (59 samples, 0.15%)core::sync::atomic::AtomicUsize::fetch_sub (54 samples, 0.14%)core::sync::atomic::atomic_sub (54 samples, 0.14%)[libc.so.6] (33 samples, 0.08%)__entry_text_start (6 samples, 0.02%)futex_unqueue (8 samples, 0.02%)update_curr (13 samples, 0.03%)cpuacct_charge (4 samples, 0.01%)dequeue_task (24 samples, 0.06%)dequeue_task_fair (24 samples, 0.06%)dequeue_entity (24 samples, 0.06%)finish_task_switch.isra.0 (12 samples, 0.03%)raw_spin_rq_unlock (7 samples, 0.02%)prepare_task_switch (14 samples, 0.04%)__perf_event_task_sched_out (4 samples, 0.01%)psi_group_change (16 samples, 0.04%)psi_task_switch (31 samples, 0.08%)sched_clock_cpu (6 samples, 0.02%)__schedule (97 samples, 0.25%)futex_wait_queue (109 samples, 0.28%)schedule (102 samples, 0.26%)__get_user_nocheck_4 (11 samples, 0.03%)futex_q_lock (4 samples, 0.01%)futex_q_unlock (4 samples, 0.01%)futex_wait_setup (28 samples, 0.07%)__x64_sys_futex (156 samples, 0.40%)do_futex (154 samples, 0.39%)futex_wait (153 samples, 0.39%)__rseq_handle_notify_resume (10 samples, 0.03%)exit_to_user_mode_loop (17 samples, 0.04%)do_syscall_64 (183 samples, 0.47%)syscall_exit_to_user_mode (22 samples, 0.06%)exit_to_user_mode_prepare (22 samples, 0.06%)__lll_lock_wait_private (227 samples, 0.58%)entry_SYSCALL_64_after_hwframe (185 samples, 0.47%)[libc.so.6] (583 samples, 1.49%)__entry_text_start (7 samples, 0.02%)__x64_sys_futex (6 samples, 0.02%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)raw_irqentry_exit_cond_resched (5 samples, 0.01%)preempt_schedule_irq (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)_raw_spin_lock (8 samples, 0.02%)futex_hash (4 samples, 0.01%)_raw_spin_lock (18 samples, 0.05%)native_queued_spin_lock_slowpath (18 samples, 0.05%)get_futex_key (4 samples, 0.01%)futex_wake (53 samples, 0.14%)__x64_sys_futex (71 samples, 0.18%)do_futex (68 samples, 0.17%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)do_syscall_64 (86 samples, 0.22%)syscall_exit_to_user_mode (14 samples, 0.04%)exit_to_user_mode_prepare (13 samples, 0.03%)entry_SYSCALL_64_after_hwframe (94 samples, 0.24%)alloc::alloc::dealloc (730 samples, 1.87%)a..cfree (727 samples, 1.86%)c..__lll_lock_wake_private (106 samples, 0.27%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (732 samples, 1.87%)<..core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (738 samples, 1.89%)c..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (738 samples, 1.89%)<..arrayvec::arrayvec::ArrayVec<T,_>::clear (738 samples, 1.89%)a..arrayvec::arrayvec_impl::ArrayVecImpl::clear (738 samples, 1.89%)a..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (738 samples, 1.89%)a..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (736 samples, 1.88%)c..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (736 samples, 1.88%)c..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (736 samples, 1.88%)c..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (736 samples, 1.88%)<..alloc::raw_vec::RawVec<T,A>::current_memory (4 samples, 0.01%)std::sync::poison::Flag::done (9 samples, 0.02%)std::thread::panicking (9 samples, 0.02%)std::panicking::panicking (9 samples, 0.02%)std::panicking::panic_count::count_is_zero (9 samples, 0.02%)core::sync::atomic::AtomicUsize::load (9 samples, 0.02%)core::sync::atomic::atomic_load (9 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (76 samples, 0.19%)std::sys::unix::locks::futex_mutex::Mutex::unlock (67 samples, 0.17%)core::sync::atomic::AtomicU32::swap (65 samples, 0.17%)core::sync::atomic::atomic_swap (65 samples, 0.17%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (77 samples, 0.20%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (77 samples, 0.20%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (77 samples, 0.20%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (77 samples, 0.20%)core::sync::atomic::AtomicU32::fetch_sub (70 samples, 0.18%)core::sync::atomic::atomic_sub (70 samples, 0.18%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (51 samples, 0.13%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (51 samples, 0.13%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (51 samples, 0.13%)core::sync::atomic::AtomicU32::fetch_sub (48 samples, 0.12%)core::sync::atomic::atomic_sub (48 samples, 0.12%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (7 samples, 0.02%)core::num::<impl u64>::wrapping_add (6 samples, 0.02%)core::num::<impl u64>::rotate_left (14 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (34 samples, 0.09%)core::num::<impl u64>::wrapping_add (13 samples, 0.03%)hashbrown::map::make_hash (70 samples, 0.18%)core::hash::BuildHasher::hash_one (70 samples, 0.18%)core::hash::impls::<impl core::hash::Hash for &T>::hash (14 samples, 0.04%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (14 samples, 0.04%)core::hash::impls::<impl core::hash::Hash for u32>::hash (14 samples, 0.04%)core::hash::Hasher::write_u32 (14 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (14 samples, 0.04%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (14 samples, 0.04%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (14 samples, 0.04%)core::hash::sip::u8to64_le (11 samples, 0.03%)<hashbrown::raw::bitmask::BitMaskIter as core::iter::traits::iterator::Iterator>::next (4 samples, 0.01%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (8 samples, 0.02%)hashbrown::map::equivalent_key::_{{closure}} (8 samples, 0.02%)<core::num::nonzero::NonZeroU32 as core::cmp::PartialEq>::eq (8 samples, 0.02%)hashbrown::raw::h2 (9 samples, 0.02%)hashbrown::map::HashMap<K,V,S,A>::get_inner (109 samples, 0.28%)hashbrown::raw::RawTable<T,A>::get (39 samples, 0.10%)hashbrown::raw::RawTable<T,A>::find (39 samples, 0.10%)hashbrown::raw::RawTableInner<A>::find_inner (39 samples, 0.10%)hashbrown::raw::sse2::Group::load (13 samples, 0.03%)core::core_arch::x86::sse2::_mm_loadu_si128 (13 samples, 0.03%)core::intrinsics::copy_nonoverlapping (13 samples, 0.03%)std::collections::hash::map::HashMap<K,V,S>::get (113 samples, 0.29%)hashbrown::map::HashMap<K,V,S,A>::get (113 samples, 0.29%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (6 samples, 0.02%)std::sync::mutex::MutexGuard<T>::new (14 samples, 0.04%)std::sync::poison::Flag::guard (8 samples, 0.02%)std::sync::mutex::Mutex<T>::lock (47 samples, 0.12%)std::sys::unix::locks::futex_mutex::Mutex::lock (33 samples, 0.08%)core::sync::atomic::AtomicU32::compare_exchange (32 samples, 0.08%)core::sync::atomic::atomic_compare_exchange (32 samples, 0.08%)core::sync::atomic::AtomicU32::compare_exchange_weak (117 samples, 0.30%)core::sync::atomic::atomic_compare_exchange_weak (117 samples, 0.30%)std::sync::rwlock::RwLock<T>::read (140 samples, 0.36%)std::sys::unix::locks::futex_rwlock::RwLock::read (140 samples, 0.36%)std::sys::unix::locks::futex_rwlock::is_read_lockable (6 samples, 0.02%)zssp::antireplay::Window<_,_>::check (7 samples, 0.02%)core::sync::atomic::AtomicU64::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (16 samples, 0.04%)arrayvec::arrayvec_impl::ArrayVecImpl::push (16 samples, 0.04%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (16 samples, 0.04%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (14 samples, 0.04%)core::ptr::write (12 samples, 0.03%)core::mem::maybe_uninit::MaybeUninit<T>::write (4 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (51 samples, 0.13%)<T as core::convert::TryInto<U>>::try_into (28 samples, 0.07%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (28 samples, 0.07%)core::result::Result<T,E>::map (28 samples, 0.07%)zssp::zeta::from_nonce (33 samples, 0.08%)<core::slice::iter::IterMut<T> as core::iter::traits::iterator::Iterator>::next (6 samples, 0.02%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (76 samples, 0.19%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (76 samples, 0.19%)core::sync::atomic::AtomicU32::fetch_sub (75 samples, 0.19%)core::sync::atomic::atomic_sub (75 samples, 0.19%)CRYPTO_gcm128_decrypt (231 samples, 0.59%)[libcrypto.so.3] (86 samples, 0.22%)CRYPTO_gcm128_decrypt_ctr32 (440 samples, 1.13%)[libcrypto.so.3] (352 samples, 0.90%)[libcrypto.so.3] (27 samples, 0.07%)CRYPTO_gcm128_setiv (23 samples, 0.06%)[libcrypto.so.3] (20 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,270 samples, 10.93%)<zssp::crypto_im..zssp::crypto_impl::openssl::CipherCtx::update (4,270 samples, 10.93%)zssp::crypto_imp..EVP_DecryptUpdate (4,266 samples, 10.92%)EVP_DecryptUpdate[libcrypto.so.3] (4,171 samples, 10.67%)[libcrypto.so.3][libcrypto.so.3] (4,167 samples, 10.66%)[libcrypto.so.3][libcrypto.so.3] (4,144 samples, 10.60%)[libcrypto.so.3][libcrypto.so.3] (3,418 samples, 8.75%)[libcrypto.s..[libcrypto.so.3] (3,257 samples, 8.33%)[libcrypto.s..asm_sysvec_reschedule_ipi (17 samples, 0.04%)sysvec_reschedule_ipi (16 samples, 0.04%)irqentry_exit (16 samples, 0.04%)irqentry_exit_to_user_mode (16 samples, 0.04%)exit_to_user_mode_prepare (16 samples, 0.04%)exit_to_user_mode_loop (16 samples, 0.04%)schedule (15 samples, 0.04%)__schedule (15 samples, 0.04%)finish_task_switch.isra.0 (15 samples, 0.04%)__perf_event_task_sched_in (15 samples, 0.04%)perf_ctx_enable (15 samples, 0.04%)CRYPTO_clear_free (92 samples, 0.24%)OPENSSL_cleanse (92 samples, 0.24%)EVP_CIPHER_free (27 samples, 0.07%)cfree (42 samples, 0.11%)[libc.so.6] (7 samples, 0.02%)EVP_CIPHER_CTX_free (184 samples, 0.47%)EVP_CIPHER_CTX_reset (180 samples, 0.46%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (210 samples, 0.54%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (210 samples, 0.54%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (210 samples, 0.54%)cfree (25 samples, 0.06%)[libc.so.6] (15 samples, 0.04%)CRYPTO_gcm128_finish (29 samples, 0.07%)[libcrypto.so.3] (18 samples, 0.05%)[libcrypto.so.3] (33 samples, 0.08%)zssp::crypto_impl::openssl::CipherCtx::finalize (46 samples, 0.12%)EVP_DecryptFinal_ex (45 samples, 0.12%)[libcrypto.so.3] (38 samples, 0.10%)[libcrypto.so.3] (37 samples, 0.09%)OSSL_PARAM_get_octet_string (6 samples, 0.02%)[libc.so.6] (17 samples, 0.04%)OSSL_PARAM_locate (26 samples, 0.07%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (333 samples, 0.85%)zssp::crypto_impl::openssl::CipherCtx::set_tag (77 samples, 0.20%)EVP_CIPHER_CTX_ctrl (77 samples, 0.20%)[libcrypto.so.3] (39 samples, 0.10%)OSSL_PARAM_locate_const (4 samples, 0.01%)[libc.so.6] (15 samples, 0.04%)OSSL_PARAM_locate (28 samples, 0.07%)EVP_CIPHER_CTX_set_padding (64 samples, 0.16%)[libcrypto.so.3] (42 samples, 0.11%)[libc.so.6] (25 samples, 0.06%)OSSL_PARAM_locate (48 samples, 0.12%)strcmp@plt (7 samples, 0.02%)EVP_CIPHER_CTX_get_iv_length (81 samples, 0.21%)[libcrypto.so.3] (63 samples, 0.16%)[libc.so.6] (20 samples, 0.05%)OSSL_PARAM_locate (41 samples, 0.10%)EVP_CIPHER_CTX_get_key_length (60 samples, 0.15%)[libcrypto.so.3] (53 samples, 0.14%)pthread_rwlock_rdlock (159 samples, 0.41%)CRYPTO_THREAD_read_lock (163 samples, 0.42%)pthread_rwlock_unlock (79 samples, 0.20%)CRYPTO_THREAD_unlock (86 samples, 0.22%)pthread_rwlock_unlock@plt (5 samples, 0.01%)EVP_CIPHER_up_ref (42 samples, 0.11%)OPENSSL_LH_retrieve (70 samples, 0.18%)[libcrypto.so.3] (57 samples, 0.15%)[libcrypto.so.3] (6 samples, 0.02%)pthread_rwlock_rdlock (43 samples, 0.11%)CRYPTO_THREAD_read_lock (47 samples, 0.12%)pthread_rwlock_unlock (66 samples, 0.17%)CRYPTO_THREAD_unlock (69 samples, 0.18%)CRYPTO_strndup (13 samples, 0.03%)OPENSSL_strcasecmp (38 samples, 0.10%)OPENSSL_LH_retrieve (162 samples, 0.41%)[libcrypto.so.3] (151 samples, 0.39%)[libcrypto.so.3] (36 samples, 0.09%)cfree (12 samples, 0.03%)[libc.so.6] (4 samples, 0.01%)[libcrypto.so.3] (310 samples, 0.79%)pthread_getspecific (7 samples, 0.02%)EVP_CIPHER_fetch (702 samples, 1.80%)E..[libcrypto.so.3] (699 samples, 1.79%)[..[libcrypto.so.3] (690 samples, 1.77%)EVP_CIPHER_free (18 samples, 0.05%)EVP_CIPHER_up_ref (27 samples, 0.07%)[libc.so.6] (10 samples, 0.03%)malloc (9 samples, 0.02%)CRYPTO_zalloc (22 samples, 0.06%)OPENSSL_init_crypto (4 samples, 0.01%)CRYPTO_gcm128_init (155 samples, 0.40%)[libcrypto.so.3] (126 samples, 0.32%)[libcrypto.so.3] (188 samples, 0.48%)[libcrypto.so.3] (32 samples, 0.08%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,141 samples, 2.92%)zs..EVP_CipherInit_ex (1,141 samples, 2.92%)EV..[libcrypto.so.3] (1,141 samples, 2.92%)[l..[libcrypto.so.3] (224 samples, 0.57%)malloc (12 samples, 0.03%)CRYPTO_zalloc (17 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (1,226 samples, 3.14%)<zs..zssp::crypto_impl::openssl::CipherCtx::new (20 samples, 0.05%)core::slice::index::<impl core::ops::index::Index<I> for [T]>::index (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (5 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (5 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::add (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::offset (4 samples, 0.01%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (4 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (5 samples, 0.01%)std::io::impls::<impl std::io::Write for &mut W>::write (232 samples, 0.59%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (232 samples, 0.59%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (230 samples, 0.59%)core::intrinsics::copy_nonoverlapping (228 samples, 0.58%)[libc.so.6] (227 samples, 0.58%)zssp::antireplay::Window<_,_>::update (27 samples, 0.07%)core::sync::atomic::AtomicU64::fetch_max (26 samples, 0.07%)core::sync::atomic::atomic_umax (26 samples, 0.07%)zssp::zeta::receive_payload_in_place (6,190 samples, 15.84%)zssp::zeta::receive_payl..zssp::zssp::Context<Crypto>::receive (8,570 samples, 21.93%)zssp::zssp::Context<Crypto>::receivezssp::zssp::parse_fragment_header (65 samples, 0.17%)core::slice::<impl [T]>::copy_from_slice (4 samples, 0.01%)core::intrinsics::copy_nonoverlapping (4 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (56 samples, 0.14%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (56 samples, 0.14%)std::sys::unix::locks::futex_mutex::Mutex::unlock (55 samples, 0.14%)core::sync::atomic::AtomicU32::swap (52 samples, 0.13%)core::sync::atomic::atomic_swap (52 samples, 0.13%)std::sync::mutex::Mutex<T>::lock (64 samples, 0.16%)std::sys::unix::locks::futex_mutex::Mutex::lock (61 samples, 0.16%)core::sync::atomic::AtomicU32::compare_exchange (55 samples, 0.14%)core::sync::atomic::atomic_compare_exchange (55 samples, 0.14%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (314 samples, 0.80%)zssp::crypto_impl::openssl::CipherCtx::update (188 samples, 0.48%)EVP_EncryptUpdate (183 samples, 0.47%)[libcrypto.so.3] (148 samples, 0.38%)[libcrypto.so.3] (106 samples, 0.27%)[libcrypto.so.3] (96 samples, 0.25%)CRYPTO_gcm128_encrypt (250 samples, 0.64%)[libcrypto.so.3] (143 samples, 0.37%)CRYPTO_gcm128_encrypt_ctr32 (415 samples, 1.06%)[libcrypto.so.3] (319 samples, 0.82%)[libcrypto.so.3] (28 samples, 0.07%)[libcrypto.so.3] (19 samples, 0.05%)CRYPTO_gcm128_setiv (25 samples, 0.06%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)perf_ctx_enable (18 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,317 samples, 8.49%)<zssp::crypt..zssp::crypto_impl::openssl::CipherCtx::update (3,311 samples, 8.47%)zssp::crypto..EVP_EncryptUpdate (3,309 samples, 8.47%)EVP_EncryptU..[libcrypto.so.3] (3,257 samples, 8.33%)[libcrypto.s..[libcrypto.so.3] (3,255 samples, 8.33%)[libcrypto.s..[libcrypto.so.3] (3,235 samples, 8.28%)[libcrypto.s..[libcrypto.so.3] (2,506 samples, 6.41%)[libcryp..[libcrypto.so.3] (2,299 samples, 5.88%)[libcry..asm_sysvec_reschedule_ipi (20 samples, 0.05%)sysvec_reschedule_ipi (20 samples, 0.05%)irqentry_exit (20 samples, 0.05%)irqentry_exit_to_user_mode (20 samples, 0.05%)exit_to_user_mode_prepare (20 samples, 0.05%)exit_to_user_mode_loop (20 samples, 0.05%)schedule (19 samples, 0.05%)__schedule (19 samples, 0.05%)finish_task_switch.isra.0 (19 samples, 0.05%)__perf_event_task_sched_in (19 samples, 0.05%)CRYPTO_clear_free (107 samples, 0.27%)OPENSSL_cleanse (103 samples, 0.26%)EVP_CIPHER_free (30 samples, 0.08%)EVP_CIPHER_CTX_free (206 samples, 0.53%)EVP_CIPHER_CTX_reset (205 samples, 0.52%)cfree (52 samples, 0.13%)[libc.so.6] (11 samples, 0.03%)cfree (11 samples, 0.03%)[libc.so.6] (5 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (220 samples, 0.56%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (220 samples, 0.56%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (220 samples, 0.56%)[libcrypto.so.3] (15 samples, 0.04%)zssp::crypto_impl::openssl::CipherCtx::finalize (47 samples, 0.12%)EVP_EncryptFinal_ex (46 samples, 0.12%)[libcrypto.so.3] (33 samples, 0.08%)[libcrypto.so.3] (33 samples, 0.08%)[libcrypto.so.3] (29 samples, 0.07%)CRYPTO_gcm128_tag (29 samples, 0.07%)CRYPTO_gcm128_finish (22 samples, 0.06%)[libc.so.6] (32 samples, 0.08%)OSSL_PARAM_locate (53 samples, 0.14%)strcmp@plt (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (358 samples, 0.92%)zssp::crypto_impl::openssl::CipherCtx::get_tag (91 samples, 0.23%)EVP_CIPHER_CTX_ctrl (91 samples, 0.23%)[libcrypto.so.3] (68 samples, 0.17%)OSSL_PARAM_set_octet_string (5 samples, 0.01%)[libc.so.6] (12 samples, 0.03%)EVP_CIPHER_CTX_set_padding (64 samples, 0.16%)[libcrypto.so.3] (38 samples, 0.10%)OSSL_PARAM_locate (32 samples, 0.08%)strcmp@plt (4 samples, 0.01%)[libc.so.6] (17 samples, 0.04%)OSSL_PARAM_locate (40 samples, 0.10%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_get_iv_length (79 samples, 0.20%)[libcrypto.so.3] (55 samples, 0.14%)[libc.so.6] (20 samples, 0.05%)OSSL_PARAM_locate (37 samples, 0.09%)EVP_CIPHER_CTX_get_key_length (63 samples, 0.16%)[libcrypto.so.3] (54 samples, 0.14%)pthread_rwlock_rdlock (130 samples, 0.33%)CRYPTO_THREAD_read_lock (136 samples, 0.35%)pthread_rwlock_unlock (73 samples, 0.19%)CRYPTO_THREAD_unlock (82 samples, 0.21%)pthread_rwlock_unlock@plt (5 samples, 0.01%)EVP_CIPHER_up_ref (24 samples, 0.06%)OPENSSL_LH_retrieve (75 samples, 0.19%)[libcrypto.so.3] (56 samples, 0.14%)[libcrypto.so.3] (5 samples, 0.01%)pthread_rwlock_rdlock (38 samples, 0.10%)CRYPTO_THREAD_read_lock (40 samples, 0.10%)pthread_rwlock_unlock (43 samples, 0.11%)CRYPTO_THREAD_unlock (45 samples, 0.12%)OPENSSL_strnlen (5 samples, 0.01%)CRYPTO_strndup (10 samples, 0.03%)malloc (5 samples, 0.01%)OPENSSL_strcasecmp (41 samples, 0.10%)OPENSSL_LH_retrieve (132 samples, 0.34%)[libcrypto.so.3] (118 samples, 0.30%)[libcrypto.so.3] (29 samples, 0.07%)cfree (19 samples, 0.05%)[libc.so.6] (7 samples, 0.02%)[libcrypto.so.3] (264 samples, 0.68%)EVP_CIPHER_fetch (603 samples, 1.54%)[libcrypto.so.3] (600 samples, 1.54%)[libcrypto.so.3] (594 samples, 1.52%)EVP_CIPHER_free (17 samples, 0.04%)EVP_CIPHER_up_ref (20 samples, 0.05%)OBJ_nid2sn (6 samples, 0.02%)malloc (8 samples, 0.02%)CRYPTO_zalloc (14 samples, 0.04%)OPENSSL_init_crypto (5 samples, 0.01%)CRYPTO_gcm128_init (161 samples, 0.41%)[libcrypto.so.3] (132 samples, 0.34%)[libcrypto.so.3] (201 samples, 0.51%)[libcrypto.so.3] (39 samples, 0.10%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,042 samples, 2.67%)zs..EVP_CipherInit_ex (1,042 samples, 2.67%)EV..[libcrypto.so.3] (1,042 samples, 2.67%)[l..[libcrypto.so.3] (232 samples, 0.59%)[libc.so.6] (4 samples, 0.01%)malloc (9 samples, 0.02%)CRYPTO_zalloc (15 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (1,125 samples, 2.88%)<z..zssp::crypto_impl::openssl::CipherCtx::new (16 samples, 0.04%)__rdl_alloc (9 samples, 0.02%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (9 samples, 0.02%)[libc.so.6] (54 samples, 0.14%)[libc.so.6] (925 samples, 2.37%)[l..__entry_text_start (4 samples, 0.01%)__get_user_nocheck_4 (5 samples, 0.01%)futex_q_lock (5 samples, 0.01%)futex_q_unlock (6 samples, 0.02%)futex_wait (24 samples, 0.06%)futex_wait_setup (19 samples, 0.05%)__x64_sys_futex (26 samples, 0.07%)do_futex (26 samples, 0.07%)do_syscall_64 (31 samples, 0.08%)syscall_exit_to_user_mode (4 samples, 0.01%)__lll_lock_wait_private (53 samples, 0.14%)entry_SYSCALL_64_after_hwframe (33 samples, 0.08%)__entry_text_start (4 samples, 0.01%)_raw_spin_lock (4 samples, 0.01%)futex_hash (4 samples, 0.01%)_raw_spin_lock (4 samples, 0.01%)native_queued_spin_lock_slowpath (4 samples, 0.01%)futex_wake_mark (13 samples, 0.03%)preempt_schedule_thunk (5 samples, 0.01%)preempt_schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)__smp_call_single_queue (12 samples, 0.03%)native_send_call_func_single_ipi (9 samples, 0.02%)x2apic_send_IPI (9 samples, 0.02%)native_write_msr (6 samples, 0.02%)llist_add_batch (17 samples, 0.04%)do_futex (136 samples, 0.35%)futex_wake (128 samples, 0.33%)wake_up_q (75 samples, 0.19%)try_to_wake_up (66 samples, 0.17%)ttwu_queue_wakelist (36 samples, 0.09%)__x64_sys_futex (137 samples, 0.35%)do_syscall_64 (141 samples, 0.36%)entry_SYSCALL_64_after_hwframe (144 samples, 0.37%)alloc::vec::Vec<T,A>::with_capacity_in (1,803 samples, 4.61%)alloc..alloc::raw_vec::RawVec<T,A>::with_capacity_in (1,803 samples, 4.61%)alloc..alloc::raw_vec::RawVec<T,A>::allocate_in (1,803 samples, 4.61%)alloc..<alloc::alloc::Global as core::alloc::Allocator>::allocate (1,801 samples, 4.61%)<allo..alloc::alloc::Global::alloc_impl (1,801 samples, 4.61%)alloc..alloc::alloc::alloc (1,801 samples, 4.61%)alloc..malloc (1,789 samples, 4.58%)malloc__lll_lock_wake_private (156 samples, 0.40%)alloc::slice::<impl [T]>::to_vec (2,000 samples, 5.12%)alloc:..alloc::slice::<impl [T]>::to_vec_in (2,000 samples, 5.12%)alloc:..alloc::slice::hack::to_vec (2,000 samples, 5.12%)alloc:..<T as alloc::slice::hack::ConvertVec>::to_vec (2,000 samples, 5.12%)<T as ..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (197 samples, 0.50%)core::intrinsics::copy_nonoverlapping (197 samples, 0.50%)[libc.so.6] (194 samples, 0.50%)core::result::Result<T,E>::is_ok (4 samples, 0.01%)<std::sync::mpmc::select::Token as core::default::Default>::default (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (4 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (66 samples, 0.17%)core::sync::atomic::atomic_compare_exchange_weak (66 samples, 0.17%)std::sync::mpmc::array::Channel<T>::start_send (102 samples, 0.26%)core::sync::atomic::AtomicUsize::load (11 samples, 0.03%)core::sync::atomic::atomic_load (11 samples, 0.03%)core::sync::atomic::AtomicBool::load (6 samples, 0.02%)core::sync::atomic::atomic_load (5 samples, 0.01%)futex_wake_mark (8 samples, 0.02%)__smp_call_single_queue (7 samples, 0.02%)native_send_call_func_single_ipi (6 samples, 0.02%)x2apic_send_IPI (6 samples, 0.02%)native_write_msr (4 samples, 0.01%)llist_add_batch (7 samples, 0.02%)futex_wake (71 samples, 0.18%)wake_up_q (44 samples, 0.11%)try_to_wake_up (43 samples, 0.11%)ttwu_queue_wakelist (24 samples, 0.06%)__x64_sys_futex (75 samples, 0.19%)do_futex (75 samples, 0.19%)entry_SYSCALL_64_after_hwframe (76 samples, 0.19%)do_syscall_64 (76 samples, 0.19%)<core::slice::iter::Iter<T> as core::iter::traits::iterator::Iterator>::position (88 samples, 0.23%)std::sync::mpmc::waker::Waker::try_select::_{{closure}} (88 samples, 0.23%)std::sync::mpmc::context::Context::unpark (84 samples, 0.21%)std::thread::Thread::unpark (84 samples, 0.21%)std::sys_common::thread_parking::futex::Parker::unpark (84 samples, 0.21%)std::sys::unix::futex::futex_wake (81 samples, 0.21%)syscall (80 samples, 0.20%)std::sync::mpmc::waker::Waker::try_select (89 samples, 0.23%)std::sync::mpmc::array::Channel<T>::write (112 samples, 0.29%)std::sync::mpmc::waker::SyncWaker::notify (105 samples, 0.27%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)futex_wait_queue (6 samples, 0.02%)schedule (6 samples, 0.02%)__schedule (6 samples, 0.02%)std::sync::mpmc::context::Context::wait_until (7 samples, 0.02%)std::thread::park (7 samples, 0.02%)std::sys_common::thread_parking::futex::Parker::park (7 samples, 0.02%)std::sys::unix::futex::futex_wait (7 samples, 0.02%)syscall (7 samples, 0.02%)entry_SYSCALL_64_after_hwframe (7 samples, 0.02%)do_syscall_64 (7 samples, 0.02%)__x64_sys_futex (7 samples, 0.02%)do_futex (7 samples, 0.02%)futex_wait (7 samples, 0.02%)std::sync::mpmc::context::Context::with (8 samples, 0.02%)std::thread::local::LocalKey<T>::try_with (8 samples, 0.02%)std::sync::mpmc::context::Context::with::_{{closure}} (8 samples, 0.02%)std::sync::mpmc::context::Context::with::_{{closure}} (8 samples, 0.02%)std::sync::mpmc::array::Channel<T>::send::_{{closure}} (8 samples, 0.02%)core::hint::spin_loop (14 samples, 0.04%)core::core_arch::x86::sse2::_mm_pause (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::send (251 samples, 0.64%)std::sync::mpmc::utils::Backoff::spin_light (15 samples, 0.04%)benchmark::bob_main::_{{closure}} (3,468 samples, 8.87%)benchmark::bo..std::sync::mpsc::SyncSender<T>::send (1,464 samples, 3.75%)std:..std::sync::mpmc::Sender<T>::send (1,462 samples, 3.74%)std:..core::iter::range::<impl core::iter::traits::iterator::Iterator for core::ops::range::Range<A>>::next (6 samples, 0.02%)<core::ops::range::Range<T> as core::iter::range::RangeIteratorImpl>::spec_next (6 samples, 0.02%)core::mem::drop (25 samples, 0.06%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (25 samples, 0.06%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (25 samples, 0.06%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (25 samples, 0.06%)core::sync::atomic::AtomicU32::fetch_sub (24 samples, 0.06%)core::sync::atomic::atomic_sub (24 samples, 0.06%)core::slice::<impl [T]>::copy_from_slice (14 samples, 0.04%)core::intrinsics::copy_nonoverlapping (14 samples, 0.04%)core::sync::atomic::AtomicU32::compare_exchange_weak (20 samples, 0.05%)core::sync::atomic::atomic_compare_exchange_weak (20 samples, 0.05%)std::sync::rwlock::RwLock<T>::read (22 samples, 0.06%)std::sys::unix::locks::futex_rwlock::RwLock::read (22 samples, 0.06%)benchmark::alice_main (18,175 samples, 46.50%)benchmark::alice_mainzssp::zssp::Context<Crypto>::send (8,691 samples, 22.24%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,691 samples, 22.24%)zssp::zeta::send_payloadzssp::zeta::get_counter (14 samples, 0.04%)core::sync::atomic::AtomicU64::fetch_add (11 samples, 0.03%)core::sync::atomic::atomic_add (11 samples, 0.03%)[libc.so.6] (15 samples, 0.04%)arrayvec::arrayvec::ArrayVec<T,_>::clear (4 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (4 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (4 samples, 0.01%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (10 samples, 0.03%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (10 samples, 0.03%)core::sync::atomic::AtomicUsize::fetch_sub (10 samples, 0.03%)core::sync::atomic::atomic_sub (10 samples, 0.03%)core::time::Duration::as_millis (5 samples, 0.01%)_ZN3std4sync4mpmc5array16Channel$LT$T$GT$10start_recv17h7800ca29c64cb868E.llvm.12455019271255371362 (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::read (6 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (65 samples, 0.17%)core::sync::atomic::atomic_compare_exchange_weak (65 samples, 0.17%)core::sync::atomic::AtomicUsize::load (84 samples, 0.21%)core::sync::atomic::atomic_load (84 samples, 0.21%)std::sync::mpmc::array::Channel<T>::start_recv (190 samples, 0.49%)core::sync::atomic::fence (4 samples, 0.01%)core::sync::atomic::AtomicU32::swap (5 samples, 0.01%)core::sync::atomic::atomic_swap (5 samples, 0.01%)[[vdso]] (4 samples, 0.01%)core::option::Option<T>::and_then (5 samples, 0.01%)std::sys::unix::futex::futex_wait::_{{closure}} (5 samples, 0.01%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (5 samples, 0.01%)clock_gettime (5 samples, 0.01%)futex_setup_timer (5 samples, 0.01%)hrtimer_init_sleeper (4 samples, 0.01%)__hrtimer_init (4 samples, 0.01%)futex_unqueue (4 samples, 0.01%)hrtimer_sleeper_start_expires (6 samples, 0.02%)hrtimer_start_range_ns (6 samples, 0.02%)update_curr (5 samples, 0.01%)dequeue_entity (14 samples, 0.04%)update_load_avg (5 samples, 0.01%)dequeue_task (18 samples, 0.05%)dequeue_task_fair (18 samples, 0.05%)finish_task_switch.isra.0 (14 samples, 0.04%)raw_spin_rq_unlock (6 samples, 0.02%)pick_next_task (5 samples, 0.01%)prepare_task_switch (6 samples, 0.02%)__perf_event_task_sched_out (4 samples, 0.01%)psi_group_change (12 samples, 0.03%)record_times (5 samples, 0.01%)psi_task_switch (22 samples, 0.06%)__schedule (72 samples, 0.18%)futex_wait_queue (90 samples, 0.23%)schedule (81 samples, 0.21%)__get_user_nocheck_4 (6 samples, 0.02%)futex_q_lock (7 samples, 0.02%)futex_wait_setup (19 samples, 0.05%)hrtimer_cancel (5 samples, 0.01%)hrtimer_try_to_cancel (5 samples, 0.01%)do_futex (131 samples, 0.34%)futex_wait (130 samples, 0.33%)__x64_sys_futex (143 samples, 0.37%)get_timespec64 (5 samples, 0.01%)exit_to_user_mode_loop (11 samples, 0.03%)__rseq_handle_notify_resume (4 samples, 0.01%)std::sync::mpmc::context::Context::wait_until (178 samples, 0.46%)std::thread::park_timeout (177 samples, 0.45%)std::sys_common::thread_parking::futex::Parker::park_timeout (175 samples, 0.45%)std::sys::unix::futex::futex_wait (170 samples, 0.43%)syscall (164 samples, 0.42%)entry_SYSCALL_64_after_hwframe (159 samples, 0.41%)do_syscall_64 (159 samples, 0.41%)syscall_exit_to_user_mode (15 samples, 0.04%)exit_to_user_mode_prepare (15 samples, 0.04%)core::sync::atomic::AtomicBool::store (8 samples, 0.02%)core::sync::atomic::atomic_store (8 samples, 0.02%)std::sync::mpmc::waker::Waker::register (5 samples, 0.01%)std::sync::mpmc::waker::Waker::register_with_packet (5 samples, 0.01%)alloc::vec::Vec<T,A>::push (5 samples, 0.01%)core::ptr::write (5 samples, 0.01%)std::sync::mpmc::waker::SyncWaker::register (24 samples, 0.06%)std::sync::mutex::Mutex<T>::lock (8 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::lock (8 samples, 0.02%)core::sync::atomic::AtomicU32::compare_exchange (8 samples, 0.02%)core::sync::atomic::atomic_compare_exchange (8 samples, 0.02%)std::sync::mpmc::context::Context::with (207 samples, 0.53%)std::thread::local::LocalKey<T>::try_with (207 samples, 0.53%)std::sync::mpmc::context::Context::with::_{{closure}} (207 samples, 0.53%)std::sync::mpmc::context::Context::with::_{{closure}} (207 samples, 0.53%)std::sync::mpmc::array::Channel<T>::recv::_{{closure}} (207 samples, 0.53%)std::sync::mpmc::Receiver<T>::recv_deadline (431 samples, 1.10%)std::sync::mpmc::array::Channel<T>::recv (431 samples, 1.10%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (5 samples, 0.01%)clock_gettime (5 samples, 0.01%)[[vdso]] (5 samples, 0.01%)std::sync::mpmc::Receiver<T>::recv_timeout (4 samples, 0.01%)[[vdso]] (131 samples, 0.34%)[[vdso]] (88 samples, 0.23%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (146 samples, 0.37%)clock_gettime (140 samples, 0.36%)__vdso_clock_gettime (6 samples, 0.02%)std::sync::mpsc::Receiver<T>::recv_timeout (605 samples, 1.55%)std::sync::mpmc::Receiver<T>::recv_timeout (602 samples, 1.54%)std::time::SystemTime::checked_add (12 samples, 0.03%)std::sys::unix::time::SystemTime::checked_add_duration (12 samples, 0.03%)std::sys::unix::time::Timespec::checked_add_duration (12 samples, 0.03%)core::option::Option<T>::and_then (4 samples, 0.01%)core::cmp::impls::<impl core::cmp::PartialOrd<&B> for &A>::ge (6 samples, 0.02%)core::cmp::PartialOrd::ge (6 samples, 0.02%)<std::sys::unix::time::Timespec as core::cmp::PartialOrd>::partial_cmp (5 samples, 0.01%)std::time::Instant::duration_since (22 samples, 0.06%)std::time::Instant::checked_duration_since (21 samples, 0.05%)std::sys::unix::time::inner::Instant::checked_sub_instant (21 samples, 0.05%)std::sys::unix::time::Timespec::sub_timespec (21 samples, 0.05%)<std::time::Instant as core::ops::arith::Sub>::sub (27 samples, 0.07%)std::time::Instant::elapsed (5 samples, 0.01%)[[vdso]] (148 samples, 0.38%)[[vdso]] (107 samples, 0.27%)std::time::Instant::elapsed (192 samples, 0.49%)std::time::Instant::now (159 samples, 0.41%)std::sys::unix::time::inner::Instant::now (159 samples, 0.41%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (155 samples, 0.40%)clock_gettime (154 samples, 0.39%)__vdso_clock_gettime (4 samples, 0.01%)<T as core::convert::TryInto<U>>::try_into (306 samples, 0.78%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (306 samples, 0.78%)core::result::Result<T,E>::map (306 samples, 0.78%)<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (13 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (61 samples, 0.16%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (61 samples, 0.16%)std::sys::unix::locks::futex_mutex::Mutex::unlock (60 samples, 0.15%)core::sync::atomic::AtomicU32::swap (56 samples, 0.14%)core::sync::atomic::atomic_swap (56 samples, 0.14%)std::sync::mutex::MutexGuard<T>::new (6 samples, 0.02%)std::sync::poison::Flag::guard (6 samples, 0.02%)std::thread::panicking (6 samples, 0.02%)std::panicking::panicking (6 samples, 0.02%)std::panicking::panic_count::count_is_zero (6 samples, 0.02%)std::sync::mutex::Mutex<T>::lock (77 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (71 samples, 0.18%)core::sync::atomic::AtomicU32::compare_exchange (69 samples, 0.18%)core::sync::atomic::atomic_compare_exchange (69 samples, 0.18%)EVP_CIPHER_CTX_get_block_size (5 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (344 samples, 0.88%)zssp::crypto_impl::openssl::CipherCtx::update (200 samples, 0.51%)EVP_DecryptUpdate (196 samples, 0.50%)[libcrypto.so.3] (166 samples, 0.42%)[libcrypto.so.3] (119 samples, 0.30%)[libcrypto.so.3] (113 samples, 0.29%)__rust_probestack (9 samples, 0.02%)core::cmp::Ord::max (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)core::num::nonzero::NonZeroU32::new (6 samples, 0.02%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)alloc::sync::Weak<T>::upgrade::_{{closure}} (7 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (43 samples, 0.11%)core::sync::atomic::atomic_compare_exchange_weak (43 samples, 0.11%)alloc::sync::Weak<T>::upgrade (63 samples, 0.16%)core::sync::atomic::AtomicUsize::fetch_update (61 samples, 0.16%)core::sync::atomic::AtomicUsize::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)core::option::Option<T>::map (71 samples, 0.18%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (71 samples, 0.18%)zssp::zssp::Context<Crypto>::receive (8 samples, 0.02%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (55 samples, 0.14%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (55 samples, 0.14%)core::sync::atomic::AtomicUsize::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)__rdl_dealloc (4 samples, 0.01%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::dealloc (4 samples, 0.01%)[libc.so.6] (73 samples, 0.19%)__entry_text_start (13 samples, 0.03%)futex_unqueue (8 samples, 0.02%)update_curr (16 samples, 0.04%)cpuacct_charge (8 samples, 0.02%)dequeue_entity (23 samples, 0.06%)update_load_avg (4 samples, 0.01%)dequeue_task_fair (28 samples, 0.07%)dequeue_task (29 samples, 0.07%)__perf_event_task_sched_in (8 samples, 0.02%)perf_ctx_enable (8 samples, 0.02%)x86_pmu_enable (4 samples, 0.01%)intel_pmu_enable_all (4 samples, 0.01%)native_write_msr (4 samples, 0.01%)finish_task_switch.isra.0 (19 samples, 0.05%)raw_spin_rq_unlock (5 samples, 0.01%)pick_next_task_fair (5 samples, 0.01%)pick_next_task (10 samples, 0.03%)put_prev_task_fair (4 samples, 0.01%)prepare_task_switch (16 samples, 0.04%)__perf_event_task_sched_out (5 samples, 0.01%)_raw_spin_lock (4 samples, 0.01%)psi_group_change (22 samples, 0.06%)psi_task_switch (35 samples, 0.09%)futex_wait_queue (143 samples, 0.37%)schedule (136 samples, 0.35%)__schedule (130 samples, 0.33%)__get_user_nocheck_4 (8 samples, 0.02%)_raw_spin_lock (6 samples, 0.02%)futex_q_lock (8 samples, 0.02%)futex_wait (192 samples, 0.49%)futex_wait_setup (30 samples, 0.08%)__x64_sys_futex (201 samples, 0.51%)do_futex (200 samples, 0.51%)__rseq_handle_notify_resume (7 samples, 0.02%)rseq_update_cpu_node_id (5 samples, 0.01%)exit_to_user_mode_loop (12 samples, 0.03%)exit_to_user_mode_prepare (19 samples, 0.05%)do_syscall_64 (227 samples, 0.58%)syscall_exit_to_user_mode (22 samples, 0.06%)entry_SYSCALL_64_after_hwframe (233 samples, 0.60%)__lll_lock_wait_private (283 samples, 0.72%)[libc.so.6] (759 samples, 1.94%)[..__entry_text_start (8 samples, 0.02%)futex_hash (7 samples, 0.02%)_raw_spin_lock (12 samples, 0.03%)native_queued_spin_lock_slowpath (12 samples, 0.03%)__x64_sys_futex (65 samples, 0.17%)do_futex (61 samples, 0.16%)futex_wake (51 samples, 0.13%)entry_SYSCALL_64_after_hwframe (78 samples, 0.20%)do_syscall_64 (77 samples, 0.20%)syscall_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (6 samples, 0.02%)alloc::alloc::dealloc (903 samples, 2.31%)a..cfree (896 samples, 2.29%)c..__lll_lock_wake_private (96 samples, 0.25%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (906 samples, 2.32%)<..core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (914 samples, 2.34%)c..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (914 samples, 2.34%)<..arrayvec::arrayvec::ArrayVec<T,_>::clear (914 samples, 2.34%)a..arrayvec::arrayvec_impl::ArrayVecImpl::clear (914 samples, 2.34%)a..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (914 samples, 2.34%)a..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (913 samples, 2.34%)c..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (911 samples, 2.33%)c..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (911 samples, 2.33%)c..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (911 samples, 2.33%)<..alloc::raw_vec::RawVec<T,A>::current_memory (5 samples, 0.01%)std::sync::poison::Flag::done (9 samples, 0.02%)std::thread::panicking (9 samples, 0.02%)std::panicking::panicking (9 samples, 0.02%)std::panicking::panic_count::count_is_zero (9 samples, 0.02%)core::sync::atomic::AtomicUsize::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (71 samples, 0.18%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (71 samples, 0.18%)std::sys::unix::locks::futex_mutex::Mutex::unlock (62 samples, 0.16%)core::sync::atomic::AtomicU32::swap (61 samples, 0.16%)core::sync::atomic::atomic_swap (61 samples, 0.16%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (58 samples, 0.15%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (58 samples, 0.15%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (58 samples, 0.15%)core::sync::atomic::AtomicU32::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (48 samples, 0.12%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (48 samples, 0.12%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (48 samples, 0.12%)core::sync::atomic::AtomicU32::fetch_sub (44 samples, 0.11%)core::sync::atomic::atomic_sub (44 samples, 0.11%)core::num::<impl u64>::rotate_left (6 samples, 0.02%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (11 samples, 0.03%)core::num::<impl u64>::rotate_left (11 samples, 0.03%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (30 samples, 0.08%)core::num::<impl u64>::wrapping_add (12 samples, 0.03%)<std::collections::hash::map::RandomState as core::hash::BuildHasher>::build_hasher (5 samples, 0.01%)hashbrown::map::make_hash (76 samples, 0.19%)core::hash::BuildHasher::hash_one (76 samples, 0.19%)core::hash::impls::<impl core::hash::Hash for &T>::hash (17 samples, 0.04%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (17 samples, 0.04%)core::hash::impls::<impl core::hash::Hash for u32>::hash (17 samples, 0.04%)core::hash::Hasher::write_u32 (17 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (17 samples, 0.04%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (17 samples, 0.04%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (17 samples, 0.04%)core::hash::sip::u8to64_le (7 samples, 0.02%)<hashbrown::raw::bitmask::BitMaskIter as core::iter::traits::iterator::Iterator>::next (9 samples, 0.02%)hashbrown::raw::bitmask::BitMask::lowest_set_bit (5 samples, 0.01%)hashbrown::map::equivalent_key::_{{closure}} (7 samples, 0.02%)<core::num::nonzero::NonZeroU32 as core::cmp::PartialEq>::eq (7 samples, 0.02%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (11 samples, 0.03%)hashbrown::raw::Bucket<T>::as_ref (4 samples, 0.01%)hashbrown::raw::Bucket<T>::as_ptr (4 samples, 0.01%)core::ptr::mut_ptr::<impl *mut T>::sub (4 samples, 0.01%)core::ptr::mut_ptr::<impl *mut T>::offset (4 samples, 0.01%)hashbrown::raw::h2 (10 samples, 0.03%)hashbrown::map::HashMap<K,V,S,A>::get_inner (119 samples, 0.30%)hashbrown::raw::RawTable<T,A>::get (43 samples, 0.11%)hashbrown::raw::RawTable<T,A>::find (43 samples, 0.11%)hashbrown::raw::RawTableInner<A>::find_inner (43 samples, 0.11%)hashbrown::raw::sse2::Group::match_byte (4 samples, 0.01%)core::core_arch::x86::sse2::_mm_movemask_epi8 (4 samples, 0.01%)std::collections::hash::map::HashMap<K,V,S>::get (122 samples, 0.31%)hashbrown::map::HashMap<K,V,S,A>::get (122 samples, 0.31%)std::sync::mutex::MutexGuard<T>::new (8 samples, 0.02%)std::sync::poison::Flag::guard (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (47 samples, 0.12%)std::sys::unix::locks::futex_mutex::Mutex::lock (37 samples, 0.09%)core::sync::atomic::AtomicU32::compare_exchange (37 samples, 0.09%)core::sync::atomic::atomic_compare_exchange (37 samples, 0.09%)core::sync::atomic::AtomicU32::compare_exchange_weak (108 samples, 0.28%)core::sync::atomic::atomic_compare_exchange_weak (108 samples, 0.28%)std::sync::rwlock::RwLock<T>::read (129 samples, 0.33%)std::sys::unix::locks::futex_rwlock::RwLock::read (129 samples, 0.33%)std::sys::unix::locks::futex_rwlock::is_read_lockable (7 samples, 0.02%)zssp::antireplay::Window<_,_>::check (11 samples, 0.03%)core::sync::atomic::AtomicU64::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (13 samples, 0.03%)core::ptr::write (12 samples, 0.03%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init_read (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::read (4 samples, 0.01%)core::ptr::read (4 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (4 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::write (4 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (5 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (62 samples, 0.16%)<T as core::convert::TryInto<U>>::try_into (26 samples, 0.07%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (26 samples, 0.07%)core::result::Result<T,E>::map (26 samples, 0.07%)zssp::zeta::from_nonce (31 samples, 0.08%)core::num::<impl u64>::from_be_bytes (4 samples, 0.01%)core::num::<impl u64>::from_be (4 samples, 0.01%)core::num::<impl u64>::swap_bytes (4 samples, 0.01%)<core::slice::iter::IterMut<T> as core::iter::traits::iterator::Iterator>::next (9 samples, 0.02%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (55 samples, 0.14%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (55 samples, 0.14%)core::sync::atomic::AtomicU32::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)[libcrypto.so.3] (103 samples, 0.26%)CRYPTO_gcm128_decrypt (234 samples, 0.60%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)[libcrypto.so.3] (17 samples, 0.04%)CRYPTO_gcm128_decrypt_ctr32 (452 samples, 1.16%)[libcrypto.so.3] (355 samples, 0.91%)CRYPTO_gcm128_setiv (15 samples, 0.04%)[libcrypto.so.3] (14 samples, 0.04%)asm_sysvec_apic_timer_interrupt (5 samples, 0.01%)sysvec_apic_timer_interrupt (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)__perf_event_task_sched_in (9 samples, 0.02%)perf_ctx_enable (9 samples, 0.02%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,297 samples, 10.99%)<zssp::crypto_im..zssp::crypto_impl::openssl::CipherCtx::update (4,297 samples, 10.99%)zssp::crypto_imp..EVP_DecryptUpdate (4,294 samples, 10.99%)EVP_DecryptUpdate[libcrypto.so.3] (4,195 samples, 10.73%)[libcrypto.so.3][libcrypto.so.3] (4,187 samples, 10.71%)[libcrypto.so.3][libcrypto.so.3] (4,169 samples, 10.67%)[libcrypto.so.3][libcrypto.so.3] (3,429 samples, 8.77%)[libcrypto.s..[libcrypto.so.3] (3,280 samples, 8.39%)[libcrypto.s..asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)irqentry_exit_to_user_mode (10 samples, 0.03%)exit_to_user_mode_prepare (10 samples, 0.03%)exit_to_user_mode_loop (10 samples, 0.03%)schedule (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)CRYPTO_clear_free (106 samples, 0.27%)OPENSSL_cleanse (103 samples, 0.26%)EVP_CIPHER_free (44 samples, 0.11%)EVP_CIPHER_CTX_free (199 samples, 0.51%)EVP_CIPHER_CTX_reset (198 samples, 0.51%)cfree (37 samples, 0.09%)[libc.so.6] (5 samples, 0.01%)cfree (9 samples, 0.02%)[libc.so.6] (4 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (212 samples, 0.54%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (212 samples, 0.54%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (212 samples, 0.54%)[libcrypto.so.3] (26 samples, 0.07%)[libcrypto.so.3] (26 samples, 0.07%)[libcrypto.so.3] (26 samples, 0.07%)CRYPTO_gcm128_finish (24 samples, 0.06%)[libcrypto.so.3] (18 samples, 0.05%)zssp::crypto_impl::openssl::CipherCtx::finalize (36 samples, 0.09%)EVP_DecryptFinal_ex (36 samples, 0.09%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)OSSL_PARAM_get_octet_string (7 samples, 0.02%)[libcrypto.so.3] (5 samples, 0.01%)[libc.so.6] (12 samples, 0.03%)OSSL_PARAM_locate (30 samples, 0.08%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (336 samples, 0.86%)zssp::crypto_impl::openssl::CipherCtx::set_tag (88 samples, 0.23%)EVP_CIPHER_CTX_ctrl (88 samples, 0.23%)[libcrypto.so.3] (49 samples, 0.13%)[libc.so.6] (27 samples, 0.07%)OSSL_PARAM_locate (38 samples, 0.10%)EVP_CIPHER_CTX_set_padding (90 samples, 0.23%)[libcrypto.so.3] (53 samples, 0.14%)[libc.so.6] (24 samples, 0.06%)OSSL_PARAM_locate (48 samples, 0.12%)EVP_CIPHER_CTX_get_iv_length (82 samples, 0.21%)[libcrypto.so.3] (60 samples, 0.15%)[libc.so.6] (18 samples, 0.05%)OSSL_PARAM_locate (34 samples, 0.09%)EVP_CIPHER_CTX_get_key_length (51 samples, 0.13%)[libcrypto.so.3] (42 samples, 0.11%)CRYPTO_THREAD_read_lock (155 samples, 0.40%)pthread_rwlock_rdlock (151 samples, 0.39%)pthread_rwlock_unlock (88 samples, 0.23%)CRYPTO_THREAD_unlock (91 samples, 0.23%)EVP_CIPHER_up_ref (29 samples, 0.07%)OPENSSL_LH_retrieve (59 samples, 0.15%)[libcrypto.so.3] (51 samples, 0.13%)pthread_rwlock_rdlock (40 samples, 0.10%)CRYPTO_THREAD_read_lock (43 samples, 0.11%)pthread_rwlock_unlock (49 samples, 0.13%)CRYPTO_THREAD_unlock (54 samples, 0.14%)OPENSSL_strnlen (7 samples, 0.02%)CRYPTO_strndup (15 samples, 0.04%)OPENSSL_strcasecmp (25 samples, 0.06%)OPENSSL_LH_retrieve (150 samples, 0.38%)[libcrypto.so.3] (142 samples, 0.36%)[libcrypto.so.3] (42 samples, 0.11%)[libcrypto.so.3] (298 samples, 0.76%)cfree (23 samples, 0.06%)[libc.so.6] (6 samples, 0.02%)[libcrypto.so.3] (652 samples, 1.67%)EVP_CIPHER_fetch (660 samples, 1.69%)[libcrypto.so.3] (658 samples, 1.68%)EVP_CIPHER_free (15 samples, 0.04%)EVP_CIPHER_up_ref (14 samples, 0.04%)CRYPTO_malloc (4 samples, 0.01%)[libc.so.6] (8 samples, 0.02%)malloc (7 samples, 0.02%)CRYPTO_zalloc (21 samples, 0.05%)CRYPTO_gcm128_init (149 samples, 0.38%)[libcrypto.so.3] (122 samples, 0.31%)[libcrypto.so.3] (188 samples, 0.48%)[libcrypto.so.3] (34 samples, 0.09%)EVP_CipherInit_ex (1,072 samples, 2.74%)EV..[libcrypto.so.3] (1,072 samples, 2.74%)[l..[libcrypto.so.3] (221 samples, 0.57%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,074 samples, 2.75%)zs..[libc.so.6] (4 samples, 0.01%)malloc (15 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (1,188 samples, 3.04%)<zs..zssp::crypto_impl::openssl::CipherCtx::new (22 samples, 0.06%)CRYPTO_zalloc (22 samples, 0.06%)core::slice::index::<impl core::ops::index::Index<I> for [T]>::index (5 samples, 0.01%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index (5 samples, 0.01%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::add (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::offset (4 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (6 samples, 0.02%)std::io::impls::<impl std::io::Write for &mut W>::write (183 samples, 0.47%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (183 samples, 0.47%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (181 samples, 0.46%)core::intrinsics::copy_nonoverlapping (181 samples, 0.46%)[libc.so.6] (181 samples, 0.46%)zssp::zeta::receive_payload_in_place (6,109 samples, 15.63%)zssp::zeta::receive_payl..zssp::antireplay::Window<_,_>::update (13 samples, 0.03%)core::sync::atomic::AtomicU64::fetch_max (13 samples, 0.03%)core::sync::atomic::atomic_umax (13 samples, 0.03%)zssp::zssp::Context<Crypto>::receive (8,570 samples, 21.93%)zssp::zssp::Context<Crypto>::receivezssp::zssp::parse_fragment_header (78 samples, 0.20%)core::slice::<impl [T]>::copy_from_slice (6 samples, 0.02%)core::intrinsics::copy_nonoverlapping (6 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (60 samples, 0.15%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (60 samples, 0.15%)std::sys::unix::locks::futex_mutex::Mutex::unlock (58 samples, 0.15%)core::sync::atomic::AtomicU32::swap (53 samples, 0.14%)core::sync::atomic::atomic_swap (53 samples, 0.14%)std::sync::mutex::MutexGuard<T>::new (4 samples, 0.01%)std::sync::poison::Flag::guard (4 samples, 0.01%)std::thread::panicking (4 samples, 0.01%)std::panicking::panicking (4 samples, 0.01%)std::panicking::panic_count::count_is_zero (4 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (90 samples, 0.23%)std::sys::unix::locks::futex_mutex::Mutex::lock (86 samples, 0.22%)core::sync::atomic::AtomicU32::compare_exchange (80 samples, 0.20%)core::sync::atomic::atomic_compare_exchange (80 samples, 0.20%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (337 samples, 0.86%)zssp::crypto_impl::openssl::CipherCtx::update (184 samples, 0.47%)EVP_EncryptUpdate (183 samples, 0.47%)[libcrypto.so.3] (160 samples, 0.41%)[libcrypto.so.3] (121 samples, 0.31%)[libcrypto.so.3] (116 samples, 0.30%)CRYPTO_gcm128_encrypt (243 samples, 0.62%)[libcrypto.so.3] (137 samples, 0.35%)[libcrypto.so.3] (326 samples, 0.83%)[libcrypto.so.3] (21 samples, 0.05%)CRYPTO_gcm128_encrypt_ctr32 (442 samples, 1.13%)CRYPTO_gcm128_setiv (16 samples, 0.04%)[libcrypto.so.3] (15 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,402 samples, 8.70%)<zssp::crypt..zssp::crypto_impl::openssl::CipherCtx::update (3,400 samples, 8.70%)zssp::crypto..EVP_EncryptUpdate (3,399 samples, 8.70%)EVP_EncryptU..[libcrypto.so.3] (3,327 samples, 8.51%)[libcrypto.s..[libcrypto.so.3] (3,322 samples, 8.50%)[libcrypto.s..[libcrypto.so.3] (3,300 samples, 8.44%)[libcrypto.s..[libcrypto.so.3] (2,562 samples, 6.56%)[libcrypt..[libcrypto.so.3] (2,334 samples, 5.97%)[libcryp..CRYPTO_clear_free (96 samples, 0.25%)OPENSSL_cleanse (96 samples, 0.25%)EVP_CIPHER_free (43 samples, 0.11%)cfree (23 samples, 0.06%)[libc.so.6] (5 samples, 0.01%)EVP_CIPHER_CTX_free (185 samples, 0.47%)EVP_CIPHER_CTX_reset (185 samples, 0.47%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (191 samples, 0.49%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (191 samples, 0.49%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (191 samples, 0.49%)cfree (5 samples, 0.01%)[libc.so.6] (4 samples, 0.01%)zssp::crypto_impl::openssl::CipherCtx::finalize (69 samples, 0.18%)EVP_EncryptFinal_ex (67 samples, 0.17%)[libcrypto.so.3] (51 samples, 0.13%)[libcrypto.so.3] (50 samples, 0.13%)[libcrypto.so.3] (45 samples, 0.12%)CRYPTO_gcm128_tag (45 samples, 0.12%)CRYPTO_gcm128_finish (37 samples, 0.09%)[libcrypto.so.3] (22 samples, 0.06%)[libc.so.6] (15 samples, 0.04%)OSSL_PARAM_locate (39 samples, 0.10%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (345 samples, 0.88%)zssp::crypto_impl::openssl::CipherCtx::get_tag (84 samples, 0.21%)EVP_CIPHER_CTX_ctrl (83 samples, 0.21%)[libcrypto.so.3] (51 samples, 0.13%)[libc.so.6] (25 samples, 0.06%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)OSSL_PARAM_locate (41 samples, 0.10%)strcmp@plt (4 samples, 0.01%)[libcrypto.so.3] (49 samples, 0.13%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (83 samples, 0.21%)asm_sysvec_reschedule_ipi (6 samples, 0.02%)sysvec_reschedule_ipi (6 samples, 0.02%)irqentry_exit (6 samples, 0.02%)irqentry_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (6 samples, 0.02%)exit_to_user_mode_loop (6 samples, 0.02%)schedule (6 samples, 0.02%)__schedule (6 samples, 0.02%)finish_task_switch.isra.0 (6 samples, 0.02%)[libc.so.6] (21 samples, 0.05%)OSSL_PARAM_locate (41 samples, 0.10%)EVP_CIPHER_CTX_get_iv_length (82 samples, 0.21%)[libcrypto.so.3] (54 samples, 0.14%)[libc.so.6] (9 samples, 0.02%)OSSL_PARAM_locate (26 samples, 0.07%)EVP_CIPHER_CTX_get_key_length (48 samples, 0.12%)[libcrypto.so.3] (40 samples, 0.10%)CRYPTO_THREAD_read_lock (116 samples, 0.30%)pthread_rwlock_rdlock (112 samples, 0.29%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)pthread_rwlock_unlock (95 samples, 0.24%)asm_sysvec_reschedule_ipi (4 samples, 0.01%)sysvec_reschedule_ipi (4 samples, 0.01%)irqentry_exit (4 samples, 0.01%)irqentry_exit_to_user_mode (4 samples, 0.01%)exit_to_user_mode_prepare (4 samples, 0.01%)exit_to_user_mode_loop (4 samples, 0.01%)schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_pmu_nop_void (4 samples, 0.01%)CRYPTO_THREAD_unlock (101 samples, 0.26%)EVP_CIPHER_up_ref (39 samples, 0.10%)OPENSSL_LH_retrieve (56 samples, 0.14%)[libcrypto.so.3] (48 samples, 0.12%)[libcrypto.so.3] (6 samples, 0.02%)pthread_rwlock_rdlock (20 samples, 0.05%)CRYPTO_THREAD_read_lock (24 samples, 0.06%)pthread_rwlock_unlock (51 samples, 0.13%)CRYPTO_THREAD_unlock (54 samples, 0.14%)CRYPTO_strndup (10 samples, 0.03%)malloc (6 samples, 0.02%)OPENSSL_strcasecmp (44 samples, 0.11%)OPENSSL_LH_retrieve (173 samples, 0.44%)[libcrypto.so.3] (157 samples, 0.40%)[libcrypto.so.3] (53 samples, 0.14%)cfree (17 samples, 0.04%)[libc.so.6] (6 samples, 0.02%)[libcrypto.so.3] (290 samples, 0.74%)EVP_CIPHER_fetch (622 samples, 1.59%)[libcrypto.so.3] (620 samples, 1.59%)[libcrypto.so.3] (612 samples, 1.57%)EVP_CIPHER_free (14 samples, 0.04%)EVP_CIPHER_up_ref (18 samples, 0.05%)CRYPTO_malloc (4 samples, 0.01%)[libc.so.6] (9 samples, 0.02%)malloc (8 samples, 0.02%)CRYPTO_zalloc (24 samples, 0.06%)OPENSSL_init_crypto (4 samples, 0.01%)CRYPTO_gcm128_init (157 samples, 0.40%)[libcrypto.so.3] (141 samples, 0.36%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)EVP_CipherInit_ex (1,051 samples, 2.69%)EV..[libcrypto.so.3] (1,051 samples, 2.69%)[l..[libcrypto.so.3] (235 samples, 0.60%)[libcrypto.so.3] (202 samples, 0.52%)[libcrypto.so.3] (43 samples, 0.11%)asm_sysvec_reschedule_ipi (6 samples, 0.02%)sysvec_reschedule_ipi (6 samples, 0.02%)irqentry_exit (6 samples, 0.02%)irqentry_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (6 samples, 0.02%)exit_to_user_mode_loop (6 samples, 0.02%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,054 samples, 2.70%)zs..[libc.so.6] (4 samples, 0.01%)malloc (7 samples, 0.02%)CRYPTO_zalloc (13 samples, 0.03%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (1,154 samples, 2.95%)<zs..zssp::crypto_impl::openssl::CipherCtx::new (14 samples, 0.04%)__rdl_alloc (6 samples, 0.02%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (6 samples, 0.02%)[libc.so.6] (49 samples, 0.13%)[libc.so.6] (850 samples, 2.17%)[..asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)__get_user_nocheck_4 (8 samples, 0.02%)futex_q_lock (13 samples, 0.03%)futex_q_unlock (5 samples, 0.01%)__x64_sys_futex (35 samples, 0.09%)do_futex (33 samples, 0.08%)futex_wait (32 samples, 0.08%)futex_wait_setup (28 samples, 0.07%)entry_SYSCALL_64_after_hwframe (37 samples, 0.09%)do_syscall_64 (37 samples, 0.09%)__lll_lock_wait_private (55 samples, 0.14%)futex_wake_mark (11 samples, 0.03%)wake_q_add_safe (4 samples, 0.01%)call_function_single_prep_ipi (4 samples, 0.01%)__smp_call_single_queue (17 samples, 0.04%)native_send_call_func_single_ipi (13 samples, 0.03%)x2apic_send_IPI (12 samples, 0.03%)native_write_msr (10 samples, 0.03%)llist_add_batch (10 samples, 0.03%)futex_wake (123 samples, 0.31%)wake_up_q (63 samples, 0.16%)try_to_wake_up (58 samples, 0.15%)ttwu_queue_wakelist (38 samples, 0.10%)__x64_sys_futex (133 samples, 0.34%)do_futex (132 samples, 0.34%)entry_SYSCALL_64_after_hwframe (145 samples, 0.37%)do_syscall_64 (144 samples, 0.37%)syscall_exit_to_user_mode (7 samples, 0.02%)exit_to_user_mode_prepare (5 samples, 0.01%)alloc::vec::Vec<T,A>::with_capacity_in (1,653 samples, 4.23%)alloc..alloc::raw_vec::RawVec<T,A>::with_capacity_in (1,653 samples, 4.23%)alloc..alloc::raw_vec::RawVec<T,A>::allocate_in (1,653 samples, 4.23%)alloc..<alloc::alloc::Global as core::alloc::Allocator>::allocate (1,651 samples, 4.22%)<allo..alloc::alloc::Global::alloc_impl (1,651 samples, 4.22%)alloc..alloc::alloc::alloc (1,651 samples, 4.22%)alloc..malloc (1,644 samples, 4.21%)malloc__lll_lock_wake_private (152 samples, 0.39%)alloc::slice::<impl [T]>::to_vec (1,857 samples, 4.75%)alloc:..alloc::slice::<impl [T]>::to_vec_in (1,857 samples, 4.75%)alloc:..alloc::slice::hack::to_vec (1,857 samples, 4.75%)alloc:..<T as alloc::slice::hack::ConvertVec>::to_vec (1,857 samples, 4.75%)<T as ..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (204 samples, 0.52%)core::intrinsics::copy_nonoverlapping (204 samples, 0.52%)[libc.so.6] (203 samples, 0.52%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (100 samples, 0.26%)core::sync::atomic::atomic_compare_exchange_weak (100 samples, 0.26%)std::sync::mpmc::array::Channel<T>::start_send (155 samples, 0.40%)core::sync::atomic::AtomicUsize::load (32 samples, 0.08%)core::sync::atomic::atomic_load (32 samples, 0.08%)core::ptr::mut_ptr::<impl *mut T>::write (4 samples, 0.01%)core::ptr::write (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (185 samples, 0.47%)std::sync::mpmc::array::Channel<T>::write (13 samples, 0.03%)std::sync::mpmc::waker::SyncWaker::notify (7 samples, 0.02%)core::sync::atomic::AtomicBool::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)benchmark::bob_main::_{{closure}} (3,332 samples, 8.53%)benchmark::b..std::sync::mpsc::SyncSender<T>::send (1,474 samples, 3.77%)std:..std::sync::mpmc::Sender<T>::send (1,473 samples, 3.77%)std:..core::mem::drop (27 samples, 0.07%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (27 samples, 0.07%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (27 samples, 0.07%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (27 samples, 0.07%)core::sync::atomic::AtomicU32::fetch_sub (27 samples, 0.07%)core::sync::atomic::atomic_sub (27 samples, 0.07%)core::slice::<impl [T]>::copy_from_slice (7 samples, 0.02%)core::intrinsics::copy_nonoverlapping (7 samples, 0.02%)core::sync::atomic::AtomicU32::compare_exchange_weak (35 samples, 0.09%)core::sync::atomic::atomic_compare_exchange_weak (35 samples, 0.09%)std::sync::rwlock::RwLock<T>::read (36 samples, 0.09%)std::sys::unix::locks::futex_rwlock::RwLock::read (36 samples, 0.09%)benchmark::bob_main (18,132 samples, 46.39%)benchmark::bob_mainzssp::zssp::Context<Crypto>::send (8,692 samples, 22.24%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,691 samples, 22.24%)zssp::zeta::send_payloadzssp::zeta::get_counter (12 samples, 0.03%)core::sync::atomic::AtomicU64::fetch_add (11 samples, 0.03%)core::sync::atomic::atomic_add (11 samples, 0.03%)cfree (19 samples, 0.05%)clock_gettime (12 samples, 0.03%)core::hash::BuildHasher::hash_one (20 samples, 0.05%)core::hash::impls::<impl core::hash::Hash for &T>::hash (12 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (4 samples, 0.01%)core::result::Result<T,E>::unwrap (7 samples, 0.02%)pthread_rwlock_rdlock (21 samples, 0.05%)pthread_rwlock_unlock (15 samples, 0.04%)std::sync::mpmc::Receiver<T>::recv_timeout (12 samples, 0.03%)std::sync::mpmc::Sender<T>::send (15 samples, 0.04%)<std::sync::mpmc::select::Token as core::default::Default>::default (7 samples, 0.02%)std::sync::mpmc::array::Channel<T>::recv (24 samples, 0.06%)std::sync::mpmc::array::Channel<T>::send (11 samples, 0.03%)std::sync::mpmc::array::Channel<T>::start_recv (12 samples, 0.03%)std::sync::mpmc::utils::Backoff::new (9 samples, 0.02%)std::sync::mpmc::waker::SyncWaker::notify (19 samples, 0.05%)core::sync::atomic::AtomicBool::load (4 samples, 0.01%)core::sync::atomic::atomic_load (4 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (6 samples, 0.02%)std::sys::unix::time::Timespec::sub_timespec (6 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (15 samples, 0.04%)std::time::SystemTime::checked_add (5 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (21 samples, 0.05%)zssp::zeta::from_nonce (5 samples, 0.01%)alloc::vec::Vec<T,A>::with_capacity_in (14 samples, 0.04%)alloc::raw_vec::RawVec<T,A>::with_capacity_in (14 samples, 0.04%)alloc::raw_vec::RawVec<T,A>::allocate_in (14 samples, 0.04%)<alloc::alloc::Global as core::alloc::Allocator>::allocate (14 samples, 0.04%)alloc::alloc::Global::alloc_impl (14 samples, 0.04%)alloc::alloc::alloc (14 samples, 0.04%)alloc::slice::<impl [T]>::to_vec (20 samples, 0.05%)alloc::slice::<impl [T]>::to_vec_in (20 samples, 0.05%)alloc::slice::hack::to_vec (20 samples, 0.05%)<T as alloc::slice::hack::ConvertVec>::to_vec (20 samples, 0.05%)core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (6 samples, 0.02%)core::intrinsics::copy_nonoverlapping (6 samples, 0.02%)zssp::zeta::send_payload (50 samples, 0.13%)benchmark::bob_main::_{{closure}} (29 samples, 0.07%)std::sync::mpsc::SyncSender<T>::send (9 samples, 0.02%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (7 samples, 0.02%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (7 samples, 0.02%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (7 samples, 0.02%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (7 samples, 0.02%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (7 samples, 0.02%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (7 samples, 0.02%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (7 samples, 0.02%)alloc::alloc::dealloc (7 samples, 0.02%)cfree (5 samples, 0.01%)__lll_lock_wake_private (5 samples, 0.01%)__entry_text_start (5 samples, 0.01%)getrandom::imp::getrandom_inner (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (70 samples, 0.18%)std::sync::mpmc::array::Channel<T>::start_recv (13 samples, 0.03%)[unknown] (37,550 samples, 96.08%)[unknown]zssp::zssp::parse_fragment_header (4 samples, 0.01%)EVP_DecryptUpdate (31 samples, 0.08%)__bss_start (36 samples, 0.09%)[libcrypto.so.3] (5 samples, 0.01%)_raw_spin_unlock (4 samples, 0.01%)preempt_schedule_thunk (4 samples, 0.01%)preempt_schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)entry_SYSCALL_64_after_hwframe (9 samples, 0.02%)do_syscall_64 (7 samples, 0.02%)__x64_sys_exit_group (6 samples, 0.02%)do_group_exit (6 samples, 0.02%)do_exit (6 samples, 0.02%)exit_mm (6 samples, 0.02%)mmput (6 samples, 0.02%)__mmput (6 samples, 0.02%)exit_mmap (6 samples, 0.02%)unmap_vmas (5 samples, 0.01%)unmap_single_vma (5 samples, 0.01%)unmap_page_range (5 samples, 0.01%)zap_pmd_range.isra.0 (5 samples, 0.01%)zap_pte_range (5 samples, 0.01%)entry_SYSCALL_64_safe_stack (15 samples, 0.04%)ret_from_fork (10 samples, 0.03%)schedule_tail (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_ctx_enable (10 samples, 0.03%)syscall_return_via_sysret (9 samples, 0.02%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (5 samples, 0.01%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (5 samples, 0.01%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (5 samples, 0.01%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (5 samples, 0.01%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (5 samples, 0.01%)alloc::alloc::dealloc (5 samples, 0.01%)benchmark (39,077 samples, 99.99%)benchmarkzssp::zssp::Context<Crypto>::receive (41 samples, 0.10%)perf_event_exec (4 samples, 0.01%)perf_event_enable_on_exec (4 samples, 0.01%)ctx_resched (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)all (39,082 samples, 100%)perf-exec (5 samples, 0.01%)entry_SYSCALL_64_after_hwframe (5 samples, 0.01%)do_syscall_64 (5 samples, 0.01%)__x64_sys_execve (5 samples, 0.01%)do_execveat_common.isra.0 (5 samples, 0.01%)bprm_execve (5 samples, 0.01%)bprm_execve.part.0 (5 samples, 0.01%)exec_binprm (5 samples, 0.01%)search_binary_handler (5 samples, 0.01%)load_elf_binary (5 samples, 0.01%)begin_new_exec (5 samples, 0.01%) \ No newline at end of file diff --git a/src/zeta.rs b/src/zeta.rs index f6d3cb7..cd4ce9a 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -1676,16 +1676,11 @@ pub(crate) fn send_payload( mtu_sized_buffer[..HEADER_SIZE].copy_from_slice(&header); mtu_sized_buffer[FRAGMENT_NO_IDX] = fragment_no as u8; - cipher.encrypt( - &payload[i..j], - &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + fragment_len], - ); + let fragment_start = &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + fragment_len]; + cipher.encrypt(&payload[i..j], fragment_start); - state.hk_send.encrypt_in_place( - (&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]) - .try_into() - .unwrap(), - ); + let header_auth = &mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]; + state.hk_send.encrypt_in_place(header_auth.try_into().unwrap()); if !send(&mut mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { return Ok(()); @@ -1699,17 +1694,12 @@ pub(crate) fn send_payload( mtu_sized_buffer[..HEADER_SIZE].copy_from_slice(&header); mtu_sized_buffer[FRAGMENT_NO_IDX] = fragment_no as u8; - cipher.encrypt( - &payload[i..], - &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + payload_rem], - ); + let fragment_start = &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + payload_rem]; + cipher.encrypt(&payload[i..], fragment_start); mtu_sized_buffer[HEADER_SIZE + payload_rem..HEADER_SIZE + fragment_len].copy_from_slice(&cipher.finish()); - state.hk_send.encrypt_in_place( - (&mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]) - .try_into() - .unwrap(), - ); + let header_auth = &mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]; + state.hk_send.encrypt_in_place(header_auth.try_into().unwrap()); if !send(&mut mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { return Ok(()); diff --git a/src/zssp.rs b/src/zssp.rs index 2996c0c..27b24a6 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -549,7 +549,8 @@ impl Context { challenge_packet.extend(challenge); let nonce = to_nonce(PACKET_TYPE_CHALLENGE, ctx.rng.lock().unwrap().next_u64()); challenge_packet[FRAGMENT_COUNT_IDX] = 1; - challenge_packet[PACKET_NONCE_START..HEADER_SIZE].copy_from_slice(&nonce[..PACKET_NONCE_SIZE]); + challenge_packet[PACKET_NONCE_START..HEADER_SIZE] + .copy_from_slice(&nonce[..PACKET_NONCE_SIZE]); set_header(&mut challenge_packet, 0, &nonce); send_unassociated_reply(&mut challenge_packet); From ef0cde79ce560c96d54e1bba5fe506255c8afe8a Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 17:03:00 -0400 Subject: [PATCH 79/91] improved performance --- .gitignore | 3 +- flamegraph.svg | 2 +- src/crypto_impl/openssl.rs | 62 +++++++++++++++++++++++--------------- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 790f4b6..68e7d6c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target -./perf* +perf*.data +perf*.old diff --git a/flamegraph.svg b/flamegraph.svg index 8075ab4..a00c032 100644 --- a/flamegraph.svg +++ b/flamegraph.svg @@ -488,4 +488,4 @@ function search(term) { function format_percent(n) { return n.toFixed(4) + "%"; } -]]>Flame Graph Reset ZoomSearch <zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (13 samples, 0.03%)zssp::crypto_impl::openssl::CipherCtx::update (5 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (35 samples, 0.09%)zssp::crypto_impl::openssl::CipherCtx::update (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (71 samples, 0.18%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (8 samples, 0.02%)CRYPTO_THREAD_read_lock (7 samples, 0.02%)CRYPTO_gcm128_decrypt (8 samples, 0.02%)CRYPTO_gcm128_decrypt_ctr32 (21 samples, 0.05%)CRYPTO_gcm128_encrypt (10 samples, 0.03%)CRYPTO_gcm128_encrypt_ctr32 (16 samples, 0.04%)CRYPTO_gcm128_finish (4 samples, 0.01%)CRYPTO_gcm128_init (9 samples, 0.02%)CRYPTO_gcm128_tag (5 samples, 0.01%)CRYPTO_zalloc (25 samples, 0.06%)EVP_CIPHER_CTX_ctrl (5 samples, 0.01%)EVP_CIPHER_CTX_reset (7 samples, 0.02%)EVP_CIPHER_CTX_set_padding (12 samples, 0.03%)EVP_CIPHER_fetch (6 samples, 0.02%)EVP_CIPHER_free (17 samples, 0.04%)EVP_DecryptUpdate (4 samples, 0.01%)EVP_EncryptFinal_ex (6 samples, 0.02%)OPENSSL_LH_retrieve (10 samples, 0.03%)OPENSSL_strnlen (5 samples, 0.01%)OSSL_PARAM_locate (121 samples, 0.31%)OSSL_PARAM_set_uint64 (4 samples, 0.01%)[libc.so.6] (60 samples, 0.15%)[libcrypto.so.3] (289 samples, 0.74%)cfree (13 samples, 0.03%)malloc (31 samples, 0.08%)pthread_rwlock_rdlock (5 samples, 0.01%)pthread_rwlock_unlock (7 samples, 0.02%)std::sync::mpmc::Sender<T>::send (9 samples, 0.02%)<std::sync::mpmc::select::Token as core::default::Default>::default (11 samples, 0.03%)std::sync::mpmc::array::Channel<T>::start_send (11 samples, 0.03%)std::sync::mpmc::array::Channel<T>::send (45 samples, 0.12%)std::sync::mpmc::array::Channel<T>::write (7 samples, 0.02%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::try_recv (19 samples, 0.05%)std::sync::mpmc::utils::Backoff::new (11 samples, 0.03%)core::sync::atomic::AtomicBool::load (10 samples, 0.03%)core::sync::atomic::atomic_load (10 samples, 0.03%)std::sync::mpmc::waker::SyncWaker::notify (11 samples, 0.03%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (7 samples, 0.02%)std::time::Instant::elapsed (7 samples, 0.02%)syscall (10 samples, 0.03%)__entry_text_start (6 samples, 0.02%)[anon] (1,024 samples, 2.62%)[a..zssp::zeta::receive_payload_in_place (25 samples, 0.06%)std::io::impls::<impl std::io::Write for &mut W>::write (7 samples, 0.02%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (7 samples, 0.02%)core::intrinsics::copy_nonoverlapping (7 samples, 0.02%)<std::sync::mpmc::zero::ZeroToken as core::default::Default>::default (4 samples, 0.01%)EVP_DecryptUpdate (4 samples, 0.01%)[libc.so.6] (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (8 samples, 0.02%)std::sync::mpmc::array::Channel<T>::start_recv (9 samples, 0.02%)[benchmark] (54 samples, 0.14%)zssp::zssp::Context<Crypto>::receive (17 samples, 0.04%)CRYPTO_THREAD_read_lock (17 samples, 0.04%)CRYPTO_THREAD_run_once (4 samples, 0.01%)CRYPTO_THREAD_unlock (19 samples, 0.05%)CRYPTO_gcm128_finish (14 samples, 0.04%)CRYPTO_gcm128_setiv (7 samples, 0.02%)CRYPTO_get_ex_data (10 samples, 0.03%)OPENSSL_sk_value (4 samples, 0.01%)OSSL_PARAM_construct_size_t (5 samples, 0.01%)[libc.so.6] (17 samples, 0.04%)[libcrypto.so.3] (145 samples, 0.37%)pthread_getspecific (7 samples, 0.02%)pthread_rwlock_rdlock (22 samples, 0.06%)[libcrypto.so.3] (316 samples, 0.81%)pthread_rwlock_unlock (22 samples, 0.06%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (14 samples, 0.04%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (14 samples, 0.04%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (80 samples, 0.20%)zssp::crypto_impl::openssl::CipherCtx::update (10 samples, 0.03%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (8 samples, 0.02%)zssp::crypto_impl::openssl::CipherCtx::update (8 samples, 0.02%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (7 samples, 0.02%)CRYPTO_THREAD_read_lock (6 samples, 0.02%)CRYPTO_THREAD_unlock (11 samples, 0.03%)CRYPTO_gcm128_decrypt (10 samples, 0.03%)CRYPTO_gcm128_decrypt_ctr32 (22 samples, 0.06%)CRYPTO_gcm128_encrypt (15 samples, 0.04%)CRYPTO_gcm128_encrypt_ctr32 (28 samples, 0.07%)CRYPTO_strndup (5 samples, 0.01%)EVP_CIPHER_CTX_ctrl (10 samples, 0.03%)EVP_CIPHER_CTX_get_iv_length (4 samples, 0.01%)EVP_CIPHER_get_block_size (9 samples, 0.02%)EVP_DecryptUpdate (54 samples, 0.14%)EVP_EncryptUpdate (45 samples, 0.12%)OPENSSL_LH_retrieve (8 samples, 0.02%)OPENSSL_init_crypto (4 samples, 0.01%)OSSL_PARAM_locate (18 samples, 0.05%)[[vdso]] (8 samples, 0.02%)[benchmark] (13 samples, 0.03%)EVP_DecryptUpdate (13 samples, 0.03%)[libc.so.6] (88 samples, 0.23%)[libcrypto.so.3] (322 samples, 0.82%)__bss_start (11 samples, 0.03%)[libcrypto.so.3] (11 samples, 0.03%)__entry_text_start (39 samples, 0.10%)_copy_to_iter (4 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)_copy_to_iter (37 samples, 0.09%)copyout (29 samples, 0.07%)asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)raw_irqentry_exit_cond_resched (10 samples, 0.03%)preempt_schedule_irq (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_pmu_nop_void (5 samples, 0.01%)__memcpy (6 samples, 0.02%)chacha_block_generic (225 samples, 0.58%)chacha_permute (202 samples, 0.52%)__x64_sys_getrandom (367 samples, 0.94%)get_random_bytes_user (354 samples, 0.91%)crng_make_state (276 samples, 0.71%)crng_fast_key_erasure (253 samples, 0.65%)exit_to_user_mode_prepare (4 samples, 0.01%)exit_to_user_mode_prepare (13 samples, 0.03%)fpregs_assert_state_consistent (5 samples, 0.01%)do_syscall_64 (396 samples, 1.01%)syscall_exit_to_user_mode (21 samples, 0.05%)entry_SYSCALL_64_after_hwframe (403 samples, 1.03%)<rand_core::os::OsRng as rand_core::RngCore>::next_u64 (485 samples, 1.24%)rand_core::impls::next_u64_via_fill (485 samples, 1.24%)<rand_core::os::OsRng as rand_core::RngCore>::fill_bytes (485 samples, 1.24%)<rand_core::os::OsRng as rand_core::RngCore>::try_fill_bytes (485 samples, 1.24%)getrandom::getrandom (485 samples, 1.24%)getrandom::getrandom_uninit (485 samples, 1.24%)getrandom::imp::getrandom_inner (484 samples, 1.24%)getrandom::util_libc::sys_fill_exact (484 samples, 1.24%)getrandom::imp::getrandom_inner::_{{closure}} (482 samples, 1.23%)getrandom::imp::getrandom (482 samples, 1.23%)syscall (482 samples, 1.23%)syscall_return_via_sysret (11 samples, 0.03%)[libc.so.6] (18 samples, 0.05%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (7 samples, 0.02%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (7 samples, 0.02%)core::sync::atomic::AtomicUsize::fetch_sub (6 samples, 0.02%)core::sync::atomic::atomic_sub (6 samples, 0.02%)_ZN3std4sync4mpmc5array16Channel$LT$T$GT$10start_recv17h7800ca29c64cb868E.llvm.12455019271255371362 (7 samples, 0.02%)core::result::Result<T,E>::map_err (5 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (15 samples, 0.04%)std::sync::mpmc::array::Channel<T>::read (17 samples, 0.04%)core::slice::<impl [T]>::get_unchecked (8 samples, 0.02%)<usize as core::slice::index::SliceIndex<[T]>>::get_unchecked (8 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::add (8 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::offset (8 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (47 samples, 0.12%)core::sync::atomic::atomic_compare_exchange_weak (47 samples, 0.12%)core::sync::atomic::AtomicUsize::load (143 samples, 0.37%)core::sync::atomic::atomic_load (143 samples, 0.37%)core::sync::atomic::fence (16 samples, 0.04%)std::sync::mpsc::Receiver<T>::try_recv (299 samples, 0.77%)std::sync::mpmc::Receiver<T>::try_recv (299 samples, 0.77%)std::sync::mpmc::array::Channel<T>::try_recv (287 samples, 0.73%)std::sync::mpmc::array::Channel<T>::start_recv (248 samples, 0.63%)<std::time::Instant as core::ops::arith::Sub>::sub (6 samples, 0.02%)std::time::Instant::duration_since (6 samples, 0.02%)std::time::Instant::checked_duration_since (5 samples, 0.01%)std::sys::unix::time::inner::Instant::checked_sub_instant (5 samples, 0.01%)std::sys::unix::time::Timespec::sub_timespec (5 samples, 0.01%)std::time::Instant::elapsed (28 samples, 0.07%)std::time::Instant::now (22 samples, 0.06%)std::sys::unix::time::inner::Instant::now (22 samples, 0.06%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (22 samples, 0.06%)clock_gettime (21 samples, 0.05%)[[vdso]] (21 samples, 0.05%)[[vdso]] (17 samples, 0.04%)<T as core::convert::TryInto<U>>::try_into (386 samples, 0.99%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (386 samples, 0.99%)core::result::Result<T,E>::map (386 samples, 0.99%)<std::sync::mpmc::zero::ZeroToken as core::default::Default>::default (4 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (61 samples, 0.16%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (61 samples, 0.16%)std::sys::unix::locks::futex_mutex::Mutex::unlock (58 samples, 0.15%)core::sync::atomic::AtomicU32::swap (56 samples, 0.14%)core::sync::atomic::atomic_swap (56 samples, 0.14%)std::sync::mutex::Mutex<T>::lock (67 samples, 0.17%)std::sys::unix::locks::futex_mutex::Mutex::lock (66 samples, 0.17%)core::sync::atomic::AtomicU32::compare_exchange (65 samples, 0.17%)core::sync::atomic::atomic_compare_exchange (65 samples, 0.17%)EVP_CIPHER_CTX_get_block_size (4 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (362 samples, 0.93%)zssp::crypto_impl::openssl::CipherCtx::update (228 samples, 0.58%)EVP_DecryptUpdate (221 samples, 0.57%)[libcrypto.so.3] (203 samples, 0.52%)[libcrypto.so.3] (148 samples, 0.38%)[libcrypto.so.3] (137 samples, 0.35%)__rust_probestack (8 samples, 0.02%)alloc::sync::Weak<T>::upgrade::_{{closure}} (5 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (54 samples, 0.14%)core::sync::atomic::atomic_compare_exchange_weak (54 samples, 0.14%)alloc::sync::Weak<T>::upgrade (78 samples, 0.20%)core::sync::atomic::AtomicUsize::fetch_update (72 samples, 0.18%)core::sync::atomic::AtomicUsize::load (9 samples, 0.02%)core::sync::atomic::atomic_load (9 samples, 0.02%)core::option::Option<T>::map (82 samples, 0.21%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (82 samples, 0.21%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (59 samples, 0.15%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (59 samples, 0.15%)core::sync::atomic::AtomicUsize::fetch_sub (54 samples, 0.14%)core::sync::atomic::atomic_sub (54 samples, 0.14%)[libc.so.6] (33 samples, 0.08%)__entry_text_start (6 samples, 0.02%)futex_unqueue (8 samples, 0.02%)update_curr (13 samples, 0.03%)cpuacct_charge (4 samples, 0.01%)dequeue_task (24 samples, 0.06%)dequeue_task_fair (24 samples, 0.06%)dequeue_entity (24 samples, 0.06%)finish_task_switch.isra.0 (12 samples, 0.03%)raw_spin_rq_unlock (7 samples, 0.02%)prepare_task_switch (14 samples, 0.04%)__perf_event_task_sched_out (4 samples, 0.01%)psi_group_change (16 samples, 0.04%)psi_task_switch (31 samples, 0.08%)sched_clock_cpu (6 samples, 0.02%)__schedule (97 samples, 0.25%)futex_wait_queue (109 samples, 0.28%)schedule (102 samples, 0.26%)__get_user_nocheck_4 (11 samples, 0.03%)futex_q_lock (4 samples, 0.01%)futex_q_unlock (4 samples, 0.01%)futex_wait_setup (28 samples, 0.07%)__x64_sys_futex (156 samples, 0.40%)do_futex (154 samples, 0.39%)futex_wait (153 samples, 0.39%)__rseq_handle_notify_resume (10 samples, 0.03%)exit_to_user_mode_loop (17 samples, 0.04%)do_syscall_64 (183 samples, 0.47%)syscall_exit_to_user_mode (22 samples, 0.06%)exit_to_user_mode_prepare (22 samples, 0.06%)__lll_lock_wait_private (227 samples, 0.58%)entry_SYSCALL_64_after_hwframe (185 samples, 0.47%)[libc.so.6] (583 samples, 1.49%)__entry_text_start (7 samples, 0.02%)__x64_sys_futex (6 samples, 0.02%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)raw_irqentry_exit_cond_resched (5 samples, 0.01%)preempt_schedule_irq (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)_raw_spin_lock (8 samples, 0.02%)futex_hash (4 samples, 0.01%)_raw_spin_lock (18 samples, 0.05%)native_queued_spin_lock_slowpath (18 samples, 0.05%)get_futex_key (4 samples, 0.01%)futex_wake (53 samples, 0.14%)__x64_sys_futex (71 samples, 0.18%)do_futex (68 samples, 0.17%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)do_syscall_64 (86 samples, 0.22%)syscall_exit_to_user_mode (14 samples, 0.04%)exit_to_user_mode_prepare (13 samples, 0.03%)entry_SYSCALL_64_after_hwframe (94 samples, 0.24%)alloc::alloc::dealloc (730 samples, 1.87%)a..cfree (727 samples, 1.86%)c..__lll_lock_wake_private (106 samples, 0.27%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (732 samples, 1.87%)<..core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (738 samples, 1.89%)c..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (738 samples, 1.89%)<..arrayvec::arrayvec::ArrayVec<T,_>::clear (738 samples, 1.89%)a..arrayvec::arrayvec_impl::ArrayVecImpl::clear (738 samples, 1.89%)a..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (738 samples, 1.89%)a..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (736 samples, 1.88%)c..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (736 samples, 1.88%)c..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (736 samples, 1.88%)c..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (736 samples, 1.88%)<..alloc::raw_vec::RawVec<T,A>::current_memory (4 samples, 0.01%)std::sync::poison::Flag::done (9 samples, 0.02%)std::thread::panicking (9 samples, 0.02%)std::panicking::panicking (9 samples, 0.02%)std::panicking::panic_count::count_is_zero (9 samples, 0.02%)core::sync::atomic::AtomicUsize::load (9 samples, 0.02%)core::sync::atomic::atomic_load (9 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (76 samples, 0.19%)std::sys::unix::locks::futex_mutex::Mutex::unlock (67 samples, 0.17%)core::sync::atomic::AtomicU32::swap (65 samples, 0.17%)core::sync::atomic::atomic_swap (65 samples, 0.17%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (77 samples, 0.20%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (77 samples, 0.20%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (77 samples, 0.20%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (77 samples, 0.20%)core::sync::atomic::AtomicU32::fetch_sub (70 samples, 0.18%)core::sync::atomic::atomic_sub (70 samples, 0.18%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (51 samples, 0.13%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (51 samples, 0.13%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (51 samples, 0.13%)core::sync::atomic::AtomicU32::fetch_sub (48 samples, 0.12%)core::sync::atomic::atomic_sub (48 samples, 0.12%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (7 samples, 0.02%)core::num::<impl u64>::wrapping_add (6 samples, 0.02%)core::num::<impl u64>::rotate_left (14 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (34 samples, 0.09%)core::num::<impl u64>::wrapping_add (13 samples, 0.03%)hashbrown::map::make_hash (70 samples, 0.18%)core::hash::BuildHasher::hash_one (70 samples, 0.18%)core::hash::impls::<impl core::hash::Hash for &T>::hash (14 samples, 0.04%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (14 samples, 0.04%)core::hash::impls::<impl core::hash::Hash for u32>::hash (14 samples, 0.04%)core::hash::Hasher::write_u32 (14 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (14 samples, 0.04%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (14 samples, 0.04%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (14 samples, 0.04%)core::hash::sip::u8to64_le (11 samples, 0.03%)<hashbrown::raw::bitmask::BitMaskIter as core::iter::traits::iterator::Iterator>::next (4 samples, 0.01%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (8 samples, 0.02%)hashbrown::map::equivalent_key::_{{closure}} (8 samples, 0.02%)<core::num::nonzero::NonZeroU32 as core::cmp::PartialEq>::eq (8 samples, 0.02%)hashbrown::raw::h2 (9 samples, 0.02%)hashbrown::map::HashMap<K,V,S,A>::get_inner (109 samples, 0.28%)hashbrown::raw::RawTable<T,A>::get (39 samples, 0.10%)hashbrown::raw::RawTable<T,A>::find (39 samples, 0.10%)hashbrown::raw::RawTableInner<A>::find_inner (39 samples, 0.10%)hashbrown::raw::sse2::Group::load (13 samples, 0.03%)core::core_arch::x86::sse2::_mm_loadu_si128 (13 samples, 0.03%)core::intrinsics::copy_nonoverlapping (13 samples, 0.03%)std::collections::hash::map::HashMap<K,V,S>::get (113 samples, 0.29%)hashbrown::map::HashMap<K,V,S,A>::get (113 samples, 0.29%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (6 samples, 0.02%)std::sync::mutex::MutexGuard<T>::new (14 samples, 0.04%)std::sync::poison::Flag::guard (8 samples, 0.02%)std::sync::mutex::Mutex<T>::lock (47 samples, 0.12%)std::sys::unix::locks::futex_mutex::Mutex::lock (33 samples, 0.08%)core::sync::atomic::AtomicU32::compare_exchange (32 samples, 0.08%)core::sync::atomic::atomic_compare_exchange (32 samples, 0.08%)core::sync::atomic::AtomicU32::compare_exchange_weak (117 samples, 0.30%)core::sync::atomic::atomic_compare_exchange_weak (117 samples, 0.30%)std::sync::rwlock::RwLock<T>::read (140 samples, 0.36%)std::sys::unix::locks::futex_rwlock::RwLock::read (140 samples, 0.36%)std::sys::unix::locks::futex_rwlock::is_read_lockable (6 samples, 0.02%)zssp::antireplay::Window<_,_>::check (7 samples, 0.02%)core::sync::atomic::AtomicU64::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (16 samples, 0.04%)arrayvec::arrayvec_impl::ArrayVecImpl::push (16 samples, 0.04%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (16 samples, 0.04%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (14 samples, 0.04%)core::ptr::write (12 samples, 0.03%)core::mem::maybe_uninit::MaybeUninit<T>::write (4 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (51 samples, 0.13%)<T as core::convert::TryInto<U>>::try_into (28 samples, 0.07%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (28 samples, 0.07%)core::result::Result<T,E>::map (28 samples, 0.07%)zssp::zeta::from_nonce (33 samples, 0.08%)<core::slice::iter::IterMut<T> as core::iter::traits::iterator::Iterator>::next (6 samples, 0.02%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (76 samples, 0.19%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (76 samples, 0.19%)core::sync::atomic::AtomicU32::fetch_sub (75 samples, 0.19%)core::sync::atomic::atomic_sub (75 samples, 0.19%)CRYPTO_gcm128_decrypt (231 samples, 0.59%)[libcrypto.so.3] (86 samples, 0.22%)CRYPTO_gcm128_decrypt_ctr32 (440 samples, 1.13%)[libcrypto.so.3] (352 samples, 0.90%)[libcrypto.so.3] (27 samples, 0.07%)CRYPTO_gcm128_setiv (23 samples, 0.06%)[libcrypto.so.3] (20 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,270 samples, 10.93%)<zssp::crypto_im..zssp::crypto_impl::openssl::CipherCtx::update (4,270 samples, 10.93%)zssp::crypto_imp..EVP_DecryptUpdate (4,266 samples, 10.92%)EVP_DecryptUpdate[libcrypto.so.3] (4,171 samples, 10.67%)[libcrypto.so.3][libcrypto.so.3] (4,167 samples, 10.66%)[libcrypto.so.3][libcrypto.so.3] (4,144 samples, 10.60%)[libcrypto.so.3][libcrypto.so.3] (3,418 samples, 8.75%)[libcrypto.s..[libcrypto.so.3] (3,257 samples, 8.33%)[libcrypto.s..asm_sysvec_reschedule_ipi (17 samples, 0.04%)sysvec_reschedule_ipi (16 samples, 0.04%)irqentry_exit (16 samples, 0.04%)irqentry_exit_to_user_mode (16 samples, 0.04%)exit_to_user_mode_prepare (16 samples, 0.04%)exit_to_user_mode_loop (16 samples, 0.04%)schedule (15 samples, 0.04%)__schedule (15 samples, 0.04%)finish_task_switch.isra.0 (15 samples, 0.04%)__perf_event_task_sched_in (15 samples, 0.04%)perf_ctx_enable (15 samples, 0.04%)CRYPTO_clear_free (92 samples, 0.24%)OPENSSL_cleanse (92 samples, 0.24%)EVP_CIPHER_free (27 samples, 0.07%)cfree (42 samples, 0.11%)[libc.so.6] (7 samples, 0.02%)EVP_CIPHER_CTX_free (184 samples, 0.47%)EVP_CIPHER_CTX_reset (180 samples, 0.46%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (210 samples, 0.54%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (210 samples, 0.54%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (210 samples, 0.54%)cfree (25 samples, 0.06%)[libc.so.6] (15 samples, 0.04%)CRYPTO_gcm128_finish (29 samples, 0.07%)[libcrypto.so.3] (18 samples, 0.05%)[libcrypto.so.3] (33 samples, 0.08%)zssp::crypto_impl::openssl::CipherCtx::finalize (46 samples, 0.12%)EVP_DecryptFinal_ex (45 samples, 0.12%)[libcrypto.so.3] (38 samples, 0.10%)[libcrypto.so.3] (37 samples, 0.09%)OSSL_PARAM_get_octet_string (6 samples, 0.02%)[libc.so.6] (17 samples, 0.04%)OSSL_PARAM_locate (26 samples, 0.07%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (333 samples, 0.85%)zssp::crypto_impl::openssl::CipherCtx::set_tag (77 samples, 0.20%)EVP_CIPHER_CTX_ctrl (77 samples, 0.20%)[libcrypto.so.3] (39 samples, 0.10%)OSSL_PARAM_locate_const (4 samples, 0.01%)[libc.so.6] (15 samples, 0.04%)OSSL_PARAM_locate (28 samples, 0.07%)EVP_CIPHER_CTX_set_padding (64 samples, 0.16%)[libcrypto.so.3] (42 samples, 0.11%)[libc.so.6] (25 samples, 0.06%)OSSL_PARAM_locate (48 samples, 0.12%)strcmp@plt (7 samples, 0.02%)EVP_CIPHER_CTX_get_iv_length (81 samples, 0.21%)[libcrypto.so.3] (63 samples, 0.16%)[libc.so.6] (20 samples, 0.05%)OSSL_PARAM_locate (41 samples, 0.10%)EVP_CIPHER_CTX_get_key_length (60 samples, 0.15%)[libcrypto.so.3] (53 samples, 0.14%)pthread_rwlock_rdlock (159 samples, 0.41%)CRYPTO_THREAD_read_lock (163 samples, 0.42%)pthread_rwlock_unlock (79 samples, 0.20%)CRYPTO_THREAD_unlock (86 samples, 0.22%)pthread_rwlock_unlock@plt (5 samples, 0.01%)EVP_CIPHER_up_ref (42 samples, 0.11%)OPENSSL_LH_retrieve (70 samples, 0.18%)[libcrypto.so.3] (57 samples, 0.15%)[libcrypto.so.3] (6 samples, 0.02%)pthread_rwlock_rdlock (43 samples, 0.11%)CRYPTO_THREAD_read_lock (47 samples, 0.12%)pthread_rwlock_unlock (66 samples, 0.17%)CRYPTO_THREAD_unlock (69 samples, 0.18%)CRYPTO_strndup (13 samples, 0.03%)OPENSSL_strcasecmp (38 samples, 0.10%)OPENSSL_LH_retrieve (162 samples, 0.41%)[libcrypto.so.3] (151 samples, 0.39%)[libcrypto.so.3] (36 samples, 0.09%)cfree (12 samples, 0.03%)[libc.so.6] (4 samples, 0.01%)[libcrypto.so.3] (310 samples, 0.79%)pthread_getspecific (7 samples, 0.02%)EVP_CIPHER_fetch (702 samples, 1.80%)E..[libcrypto.so.3] (699 samples, 1.79%)[..[libcrypto.so.3] (690 samples, 1.77%)EVP_CIPHER_free (18 samples, 0.05%)EVP_CIPHER_up_ref (27 samples, 0.07%)[libc.so.6] (10 samples, 0.03%)malloc (9 samples, 0.02%)CRYPTO_zalloc (22 samples, 0.06%)OPENSSL_init_crypto (4 samples, 0.01%)CRYPTO_gcm128_init (155 samples, 0.40%)[libcrypto.so.3] (126 samples, 0.32%)[libcrypto.so.3] (188 samples, 0.48%)[libcrypto.so.3] (32 samples, 0.08%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,141 samples, 2.92%)zs..EVP_CipherInit_ex (1,141 samples, 2.92%)EV..[libcrypto.so.3] (1,141 samples, 2.92%)[l..[libcrypto.so.3] (224 samples, 0.57%)malloc (12 samples, 0.03%)CRYPTO_zalloc (17 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (1,226 samples, 3.14%)<zs..zssp::crypto_impl::openssl::CipherCtx::new (20 samples, 0.05%)core::slice::index::<impl core::ops::index::Index<I> for [T]>::index (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (5 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (5 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::add (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::offset (4 samples, 0.01%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (4 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (5 samples, 0.01%)std::io::impls::<impl std::io::Write for &mut W>::write (232 samples, 0.59%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (232 samples, 0.59%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (230 samples, 0.59%)core::intrinsics::copy_nonoverlapping (228 samples, 0.58%)[libc.so.6] (227 samples, 0.58%)zssp::antireplay::Window<_,_>::update (27 samples, 0.07%)core::sync::atomic::AtomicU64::fetch_max (26 samples, 0.07%)core::sync::atomic::atomic_umax (26 samples, 0.07%)zssp::zeta::receive_payload_in_place (6,190 samples, 15.84%)zssp::zeta::receive_payl..zssp::zssp::Context<Crypto>::receive (8,570 samples, 21.93%)zssp::zssp::Context<Crypto>::receivezssp::zssp::parse_fragment_header (65 samples, 0.17%)core::slice::<impl [T]>::copy_from_slice (4 samples, 0.01%)core::intrinsics::copy_nonoverlapping (4 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (56 samples, 0.14%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (56 samples, 0.14%)std::sys::unix::locks::futex_mutex::Mutex::unlock (55 samples, 0.14%)core::sync::atomic::AtomicU32::swap (52 samples, 0.13%)core::sync::atomic::atomic_swap (52 samples, 0.13%)std::sync::mutex::Mutex<T>::lock (64 samples, 0.16%)std::sys::unix::locks::futex_mutex::Mutex::lock (61 samples, 0.16%)core::sync::atomic::AtomicU32::compare_exchange (55 samples, 0.14%)core::sync::atomic::atomic_compare_exchange (55 samples, 0.14%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (314 samples, 0.80%)zssp::crypto_impl::openssl::CipherCtx::update (188 samples, 0.48%)EVP_EncryptUpdate (183 samples, 0.47%)[libcrypto.so.3] (148 samples, 0.38%)[libcrypto.so.3] (106 samples, 0.27%)[libcrypto.so.3] (96 samples, 0.25%)CRYPTO_gcm128_encrypt (250 samples, 0.64%)[libcrypto.so.3] (143 samples, 0.37%)CRYPTO_gcm128_encrypt_ctr32 (415 samples, 1.06%)[libcrypto.so.3] (319 samples, 0.82%)[libcrypto.so.3] (28 samples, 0.07%)[libcrypto.so.3] (19 samples, 0.05%)CRYPTO_gcm128_setiv (25 samples, 0.06%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)perf_ctx_enable (18 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,317 samples, 8.49%)<zssp::crypt..zssp::crypto_impl::openssl::CipherCtx::update (3,311 samples, 8.47%)zssp::crypto..EVP_EncryptUpdate (3,309 samples, 8.47%)EVP_EncryptU..[libcrypto.so.3] (3,257 samples, 8.33%)[libcrypto.s..[libcrypto.so.3] (3,255 samples, 8.33%)[libcrypto.s..[libcrypto.so.3] (3,235 samples, 8.28%)[libcrypto.s..[libcrypto.so.3] (2,506 samples, 6.41%)[libcryp..[libcrypto.so.3] (2,299 samples, 5.88%)[libcry..asm_sysvec_reschedule_ipi (20 samples, 0.05%)sysvec_reschedule_ipi (20 samples, 0.05%)irqentry_exit (20 samples, 0.05%)irqentry_exit_to_user_mode (20 samples, 0.05%)exit_to_user_mode_prepare (20 samples, 0.05%)exit_to_user_mode_loop (20 samples, 0.05%)schedule (19 samples, 0.05%)__schedule (19 samples, 0.05%)finish_task_switch.isra.0 (19 samples, 0.05%)__perf_event_task_sched_in (19 samples, 0.05%)CRYPTO_clear_free (107 samples, 0.27%)OPENSSL_cleanse (103 samples, 0.26%)EVP_CIPHER_free (30 samples, 0.08%)EVP_CIPHER_CTX_free (206 samples, 0.53%)EVP_CIPHER_CTX_reset (205 samples, 0.52%)cfree (52 samples, 0.13%)[libc.so.6] (11 samples, 0.03%)cfree (11 samples, 0.03%)[libc.so.6] (5 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (220 samples, 0.56%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (220 samples, 0.56%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (220 samples, 0.56%)[libcrypto.so.3] (15 samples, 0.04%)zssp::crypto_impl::openssl::CipherCtx::finalize (47 samples, 0.12%)EVP_EncryptFinal_ex (46 samples, 0.12%)[libcrypto.so.3] (33 samples, 0.08%)[libcrypto.so.3] (33 samples, 0.08%)[libcrypto.so.3] (29 samples, 0.07%)CRYPTO_gcm128_tag (29 samples, 0.07%)CRYPTO_gcm128_finish (22 samples, 0.06%)[libc.so.6] (32 samples, 0.08%)OSSL_PARAM_locate (53 samples, 0.14%)strcmp@plt (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (358 samples, 0.92%)zssp::crypto_impl::openssl::CipherCtx::get_tag (91 samples, 0.23%)EVP_CIPHER_CTX_ctrl (91 samples, 0.23%)[libcrypto.so.3] (68 samples, 0.17%)OSSL_PARAM_set_octet_string (5 samples, 0.01%)[libc.so.6] (12 samples, 0.03%)EVP_CIPHER_CTX_set_padding (64 samples, 0.16%)[libcrypto.so.3] (38 samples, 0.10%)OSSL_PARAM_locate (32 samples, 0.08%)strcmp@plt (4 samples, 0.01%)[libc.so.6] (17 samples, 0.04%)OSSL_PARAM_locate (40 samples, 0.10%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_get_iv_length (79 samples, 0.20%)[libcrypto.so.3] (55 samples, 0.14%)[libc.so.6] (20 samples, 0.05%)OSSL_PARAM_locate (37 samples, 0.09%)EVP_CIPHER_CTX_get_key_length (63 samples, 0.16%)[libcrypto.so.3] (54 samples, 0.14%)pthread_rwlock_rdlock (130 samples, 0.33%)CRYPTO_THREAD_read_lock (136 samples, 0.35%)pthread_rwlock_unlock (73 samples, 0.19%)CRYPTO_THREAD_unlock (82 samples, 0.21%)pthread_rwlock_unlock@plt (5 samples, 0.01%)EVP_CIPHER_up_ref (24 samples, 0.06%)OPENSSL_LH_retrieve (75 samples, 0.19%)[libcrypto.so.3] (56 samples, 0.14%)[libcrypto.so.3] (5 samples, 0.01%)pthread_rwlock_rdlock (38 samples, 0.10%)CRYPTO_THREAD_read_lock (40 samples, 0.10%)pthread_rwlock_unlock (43 samples, 0.11%)CRYPTO_THREAD_unlock (45 samples, 0.12%)OPENSSL_strnlen (5 samples, 0.01%)CRYPTO_strndup (10 samples, 0.03%)malloc (5 samples, 0.01%)OPENSSL_strcasecmp (41 samples, 0.10%)OPENSSL_LH_retrieve (132 samples, 0.34%)[libcrypto.so.3] (118 samples, 0.30%)[libcrypto.so.3] (29 samples, 0.07%)cfree (19 samples, 0.05%)[libc.so.6] (7 samples, 0.02%)[libcrypto.so.3] (264 samples, 0.68%)EVP_CIPHER_fetch (603 samples, 1.54%)[libcrypto.so.3] (600 samples, 1.54%)[libcrypto.so.3] (594 samples, 1.52%)EVP_CIPHER_free (17 samples, 0.04%)EVP_CIPHER_up_ref (20 samples, 0.05%)OBJ_nid2sn (6 samples, 0.02%)malloc (8 samples, 0.02%)CRYPTO_zalloc (14 samples, 0.04%)OPENSSL_init_crypto (5 samples, 0.01%)CRYPTO_gcm128_init (161 samples, 0.41%)[libcrypto.so.3] (132 samples, 0.34%)[libcrypto.so.3] (201 samples, 0.51%)[libcrypto.so.3] (39 samples, 0.10%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,042 samples, 2.67%)zs..EVP_CipherInit_ex (1,042 samples, 2.67%)EV..[libcrypto.so.3] (1,042 samples, 2.67%)[l..[libcrypto.so.3] (232 samples, 0.59%)[libc.so.6] (4 samples, 0.01%)malloc (9 samples, 0.02%)CRYPTO_zalloc (15 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (1,125 samples, 2.88%)<z..zssp::crypto_impl::openssl::CipherCtx::new (16 samples, 0.04%)__rdl_alloc (9 samples, 0.02%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (9 samples, 0.02%)[libc.so.6] (54 samples, 0.14%)[libc.so.6] (925 samples, 2.37%)[l..__entry_text_start (4 samples, 0.01%)__get_user_nocheck_4 (5 samples, 0.01%)futex_q_lock (5 samples, 0.01%)futex_q_unlock (6 samples, 0.02%)futex_wait (24 samples, 0.06%)futex_wait_setup (19 samples, 0.05%)__x64_sys_futex (26 samples, 0.07%)do_futex (26 samples, 0.07%)do_syscall_64 (31 samples, 0.08%)syscall_exit_to_user_mode (4 samples, 0.01%)__lll_lock_wait_private (53 samples, 0.14%)entry_SYSCALL_64_after_hwframe (33 samples, 0.08%)__entry_text_start (4 samples, 0.01%)_raw_spin_lock (4 samples, 0.01%)futex_hash (4 samples, 0.01%)_raw_spin_lock (4 samples, 0.01%)native_queued_spin_lock_slowpath (4 samples, 0.01%)futex_wake_mark (13 samples, 0.03%)preempt_schedule_thunk (5 samples, 0.01%)preempt_schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)__smp_call_single_queue (12 samples, 0.03%)native_send_call_func_single_ipi (9 samples, 0.02%)x2apic_send_IPI (9 samples, 0.02%)native_write_msr (6 samples, 0.02%)llist_add_batch (17 samples, 0.04%)do_futex (136 samples, 0.35%)futex_wake (128 samples, 0.33%)wake_up_q (75 samples, 0.19%)try_to_wake_up (66 samples, 0.17%)ttwu_queue_wakelist (36 samples, 0.09%)__x64_sys_futex (137 samples, 0.35%)do_syscall_64 (141 samples, 0.36%)entry_SYSCALL_64_after_hwframe (144 samples, 0.37%)alloc::vec::Vec<T,A>::with_capacity_in (1,803 samples, 4.61%)alloc..alloc::raw_vec::RawVec<T,A>::with_capacity_in (1,803 samples, 4.61%)alloc..alloc::raw_vec::RawVec<T,A>::allocate_in (1,803 samples, 4.61%)alloc..<alloc::alloc::Global as core::alloc::Allocator>::allocate (1,801 samples, 4.61%)<allo..alloc::alloc::Global::alloc_impl (1,801 samples, 4.61%)alloc..alloc::alloc::alloc (1,801 samples, 4.61%)alloc..malloc (1,789 samples, 4.58%)malloc__lll_lock_wake_private (156 samples, 0.40%)alloc::slice::<impl [T]>::to_vec (2,000 samples, 5.12%)alloc:..alloc::slice::<impl [T]>::to_vec_in (2,000 samples, 5.12%)alloc:..alloc::slice::hack::to_vec (2,000 samples, 5.12%)alloc:..<T as alloc::slice::hack::ConvertVec>::to_vec (2,000 samples, 5.12%)<T as ..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (197 samples, 0.50%)core::intrinsics::copy_nonoverlapping (197 samples, 0.50%)[libc.so.6] (194 samples, 0.50%)core::result::Result<T,E>::is_ok (4 samples, 0.01%)<std::sync::mpmc::select::Token as core::default::Default>::default (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (4 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (66 samples, 0.17%)core::sync::atomic::atomic_compare_exchange_weak (66 samples, 0.17%)std::sync::mpmc::array::Channel<T>::start_send (102 samples, 0.26%)core::sync::atomic::AtomicUsize::load (11 samples, 0.03%)core::sync::atomic::atomic_load (11 samples, 0.03%)core::sync::atomic::AtomicBool::load (6 samples, 0.02%)core::sync::atomic::atomic_load (5 samples, 0.01%)futex_wake_mark (8 samples, 0.02%)__smp_call_single_queue (7 samples, 0.02%)native_send_call_func_single_ipi (6 samples, 0.02%)x2apic_send_IPI (6 samples, 0.02%)native_write_msr (4 samples, 0.01%)llist_add_batch (7 samples, 0.02%)futex_wake (71 samples, 0.18%)wake_up_q (44 samples, 0.11%)try_to_wake_up (43 samples, 0.11%)ttwu_queue_wakelist (24 samples, 0.06%)__x64_sys_futex (75 samples, 0.19%)do_futex (75 samples, 0.19%)entry_SYSCALL_64_after_hwframe (76 samples, 0.19%)do_syscall_64 (76 samples, 0.19%)<core::slice::iter::Iter<T> as core::iter::traits::iterator::Iterator>::position (88 samples, 0.23%)std::sync::mpmc::waker::Waker::try_select::_{{closure}} (88 samples, 0.23%)std::sync::mpmc::context::Context::unpark (84 samples, 0.21%)std::thread::Thread::unpark (84 samples, 0.21%)std::sys_common::thread_parking::futex::Parker::unpark (84 samples, 0.21%)std::sys::unix::futex::futex_wake (81 samples, 0.21%)syscall (80 samples, 0.20%)std::sync::mpmc::waker::Waker::try_select (89 samples, 0.23%)std::sync::mpmc::array::Channel<T>::write (112 samples, 0.29%)std::sync::mpmc::waker::SyncWaker::notify (105 samples, 0.27%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)futex_wait_queue (6 samples, 0.02%)schedule (6 samples, 0.02%)__schedule (6 samples, 0.02%)std::sync::mpmc::context::Context::wait_until (7 samples, 0.02%)std::thread::park (7 samples, 0.02%)std::sys_common::thread_parking::futex::Parker::park (7 samples, 0.02%)std::sys::unix::futex::futex_wait (7 samples, 0.02%)syscall (7 samples, 0.02%)entry_SYSCALL_64_after_hwframe (7 samples, 0.02%)do_syscall_64 (7 samples, 0.02%)__x64_sys_futex (7 samples, 0.02%)do_futex (7 samples, 0.02%)futex_wait (7 samples, 0.02%)std::sync::mpmc::context::Context::with (8 samples, 0.02%)std::thread::local::LocalKey<T>::try_with (8 samples, 0.02%)std::sync::mpmc::context::Context::with::_{{closure}} (8 samples, 0.02%)std::sync::mpmc::context::Context::with::_{{closure}} (8 samples, 0.02%)std::sync::mpmc::array::Channel<T>::send::_{{closure}} (8 samples, 0.02%)core::hint::spin_loop (14 samples, 0.04%)core::core_arch::x86::sse2::_mm_pause (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::send (251 samples, 0.64%)std::sync::mpmc::utils::Backoff::spin_light (15 samples, 0.04%)benchmark::bob_main::_{{closure}} (3,468 samples, 8.87%)benchmark::bo..std::sync::mpsc::SyncSender<T>::send (1,464 samples, 3.75%)std:..std::sync::mpmc::Sender<T>::send (1,462 samples, 3.74%)std:..core::iter::range::<impl core::iter::traits::iterator::Iterator for core::ops::range::Range<A>>::next (6 samples, 0.02%)<core::ops::range::Range<T> as core::iter::range::RangeIteratorImpl>::spec_next (6 samples, 0.02%)core::mem::drop (25 samples, 0.06%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (25 samples, 0.06%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (25 samples, 0.06%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (25 samples, 0.06%)core::sync::atomic::AtomicU32::fetch_sub (24 samples, 0.06%)core::sync::atomic::atomic_sub (24 samples, 0.06%)core::slice::<impl [T]>::copy_from_slice (14 samples, 0.04%)core::intrinsics::copy_nonoverlapping (14 samples, 0.04%)core::sync::atomic::AtomicU32::compare_exchange_weak (20 samples, 0.05%)core::sync::atomic::atomic_compare_exchange_weak (20 samples, 0.05%)std::sync::rwlock::RwLock<T>::read (22 samples, 0.06%)std::sys::unix::locks::futex_rwlock::RwLock::read (22 samples, 0.06%)benchmark::alice_main (18,175 samples, 46.50%)benchmark::alice_mainzssp::zssp::Context<Crypto>::send (8,691 samples, 22.24%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,691 samples, 22.24%)zssp::zeta::send_payloadzssp::zeta::get_counter (14 samples, 0.04%)core::sync::atomic::AtomicU64::fetch_add (11 samples, 0.03%)core::sync::atomic::atomic_add (11 samples, 0.03%)[libc.so.6] (15 samples, 0.04%)arrayvec::arrayvec::ArrayVec<T,_>::clear (4 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (4 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (4 samples, 0.01%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (10 samples, 0.03%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (10 samples, 0.03%)core::sync::atomic::AtomicUsize::fetch_sub (10 samples, 0.03%)core::sync::atomic::atomic_sub (10 samples, 0.03%)core::time::Duration::as_millis (5 samples, 0.01%)_ZN3std4sync4mpmc5array16Channel$LT$T$GT$10start_recv17h7800ca29c64cb868E.llvm.12455019271255371362 (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::read (6 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (65 samples, 0.17%)core::sync::atomic::atomic_compare_exchange_weak (65 samples, 0.17%)core::sync::atomic::AtomicUsize::load (84 samples, 0.21%)core::sync::atomic::atomic_load (84 samples, 0.21%)std::sync::mpmc::array::Channel<T>::start_recv (190 samples, 0.49%)core::sync::atomic::fence (4 samples, 0.01%)core::sync::atomic::AtomicU32::swap (5 samples, 0.01%)core::sync::atomic::atomic_swap (5 samples, 0.01%)[[vdso]] (4 samples, 0.01%)core::option::Option<T>::and_then (5 samples, 0.01%)std::sys::unix::futex::futex_wait::_{{closure}} (5 samples, 0.01%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (5 samples, 0.01%)clock_gettime (5 samples, 0.01%)futex_setup_timer (5 samples, 0.01%)hrtimer_init_sleeper (4 samples, 0.01%)__hrtimer_init (4 samples, 0.01%)futex_unqueue (4 samples, 0.01%)hrtimer_sleeper_start_expires (6 samples, 0.02%)hrtimer_start_range_ns (6 samples, 0.02%)update_curr (5 samples, 0.01%)dequeue_entity (14 samples, 0.04%)update_load_avg (5 samples, 0.01%)dequeue_task (18 samples, 0.05%)dequeue_task_fair (18 samples, 0.05%)finish_task_switch.isra.0 (14 samples, 0.04%)raw_spin_rq_unlock (6 samples, 0.02%)pick_next_task (5 samples, 0.01%)prepare_task_switch (6 samples, 0.02%)__perf_event_task_sched_out (4 samples, 0.01%)psi_group_change (12 samples, 0.03%)record_times (5 samples, 0.01%)psi_task_switch (22 samples, 0.06%)__schedule (72 samples, 0.18%)futex_wait_queue (90 samples, 0.23%)schedule (81 samples, 0.21%)__get_user_nocheck_4 (6 samples, 0.02%)futex_q_lock (7 samples, 0.02%)futex_wait_setup (19 samples, 0.05%)hrtimer_cancel (5 samples, 0.01%)hrtimer_try_to_cancel (5 samples, 0.01%)do_futex (131 samples, 0.34%)futex_wait (130 samples, 0.33%)__x64_sys_futex (143 samples, 0.37%)get_timespec64 (5 samples, 0.01%)exit_to_user_mode_loop (11 samples, 0.03%)__rseq_handle_notify_resume (4 samples, 0.01%)std::sync::mpmc::context::Context::wait_until (178 samples, 0.46%)std::thread::park_timeout (177 samples, 0.45%)std::sys_common::thread_parking::futex::Parker::park_timeout (175 samples, 0.45%)std::sys::unix::futex::futex_wait (170 samples, 0.43%)syscall (164 samples, 0.42%)entry_SYSCALL_64_after_hwframe (159 samples, 0.41%)do_syscall_64 (159 samples, 0.41%)syscall_exit_to_user_mode (15 samples, 0.04%)exit_to_user_mode_prepare (15 samples, 0.04%)core::sync::atomic::AtomicBool::store (8 samples, 0.02%)core::sync::atomic::atomic_store (8 samples, 0.02%)std::sync::mpmc::waker::Waker::register (5 samples, 0.01%)std::sync::mpmc::waker::Waker::register_with_packet (5 samples, 0.01%)alloc::vec::Vec<T,A>::push (5 samples, 0.01%)core::ptr::write (5 samples, 0.01%)std::sync::mpmc::waker::SyncWaker::register (24 samples, 0.06%)std::sync::mutex::Mutex<T>::lock (8 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::lock (8 samples, 0.02%)core::sync::atomic::AtomicU32::compare_exchange (8 samples, 0.02%)core::sync::atomic::atomic_compare_exchange (8 samples, 0.02%)std::sync::mpmc::context::Context::with (207 samples, 0.53%)std::thread::local::LocalKey<T>::try_with (207 samples, 0.53%)std::sync::mpmc::context::Context::with::_{{closure}} (207 samples, 0.53%)std::sync::mpmc::context::Context::with::_{{closure}} (207 samples, 0.53%)std::sync::mpmc::array::Channel<T>::recv::_{{closure}} (207 samples, 0.53%)std::sync::mpmc::Receiver<T>::recv_deadline (431 samples, 1.10%)std::sync::mpmc::array::Channel<T>::recv (431 samples, 1.10%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (5 samples, 0.01%)clock_gettime (5 samples, 0.01%)[[vdso]] (5 samples, 0.01%)std::sync::mpmc::Receiver<T>::recv_timeout (4 samples, 0.01%)[[vdso]] (131 samples, 0.34%)[[vdso]] (88 samples, 0.23%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (146 samples, 0.37%)clock_gettime (140 samples, 0.36%)__vdso_clock_gettime (6 samples, 0.02%)std::sync::mpsc::Receiver<T>::recv_timeout (605 samples, 1.55%)std::sync::mpmc::Receiver<T>::recv_timeout (602 samples, 1.54%)std::time::SystemTime::checked_add (12 samples, 0.03%)std::sys::unix::time::SystemTime::checked_add_duration (12 samples, 0.03%)std::sys::unix::time::Timespec::checked_add_duration (12 samples, 0.03%)core::option::Option<T>::and_then (4 samples, 0.01%)core::cmp::impls::<impl core::cmp::PartialOrd<&B> for &A>::ge (6 samples, 0.02%)core::cmp::PartialOrd::ge (6 samples, 0.02%)<std::sys::unix::time::Timespec as core::cmp::PartialOrd>::partial_cmp (5 samples, 0.01%)std::time::Instant::duration_since (22 samples, 0.06%)std::time::Instant::checked_duration_since (21 samples, 0.05%)std::sys::unix::time::inner::Instant::checked_sub_instant (21 samples, 0.05%)std::sys::unix::time::Timespec::sub_timespec (21 samples, 0.05%)<std::time::Instant as core::ops::arith::Sub>::sub (27 samples, 0.07%)std::time::Instant::elapsed (5 samples, 0.01%)[[vdso]] (148 samples, 0.38%)[[vdso]] (107 samples, 0.27%)std::time::Instant::elapsed (192 samples, 0.49%)std::time::Instant::now (159 samples, 0.41%)std::sys::unix::time::inner::Instant::now (159 samples, 0.41%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (155 samples, 0.40%)clock_gettime (154 samples, 0.39%)__vdso_clock_gettime (4 samples, 0.01%)<T as core::convert::TryInto<U>>::try_into (306 samples, 0.78%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (306 samples, 0.78%)core::result::Result<T,E>::map (306 samples, 0.78%)<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (13 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (61 samples, 0.16%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (61 samples, 0.16%)std::sys::unix::locks::futex_mutex::Mutex::unlock (60 samples, 0.15%)core::sync::atomic::AtomicU32::swap (56 samples, 0.14%)core::sync::atomic::atomic_swap (56 samples, 0.14%)std::sync::mutex::MutexGuard<T>::new (6 samples, 0.02%)std::sync::poison::Flag::guard (6 samples, 0.02%)std::thread::panicking (6 samples, 0.02%)std::panicking::panicking (6 samples, 0.02%)std::panicking::panic_count::count_is_zero (6 samples, 0.02%)std::sync::mutex::Mutex<T>::lock (77 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (71 samples, 0.18%)core::sync::atomic::AtomicU32::compare_exchange (69 samples, 0.18%)core::sync::atomic::atomic_compare_exchange (69 samples, 0.18%)EVP_CIPHER_CTX_get_block_size (5 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (344 samples, 0.88%)zssp::crypto_impl::openssl::CipherCtx::update (200 samples, 0.51%)EVP_DecryptUpdate (196 samples, 0.50%)[libcrypto.so.3] (166 samples, 0.42%)[libcrypto.so.3] (119 samples, 0.30%)[libcrypto.so.3] (113 samples, 0.29%)__rust_probestack (9 samples, 0.02%)core::cmp::Ord::max (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)core::num::nonzero::NonZeroU32::new (6 samples, 0.02%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)alloc::sync::Weak<T>::upgrade::_{{closure}} (7 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (43 samples, 0.11%)core::sync::atomic::atomic_compare_exchange_weak (43 samples, 0.11%)alloc::sync::Weak<T>::upgrade (63 samples, 0.16%)core::sync::atomic::AtomicUsize::fetch_update (61 samples, 0.16%)core::sync::atomic::AtomicUsize::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)core::option::Option<T>::map (71 samples, 0.18%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (71 samples, 0.18%)zssp::zssp::Context<Crypto>::receive (8 samples, 0.02%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (55 samples, 0.14%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (55 samples, 0.14%)core::sync::atomic::AtomicUsize::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)__rdl_dealloc (4 samples, 0.01%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::dealloc (4 samples, 0.01%)[libc.so.6] (73 samples, 0.19%)__entry_text_start (13 samples, 0.03%)futex_unqueue (8 samples, 0.02%)update_curr (16 samples, 0.04%)cpuacct_charge (8 samples, 0.02%)dequeue_entity (23 samples, 0.06%)update_load_avg (4 samples, 0.01%)dequeue_task_fair (28 samples, 0.07%)dequeue_task (29 samples, 0.07%)__perf_event_task_sched_in (8 samples, 0.02%)perf_ctx_enable (8 samples, 0.02%)x86_pmu_enable (4 samples, 0.01%)intel_pmu_enable_all (4 samples, 0.01%)native_write_msr (4 samples, 0.01%)finish_task_switch.isra.0 (19 samples, 0.05%)raw_spin_rq_unlock (5 samples, 0.01%)pick_next_task_fair (5 samples, 0.01%)pick_next_task (10 samples, 0.03%)put_prev_task_fair (4 samples, 0.01%)prepare_task_switch (16 samples, 0.04%)__perf_event_task_sched_out (5 samples, 0.01%)_raw_spin_lock (4 samples, 0.01%)psi_group_change (22 samples, 0.06%)psi_task_switch (35 samples, 0.09%)futex_wait_queue (143 samples, 0.37%)schedule (136 samples, 0.35%)__schedule (130 samples, 0.33%)__get_user_nocheck_4 (8 samples, 0.02%)_raw_spin_lock (6 samples, 0.02%)futex_q_lock (8 samples, 0.02%)futex_wait (192 samples, 0.49%)futex_wait_setup (30 samples, 0.08%)__x64_sys_futex (201 samples, 0.51%)do_futex (200 samples, 0.51%)__rseq_handle_notify_resume (7 samples, 0.02%)rseq_update_cpu_node_id (5 samples, 0.01%)exit_to_user_mode_loop (12 samples, 0.03%)exit_to_user_mode_prepare (19 samples, 0.05%)do_syscall_64 (227 samples, 0.58%)syscall_exit_to_user_mode (22 samples, 0.06%)entry_SYSCALL_64_after_hwframe (233 samples, 0.60%)__lll_lock_wait_private (283 samples, 0.72%)[libc.so.6] (759 samples, 1.94%)[..__entry_text_start (8 samples, 0.02%)futex_hash (7 samples, 0.02%)_raw_spin_lock (12 samples, 0.03%)native_queued_spin_lock_slowpath (12 samples, 0.03%)__x64_sys_futex (65 samples, 0.17%)do_futex (61 samples, 0.16%)futex_wake (51 samples, 0.13%)entry_SYSCALL_64_after_hwframe (78 samples, 0.20%)do_syscall_64 (77 samples, 0.20%)syscall_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (6 samples, 0.02%)alloc::alloc::dealloc (903 samples, 2.31%)a..cfree (896 samples, 2.29%)c..__lll_lock_wake_private (96 samples, 0.25%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (906 samples, 2.32%)<..core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (914 samples, 2.34%)c..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (914 samples, 2.34%)<..arrayvec::arrayvec::ArrayVec<T,_>::clear (914 samples, 2.34%)a..arrayvec::arrayvec_impl::ArrayVecImpl::clear (914 samples, 2.34%)a..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (914 samples, 2.34%)a..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (913 samples, 2.34%)c..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (911 samples, 2.33%)c..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (911 samples, 2.33%)c..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (911 samples, 2.33%)<..alloc::raw_vec::RawVec<T,A>::current_memory (5 samples, 0.01%)std::sync::poison::Flag::done (9 samples, 0.02%)std::thread::panicking (9 samples, 0.02%)std::panicking::panicking (9 samples, 0.02%)std::panicking::panic_count::count_is_zero (9 samples, 0.02%)core::sync::atomic::AtomicUsize::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (71 samples, 0.18%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (71 samples, 0.18%)std::sys::unix::locks::futex_mutex::Mutex::unlock (62 samples, 0.16%)core::sync::atomic::AtomicU32::swap (61 samples, 0.16%)core::sync::atomic::atomic_swap (61 samples, 0.16%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (58 samples, 0.15%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (58 samples, 0.15%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (58 samples, 0.15%)core::sync::atomic::AtomicU32::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (48 samples, 0.12%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (48 samples, 0.12%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (48 samples, 0.12%)core::sync::atomic::AtomicU32::fetch_sub (44 samples, 0.11%)core::sync::atomic::atomic_sub (44 samples, 0.11%)core::num::<impl u64>::rotate_left (6 samples, 0.02%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (11 samples, 0.03%)core::num::<impl u64>::rotate_left (11 samples, 0.03%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (51 samples, 0.13%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (30 samples, 0.08%)core::num::<impl u64>::wrapping_add (12 samples, 0.03%)<std::collections::hash::map::RandomState as core::hash::BuildHasher>::build_hasher (5 samples, 0.01%)hashbrown::map::make_hash (76 samples, 0.19%)core::hash::BuildHasher::hash_one (76 samples, 0.19%)core::hash::impls::<impl core::hash::Hash for &T>::hash (17 samples, 0.04%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (17 samples, 0.04%)core::hash::impls::<impl core::hash::Hash for u32>::hash (17 samples, 0.04%)core::hash::Hasher::write_u32 (17 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (17 samples, 0.04%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (17 samples, 0.04%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (17 samples, 0.04%)core::hash::sip::u8to64_le (7 samples, 0.02%)<hashbrown::raw::bitmask::BitMaskIter as core::iter::traits::iterator::Iterator>::next (9 samples, 0.02%)hashbrown::raw::bitmask::BitMask::lowest_set_bit (5 samples, 0.01%)hashbrown::map::equivalent_key::_{{closure}} (7 samples, 0.02%)<core::num::nonzero::NonZeroU32 as core::cmp::PartialEq>::eq (7 samples, 0.02%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (11 samples, 0.03%)hashbrown::raw::Bucket<T>::as_ref (4 samples, 0.01%)hashbrown::raw::Bucket<T>::as_ptr (4 samples, 0.01%)core::ptr::mut_ptr::<impl *mut T>::sub (4 samples, 0.01%)core::ptr::mut_ptr::<impl *mut T>::offset (4 samples, 0.01%)hashbrown::raw::h2 (10 samples, 0.03%)hashbrown::map::HashMap<K,V,S,A>::get_inner (119 samples, 0.30%)hashbrown::raw::RawTable<T,A>::get (43 samples, 0.11%)hashbrown::raw::RawTable<T,A>::find (43 samples, 0.11%)hashbrown::raw::RawTableInner<A>::find_inner (43 samples, 0.11%)hashbrown::raw::sse2::Group::match_byte (4 samples, 0.01%)core::core_arch::x86::sse2::_mm_movemask_epi8 (4 samples, 0.01%)std::collections::hash::map::HashMap<K,V,S>::get (122 samples, 0.31%)hashbrown::map::HashMap<K,V,S,A>::get (122 samples, 0.31%)std::sync::mutex::MutexGuard<T>::new (8 samples, 0.02%)std::sync::poison::Flag::guard (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (47 samples, 0.12%)std::sys::unix::locks::futex_mutex::Mutex::lock (37 samples, 0.09%)core::sync::atomic::AtomicU32::compare_exchange (37 samples, 0.09%)core::sync::atomic::atomic_compare_exchange (37 samples, 0.09%)core::sync::atomic::AtomicU32::compare_exchange_weak (108 samples, 0.28%)core::sync::atomic::atomic_compare_exchange_weak (108 samples, 0.28%)std::sync::rwlock::RwLock<T>::read (129 samples, 0.33%)std::sys::unix::locks::futex_rwlock::RwLock::read (129 samples, 0.33%)std::sys::unix::locks::futex_rwlock::is_read_lockable (7 samples, 0.02%)zssp::antireplay::Window<_,_>::check (11 samples, 0.03%)core::sync::atomic::AtomicU64::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (13 samples, 0.03%)core::ptr::write (12 samples, 0.03%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init_read (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::read (4 samples, 0.01%)core::ptr::read (4 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (4 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::write (4 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (5 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (62 samples, 0.16%)<T as core::convert::TryInto<U>>::try_into (26 samples, 0.07%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (26 samples, 0.07%)core::result::Result<T,E>::map (26 samples, 0.07%)zssp::zeta::from_nonce (31 samples, 0.08%)core::num::<impl u64>::from_be_bytes (4 samples, 0.01%)core::num::<impl u64>::from_be (4 samples, 0.01%)core::num::<impl u64>::swap_bytes (4 samples, 0.01%)<core::slice::iter::IterMut<T> as core::iter::traits::iterator::Iterator>::next (9 samples, 0.02%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (55 samples, 0.14%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (55 samples, 0.14%)core::sync::atomic::AtomicU32::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)[libcrypto.so.3] (103 samples, 0.26%)CRYPTO_gcm128_decrypt (234 samples, 0.60%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)[libcrypto.so.3] (17 samples, 0.04%)CRYPTO_gcm128_decrypt_ctr32 (452 samples, 1.16%)[libcrypto.so.3] (355 samples, 0.91%)CRYPTO_gcm128_setiv (15 samples, 0.04%)[libcrypto.so.3] (14 samples, 0.04%)asm_sysvec_apic_timer_interrupt (5 samples, 0.01%)sysvec_apic_timer_interrupt (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)__perf_event_task_sched_in (9 samples, 0.02%)perf_ctx_enable (9 samples, 0.02%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,297 samples, 10.99%)<zssp::crypto_im..zssp::crypto_impl::openssl::CipherCtx::update (4,297 samples, 10.99%)zssp::crypto_imp..EVP_DecryptUpdate (4,294 samples, 10.99%)EVP_DecryptUpdate[libcrypto.so.3] (4,195 samples, 10.73%)[libcrypto.so.3][libcrypto.so.3] (4,187 samples, 10.71%)[libcrypto.so.3][libcrypto.so.3] (4,169 samples, 10.67%)[libcrypto.so.3][libcrypto.so.3] (3,429 samples, 8.77%)[libcrypto.s..[libcrypto.so.3] (3,280 samples, 8.39%)[libcrypto.s..asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)irqentry_exit_to_user_mode (10 samples, 0.03%)exit_to_user_mode_prepare (10 samples, 0.03%)exit_to_user_mode_loop (10 samples, 0.03%)schedule (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)CRYPTO_clear_free (106 samples, 0.27%)OPENSSL_cleanse (103 samples, 0.26%)EVP_CIPHER_free (44 samples, 0.11%)EVP_CIPHER_CTX_free (199 samples, 0.51%)EVP_CIPHER_CTX_reset (198 samples, 0.51%)cfree (37 samples, 0.09%)[libc.so.6] (5 samples, 0.01%)cfree (9 samples, 0.02%)[libc.so.6] (4 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (212 samples, 0.54%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (212 samples, 0.54%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (212 samples, 0.54%)[libcrypto.so.3] (26 samples, 0.07%)[libcrypto.so.3] (26 samples, 0.07%)[libcrypto.so.3] (26 samples, 0.07%)CRYPTO_gcm128_finish (24 samples, 0.06%)[libcrypto.so.3] (18 samples, 0.05%)zssp::crypto_impl::openssl::CipherCtx::finalize (36 samples, 0.09%)EVP_DecryptFinal_ex (36 samples, 0.09%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)OSSL_PARAM_get_octet_string (7 samples, 0.02%)[libcrypto.so.3] (5 samples, 0.01%)[libc.so.6] (12 samples, 0.03%)OSSL_PARAM_locate (30 samples, 0.08%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (336 samples, 0.86%)zssp::crypto_impl::openssl::CipherCtx::set_tag (88 samples, 0.23%)EVP_CIPHER_CTX_ctrl (88 samples, 0.23%)[libcrypto.so.3] (49 samples, 0.13%)[libc.so.6] (27 samples, 0.07%)OSSL_PARAM_locate (38 samples, 0.10%)EVP_CIPHER_CTX_set_padding (90 samples, 0.23%)[libcrypto.so.3] (53 samples, 0.14%)[libc.so.6] (24 samples, 0.06%)OSSL_PARAM_locate (48 samples, 0.12%)EVP_CIPHER_CTX_get_iv_length (82 samples, 0.21%)[libcrypto.so.3] (60 samples, 0.15%)[libc.so.6] (18 samples, 0.05%)OSSL_PARAM_locate (34 samples, 0.09%)EVP_CIPHER_CTX_get_key_length (51 samples, 0.13%)[libcrypto.so.3] (42 samples, 0.11%)CRYPTO_THREAD_read_lock (155 samples, 0.40%)pthread_rwlock_rdlock (151 samples, 0.39%)pthread_rwlock_unlock (88 samples, 0.23%)CRYPTO_THREAD_unlock (91 samples, 0.23%)EVP_CIPHER_up_ref (29 samples, 0.07%)OPENSSL_LH_retrieve (59 samples, 0.15%)[libcrypto.so.3] (51 samples, 0.13%)pthread_rwlock_rdlock (40 samples, 0.10%)CRYPTO_THREAD_read_lock (43 samples, 0.11%)pthread_rwlock_unlock (49 samples, 0.13%)CRYPTO_THREAD_unlock (54 samples, 0.14%)OPENSSL_strnlen (7 samples, 0.02%)CRYPTO_strndup (15 samples, 0.04%)OPENSSL_strcasecmp (25 samples, 0.06%)OPENSSL_LH_retrieve (150 samples, 0.38%)[libcrypto.so.3] (142 samples, 0.36%)[libcrypto.so.3] (42 samples, 0.11%)[libcrypto.so.3] (298 samples, 0.76%)cfree (23 samples, 0.06%)[libc.so.6] (6 samples, 0.02%)[libcrypto.so.3] (652 samples, 1.67%)EVP_CIPHER_fetch (660 samples, 1.69%)[libcrypto.so.3] (658 samples, 1.68%)EVP_CIPHER_free (15 samples, 0.04%)EVP_CIPHER_up_ref (14 samples, 0.04%)CRYPTO_malloc (4 samples, 0.01%)[libc.so.6] (8 samples, 0.02%)malloc (7 samples, 0.02%)CRYPTO_zalloc (21 samples, 0.05%)CRYPTO_gcm128_init (149 samples, 0.38%)[libcrypto.so.3] (122 samples, 0.31%)[libcrypto.so.3] (188 samples, 0.48%)[libcrypto.so.3] (34 samples, 0.09%)EVP_CipherInit_ex (1,072 samples, 2.74%)EV..[libcrypto.so.3] (1,072 samples, 2.74%)[l..[libcrypto.so.3] (221 samples, 0.57%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,074 samples, 2.75%)zs..[libc.so.6] (4 samples, 0.01%)malloc (15 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (1,188 samples, 3.04%)<zs..zssp::crypto_impl::openssl::CipherCtx::new (22 samples, 0.06%)CRYPTO_zalloc (22 samples, 0.06%)core::slice::index::<impl core::ops::index::Index<I> for [T]>::index (5 samples, 0.01%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index (5 samples, 0.01%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::add (4 samples, 0.01%)core::ptr::const_ptr::<impl *const T>::offset (4 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (6 samples, 0.02%)std::io::impls::<impl std::io::Write for &mut W>::write (183 samples, 0.47%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (183 samples, 0.47%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (181 samples, 0.46%)core::intrinsics::copy_nonoverlapping (181 samples, 0.46%)[libc.so.6] (181 samples, 0.46%)zssp::zeta::receive_payload_in_place (6,109 samples, 15.63%)zssp::zeta::receive_payl..zssp::antireplay::Window<_,_>::update (13 samples, 0.03%)core::sync::atomic::AtomicU64::fetch_max (13 samples, 0.03%)core::sync::atomic::atomic_umax (13 samples, 0.03%)zssp::zssp::Context<Crypto>::receive (8,570 samples, 21.93%)zssp::zssp::Context<Crypto>::receivezssp::zssp::parse_fragment_header (78 samples, 0.20%)core::slice::<impl [T]>::copy_from_slice (6 samples, 0.02%)core::intrinsics::copy_nonoverlapping (6 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (60 samples, 0.15%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (60 samples, 0.15%)std::sys::unix::locks::futex_mutex::Mutex::unlock (58 samples, 0.15%)core::sync::atomic::AtomicU32::swap (53 samples, 0.14%)core::sync::atomic::atomic_swap (53 samples, 0.14%)std::sync::mutex::MutexGuard<T>::new (4 samples, 0.01%)std::sync::poison::Flag::guard (4 samples, 0.01%)std::thread::panicking (4 samples, 0.01%)std::panicking::panicking (4 samples, 0.01%)std::panicking::panic_count::count_is_zero (4 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (90 samples, 0.23%)std::sys::unix::locks::futex_mutex::Mutex::lock (86 samples, 0.22%)core::sync::atomic::AtomicU32::compare_exchange (80 samples, 0.20%)core::sync::atomic::atomic_compare_exchange (80 samples, 0.20%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (337 samples, 0.86%)zssp::crypto_impl::openssl::CipherCtx::update (184 samples, 0.47%)EVP_EncryptUpdate (183 samples, 0.47%)[libcrypto.so.3] (160 samples, 0.41%)[libcrypto.so.3] (121 samples, 0.31%)[libcrypto.so.3] (116 samples, 0.30%)CRYPTO_gcm128_encrypt (243 samples, 0.62%)[libcrypto.so.3] (137 samples, 0.35%)[libcrypto.so.3] (326 samples, 0.83%)[libcrypto.so.3] (21 samples, 0.05%)CRYPTO_gcm128_encrypt_ctr32 (442 samples, 1.13%)CRYPTO_gcm128_setiv (16 samples, 0.04%)[libcrypto.so.3] (15 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,402 samples, 8.70%)<zssp::crypt..zssp::crypto_impl::openssl::CipherCtx::update (3,400 samples, 8.70%)zssp::crypto..EVP_EncryptUpdate (3,399 samples, 8.70%)EVP_EncryptU..[libcrypto.so.3] (3,327 samples, 8.51%)[libcrypto.s..[libcrypto.so.3] (3,322 samples, 8.50%)[libcrypto.s..[libcrypto.so.3] (3,300 samples, 8.44%)[libcrypto.s..[libcrypto.so.3] (2,562 samples, 6.56%)[libcrypt..[libcrypto.so.3] (2,334 samples, 5.97%)[libcryp..CRYPTO_clear_free (96 samples, 0.25%)OPENSSL_cleanse (96 samples, 0.25%)EVP_CIPHER_free (43 samples, 0.11%)cfree (23 samples, 0.06%)[libc.so.6] (5 samples, 0.01%)EVP_CIPHER_CTX_free (185 samples, 0.47%)EVP_CIPHER_CTX_reset (185 samples, 0.47%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (191 samples, 0.49%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::CipherCtx> (191 samples, 0.49%)<zssp::crypto_impl::openssl::CipherCtx as core::ops::drop::Drop>::drop (191 samples, 0.49%)cfree (5 samples, 0.01%)[libc.so.6] (4 samples, 0.01%)zssp::crypto_impl::openssl::CipherCtx::finalize (69 samples, 0.18%)EVP_EncryptFinal_ex (67 samples, 0.17%)[libcrypto.so.3] (51 samples, 0.13%)[libcrypto.so.3] (50 samples, 0.13%)[libcrypto.so.3] (45 samples, 0.12%)CRYPTO_gcm128_tag (45 samples, 0.12%)CRYPTO_gcm128_finish (37 samples, 0.09%)[libcrypto.so.3] (22 samples, 0.06%)[libc.so.6] (15 samples, 0.04%)OSSL_PARAM_locate (39 samples, 0.10%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (345 samples, 0.88%)zssp::crypto_impl::openssl::CipherCtx::get_tag (84 samples, 0.21%)EVP_CIPHER_CTX_ctrl (83 samples, 0.21%)[libcrypto.so.3] (51 samples, 0.13%)[libc.so.6] (25 samples, 0.06%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)OSSL_PARAM_locate (41 samples, 0.10%)strcmp@plt (4 samples, 0.01%)[libcrypto.so.3] (49 samples, 0.13%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (83 samples, 0.21%)asm_sysvec_reschedule_ipi (6 samples, 0.02%)sysvec_reschedule_ipi (6 samples, 0.02%)irqentry_exit (6 samples, 0.02%)irqentry_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (6 samples, 0.02%)exit_to_user_mode_loop (6 samples, 0.02%)schedule (6 samples, 0.02%)__schedule (6 samples, 0.02%)finish_task_switch.isra.0 (6 samples, 0.02%)[libc.so.6] (21 samples, 0.05%)OSSL_PARAM_locate (41 samples, 0.10%)EVP_CIPHER_CTX_get_iv_length (82 samples, 0.21%)[libcrypto.so.3] (54 samples, 0.14%)[libc.so.6] (9 samples, 0.02%)OSSL_PARAM_locate (26 samples, 0.07%)EVP_CIPHER_CTX_get_key_length (48 samples, 0.12%)[libcrypto.so.3] (40 samples, 0.10%)CRYPTO_THREAD_read_lock (116 samples, 0.30%)pthread_rwlock_rdlock (112 samples, 0.29%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)pthread_rwlock_unlock (95 samples, 0.24%)asm_sysvec_reschedule_ipi (4 samples, 0.01%)sysvec_reschedule_ipi (4 samples, 0.01%)irqentry_exit (4 samples, 0.01%)irqentry_exit_to_user_mode (4 samples, 0.01%)exit_to_user_mode_prepare (4 samples, 0.01%)exit_to_user_mode_loop (4 samples, 0.01%)schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_pmu_nop_void (4 samples, 0.01%)CRYPTO_THREAD_unlock (101 samples, 0.26%)EVP_CIPHER_up_ref (39 samples, 0.10%)OPENSSL_LH_retrieve (56 samples, 0.14%)[libcrypto.so.3] (48 samples, 0.12%)[libcrypto.so.3] (6 samples, 0.02%)pthread_rwlock_rdlock (20 samples, 0.05%)CRYPTO_THREAD_read_lock (24 samples, 0.06%)pthread_rwlock_unlock (51 samples, 0.13%)CRYPTO_THREAD_unlock (54 samples, 0.14%)CRYPTO_strndup (10 samples, 0.03%)malloc (6 samples, 0.02%)OPENSSL_strcasecmp (44 samples, 0.11%)OPENSSL_LH_retrieve (173 samples, 0.44%)[libcrypto.so.3] (157 samples, 0.40%)[libcrypto.so.3] (53 samples, 0.14%)cfree (17 samples, 0.04%)[libc.so.6] (6 samples, 0.02%)[libcrypto.so.3] (290 samples, 0.74%)EVP_CIPHER_fetch (622 samples, 1.59%)[libcrypto.so.3] (620 samples, 1.59%)[libcrypto.so.3] (612 samples, 1.57%)EVP_CIPHER_free (14 samples, 0.04%)EVP_CIPHER_up_ref (18 samples, 0.05%)CRYPTO_malloc (4 samples, 0.01%)[libc.so.6] (9 samples, 0.02%)malloc (8 samples, 0.02%)CRYPTO_zalloc (24 samples, 0.06%)OPENSSL_init_crypto (4 samples, 0.01%)CRYPTO_gcm128_init (157 samples, 0.40%)[libcrypto.so.3] (141 samples, 0.36%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)EVP_CipherInit_ex (1,051 samples, 2.69%)EV..[libcrypto.so.3] (1,051 samples, 2.69%)[l..[libcrypto.so.3] (235 samples, 0.60%)[libcrypto.so.3] (202 samples, 0.52%)[libcrypto.so.3] (43 samples, 0.11%)asm_sysvec_reschedule_ipi (6 samples, 0.02%)sysvec_reschedule_ipi (6 samples, 0.02%)irqentry_exit (6 samples, 0.02%)irqentry_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (6 samples, 0.02%)exit_to_user_mode_loop (6 samples, 0.02%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (1,054 samples, 2.70%)zs..[libc.so.6] (4 samples, 0.01%)malloc (7 samples, 0.02%)CRYPTO_zalloc (13 samples, 0.03%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (1,154 samples, 2.95%)<zs..zssp::crypto_impl::openssl::CipherCtx::new (14 samples, 0.04%)__rdl_alloc (6 samples, 0.02%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (6 samples, 0.02%)[libc.so.6] (49 samples, 0.13%)[libc.so.6] (850 samples, 2.17%)[..asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)__get_user_nocheck_4 (8 samples, 0.02%)futex_q_lock (13 samples, 0.03%)futex_q_unlock (5 samples, 0.01%)__x64_sys_futex (35 samples, 0.09%)do_futex (33 samples, 0.08%)futex_wait (32 samples, 0.08%)futex_wait_setup (28 samples, 0.07%)entry_SYSCALL_64_after_hwframe (37 samples, 0.09%)do_syscall_64 (37 samples, 0.09%)__lll_lock_wait_private (55 samples, 0.14%)futex_wake_mark (11 samples, 0.03%)wake_q_add_safe (4 samples, 0.01%)call_function_single_prep_ipi (4 samples, 0.01%)__smp_call_single_queue (17 samples, 0.04%)native_send_call_func_single_ipi (13 samples, 0.03%)x2apic_send_IPI (12 samples, 0.03%)native_write_msr (10 samples, 0.03%)llist_add_batch (10 samples, 0.03%)futex_wake (123 samples, 0.31%)wake_up_q (63 samples, 0.16%)try_to_wake_up (58 samples, 0.15%)ttwu_queue_wakelist (38 samples, 0.10%)__x64_sys_futex (133 samples, 0.34%)do_futex (132 samples, 0.34%)entry_SYSCALL_64_after_hwframe (145 samples, 0.37%)do_syscall_64 (144 samples, 0.37%)syscall_exit_to_user_mode (7 samples, 0.02%)exit_to_user_mode_prepare (5 samples, 0.01%)alloc::vec::Vec<T,A>::with_capacity_in (1,653 samples, 4.23%)alloc..alloc::raw_vec::RawVec<T,A>::with_capacity_in (1,653 samples, 4.23%)alloc..alloc::raw_vec::RawVec<T,A>::allocate_in (1,653 samples, 4.23%)alloc..<alloc::alloc::Global as core::alloc::Allocator>::allocate (1,651 samples, 4.22%)<allo..alloc::alloc::Global::alloc_impl (1,651 samples, 4.22%)alloc..alloc::alloc::alloc (1,651 samples, 4.22%)alloc..malloc (1,644 samples, 4.21%)malloc__lll_lock_wake_private (152 samples, 0.39%)alloc::slice::<impl [T]>::to_vec (1,857 samples, 4.75%)alloc:..alloc::slice::<impl [T]>::to_vec_in (1,857 samples, 4.75%)alloc:..alloc::slice::hack::to_vec (1,857 samples, 4.75%)alloc:..<T as alloc::slice::hack::ConvertVec>::to_vec (1,857 samples, 4.75%)<T as ..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (204 samples, 0.52%)core::intrinsics::copy_nonoverlapping (204 samples, 0.52%)[libc.so.6] (203 samples, 0.52%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (100 samples, 0.26%)core::sync::atomic::atomic_compare_exchange_weak (100 samples, 0.26%)std::sync::mpmc::array::Channel<T>::start_send (155 samples, 0.40%)core::sync::atomic::AtomicUsize::load (32 samples, 0.08%)core::sync::atomic::atomic_load (32 samples, 0.08%)core::ptr::mut_ptr::<impl *mut T>::write (4 samples, 0.01%)core::ptr::write (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (185 samples, 0.47%)std::sync::mpmc::array::Channel<T>::write (13 samples, 0.03%)std::sync::mpmc::waker::SyncWaker::notify (7 samples, 0.02%)core::sync::atomic::AtomicBool::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)benchmark::bob_main::_{{closure}} (3,332 samples, 8.53%)benchmark::b..std::sync::mpsc::SyncSender<T>::send (1,474 samples, 3.77%)std:..std::sync::mpmc::Sender<T>::send (1,473 samples, 3.77%)std:..core::mem::drop (27 samples, 0.07%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (27 samples, 0.07%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (27 samples, 0.07%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (27 samples, 0.07%)core::sync::atomic::AtomicU32::fetch_sub (27 samples, 0.07%)core::sync::atomic::atomic_sub (27 samples, 0.07%)core::slice::<impl [T]>::copy_from_slice (7 samples, 0.02%)core::intrinsics::copy_nonoverlapping (7 samples, 0.02%)core::sync::atomic::AtomicU32::compare_exchange_weak (35 samples, 0.09%)core::sync::atomic::atomic_compare_exchange_weak (35 samples, 0.09%)std::sync::rwlock::RwLock<T>::read (36 samples, 0.09%)std::sys::unix::locks::futex_rwlock::RwLock::read (36 samples, 0.09%)benchmark::bob_main (18,132 samples, 46.39%)benchmark::bob_mainzssp::zssp::Context<Crypto>::send (8,692 samples, 22.24%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,691 samples, 22.24%)zssp::zeta::send_payloadzssp::zeta::get_counter (12 samples, 0.03%)core::sync::atomic::AtomicU64::fetch_add (11 samples, 0.03%)core::sync::atomic::atomic_add (11 samples, 0.03%)cfree (19 samples, 0.05%)clock_gettime (12 samples, 0.03%)core::hash::BuildHasher::hash_one (20 samples, 0.05%)core::hash::impls::<impl core::hash::Hash for &T>::hash (12 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (4 samples, 0.01%)core::result::Result<T,E>::unwrap (7 samples, 0.02%)pthread_rwlock_rdlock (21 samples, 0.05%)pthread_rwlock_unlock (15 samples, 0.04%)std::sync::mpmc::Receiver<T>::recv_timeout (12 samples, 0.03%)std::sync::mpmc::Sender<T>::send (15 samples, 0.04%)<std::sync::mpmc::select::Token as core::default::Default>::default (7 samples, 0.02%)std::sync::mpmc::array::Channel<T>::recv (24 samples, 0.06%)std::sync::mpmc::array::Channel<T>::send (11 samples, 0.03%)std::sync::mpmc::array::Channel<T>::start_recv (12 samples, 0.03%)std::sync::mpmc::utils::Backoff::new (9 samples, 0.02%)std::sync::mpmc::waker::SyncWaker::notify (19 samples, 0.05%)core::sync::atomic::AtomicBool::load (4 samples, 0.01%)core::sync::atomic::atomic_load (4 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (6 samples, 0.02%)std::sys::unix::time::Timespec::sub_timespec (6 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (15 samples, 0.04%)std::time::SystemTime::checked_add (5 samples, 0.01%)zssp::fragged::Fragged<Fragment,_>::assemble (21 samples, 0.05%)zssp::zeta::from_nonce (5 samples, 0.01%)alloc::vec::Vec<T,A>::with_capacity_in (14 samples, 0.04%)alloc::raw_vec::RawVec<T,A>::with_capacity_in (14 samples, 0.04%)alloc::raw_vec::RawVec<T,A>::allocate_in (14 samples, 0.04%)<alloc::alloc::Global as core::alloc::Allocator>::allocate (14 samples, 0.04%)alloc::alloc::Global::alloc_impl (14 samples, 0.04%)alloc::alloc::alloc (14 samples, 0.04%)alloc::slice::<impl [T]>::to_vec (20 samples, 0.05%)alloc::slice::<impl [T]>::to_vec_in (20 samples, 0.05%)alloc::slice::hack::to_vec (20 samples, 0.05%)<T as alloc::slice::hack::ConvertVec>::to_vec (20 samples, 0.05%)core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (6 samples, 0.02%)core::intrinsics::copy_nonoverlapping (6 samples, 0.02%)zssp::zeta::send_payload (50 samples, 0.13%)benchmark::bob_main::_{{closure}} (29 samples, 0.07%)std::sync::mpsc::SyncSender<T>::send (9 samples, 0.02%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (7 samples, 0.02%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (7 samples, 0.02%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (7 samples, 0.02%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (7 samples, 0.02%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (7 samples, 0.02%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (7 samples, 0.02%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (7 samples, 0.02%)alloc::alloc::dealloc (7 samples, 0.02%)cfree (5 samples, 0.01%)__lll_lock_wake_private (5 samples, 0.01%)__entry_text_start (5 samples, 0.01%)getrandom::imp::getrandom_inner (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (70 samples, 0.18%)std::sync::mpmc::array::Channel<T>::start_recv (13 samples, 0.03%)[unknown] (37,550 samples, 96.08%)[unknown]zssp::zssp::parse_fragment_header (4 samples, 0.01%)EVP_DecryptUpdate (31 samples, 0.08%)__bss_start (36 samples, 0.09%)[libcrypto.so.3] (5 samples, 0.01%)_raw_spin_unlock (4 samples, 0.01%)preempt_schedule_thunk (4 samples, 0.01%)preempt_schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)entry_SYSCALL_64_after_hwframe (9 samples, 0.02%)do_syscall_64 (7 samples, 0.02%)__x64_sys_exit_group (6 samples, 0.02%)do_group_exit (6 samples, 0.02%)do_exit (6 samples, 0.02%)exit_mm (6 samples, 0.02%)mmput (6 samples, 0.02%)__mmput (6 samples, 0.02%)exit_mmap (6 samples, 0.02%)unmap_vmas (5 samples, 0.01%)unmap_single_vma (5 samples, 0.01%)unmap_page_range (5 samples, 0.01%)zap_pmd_range.isra.0 (5 samples, 0.01%)zap_pte_range (5 samples, 0.01%)entry_SYSCALL_64_safe_stack (15 samples, 0.04%)ret_from_fork (10 samples, 0.03%)schedule_tail (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_ctx_enable (10 samples, 0.03%)syscall_return_via_sysret (9 samples, 0.02%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (5 samples, 0.01%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (5 samples, 0.01%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (5 samples, 0.01%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (5 samples, 0.01%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (5 samples, 0.01%)alloc::alloc::dealloc (5 samples, 0.01%)benchmark (39,077 samples, 99.99%)benchmarkzssp::zssp::Context<Crypto>::receive (41 samples, 0.10%)perf_event_exec (4 samples, 0.01%)perf_event_enable_on_exec (4 samples, 0.01%)ctx_resched (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)all (39,082 samples, 100%)perf-exec (5 samples, 0.01%)entry_SYSCALL_64_after_hwframe (5 samples, 0.01%)do_syscall_64 (5 samples, 0.01%)__x64_sys_execve (5 samples, 0.01%)do_execveat_common.isra.0 (5 samples, 0.01%)bprm_execve (5 samples, 0.01%)bprm_execve.part.0 (5 samples, 0.01%)exec_binprm (5 samples, 0.01%)search_binary_handler (5 samples, 0.01%)load_elf_binary (5 samples, 0.01%)begin_new_exec (5 samples, 0.01%) \ No newline at end of file +]]>Flame Graph Reset ZoomSearch <zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (13 samples, 0.03%)zssp::crypto_impl::openssl::CipherCtx::update (7 samples, 0.02%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (28 samples, 0.07%)zssp::crypto_impl::openssl::CipherCtx::update (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (68 samples, 0.18%)zssp::crypto_impl::openssl::CipherCtx::finalize (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (6 samples, 0.02%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (102 samples, 0.26%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (5 samples, 0.01%)CRYPTO_gcm128_decrypt (8 samples, 0.02%)CRYPTO_gcm128_decrypt_ctr32 (22 samples, 0.06%)CRYPTO_gcm128_encrypt (17 samples, 0.04%)CRYPTO_gcm128_encrypt_ctr32 (15 samples, 0.04%)CRYPTO_gcm128_finish (7 samples, 0.02%)CRYPTO_gcm128_tag (4 samples, 0.01%)EVP_CIPHER_CTX_ctrl (9 samples, 0.02%)EVP_CIPHER_CTX_set_padding (14 samples, 0.04%)EVP_CIPHER_get_block_size (12 samples, 0.03%)EVP_CipherInit_ex (9 samples, 0.02%)EVP_DecryptUpdate (13 samples, 0.03%)EVP_EncryptFinal_ex (5 samples, 0.01%)OSSL_PARAM_locate (103 samples, 0.27%)[benchmark] (6 samples, 0.02%)EVP_DecryptUpdate (6 samples, 0.02%)[libc.so.6] (82 samples, 0.21%)[libcrypto.so.3] (124 samples, 0.32%)__lll_lock_wake_private (4 samples, 0.01%)malloc (13 samples, 0.03%)std::sync::mpmc::Sender<T>::send (5 samples, 0.01%)<std::sync::mpmc::select::Token as core::default::Default>::default (8 samples, 0.02%)std::sync::mpmc::array::Channel<T>::start_send (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::send (53 samples, 0.14%)std::sync::mpmc::array::Channel<T>::write (10 samples, 0.03%)std::sync::mpmc::array::Channel<T>::start_recv (8 samples, 0.02%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::try_recv (24 samples, 0.06%)std::sync::mpmc::array::Channel<T>::read (8 samples, 0.02%)std::sync::mpmc::utils::Backoff::new (9 samples, 0.02%)std::sync::mpmc::waker::SyncWaker::notify (13 samples, 0.03%)core::sync::atomic::AtomicBool::load (8 samples, 0.02%)core::sync::atomic::atomic_load (8 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (7 samples, 0.02%)<std::time::Instant as core::ops::arith::Sub>::sub (4 samples, 0.01%)std::time::Instant::duration_since (4 samples, 0.01%)std::time::Instant::checked_duration_since (4 samples, 0.01%)std::sys::unix::time::inner::Instant::checked_sub_instant (4 samples, 0.01%)std::time::Instant::elapsed (21 samples, 0.05%)syscall (12 samples, 0.03%)__entry_text_start (10 samples, 0.03%)[anon] (912 samples, 2.35%)[..zssp::zeta::receive_payload_in_place (23 samples, 0.06%)std::io::impls::<impl std::io::Write for &mut W>::write (5 samples, 0.01%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (5 samples, 0.01%)core::intrinsics::copy_nonoverlapping (5 samples, 0.01%)[libc.so.6] (6 samples, 0.02%)__rust_probestack (5 samples, 0.01%)[benchmark] (38 samples, 0.10%)zssp::zssp::Context<Crypto>::receive (22 samples, 0.06%)CRYPTO_gcm128_finish (11 samples, 0.03%)[libcrypto.so.3] (93 samples, 0.24%)std::sync::mpmc::utils::Backoff::new (5 samples, 0.01%)[libcrypto.so.3] (123 samples, 0.32%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (20 samples, 0.05%)<std::sync::mpmc::zero::ZeroToken as core::default::Default>::default (7 samples, 0.02%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (17 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (14 samples, 0.04%)zssp::crypto_impl::openssl::CipherCtx::update (14 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (5 samples, 0.01%)zssp::crypto_impl::openssl::CipherCtx::update (5 samples, 0.01%)CRYPTO_gcm128_decrypt (6 samples, 0.02%)CRYPTO_gcm128_decrypt_ctr32 (25 samples, 0.06%)CRYPTO_gcm128_encrypt (18 samples, 0.05%)CRYPTO_gcm128_encrypt_ctr32 (33 samples, 0.08%)CRYPTO_gcm128_finish (4 samples, 0.01%)CRYPTO_gcm128_setiv (4 samples, 0.01%)EVP_CIPHER_CTX_ctrl (16 samples, 0.04%)EVP_CIPHER_CTX_get_iv_length (11 samples, 0.03%)EVP_CIPHER_get_block_size (4 samples, 0.01%)EVP_CipherInit_ex (4 samples, 0.01%)EVP_DecryptUpdate (46 samples, 0.12%)EVP_EncryptUpdate (45 samples, 0.12%)OSSL_PARAM_locate (33 samples, 0.08%)[[vdso]] (16 samples, 0.04%)[benchmark] (9 samples, 0.02%)EVP_DecryptUpdate (9 samples, 0.02%)[libc.so.6] (73 samples, 0.19%)[libcrypto.so.3] (329 samples, 0.85%)__bss_start (20 samples, 0.05%)[libcrypto.so.3] (20 samples, 0.05%)__lll_lock_wait_private (4 samples, 0.01%)__entry_text_start (35 samples, 0.09%)crng_make_state (5 samples, 0.01%)_copy_to_iter (38 samples, 0.10%)copyout (28 samples, 0.07%)copyout (5 samples, 0.01%)__memcpy (14 samples, 0.04%)chacha_block_generic (267 samples, 0.69%)chacha_permute (242 samples, 0.62%)__x64_sys_getrandom (440 samples, 1.13%)get_random_bytes_user (428 samples, 1.10%)crng_make_state (343 samples, 0.88%)crng_fast_key_erasure (302 samples, 0.78%)exit_to_user_mode_prepare (19 samples, 0.05%)do_syscall_64 (474 samples, 1.22%)syscall_exit_to_user_mode (24 samples, 0.06%)entry_SYSCALL_64_after_hwframe (494 samples, 1.27%)syscall_exit_to_user_mode (4 samples, 0.01%)<rand_core::os::OsRng as rand_core::RngCore>::next_u64 (593 samples, 1.53%)rand_core::impls::next_u64_via_fill (593 samples, 1.53%)<rand_core::os::OsRng as rand_core::RngCore>::fill_bytes (593 samples, 1.53%)<rand_core::os::OsRng as rand_core::RngCore>::try_fill_bytes (587 samples, 1.51%)getrandom::getrandom (587 samples, 1.51%)getrandom::getrandom_uninit (587 samples, 1.51%)getrandom::imp::getrandom_inner (586 samples, 1.51%)getrandom::util_libc::sys_fill_exact (583 samples, 1.50%)getrandom::imp::getrandom_inner::_{{closure}} (577 samples, 1.49%)getrandom::imp::getrandom (577 samples, 1.49%)syscall (575 samples, 1.48%)syscall_return_via_sysret (9 samples, 0.02%)[libc.so.6] (20 samples, 0.05%)arrayvec::arrayvec::ArrayVec<T,_>::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (7 samples, 0.02%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (12 samples, 0.03%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (12 samples, 0.03%)core::sync::atomic::AtomicUsize::fetch_sub (12 samples, 0.03%)core::sync::atomic::atomic_sub (12 samples, 0.03%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)_ZN3std4sync4mpmc5array16Channel$LT$T$GT$10start_recv17h7800ca29c64cb868E.llvm.12455019271255371362 (9 samples, 0.02%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (20 samples, 0.05%)std::sync::mpmc::array::Channel<T>::read (25 samples, 0.06%)std::sync::mpmc::waker::SyncWaker::notify (5 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (44 samples, 0.11%)core::sync::atomic::atomic_compare_exchange_weak (44 samples, 0.11%)core::sync::atomic::AtomicUsize::load (177 samples, 0.46%)core::sync::atomic::atomic_load (177 samples, 0.46%)core::sync::atomic::fence (10 samples, 0.03%)std::sync::mpsc::Receiver<T>::try_recv (337 samples, 0.87%)std::sync::mpmc::Receiver<T>::try_recv (337 samples, 0.87%)std::sync::mpmc::array::Channel<T>::try_recv (326 samples, 0.84%)std::sync::mpmc::array::Channel<T>::start_recv (270 samples, 0.70%)<std::time::Instant as core::ops::arith::Sub>::sub (8 samples, 0.02%)std::time::Instant::duration_since (8 samples, 0.02%)std::time::Instant::checked_duration_since (8 samples, 0.02%)std::sys::unix::time::inner::Instant::checked_sub_instant (8 samples, 0.02%)std::sys::unix::time::Timespec::sub_timespec (8 samples, 0.02%)[[vdso]] (33 samples, 0.08%)[[vdso]] (19 samples, 0.05%)std::time::Instant::elapsed (44 samples, 0.11%)std::time::Instant::now (36 samples, 0.09%)std::sys::unix::time::inner::Instant::now (36 samples, 0.09%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (35 samples, 0.09%)clock_gettime (35 samples, 0.09%)<T as core::convert::TryInto<U>>::try_into (401 samples, 1.03%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (401 samples, 1.03%)core::result::Result<T,E>::map (401 samples, 1.03%)<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (4 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (71 samples, 0.18%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (71 samples, 0.18%)std::sys::unix::locks::futex_mutex::Mutex::unlock (64 samples, 0.16%)core::sync::atomic::AtomicU32::swap (60 samples, 0.15%)core::sync::atomic::atomic_swap (60 samples, 0.15%)std::sync::mutex::MutexGuard<T>::new (5 samples, 0.01%)std::sync::poison::Flag::guard (5 samples, 0.01%)std::thread::panicking (5 samples, 0.01%)std::panicking::panicking (5 samples, 0.01%)std::panicking::panic_count::count_is_zero (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (77 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (72 samples, 0.19%)core::sync::atomic::AtomicU32::compare_exchange (70 samples, 0.18%)core::sync::atomic::atomic_compare_exchange (70 samples, 0.18%)EVP_CIPHER_CTX_get_block_size (5 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (394 samples, 1.01%)zssp::crypto_impl::openssl::CipherCtx::update (243 samples, 0.63%)EVP_DecryptUpdate (242 samples, 0.62%)[libcrypto.so.3] (204 samples, 0.53%)[libcrypto.so.3] (152 samples, 0.39%)[libcrypto.so.3] (144 samples, 0.37%)__rust_probestack (9 samples, 0.02%)core::cmp::Ord::max (4 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::num::nonzero::NonZeroU32::new (13 samples, 0.03%)asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)irqentry_exit_to_user_mode (10 samples, 0.03%)exit_to_user_mode_prepare (10 samples, 0.03%)exit_to_user_mode_loop (10 samples, 0.03%)schedule (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_pmu_nop_void (5 samples, 0.01%)alloc::sync::Weak<T>::upgrade::_{{closure}} (9 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (61 samples, 0.16%)core::sync::atomic::atomic_compare_exchange_weak (61 samples, 0.16%)alloc::sync::Weak<T>::upgrade (81 samples, 0.21%)core::sync::atomic::AtomicUsize::fetch_update (78 samples, 0.20%)core::sync::atomic::AtomicUsize::load (4 samples, 0.01%)core::sync::atomic::atomic_load (4 samples, 0.01%)core::option::Option<T>::map (82 samples, 0.21%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (82 samples, 0.21%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (55 samples, 0.14%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (55 samples, 0.14%)core::sync::atomic::AtomicUsize::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)__rdl_dealloc (4 samples, 0.01%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::dealloc (4 samples, 0.01%)__rust_dealloc (5 samples, 0.01%)[libc.so.6] (48 samples, 0.12%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)__entry_text_start (14 samples, 0.04%)futex_unqueue (10 samples, 0.03%)update_curr (11 samples, 0.03%)cpuacct_charge (5 samples, 0.01%)update_load_avg (8 samples, 0.02%)dequeue_entity (28 samples, 0.07%)dequeue_task (37 samples, 0.10%)dequeue_task_fair (36 samples, 0.09%)__perf_event_task_sched_in (8 samples, 0.02%)perf_ctx_enable (8 samples, 0.02%)finish_task_switch.isra.0 (18 samples, 0.05%)raw_spin_rq_unlock (9 samples, 0.02%)pick_next_task_fair (4 samples, 0.01%)newidle_balance (4 samples, 0.01%)pick_next_task (11 samples, 0.03%)prepare_task_switch (14 samples, 0.04%)__perf_event_task_sched_out (5 samples, 0.01%)psi_group_change (26 samples, 0.07%)psi_task_switch (44 samples, 0.11%)__schedule (149 samples, 0.38%)futex_wait_queue (165 samples, 0.42%)schedule (159 samples, 0.41%)__get_user_nocheck_4 (16 samples, 0.04%)_raw_spin_lock (5 samples, 0.01%)futex_q_lock (4 samples, 0.01%)futex_q_unlock (7 samples, 0.02%)futex_wait_setup (41 samples, 0.11%)futex_wait (230 samples, 0.59%)do_futex (236 samples, 0.61%)__x64_sys_futex (247 samples, 0.64%)__rseq_handle_notify_resume (7 samples, 0.02%)exit_to_user_mode_loop (21 samples, 0.05%)exit_to_user_mode_prepare (27 samples, 0.07%)do_syscall_64 (283 samples, 0.73%)syscall_exit_to_user_mode (31 samples, 0.08%)[libc.so.6] (785 samples, 2.02%)[..__lll_lock_wait_private (346 samples, 0.89%)entry_SYSCALL_64_after_hwframe (288 samples, 0.74%)__entry_text_start (13 samples, 0.03%)futex_hash (7 samples, 0.02%)_raw_spin_lock (25 samples, 0.06%)native_queued_spin_lock_slowpath (25 samples, 0.06%)do_futex (66 samples, 0.17%)futex_wake (54 samples, 0.14%)__x64_sys_futex (72 samples, 0.19%)exit_to_user_mode_prepare (10 samples, 0.03%)do_syscall_64 (90 samples, 0.23%)syscall_exit_to_user_mode (16 samples, 0.04%)entry_SYSCALL_64_after_hwframe (98 samples, 0.25%)alloc::alloc::dealloc (965 samples, 2.48%)al..cfree (956 samples, 2.46%)cf..__lll_lock_wake_private (119 samples, 0.31%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (970 samples, 2.50%)<a..zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (986 samples, 2.54%)co..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (986 samples, 2.54%)<a..arrayvec::arrayvec::ArrayVec<T,_>::clear (986 samples, 2.54%)ar..arrayvec::arrayvec_impl::ArrayVecImpl::clear (986 samples, 2.54%)ar..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (986 samples, 2.54%)ar..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (984 samples, 2.53%)co..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (981 samples, 2.53%)co..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (981 samples, 2.53%)co..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (981 samples, 2.53%)<a..alloc::raw_vec::RawVec<T,A>::current_memory (8 samples, 0.02%)core::alloc::layout::Layout::array (6 samples, 0.02%)core::alloc::layout::Layout::array::inner (6 samples, 0.02%)std::sync::poison::Flag::done (10 samples, 0.03%)std::thread::panicking (10 samples, 0.03%)std::panicking::panicking (10 samples, 0.03%)std::panicking::panic_count::count_is_zero (10 samples, 0.03%)core::sync::atomic::AtomicUsize::load (9 samples, 0.02%)core::sync::atomic::atomic_load (9 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (78 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::unlock (68 samples, 0.18%)core::sync::atomic::AtomicU32::swap (66 samples, 0.17%)core::sync::atomic::atomic_swap (66 samples, 0.17%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (88 samples, 0.23%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (85 samples, 0.22%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (85 samples, 0.22%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (85 samples, 0.22%)core::sync::atomic::AtomicU32::fetch_sub (76 samples, 0.20%)core::sync::atomic::atomic_sub (76 samples, 0.20%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (66 samples, 0.17%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (66 samples, 0.17%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (66 samples, 0.17%)core::sync::atomic::AtomicU32::fetch_sub (57 samples, 0.15%)core::sync::atomic::atomic_sub (57 samples, 0.15%)core::num::<impl u64>::rotate_left (4 samples, 0.01%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (20 samples, 0.05%)core::num::<impl u64>::wrapping_add (4 samples, 0.01%)core::num::<impl u64>::rotate_left (7 samples, 0.02%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (62 samples, 0.16%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (62 samples, 0.16%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (62 samples, 0.16%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (32 samples, 0.08%)core::num::<impl u64>::wrapping_add (14 samples, 0.04%)<std::collections::hash::map::RandomState as core::hash::BuildHasher>::build_hasher (17 samples, 0.04%)core::hash::sip::SipHasher13::new_with_keys (8 samples, 0.02%)core::hash::sip::Hasher<S>::new_with_keys (8 samples, 0.02%)core::hash::sip::Hasher<S>::reset (8 samples, 0.02%)hashbrown::map::make_hash (104 samples, 0.27%)core::hash::BuildHasher::hash_one (104 samples, 0.27%)core::hash::impls::<impl core::hash::Hash for &T>::hash (23 samples, 0.06%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (23 samples, 0.06%)core::hash::impls::<impl core::hash::Hash for u32>::hash (23 samples, 0.06%)core::hash::Hasher::write_u32 (23 samples, 0.06%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (23 samples, 0.06%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (23 samples, 0.06%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (23 samples, 0.06%)core::hash::sip::u8to64_le (15 samples, 0.04%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (5 samples, 0.01%)hashbrown::raw::h2 (8 samples, 0.02%)hashbrown::raw::sse2::Group::load (29 samples, 0.07%)core::core_arch::x86::sse2::_mm_loadu_si128 (29 samples, 0.07%)core::intrinsics::copy_nonoverlapping (29 samples, 0.07%)hashbrown::map::HashMap<K,V,S,A>::get_inner (163 samples, 0.42%)hashbrown::raw::RawTable<T,A>::get (59 samples, 0.15%)hashbrown::raw::RawTable<T,A>::find (59 samples, 0.15%)hashbrown::raw::RawTableInner<A>::find_inner (59 samples, 0.15%)hashbrown::raw::sse2::Group::match_byte (7 samples, 0.02%)core::core_arch::x86::sse2::_mm_movemask_epi8 (7 samples, 0.02%)std::collections::hash::map::HashMap<K,V,S>::get (164 samples, 0.42%)hashbrown::map::HashMap<K,V,S,A>::get (164 samples, 0.42%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (42 samples, 0.11%)std::sys::unix::locks::futex_mutex::Mutex::lock (38 samples, 0.10%)core::sync::atomic::AtomicU32::compare_exchange (30 samples, 0.08%)core::sync::atomic::atomic_compare_exchange (30 samples, 0.08%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::sync::atomic::AtomicU32::compare_exchange_weak (128 samples, 0.33%)core::sync::atomic::atomic_compare_exchange_weak (128 samples, 0.33%)std::sync::rwlock::RwLock<T>::read (162 samples, 0.42%)std::sys::unix::locks::futex_rwlock::RwLock::read (162 samples, 0.42%)std::sys::unix::locks::futex_rwlock::is_read_lockable (12 samples, 0.03%)core::num::<impl u64>::wrapping_add (5 samples, 0.01%)zssp::antireplay::Window<_,_>::check (13 samples, 0.03%)core::sync::atomic::AtomicU64::load (7 samples, 0.02%)core::sync::atomic::atomic_load (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (20 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push (20 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (20 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (13 samples, 0.03%)core::ptr::write (12 samples, 0.03%)core::array::equality::_<impl core::cmp::PartialEq<[B: N]> for [A: N]>::ne (5 samples, 0.01%)<T as core::array::equality::SpecArrayEq<U,_>>::spec_ne (5 samples, 0.01%)<T as core::array::equality::SpecArrayEq<U,_>>::spec_eq (5 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init_read (7 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::read (7 samples, 0.02%)core::ptr::read (7 samples, 0.02%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (7 samples, 0.02%)core::num::<impl u64>::wrapping_shl (6 samples, 0.02%)core::num::<impl u64>::unchecked_shl (6 samples, 0.02%)zssp::fragged::Fragged<Fragment,_>::assemble (98 samples, 0.25%)zssp::fragged::Fragged<Fragment,_>::drop_in_place (5 samples, 0.01%)<T as core::convert::TryInto<U>>::try_into (26 samples, 0.07%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (26 samples, 0.07%)core::result::Result<T,E>::map (26 samples, 0.07%)zssp::zeta::from_nonce (32 samples, 0.08%)core::num::<impl u64>::from_be_bytes (6 samples, 0.02%)core::num::<impl u64>::from_be (6 samples, 0.02%)core::num::<impl u64>::swap_bytes (6 samples, 0.02%)<alloc::vec::Vec<T,A> as core::convert::AsMut<[T]>>::as_mut (4 samples, 0.01%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (69 samples, 0.18%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (69 samples, 0.18%)core::sync::atomic::AtomicU32::fetch_sub (68 samples, 0.18%)core::sync::atomic::atomic_sub (68 samples, 0.18%)CRYPTO_gcm128_decrypt (249 samples, 0.64%)[libcrypto.so.3] (120 samples, 0.31%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)CRYPTO_gcm128_decrypt_ctr32 (478 samples, 1.23%)[libcrypto.so.3] (360 samples, 0.93%)[libcrypto.so.3] (22 samples, 0.06%)CRYPTO_gcm128_setiv (23 samples, 0.06%)[libcrypto.so.3] (20 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,848 samples, 12.48%)<zssp::crypto_impl:..zssp::crypto_impl::openssl::CipherCtx::update (4,846 samples, 12.48%)zssp::crypto_impl::..EVP_DecryptUpdate (4,846 samples, 12.48%)EVP_DecryptUpdate[libcrypto.so.3] (4,745 samples, 12.22%)[libcrypto.so.3][libcrypto.so.3] (4,741 samples, 12.21%)[libcrypto.so.3][libcrypto.so.3] (4,719 samples, 12.15%)[libcrypto.so.3][libcrypto.so.3] (3,937 samples, 10.14%)[libcrypto.so.3][libcrypto.so.3] (3,769 samples, 9.71%)[libcrypto.so...asm_sysvec_apic_timer_interrupt (6 samples, 0.02%)sysvec_apic_timer_interrupt (6 samples, 0.02%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (13 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (13 samples, 0.03%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (13 samples, 0.03%)std::sys::unix::locks::futex_mutex::Mutex::unlock (12 samples, 0.03%)core::sync::atomic::AtomicU32::swap (12 samples, 0.03%)core::sync::atomic::atomic_swap (12 samples, 0.03%)[libc.so.6] (6 samples, 0.02%)[libcrypto.so.3] (20 samples, 0.05%)CRYPTO_gcm128_finish (40 samples, 0.10%)zssp::crypto_impl::openssl::CipherCtx::finalize (59 samples, 0.15%)EVP_DecryptFinal_ex (59 samples, 0.15%)[libcrypto.so.3] (50 samples, 0.13%)[libcrypto.so.3] (50 samples, 0.13%)[libcrypto.so.3] (46 samples, 0.12%)OSSL_PARAM_get_octet_string (5 samples, 0.01%)[libcrypto.so.3] (4 samples, 0.01%)OSSL_PARAM_locate (27 samples, 0.07%)[libc.so.6] (16 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (145 samples, 0.37%)zssp::crypto_impl::openssl::CipherCtx::set_tag (70 samples, 0.18%)EVP_CIPHER_CTX_ctrl (70 samples, 0.18%)[libcrypto.so.3] (40 samples, 0.10%)std::sync::mutex::Mutex<T>::lock (18 samples, 0.05%)std::sys::unix::locks::futex_mutex::Mutex::lock (18 samples, 0.05%)core::sync::atomic::AtomicU32::compare_exchange (17 samples, 0.04%)core::sync::atomic::atomic_compare_exchange (17 samples, 0.04%)[libc.so.6] (14 samples, 0.04%)OSSL_PARAM_locate (37 samples, 0.10%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_get_iv_length (70 samples, 0.18%)[libcrypto.so.3] (47 samples, 0.12%)[libc.so.6] (22 samples, 0.06%)OSSL_PARAM_locate (50 samples, 0.13%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (92 samples, 0.24%)[libcrypto.so.3] (72 samples, 0.19%)OSSL_PARAM_locate_const (5 samples, 0.01%)EVP_CIPHER_free (13 samples, 0.03%)EVP_CIPHER_up_ref (46 samples, 0.12%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (292 samples, 0.75%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (273 samples, 0.70%)EVP_CipherInit_ex (272 samples, 0.70%)[libcrypto.so.3] (272 samples, 0.70%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::get_unchecked_ptr (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::add (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::offset (6 samples, 0.02%)std::io::impls::<impl std::io::Write for &mut W>::write (253 samples, 0.65%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (253 samples, 0.65%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (250 samples, 0.64%)core::intrinsics::copy_nonoverlapping (244 samples, 0.63%)[libc.so.6] (242 samples, 0.62%)zssp::antireplay::Window<_,_>::update (15 samples, 0.04%)core::sync::atomic::AtomicU64::fetch_max (15 samples, 0.04%)core::sync::atomic::atomic_umax (15 samples, 0.04%)zssp::zeta::receive_payload_in_place (5,655 samples, 14.56%)zssp::zeta::receive_pa..zssp::zssp::Context<Crypto>::receive (8,525 samples, 21.95%)zssp::zssp::Context<Crypto>::receivezssp::zssp::parse_fragment_header (74 samples, 0.19%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (60 samples, 0.15%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (60 samples, 0.15%)std::sys::unix::locks::futex_mutex::Mutex::unlock (59 samples, 0.15%)core::sync::atomic::AtomicU32::swap (54 samples, 0.14%)core::sync::atomic::atomic_swap (54 samples, 0.14%)std::sync::mutex::Mutex<T>::lock (79 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (77 samples, 0.20%)core::sync::atomic::AtomicU32::compare_exchange (74 samples, 0.19%)core::sync::atomic::atomic_compare_exchange (74 samples, 0.19%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (325 samples, 0.84%)zssp::crypto_impl::openssl::CipherCtx::update (180 samples, 0.46%)EVP_EncryptUpdate (180 samples, 0.46%)[libcrypto.so.3] (154 samples, 0.40%)[libcrypto.so.3] (123 samples, 0.32%)[libcrypto.so.3] (115 samples, 0.30%)<std::sync::mutex::MutexGuard<T> as core::ops::deref::Deref>::deref (5 samples, 0.01%)CRYPTO_gcm128_encrypt (264 samples, 0.68%)[libcrypto.so.3] (144 samples, 0.37%)[libcrypto.so.3] (20 samples, 0.05%)CRYPTO_gcm128_encrypt_ctr32 (501 samples, 1.29%)[libcrypto.so.3] (349 samples, 0.90%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)CRYPTO_gcm128_setiv (23 samples, 0.06%)[libcrypto.so.3] (20 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,870 samples, 9.97%)<zssp::crypto_..zssp::crypto_impl::openssl::CipherCtx::update (3,865 samples, 9.95%)zssp::crypto_i..EVP_EncryptUpdate (3,859 samples, 9.94%)EVP_EncryptUpd..[libcrypto.so.3] (3,809 samples, 9.81%)[libcrypto.so...[libcrypto.so.3] (3,805 samples, 9.80%)[libcrypto.so...[libcrypto.so.3] (3,789 samples, 9.76%)[libcrypto.so...[libcrypto.so.3] (2,964 samples, 7.63%)[libcrypto..[libcrypto.so.3] (2,713 samples, 6.99%)[libcrypt..asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (7 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (7 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (7 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::unlock (6 samples, 0.02%)core::sync::atomic::AtomicU32::swap (6 samples, 0.02%)core::sync::atomic::atomic_swap (6 samples, 0.02%)[libcrypto.so.3] (31 samples, 0.08%)zssp::crypto_impl::openssl::CipherCtx::finalize (75 samples, 0.19%)EVP_EncryptFinal_ex (75 samples, 0.19%)[libcrypto.so.3] (61 samples, 0.16%)[libcrypto.so.3] (59 samples, 0.15%)[libcrypto.so.3] (56 samples, 0.14%)CRYPTO_gcm128_tag (55 samples, 0.14%)CRYPTO_gcm128_finish (42 samples, 0.11%)[libc.so.6] (29 samples, 0.07%)OSSL_PARAM_locate (60 samples, 0.15%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (177 samples, 0.46%)zssp::crypto_impl::openssl::CipherCtx::get_tag (94 samples, 0.24%)EVP_CIPHER_CTX_ctrl (94 samples, 0.24%)[libcrypto.so.3] (69 samples, 0.18%)OSSL_PARAM_set_octet_string (5 samples, 0.01%)[libc.so.6] (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (15 samples, 0.04%)std::sys::unix::locks::futex_mutex::Mutex::lock (13 samples, 0.03%)core::sync::atomic::AtomicU32::compare_exchange (11 samples, 0.03%)core::sync::atomic::atomic_compare_exchange (11 samples, 0.03%)[libc.so.6] (25 samples, 0.06%)EVP_CIPHER_CTX_get_iv_length (76 samples, 0.20%)[libcrypto.so.3] (53 samples, 0.14%)OSSL_PARAM_locate (46 samples, 0.12%)[libc.so.6] (37 samples, 0.10%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)OSSL_PARAM_locate (49 samples, 0.13%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (95 samples, 0.24%)[libcrypto.so.3] (62 samples, 0.16%)OSSL_PARAM_locate_const (5 samples, 0.01%)EVP_CIPHER_free (14 samples, 0.04%)EVP_CIPHER_up_ref (28 samples, 0.07%)EVP_CipherInit_ex (245 samples, 0.63%)[libcrypto.so.3] (245 samples, 0.63%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (262 samples, 0.67%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (247 samples, 0.64%)__rdl_alloc (10 samples, 0.03%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (10 samples, 0.03%)__rust_alloc (5 samples, 0.01%)[libc.so.6] (74 samples, 0.19%)asm_exc_page_fault (5 samples, 0.01%)exc_page_fault (5 samples, 0.01%)do_user_addr_fault (5 samples, 0.01%)handle_mm_fault (5 samples, 0.01%)__handle_mm_fault (5 samples, 0.01%)handle_pte_fault (5 samples, 0.01%)do_anonymous_page (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)finish_task_switch.isra.0 (13 samples, 0.03%)__perf_event_task_sched_in (13 samples, 0.03%)perf_pmu_nop_void (8 samples, 0.02%)[libc.so.6] (1,111 samples, 2.86%)[l..asm_sysvec_reschedule_ipi (16 samples, 0.04%)sysvec_reschedule_ipi (16 samples, 0.04%)irqentry_exit (16 samples, 0.04%)irqentry_exit_to_user_mode (16 samples, 0.04%)exit_to_user_mode_prepare (16 samples, 0.04%)exit_to_user_mode_loop (16 samples, 0.04%)schedule (14 samples, 0.04%)__schedule (14 samples, 0.04%)__entry_text_start (10 samples, 0.03%)__get_user_nocheck_4 (9 samples, 0.02%)_raw_spin_lock (6 samples, 0.02%)futex_q_lock (10 samples, 0.03%)futex_q_unlock (12 samples, 0.03%)futex_wait_setup (44 samples, 0.11%)get_futex_key (4 samples, 0.01%)futex_wait (52 samples, 0.13%)__x64_sys_futex (57 samples, 0.15%)do_futex (54 samples, 0.14%)entry_SYSCALL_64_after_hwframe (62 samples, 0.16%)do_syscall_64 (60 samples, 0.15%)__lll_lock_wait_private (101 samples, 0.26%)__entry_text_start (7 samples, 0.02%)_raw_spin_lock (9 samples, 0.02%)_raw_spin_lock (12 samples, 0.03%)native_queued_spin_lock_slowpath (12 samples, 0.03%)futex_wake_mark (24 samples, 0.06%)__futex_unqueue (4 samples, 0.01%)__smp_call_single_queue (20 samples, 0.05%)native_send_call_func_single_ipi (17 samples, 0.04%)x2apic_send_IPI (17 samples, 0.04%)native_write_msr (15 samples, 0.04%)llist_add_batch (17 samples, 0.04%)try_to_wake_up (89 samples, 0.23%)ttwu_queue_wakelist (55 samples, 0.14%)futex_wake (196 samples, 0.50%)wake_up_q (96 samples, 0.25%)do_futex (210 samples, 0.54%)__x64_sys_futex (214 samples, 0.55%)entry_SYSCALL_64_after_hwframe (224 samples, 0.58%)do_syscall_64 (222 samples, 0.57%)__lll_lock_wake_private (239 samples, 0.62%)alloc::vec::Vec<T,A>::with_capacity_in (2,224 samples, 5.73%)alloc::..alloc::raw_vec::RawVec<T,A>::with_capacity_in (2,224 samples, 5.73%)alloc::..alloc::raw_vec::RawVec<T,A>::allocate_in (2,224 samples, 5.73%)alloc::..<alloc::alloc::Global as core::alloc::Allocator>::allocate (2,221 samples, 5.72%)<alloc:..alloc::alloc::Global::alloc_impl (2,221 samples, 5.72%)alloc::..alloc::alloc::alloc (2,221 samples, 5.72%)alloc::..malloc (2,206 samples, 5.68%)mallocalloc::slice::<impl [T]>::to_vec (2,451 samples, 6.31%)alloc::s..alloc::slice::<impl [T]>::to_vec_in (2,451 samples, 6.31%)alloc::s..alloc::slice::hack::to_vec (2,451 samples, 6.31%)alloc::s..<T as alloc::slice::hack::ConvertVec>::to_vec (2,451 samples, 6.31%)<T as al..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (227 samples, 0.58%)core::intrinsics::copy_nonoverlapping (227 samples, 0.58%)[libc.so.6] (222 samples, 0.57%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (4 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (80 samples, 0.21%)core::sync::atomic::atomic_compare_exchange_weak (80 samples, 0.21%)std::sync::mpmc::array::Channel<T>::start_send (113 samples, 0.29%)core::sync::atomic::AtomicUsize::load (7 samples, 0.02%)core::sync::atomic::atomic_load (7 samples, 0.02%)core::sync::atomic::AtomicU32::swap (6 samples, 0.02%)core::sync::atomic::atomic_swap (6 samples, 0.02%)futex_wake_mark (7 samples, 0.02%)__smp_call_single_queue (6 samples, 0.02%)__x64_sys_futex (57 samples, 0.15%)do_futex (57 samples, 0.15%)futex_wake (55 samples, 0.14%)wake_up_q (36 samples, 0.09%)try_to_wake_up (35 samples, 0.09%)ttwu_queue_wakelist (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::write (91 samples, 0.23%)std::sync::mpmc::waker::SyncWaker::notify (85 samples, 0.22%)std::sync::mpmc::waker::Waker::try_select (74 samples, 0.19%)<core::slice::iter::Iter<T> as core::iter::traits::iterator::Iterator>::position (74 samples, 0.19%)std::sync::mpmc::waker::Waker::try_select::_{{closure}} (74 samples, 0.19%)std::sync::mpmc::context::Context::unpark (72 samples, 0.19%)std::thread::Thread::unpark (72 samples, 0.19%)std::sys_common::thread_parking::futex::Parker::unpark (72 samples, 0.19%)std::sys::unix::futex::futex_wake (66 samples, 0.17%)syscall (66 samples, 0.17%)entry_SYSCALL_64_after_hwframe (61 samples, 0.16%)do_syscall_64 (61 samples, 0.16%)std::sync::mpmc::context::Context::wait_until (4 samples, 0.01%)std::thread::park (4 samples, 0.01%)std::sys_common::thread_parking::futex::Parker::park (4 samples, 0.01%)std::sys::unix::futex::futex_wait (4 samples, 0.01%)syscall (4 samples, 0.01%)entry_SYSCALL_64_after_hwframe (4 samples, 0.01%)do_syscall_64 (4 samples, 0.01%)std::sync::mpmc::context::Context::with (5 samples, 0.01%)std::thread::local::LocalKey<T>::try_with (5 samples, 0.01%)std::sync::mpmc::context::Context::with::_{{closure}} (5 samples, 0.01%)std::sync::mpmc::context::Context::with::_{{closure}} (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send::_{{closure}} (5 samples, 0.01%)benchmark::bob_main::_{{closure}} (4,067 samples, 10.47%)benchmark::bob_..std::sync::mpsc::SyncSender<T>::send (1,615 samples, 4.16%)std::..std::sync::mpmc::Sender<T>::send (1,613 samples, 4.15%)std::..std::sync::mpmc::array::Channel<T>::send (265 samples, 0.68%)std::sync::mpmc::utils::Backoff::spin_light (25 samples, 0.06%)core::hint::spin_loop (25 samples, 0.06%)core::core_arch::x86::sse2::_mm_pause (25 samples, 0.06%)core::mem::drop (16 samples, 0.04%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (16 samples, 0.04%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (16 samples, 0.04%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (16 samples, 0.04%)core::sync::atomic::AtomicU32::fetch_sub (15 samples, 0.04%)core::sync::atomic::atomic_sub (15 samples, 0.04%)core::slice::<impl [T]>::copy_from_slice (6 samples, 0.02%)core::intrinsics::copy_nonoverlapping (6 samples, 0.02%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (4 samples, 0.01%)std::sync::rwlock::RwLock<T>::read (23 samples, 0.06%)std::sys::unix::locks::futex_rwlock::RwLock::read (23 samples, 0.06%)core::sync::atomic::AtomicU32::compare_exchange_weak (21 samples, 0.05%)core::sync::atomic::atomic_compare_exchange_weak (21 samples, 0.05%)benchmark::alice_main (18,418 samples, 47.43%)benchmark::alice_mainzssp::zssp::Context<Crypto>::send (8,789 samples, 22.63%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,789 samples, 22.63%)zssp::zeta::send_payloadzssp::zeta::get_counter (18 samples, 0.05%)core::sync::atomic::AtomicU64::fetch_add (16 samples, 0.04%)core::sync::atomic::atomic_add (16 samples, 0.04%)[libc.so.6] (15 samples, 0.04%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (15 samples, 0.04%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (15 samples, 0.04%)core::sync::atomic::AtomicUsize::fetch_sub (14 samples, 0.04%)core::sync::atomic::atomic_sub (14 samples, 0.04%)core::time::Duration::as_millis (8 samples, 0.02%)core::result::Result<T,E>::map_err (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::read (7 samples, 0.02%)std::sync::mpmc::waker::SyncWaker::notify (6 samples, 0.02%)core::slice::<impl [T]>::get_unchecked (6 samples, 0.02%)<usize as core::slice::index::SliceIndex<[T]>>::get_unchecked (6 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::add (6 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::offset (6 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (90 samples, 0.23%)core::sync::atomic::atomic_compare_exchange_weak (90 samples, 0.23%)core::sync::atomic::AtomicUsize::load (63 samples, 0.16%)core::sync::atomic::atomic_load (63 samples, 0.16%)std::sync::mpmc::array::Channel<T>::start_recv (200 samples, 0.52%)core::sync::atomic::AtomicUsize::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)core::sync::atomic::AtomicU32::swap (4 samples, 0.01%)core::sync::atomic::atomic_swap (4 samples, 0.01%)core::option::Option<T>::and_then (7 samples, 0.02%)std::sys::unix::futex::futex_wait::_{{closure}} (7 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (7 samples, 0.02%)clock_gettime (7 samples, 0.02%)[[vdso]] (7 samples, 0.02%)[[vdso]] (4 samples, 0.01%)futex_unqueue (7 samples, 0.02%)enqueue_hrtimer (5 samples, 0.01%)hrtimer_sleeper_start_expires (6 samples, 0.02%)hrtimer_start_range_ns (6 samples, 0.02%)__hrtimer_start_range_ns (6 samples, 0.02%)update_curr (5 samples, 0.01%)dequeue_task (14 samples, 0.04%)dequeue_task_fair (14 samples, 0.04%)dequeue_entity (14 samples, 0.04%)update_load_avg (5 samples, 0.01%)__perf_event_task_sched_in (11 samples, 0.03%)perf_ctx_enable (11 samples, 0.03%)finish_task_switch.isra.0 (16 samples, 0.04%)raw_spin_rq_unlock (4 samples, 0.01%)pick_next_task (4 samples, 0.01%)prepare_task_switch (4 samples, 0.01%)psi_group_change (8 samples, 0.02%)psi_task_switch (13 samples, 0.03%)futex_wait_queue (78 samples, 0.20%)schedule (68 samples, 0.18%)__schedule (66 samples, 0.17%)__get_user_nocheck_4 (4 samples, 0.01%)futex_q_lock (10 samples, 0.03%)futex_wait_setup (18 samples, 0.05%)hrtimer_cancel (7 samples, 0.02%)hrtimer_try_to_cancel (6 samples, 0.02%)futex_wait (117 samples, 0.30%)do_futex (120 samples, 0.31%)__x64_sys_futex (130 samples, 0.33%)get_timespec64 (4 samples, 0.01%)_copy_from_user (4 samples, 0.01%)exit_to_user_mode_prepare (8 samples, 0.02%)exit_to_user_mode_loop (8 samples, 0.02%)__rseq_handle_notify_resume (5 samples, 0.01%)std::sys_common::thread_parking::futex::Parker::park_timeout (157 samples, 0.40%)std::sys::unix::futex::futex_wait (152 samples, 0.39%)syscall (145 samples, 0.37%)entry_SYSCALL_64_after_hwframe (140 samples, 0.36%)do_syscall_64 (140 samples, 0.36%)syscall_exit_to_user_mode (9 samples, 0.02%)std::thread::park_timeout (159 samples, 0.41%)std::sync::mpmc::context::Context::wait_until (168 samples, 0.43%)core::sync::atomic::AtomicBool::store (6 samples, 0.02%)core::sync::atomic::atomic_store (6 samples, 0.02%)std::sync::mpmc::context::Context::with (187 samples, 0.48%)std::thread::local::LocalKey<T>::try_with (187 samples, 0.48%)std::sync::mpmc::context::Context::with::_{{closure}} (187 samples, 0.48%)std::sync::mpmc::context::Context::with::_{{closure}} (187 samples, 0.48%)std::sync::mpmc::array::Channel<T>::recv::_{{closure}} (185 samples, 0.48%)std::sync::mpmc::waker::SyncWaker::register (15 samples, 0.04%)std::sync::mutex::Mutex<T>::lock (6 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::lock (6 samples, 0.02%)core::sync::atomic::AtomicU32::compare_exchange (6 samples, 0.02%)core::sync::atomic::atomic_compare_exchange (6 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (4 samples, 0.01%)clock_gettime (4 samples, 0.01%)[[vdso]] (4 samples, 0.01%)std::sync::mpmc::Receiver<T>::recv_deadline (418 samples, 1.08%)std::sync::mpmc::array::Channel<T>::recv (416 samples, 1.07%)[[vdso]] (164 samples, 0.42%)[[vdso]] (114 samples, 0.29%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (175 samples, 0.45%)clock_gettime (173 samples, 0.45%)__vdso_clock_gettime (6 samples, 0.02%)std::sync::mpsc::Receiver<T>::recv_timeout (634 samples, 1.63%)std::sync::mpmc::Receiver<T>::recv_timeout (628 samples, 1.62%)std::time::SystemTime::checked_add (17 samples, 0.04%)std::sys::unix::time::SystemTime::checked_add_duration (16 samples, 0.04%)std::sys::unix::time::Timespec::checked_add_duration (16 samples, 0.04%)core::option::Option<T>::and_then (8 samples, 0.02%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::cmp::impls::<impl core::cmp::PartialOrd<&B> for &A>::ge (21 samples, 0.05%)core::cmp::PartialOrd::ge (21 samples, 0.05%)<std::sys::unix::time::Timespec as core::cmp::PartialOrd>::partial_cmp (15 samples, 0.04%)std::time::Instant::duration_since (45 samples, 0.12%)std::time::Instant::checked_duration_since (45 samples, 0.12%)std::sys::unix::time::inner::Instant::checked_sub_instant (45 samples, 0.12%)std::sys::unix::time::Timespec::sub_timespec (45 samples, 0.12%)core::time::Duration::new (5 samples, 0.01%)<std::time::Instant as core::ops::arith::Sub>::sub (48 samples, 0.12%)[[vdso]] (111 samples, 0.29%)[[vdso]] (145 samples, 0.37%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)std::time::Instant::elapsed (208 samples, 0.54%)std::time::Instant::now (158 samples, 0.41%)std::sys::unix::time::inner::Instant::now (158 samples, 0.41%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (155 samples, 0.40%)clock_gettime (151 samples, 0.39%)__vdso_clock_gettime (4 samples, 0.01%)<T as core::convert::TryInto<U>>::try_into (317 samples, 0.82%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (317 samples, 0.82%)core::result::Result<T,E>::map (317 samples, 0.82%)<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (6 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (76 samples, 0.20%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (76 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::unlock (75 samples, 0.19%)core::sync::atomic::AtomicU32::swap (71 samples, 0.18%)core::sync::atomic::atomic_swap (71 samples, 0.18%)std::sync::mutex::MutexGuard<T>::new (6 samples, 0.02%)std::sync::poison::Flag::guard (6 samples, 0.02%)std::thread::panicking (6 samples, 0.02%)std::panicking::panicking (6 samples, 0.02%)std::panicking::panic_count::count_is_zero (6 samples, 0.02%)std::sync::mutex::Mutex<T>::lock (73 samples, 0.19%)std::sys::unix::locks::futex_mutex::Mutex::lock (67 samples, 0.17%)core::sync::atomic::AtomicU32::compare_exchange (66 samples, 0.17%)core::sync::atomic::atomic_compare_exchange (66 samples, 0.17%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (355 samples, 0.91%)zssp::crypto_impl::openssl::CipherCtx::update (206 samples, 0.53%)EVP_DecryptUpdate (202 samples, 0.52%)[libcrypto.so.3] (165 samples, 0.42%)[libcrypto.so.3] (125 samples, 0.32%)[libcrypto.so.3] (116 samples, 0.30%)__rust_probestack (6 samples, 0.02%)core::cmp::Ord::max (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)alloc::sync::Weak<T>::upgrade::_{{closure}} (6 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (73 samples, 0.19%)core::sync::atomic::atomic_compare_exchange_weak (73 samples, 0.19%)alloc::sync::Weak<T>::upgrade (92 samples, 0.24%)core::sync::atomic::AtomicUsize::fetch_update (88 samples, 0.23%)core::sync::atomic::AtomicUsize::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)core::option::Option<T>::map (99 samples, 0.25%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (99 samples, 0.25%)zssp::zssp::Context<Crypto>::receive (7 samples, 0.02%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (45 samples, 0.12%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (45 samples, 0.12%)core::sync::atomic::AtomicUsize::fetch_sub (43 samples, 0.11%)core::sync::atomic::atomic_sub (43 samples, 0.11%)__rdl_dealloc (4 samples, 0.01%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::dealloc (4 samples, 0.01%)__rust_dealloc (9 samples, 0.02%)[libc.so.6] (76 samples, 0.20%)__entry_text_start (13 samples, 0.03%)futex_unqueue (17 samples, 0.04%)_raw_spin_lock (4 samples, 0.01%)cpuacct_charge (10 samples, 0.03%)update_curr (18 samples, 0.05%)dequeue_entity (32 samples, 0.08%)update_load_avg (5 samples, 0.01%)dequeue_task (42 samples, 0.11%)dequeue_task_fair (42 samples, 0.11%)__perf_event_task_sched_in (9 samples, 0.02%)perf_ctx_enable (9 samples, 0.02%)x86_pmu_enable (9 samples, 0.02%)intel_pmu_enable_all (9 samples, 0.02%)native_write_msr (9 samples, 0.02%)finish_task_switch.isra.0 (29 samples, 0.07%)raw_spin_rq_unlock (12 samples, 0.03%)pick_next_task_fair (4 samples, 0.01%)pick_next_task (10 samples, 0.03%)put_prev_task_fair (6 samples, 0.02%)prepare_task_switch (15 samples, 0.04%)__perf_event_task_sched_out (6 samples, 0.02%)psi_group_change (38 samples, 0.10%)psi_task_switch (52 samples, 0.13%)__schedule (179 samples, 0.46%)update_rq_clock (5 samples, 0.01%)sched_clock_cpu (5 samples, 0.01%)sched_clock (4 samples, 0.01%)native_sched_clock (4 samples, 0.01%)futex_wait_queue (199 samples, 0.51%)schedule (187 samples, 0.48%)__get_user_nocheck_4 (26 samples, 0.07%)_raw_spin_lock (4 samples, 0.01%)futex_hash (5 samples, 0.01%)futex_q_lock (5 samples, 0.01%)futex_q_unlock (5 samples, 0.01%)futex_wait (278 samples, 0.72%)futex_wait_setup (52 samples, 0.13%)do_futex (282 samples, 0.73%)__x64_sys_futex (292 samples, 0.75%)__get_user_8 (5 samples, 0.01%)rseq_ip_fixup (11 samples, 0.03%)exit_to_user_mode_loop (24 samples, 0.06%)__rseq_handle_notify_resume (15 samples, 0.04%)exit_to_user_mode_prepare (31 samples, 0.08%)do_syscall_64 (331 samples, 0.85%)syscall_exit_to_user_mode (36 samples, 0.09%)__lll_lock_wait_private (397 samples, 1.02%)entry_SYSCALL_64_after_hwframe (335 samples, 0.86%)[libc.so.6] (911 samples, 2.35%)[..__entry_text_start (15 samples, 0.04%)do_syscall_64 (4 samples, 0.01%)_raw_spin_lock (5 samples, 0.01%)futex_hash (8 samples, 0.02%)_raw_spin_lock (25 samples, 0.06%)native_queued_spin_lock_slowpath (25 samples, 0.06%)_raw_spin_unlock (4 samples, 0.01%)preempt_schedule_thunk (4 samples, 0.01%)preempt_schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)futex_wake (77 samples, 0.20%)do_futex (97 samples, 0.25%)__x64_sys_futex (101 samples, 0.26%)do_syscall_64 (113 samples, 0.29%)syscall_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (5 samples, 0.01%)alloc::alloc::dealloc (1,146 samples, 2.95%)all..cfree (1,133 samples, 2.92%)cf..__lll_lock_wake_private (146 samples, 0.38%)entry_SYSCALL_64_after_hwframe (120 samples, 0.31%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (1,151 samples, 2.96%)<al..zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (1,169 samples, 3.01%)cor..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (1,169 samples, 3.01%)<ar..arrayvec::arrayvec::ArrayVec<T,_>::clear (1,169 samples, 3.01%)arr..arrayvec::arrayvec_impl::ArrayVecImpl::clear (1,169 samples, 3.01%)arr..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (1,169 samples, 3.01%)arr..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (1,162 samples, 2.99%)cor..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (1,161 samples, 2.99%)cor..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (1,161 samples, 2.99%)cor..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (1,161 samples, 2.99%)<al..alloc::raw_vec::RawVec<T,A>::current_memory (9 samples, 0.02%)std::sync::poison::Flag::done (4 samples, 0.01%)std::thread::panicking (4 samples, 0.01%)std::panicking::panicking (4 samples, 0.01%)std::panicking::panic_count::count_is_zero (4 samples, 0.01%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (83 samples, 0.21%)std::sys::unix::locks::futex_mutex::Mutex::unlock (79 samples, 0.20%)core::sync::atomic::AtomicU32::swap (76 samples, 0.20%)core::sync::atomic::atomic_swap (76 samples, 0.20%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (87 samples, 0.22%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (4 samples, 0.01%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (88 samples, 0.23%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (88 samples, 0.23%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (88 samples, 0.23%)core::sync::atomic::AtomicU32::fetch_sub (80 samples, 0.21%)core::sync::atomic::atomic_sub (80 samples, 0.21%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (59 samples, 0.15%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (59 samples, 0.15%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (59 samples, 0.15%)core::sync::atomic::AtomicU32::fetch_sub (54 samples, 0.14%)core::sync::atomic::atomic_sub (54 samples, 0.14%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (6 samples, 0.02%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (6 samples, 0.02%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::add (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::offset (6 samples, 0.02%)core::num::<impl u64>::rotate_left (9 samples, 0.02%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (22 samples, 0.06%)core::num::<impl u64>::wrapping_add (6 samples, 0.02%)core::num::<impl u64>::rotate_left (15 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (78 samples, 0.20%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (78 samples, 0.20%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (78 samples, 0.20%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (44 samples, 0.11%)core::num::<impl u64>::wrapping_add (20 samples, 0.05%)hashbrown::map::make_hash (98 samples, 0.25%)core::hash::BuildHasher::hash_one (98 samples, 0.25%)core::hash::impls::<impl core::hash::Hash for &T>::hash (15 samples, 0.04%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (15 samples, 0.04%)core::hash::impls::<impl core::hash::Hash for u32>::hash (15 samples, 0.04%)core::hash::Hasher::write_u32 (15 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (15 samples, 0.04%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (15 samples, 0.04%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (15 samples, 0.04%)core::hash::sip::u8to64_le (10 samples, 0.03%)<hashbrown::raw::bitmask::BitMaskIter as core::iter::traits::iterator::Iterator>::next (6 samples, 0.02%)hashbrown::map::equivalent_key::_{{closure}} (10 samples, 0.03%)<core::num::nonzero::NonZeroU32 as core::cmp::PartialEq>::eq (10 samples, 0.03%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (17 samples, 0.04%)hashbrown::raw::Bucket<T>::as_ref (7 samples, 0.02%)hashbrown::raw::Bucket<T>::as_ptr (7 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::sub (7 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::offset (7 samples, 0.02%)hashbrown::raw::h2 (14 samples, 0.04%)hashbrown::map::HashMap<K,V,S,A>::get_inner (149 samples, 0.38%)hashbrown::raw::RawTable<T,A>::get (51 samples, 0.13%)hashbrown::raw::RawTable<T,A>::find (51 samples, 0.13%)hashbrown::raw::RawTableInner<A>::find_inner (51 samples, 0.13%)hashbrown::raw::sse2::Group::match_byte (4 samples, 0.01%)core::core_arch::x86::sse2::_mm_movemask_epi8 (4 samples, 0.01%)std::collections::hash::map::HashMap<K,V,S>::get (153 samples, 0.39%)hashbrown::map::HashMap<K,V,S,A>::get (153 samples, 0.39%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)std::sync::mutex::MutexGuard<T>::new (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (50 samples, 0.13%)std::sys::unix::locks::futex_mutex::Mutex::lock (44 samples, 0.11%)core::sync::atomic::AtomicU32::compare_exchange (42 samples, 0.11%)core::sync::atomic::atomic_compare_exchange (42 samples, 0.11%)core::sync::atomic::AtomicU32::compare_exchange_weak (135 samples, 0.35%)core::sync::atomic::atomic_compare_exchange_weak (135 samples, 0.35%)std::sync::rwlock::RwLock<T>::read (158 samples, 0.41%)std::sys::unix::locks::futex_rwlock::RwLock::read (158 samples, 0.41%)std::sys::unix::locks::futex_rwlock::is_read_lockable (8 samples, 0.02%)zssp::antireplay::Window<_,_>::check (10 samples, 0.03%)core::sync::atomic::AtomicU64::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (11 samples, 0.03%)core::ptr::write (11 samples, 0.03%)zssp::fragged::Fragged<Fragment,_>::assemble (59 samples, 0.15%)<T as core::convert::TryInto<U>>::try_into (39 samples, 0.10%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (39 samples, 0.10%)core::result::Result<T,E>::map (39 samples, 0.10%)zssp::zeta::from_nonce (44 samples, 0.11%)<core::slice::iter::IterMut<T> as core::iter::traits::iterator::Iterator>::next (4 samples, 0.01%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (60 samples, 0.15%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (60 samples, 0.15%)core::sync::atomic::AtomicU32::fetch_sub (60 samples, 0.15%)core::sync::atomic::atomic_sub (60 samples, 0.15%)EVP_CIPHER_CTX_get_block_size (5 samples, 0.01%)CRYPTO_gcm128_decrypt (268 samples, 0.69%)[libcrypto.so.3] (104 samples, 0.27%)CRYPTO_gcm128_decrypt_ctr32 (512 samples, 1.32%)[libcrypto.so.3] (396 samples, 1.02%)[libcrypto.so.3] (25 samples, 0.06%)CRYPTO_gcm128_setiv (25 samples, 0.06%)[libcrypto.so.3] (22 samples, 0.06%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,728 samples, 12.18%)<zssp::crypto_impl..zssp::crypto_impl::openssl::CipherCtx::update (4,725 samples, 12.17%)zssp::crypto_impl:..EVP_DecryptUpdate (4,725 samples, 12.17%)EVP_DecryptUpdate[libcrypto.so.3] (4,634 samples, 11.93%)[libcrypto.so.3][libcrypto.so.3] (4,632 samples, 11.93%)[libcrypto.so.3][libcrypto.so.3] (4,611 samples, 11.87%)[libcrypto.so.3][libcrypto.so.3] (3,756 samples, 9.67%)[libcrypto.so...[libcrypto.so.3] (3,594 samples, 9.26%)[libcrypto.so..core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (7 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (7 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (7 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::unlock (6 samples, 0.02%)core::sync::atomic::AtomicU32::swap (6 samples, 0.02%)core::sync::atomic::atomic_swap (6 samples, 0.02%)[libc.so.6] (4 samples, 0.01%)CRYPTO_gcm128_finish (47 samples, 0.12%)[libcrypto.so.3] (28 samples, 0.07%)zssp::crypto_impl::openssl::CipherCtx::finalize (63 samples, 0.16%)EVP_DecryptFinal_ex (63 samples, 0.16%)[libcrypto.so.3] (58 samples, 0.15%)[libcrypto.so.3] (56 samples, 0.14%)[libcrypto.so.3] (51 samples, 0.13%)OSSL_PARAM_locate (27 samples, 0.07%)[libc.so.6] (12 samples, 0.03%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (152 samples, 0.39%)zssp::crypto_impl::openssl::CipherCtx::set_tag (82 samples, 0.21%)EVP_CIPHER_CTX_ctrl (82 samples, 0.21%)[libcrypto.so.3] (41 samples, 0.11%)std::sync::mutex::Mutex<T>::lock (16 samples, 0.04%)std::sys::unix::locks::futex_mutex::Mutex::lock (16 samples, 0.04%)core::sync::atomic::AtomicU32::compare_exchange (15 samples, 0.04%)core::sync::atomic::atomic_compare_exchange (15 samples, 0.04%)[libc.so.6] (22 samples, 0.06%)OSSL_PARAM_locate (47 samples, 0.12%)EVP_CIPHER_CTX_get_iv_length (68 samples, 0.18%)[libcrypto.so.3] (52 samples, 0.13%)[libc.so.6] (27 samples, 0.07%)OSSL_PARAM_locate (49 samples, 0.13%)strcmp@plt (7 samples, 0.02%)EVP_CIPHER_CTX_set_padding (86 samples, 0.22%)[libcrypto.so.3] (66 samples, 0.17%)OSSL_PARAM_locate_const (6 samples, 0.02%)EVP_CIPHER_free (15 samples, 0.04%)EVP_CipherInit_ex (252 samples, 0.65%)[libcrypto.so.3] (252 samples, 0.65%)EVP_CIPHER_up_ref (51 samples, 0.13%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (270 samples, 0.70%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (254 samples, 0.65%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (7 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (7 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::remaining_capacity (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (240 samples, 0.62%)core::intrinsics::copy_nonoverlapping (238 samples, 0.61%)[libc.so.6] (238 samples, 0.61%)std::io::impls::<impl std::io::Write for &mut W>::write (247 samples, 0.64%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (247 samples, 0.64%)zssp::zeta::receive_payload_in_place (5,500 samples, 14.16%)zssp::zeta::receive_pa..zssp::antireplay::Window<_,_>::update (8 samples, 0.02%)core::sync::atomic::AtomicU64::fetch_max (8 samples, 0.02%)core::sync::atomic::atomic_umax (8 samples, 0.02%)zssp::zssp::Context<Crypto>::receive (8,391 samples, 21.61%)zssp::zssp::Context<Crypto>::recei..zssp::zssp::parse_fragment_header (101 samples, 0.26%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (73 samples, 0.19%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (73 samples, 0.19%)std::sys::unix::locks::futex_mutex::Mutex::unlock (70 samples, 0.18%)core::sync::atomic::AtomicU32::swap (66 samples, 0.17%)core::sync::atomic::atomic_swap (66 samples, 0.17%)std::sync::mutex::Mutex<T>::lock (77 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (76 samples, 0.20%)core::sync::atomic::AtomicU32::compare_exchange (73 samples, 0.19%)core::sync::atomic::atomic_compare_exchange (73 samples, 0.19%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (321 samples, 0.83%)zssp::crypto_impl::openssl::CipherCtx::update (162 samples, 0.42%)EVP_EncryptUpdate (158 samples, 0.41%)[libcrypto.so.3] (139 samples, 0.36%)[libcrypto.so.3] (108 samples, 0.28%)[libcrypto.so.3] (99 samples, 0.25%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)<std::sync::mutex::MutexGuard<T> as core::ops::deref::Deref>::deref (10 samples, 0.03%)[libcrypto.so.3] (174 samples, 0.45%)CRYPTO_gcm128_encrypt (280 samples, 0.72%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)CRYPTO_gcm128_encrypt_ctr32 (476 samples, 1.23%)[libcrypto.so.3] (345 samples, 0.89%)[libcrypto.so.3] (31 samples, 0.08%)CRYPTO_gcm128_setiv (18 samples, 0.05%)[libcrypto.so.3] (15 samples, 0.04%)perf_ctx_enable (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,908 samples, 10.06%)<zssp::crypto_i..zssp::crypto_impl::openssl::CipherCtx::update (3,898 samples, 10.04%)zssp::crypto_im..EVP_EncryptUpdate (3,895 samples, 10.03%)EVP_EncryptUpda..[libcrypto.so.3] (3,844 samples, 9.90%)[libcrypto.so...[libcrypto.so.3] (3,838 samples, 9.88%)[libcrypto.so...[libcrypto.so.3] (3,819 samples, 9.83%)[libcrypto.so...[libcrypto.so.3] (3,007 samples, 7.74%)[libcrypto...[libcrypto.so.3] (2,753 samples, 7.09%)[libcrypto..asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)irqentry_exit_to_user_mode (10 samples, 0.03%)exit_to_user_mode_prepare (10 samples, 0.03%)exit_to_user_mode_loop (10 samples, 0.03%)schedule (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_pmu_nop_void (5 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (11 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (11 samples, 0.03%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (11 samples, 0.03%)std::sys::unix::locks::futex_mutex::Mutex::unlock (11 samples, 0.03%)core::sync::atomic::AtomicU32::swap (11 samples, 0.03%)core::sync::atomic::atomic_swap (11 samples, 0.03%)zssp::crypto_impl::openssl::CipherCtx::finalize (60 samples, 0.15%)EVP_EncryptFinal_ex (60 samples, 0.15%)[libcrypto.so.3] (53 samples, 0.14%)[libcrypto.so.3] (53 samples, 0.14%)[libcrypto.so.3] (48 samples, 0.12%)CRYPTO_gcm128_tag (48 samples, 0.12%)CRYPTO_gcm128_finish (40 samples, 0.10%)[libcrypto.so.3] (26 samples, 0.07%)[libc.so.6] (21 samples, 0.05%)OSSL_PARAM_locate (45 samples, 0.12%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (153 samples, 0.39%)zssp::crypto_impl::openssl::CipherCtx::get_tag (80 samples, 0.21%)EVP_CIPHER_CTX_ctrl (79 samples, 0.20%)[libcrypto.so.3] (55 samples, 0.14%)OSSL_PARAM_set_octet_string (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (13 samples, 0.03%)std::sys::unix::locks::futex_mutex::Mutex::lock (13 samples, 0.03%)core::sync::atomic::AtomicU32::compare_exchange (13 samples, 0.03%)core::sync::atomic::atomic_compare_exchange (13 samples, 0.03%)[libcrypto.so.3] (40 samples, 0.10%)OSSL_PARAM_locate (36 samples, 0.09%)[libc.so.6] (20 samples, 0.05%)EVP_CIPHER_CTX_get_iv_length (67 samples, 0.17%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)[libc.so.6] (17 samples, 0.04%)OSSL_PARAM_locate (31 samples, 0.08%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (67 samples, 0.17%)[libcrypto.so.3] (49 samples, 0.13%)OSSL_PARAM_locate_const (6 samples, 0.02%)EVP_CIPHER_free (21 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (206 samples, 0.53%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (193 samples, 0.50%)EVP_CipherInit_ex (193 samples, 0.50%)[libcrypto.so.3] (193 samples, 0.50%)EVP_CIPHER_up_ref (27 samples, 0.07%)__rdl_alloc (6 samples, 0.02%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (6 samples, 0.02%)[libc.so.6] (60 samples, 0.15%)[libc.so.6] (978 samples, 2.52%)[l..asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)__entry_text_start (4 samples, 0.01%)__get_user_nocheck_4 (14 samples, 0.04%)futex_q_lock (5 samples, 0.01%)futex_q_unlock (17 samples, 0.04%)futex_wait (45 samples, 0.12%)futex_wait_setup (42 samples, 0.11%)do_futex (46 samples, 0.12%)__x64_sys_futex (47 samples, 0.12%)entry_SYSCALL_64_after_hwframe (52 samples, 0.13%)do_syscall_64 (52 samples, 0.13%)syscall_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)__lll_lock_wait_private (74 samples, 0.19%)__entry_text_start (10 samples, 0.03%)futex_hash (7 samples, 0.02%)_raw_spin_lock (8 samples, 0.02%)native_queued_spin_lock_slowpath (8 samples, 0.02%)futex_wake_mark (10 samples, 0.03%)wake_q_add_safe (4 samples, 0.01%)__smp_call_single_queue (12 samples, 0.03%)native_send_call_func_single_ipi (8 samples, 0.02%)x2apic_send_IPI (7 samples, 0.02%)native_write_msr (5 samples, 0.01%)llist_add_batch (10 samples, 0.03%)try_to_wake_up (77 samples, 0.20%)ttwu_queue_wakelist (37 samples, 0.10%)futex_wake (145 samples, 0.37%)wake_up_q (85 samples, 0.22%)do_futex (158 samples, 0.41%)__x64_sys_futex (159 samples, 0.41%)exit_to_user_mode_prepare (6 samples, 0.02%)do_syscall_64 (172 samples, 0.44%)syscall_exit_to_user_mode (8 samples, 0.02%)entry_SYSCALL_64_after_hwframe (175 samples, 0.45%)alloc::vec::Vec<T,A>::with_capacity_in (1,972 samples, 5.08%)alloc:..alloc::raw_vec::RawVec<T,A>::with_capacity_in (1,972 samples, 5.08%)alloc:..alloc::raw_vec::RawVec<T,A>::allocate_in (1,972 samples, 5.08%)alloc:..<alloc::alloc::Global as core::alloc::Allocator>::allocate (1,970 samples, 5.07%)<alloc..alloc::alloc::Global::alloc_impl (1,970 samples, 5.07%)alloc:..alloc::alloc::alloc (1,970 samples, 5.07%)alloc:..malloc (1,962 samples, 5.05%)malloc__lll_lock_wake_private (192 samples, 0.49%)alloc::slice::<impl [T]>::to_vec (2,175 samples, 5.60%)alloc::..alloc::slice::<impl [T]>::to_vec_in (2,175 samples, 5.60%)alloc::..alloc::slice::hack::to_vec (2,175 samples, 5.60%)alloc::..<T as alloc::slice::hack::ConvertVec>::to_vec (2,175 samples, 5.60%)<T as a..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (203 samples, 0.52%)core::intrinsics::copy_nonoverlapping (203 samples, 0.52%)[libc.so.6] (201 samples, 0.52%)core::sync::atomic::AtomicUsize::compare_exchange_weak (118 samples, 0.30%)core::sync::atomic::atomic_compare_exchange_weak (118 samples, 0.30%)std::sync::mpmc::array::Channel<T>::start_send (183 samples, 0.47%)core::sync::atomic::AtomicUsize::load (37 samples, 0.10%)core::sync::atomic::atomic_load (37 samples, 0.10%)core::ptr::mut_ptr::<impl *mut T>::write (7 samples, 0.02%)core::ptr::write (7 samples, 0.02%)benchmark::bob_main::_{{closure}} (3,947 samples, 10.16%)benchmark::bob_..std::sync::mpsc::SyncSender<T>::send (1,771 samples, 4.56%)std::..std::sync::mpmc::Sender<T>::send (1,764 samples, 4.54%)std::..std::sync::mpmc::array::Channel<T>::send (219 samples, 0.56%)std::sync::mpmc::array::Channel<T>::write (17 samples, 0.04%)std::sync::mpmc::waker::SyncWaker::notify (5 samples, 0.01%)core::mem::drop (38 samples, 0.10%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (38 samples, 0.10%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (38 samples, 0.10%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (38 samples, 0.10%)core::sync::atomic::AtomicU32::fetch_sub (36 samples, 0.09%)core::sync::atomic::atomic_sub (36 samples, 0.09%)core::slice::<impl [T]>::copy_from_slice (10 samples, 0.03%)core::intrinsics::copy_nonoverlapping (10 samples, 0.03%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index (6 samples, 0.02%)core::slice::index::<impl core::ops::index::Index<I> for [T]>::index (11 samples, 0.03%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index (5 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (5 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (5 samples, 0.01%)core::sync::atomic::AtomicU32::compare_exchange_weak (34 samples, 0.09%)core::sync::atomic::atomic_compare_exchange_weak (34 samples, 0.09%)std::sync::rwlock::RwLock<T>::read (38 samples, 0.10%)std::sys::unix::locks::futex_rwlock::RwLock::read (38 samples, 0.10%)benchmark::bob_main (17,970 samples, 46.28%)benchmark::bob_mainzssp::zssp::Context<Crypto>::send (8,668 samples, 22.32%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,667 samples, 22.32%)zssp::zeta::send_payloadzssp::zeta::get_counter (8 samples, 0.02%)core::sync::atomic::AtomicU64::fetch_add (7 samples, 0.02%)core::sync::atomic::atomic_add (7 samples, 0.02%)cfree (25 samples, 0.06%)clock_gettime (17 samples, 0.04%)core::hash::BuildHasher::hash_one (25 samples, 0.06%)core::hash::impls::<impl core::hash::Hash for &T>::hash (18 samples, 0.05%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (4 samples, 0.01%)core::result::Result<T,E>::unwrap (10 samples, 0.03%)malloc (11 samples, 0.03%)std::sync::mpmc::Receiver<T>::recv_timeout (25 samples, 0.06%)std::sync::mpmc::Receiver<T>::recv_deadline (10 samples, 0.03%)std::sync::mpmc::Sender<T>::send (13 samples, 0.03%)std::sync::mpmc::array::Channel<T>::recv (28 samples, 0.07%)std::sync::mpmc::array::Channel<T>::send (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::start_recv (21 samples, 0.05%)std::sync::mpmc::utils::Backoff::new (22 samples, 0.06%)std::sync::mpmc::waker::SyncWaker::notify (22 samples, 0.06%)core::sync::atomic::AtomicBool::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (9 samples, 0.02%)std::sys::unix::time::Timespec::sub_timespec (9 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (9 samples, 0.02%)std::time::SystemTime::checked_add (7 samples, 0.02%)zssp::fragged::Fragged<Fragment,_>::assemble (19 samples, 0.05%)zssp::zeta::from_nonce (8 samples, 0.02%)alloc::vec::Vec<T,A>::with_capacity_in (20 samples, 0.05%)alloc::raw_vec::RawVec<T,A>::with_capacity_in (20 samples, 0.05%)alloc::raw_vec::RawVec<T,A>::allocate_in (20 samples, 0.05%)<alloc::alloc::Global as core::alloc::Allocator>::allocate (20 samples, 0.05%)alloc::alloc::Global::alloc_impl (20 samples, 0.05%)alloc::alloc::alloc (20 samples, 0.05%)alloc::slice::<impl [T]>::to_vec (29 samples, 0.07%)alloc::slice::<impl [T]>::to_vec_in (29 samples, 0.07%)alloc::slice::hack::to_vec (29 samples, 0.07%)<T as alloc::slice::hack::ConvertVec>::to_vec (29 samples, 0.07%)core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (9 samples, 0.02%)core::intrinsics::copy_nonoverlapping (9 samples, 0.02%)zssp::zeta::send_payload (76 samples, 0.20%)benchmark::bob_main::_{{closure}} (34 samples, 0.09%)std::sync::mpsc::SyncSender<T>::send (5 samples, 0.01%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (6 samples, 0.02%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::clear (6 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (6 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (6 samples, 0.02%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (6 samples, 0.02%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (6 samples, 0.02%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (6 samples, 0.02%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (6 samples, 0.02%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (6 samples, 0.02%)alloc::alloc::dealloc (6 samples, 0.02%)std::collections::hash::map::HashMap<K,V,S>::get (5 samples, 0.01%)hashbrown::map::HashMap<K,V,S,A>::get (5 samples, 0.01%)hashbrown::map::HashMap<K,V,S,A>::get_inner (5 samples, 0.01%)hashbrown::map::make_hash (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (78 samples, 0.20%)std::sync::mpmc::array::Channel<T>::start_recv (8 samples, 0.02%)[unknown] (37,624 samples, 96.89%)[unknown]zssp::zssp::parse_fragment_header (7 samples, 0.02%)EVP_DecryptUpdate (24 samples, 0.06%)__bss_start (26 samples, 0.07%)__rust_probestack (6 samples, 0.02%)entry_SYSCALL_64_after_hwframe (7 samples, 0.02%)entry_SYSCALL_64_safe_stack (14 samples, 0.04%)ret_from_fork (10 samples, 0.03%)schedule_tail (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_ctx_enable (10 samples, 0.03%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (5 samples, 0.01%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (5 samples, 0.01%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (5 samples, 0.01%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (5 samples, 0.01%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (5 samples, 0.01%)alloc::alloc::dealloc (5 samples, 0.01%)benchmark (38,828 samples, 99.99%)benchmarkzssp::zssp::Context<Crypto>::receive (58 samples, 0.15%)std::collections::hash::map::HashMap<K,V,S>::get (6 samples, 0.02%)hashbrown::map::HashMap<K,V,S,A>::get (6 samples, 0.02%)hashbrown::map::HashMap<K,V,S,A>::get_inner (6 samples, 0.02%)hashbrown::map::make_hash (6 samples, 0.02%)all (38,833 samples, 100%)perf-exec (5 samples, 0.01%)entry_SYSCALL_64_after_hwframe (5 samples, 0.01%)do_syscall_64 (5 samples, 0.01%)__x64_sys_execve (5 samples, 0.01%)do_execveat_common.isra.0 (5 samples, 0.01%)bprm_execve (5 samples, 0.01%)bprm_execve.part.0 (5 samples, 0.01%)exec_binprm (5 samples, 0.01%)search_binary_handler (5 samples, 0.01%)load_elf_binary (5 samples, 0.01%)begin_new_exec (5 samples, 0.01%)perf_event_exec (5 samples, 0.01%)perf_event_enable_on_exec (4 samples, 0.01%)ctx_resched (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%) \ No newline at end of file diff --git a/src/crypto_impl/openssl.rs b/src/crypto_impl/openssl.rs index 596352f..ce2b25b 100644 --- a/src/crypto_impl/openssl.rs +++ b/src/crypto_impl/openssl.rs @@ -1,10 +1,9 @@ use std::{ ptr::{self, NonNull}, - sync::Mutex, + sync::{Mutex, MutexGuard}, }; use openssl_sys::*; -use zeroize::Zeroizing; use crate::crypto::*; @@ -150,8 +149,8 @@ impl Aes256Dec for Aes256OpenSSLDec { } } -pub struct AesGcmOpenSSLEnc(CipherCtx); -impl AesGcmEncContext for AesGcmOpenSSLEnc { +pub struct AesGcmOpenSSLEnc<'a>(MutexGuard<'a, CipherCtx>); +impl<'a> AesGcmEncContext for AesGcmOpenSSLEnc<'a> { fn encrypt(&mut self, input: &[u8], output: &mut [u8]) { unsafe { assert!(self.0.update::(input, output.as_mut_ptr())) }; } @@ -166,8 +165,8 @@ impl AesGcmEncContext for AesGcmOpenSSLEnc { } } -pub struct AesGcmOpenSSLDec(CipherCtx); -impl AesGcmDecContext for AesGcmOpenSSLDec { +pub struct AesGcmOpenSSLDec<'a>(MutexGuard<'a, CipherCtx>); +impl<'a> AesGcmDecContext for AesGcmOpenSSLDec<'a> { fn decrypt_in_place(&mut self, data: &mut [u8]) { let p = data.as_mut_ptr(); unsafe { assert!(self.0.update::(data, p)) }; @@ -179,39 +178,54 @@ impl AesGcmDecContext for AesGcmOpenSSLDec { } pub struct AesGcmOpenSSLPool { - enc_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, - dec_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, + enc: [Mutex; 8], + dec: [Mutex; 8], } -impl HighThroughputAesGcmPool for AesGcmOpenSSLPool { - type EncContext<'a> = AesGcmOpenSSLEnc; +unsafe impl Send for AesGcmOpenSSLPool {} +unsafe impl Sync for AesGcmOpenSSLPool {} - type DecContext<'a> = AesGcmOpenSSLDec; +impl HighThroughputAesGcmPool for AesGcmOpenSSLPool { + type EncContext<'a> = AesGcmOpenSSLEnc<'a>; + + type DecContext<'a> = AesGcmOpenSSLDec<'a>; fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self { - AesGcmOpenSSLPool { - enc_key: Zeroizing::new(*encrypt_key), - dec_key: Zeroizing::new(*decrypt_key), + unsafe { + AesGcmOpenSSLPool { + enc: std::array::from_fn(|_| { + let ctx = CipherCtx::new().unwrap(); + let t = openssl_sys::EVP_aes_256_gcm(); + assert!(ctx.cipher_init::(t, encrypt_key.as_ptr(), ptr::null())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + Mutex::new(ctx) + }), + dec: std::array::from_fn(|_| { + let ctx = CipherCtx::new().unwrap(); + let t = openssl_sys::EVP_aes_256_gcm(); + assert!(ctx.cipher_init::(t, decrypt_key.as_ptr(), ptr::null())); + openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + Mutex::new(ctx) + }), + } } } fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> AesGcmOpenSSLEnc { - let ctx = CipherCtx::new().unwrap(); + let i = u64::from_be_bytes(nonce[4..].try_into().unwrap()); + let g = self.enc[(i as usize) % self.enc.len()].lock().unwrap(); unsafe { - let t = openssl_sys::EVP_aes_256_gcm(); - assert!(ctx.cipher_init::(t, self.enc_key.as_ptr(), nonce.as_ptr())); - openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + assert!(g.cipher_init::(ptr::null(), ptr::null(), nonce.as_ptr())); } - AesGcmOpenSSLEnc(ctx) + AesGcmOpenSSLEnc(g) } fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> AesGcmOpenSSLDec { - let ctx = CipherCtx::new().unwrap(); + let i = u64::from_be_bytes(nonce[4..].try_into().unwrap()); + let g = self.dec[(i as usize) % self.enc.len()].lock().unwrap(); unsafe { - let t = openssl_sys::EVP_aes_256_gcm(); - assert!(ctx.cipher_init::(t, self.dec_key.as_ptr(), nonce.as_ptr())); - openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); + assert!(g.cipher_init::(ptr::null(), ptr::null(), nonce.as_ptr())); } - AesGcmOpenSSLDec(ctx) + AesGcmOpenSSLDec(g) } } From c8bee176a26d690f8c2814e3270c2f03ba48aa12 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 17:52:18 -0400 Subject: [PATCH 80/91] removed flamegraph.svg --- .gitignore | 1 + flamegraph.svg | 491 ------------------------------------------------- 2 files changed, 1 insertion(+), 491 deletions(-) delete mode 100644 flamegraph.svg diff --git a/.gitignore b/.gitignore index 68e7d6c..c3a4a94 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target perf*.data perf*.old +.svg diff --git a/flamegraph.svg b/flamegraph.svg deleted file mode 100644 index a00c032..0000000 --- a/flamegraph.svg +++ /dev/null @@ -1,491 +0,0 @@ -Flame Graph Reset ZoomSearch <zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (13 samples, 0.03%)zssp::crypto_impl::openssl::CipherCtx::update (7 samples, 0.02%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (28 samples, 0.07%)zssp::crypto_impl::openssl::CipherCtx::update (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (68 samples, 0.18%)zssp::crypto_impl::openssl::CipherCtx::finalize (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (6 samples, 0.02%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (102 samples, 0.26%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (4 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (5 samples, 0.01%)CRYPTO_gcm128_decrypt (8 samples, 0.02%)CRYPTO_gcm128_decrypt_ctr32 (22 samples, 0.06%)CRYPTO_gcm128_encrypt (17 samples, 0.04%)CRYPTO_gcm128_encrypt_ctr32 (15 samples, 0.04%)CRYPTO_gcm128_finish (7 samples, 0.02%)CRYPTO_gcm128_tag (4 samples, 0.01%)EVP_CIPHER_CTX_ctrl (9 samples, 0.02%)EVP_CIPHER_CTX_set_padding (14 samples, 0.04%)EVP_CIPHER_get_block_size (12 samples, 0.03%)EVP_CipherInit_ex (9 samples, 0.02%)EVP_DecryptUpdate (13 samples, 0.03%)EVP_EncryptFinal_ex (5 samples, 0.01%)OSSL_PARAM_locate (103 samples, 0.27%)[benchmark] (6 samples, 0.02%)EVP_DecryptUpdate (6 samples, 0.02%)[libc.so.6] (82 samples, 0.21%)[libcrypto.so.3] (124 samples, 0.32%)__lll_lock_wake_private (4 samples, 0.01%)malloc (13 samples, 0.03%)std::sync::mpmc::Sender<T>::send (5 samples, 0.01%)<std::sync::mpmc::select::Token as core::default::Default>::default (8 samples, 0.02%)std::sync::mpmc::array::Channel<T>::start_send (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::send (53 samples, 0.14%)std::sync::mpmc::array::Channel<T>::write (10 samples, 0.03%)std::sync::mpmc::array::Channel<T>::start_recv (8 samples, 0.02%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::try_recv (24 samples, 0.06%)std::sync::mpmc::array::Channel<T>::read (8 samples, 0.02%)std::sync::mpmc::utils::Backoff::new (9 samples, 0.02%)std::sync::mpmc::waker::SyncWaker::notify (13 samples, 0.03%)core::sync::atomic::AtomicBool::load (8 samples, 0.02%)core::sync::atomic::atomic_load (8 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (7 samples, 0.02%)<std::time::Instant as core::ops::arith::Sub>::sub (4 samples, 0.01%)std::time::Instant::duration_since (4 samples, 0.01%)std::time::Instant::checked_duration_since (4 samples, 0.01%)std::sys::unix::time::inner::Instant::checked_sub_instant (4 samples, 0.01%)std::time::Instant::elapsed (21 samples, 0.05%)syscall (12 samples, 0.03%)__entry_text_start (10 samples, 0.03%)[anon] (912 samples, 2.35%)[..zssp::zeta::receive_payload_in_place (23 samples, 0.06%)std::io::impls::<impl std::io::Write for &mut W>::write (5 samples, 0.01%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (5 samples, 0.01%)core::intrinsics::copy_nonoverlapping (5 samples, 0.01%)[libc.so.6] (6 samples, 0.02%)__rust_probestack (5 samples, 0.01%)[benchmark] (38 samples, 0.10%)zssp::zssp::Context<Crypto>::receive (22 samples, 0.06%)CRYPTO_gcm128_finish (11 samples, 0.03%)[libcrypto.so.3] (93 samples, 0.24%)std::sync::mpmc::utils::Backoff::new (5 samples, 0.01%)[libcrypto.so.3] (123 samples, 0.32%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (20 samples, 0.05%)<std::sync::mpmc::zero::ZeroToken as core::default::Default>::default (7 samples, 0.02%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (17 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (14 samples, 0.04%)zssp::crypto_impl::openssl::CipherCtx::update (14 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (5 samples, 0.01%)zssp::crypto_impl::openssl::CipherCtx::update (5 samples, 0.01%)CRYPTO_gcm128_decrypt (6 samples, 0.02%)CRYPTO_gcm128_decrypt_ctr32 (25 samples, 0.06%)CRYPTO_gcm128_encrypt (18 samples, 0.05%)CRYPTO_gcm128_encrypt_ctr32 (33 samples, 0.08%)CRYPTO_gcm128_finish (4 samples, 0.01%)CRYPTO_gcm128_setiv (4 samples, 0.01%)EVP_CIPHER_CTX_ctrl (16 samples, 0.04%)EVP_CIPHER_CTX_get_iv_length (11 samples, 0.03%)EVP_CIPHER_get_block_size (4 samples, 0.01%)EVP_CipherInit_ex (4 samples, 0.01%)EVP_DecryptUpdate (46 samples, 0.12%)EVP_EncryptUpdate (45 samples, 0.12%)OSSL_PARAM_locate (33 samples, 0.08%)[[vdso]] (16 samples, 0.04%)[benchmark] (9 samples, 0.02%)EVP_DecryptUpdate (9 samples, 0.02%)[libc.so.6] (73 samples, 0.19%)[libcrypto.so.3] (329 samples, 0.85%)__bss_start (20 samples, 0.05%)[libcrypto.so.3] (20 samples, 0.05%)__lll_lock_wait_private (4 samples, 0.01%)__entry_text_start (35 samples, 0.09%)crng_make_state (5 samples, 0.01%)_copy_to_iter (38 samples, 0.10%)copyout (28 samples, 0.07%)copyout (5 samples, 0.01%)__memcpy (14 samples, 0.04%)chacha_block_generic (267 samples, 0.69%)chacha_permute (242 samples, 0.62%)__x64_sys_getrandom (440 samples, 1.13%)get_random_bytes_user (428 samples, 1.10%)crng_make_state (343 samples, 0.88%)crng_fast_key_erasure (302 samples, 0.78%)exit_to_user_mode_prepare (19 samples, 0.05%)do_syscall_64 (474 samples, 1.22%)syscall_exit_to_user_mode (24 samples, 0.06%)entry_SYSCALL_64_after_hwframe (494 samples, 1.27%)syscall_exit_to_user_mode (4 samples, 0.01%)<rand_core::os::OsRng as rand_core::RngCore>::next_u64 (593 samples, 1.53%)rand_core::impls::next_u64_via_fill (593 samples, 1.53%)<rand_core::os::OsRng as rand_core::RngCore>::fill_bytes (593 samples, 1.53%)<rand_core::os::OsRng as rand_core::RngCore>::try_fill_bytes (587 samples, 1.51%)getrandom::getrandom (587 samples, 1.51%)getrandom::getrandom_uninit (587 samples, 1.51%)getrandom::imp::getrandom_inner (586 samples, 1.51%)getrandom::util_libc::sys_fill_exact (583 samples, 1.50%)getrandom::imp::getrandom_inner::_{{closure}} (577 samples, 1.49%)getrandom::imp::getrandom (577 samples, 1.49%)syscall (575 samples, 1.48%)syscall_return_via_sysret (9 samples, 0.02%)[libc.so.6] (20 samples, 0.05%)arrayvec::arrayvec::ArrayVec<T,_>::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (7 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (7 samples, 0.02%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (12 samples, 0.03%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (12 samples, 0.03%)core::sync::atomic::AtomicUsize::fetch_sub (12 samples, 0.03%)core::sync::atomic::atomic_sub (12 samples, 0.03%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)_ZN3std4sync4mpmc5array16Channel$LT$T$GT$10start_recv17h7800ca29c64cb868E.llvm.12455019271255371362 (9 samples, 0.02%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (20 samples, 0.05%)std::sync::mpmc::array::Channel<T>::read (25 samples, 0.06%)std::sync::mpmc::waker::SyncWaker::notify (5 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (44 samples, 0.11%)core::sync::atomic::atomic_compare_exchange_weak (44 samples, 0.11%)core::sync::atomic::AtomicUsize::load (177 samples, 0.46%)core::sync::atomic::atomic_load (177 samples, 0.46%)core::sync::atomic::fence (10 samples, 0.03%)std::sync::mpsc::Receiver<T>::try_recv (337 samples, 0.87%)std::sync::mpmc::Receiver<T>::try_recv (337 samples, 0.87%)std::sync::mpmc::array::Channel<T>::try_recv (326 samples, 0.84%)std::sync::mpmc::array::Channel<T>::start_recv (270 samples, 0.70%)<std::time::Instant as core::ops::arith::Sub>::sub (8 samples, 0.02%)std::time::Instant::duration_since (8 samples, 0.02%)std::time::Instant::checked_duration_since (8 samples, 0.02%)std::sys::unix::time::inner::Instant::checked_sub_instant (8 samples, 0.02%)std::sys::unix::time::Timespec::sub_timespec (8 samples, 0.02%)[[vdso]] (33 samples, 0.08%)[[vdso]] (19 samples, 0.05%)std::time::Instant::elapsed (44 samples, 0.11%)std::time::Instant::now (36 samples, 0.09%)std::sys::unix::time::inner::Instant::now (36 samples, 0.09%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (35 samples, 0.09%)clock_gettime (35 samples, 0.09%)<T as core::convert::TryInto<U>>::try_into (401 samples, 1.03%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (401 samples, 1.03%)core::result::Result<T,E>::map (401 samples, 1.03%)<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (4 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (71 samples, 0.18%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (71 samples, 0.18%)std::sys::unix::locks::futex_mutex::Mutex::unlock (64 samples, 0.16%)core::sync::atomic::AtomicU32::swap (60 samples, 0.15%)core::sync::atomic::atomic_swap (60 samples, 0.15%)std::sync::mutex::MutexGuard<T>::new (5 samples, 0.01%)std::sync::poison::Flag::guard (5 samples, 0.01%)std::thread::panicking (5 samples, 0.01%)std::panicking::panicking (5 samples, 0.01%)std::panicking::panic_count::count_is_zero (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (77 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (72 samples, 0.19%)core::sync::atomic::AtomicU32::compare_exchange (70 samples, 0.18%)core::sync::atomic::atomic_compare_exchange (70 samples, 0.18%)EVP_CIPHER_CTX_get_block_size (5 samples, 0.01%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (394 samples, 1.01%)zssp::crypto_impl::openssl::CipherCtx::update (243 samples, 0.63%)EVP_DecryptUpdate (242 samples, 0.62%)[libcrypto.so.3] (204 samples, 0.53%)[libcrypto.so.3] (152 samples, 0.39%)[libcrypto.so.3] (144 samples, 0.37%)__rust_probestack (9 samples, 0.02%)core::cmp::Ord::max (4 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::num::nonzero::NonZeroU32::new (13 samples, 0.03%)asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)irqentry_exit_to_user_mode (10 samples, 0.03%)exit_to_user_mode_prepare (10 samples, 0.03%)exit_to_user_mode_loop (10 samples, 0.03%)schedule (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_pmu_nop_void (5 samples, 0.01%)alloc::sync::Weak<T>::upgrade::_{{closure}} (9 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (61 samples, 0.16%)core::sync::atomic::atomic_compare_exchange_weak (61 samples, 0.16%)alloc::sync::Weak<T>::upgrade (81 samples, 0.21%)core::sync::atomic::AtomicUsize::fetch_update (78 samples, 0.20%)core::sync::atomic::AtomicUsize::load (4 samples, 0.01%)core::sync::atomic::atomic_load (4 samples, 0.01%)core::option::Option<T>::map (82 samples, 0.21%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (82 samples, 0.21%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (55 samples, 0.14%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (55 samples, 0.14%)core::sync::atomic::AtomicUsize::fetch_sub (53 samples, 0.14%)core::sync::atomic::atomic_sub (53 samples, 0.14%)__rdl_dealloc (4 samples, 0.01%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::dealloc (4 samples, 0.01%)__rust_dealloc (5 samples, 0.01%)[libc.so.6] (48 samples, 0.12%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)__entry_text_start (14 samples, 0.04%)futex_unqueue (10 samples, 0.03%)update_curr (11 samples, 0.03%)cpuacct_charge (5 samples, 0.01%)update_load_avg (8 samples, 0.02%)dequeue_entity (28 samples, 0.07%)dequeue_task (37 samples, 0.10%)dequeue_task_fair (36 samples, 0.09%)__perf_event_task_sched_in (8 samples, 0.02%)perf_ctx_enable (8 samples, 0.02%)finish_task_switch.isra.0 (18 samples, 0.05%)raw_spin_rq_unlock (9 samples, 0.02%)pick_next_task_fair (4 samples, 0.01%)newidle_balance (4 samples, 0.01%)pick_next_task (11 samples, 0.03%)prepare_task_switch (14 samples, 0.04%)__perf_event_task_sched_out (5 samples, 0.01%)psi_group_change (26 samples, 0.07%)psi_task_switch (44 samples, 0.11%)__schedule (149 samples, 0.38%)futex_wait_queue (165 samples, 0.42%)schedule (159 samples, 0.41%)__get_user_nocheck_4 (16 samples, 0.04%)_raw_spin_lock (5 samples, 0.01%)futex_q_lock (4 samples, 0.01%)futex_q_unlock (7 samples, 0.02%)futex_wait_setup (41 samples, 0.11%)futex_wait (230 samples, 0.59%)do_futex (236 samples, 0.61%)__x64_sys_futex (247 samples, 0.64%)__rseq_handle_notify_resume (7 samples, 0.02%)exit_to_user_mode_loop (21 samples, 0.05%)exit_to_user_mode_prepare (27 samples, 0.07%)do_syscall_64 (283 samples, 0.73%)syscall_exit_to_user_mode (31 samples, 0.08%)[libc.so.6] (785 samples, 2.02%)[..__lll_lock_wait_private (346 samples, 0.89%)entry_SYSCALL_64_after_hwframe (288 samples, 0.74%)__entry_text_start (13 samples, 0.03%)futex_hash (7 samples, 0.02%)_raw_spin_lock (25 samples, 0.06%)native_queued_spin_lock_slowpath (25 samples, 0.06%)do_futex (66 samples, 0.17%)futex_wake (54 samples, 0.14%)__x64_sys_futex (72 samples, 0.19%)exit_to_user_mode_prepare (10 samples, 0.03%)do_syscall_64 (90 samples, 0.23%)syscall_exit_to_user_mode (16 samples, 0.04%)entry_SYSCALL_64_after_hwframe (98 samples, 0.25%)alloc::alloc::dealloc (965 samples, 2.48%)al..cfree (956 samples, 2.46%)cf..__lll_lock_wake_private (119 samples, 0.31%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (970 samples, 2.50%)<a..zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (986 samples, 2.54%)co..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (986 samples, 2.54%)<a..arrayvec::arrayvec::ArrayVec<T,_>::clear (986 samples, 2.54%)ar..arrayvec::arrayvec_impl::ArrayVecImpl::clear (986 samples, 2.54%)ar..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (986 samples, 2.54%)ar..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (984 samples, 2.53%)co..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (981 samples, 2.53%)co..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (981 samples, 2.53%)co..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (981 samples, 2.53%)<a..alloc::raw_vec::RawVec<T,A>::current_memory (8 samples, 0.02%)core::alloc::layout::Layout::array (6 samples, 0.02%)core::alloc::layout::Layout::array::inner (6 samples, 0.02%)std::sync::poison::Flag::done (10 samples, 0.03%)std::thread::panicking (10 samples, 0.03%)std::panicking::panicking (10 samples, 0.03%)std::panicking::panic_count::count_is_zero (10 samples, 0.03%)core::sync::atomic::AtomicUsize::load (9 samples, 0.02%)core::sync::atomic::atomic_load (9 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (78 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::unlock (68 samples, 0.18%)core::sync::atomic::AtomicU32::swap (66 samples, 0.17%)core::sync::atomic::atomic_swap (66 samples, 0.17%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (88 samples, 0.23%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (5 samples, 0.01%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (85 samples, 0.22%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (85 samples, 0.22%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (85 samples, 0.22%)core::sync::atomic::AtomicU32::fetch_sub (76 samples, 0.20%)core::sync::atomic::atomic_sub (76 samples, 0.20%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (66 samples, 0.17%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (66 samples, 0.17%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (66 samples, 0.17%)core::sync::atomic::AtomicU32::fetch_sub (57 samples, 0.15%)core::sync::atomic::atomic_sub (57 samples, 0.15%)core::num::<impl u64>::rotate_left (4 samples, 0.01%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (20 samples, 0.05%)core::num::<impl u64>::wrapping_add (4 samples, 0.01%)core::num::<impl u64>::rotate_left (7 samples, 0.02%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (62 samples, 0.16%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (62 samples, 0.16%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (62 samples, 0.16%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (32 samples, 0.08%)core::num::<impl u64>::wrapping_add (14 samples, 0.04%)<std::collections::hash::map::RandomState as core::hash::BuildHasher>::build_hasher (17 samples, 0.04%)core::hash::sip::SipHasher13::new_with_keys (8 samples, 0.02%)core::hash::sip::Hasher<S>::new_with_keys (8 samples, 0.02%)core::hash::sip::Hasher<S>::reset (8 samples, 0.02%)hashbrown::map::make_hash (104 samples, 0.27%)core::hash::BuildHasher::hash_one (104 samples, 0.27%)core::hash::impls::<impl core::hash::Hash for &T>::hash (23 samples, 0.06%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (23 samples, 0.06%)core::hash::impls::<impl core::hash::Hash for u32>::hash (23 samples, 0.06%)core::hash::Hasher::write_u32 (23 samples, 0.06%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (23 samples, 0.06%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (23 samples, 0.06%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (23 samples, 0.06%)core::hash::sip::u8to64_le (15 samples, 0.04%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (5 samples, 0.01%)hashbrown::raw::h2 (8 samples, 0.02%)hashbrown::raw::sse2::Group::load (29 samples, 0.07%)core::core_arch::x86::sse2::_mm_loadu_si128 (29 samples, 0.07%)core::intrinsics::copy_nonoverlapping (29 samples, 0.07%)hashbrown::map::HashMap<K,V,S,A>::get_inner (163 samples, 0.42%)hashbrown::raw::RawTable<T,A>::get (59 samples, 0.15%)hashbrown::raw::RawTable<T,A>::find (59 samples, 0.15%)hashbrown::raw::RawTableInner<A>::find_inner (59 samples, 0.15%)hashbrown::raw::sse2::Group::match_byte (7 samples, 0.02%)core::core_arch::x86::sse2::_mm_movemask_epi8 (7 samples, 0.02%)std::collections::hash::map::HashMap<K,V,S>::get (164 samples, 0.42%)hashbrown::map::HashMap<K,V,S,A>::get (164 samples, 0.42%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (42 samples, 0.11%)std::sys::unix::locks::futex_mutex::Mutex::lock (38 samples, 0.10%)core::sync::atomic::AtomicU32::compare_exchange (30 samples, 0.08%)core::sync::atomic::atomic_compare_exchange (30 samples, 0.08%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::sync::atomic::AtomicU32::compare_exchange_weak (128 samples, 0.33%)core::sync::atomic::atomic_compare_exchange_weak (128 samples, 0.33%)std::sync::rwlock::RwLock<T>::read (162 samples, 0.42%)std::sys::unix::locks::futex_rwlock::RwLock::read (162 samples, 0.42%)std::sys::unix::locks::futex_rwlock::is_read_lockable (12 samples, 0.03%)core::num::<impl u64>::wrapping_add (5 samples, 0.01%)zssp::antireplay::Window<_,_>::check (13 samples, 0.03%)core::sync::atomic::AtomicU64::load (7 samples, 0.02%)core::sync::atomic::atomic_load (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (20 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push (20 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (20 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (13 samples, 0.03%)core::ptr::write (12 samples, 0.03%)core::array::equality::_<impl core::cmp::PartialEq<[B: N]> for [A: N]>::ne (5 samples, 0.01%)<T as core::array::equality::SpecArrayEq<U,_>>::spec_ne (5 samples, 0.01%)<T as core::array::equality::SpecArrayEq<U,_>>::spec_eq (5 samples, 0.01%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init_read (7 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::read (7 samples, 0.02%)core::ptr::read (7 samples, 0.02%)core::mem::maybe_uninit::MaybeUninit<T>::assume_init (7 samples, 0.02%)core::num::<impl u64>::wrapping_shl (6 samples, 0.02%)core::num::<impl u64>::unchecked_shl (6 samples, 0.02%)zssp::fragged::Fragged<Fragment,_>::assemble (98 samples, 0.25%)zssp::fragged::Fragged<Fragment,_>::drop_in_place (5 samples, 0.01%)<T as core::convert::TryInto<U>>::try_into (26 samples, 0.07%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (26 samples, 0.07%)core::result::Result<T,E>::map (26 samples, 0.07%)zssp::zeta::from_nonce (32 samples, 0.08%)core::num::<impl u64>::from_be_bytes (6 samples, 0.02%)core::num::<impl u64>::from_be (6 samples, 0.02%)core::num::<impl u64>::swap_bytes (6 samples, 0.02%)<alloc::vec::Vec<T,A> as core::convert::AsMut<[T]>>::as_mut (4 samples, 0.01%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (69 samples, 0.18%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (69 samples, 0.18%)core::sync::atomic::AtomicU32::fetch_sub (68 samples, 0.18%)core::sync::atomic::atomic_sub (68 samples, 0.18%)CRYPTO_gcm128_decrypt (249 samples, 0.64%)[libcrypto.so.3] (120 samples, 0.31%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)CRYPTO_gcm128_decrypt_ctr32 (478 samples, 1.23%)[libcrypto.so.3] (360 samples, 0.93%)[libcrypto.so.3] (22 samples, 0.06%)CRYPTO_gcm128_setiv (23 samples, 0.06%)[libcrypto.so.3] (20 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,848 samples, 12.48%)<zssp::crypto_impl:..zssp::crypto_impl::openssl::CipherCtx::update (4,846 samples, 12.48%)zssp::crypto_impl::..EVP_DecryptUpdate (4,846 samples, 12.48%)EVP_DecryptUpdate[libcrypto.so.3] (4,745 samples, 12.22%)[libcrypto.so.3][libcrypto.so.3] (4,741 samples, 12.21%)[libcrypto.so.3][libcrypto.so.3] (4,719 samples, 12.15%)[libcrypto.so.3][libcrypto.so.3] (3,937 samples, 10.14%)[libcrypto.so.3][libcrypto.so.3] (3,769 samples, 9.71%)[libcrypto.so...asm_sysvec_apic_timer_interrupt (6 samples, 0.02%)sysvec_apic_timer_interrupt (6 samples, 0.02%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (13 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (13 samples, 0.03%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (13 samples, 0.03%)std::sys::unix::locks::futex_mutex::Mutex::unlock (12 samples, 0.03%)core::sync::atomic::AtomicU32::swap (12 samples, 0.03%)core::sync::atomic::atomic_swap (12 samples, 0.03%)[libc.so.6] (6 samples, 0.02%)[libcrypto.so.3] (20 samples, 0.05%)CRYPTO_gcm128_finish (40 samples, 0.10%)zssp::crypto_impl::openssl::CipherCtx::finalize (59 samples, 0.15%)EVP_DecryptFinal_ex (59 samples, 0.15%)[libcrypto.so.3] (50 samples, 0.13%)[libcrypto.so.3] (50 samples, 0.13%)[libcrypto.so.3] (46 samples, 0.12%)OSSL_PARAM_get_octet_string (5 samples, 0.01%)[libcrypto.so.3] (4 samples, 0.01%)OSSL_PARAM_locate (27 samples, 0.07%)[libc.so.6] (16 samples, 0.04%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (145 samples, 0.37%)zssp::crypto_impl::openssl::CipherCtx::set_tag (70 samples, 0.18%)EVP_CIPHER_CTX_ctrl (70 samples, 0.18%)[libcrypto.so.3] (40 samples, 0.10%)std::sync::mutex::Mutex<T>::lock (18 samples, 0.05%)std::sys::unix::locks::futex_mutex::Mutex::lock (18 samples, 0.05%)core::sync::atomic::AtomicU32::compare_exchange (17 samples, 0.04%)core::sync::atomic::atomic_compare_exchange (17 samples, 0.04%)[libc.so.6] (14 samples, 0.04%)OSSL_PARAM_locate (37 samples, 0.10%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_get_iv_length (70 samples, 0.18%)[libcrypto.so.3] (47 samples, 0.12%)[libc.so.6] (22 samples, 0.06%)OSSL_PARAM_locate (50 samples, 0.13%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (92 samples, 0.24%)[libcrypto.so.3] (72 samples, 0.19%)OSSL_PARAM_locate_const (5 samples, 0.01%)EVP_CIPHER_free (13 samples, 0.03%)EVP_CIPHER_up_ref (46 samples, 0.12%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (292 samples, 0.75%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (273 samples, 0.70%)EVP_CipherInit_ex (272 samples, 0.70%)[libcrypto.so.3] (272 samples, 0.70%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (6 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (7 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::get_unchecked_ptr (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::add (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::offset (6 samples, 0.02%)std::io::impls::<impl std::io::Write for &mut W>::write (253 samples, 0.65%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (253 samples, 0.65%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (250 samples, 0.64%)core::intrinsics::copy_nonoverlapping (244 samples, 0.63%)[libc.so.6] (242 samples, 0.62%)zssp::antireplay::Window<_,_>::update (15 samples, 0.04%)core::sync::atomic::AtomicU64::fetch_max (15 samples, 0.04%)core::sync::atomic::atomic_umax (15 samples, 0.04%)zssp::zeta::receive_payload_in_place (5,655 samples, 14.56%)zssp::zeta::receive_pa..zssp::zssp::Context<Crypto>::receive (8,525 samples, 21.95%)zssp::zssp::Context<Crypto>::receivezssp::zssp::parse_fragment_header (74 samples, 0.19%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (60 samples, 0.15%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (60 samples, 0.15%)std::sys::unix::locks::futex_mutex::Mutex::unlock (59 samples, 0.15%)core::sync::atomic::AtomicU32::swap (54 samples, 0.14%)core::sync::atomic::atomic_swap (54 samples, 0.14%)std::sync::mutex::Mutex<T>::lock (79 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (77 samples, 0.20%)core::sync::atomic::AtomicU32::compare_exchange (74 samples, 0.19%)core::sync::atomic::atomic_compare_exchange (74 samples, 0.19%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (325 samples, 0.84%)zssp::crypto_impl::openssl::CipherCtx::update (180 samples, 0.46%)EVP_EncryptUpdate (180 samples, 0.46%)[libcrypto.so.3] (154 samples, 0.40%)[libcrypto.so.3] (123 samples, 0.32%)[libcrypto.so.3] (115 samples, 0.30%)<std::sync::mutex::MutexGuard<T> as core::ops::deref::Deref>::deref (5 samples, 0.01%)CRYPTO_gcm128_encrypt (264 samples, 0.68%)[libcrypto.so.3] (144 samples, 0.37%)[libcrypto.so.3] (20 samples, 0.05%)CRYPTO_gcm128_encrypt_ctr32 (501 samples, 1.29%)[libcrypto.so.3] (349 samples, 0.90%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)CRYPTO_gcm128_setiv (23 samples, 0.06%)[libcrypto.so.3] (20 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,870 samples, 9.97%)<zssp::crypto_..zssp::crypto_impl::openssl::CipherCtx::update (3,865 samples, 9.95%)zssp::crypto_i..EVP_EncryptUpdate (3,859 samples, 9.94%)EVP_EncryptUpd..[libcrypto.so.3] (3,809 samples, 9.81%)[libcrypto.so...[libcrypto.so.3] (3,805 samples, 9.80%)[libcrypto.so...[libcrypto.so.3] (3,789 samples, 9.76%)[libcrypto.so...[libcrypto.so.3] (2,964 samples, 7.63%)[libcrypto..[libcrypto.so.3] (2,713 samples, 6.99%)[libcrypt..asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (7 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (7 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (7 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::unlock (6 samples, 0.02%)core::sync::atomic::AtomicU32::swap (6 samples, 0.02%)core::sync::atomic::atomic_swap (6 samples, 0.02%)[libcrypto.so.3] (31 samples, 0.08%)zssp::crypto_impl::openssl::CipherCtx::finalize (75 samples, 0.19%)EVP_EncryptFinal_ex (75 samples, 0.19%)[libcrypto.so.3] (61 samples, 0.16%)[libcrypto.so.3] (59 samples, 0.15%)[libcrypto.so.3] (56 samples, 0.14%)CRYPTO_gcm128_tag (55 samples, 0.14%)CRYPTO_gcm128_finish (42 samples, 0.11%)[libc.so.6] (29 samples, 0.07%)OSSL_PARAM_locate (60 samples, 0.15%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (177 samples, 0.46%)zssp::crypto_impl::openssl::CipherCtx::get_tag (94 samples, 0.24%)EVP_CIPHER_CTX_ctrl (94 samples, 0.24%)[libcrypto.so.3] (69 samples, 0.18%)OSSL_PARAM_set_octet_string (5 samples, 0.01%)[libc.so.6] (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (15 samples, 0.04%)std::sys::unix::locks::futex_mutex::Mutex::lock (13 samples, 0.03%)core::sync::atomic::AtomicU32::compare_exchange (11 samples, 0.03%)core::sync::atomic::atomic_compare_exchange (11 samples, 0.03%)[libc.so.6] (25 samples, 0.06%)EVP_CIPHER_CTX_get_iv_length (76 samples, 0.20%)[libcrypto.so.3] (53 samples, 0.14%)OSSL_PARAM_locate (46 samples, 0.12%)[libc.so.6] (37 samples, 0.10%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)OSSL_PARAM_locate (49 samples, 0.13%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (95 samples, 0.24%)[libcrypto.so.3] (62 samples, 0.16%)OSSL_PARAM_locate_const (5 samples, 0.01%)EVP_CIPHER_free (14 samples, 0.04%)EVP_CIPHER_up_ref (28 samples, 0.07%)EVP_CipherInit_ex (245 samples, 0.63%)[libcrypto.so.3] (245 samples, 0.63%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (262 samples, 0.67%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (247 samples, 0.64%)__rdl_alloc (10 samples, 0.03%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (10 samples, 0.03%)__rust_alloc (5 samples, 0.01%)[libc.so.6] (74 samples, 0.19%)asm_exc_page_fault (5 samples, 0.01%)exc_page_fault (5 samples, 0.01%)do_user_addr_fault (5 samples, 0.01%)handle_mm_fault (5 samples, 0.01%)__handle_mm_fault (5 samples, 0.01%)handle_pte_fault (5 samples, 0.01%)do_anonymous_page (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)finish_task_switch.isra.0 (13 samples, 0.03%)__perf_event_task_sched_in (13 samples, 0.03%)perf_pmu_nop_void (8 samples, 0.02%)[libc.so.6] (1,111 samples, 2.86%)[l..asm_sysvec_reschedule_ipi (16 samples, 0.04%)sysvec_reschedule_ipi (16 samples, 0.04%)irqentry_exit (16 samples, 0.04%)irqentry_exit_to_user_mode (16 samples, 0.04%)exit_to_user_mode_prepare (16 samples, 0.04%)exit_to_user_mode_loop (16 samples, 0.04%)schedule (14 samples, 0.04%)__schedule (14 samples, 0.04%)__entry_text_start (10 samples, 0.03%)__get_user_nocheck_4 (9 samples, 0.02%)_raw_spin_lock (6 samples, 0.02%)futex_q_lock (10 samples, 0.03%)futex_q_unlock (12 samples, 0.03%)futex_wait_setup (44 samples, 0.11%)get_futex_key (4 samples, 0.01%)futex_wait (52 samples, 0.13%)__x64_sys_futex (57 samples, 0.15%)do_futex (54 samples, 0.14%)entry_SYSCALL_64_after_hwframe (62 samples, 0.16%)do_syscall_64 (60 samples, 0.15%)__lll_lock_wait_private (101 samples, 0.26%)__entry_text_start (7 samples, 0.02%)_raw_spin_lock (9 samples, 0.02%)_raw_spin_lock (12 samples, 0.03%)native_queued_spin_lock_slowpath (12 samples, 0.03%)futex_wake_mark (24 samples, 0.06%)__futex_unqueue (4 samples, 0.01%)__smp_call_single_queue (20 samples, 0.05%)native_send_call_func_single_ipi (17 samples, 0.04%)x2apic_send_IPI (17 samples, 0.04%)native_write_msr (15 samples, 0.04%)llist_add_batch (17 samples, 0.04%)try_to_wake_up (89 samples, 0.23%)ttwu_queue_wakelist (55 samples, 0.14%)futex_wake (196 samples, 0.50%)wake_up_q (96 samples, 0.25%)do_futex (210 samples, 0.54%)__x64_sys_futex (214 samples, 0.55%)entry_SYSCALL_64_after_hwframe (224 samples, 0.58%)do_syscall_64 (222 samples, 0.57%)__lll_lock_wake_private (239 samples, 0.62%)alloc::vec::Vec<T,A>::with_capacity_in (2,224 samples, 5.73%)alloc::..alloc::raw_vec::RawVec<T,A>::with_capacity_in (2,224 samples, 5.73%)alloc::..alloc::raw_vec::RawVec<T,A>::allocate_in (2,224 samples, 5.73%)alloc::..<alloc::alloc::Global as core::alloc::Allocator>::allocate (2,221 samples, 5.72%)<alloc:..alloc::alloc::Global::alloc_impl (2,221 samples, 5.72%)alloc::..alloc::alloc::alloc (2,221 samples, 5.72%)alloc::..malloc (2,206 samples, 5.68%)mallocalloc::slice::<impl [T]>::to_vec (2,451 samples, 6.31%)alloc::s..alloc::slice::<impl [T]>::to_vec_in (2,451 samples, 6.31%)alloc::s..alloc::slice::hack::to_vec (2,451 samples, 6.31%)alloc::s..<T as alloc::slice::hack::ConvertVec>::to_vec (2,451 samples, 6.31%)<T as al..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (227 samples, 0.58%)core::intrinsics::copy_nonoverlapping (227 samples, 0.58%)[libc.so.6] (222 samples, 0.57%)<std::sync::mpmc::select::Token as core::default::Default>::default (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send (4 samples, 0.01%)core::sync::atomic::AtomicUsize::compare_exchange_weak (80 samples, 0.21%)core::sync::atomic::atomic_compare_exchange_weak (80 samples, 0.21%)std::sync::mpmc::array::Channel<T>::start_send (113 samples, 0.29%)core::sync::atomic::AtomicUsize::load (7 samples, 0.02%)core::sync::atomic::atomic_load (7 samples, 0.02%)core::sync::atomic::AtomicU32::swap (6 samples, 0.02%)core::sync::atomic::atomic_swap (6 samples, 0.02%)futex_wake_mark (7 samples, 0.02%)__smp_call_single_queue (6 samples, 0.02%)__x64_sys_futex (57 samples, 0.15%)do_futex (57 samples, 0.15%)futex_wake (55 samples, 0.14%)wake_up_q (36 samples, 0.09%)try_to_wake_up (35 samples, 0.09%)ttwu_queue_wakelist (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::write (91 samples, 0.23%)std::sync::mpmc::waker::SyncWaker::notify (85 samples, 0.22%)std::sync::mpmc::waker::Waker::try_select (74 samples, 0.19%)<core::slice::iter::Iter<T> as core::iter::traits::iterator::Iterator>::position (74 samples, 0.19%)std::sync::mpmc::waker::Waker::try_select::_{{closure}} (74 samples, 0.19%)std::sync::mpmc::context::Context::unpark (72 samples, 0.19%)std::thread::Thread::unpark (72 samples, 0.19%)std::sys_common::thread_parking::futex::Parker::unpark (72 samples, 0.19%)std::sys::unix::futex::futex_wake (66 samples, 0.17%)syscall (66 samples, 0.17%)entry_SYSCALL_64_after_hwframe (61 samples, 0.16%)do_syscall_64 (61 samples, 0.16%)std::sync::mpmc::context::Context::wait_until (4 samples, 0.01%)std::thread::park (4 samples, 0.01%)std::sys_common::thread_parking::futex::Parker::park (4 samples, 0.01%)std::sys::unix::futex::futex_wait (4 samples, 0.01%)syscall (4 samples, 0.01%)entry_SYSCALL_64_after_hwframe (4 samples, 0.01%)do_syscall_64 (4 samples, 0.01%)std::sync::mpmc::context::Context::with (5 samples, 0.01%)std::thread::local::LocalKey<T>::try_with (5 samples, 0.01%)std::sync::mpmc::context::Context::with::_{{closure}} (5 samples, 0.01%)std::sync::mpmc::context::Context::with::_{{closure}} (5 samples, 0.01%)std::sync::mpmc::array::Channel<T>::send::_{{closure}} (5 samples, 0.01%)benchmark::bob_main::_{{closure}} (4,067 samples, 10.47%)benchmark::bob_..std::sync::mpsc::SyncSender<T>::send (1,615 samples, 4.16%)std::..std::sync::mpmc::Sender<T>::send (1,613 samples, 4.15%)std::..std::sync::mpmc::array::Channel<T>::send (265 samples, 0.68%)std::sync::mpmc::utils::Backoff::spin_light (25 samples, 0.06%)core::hint::spin_loop (25 samples, 0.06%)core::core_arch::x86::sse2::_mm_pause (25 samples, 0.06%)core::mem::drop (16 samples, 0.04%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (16 samples, 0.04%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (16 samples, 0.04%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (16 samples, 0.04%)core::sync::atomic::AtomicU32::fetch_sub (15 samples, 0.04%)core::sync::atomic::atomic_sub (15 samples, 0.04%)core::slice::<impl [T]>::copy_from_slice (6 samples, 0.02%)core::intrinsics::copy_nonoverlapping (6 samples, 0.02%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (4 samples, 0.01%)std::sync::rwlock::RwLock<T>::read (23 samples, 0.06%)std::sys::unix::locks::futex_rwlock::RwLock::read (23 samples, 0.06%)core::sync::atomic::AtomicU32::compare_exchange_weak (21 samples, 0.05%)core::sync::atomic::atomic_compare_exchange_weak (21 samples, 0.05%)benchmark::alice_main (18,418 samples, 47.43%)benchmark::alice_mainzssp::zssp::Context<Crypto>::send (8,789 samples, 22.63%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,789 samples, 22.63%)zssp::zeta::send_payloadzssp::zeta::get_counter (18 samples, 0.05%)core::sync::atomic::AtomicU64::fetch_add (16 samples, 0.04%)core::sync::atomic::atomic_add (16 samples, 0.04%)[libc.so.6] (15 samples, 0.04%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (15 samples, 0.04%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (15 samples, 0.04%)core::sync::atomic::AtomicUsize::fetch_sub (14 samples, 0.04%)core::sync::atomic::atomic_sub (14 samples, 0.04%)core::time::Duration::as_millis (8 samples, 0.02%)core::result::Result<T,E>::map_err (4 samples, 0.01%)std::sync::mpmc::array::Channel<T>::read (7 samples, 0.02%)std::sync::mpmc::waker::SyncWaker::notify (6 samples, 0.02%)core::slice::<impl [T]>::get_unchecked (6 samples, 0.02%)<usize as core::slice::index::SliceIndex<[T]>>::get_unchecked (6 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::add (6 samples, 0.02%)core::ptr::const_ptr::<impl *const T>::offset (6 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (90 samples, 0.23%)core::sync::atomic::atomic_compare_exchange_weak (90 samples, 0.23%)core::sync::atomic::AtomicUsize::load (63 samples, 0.16%)core::sync::atomic::atomic_load (63 samples, 0.16%)std::sync::mpmc::array::Channel<T>::start_recv (200 samples, 0.52%)core::sync::atomic::AtomicUsize::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)core::sync::atomic::AtomicU32::swap (4 samples, 0.01%)core::sync::atomic::atomic_swap (4 samples, 0.01%)core::option::Option<T>::and_then (7 samples, 0.02%)std::sys::unix::futex::futex_wait::_{{closure}} (7 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (7 samples, 0.02%)clock_gettime (7 samples, 0.02%)[[vdso]] (7 samples, 0.02%)[[vdso]] (4 samples, 0.01%)futex_unqueue (7 samples, 0.02%)enqueue_hrtimer (5 samples, 0.01%)hrtimer_sleeper_start_expires (6 samples, 0.02%)hrtimer_start_range_ns (6 samples, 0.02%)__hrtimer_start_range_ns (6 samples, 0.02%)update_curr (5 samples, 0.01%)dequeue_task (14 samples, 0.04%)dequeue_task_fair (14 samples, 0.04%)dequeue_entity (14 samples, 0.04%)update_load_avg (5 samples, 0.01%)__perf_event_task_sched_in (11 samples, 0.03%)perf_ctx_enable (11 samples, 0.03%)finish_task_switch.isra.0 (16 samples, 0.04%)raw_spin_rq_unlock (4 samples, 0.01%)pick_next_task (4 samples, 0.01%)prepare_task_switch (4 samples, 0.01%)psi_group_change (8 samples, 0.02%)psi_task_switch (13 samples, 0.03%)futex_wait_queue (78 samples, 0.20%)schedule (68 samples, 0.18%)__schedule (66 samples, 0.17%)__get_user_nocheck_4 (4 samples, 0.01%)futex_q_lock (10 samples, 0.03%)futex_wait_setup (18 samples, 0.05%)hrtimer_cancel (7 samples, 0.02%)hrtimer_try_to_cancel (6 samples, 0.02%)futex_wait (117 samples, 0.30%)do_futex (120 samples, 0.31%)__x64_sys_futex (130 samples, 0.33%)get_timespec64 (4 samples, 0.01%)_copy_from_user (4 samples, 0.01%)exit_to_user_mode_prepare (8 samples, 0.02%)exit_to_user_mode_loop (8 samples, 0.02%)__rseq_handle_notify_resume (5 samples, 0.01%)std::sys_common::thread_parking::futex::Parker::park_timeout (157 samples, 0.40%)std::sys::unix::futex::futex_wait (152 samples, 0.39%)syscall (145 samples, 0.37%)entry_SYSCALL_64_after_hwframe (140 samples, 0.36%)do_syscall_64 (140 samples, 0.36%)syscall_exit_to_user_mode (9 samples, 0.02%)std::thread::park_timeout (159 samples, 0.41%)std::sync::mpmc::context::Context::wait_until (168 samples, 0.43%)core::sync::atomic::AtomicBool::store (6 samples, 0.02%)core::sync::atomic::atomic_store (6 samples, 0.02%)std::sync::mpmc::context::Context::with (187 samples, 0.48%)std::thread::local::LocalKey<T>::try_with (187 samples, 0.48%)std::sync::mpmc::context::Context::with::_{{closure}} (187 samples, 0.48%)std::sync::mpmc::context::Context::with::_{{closure}} (187 samples, 0.48%)std::sync::mpmc::array::Channel<T>::recv::_{{closure}} (185 samples, 0.48%)std::sync::mpmc::waker::SyncWaker::register (15 samples, 0.04%)std::sync::mutex::Mutex<T>::lock (6 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::lock (6 samples, 0.02%)core::sync::atomic::AtomicU32::compare_exchange (6 samples, 0.02%)core::sync::atomic::atomic_compare_exchange (6 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (4 samples, 0.01%)clock_gettime (4 samples, 0.01%)[[vdso]] (4 samples, 0.01%)std::sync::mpmc::Receiver<T>::recv_deadline (418 samples, 1.08%)std::sync::mpmc::array::Channel<T>::recv (416 samples, 1.07%)[[vdso]] (164 samples, 0.42%)[[vdso]] (114 samples, 0.29%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (175 samples, 0.45%)clock_gettime (173 samples, 0.45%)__vdso_clock_gettime (6 samples, 0.02%)std::sync::mpsc::Receiver<T>::recv_timeout (634 samples, 1.63%)std::sync::mpmc::Receiver<T>::recv_timeout (628 samples, 1.62%)std::time::SystemTime::checked_add (17 samples, 0.04%)std::sys::unix::time::SystemTime::checked_add_duration (16 samples, 0.04%)std::sys::unix::time::Timespec::checked_add_duration (16 samples, 0.04%)core::option::Option<T>::and_then (8 samples, 0.02%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)core::cmp::impls::<impl core::cmp::PartialOrd<&B> for &A>::ge (21 samples, 0.05%)core::cmp::PartialOrd::ge (21 samples, 0.05%)<std::sys::unix::time::Timespec as core::cmp::PartialOrd>::partial_cmp (15 samples, 0.04%)std::time::Instant::duration_since (45 samples, 0.12%)std::time::Instant::checked_duration_since (45 samples, 0.12%)std::sys::unix::time::inner::Instant::checked_sub_instant (45 samples, 0.12%)std::sys::unix::time::Timespec::sub_timespec (45 samples, 0.12%)core::time::Duration::new (5 samples, 0.01%)<std::time::Instant as core::ops::arith::Sub>::sub (48 samples, 0.12%)[[vdso]] (111 samples, 0.29%)[[vdso]] (145 samples, 0.37%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)std::time::Instant::elapsed (208 samples, 0.54%)std::time::Instant::now (158 samples, 0.41%)std::sys::unix::time::inner::Instant::now (158 samples, 0.41%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (155 samples, 0.40%)clock_gettime (151 samples, 0.39%)__vdso_clock_gettime (4 samples, 0.01%)<T as core::convert::TryInto<U>>::try_into (317 samples, 0.82%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (317 samples, 0.82%)core::result::Result<T,E>::map (317 samples, 0.82%)<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (6 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (76 samples, 0.20%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (76 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::unlock (75 samples, 0.19%)core::sync::atomic::AtomicU32::swap (71 samples, 0.18%)core::sync::atomic::atomic_swap (71 samples, 0.18%)std::sync::mutex::MutexGuard<T>::new (6 samples, 0.02%)std::sync::poison::Flag::guard (6 samples, 0.02%)std::thread::panicking (6 samples, 0.02%)std::panicking::panicking (6 samples, 0.02%)std::panicking::panic_count::count_is_zero (6 samples, 0.02%)std::sync::mutex::Mutex<T>::lock (73 samples, 0.19%)std::sys::unix::locks::futex_mutex::Mutex::lock (67 samples, 0.17%)core::sync::atomic::AtomicU32::compare_exchange (66 samples, 0.17%)core::sync::atomic::atomic_compare_exchange (66 samples, 0.17%)<zssp::crypto_impl::openssl::Aes256OpenSSLDec as zssp::crypto::aes::Aes256Dec>::decrypt_in_place (355 samples, 0.91%)zssp::crypto_impl::openssl::CipherCtx::update (206 samples, 0.53%)EVP_DecryptUpdate (202 samples, 0.52%)[libcrypto.so.3] (165 samples, 0.42%)[libcrypto.so.3] (125 samples, 0.32%)[libcrypto.so.3] (116 samples, 0.30%)__rust_probestack (6 samples, 0.02%)core::cmp::Ord::max (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)alloc::sync::Weak<T>::upgrade::_{{closure}} (6 samples, 0.02%)core::sync::atomic::AtomicUsize::compare_exchange_weak (73 samples, 0.19%)core::sync::atomic::atomic_compare_exchange_weak (73 samples, 0.19%)alloc::sync::Weak<T>::upgrade (92 samples, 0.24%)core::sync::atomic::AtomicUsize::fetch_update (88 samples, 0.23%)core::sync::atomic::AtomicUsize::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)core::option::Option<T>::map (99 samples, 0.25%)zssp::zssp::Context<Crypto>::receive::_{{closure}} (99 samples, 0.25%)zssp::zssp::Context<Crypto>::receive (7 samples, 0.02%)core::ptr::drop_in_place<alloc::sync::Arc<zssp::zeta::Session<benchmark::TestApplication>>> (45 samples, 0.12%)<alloc::sync::Arc<T> as core::ops::drop::Drop>::drop (45 samples, 0.12%)core::sync::atomic::AtomicUsize::fetch_sub (43 samples, 0.11%)core::sync::atomic::atomic_sub (43 samples, 0.11%)__rdl_dealloc (4 samples, 0.01%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::dealloc (4 samples, 0.01%)__rust_dealloc (9 samples, 0.02%)[libc.so.6] (76 samples, 0.20%)__entry_text_start (13 samples, 0.03%)futex_unqueue (17 samples, 0.04%)_raw_spin_lock (4 samples, 0.01%)cpuacct_charge (10 samples, 0.03%)update_curr (18 samples, 0.05%)dequeue_entity (32 samples, 0.08%)update_load_avg (5 samples, 0.01%)dequeue_task (42 samples, 0.11%)dequeue_task_fair (42 samples, 0.11%)__perf_event_task_sched_in (9 samples, 0.02%)perf_ctx_enable (9 samples, 0.02%)x86_pmu_enable (9 samples, 0.02%)intel_pmu_enable_all (9 samples, 0.02%)native_write_msr (9 samples, 0.02%)finish_task_switch.isra.0 (29 samples, 0.07%)raw_spin_rq_unlock (12 samples, 0.03%)pick_next_task_fair (4 samples, 0.01%)pick_next_task (10 samples, 0.03%)put_prev_task_fair (6 samples, 0.02%)prepare_task_switch (15 samples, 0.04%)__perf_event_task_sched_out (6 samples, 0.02%)psi_group_change (38 samples, 0.10%)psi_task_switch (52 samples, 0.13%)__schedule (179 samples, 0.46%)update_rq_clock (5 samples, 0.01%)sched_clock_cpu (5 samples, 0.01%)sched_clock (4 samples, 0.01%)native_sched_clock (4 samples, 0.01%)futex_wait_queue (199 samples, 0.51%)schedule (187 samples, 0.48%)__get_user_nocheck_4 (26 samples, 0.07%)_raw_spin_lock (4 samples, 0.01%)futex_hash (5 samples, 0.01%)futex_q_lock (5 samples, 0.01%)futex_q_unlock (5 samples, 0.01%)futex_wait (278 samples, 0.72%)futex_wait_setup (52 samples, 0.13%)do_futex (282 samples, 0.73%)__x64_sys_futex (292 samples, 0.75%)__get_user_8 (5 samples, 0.01%)rseq_ip_fixup (11 samples, 0.03%)exit_to_user_mode_loop (24 samples, 0.06%)__rseq_handle_notify_resume (15 samples, 0.04%)exit_to_user_mode_prepare (31 samples, 0.08%)do_syscall_64 (331 samples, 0.85%)syscall_exit_to_user_mode (36 samples, 0.09%)__lll_lock_wait_private (397 samples, 1.02%)entry_SYSCALL_64_after_hwframe (335 samples, 0.86%)[libc.so.6] (911 samples, 2.35%)[..__entry_text_start (15 samples, 0.04%)do_syscall_64 (4 samples, 0.01%)_raw_spin_lock (5 samples, 0.01%)futex_hash (8 samples, 0.02%)_raw_spin_lock (25 samples, 0.06%)native_queued_spin_lock_slowpath (25 samples, 0.06%)_raw_spin_unlock (4 samples, 0.01%)preempt_schedule_thunk (4 samples, 0.01%)preempt_schedule (4 samples, 0.01%)__schedule (4 samples, 0.01%)finish_task_switch.isra.0 (4 samples, 0.01%)__perf_event_task_sched_in (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%)futex_wake (77 samples, 0.20%)do_futex (97 samples, 0.25%)__x64_sys_futex (101 samples, 0.26%)do_syscall_64 (113 samples, 0.29%)syscall_exit_to_user_mode (6 samples, 0.02%)exit_to_user_mode_prepare (5 samples, 0.01%)alloc::alloc::dealloc (1,146 samples, 2.95%)all..cfree (1,133 samples, 2.92%)cf..__lll_lock_wake_private (146 samples, 0.38%)entry_SYSCALL_64_after_hwframe (120 samples, 0.31%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (1,151 samples, 2.96%)<al..zssp::zssp::Context<Crypto>::receive (5 samples, 0.01%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (1,169 samples, 3.01%)cor..<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (1,169 samples, 3.01%)<ar..arrayvec::arrayvec::ArrayVec<T,_>::clear (1,169 samples, 3.01%)arr..arrayvec::arrayvec_impl::ArrayVecImpl::clear (1,169 samples, 3.01%)arr..arrayvec::arrayvec_impl::ArrayVecImpl::truncate (1,169 samples, 3.01%)arr..core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (1,162 samples, 2.99%)cor..core::ptr::drop_in_place<alloc::vec::Vec<u8>> (1,161 samples, 2.99%)cor..core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (1,161 samples, 2.99%)cor..<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (1,161 samples, 2.99%)<al..alloc::raw_vec::RawVec<T,A>::current_memory (9 samples, 0.02%)std::sync::poison::Flag::done (4 samples, 0.01%)std::thread::panicking (4 samples, 0.01%)std::panicking::panicking (4 samples, 0.01%)std::panicking::panic_count::count_is_zero (4 samples, 0.01%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (83 samples, 0.21%)std::sys::unix::locks::futex_mutex::Mutex::unlock (79 samples, 0.20%)core::sync::atomic::AtomicU32::swap (76 samples, 0.20%)core::sync::atomic::atomic_swap (76 samples, 0.20%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (87 samples, 0.22%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (4 samples, 0.01%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<std::collections::hash::map::HashMap<core::num::nonzero::NonZeroU32,alloc::sync::Weak<zssp::zeta::Session<benchmark::TestApplication>>>>> (88 samples, 0.23%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (88 samples, 0.23%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (88 samples, 0.23%)core::sync::atomic::AtomicU32::fetch_sub (80 samples, 0.21%)core::sync::atomic::atomic_sub (80 samples, 0.21%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (59 samples, 0.15%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (59 samples, 0.15%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (59 samples, 0.15%)core::sync::atomic::AtomicU32::fetch_sub (54 samples, 0.14%)core::sync::atomic::atomic_sub (54 samples, 0.14%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (6 samples, 0.02%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (6 samples, 0.02%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::add (6 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::offset (6 samples, 0.02%)core::num::<impl u64>::rotate_left (9 samples, 0.02%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::c_rounds (22 samples, 0.06%)core::num::<impl u64>::wrapping_add (6 samples, 0.02%)core::num::<impl u64>::rotate_left (15 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::finish (78 samples, 0.20%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::finish (78 samples, 0.20%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::finish (78 samples, 0.20%)<core::hash::sip::Sip13Rounds as core::hash::sip::Sip>::d_rounds (44 samples, 0.11%)core::num::<impl u64>::wrapping_add (20 samples, 0.05%)hashbrown::map::make_hash (98 samples, 0.25%)core::hash::BuildHasher::hash_one (98 samples, 0.25%)core::hash::impls::<impl core::hash::Hash for &T>::hash (15 samples, 0.04%)<core::num::nonzero::NonZeroU32 as core::hash::Hash>::hash (15 samples, 0.04%)core::hash::impls::<impl core::hash::Hash for u32>::hash (15 samples, 0.04%)core::hash::Hasher::write_u32 (15 samples, 0.04%)<std::collections::hash::map::DefaultHasher as core::hash::Hasher>::write (15 samples, 0.04%)<core::hash::sip::SipHasher13 as core::hash::Hasher>::write (15 samples, 0.04%)<core::hash::sip::Hasher<S> as core::hash::Hasher>::write (15 samples, 0.04%)core::hash::sip::u8to64_le (10 samples, 0.03%)<hashbrown::raw::bitmask::BitMaskIter as core::iter::traits::iterator::Iterator>::next (6 samples, 0.02%)hashbrown::map::equivalent_key::_{{closure}} (10 samples, 0.03%)<core::num::nonzero::NonZeroU32 as core::cmp::PartialEq>::eq (10 samples, 0.03%)hashbrown::raw::RawTable<T,A>::find::_{{closure}} (17 samples, 0.04%)hashbrown::raw::Bucket<T>::as_ref (7 samples, 0.02%)hashbrown::raw::Bucket<T>::as_ptr (7 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::sub (7 samples, 0.02%)core::ptr::mut_ptr::<impl *mut T>::offset (7 samples, 0.02%)hashbrown::raw::h2 (14 samples, 0.04%)hashbrown::map::HashMap<K,V,S,A>::get_inner (149 samples, 0.38%)hashbrown::raw::RawTable<T,A>::get (51 samples, 0.13%)hashbrown::raw::RawTable<T,A>::find (51 samples, 0.13%)hashbrown::raw::RawTableInner<A>::find_inner (51 samples, 0.13%)hashbrown::raw::sse2::Group::match_byte (4 samples, 0.01%)core::core_arch::x86::sse2::_mm_movemask_epi8 (4 samples, 0.01%)std::collections::hash::map::HashMap<K,V,S>::get (153 samples, 0.39%)hashbrown::map::HashMap<K,V,S,A>::get (153 samples, 0.39%)zssp::zssp::Context<Crypto>::receive (4 samples, 0.01%)std::sync::mutex::MutexGuard<T>::new (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (50 samples, 0.13%)std::sys::unix::locks::futex_mutex::Mutex::lock (44 samples, 0.11%)core::sync::atomic::AtomicU32::compare_exchange (42 samples, 0.11%)core::sync::atomic::atomic_compare_exchange (42 samples, 0.11%)core::sync::atomic::AtomicU32::compare_exchange_weak (135 samples, 0.35%)core::sync::atomic::atomic_compare_exchange_weak (135 samples, 0.35%)std::sync::rwlock::RwLock<T>::read (158 samples, 0.41%)std::sys::unix::locks::futex_rwlock::RwLock::read (158 samples, 0.41%)std::sys::unix::locks::futex_rwlock::is_read_lockable (8 samples, 0.02%)zssp::antireplay::Window<_,_>::check (10 samples, 0.03%)core::sync::atomic::AtomicU64::load (6 samples, 0.02%)core::sync::atomic::atomic_load (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::try_push (19 samples, 0.05%)arrayvec::arrayvec_impl::ArrayVecImpl::push_unchecked (11 samples, 0.03%)core::ptr::write (11 samples, 0.03%)zssp::fragged::Fragged<Fragment,_>::assemble (59 samples, 0.15%)<T as core::convert::TryInto<U>>::try_into (39 samples, 0.10%)core::array::_<impl core::convert::TryFrom<&[T]> for [T: N]>::try_from (39 samples, 0.10%)core::result::Result<T,E>::map (39 samples, 0.10%)zssp::zeta::from_nonce (44 samples, 0.11%)<core::slice::iter::IterMut<T> as core::iter::traits::iterator::Iterator>::next (4 samples, 0.01%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (60 samples, 0.15%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (60 samples, 0.15%)core::sync::atomic::AtomicU32::fetch_sub (60 samples, 0.15%)core::sync::atomic::atomic_sub (60 samples, 0.15%)EVP_CIPHER_CTX_get_block_size (5 samples, 0.01%)CRYPTO_gcm128_decrypt (268 samples, 0.69%)[libcrypto.so.3] (104 samples, 0.27%)CRYPTO_gcm128_decrypt_ctr32 (512 samples, 1.32%)[libcrypto.so.3] (396 samples, 1.02%)[libcrypto.so.3] (25 samples, 0.06%)CRYPTO_gcm128_setiv (25 samples, 0.06%)[libcrypto.so.3] (22 samples, 0.06%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::decrypt_in_place (4,728 samples, 12.18%)<zssp::crypto_impl..zssp::crypto_impl::openssl::CipherCtx::update (4,725 samples, 12.17%)zssp::crypto_impl:..EVP_DecryptUpdate (4,725 samples, 12.17%)EVP_DecryptUpdate[libcrypto.so.3] (4,634 samples, 11.93%)[libcrypto.so.3][libcrypto.so.3] (4,632 samples, 11.93%)[libcrypto.so.3][libcrypto.so.3] (4,611 samples, 11.87%)[libcrypto.so.3][libcrypto.so.3] (3,756 samples, 9.67%)[libcrypto.so...[libcrypto.so.3] (3,594 samples, 9.26%)[libcrypto.so..core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLDec> (7 samples, 0.02%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (7 samples, 0.02%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (7 samples, 0.02%)std::sys::unix::locks::futex_mutex::Mutex::unlock (6 samples, 0.02%)core::sync::atomic::AtomicU32::swap (6 samples, 0.02%)core::sync::atomic::atomic_swap (6 samples, 0.02%)[libc.so.6] (4 samples, 0.01%)CRYPTO_gcm128_finish (47 samples, 0.12%)[libcrypto.so.3] (28 samples, 0.07%)zssp::crypto_impl::openssl::CipherCtx::finalize (63 samples, 0.16%)EVP_DecryptFinal_ex (63 samples, 0.16%)[libcrypto.so.3] (58 samples, 0.15%)[libcrypto.so.3] (56 samples, 0.14%)[libcrypto.so.3] (51 samples, 0.13%)OSSL_PARAM_locate (27 samples, 0.07%)[libc.so.6] (12 samples, 0.03%)<zssp::crypto_impl::openssl::AesGcmOpenSSLDec as zssp::crypto::aes::AesGcmDecContext>::finish (152 samples, 0.39%)zssp::crypto_impl::openssl::CipherCtx::set_tag (82 samples, 0.21%)EVP_CIPHER_CTX_ctrl (82 samples, 0.21%)[libcrypto.so.3] (41 samples, 0.11%)std::sync::mutex::Mutex<T>::lock (16 samples, 0.04%)std::sys::unix::locks::futex_mutex::Mutex::lock (16 samples, 0.04%)core::sync::atomic::AtomicU32::compare_exchange (15 samples, 0.04%)core::sync::atomic::atomic_compare_exchange (15 samples, 0.04%)[libc.so.6] (22 samples, 0.06%)OSSL_PARAM_locate (47 samples, 0.12%)EVP_CIPHER_CTX_get_iv_length (68 samples, 0.18%)[libcrypto.so.3] (52 samples, 0.13%)[libc.so.6] (27 samples, 0.07%)OSSL_PARAM_locate (49 samples, 0.13%)strcmp@plt (7 samples, 0.02%)EVP_CIPHER_CTX_set_padding (86 samples, 0.22%)[libcrypto.so.3] (66 samples, 0.17%)OSSL_PARAM_locate_const (6 samples, 0.02%)EVP_CIPHER_free (15 samples, 0.04%)EVP_CipherInit_ex (252 samples, 0.65%)[libcrypto.so.3] (252 samples, 0.65%)EVP_CIPHER_up_ref (51 samples, 0.13%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_dec (270 samples, 0.70%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (254 samples, 0.65%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (7 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (7 samples, 0.02%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::get_unchecked_mut (4 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::remaining_capacity (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::try_extend_from_slice (240 samples, 0.62%)core::intrinsics::copy_nonoverlapping (238 samples, 0.61%)[libc.so.6] (238 samples, 0.61%)std::io::impls::<impl std::io::Write for &mut W>::write (247 samples, 0.64%)<arrayvec::arrayvec::ArrayVec<u8,_> as std::io::Write>::write (247 samples, 0.64%)zssp::zeta::receive_payload_in_place (5,500 samples, 14.16%)zssp::zeta::receive_pa..zssp::antireplay::Window<_,_>::update (8 samples, 0.02%)core::sync::atomic::AtomicU64::fetch_max (8 samples, 0.02%)core::sync::atomic::atomic_umax (8 samples, 0.02%)zssp::zssp::Context<Crypto>::receive (8,391 samples, 21.61%)zssp::zssp::Context<Crypto>::recei..zssp::zssp::parse_fragment_header (101 samples, 0.26%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (73 samples, 0.19%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (73 samples, 0.19%)std::sys::unix::locks::futex_mutex::Mutex::unlock (70 samples, 0.18%)core::sync::atomic::AtomicU32::swap (66 samples, 0.17%)core::sync::atomic::atomic_swap (66 samples, 0.17%)std::sync::mutex::Mutex<T>::lock (77 samples, 0.20%)std::sys::unix::locks::futex_mutex::Mutex::lock (76 samples, 0.20%)core::sync::atomic::AtomicU32::compare_exchange (73 samples, 0.19%)core::sync::atomic::atomic_compare_exchange (73 samples, 0.19%)<zssp::crypto_impl::openssl::Aes256OpenSSLEnc as zssp::crypto::aes::Aes256Enc>::encrypt_in_place (321 samples, 0.83%)zssp::crypto_impl::openssl::CipherCtx::update (162 samples, 0.42%)EVP_EncryptUpdate (158 samples, 0.41%)[libcrypto.so.3] (139 samples, 0.36%)[libcrypto.so.3] (108 samples, 0.28%)[libcrypto.so.3] (99 samples, 0.25%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)<std::sync::mutex::MutexGuard<T> as core::ops::deref::Deref>::deref (10 samples, 0.03%)[libcrypto.so.3] (174 samples, 0.45%)CRYPTO_gcm128_encrypt (280 samples, 0.72%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)CRYPTO_gcm128_encrypt_ctr32 (476 samples, 1.23%)[libcrypto.so.3] (345 samples, 0.89%)[libcrypto.so.3] (31 samples, 0.08%)CRYPTO_gcm128_setiv (18 samples, 0.05%)[libcrypto.so.3] (15 samples, 0.04%)perf_ctx_enable (5 samples, 0.01%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::encrypt (3,908 samples, 10.06%)<zssp::crypto_i..zssp::crypto_impl::openssl::CipherCtx::update (3,898 samples, 10.04%)zssp::crypto_im..EVP_EncryptUpdate (3,895 samples, 10.03%)EVP_EncryptUpda..[libcrypto.so.3] (3,844 samples, 9.90%)[libcrypto.so...[libcrypto.so.3] (3,838 samples, 9.88%)[libcrypto.so...[libcrypto.so.3] (3,819 samples, 9.83%)[libcrypto.so...[libcrypto.so.3] (3,007 samples, 7.74%)[libcrypto...[libcrypto.so.3] (2,753 samples, 7.09%)[libcrypto..asm_sysvec_reschedule_ipi (10 samples, 0.03%)sysvec_reschedule_ipi (10 samples, 0.03%)irqentry_exit (10 samples, 0.03%)irqentry_exit_to_user_mode (10 samples, 0.03%)exit_to_user_mode_prepare (10 samples, 0.03%)exit_to_user_mode_loop (10 samples, 0.03%)schedule (10 samples, 0.03%)__schedule (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_pmu_nop_void (5 samples, 0.01%)core::ptr::drop_in_place<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc> (11 samples, 0.03%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::crypto_impl::openssl::CipherCtx>> (11 samples, 0.03%)<std::sync::mutex::MutexGuard<T> as core::ops::drop::Drop>::drop (11 samples, 0.03%)std::sys::unix::locks::futex_mutex::Mutex::unlock (11 samples, 0.03%)core::sync::atomic::AtomicU32::swap (11 samples, 0.03%)core::sync::atomic::atomic_swap (11 samples, 0.03%)zssp::crypto_impl::openssl::CipherCtx::finalize (60 samples, 0.15%)EVP_EncryptFinal_ex (60 samples, 0.15%)[libcrypto.so.3] (53 samples, 0.14%)[libcrypto.so.3] (53 samples, 0.14%)[libcrypto.so.3] (48 samples, 0.12%)CRYPTO_gcm128_tag (48 samples, 0.12%)CRYPTO_gcm128_finish (40 samples, 0.10%)[libcrypto.so.3] (26 samples, 0.07%)[libc.so.6] (21 samples, 0.05%)OSSL_PARAM_locate (45 samples, 0.12%)<zssp::crypto_impl::openssl::AesGcmOpenSSLEnc as zssp::crypto::aes::AesGcmEncContext>::finish (153 samples, 0.39%)zssp::crypto_impl::openssl::CipherCtx::get_tag (80 samples, 0.21%)EVP_CIPHER_CTX_ctrl (79 samples, 0.20%)[libcrypto.so.3] (55 samples, 0.14%)OSSL_PARAM_set_octet_string (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (13 samples, 0.03%)std::sys::unix::locks::futex_mutex::Mutex::lock (13 samples, 0.03%)core::sync::atomic::AtomicU32::compare_exchange (13 samples, 0.03%)core::sync::atomic::atomic_compare_exchange (13 samples, 0.03%)[libcrypto.so.3] (40 samples, 0.10%)OSSL_PARAM_locate (36 samples, 0.09%)[libc.so.6] (20 samples, 0.05%)EVP_CIPHER_CTX_get_iv_length (67 samples, 0.17%)asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_pmu_nop_void (5 samples, 0.01%)[libc.so.6] (17 samples, 0.04%)OSSL_PARAM_locate (31 samples, 0.08%)strcmp@plt (5 samples, 0.01%)EVP_CIPHER_CTX_set_padding (67 samples, 0.17%)[libcrypto.so.3] (49 samples, 0.13%)OSSL_PARAM_locate_const (6 samples, 0.02%)EVP_CIPHER_free (21 samples, 0.05%)<zssp::crypto_impl::openssl::AesGcmOpenSSLPool as zssp::crypto::aes::HighThroughputAesGcmPool>::start_enc (206 samples, 0.53%)zssp::crypto_impl::openssl::CipherCtx::cipher_init (193 samples, 0.50%)EVP_CipherInit_ex (193 samples, 0.50%)[libcrypto.so.3] (193 samples, 0.50%)EVP_CIPHER_up_ref (27 samples, 0.07%)__rdl_alloc (6 samples, 0.02%)std::sys::unix::alloc::<impl core::alloc::global::GlobalAlloc for std::alloc::System>::alloc (6 samples, 0.02%)[libc.so.6] (60 samples, 0.15%)[libc.so.6] (978 samples, 2.52%)[l..asm_sysvec_reschedule_ipi (5 samples, 0.01%)sysvec_reschedule_ipi (5 samples, 0.01%)irqentry_exit (5 samples, 0.01%)irqentry_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)exit_to_user_mode_loop (5 samples, 0.01%)schedule (5 samples, 0.01%)__schedule (5 samples, 0.01%)finish_task_switch.isra.0 (5 samples, 0.01%)__perf_event_task_sched_in (5 samples, 0.01%)perf_ctx_enable (5 samples, 0.01%)__entry_text_start (4 samples, 0.01%)__get_user_nocheck_4 (14 samples, 0.04%)futex_q_lock (5 samples, 0.01%)futex_q_unlock (17 samples, 0.04%)futex_wait (45 samples, 0.12%)futex_wait_setup (42 samples, 0.11%)do_futex (46 samples, 0.12%)__x64_sys_futex (47 samples, 0.12%)entry_SYSCALL_64_after_hwframe (52 samples, 0.13%)do_syscall_64 (52 samples, 0.13%)syscall_exit_to_user_mode (5 samples, 0.01%)exit_to_user_mode_prepare (5 samples, 0.01%)__lll_lock_wait_private (74 samples, 0.19%)__entry_text_start (10 samples, 0.03%)futex_hash (7 samples, 0.02%)_raw_spin_lock (8 samples, 0.02%)native_queued_spin_lock_slowpath (8 samples, 0.02%)futex_wake_mark (10 samples, 0.03%)wake_q_add_safe (4 samples, 0.01%)__smp_call_single_queue (12 samples, 0.03%)native_send_call_func_single_ipi (8 samples, 0.02%)x2apic_send_IPI (7 samples, 0.02%)native_write_msr (5 samples, 0.01%)llist_add_batch (10 samples, 0.03%)try_to_wake_up (77 samples, 0.20%)ttwu_queue_wakelist (37 samples, 0.10%)futex_wake (145 samples, 0.37%)wake_up_q (85 samples, 0.22%)do_futex (158 samples, 0.41%)__x64_sys_futex (159 samples, 0.41%)exit_to_user_mode_prepare (6 samples, 0.02%)do_syscall_64 (172 samples, 0.44%)syscall_exit_to_user_mode (8 samples, 0.02%)entry_SYSCALL_64_after_hwframe (175 samples, 0.45%)alloc::vec::Vec<T,A>::with_capacity_in (1,972 samples, 5.08%)alloc:..alloc::raw_vec::RawVec<T,A>::with_capacity_in (1,972 samples, 5.08%)alloc:..alloc::raw_vec::RawVec<T,A>::allocate_in (1,972 samples, 5.08%)alloc:..<alloc::alloc::Global as core::alloc::Allocator>::allocate (1,970 samples, 5.07%)<alloc..alloc::alloc::Global::alloc_impl (1,970 samples, 5.07%)alloc:..alloc::alloc::alloc (1,970 samples, 5.07%)alloc:..malloc (1,962 samples, 5.05%)malloc__lll_lock_wake_private (192 samples, 0.49%)alloc::slice::<impl [T]>::to_vec (2,175 samples, 5.60%)alloc::..alloc::slice::<impl [T]>::to_vec_in (2,175 samples, 5.60%)alloc::..alloc::slice::hack::to_vec (2,175 samples, 5.60%)alloc::..<T as alloc::slice::hack::ConvertVec>::to_vec (2,175 samples, 5.60%)<T as a..core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (203 samples, 0.52%)core::intrinsics::copy_nonoverlapping (203 samples, 0.52%)[libc.so.6] (201 samples, 0.52%)core::sync::atomic::AtomicUsize::compare_exchange_weak (118 samples, 0.30%)core::sync::atomic::atomic_compare_exchange_weak (118 samples, 0.30%)std::sync::mpmc::array::Channel<T>::start_send (183 samples, 0.47%)core::sync::atomic::AtomicUsize::load (37 samples, 0.10%)core::sync::atomic::atomic_load (37 samples, 0.10%)core::ptr::mut_ptr::<impl *mut T>::write (7 samples, 0.02%)core::ptr::write (7 samples, 0.02%)benchmark::bob_main::_{{closure}} (3,947 samples, 10.16%)benchmark::bob_..std::sync::mpsc::SyncSender<T>::send (1,771 samples, 4.56%)std::..std::sync::mpmc::Sender<T>::send (1,764 samples, 4.54%)std::..std::sync::mpmc::array::Channel<T>::send (219 samples, 0.56%)std::sync::mpmc::array::Channel<T>::write (17 samples, 0.04%)std::sync::mpmc::waker::SyncWaker::notify (5 samples, 0.01%)core::mem::drop (38 samples, 0.10%)core::ptr::drop_in_place<std::sync::rwlock::RwLockReadGuard<zssp::zeta::MutableState<benchmark::TestApplication>>> (38 samples, 0.10%)<std::sync::rwlock::RwLockReadGuard<T> as core::ops::drop::Drop>::drop (38 samples, 0.10%)std::sys::unix::locks::futex_rwlock::RwLock::read_unlock (38 samples, 0.10%)core::sync::atomic::AtomicU32::fetch_sub (36 samples, 0.09%)core::sync::atomic::atomic_sub (36 samples, 0.09%)core::slice::<impl [T]>::copy_from_slice (10 samples, 0.03%)core::intrinsics::copy_nonoverlapping (10 samples, 0.03%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index (6 samples, 0.02%)core::slice::index::<impl core::ops::index::Index<I> for [T]>::index (11 samples, 0.03%)<core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index (5 samples, 0.01%)core::slice::index::<impl core::ops::index::IndexMut<I> for [T]>::index_mut (5 samples, 0.01%)<core::ops::range::Range<usize> as core::slice::index::SliceIndex<[T]>>::index_mut (5 samples, 0.01%)core::sync::atomic::AtomicU32::compare_exchange_weak (34 samples, 0.09%)core::sync::atomic::atomic_compare_exchange_weak (34 samples, 0.09%)std::sync::rwlock::RwLock<T>::read (38 samples, 0.10%)std::sys::unix::locks::futex_rwlock::RwLock::read (38 samples, 0.10%)benchmark::bob_main (17,970 samples, 46.28%)benchmark::bob_mainzssp::zssp::Context<Crypto>::send (8,668 samples, 22.32%)zssp::zssp::Context<Crypto>::sendzssp::zeta::send_payload (8,667 samples, 22.32%)zssp::zeta::send_payloadzssp::zeta::get_counter (8 samples, 0.02%)core::sync::atomic::AtomicU64::fetch_add (7 samples, 0.02%)core::sync::atomic::atomic_add (7 samples, 0.02%)cfree (25 samples, 0.06%)clock_gettime (17 samples, 0.04%)core::hash::BuildHasher::hash_one (25 samples, 0.06%)core::hash::impls::<impl core::hash::Hash for &T>::hash (18 samples, 0.05%)core::ptr::drop_in_place<std::sync::mutex::MutexGuard<zssp::fragged::Fragged<alloc::vec::Vec<u8>,48_usize>>> (4 samples, 0.01%)core::result::Result<T,E>::unwrap (10 samples, 0.03%)malloc (11 samples, 0.03%)std::sync::mpmc::Receiver<T>::recv_timeout (25 samples, 0.06%)std::sync::mpmc::Receiver<T>::recv_deadline (10 samples, 0.03%)std::sync::mpmc::Sender<T>::send (13 samples, 0.03%)std::sync::mpmc::array::Channel<T>::recv (28 samples, 0.07%)std::sync::mpmc::array::Channel<T>::send (14 samples, 0.04%)std::sync::mpmc::array::Channel<T>::start_recv (21 samples, 0.05%)std::sync::mpmc::utils::Backoff::new (22 samples, 0.06%)std::sync::mpmc::waker::SyncWaker::notify (22 samples, 0.06%)core::sync::atomic::AtomicBool::load (5 samples, 0.01%)core::sync::atomic::atomic_load (5 samples, 0.01%)std::sync::mutex::Mutex<T>::lock (9 samples, 0.02%)std::sys::unix::time::Timespec::sub_timespec (9 samples, 0.02%)std::sys::unix::time::inner::<impl std::sys::unix::time::Timespec>::now (9 samples, 0.02%)std::time::SystemTime::checked_add (7 samples, 0.02%)zssp::fragged::Fragged<Fragment,_>::assemble (19 samples, 0.05%)zssp::zeta::from_nonce (8 samples, 0.02%)alloc::vec::Vec<T,A>::with_capacity_in (20 samples, 0.05%)alloc::raw_vec::RawVec<T,A>::with_capacity_in (20 samples, 0.05%)alloc::raw_vec::RawVec<T,A>::allocate_in (20 samples, 0.05%)<alloc::alloc::Global as core::alloc::Allocator>::allocate (20 samples, 0.05%)alloc::alloc::Global::alloc_impl (20 samples, 0.05%)alloc::alloc::alloc (20 samples, 0.05%)alloc::slice::<impl [T]>::to_vec (29 samples, 0.07%)alloc::slice::<impl [T]>::to_vec_in (29 samples, 0.07%)alloc::slice::hack::to_vec (29 samples, 0.07%)<T as alloc::slice::hack::ConvertVec>::to_vec (29 samples, 0.07%)core::ptr::const_ptr::<impl *const T>::copy_to_nonoverlapping (9 samples, 0.02%)core::intrinsics::copy_nonoverlapping (9 samples, 0.02%)zssp::zeta::send_payload (76 samples, 0.20%)benchmark::bob_main::_{{closure}} (34 samples, 0.09%)std::sync::mpsc::SyncSender<T>::send (5 samples, 0.01%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (6 samples, 0.02%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (6 samples, 0.02%)arrayvec::arrayvec::ArrayVec<T,_>::clear (6 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (6 samples, 0.02%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (6 samples, 0.02%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (6 samples, 0.02%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (6 samples, 0.02%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (6 samples, 0.02%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (6 samples, 0.02%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (6 samples, 0.02%)alloc::alloc::dealloc (6 samples, 0.02%)std::collections::hash::map::HashMap<K,V,S>::get (5 samples, 0.01%)hashbrown::map::HashMap<K,V,S,A>::get (5 samples, 0.01%)hashbrown::map::HashMap<K,V,S,A>::get_inner (5 samples, 0.01%)hashbrown::map::make_hash (5 samples, 0.01%)zssp::zssp::Context<Crypto>::receive (78 samples, 0.20%)std::sync::mpmc::array::Channel<T>::start_recv (8 samples, 0.02%)[unknown] (37,624 samples, 96.89%)[unknown]zssp::zssp::parse_fragment_header (7 samples, 0.02%)EVP_DecryptUpdate (24 samples, 0.06%)__bss_start (26 samples, 0.07%)__rust_probestack (6 samples, 0.02%)entry_SYSCALL_64_after_hwframe (7 samples, 0.02%)entry_SYSCALL_64_safe_stack (14 samples, 0.04%)ret_from_fork (10 samples, 0.03%)schedule_tail (10 samples, 0.03%)finish_task_switch.isra.0 (10 samples, 0.03%)__perf_event_task_sched_in (10 samples, 0.03%)perf_ctx_enable (10 samples, 0.03%)core::ptr::drop_in_place<arrayvec::arrayvec::ArrayVec<alloc::vec::Vec<u8>,48_usize>> (5 samples, 0.01%)<arrayvec::arrayvec::ArrayVec<T,_> as core::ops::drop::Drop>::drop (5 samples, 0.01%)arrayvec::arrayvec::ArrayVec<T,_>::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::clear (5 samples, 0.01%)arrayvec::arrayvec_impl::ArrayVecImpl::truncate (5 samples, 0.01%)core::ptr::drop_in_place<[alloc::vec::Vec<u8>]> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::vec::Vec<u8>> (5 samples, 0.01%)core::ptr::drop_in_place<alloc::raw_vec::RawVec<u8>> (5 samples, 0.01%)<alloc::raw_vec::RawVec<T,A> as core::ops::drop::Drop>::drop (5 samples, 0.01%)<alloc::alloc::Global as core::alloc::Allocator>::deallocate (5 samples, 0.01%)alloc::alloc::dealloc (5 samples, 0.01%)benchmark (38,828 samples, 99.99%)benchmarkzssp::zssp::Context<Crypto>::receive (58 samples, 0.15%)std::collections::hash::map::HashMap<K,V,S>::get (6 samples, 0.02%)hashbrown::map::HashMap<K,V,S,A>::get (6 samples, 0.02%)hashbrown::map::HashMap<K,V,S,A>::get_inner (6 samples, 0.02%)hashbrown::map::make_hash (6 samples, 0.02%)all (38,833 samples, 100%)perf-exec (5 samples, 0.01%)entry_SYSCALL_64_after_hwframe (5 samples, 0.01%)do_syscall_64 (5 samples, 0.01%)__x64_sys_execve (5 samples, 0.01%)do_execveat_common.isra.0 (5 samples, 0.01%)bprm_execve (5 samples, 0.01%)bprm_execve.part.0 (5 samples, 0.01%)exec_binprm (5 samples, 0.01%)search_binary_handler (5 samples, 0.01%)load_elf_binary (5 samples, 0.01%)begin_new_exec (5 samples, 0.01%)perf_event_exec (5 samples, 0.01%)perf_event_enable_on_exec (4 samples, 0.01%)ctx_resched (4 samples, 0.01%)perf_ctx_enable (4 samples, 0.01%) \ No newline at end of file From 609c352957c003ad3b2fff9ef7877434643c16bf Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 14 Aug 2023 17:59:14 -0400 Subject: [PATCH 81/91] got rid of extra lines --- src/zeta.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/zeta.rs b/src/zeta.rs index cd4ce9a..8af5e23 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -1376,13 +1376,8 @@ pub(crate) fn received_k1_trans Date: Tue, 15 Aug 2023 12:11:59 -0400 Subject: [PATCH 82/91] made cipherctx pub --- src/crypto_impl/openssl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto_impl/openssl.rs b/src/crypto_impl/openssl.rs index ce2b25b..43125ae 100644 --- a/src/crypto_impl/openssl.rs +++ b/src/crypto_impl/openssl.rs @@ -7,7 +7,7 @@ use openssl_sys::*; use crate::crypto::*; -struct CipherCtx(NonNull); +pub struct CipherCtx(NonNull); impl Drop for CipherCtx { fn drop(&mut self) { unsafe { From ddab1cf216db1d3c853198f3e99381f04cbc4f2f Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 15 Aug 2023 17:15:29 -0400 Subject: [PATCH 83/91] added kyber update --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/crypto_impl/kyber1024.rs | 4 +++- src/frag_cache.rs | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50f595c..e1e0058 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,9 +215,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "pqc_kyber" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c578a7eab95d8649f115dd6f90894d167766e82a6bcf66ff25f60e7dfc857e" +checksum = "1b5dd33c0b42d244b01ab4f6cabaeb03c3b875017780fb3903b53b9c91fb6663" dependencies = [ "rand_core", ] diff --git a/Cargo.toml b/Cargo.toml index 80513a0..0078064 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ rand_core = { version = "0.6.4" } zeroize = { version = "1.6.0" } arrayvec = { version = "0.7.4", default-features = false, features = ["std", "zeroize"] } -pqc_kyber = { version = "0.6.0", default-features = false, features = ["kyber1024", "std"], optional = true } +pqc_kyber = { version = "0.7.0", default-features = false, features = ["kyber1024", "std"], optional = true } p384 = { version = "0.13.0", default-features = false, features = ["ecdh"], optional = true } sha2 = { version = "0.10.7", default-features = false, optional = true } hmac = { version = "0.12.1", default-features = false, optional = true } diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs index feffd5f..4a70105 100644 --- a/src/crypto_impl/kyber1024.rs +++ b/src/crypto_impl/kyber1024.rs @@ -8,7 +8,9 @@ use crate::crypto::*; pub type RustKyber1024PrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; impl Kyber1024PrivateKey for RustKyber1024PrivateKey { fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) { - let keypair = pqc_kyber::keypair(rng); + // According to the source code this can only fail if the RNG fails. + // Idk why rust allows RNG to fail. + let keypair = pqc_kyber::keypair(rng).unwrap(); (Zeroizing::new(keypair.secret), keypair.public) } diff --git a/src/frag_cache.rs b/src/frag_cache.rs index e9f15b9..5fdd804 100644 --- a/src/frag_cache.rs +++ b/src/frag_cache.rs @@ -117,7 +117,7 @@ impl UnassociatedFragCache { if self.map[idx].key == 0 { // This is a new entry so initialize it. if (fragment_count as usize) <= self.frags_unused_size { - let mut entry = &mut self.map[idx]; + let entry = &mut self.map[idx]; entry.key = key; entry.frags_idx = self.frags_first_unused as u32; entry.fragment_have = 0; @@ -135,7 +135,7 @@ impl UnassociatedFragCache { return; } } - let mut entry = &mut self.map[idx]; + let entry = &mut self.map[idx]; let new_size = entry.packet_size + fragment_size as u32; let got = 1u64.wrapping_shl(fragment_no as u32); From 49bc88d0e6bc23ad26e3aacbe8b87a153451fa8d Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 16 Aug 2023 11:16:47 -0400 Subject: [PATCH 84/91] added a pool --- examples/benchmark.rs | 79 +++++++++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/examples/benchmark.rs b/examples/benchmark.rs index b2ec3fb..b3be688 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -1,5 +1,5 @@ use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{mpsc, Arc}; +use std::sync::{mpsc, Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; @@ -13,7 +13,6 @@ use zssp::application::{ }; use zssp::crypto::P384KeyPair; use zssp::crypto_impl::*; -use zssp::result::ReceiveError; use zssp::Session; const TEST_MTU: usize = 1500; @@ -22,6 +21,34 @@ struct TestApplication { time: Instant, } +struct PooledVec(Vec); +static POOL: Mutex>> = Mutex::new(Vec::new()); +fn alloc(b: &[u8]) -> PooledVec { + let mut p = POOL.lock().unwrap(); + let mut v = p.pop().unwrap_or_default(); + v.extend(b); + PooledVec(v) +} +impl Drop for PooledVec { + fn drop(&mut self) { + let mut p = POOL.lock().unwrap(); + let mut v = Vec::new(); + std::mem::swap(&mut self.0, &mut v); + v.clear(); + p.push(v); + } +} +impl AsMut<[u8]> for PooledVec { + fn as_mut(&mut self) -> &mut [u8] { + self.0.as_mut() + } +} +impl AsRef<[u8]> for PooledVec { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + #[allow(unused)] impl CryptoLayer for TestApplication { type Rng = OsRng; @@ -37,7 +64,7 @@ impl CryptoLayer for TestApplication { type SessionData = (); - type IncomingPacketBuffer = Vec; + type IncomingPacketBuffer = PooledVec; } #[allow(unused)] impl ApplicationLayer for &TestApplication { @@ -97,8 +124,8 @@ impl ApplicationLayer for &TestApplication { fn alice_main( run: &AtomicBool, alice_app: &TestApplication, - alice_out: mpsc::SyncSender>, - alice_in: mpsc::Receiver>, + alice_out: mpsc::SyncSender, + alice_in: mpsc::Receiver, alice_keypair: P384CrateKeyPair, bob_pubkey: P384CratePublicKey, ) { @@ -113,7 +140,7 @@ fn alice_main( context .open( alice_app, - |b| alice_out.send(b.to_vec()).is_ok(), + |b| alice_out.send(alloc(b)).is_ok(), TEST_MTU, bob_pubkey.clone(), (), @@ -132,9 +159,9 @@ fn alice_main( output_data.clear(); match context.receive( alice_app, - |b| alice_out.send(b.to_vec()).is_ok(), + |b| alice_out.send(alloc(b)).is_ok(), TEST_MTU, - |_| Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)), + |_| Some((|b: &mut [u8]| alice_out.send(alloc(b)).is_ok(), TEST_MTU)), &0, pkt, &mut output_data, @@ -154,10 +181,10 @@ fn alice_main( _ => panic!(), }, Err(e) => { - println!("[alice] ERROR {:?}", e); - if let ReceiveError::ByzantineFault { unnatural, .. } = e { - assert!(!unnatural) - } + //println!("[alice] ERROR {:?}", e); + //if let ReceiveError::ByzantineFault { unnatural, .. } = e { + // assert!(!unnatural) + //} } } } else { @@ -169,7 +196,7 @@ fn alice_main( context .send( alice_session.as_ref().unwrap(), - |b| alice_out.send(b.to_vec()).is_ok(), + |b| alice_out.send(alloc(b)).is_ok(), &mut [0u8; TEST_MTU], &test_data[..1400 + ((OsRng.next_u64() as usize) % (test_data.len() - 1400))], ) @@ -181,7 +208,7 @@ fn alice_main( if current_time >= next_service { next_service = current_time + context.service(alice_app, |_| { - Some((|b: &mut [u8]| alice_out.send(b.to_vec()).is_ok(), TEST_MTU)) + Some((|b: &mut [u8]| alice_out.send(alloc(b)).is_ok(), TEST_MTU)) }); } } @@ -191,8 +218,8 @@ fn alice_main( fn bob_main( run: &AtomicBool, bob_app: &TestApplication, - bob_out: mpsc::SyncSender>, - bob_in: mpsc::Receiver>, + bob_out: mpsc::SyncSender, + bob_in: mpsc::Receiver, bob_keypair: P384CrateKeyPair, ) { let startup_time = std::time::Instant::now(); @@ -214,9 +241,9 @@ fn bob_main( output_data.clear(); match context.receive( bob_app, - |b| bob_out.send(b.to_vec()).is_ok(), + |b| bob_out.send(alloc(b)).is_ok(), TEST_MTU, - |_| Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)), + |_| Some((|b: &mut [u8]| bob_out.send(alloc(b)).is_ok(), TEST_MTU)), &0, pkt, &mut output_data, @@ -234,7 +261,7 @@ fn bob_main( context .send( &s, - |b| bob_out.send(b.to_vec()).is_ok(), + |b| bob_out.send(alloc(b)).is_ok(), &mut [0u8; TEST_MTU], &output_data, ) @@ -244,10 +271,10 @@ fn bob_main( _ => panic!(), }, Err(e) => { - println!("[bob] ERROR {:?}", e); - if let ReceiveError::ByzantineFault { unnatural, .. } = e { - assert!(!unnatural) - } + //println!("[bob] ERROR {:?}", e); + //if let ReceiveError::ByzantineFault { unnatural, .. } = e { + // assert!(!unnatural) + //} } } } @@ -265,7 +292,7 @@ fn bob_main( if current_time >= next_service { next_service = current_time + context.service(bob_app, |_| { - Some((|b: &mut [u8]| bob_out.send(b.to_vec()).is_ok(), TEST_MTU)) + Some((|b: &mut [u8]| bob_out.send(alloc(b)).is_ok(), TEST_MTU)) }); } } @@ -280,8 +307,8 @@ fn core(time: u64) { let bob_pubkey = bob_keypair.public_key(); let bob_app = TestApplication { time: Instant::now() }; - let (alice_out, bob_in) = mpsc::sync_channel::>(256); - let (bob_out, alice_in) = mpsc::sync_channel::>(256); + let (alice_out, bob_in) = mpsc::sync_channel::(256); + let (bob_out, alice_in) = mpsc::sync_channel::(256); thread::scope(|ts| { { From fea819601af64964196f94194fbbc28a234e52a9 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 16 Aug 2023 11:35:46 -0400 Subject: [PATCH 85/91] added docs for benchmark fix --- examples/benchmark.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/examples/benchmark.rs b/examples/benchmark.rs index b3be688..58e5534 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -14,6 +14,7 @@ use zssp::application::{ use zssp::crypto::P384KeyPair; use zssp::crypto_impl::*; use zssp::Session; +use zssp::result::ReceiveError; const TEST_MTU: usize = 1500; @@ -21,6 +22,8 @@ struct TestApplication { time: Instant, } +/// We have to pool allocations or else variations in the speed of the memory allocator will bias +/// our performance stats. struct PooledVec(Vec); static POOL: Mutex>> = Mutex::new(Vec::new()); fn alloc(b: &[u8]) -> PooledVec { @@ -181,10 +184,10 @@ fn alice_main( _ => panic!(), }, Err(e) => { - //println!("[alice] ERROR {:?}", e); - //if let ReceiveError::ByzantineFault { unnatural, .. } = e { - // assert!(!unnatural) - //} + println!("[alice] ERROR {:?}", e); + if let ReceiveError::ByzantineFault { unnatural, .. } = e { + assert!(!unnatural) + } } } } else { @@ -271,10 +274,10 @@ fn bob_main( _ => panic!(), }, Err(e) => { - //println!("[bob] ERROR {:?}", e); - //if let ReceiveError::ByzantineFault { unnatural, .. } = e { - // assert!(!unnatural) - //} + println!("[bob] ERROR {:?}", e); + if let ReceiveError::ByzantineFault { unnatural, .. } = e { + assert!(!unnatural) + } } } } From 4c3861624e728d7a116540cc2402f4c8e004420a Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 16 Aug 2023 11:52:55 -0400 Subject: [PATCH 86/91] updated docs --- src/zssp.rs | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/src/zssp.rs b/src/zssp.rs index 27b24a6..91f8e66 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -131,12 +131,10 @@ impl Context { /// * `send` - Function to be called to send one or more initial packets to the remote being /// contacted /// * `mtu` - MTU for initial packets - /// * `remote_static_key` - Remote side's static public NIST P-384 key - /// * `application_data` - Arbitrary data meaningful to the application to include with session + /// * `static_remote_key` - Remote side's static public NIST P-384 key + /// * `session_data` - Arbitrary data meaningful to the application to include with session /// object - /// * `ratchet_state` - The last saved and confirmed ratchet state associated with this remote - /// peer, or None if we do not have one. - /// * `local_identity_blob` - Payload to be sent to Bob that contains the information necessary + /// * `identity` - Payload to be sent to Bob that contains the information necessary /// for the upper protocol to authenticate and approve of Alice's identity. pub fn open>( &self, @@ -176,25 +174,12 @@ impl Context { /// session and will always result in a new session ReceiveOk being returned. /// /// * `app` - Interface to application using ZSSP - /// * `check_allow_incoming_session` - Function to call to check whether an unidentified new - /// session should be accepted - /// * `check_accept_session` - Function to accept sessions after final negotiation. - /// The second argument is the identity blob that the remote peer sent us. The application - /// must verify this identity is associated with the remote peer's static key. - /// The third argument is the ratchet chain length, or ratchet count. - /// To prevent desync, if this function returns (Some(_), _), no other open session with the - /// same remote peer must exist. /// * `send_unassociated_reply` - Function to send reply packets directly when no session exists /// * `send_unassociated_mtu` - MTU for unassociated replies /// * `send_to` - Function to get senders for existing sessions, permitting MTU and path lookup /// * `remote_address` - Whatever the remote address is, as long as you can Hash it - /// * `data_buf` - Buffer to receive decrypted and authenticated object data (an error is - /// returned if too small) - /// * `incoming_physical_packet_buf` - Buffer containing incoming wire packet - /// (receive() takes ownership) - /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced - /// with the remote peer. Used to check the state of local offers we may currently have or want - /// to put in-flight. + /// * `incoming_fragment_buf` - Buffer containing incoming wire packet (the context takes ownership) + /// * `output_buffer` - Buffer to receive decrypted and authenticated object data pub fn receive<'a, App: ApplicationLayer, SendFn: FnMut(&mut [u8]) -> bool>( &self, mut app: App, @@ -605,7 +590,6 @@ impl Context { /// slice of `data` /// * `mtu_sized_buffer` - A writable work buffer whose size equals the MTU /// * `data` - Data to send - /// * `current_time` - Current time in milliseconds pub fn send( &self, session: &Arc>, @@ -622,11 +606,8 @@ impl Context { /// try to satisfy this but small variations in timing of up to +/- a second or two are not /// a problem. /// + /// * `app` - Interface to application using ZSSP /// * `send_to` - Function to get a sender and an MTU to send something over an active session - /// * `current_time` - Current time in milliseconds. Does not have to be monotonic, nor synced - /// with remote peers (although both of these properties would help reliability slightly). - /// Used to determine if any current handshakes should be resent or timed-out, or if a session - /// should rekey. pub fn service, SendFn: FnMut(&mut [u8]) -> bool>( &self, mut app: App, From 9604f8268abd3b8bdf8ff3b7d9b63698002aa405 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 16 Aug 2023 11:53:57 -0400 Subject: [PATCH 87/91] fixed .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c3a4a94..b3dabfd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /target perf*.data perf*.old -.svg +*.svg From 5739911ddd827001119333033297ce5340c065ff Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 16 Aug 2023 12:05:51 -0400 Subject: [PATCH 88/91] added default crypto trait --- Cargo.toml | 6 ++---- examples/basic_test.rs | 2 +- examples/benchmark.rs | 16 ++-------------- src/crypto_impl/kyber1024.rs | 4 ++-- src/crypto_impl/mod.rs | 22 ++++++++++++++++++++++ 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0078064..c7ec4f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,10 +25,8 @@ hmac = { version = "0.12.1", default-features = false, optional = true } openssl-sys = { version = "0.9.91", default-features = false, optional = true } [features] -default = ["debug", "p384", "sha2", "pqc_kyber", "openssl-sys"] +default = ["debug", "default-crypto"] +default-crypto = ["p384", "sha2", "pqc_kyber", "openssl-sys", "rand_core/getrandom"] sha2 = ["dep:sha2", "dep:hmac"] logging = [] debug = ["logging"] - -[dev-dependencies] -rand_core = { version = "0.6.4", features = ["getrandom"] } diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 11e5d0b..58a8ed5 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -57,7 +57,7 @@ impl CryptoLayer for TestApplication { type Hmac = HmacSha512Crate; type PublicKey = P384CratePublicKey; type KeyPair = P384CrateKeyPair; - type Kem = RustKyber1024PrivateKey; + type Kem = Kyber1024CratePrivateKey; type SessionData = u128; diff --git a/examples/benchmark.rs b/examples/benchmark.rs index 58e5534..cc30885 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -8,7 +8,7 @@ use rand_core::OsRng; use rand_core::RngCore; use zssp::application::{ - AcceptAction, ApplicationLayer, CryptoLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, + AcceptAction, ApplicationLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, RATCHET_SIZE, }; use zssp::crypto::P384KeyPair; @@ -53,20 +53,8 @@ impl AsRef<[u8]> for PooledVec { } #[allow(unused)] -impl CryptoLayer for TestApplication { - type Rng = OsRng; - type PrpEnc = Aes256OpenSSLEnc; - type PrpDec = Aes256OpenSSLDec; - type Aead = AesGcmOpenSSL; - type AeadPool = AesGcmOpenSSLPool; - type Hash = Sha512Crate; - type Hmac = HmacSha512Crate; - type PublicKey = P384CratePublicKey; - type KeyPair = P384CrateKeyPair; - type Kem = RustKyber1024PrivateKey; - +impl DefaultCrypto for TestApplication { type SessionData = (); - type IncomingPacketBuffer = PooledVec; } #[allow(unused)] diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs index 4a70105..dd50978 100644 --- a/src/crypto_impl/kyber1024.rs +++ b/src/crypto_impl/kyber1024.rs @@ -5,8 +5,8 @@ use crate::crypto::*; /// A wrapper for a buffer the size of a pqc_kyber secret key. /// The crate `pqc_kyber` is low level and operates directly on buffers of bytes. -pub type RustKyber1024PrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; -impl Kyber1024PrivateKey for RustKyber1024PrivateKey { +pub type Kyber1024CratePrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; +impl Kyber1024PrivateKey for Kyber1024CratePrivateKey { fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) { // According to the source code this can only fail if the RNG fails. // Idk why rust allows RNG to fail. diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs index d481ff4..e08234b 100644 --- a/src/crypto_impl/mod.rs +++ b/src/crypto_impl/mod.rs @@ -27,3 +27,25 @@ mod openssl; pub use openssl::*; #[cfg(feature = "openssl-sys")] pub use openssl_sys; + +#[cfg(feature = "default-crypto")] +pub trait DefaultCrypto { + type SessionData; + type IncomingPacketBuffer: AsMut<[u8]> + AsRef<[u8]>; +} +#[cfg(feature = "default-crypto")] +impl crate::application::CryptoLayer for C { + type Rng = rand_core::OsRng; + type PrpEnc = Aes256OpenSSLEnc; + type PrpDec = Aes256OpenSSLDec; + type Aead = AesGcmOpenSSL; + type AeadPool = AesGcmOpenSSLPool; + type Hash = Sha512Crate; + type Hmac = HmacSha512Crate; + type PublicKey = P384CratePublicKey; + type KeyPair = P384CrateKeyPair; + type Kem = Kyber1024CratePrivateKey; + + type SessionData = C::SessionData; + type IncomingPacketBuffer = C::IncomingPacketBuffer; +} From 5a9296c2cae5aaadc31d183afa98146406cd22d8 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 16 Aug 2023 12:10:14 -0400 Subject: [PATCH 89/91] renamed crypto impls --- examples/basic_test.rs | 34 ++++++++-------- examples/benchmark.rs | 16 ++++---- src/crypto_impl/kyber1024.rs | 4 +- src/crypto_impl/mod.rs | 18 ++++----- src/crypto_impl/openssl.rs | 76 ++++++++++++++++++------------------ src/crypto_impl/p384_impl.rs | 8 ++-- src/crypto_impl/sha512.rs | 10 ++--- 7 files changed, 83 insertions(+), 83 deletions(-) diff --git a/examples/basic_test.rs b/examples/basic_test.rs index 58a8ed5..1c5534c 100644 --- a/examples/basic_test.rs +++ b/examples/basic_test.rs @@ -49,15 +49,15 @@ impl CryptoLayer for TestApplication { }; type Rng = OsRng; - type PrpEnc = Aes256OpenSSLEnc; - type PrpDec = Aes256OpenSSLDec; - type Aead = AesGcmOpenSSL; - type AeadPool = AesGcmOpenSSLPool; - type Hash = Sha512Crate; - type Hmac = HmacSha512Crate; - type PublicKey = P384CratePublicKey; - type KeyPair = P384CrateKeyPair; - type Kem = Kyber1024CratePrivateKey; + type PrpEnc = OpenSSLAes256Enc; + type PrpDec = OpenSSLAes256Dec; + type Aead = OpenSSLAesGcm; + type AeadPool = OpenSSLAesGcmPool; + type Hash = CrateSha512; + type Hmac = CrateHmacSha512; + type PublicKey = CrateP384PublicKey; + type KeyPair = CrateP384KeyPair; + type Kem = CrateKyber1024PrivateKey; type SessionData = u128; @@ -81,7 +81,7 @@ impl ApplicationLayer for &TestApplication { fn check_accept_session( &mut self, - remote_static_key: &P384CratePublicKey, + remote_static_key: &CrateP384PublicKey, identity: &[u8], ) -> AcceptAction { AcceptAction { @@ -98,7 +98,7 @@ impl ApplicationLayer for &TestApplication { fn restore_by_identity( &mut self, - remote_static_key: &P384CratePublicKey, + remote_static_key: &CrateP384PublicKey, session_data: &u128, ) -> Result, ()> { let ratchets = self.ratchets.lock().unwrap(); @@ -107,7 +107,7 @@ impl ApplicationLayer for &TestApplication { fn save_ratchet_state( &mut self, - remote_static_key: &P384CratePublicKey, + remote_static_key: &CrateP384PublicKey, session_data: &u128, update_data: RatchetUpdate<'_>, ) -> Result<(), ()> { @@ -144,8 +144,8 @@ fn alice_main( alice_out: mpsc::SyncSender>, alice_in: mpsc::Receiver>, recursive_out: mpsc::SyncSender>, - alice_keypair: P384CrateKeyPair, - bob_pubkey: P384CratePublicKey, + alice_keypair: CrateP384KeyPair, + bob_pubkey: CrateP384PublicKey, ) { let startup_time = std::time::Instant::now(); let context = zssp::Context::::new(alice_keypair, OsRng); @@ -251,7 +251,7 @@ fn bob_main( bob_out: mpsc::SyncSender>, bob_in: mpsc::Receiver>, recursive_out: mpsc::SyncSender>, - bob_keypair: P384CrateKeyPair, + bob_keypair: CrateP384KeyPair, ) { let startup_time = std::time::Instant::now(); let context = zssp::Context::::new(bob_keypair, OsRng); @@ -335,13 +335,13 @@ fn bob_main( fn core(time: u64, packet_success_rate: u32) { let run = &AtomicBool::new(true); - let alice_keypair = P384CrateKeyPair::generate(&mut OsRng); + let alice_keypair = CrateP384KeyPair::generate(&mut OsRng); let alice_app = TestApplication { time: Instant::now(), name: "alice", ratchets: Mutex::new(Ratchets::new()), }; - let bob_keypair = P384CrateKeyPair::generate(&mut OsRng); + let bob_keypair = CrateP384KeyPair::generate(&mut OsRng); let bob_pubkey = bob_keypair.public_key(); let bob_app = TestApplication { time: Instant::now(), diff --git a/examples/benchmark.rs b/examples/benchmark.rs index cc30885..30778ff 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -75,7 +75,7 @@ impl ApplicationLayer for &TestApplication { fn check_accept_session( &mut self, - remote_static_key: &P384CratePublicKey, + remote_static_key: &CrateP384PublicKey, identity: &[u8], ) -> AcceptAction { AcceptAction { @@ -91,7 +91,7 @@ impl ApplicationLayer for &TestApplication { fn restore_by_identity( &mut self, - remote_static_key: &P384CratePublicKey, + remote_static_key: &CrateP384PublicKey, session_data: &(), ) -> Result, ()> { Ok(None) @@ -99,7 +99,7 @@ impl ApplicationLayer for &TestApplication { fn save_ratchet_state( &mut self, - remote_static_key: &P384CratePublicKey, + remote_static_key: &CrateP384PublicKey, session_data: &(), update_data: RatchetUpdate<'_>, ) -> Result<(), ()> { @@ -117,8 +117,8 @@ fn alice_main( alice_app: &TestApplication, alice_out: mpsc::SyncSender, alice_in: mpsc::Receiver, - alice_keypair: P384CrateKeyPair, - bob_pubkey: P384CratePublicKey, + alice_keypair: CrateP384KeyPair, + bob_pubkey: CrateP384PublicKey, ) { let startup_time = std::time::Instant::now(); let context = zssp::Context::::new(alice_keypair, OsRng); @@ -211,7 +211,7 @@ fn bob_main( bob_app: &TestApplication, bob_out: mpsc::SyncSender, bob_in: mpsc::Receiver, - bob_keypair: P384CrateKeyPair, + bob_keypair: CrateP384KeyPair, ) { let startup_time = std::time::Instant::now(); let context = zssp::Context::::new(bob_keypair, OsRng); @@ -292,9 +292,9 @@ fn bob_main( fn core(time: u64) { let run = &AtomicBool::new(true); - let alice_keypair = P384CrateKeyPair::generate(&mut OsRng); + let alice_keypair = CrateP384KeyPair::generate(&mut OsRng); let alice_app = TestApplication { time: Instant::now() }; - let bob_keypair = P384CrateKeyPair::generate(&mut OsRng); + let bob_keypair = CrateP384KeyPair::generate(&mut OsRng); let bob_pubkey = bob_keypair.public_key(); let bob_app = TestApplication { time: Instant::now() }; diff --git a/src/crypto_impl/kyber1024.rs b/src/crypto_impl/kyber1024.rs index dd50978..ef553c2 100644 --- a/src/crypto_impl/kyber1024.rs +++ b/src/crypto_impl/kyber1024.rs @@ -5,8 +5,8 @@ use crate::crypto::*; /// A wrapper for a buffer the size of a pqc_kyber secret key. /// The crate `pqc_kyber` is low level and operates directly on buffers of bytes. -pub type Kyber1024CratePrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; -impl Kyber1024PrivateKey for Kyber1024CratePrivateKey { +pub type CrateKyber1024PrivateKey = Zeroizing<[u8; pqc_kyber::KYBER_SECRETKEYBYTES]>; +impl Kyber1024PrivateKey for CrateKyber1024PrivateKey { fn generate(rng: &mut Rng) -> (Self, [u8; KYBER_PUBLIC_KEY_SIZE]) { // According to the source code this can only fail if the RNG fails. // Idk why rust allows RNG to fail. diff --git a/src/crypto_impl/mod.rs b/src/crypto_impl/mod.rs index e08234b..8aa0ceb 100644 --- a/src/crypto_impl/mod.rs +++ b/src/crypto_impl/mod.rs @@ -36,15 +36,15 @@ pub trait DefaultCrypto { #[cfg(feature = "default-crypto")] impl crate::application::CryptoLayer for C { type Rng = rand_core::OsRng; - type PrpEnc = Aes256OpenSSLEnc; - type PrpDec = Aes256OpenSSLDec; - type Aead = AesGcmOpenSSL; - type AeadPool = AesGcmOpenSSLPool; - type Hash = Sha512Crate; - type Hmac = HmacSha512Crate; - type PublicKey = P384CratePublicKey; - type KeyPair = P384CrateKeyPair; - type Kem = Kyber1024CratePrivateKey; + type PrpEnc = OpenSSLAes256Enc; + type PrpDec = OpenSSLAes256Dec; + type Aead = OpenSSLAesGcm; + type AeadPool = OpenSSLAesGcmPool; + type Hash = CrateSha512; + type Hmac = CrateHmacSha512; + type PublicKey = CrateP384PublicKey; + type KeyPair = CrateP384KeyPair; + type Kem = CrateKyber1024PrivateKey; type SessionData = C::SessionData; type IncomingPacketBuffer = C::IncomingPacketBuffer; diff --git a/src/crypto_impl/openssl.rs b/src/crypto_impl/openssl.rs index 43125ae..8a73e92 100644 --- a/src/crypto_impl/openssl.rs +++ b/src/crypto_impl/openssl.rs @@ -7,18 +7,18 @@ use openssl_sys::*; use crate::crypto::*; -pub struct CipherCtx(NonNull); -impl Drop for CipherCtx { +pub struct OpenSSLCtx(NonNull); +impl Drop for OpenSSLCtx { fn drop(&mut self) { unsafe { EVP_CIPHER_CTX_free(self.0.as_ptr()); } } } -impl CipherCtx { +impl OpenSSLCtx { /// Creates a new context. pub fn new() -> Option { - unsafe { Some(CipherCtx(NonNull::new(EVP_CIPHER_CTX_new())?)) } + unsafe { Some(OpenSSLCtx(NonNull::new(EVP_CIPHER_CTX_new())?)) } } pub unsafe fn cipher_init( @@ -88,13 +88,13 @@ impl CipherCtx { } } -pub struct Aes256OpenSSLEnc(Mutex); -unsafe impl Send for Aes256OpenSSLEnc {} -unsafe impl Sync for Aes256OpenSSLEnc {} +pub struct OpenSSLAes256Enc(Mutex); +unsafe impl Send for OpenSSLAes256Enc {} +unsafe impl Sync for OpenSSLAes256Enc {} -impl Aes256Enc for Aes256OpenSSLEnc { +impl Aes256Enc for OpenSSLAes256Enc { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self { - let ctx = CipherCtx::new().unwrap(); + let ctx = OpenSSLCtx::new().unwrap(); unsafe { let t = openssl_sys::EVP_aes_256_ecb(); assert!(ctx.cipher_init::(t, key.as_ptr(), ptr::null())); @@ -118,13 +118,13 @@ impl Aes256Enc for Aes256OpenSSLEnc { unsafe { assert!(ctx.update::(block, ptr)) } } } -pub struct Aes256OpenSSLDec(Mutex); -unsafe impl Send for Aes256OpenSSLDec {} -unsafe impl Sync for Aes256OpenSSLDec {} +pub struct OpenSSLAes256Dec(Mutex); +unsafe impl Send for OpenSSLAes256Dec {} +unsafe impl Sync for OpenSSLAes256Dec {} -impl Aes256Dec for Aes256OpenSSLDec { +impl Aes256Dec for OpenSSLAes256Dec { fn new(key: &[u8; AES_256_KEY_SIZE]) -> Self { - let ctx = CipherCtx::new().unwrap(); + let ctx = OpenSSLCtx::new().unwrap(); unsafe { let t = openssl_sys::EVP_aes_256_ecb(); assert!(ctx.cipher_init::(t, key.as_ptr(), ptr::null())); @@ -149,8 +149,8 @@ impl Aes256Dec for Aes256OpenSSLDec { } } -pub struct AesGcmOpenSSLEnc<'a>(MutexGuard<'a, CipherCtx>); -impl<'a> AesGcmEncContext for AesGcmOpenSSLEnc<'a> { +pub struct OpenSSLAesGcmEnc<'a>(MutexGuard<'a, OpenSSLCtx>); +impl<'a> AesGcmEncContext for OpenSSLAesGcmEnc<'a> { fn encrypt(&mut self, input: &[u8], output: &mut [u8]) { unsafe { assert!(self.0.update::(input, output.as_mut_ptr())) }; } @@ -165,8 +165,8 @@ impl<'a> AesGcmEncContext for AesGcmOpenSSLEnc<'a> { } } -pub struct AesGcmOpenSSLDec<'a>(MutexGuard<'a, CipherCtx>); -impl<'a> AesGcmDecContext for AesGcmOpenSSLDec<'a> { +pub struct OpenSSLAesGcmDec<'a>(MutexGuard<'a, OpenSSLCtx>); +impl<'a> AesGcmDecContext for OpenSSLAesGcmDec<'a> { fn decrypt_in_place(&mut self, data: &mut [u8]) { let p = data.as_mut_ptr(); unsafe { assert!(self.0.update::(data, p)) }; @@ -177,30 +177,30 @@ impl<'a> AesGcmDecContext for AesGcmOpenSSLDec<'a> { } } -pub struct AesGcmOpenSSLPool { - enc: [Mutex; 8], - dec: [Mutex; 8], +pub struct OpenSSLAesGcmPool { + enc: [Mutex; 8], + dec: [Mutex; 8], } -unsafe impl Send for AesGcmOpenSSLPool {} -unsafe impl Sync for AesGcmOpenSSLPool {} +unsafe impl Send for OpenSSLAesGcmPool {} +unsafe impl Sync for OpenSSLAesGcmPool {} -impl HighThroughputAesGcmPool for AesGcmOpenSSLPool { - type EncContext<'a> = AesGcmOpenSSLEnc<'a>; +impl HighThroughputAesGcmPool for OpenSSLAesGcmPool { + type EncContext<'a> = OpenSSLAesGcmEnc<'a>; - type DecContext<'a> = AesGcmOpenSSLDec<'a>; + type DecContext<'a> = OpenSSLAesGcmDec<'a>; fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self { unsafe { - AesGcmOpenSSLPool { + OpenSSLAesGcmPool { enc: std::array::from_fn(|_| { - let ctx = CipherCtx::new().unwrap(); + let ctx = OpenSSLCtx::new().unwrap(); let t = openssl_sys::EVP_aes_256_gcm(); assert!(ctx.cipher_init::(t, encrypt_key.as_ptr(), ptr::null())); openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); Mutex::new(ctx) }), dec: std::array::from_fn(|_| { - let ctx = CipherCtx::new().unwrap(); + let ctx = OpenSSLCtx::new().unwrap(); let t = openssl_sys::EVP_aes_256_gcm(); assert!(ctx.cipher_init::(t, decrypt_key.as_ptr(), ptr::null())); openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); @@ -210,27 +210,27 @@ impl HighThroughputAesGcmPool for AesGcmOpenSSLPool { } } - fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> AesGcmOpenSSLEnc { + fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmEnc { let i = u64::from_be_bytes(nonce[4..].try_into().unwrap()); let g = self.enc[(i as usize) % self.enc.len()].lock().unwrap(); unsafe { assert!(g.cipher_init::(ptr::null(), ptr::null(), nonce.as_ptr())); } - AesGcmOpenSSLEnc(g) + OpenSSLAesGcmEnc(g) } - fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> AesGcmOpenSSLDec { + fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmDec { let i = u64::from_be_bytes(nonce[4..].try_into().unwrap()); let g = self.dec[(i as usize) % self.enc.len()].lock().unwrap(); unsafe { assert!(g.cipher_init::(ptr::null(), ptr::null(), nonce.as_ptr())); } - AesGcmOpenSSLDec(g) + OpenSSLAesGcmDec(g) } } -pub struct AesGcmOpenSSL; -impl LowThroughputAesGcm for AesGcmOpenSSL { +pub struct OpenSSLAesGcm; +impl LowThroughputAesGcm for OpenSSLAesGcm { fn encrypt_in_place( key: &[u8; AES_256_KEY_SIZE], nonce: &[u8; AES_GCM_NONCE_SIZE], @@ -238,7 +238,7 @@ impl LowThroughputAesGcm for AesGcmOpenSSL { data: &mut [u8], ) -> [u8; AES_GCM_TAG_SIZE] { let mut output = [0u8; AES_GCM_TAG_SIZE]; - let ctx = CipherCtx::new().unwrap(); + let ctx = OpenSSLCtx::new().unwrap(); unsafe { let t = openssl_sys::EVP_aes_256_gcm(); assert!(ctx.cipher_init::(t, key.as_ptr(), nonce.as_ptr())); @@ -261,7 +261,7 @@ impl LowThroughputAesGcm for AesGcmOpenSSL { data: &mut [u8], tag: &[u8; AES_GCM_TAG_SIZE], ) -> bool { - let ctx = CipherCtx::new().unwrap(); + let ctx = OpenSSLCtx::new().unwrap(); unsafe { let t = openssl_sys::EVP_aes_256_gcm(); assert!(ctx.cipher_init::(t, key.as_ptr(), nonce.as_ptr())); @@ -282,7 +282,7 @@ mod test { #[test] fn aes_128_ecb() { let key = [1u8; 16]; - let ctx = CipherCtx::new().unwrap(); + let ctx = OpenSSLCtx::new().unwrap(); unsafe { assert!(ctx.cipher_init::(openssl_sys::EVP_aes_128_ecb(), key.as_ptr(), ptr::null())); openssl_sys::EVP_CIPHER_CTX_set_padding(ctx.as_ptr(), 0); diff --git a/src/crypto_impl/p384_impl.rs b/src/crypto_impl/p384_impl.rs index 77c0938..17bfb1b 100644 --- a/src/crypto_impl/p384_impl.rs +++ b/src/crypto_impl/p384_impl.rs @@ -3,8 +3,8 @@ use rand_core::{CryptoRng, RngCore}; use crate::crypto::*; -pub type P384CratePublicKey = PublicKey; -impl P384PublicKey for P384CratePublicKey { +pub type CrateP384PublicKey = PublicKey; +impl P384PublicKey for CrateP384PublicKey { fn from_bytes(raw_key: &[u8; P384_PUBLIC_KEY_SIZE]) -> Option { PublicKey::from_sec1_bytes(raw_key).ok() } @@ -15,8 +15,8 @@ impl P384PublicKey for P384CratePublicKey { } } -pub type P384CrateKeyPair = EphemeralSecret; -impl P384KeyPair for P384CrateKeyPair { +pub type CrateP384KeyPair = EphemeralSecret; +impl P384KeyPair for CrateP384KeyPair { type PublicKey = PublicKey; fn generate(rng: &mut Rng) -> Self { diff --git a/src/crypto_impl/sha512.rs b/src/crypto_impl/sha512.rs index ba932f8..1a1bd4d 100644 --- a/src/crypto_impl/sha512.rs +++ b/src/crypto_impl/sha512.rs @@ -3,8 +3,8 @@ use sha2::{Digest, Sha512}; use crate::crypto::*; -pub type Sha512Crate = Sha512; -impl Sha512Hash for Sha512Crate { +pub type CrateSha512 = Sha512; +impl Sha512Hash for CrateSha512 { fn new() -> Self { Digest::new() } @@ -20,10 +20,10 @@ impl Sha512Hash for Sha512Crate { } } -pub struct HmacSha512Crate; -impl Sha512Hmac for HmacSha512Crate { +pub struct CrateHmacSha512; +impl Sha512Hmac for CrateHmacSha512 { fn new() -> Self { - HmacSha512Crate + CrateHmacSha512 } fn hash(&mut self, key: &[u8], full_input: &[u8], output: &mut [u8; SHA512_HASH_SIZE]) { From 81d12879a4ec395e79aaf3be658d7a7b4e53e18f Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 16 Aug 2023 12:38:17 -0400 Subject: [PATCH 90/91] cargo fmt --- examples/benchmark.rs | 5 ++--- src/proto.rs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/benchmark.rs b/examples/benchmark.rs index 30778ff..c570df9 100644 --- a/examples/benchmark.rs +++ b/examples/benchmark.rs @@ -8,13 +8,12 @@ use rand_core::OsRng; use rand_core::RngCore; use zssp::application::{ - AcceptAction, ApplicationLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, - RATCHET_SIZE, + AcceptAction, ApplicationLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, RATCHET_SIZE, }; use zssp::crypto::P384KeyPair; use zssp::crypto_impl::*; -use zssp::Session; use zssp::result::ReceiveError; +use zssp::Session; const TEST_MTU: usize = 1500; diff --git a/src/proto.rs b/src/proto.rs index e5a9249..da39de7 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -88,6 +88,7 @@ pub(crate) const LABEL_HEADER_KEY: &[u8; 4] = b"ASKH"; pub(crate) const LABEL_KEX_KEY: &[u8; 4] = b"ASKK"; pub(crate) const EXPIRE_AFTER_USES: u64 = 1 << 32 - 1; +pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - 1 << 16; /// Determines the number of counters a session will remember. If a counter arrives over /// this amount out of order relative to other received counters, it is likely to be /// rejected on the basis that the session can't remember if this counter was replayed. @@ -101,7 +102,6 @@ pub(crate) const COUNTER_WINDOW_MAX_SKIP_AHEAD: u64 = 1 << 24; /// When Bob issues a challenge to Alice to mitigate DDOS, Bob will only accept Alice's /// response once, and then its attached counter is added to the window. pub(crate) const CHALLENGE_COUNTER_WINDOW_MAX_OOO: usize = 32; -pub(crate) const THREAD_SAFE_COUNTER_HARD_EXPIRE: u64 = u64::MAX - 1 << 16; /* Packet constants */ From 4a2d3d44763900535ac2fec3367c1a315adf820b Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 23 Aug 2023 10:11:45 -0700 Subject: [PATCH 91/91] made send more permissive --- src/zeta.rs | 2 +- src/zssp.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zeta.rs b/src/zeta.rs index 8af5e23..5ff2992 100644 --- a/src/zeta.rs +++ b/src/zeta.rs @@ -1623,7 +1623,7 @@ pub(crate) fn received_k2_trans( ctx: &Arc>, - session: &Arc>, + session: &Session, payload: &[u8], mut send: impl FnMut(&mut [u8]) -> bool, mtu_sized_buffer: &mut [u8], diff --git a/src/zssp.rs b/src/zssp.rs index 91f8e66..ad55015 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -592,7 +592,7 @@ impl Context { /// * `data` - Data to send pub fn send( &self, - session: &Arc>, + session: &Session, send: impl FnMut(&mut [u8]) -> bool, mtu_sized_buffer: &mut [u8], data: &[u8],