From caaf164da4f9c0247ac20aabfbee988c3d9497af Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 22 Nov 2023 10:27:05 -0500 Subject: [PATCH 01/16] updated comment --- performance/src/result.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/performance/src/result.rs b/performance/src/result.rs index f65387b..1c8bbba 100644 --- a/performance/src/result.rs +++ b/performance/src/result.rs @@ -190,7 +190,7 @@ pub enum ReceiveOk { Unassociated, /// The received packet was authentic and belongs to this specific session. Associated(Arc>, SessionEvent), - /// The received packet was a fragment of a larger packet. + /// The received packet was an incomplete fragment of a larger packet. /// /// ***The authenticity of this fragment cannot be fully known yet.*** /// This return value should only be used for debugging and tracing purposes. From 7cd944f63592c0d218b1b3b15d20e6023612369b Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 12 Dec 2023 12:34:05 -0500 Subject: [PATCH 02/16] fixed comment typo --- performance/src/application.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/performance/src/application.rs b/performance/src/application.rs index 100e418..9ef5882 100644 --- a/performance/src/application.rs +++ b/performance/src/application.rs @@ -277,8 +277,9 @@ pub trait ApplicationLayer: Sized { /// 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 the peer's ratchet states could not be found, this function should return `None`. + /// A return value of `None` is equivalent to a return value of + /// `Some(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. From 9a7edcd65b6745c011bf05cba46079332efac9e7 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 19 Dec 2023 12:08:43 -0500 Subject: [PATCH 03/16] removed unsourced claim --- whitepaper/zssp.pdf | Bin 423914 -> 423834 bytes whitepaper/zssp.tex | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/whitepaper/zssp.pdf b/whitepaper/zssp.pdf index b0e76805d9c7623f59d440fb06ce0f1d00308c1f..105fab4bedde1daeeac76059a368dc1ac87e2b37 100644 GIT binary patch delta 24537 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 delta 24626 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 Q@?3@nMqH|@uKsRZ0QnE&tpET3 diff --git a/whitepaper/zssp.tex b/whitepaper/zssp.tex index 75262e0..58905f7 100644 --- a/whitepaper/zssp.tex +++ b/whitepaper/zssp.tex @@ -995,7 +995,7 @@ This protocol violates the Silence is Golden principle of security. So we recomm \section{ZeroTier Fragmentation Protocol} -ZSSP supports the partitioning of large packets into smaller fragments, that can each be sent individually between peers, and will be reassembled at the other end. Most datagram protocols have a maximum transmission unit (MTU), that all packets must be smaller than. Fragmentation makes it possible for the upper protocol to send packets much larger than the MTU. The MTU of UDP is inconsistent and can go as low as 1280 bytes. Satellite links can go even smaller, with MTU in the 500 bytes range. While many applications would work with such small MTU, they would not work efficiently, and having such low MTUs even as just a possibility often greatly hinders the design of a protocol, and what platforms it can support. To make real-time networking operate reliably and efficiently on top of links with low MTU, fragmentation is necessary. +ZSSP supports the partitioning of large packets into smaller fragments, that can each be sent individually between peers, and will be reassembled at the other end. Most datagram protocols have a maximum transmission unit (MTU), that all packets must be smaller than. Fragmentation makes it possible for the upper protocol to send packets much larger than the MTU. The MTU of UDP is inconsistent and can go as low as 1280 bytes. And if we consider non-IP networks, MTU can go as low as just a few hundred bytes. While many applications would work with small MTU, they might not work efficiently, and having such low MTUs even as just a possibility often greatly hinders the design of a protocol, and what platforms it can support. To make real-time networking operate reliably and efficiently on top of links with low MTU, fragmentation is necessary. ZFP is responsible for fragmentation within ZSSP. It sits at the very bottom of the ZSSP protocol stack, just above the datagram protocol. We have chosen to put the fragmentation protocol at the bottom of the ZSSP stack, instead of the top for a few reasons. First and foremost it allows key exchange packets to be as large as we need, larger than the MTU of the underlying datagram protocol. This allows us to use P384 and Kyber1024 keys, instead of their smaller and less secure variants. The second reason is that it allows the bandwidth overhead of fragmentation to be extremely small, just 16 bytes per fragment. If the fragmentation protocol were at the top of the stack instead, then each fragment would have required an additional 16 bytes for each MAC. This also benefits efficiency, because it reduces the number of MACs that must be computed and subsequently verified. From f8c763d6b9f0395f2744922ce739080841f93f5f Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 4 Jan 2024 10:18:54 -0500 Subject: [PATCH 04/16] added open_with_ratchet --- performance/Cargo.toml | 2 +- performance/src/zeta.rs | 6 ++--- performance/src/zssp.rs | 50 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/performance/Cargo.toml b/performance/Cargo.toml index 2c25885..f63d0b6 100644 --- a/performance/Cargo.toml +++ b/performance/Cargo.toml @@ -3,7 +3,7 @@ authors = ["ZeroTier, Inc. ", "Adam Ierymenko >( s_remote: C::PublicKey, session_data: C::SessionData, identity: &[u8], + ratchet_states: RatchetStates, send: impl FnOnce(&mut [u8], Option<&C::PrpEnc>), ) -> Result<(Arc>, Option), OpenError> { - let RatchetStates { state1, state2 } = app - .restore_by_identity(&s_remote, &session_data, None) - .map_err(OpenError::StorageError)? - .unwrap_or_default(); + let RatchetStates { state1, state2 } = ratchet_states; let mut session_queue = ctx.session_queue.lock().unwrap(); let mut session_map = ctx.session_map.write().unwrap(); diff --git a/performance/src/zssp.rs b/performance/src/zssp.rs index 9d7fc18..f50ec15 100644 --- a/performance/src/zssp.rs +++ b/performance/src/zssp.rs @@ -161,6 +161,54 @@ impl Context { /// * `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, + mut app: App, + send: impl Sender, + mtu: usize, + static_remote_key: C::PublicKey, + session_data: C::SessionData, + identity: &[u8], + ) -> Result<(Arc>, Option), OpenError> { + let ratchet_states = app + .restore_by_identity(&static_remote_key, &session_data, None) + .map_err(OpenError::StorageError)? + .unwrap_or_default(); + self.open_with_ratchet(app, send, mtu, static_remote_key, session_data, identity, ratchet_states) + } + /// Create a new session and send initialization packets to Bob, our remote peer. + /// This function will use the specified `ratchet_states` to connect to Bob, as opposed to + /// calling `app.restore_by_identity`. This can be used to open a session with a specific + /// one-time-password using `RatchetStates::new_otp_states()`, or in situations where it is + /// desireable to avoid having Alice call `app.restore_by_identity`. + /// + /// If using a one-time-password, a call to `app.initiator_disallows_downgrade` on the created + /// session **MUST** return `true`. Otherwise the remote peer would be able to avoid having + /// to demonstrate knowledge of the otp by requesting a ratchet downgrade. + /// Only after the created session is established may a call to + /// `app.initiator_disallows_downgrade` return false. + /// + /// The session will not be "established" right away, and so will not be able to send data to + /// the remote peer until they respond and finish the handshake. A `SessionEvent` variant of + /// `Established` will be returned by `Context::receive` when this session is able to send data. + /// + /// This function returns an `Option`, which can safely be ignored if not using + /// `Context::service_scheduled`. `Context::service_scheduled` contains documentation on how to + /// handle the return value. + /// + /// To prevent desync, when this function is called, no other open session with the same remote + /// peer must exist. Drop or call expire on any pre-existing sessions before calling. + /// + /// * `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. + /// * `ratchet_states` - The set of ratchet states that Alice should use to connect to Bob. + pub fn open_with_ratchet>( &self, app: App, send: impl Sender, @@ -168,6 +216,7 @@ impl Context { static_remote_key: C::PublicKey, session_data: C::SessionData, identity: &[u8], + ratchet_states: RatchetStates, ) -> Result<(Arc>, Option), OpenError> { mtu = mtu.max(MIN_TRANSPORT_MTU); if identity.len() > IDENTITY_MAX_SIZE { @@ -180,6 +229,7 @@ impl Context { static_remote_key, session_data, identity, + ratchet_states, |packet, hk_send| { send_with_fragmentation(send, mtu, packet, hk_send); }, From ef5973c42a745695ed8bfebcc94af2aff6b07ef0 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 5 Jan 2024 09:01:37 -0500 Subject: [PATCH 05/16] added tentative protocol requirement --- whitepaper/zssp.pdf | Bin 423834 -> 423683 bytes whitepaper/zssp.tex | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/whitepaper/zssp.pdf b/whitepaper/zssp.pdf index 105fab4bedde1daeeac76059a368dc1ac87e2b37..3a17d5d40b69ad54c481e9867bc14810b641a86e 100644 GIT binary patch delta 51219 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
    >tKGYc{XX zIor5-M{k2mB{QeSR-an&Rlj*Y*7NPVa^l39#rE+s$=`o|V5ni;$Hsqpz`q_N3}>Ijyzn&GGB0qS4E^HbiUuy-*ig1{CjL9>*>E&6;~fB`usgbchjFqzYFWno&MJs zaZ@hvvUp&*=Aw^tJErj~UAxg%bwj8 Date: Wed, 17 Jan 2024 17:43:53 -0500 Subject: [PATCH 06/16] fixed ratchet sync bug --- performance/examples/basic_test.rs | 30 ++- performance/examples/benchmark.rs | 8 +- performance/src/application.rs | 25 ++- performance/src/proto.rs | 12 +- performance/src/ratchet_state.rs | 259 ++++++++++++++++--------- performance/src/zeta.rs | 183 +++++++++-------- performance/src/zssp.rs | 21 +- reference/examples/extensive_test.rs | 35 +++- reference/examples/ping_pong.rs | 8 +- reference/src/application.rs | 25 ++- reference/src/context.rs | 28 +-- reference/src/fragmentation.rs | 8 +- reference/src/proto.rs | 5 +- reference/src/ratchet_state.rs | 267 ++++++++++++++++--------- reference/src/result.rs | 4 +- reference/src/zeta.rs | 280 ++++++++++++++------------- whitepaper/zssp.pdf | Bin 423683 -> 426253 bytes whitepaper/zssp.tex | 77 ++++---- 18 files changed, 770 insertions(+), 505 deletions(-) diff --git a/performance/examples/basic_test.rs b/performance/examples/basic_test.rs index ecde22c..37cf5ba 100644 --- a/performance/examples/basic_test.rs +++ b/performance/examples/basic_test.rs @@ -1,3 +1,4 @@ +use std::collections::hash_map::Entry; use std::collections::HashMap; use std::iter::ExactSizeIterator; use std::str::FromStr; @@ -10,7 +11,7 @@ use rand_core::OsRng; use rand_core::RngCore; use zssp::application::{ - AcceptAction, ApplicationLayer, CryptoLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, + AcceptAction, ApplicationLayer, CompareAndSwap, CryptoLayer, IncomingSessionAction, RatchetState, RatchetStates, Settings, RATCHET_SIZE, }; use zssp::crypto::P384KeyPair; @@ -114,14 +115,29 @@ impl ApplicationLayer for &TestApplication { &mut self, remote_static_key: &CrateP384PublicKey, session_data: &u128, - update_data: RatchetUpdate<'_>, - ) -> Result<(), std::io::Error> { + update_data: CompareAndSwap<'_>, + ) -> Result { let mut ratchets = self.ratchets.lock().unwrap(); - ratchets.peer_map.insert(*session_data, update_data.to_states()); + match ratchets.peer_map.entry(*session_data) { + Entry::Occupied(mut entry) => { + if update_data.compare(&entry.get()) { + entry.insert(update_data.to_new_states()); + } else { + return Ok(false); + } + } + Entry::Vacant(entry) => { + if update_data.cur_is_initial_states() { + entry.insert(update_data.to_new_states()); + } else { + return Ok(false); + } + } + } 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()); + ratchets.rf_map.insert(*rf, update_data.new_state1.clone()); + println!("[{}] new ratchet #{}", self.name, update_data.new_state1.chain_len()); } if let Some(rf) = update_data.deleted_fingerprint1() { ratchets.rf_map.remove(rf); @@ -129,7 +145,7 @@ impl ApplicationLayer for &TestApplication { if let Some(rf) = update_data.deleted_fingerprint2() { ratchets.rf_map.remove(rf); } - Ok(()) + Ok(true) } fn time(&mut self) -> i64 { diff --git a/performance/examples/benchmark.rs b/performance/examples/benchmark.rs index 02c060f..edb77cd 100644 --- a/performance/examples/benchmark.rs +++ b/performance/examples/benchmark.rs @@ -8,7 +8,7 @@ use rand_core::OsRng; use rand_core::RngCore; use zssp::application::{ - AcceptAction, ApplicationLayer, IncomingSessionAction, RatchetState, RatchetStates, RatchetUpdate, RATCHET_SIZE, + AcceptAction, ApplicationLayer, CompareAndSwap, IncomingSessionAction, RatchetState, RatchetStates, RATCHET_SIZE, }; use zssp::crypto::P384KeyPair; use zssp::crypto_impl::*; @@ -106,9 +106,9 @@ impl ApplicationLayer for &TestApplication { &mut self, remote_static_key: &CrateP384PublicKey, session_data: &(), - update_data: RatchetUpdate<'_>, - ) -> Result<(), std::io::Error> { - Ok(()) + update_data: CompareAndSwap<'_>, + ) -> Result { + Ok(true) } fn time(&mut self) -> i64 { diff --git a/performance/src/application.rs b/performance/src/application.rs index 9ef5882..d568e97 100644 --- a/performance/src/application.rs +++ b/performance/src/application.rs @@ -296,11 +296,16 @@ pub trait ApplicationLayer: Sized { session_data: &C::SessionData, fingerprint_data: Option<&C::FingerprintData>, ) -> Result, std::io::Error>; - /// 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. + /// Atomically compare-and-swap (a.k.a. compare-exchange) `update` to storage. /// - /// If this returns `Err(())`, the packet which triggered this function to be called will be + /// If `update.cur_state1` and `update.cur_state2` are currently in storage, they must + /// be swapped with `update.new_state1` and `update.new_state2`. + /// Otherwise, storage must remain unchanged. + /// + /// `Ok(true)` must be returned if the compare-and-swap was successful. + /// `Ok(false)` must be returned if comparison failed and the swap was cancelled. + /// + /// 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. /// @@ -312,12 +317,20 @@ pub trait ApplicationLayer: Sized { /// This function may also save state to volatile storage, in which case all peers which connect /// to us will have to allow downgrade across the board. /// Otherwise, when we restart, we will not be allowed to reconnect. + /// + /// # Security + /// Implementations must not perform comparison operations (equals, less than, etc.) directly + /// on two ratchet keys. Instead, comparison operations must be performed indirectly + /// upon their ratchet fingerprints. If two ratchet states have the same ratchet fingerprint, + /// it should be assumed that they also have the same ratchet key. + /// + /// The implementations of `PartialEq` for `RatchetState` and `RatchetStates` do this by default. fn save_ratchet_state( &mut self, remote_static_key: &C::PublicKey, session_data: &C::SessionData, - update_data: RatchetUpdate<'_>, - ) -> Result<(), std::io::Error>; + update: CompareAndSwap<'_>, + ) -> 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 diff --git a/performance/src/proto.rs b/performance/src/proto.rs index 5b1c3cb..2435b34 100644 --- a/performance/src/proto.rs +++ b/performance/src/proto.rs @@ -124,14 +124,12 @@ 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_MAX_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + RATCHET_SIZE + RATCHET_SIZE; +pub(crate) const HANDSHAKE_HELLO_SIZE: usize = + KID_SIZE + P384_PUBLIC_KEY_SIZE + KYBER_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + 2 * RATCHET_SIZE + AES_GCM_TAG_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 HANDSHAKE_HELLO_CHALLENGE_SIZE: usize = HANDSHAKE_HELLO_SIZE + CHALLENGE_SIZE; -pub(crate) const HEADERED_HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE: usize = HANDSHAKE_HELLO_CHALLENGE_MAX_SIZE + HEADER_SIZE; +pub(crate) const HEADERED_HANDSHAKE_HELLO_CHALLENGE_SIZE: usize = HANDSHAKE_HELLO_CHALLENGE_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; @@ -184,6 +182,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_CHALLENGE_MAX_SIZE; +pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = HANDSHAKE_HELLO_CHALLENGE_SIZE; pub(crate) const SESSION_MAX_FRAGMENTS_OOO: usize = 64; diff --git a/performance/src/ratchet_state.rs b/performance/src/ratchet_state.rs index b12aa38..b41faef 100644 --- a/performance/src/ratchet_state.rs +++ b/performance/src/ratchet_state.rs @@ -14,30 +14,23 @@ use crate::proto::*; #[derive(Clone, Eq)] pub struct RatchetState { pub(crate) key: Zeroizing<[u8; RATCHET_SIZE]>, - pub(crate) fingerprint: Option>, + pub(crate) fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, pub(crate) chain_len: u64, } impl PartialEq for RatchetState { fn eq(&self, other: &Self) -> bool { - 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) + self.fingerprint.eq(&other.fingerprint) & (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())) - } + state.write(self.fingerprint.as_ref()) } } impl RatchetState { /// Creates a new ratchet state from the given ratchet key, ratchet fingerprint and chain length. pub fn new(key: Zeroizing<[u8; RATCHET_SIZE]>, fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, chain_len: u64) -> Self { - RatchetState { key, fingerprint: Some(fingerprint), chain_len } + RatchetState { key, fingerprint, chain_len } } /// Creates a new ratchet state from the given ratchet key, ratchet fingerprint and chain length. /// @@ -46,28 +39,10 @@ impl RatchetState { 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)), + fingerprint: Zeroizing::new(fingerprint), chain_len, } } - /// The ratchet key for this ratchet state. This is directly mixed into the master secret of a - /// session and so is very sensitive. All operations upon a ratchet key must be implemented - /// in constant time. The user should prefer to do nothing with the ratchet key besides copying - /// it to or from a storage device. - /// - /// If `fingerprint` returns `None` then this is the "empty" ratchet state and the key will be - /// all zeros. - pub fn key(&self) -> &[u8; RATCHET_SIZE] { - &self.key - } - /// Ratchet keys and fingerprints are "chained together", where each set is derived from the - /// previous set. - /// - /// This function outputs the total length of that chain, as in the total number of previous - /// ratchet states that this ratchet state was derived from. - pub fn chain_len(&self) -> u64 { - self.chain_len - } /// Creates a new "empty" ratchet state, where the ratchet fingerprint is the /// empty string, the ratchet key is all zeros, and the chain length is 0. /// @@ -75,7 +50,7 @@ impl RatchetState { pub fn empty() -> Self { RatchetState { key: Zeroizing::new([0u8; RATCHET_SIZE]), - fingerprint: None, + fingerprint: Zeroizing::new([0u8; RATCHET_SIZE]), chain_len: 0, } } @@ -99,15 +74,15 @@ impl RatchetState { Self::new(rk, rf, 1) } - /// Returns true if this is the "empty" ratchet state, where the ratchet fingerprint is the - /// empty string, the ratchet key is all zeros, and the chain length is 0. - pub fn is_empty(&self) -> bool { - self.fingerprint.is_none() - } - /// Checks if the fingerprint of this ratchet state equals the fingerprint contained in argument - /// `rf`. Uses constant time equality. - pub fn fingerprint_eq(&self, rf: &[u8; RATCHET_SIZE]) -> bool { - self.fingerprint.as_ref().map_or(false, |rf0| secure_eq(rf0, rf)) + /// The ratchet key for this ratchet state. This is directly mixed into the master secret of a + /// session and so is very sensitive. All operations upon a ratchet key must be implemented + /// in constant time. The user should prefer to do nothing with the ratchet key besides copying + /// it to or from a storage device. + /// + /// If `fingerprint` returns `None` then this is the "empty" ratchet state and the key will be + /// all zeros. + pub fn key(&self) -> &[u8; RATCHET_SIZE] { + &self.key } /// The ratchet fingerprint for this ratchet state. /// @@ -118,8 +93,43 @@ impl RatchetState { /// but the security of ZSSP can survive having this value leaked. /// Operations on a ratchet fingerprint should be implemented in constant time, /// but it is ok if they are not. - pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { - self.fingerprint.as_deref() + pub fn fingerprint(&self) -> &[u8; RATCHET_SIZE] { + &self.fingerprint + } + /// Ratchet keys and fingerprints are "chained together", where each set is derived from the + /// previous set. + /// + /// This function outputs the total length of that chain, as in the total number of previous + /// ratchet states that this ratchet state was derived from. + pub fn chain_len(&self) -> u64 { + self.chain_len + } + /// Returns true if this is the "empty" ratchet state, where the ratchet fingerprint is the + /// empty string, the ratchet key is all zeros, and the chain length is 0. + pub fn is_empty(&self) -> bool { + secure_eq(self.fingerprint(), &[0u8; RATCHET_SIZE]) + } + /// Checks if the fingerprint of this ratchet state equals the + /// fingerprint contained in argument `rf`. + /// + /// Uses constant time equality. + pub fn fingerprint_eq(&self, rf: &[u8; RATCHET_SIZE]) -> bool { + secure_eq(self.fingerprint(), rf) + } + /// Returns true if the fingerprint of argument `this` equals the fingerprint represented by + /// argument `rf`. + /// If `this` is `None`, `this` is considered to be "null", as in a ratchet state that does not + /// exist or has been deleted. + /// If `rf` is `None`, `rf` is considered to be "null" as well. + /// If `rf` is `Some(None)`, `rf` is considered to be the empty ratchet fingerprint. + /// + /// Uses constant time equality. + pub fn fingerprint_eq_nullable(this: Option<&Self>, rf: Option<&[u8; RATCHET_SIZE]>) -> bool { + match (this, rf) { + (Some(this), Some(rf)) => this.fingerprint_eq(rf), + (None, None) => true, + _ => false, + } } } impl Default for RatchetState { @@ -137,7 +147,8 @@ impl Default for RatchetState { pub struct RatchetStates { /// The first ratchet state from the pair. pub state1: RatchetState, - /// The second ratchet state from the pair. It can, and usually will be `None`. + /// The second ratchet state from the pair. + /// It can, and usually will be `None`, which means that this ratchet state is "null". pub state2: Option, } impl RatchetStates { @@ -172,7 +183,6 @@ impl Default for RatchetStates { Self::new_initial_states() } } - /// A set of references to ratchet states specifying how a remote peer's persistent storage should /// be updated. This struct is designed to provide any and all potentially needed data for /// maintaining a store of these ratchet states. It should be straightforward to commit these updates @@ -181,69 +191,138 @@ impl Default for RatchetStates { /// There will only be up to two ratchet states saved to storage at a time per peer. /// Every time a third ratchet state is generated, a previous ratchet state will be deleted. /// -/// These are sensitive values should they ought to be securely stored, with restricted read-write -/// permissions if stored on disk. +/// As the name implies, these updates should be applied as one atomic compare-and-swap operation. +/// If the two ratchet states currently in storage equal `cur_state1` and `cur_state2`, +/// then `new_state1` and `new_state2` should overwrite them. +/// If not, then storage must not be modified. /// -/// To prevent desync or resource leakage, these updates should be committed atomically. If that is -/// not possible, then new ratchet states should be written before old ratchet states are deleted +/// Ratchet keys must never be checked for equality. Instead, if two ratchet states have equal +/// ratchet fingerprints, it should be assumed that they also have equal ratchet keys. +/// This policy exists to reduce the security impact of timing and other side-channel attacks. +/// +/// These are sensitive values and ought to be securely stored, with restricted read-write +/// permissions if stored on disk. #[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>, +pub struct CompareAndSwap<'a> { + /// The ratchet state to store in the first slot. + pub new_state1: &'a RatchetState, + /// The ratchet state to store in the second slot. + /// A value of `None` implies that the second slot should be set to null. + pub new_state2: Option<&'a RatchetState>, + /// This field is `true` if and only if `new_state1 != cur_state1` and `new_state1 != cur_state2`. + /// + /// This implies whether `state1` is a brand new ratchet state, or if it was previously saved. + pub new_state1_was_just_added: bool, + /// The ratchet state that we expect to see in the first slot. + /// If this value is not currently stored in the first slot, the entire update must be aborted. + pub cur_state1: &'a RatchetState, + /// The ratchet state that we expect to see in the second slot. + /// A value of `None` implies we expect the second slot to be set to "null". + /// + /// If this value is not currently stored in the second slot, the entire update must be aborted. + pub cur_state2: Option<&'a RatchetState>, + /// This field is `true` if and only if `cur_state1 != new_state1` and `cur_state1 != new_state2`. + pub cur_state1_was_just_deleted: bool, + /// This field is `true` if and only if `cur_state2 != new_state1` and `cur_state2 != new_state2`. + pub cur_state2_was_just_deleted: bool, } -impl<'a> RatchetUpdate<'a> { - /// Returns the final `RatchetStates` that should be the only thing saved after this update is - /// fully committed. Future calls to `ApplicationLayer::restore_by_identity` should return this struct. - pub fn to_states(&self) -> RatchetStates { - RatchetStates::new(self.state1.clone(), self.state2.cloned()) +impl<'a> CompareAndSwap<'a> { + pub(crate) fn new( + new_state1: &'a RatchetState, + new_state2: Option<&'a RatchetState>, + new_state1_was_just_added: bool, + cur_state1: &'a RatchetState, + cur_state2: Option<&'a RatchetState>, + cur_state1_was_just_deleted: bool, + cur_state2_was_just_deleted: bool, + ) -> Self { + Self { + new_state1, + new_state2, + new_state1_was_just_added, + cur_state1, + cur_state2, + cur_state1_was_just_deleted, + cur_state2_was_just_deleted, + } + } + /// Returns the final `RatchetStates` that must be swapped into storage if this update is + /// fully committed. + /// Future calls to `ApplicationLayer::restore_by_identity` should return this struct. + pub fn to_new_states(&self) -> RatchetStates { + RatchetStates::new(self.new_state1.clone(), self.new_state2.cloned()) + } + /// Returns the `RatchetStates` that is expected to currently be in storage for this peer. + /// This value must be compared with the current value in storage, and if they are equal, + /// the return value of `CompareAndSwap::to_new_states` must overwrite it. + pub fn to_cur_states(&self) -> RatchetStates { + RatchetStates::new(self.cur_state1.clone(), self.cur_state2.cloned()) } /// If this update specifies adding a brand new ratchet fingerprint, this function will return it. /// The returned ratchet fingerprint will always be the ratchet fingerprint of field `state1`. /// - /// If a fingerprint is returned then it is guaranteed that `state1_was_just_added` will be true. + /// If a fingerprint is returned then it is guaranteed that `state1_was_just_added` will be `true`. pub fn added_fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { - if self.state1_was_just_added { - self.state1.fingerprint() - } else { - None - } + self.new_state1_was_just_added.then_some(self.new_state1.fingerprint()) } - /// If this updated specifies to delete an old ratchet fingerprint, this function will return it. - /// The returned ratchet fingerprint will always be the ratchet fingerprint of field - /// `deleted_state1`. + /// If this updated specifies to delete `cur_state1`, and `cur_state1` has a non-zero ratchet + /// fingerprint, this function will return the ratchet fingerprint. + /// + /// If `cur_state1` is not being deleted (i.e. `cur_state1 == new_state1` or + /// `cur_state1 == new_state2`), then this will return `None`. /// /// There may be a second ratchet fingerprint to be deleted, which function /// `deleted_fingerprint2` will return. pub fn deleted_fingerprint1(&self) -> Option<&[u8; RATCHET_SIZE]> { - if let Some(rs) = &self.deleted_state1 { - rs.fingerprint() - } else { - None + if self.cur_state1_was_just_deleted { + if !self.cur_state1.is_empty() { + return Some(self.cur_state1.fingerprint()); + } } + None } - /// If this updated specifies to delete two ratchet fingerprints, this function will return the - /// second one. `deleted_fingerprint1` will return the first one. - /// The returned ratchet fingerprint will always be the ratchet fingerprint of field - /// `deleted_state2`. + /// If this updated specifies to delete `cur_state2`, and `cur_state2` has a non-zero ratchet + /// fingerprint, this function will return the ratchet fingerprint. /// - /// It is exceptionally rare that there will be more than one ratchet fingerprint to be deleted. - /// Care should be taken to make sure that this update will still be correctly committed in the - /// rare event that this returns `Some`. + /// If `cur_state2` is not being deleted (i.e. `cur_state2 == new_state1` or + /// `cur_state2 == new_state2`), then this will return `None`. + /// + /// There may be a second ratchet fingerprint to be deleted, which function + /// `deleted_fingerprint1` will return. pub fn deleted_fingerprint2(&self) -> Option<&[u8; RATCHET_SIZE]> { - if let Some(rs) = &self.deleted_state2 { - rs.fingerprint() - } else { - None + if self.cur_state2_was_just_deleted { + if let Some(rf) = self.cur_state2 { + if !rf.is_empty() { + return Some(rf.fingerprint()); + } + } } + None + } + /// Returns true if the currently stored ratchet states are expected to be the initial ratchet + /// states. This is the default value for a peer's ratchet states in the event they could not + /// be found in storage. + /// + /// If a peer could not be found in storage, and this function returns true, + /// then the peer should be added to storage with the new ratchet states specified by this + /// `CompareAndSwap` struct. + pub fn cur_is_initial_states(&self) -> bool { + self.cur_state1.is_empty() && self.cur_state2.is_none() + } + /// Compares the ratchet fingerprints of `cur_state1` and `cur_state2` with `rf1` and `rf2`. + /// If they are equal this function will return `true`. + /// + /// If this function returns `true`, then the implementation may proceed to swap out the ratchet + /// states these fingerprints come from with `new_state1` and `new_state2`. + pub fn compare_fingerprints(&self, rf1: &[u8; RATCHET_SIZE], rf2: Option<&[u8; RATCHET_SIZE]>) -> bool { + self.cur_state1.fingerprint_eq(rf1) & RatchetState::fingerprint_eq_nullable(self.cur_state2, rf2) + } + /// Compares `cur_state1` and `cur_state2` with `other.state1` and `other.state2`. + /// If they are equal this function will return `true`. + /// + /// If this function returns `true`, then the implementation may proceed to swap `other.state1` + /// and `other.state2` with `new_state1` and `new_state2`. + pub fn compare(&self, other: &RatchetStates) -> bool { + self.cur_state1.eq(&other.state1) & self.cur_state2.eq(&other.state2.as_ref()) } } diff --git a/performance/src/zeta.rs b/performance/src/zeta.rs index 5ef163a..7536419 100644 --- a/performance/src/zeta.rs +++ b/performance/src/zeta.rs @@ -97,7 +97,7 @@ pub(crate) struct StateA1 { e_secret: C::KeyPair, e1_secret: C::Kem, identity: ArrayVec, - x1: ArrayVec, + x1: ArrayVec, } pub(crate) struct StateA3 { @@ -280,7 +280,7 @@ fn create_a1_state( // ... // -> 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_ne_bytes(); @@ -299,12 +299,9 @@ fn create_a1_state( x1.extend(tag); // 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.try_extend_from_slice(ratchet_state1.fingerprint()).unwrap(); + x1.try_extend_from_slice(ratchet_state2.map_or(&[0u8; RATCHET_SIZE], |r| r.fingerprint())) + .unwrap(); let tag = noise.encrypt_and_hash_in_place(hash, to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 1), &mut x1[i..]); x1.extend(tag); @@ -426,11 +423,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()) { + if x1.len() != HANDSHAKE_HELLO_SIZE { return Err(fault!(InvalidPacket, true)); } - if n[AES_GCM_NONCE_SIZE - 8..] != x1[x1.len() - 8..] { + if !secure_eq(&n[AES_GCM_NONCE_SIZE - 8..], &x1[x1.len() - 8..]) { return Err(fault!(FailedAuth, true)); } let hmac = &mut C::Hmac::new(); @@ -460,35 +457,48 @@ pub(crate) fn received_x1_trans>( let e1_end = j; i = k; // Process message pattern 1 payload. - let k = x1.len(); - let j = k - AES_GCM_TAG_SIZE; + let j = i + RATCHET_SIZE + RATCHET_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, 1), &mut x1[i..j], tag) { return Err(fault!(FailedAuth, true)); } + debug_assert_eq!(k, x1.len()); + let rf1 = &x1[i..i + RATCHET_SIZE]; + let rf2 = &x1[i + RATCHET_SIZE..j]; let mut lookup_data = None; let mut ratchet_state = None; - while i + RATCHET_SIZE <= j { - match app.restore_by_fingerprint((&x1[i..i + RATCHET_SIZE]).try_into().unwrap()) { + if !secure_eq(rf1, &[0u8; RATCHET_SIZE]) { + match app.restore_by_fingerprint(rf1.try_into().unwrap()) { Ok(None) => {} Ok(Some((rs, data))) => { lookup_data = Some(data); ratchet_state = Some(rs); - break; } Err(e) => return Err(ReceiveError::StorageError(e)), } - i += RATCHET_SIZE; } - let ratchet_state = if let Some(rs) = ratchet_state { - rs - } else { - if app.hello_requires_recognized_ratchet() { - return Err(fault!(FailedAuth, true)); + if ratchet_state.is_none() { + if !secure_eq(rf2, &[0u8; RATCHET_SIZE]) { + match app.restore_by_fingerprint(rf1.try_into().unwrap()) { + Ok(None) => {} + Ok(Some((rs, data))) => { + lookup_data = Some(data); + ratchet_state = Some(rs); + } + Err(e) => return Err(ReceiveError::StorageError(e)), + } } - RatchetState::empty() - }; + if ratchet_state.is_none() { + if app.hello_requires_recognized_ratchet() { + return Err(fault!(FailedAuth, true)); + } + } + } + // If we get to this point and haven't found a full ratchet state, + // set it to the empty ratchet state. + let ratchet_state = ratchet_state.unwrap_or_default(); let mut hk_recv = Zeroizing::new([0u8; HASHLEN]); let mut hk_send = Zeroizing::new([0u8; HASHLEN]); @@ -589,7 +599,7 @@ pub(crate) fn received_x2_trans>( return Err(fault!(UnknownLocalKeyId, true, session)); } let (_, c) = from_nonce(n); - if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD || n[AES_GCM_NONCE_SIZE - 3..] != x2[x2.len() - 3..] { + if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD || !secure_eq(&n[AES_GCM_NONCE_SIZE - 3..], &x2[x2.len() - 3..]) { return Err(fault!(FailedAuth, true, session)); } @@ -690,23 +700,27 @@ pub(crate) fn received_x2_trans>( let new_ratchet_state = create_ratchet_state(hmac, &noise, chain_len); - let (ratchet_to_preserve, ratchet_to_delete) = if ratchet_i == 1 { - (Some(&state.ratchet_state1), state.ratchet_state2.as_ref()) + let ratchet_to_preserve = if ratchet_i == 1 { + Some(&state.ratchet_state1) } else { - (state.ratchet_state2.as_ref(), Some(&state.ratchet_state1)) + state.ratchet_state2.as_ref() }; - app.save_ratchet_state( + 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, - deleted_state1: ratchet_to_delete, - deleted_state2: None, - }, - ) - .map_err(|e| ReceiveError::StorageError(e))?; + CompareAndSwap::new( + &new_ratchet_state, + ratchet_to_preserve, + true, + &state.ratchet_state1, + state.ratchet_state2.as_ref(), + ratchet_i == 2, + ratchet_i == 1, + ), + ); + if !result.map_err(ReceiveError::StorageError)? { + return Err(fault!(OutOfSequence, true, session, true)); + } let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); let mut kek_send = Zeroizing::new([0u8; HASHLEN]); @@ -736,7 +750,7 @@ pub(crate) fn received_x2_trans>( a1 } else { // This return is unreachable. - return Err(fault!(FailedAuth, true, session)); + return Err(fault!(FailedAuth, true, session, true)); }; state.beta = ZetaAutomata::A3(Box::new(StateA3 { identity: a1.identity.clone(), x3: x3.clone() })); resend_timer @@ -858,7 +872,7 @@ pub(crate) fn received_x3_trans>( 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() { + if !responder_disallows_downgrade && zeta.ratchet_state.is_empty() { should_warn_missing_ratchet = true; } else { if !responder_silently_rejects { @@ -877,18 +891,14 @@ pub(crate) fn received_x3_trans>( noise.split(hmac, &mut nk_send, &mut nk_recv); // We must make sure the ratchet key is saved before we transition. - app.save_ratchet_state( + let result = app.save_ratchet_state( &s_remote, &session_data, - RatchetUpdate { - state1: &new_ratchet_state, - state2: None, - state1_was_just_added: true, - deleted_state1: Some(&state1), - deleted_state2: state2.as_ref(), - }, - ) - .map_err(|e| ReceiveError::StorageError(e))?; + CompareAndSwap::new(&new_ratchet_state, None, true, &state1, state2.as_ref(), true, true), + ); + if !result.map_err(ReceiveError::StorageError)? { + return Err(fault!(OutOfSequence, true)); + } let (session, reduced_service_time) = { let mut session_map = ctx.session_map.write().unwrap(); @@ -1004,18 +1014,25 @@ pub(crate) fn received_c1_trans>( if is_other { if let ZetaAutomata::A3 { .. } | ZetaAutomata::R2 { .. } = &state.beta { if state.ratchet_state2.is_some() { - app.save_ratchet_state( + let result = app.save_ratchet_state( &session.s_remote, &session.session_data, - RatchetUpdate { - state1: &state.ratchet_state1, - state2: None, - state1_was_just_added: false, - deleted_state1: state.ratchet_state2.as_ref(), - deleted_state2: None, - }, - ) - .map_err(|e| ReceiveError::StorageError(e))?; + CompareAndSwap::new( + &state.ratchet_state1, + None, + false, + &state.ratchet_state1, + state.ratchet_state2.as_ref(), + false, + true, + ), + ); + if !result.map_err(ReceiveError::StorageError)? { + drop(state); + drop(kex_lock); + session.expire(); + return Err(fault!(OutOfSequence, true, session, true)); + } } drop(state); let timeout_timer = { @@ -1415,18 +1432,22 @@ pub(crate) fn received_k1_trans>( k2.extend(tag); let new_ratchet_state = create_ratchet_state(hmac, &noise, state.ratchet_state1.chain_len); - app.save_ratchet_state( + let result = app.save_ratchet_state( &session.s_remote, &session.session_data, - RatchetUpdate { - state1: &new_ratchet_state, - state2: Some(&state.ratchet_state1), - state1_was_just_added: true, - deleted_state1: state.ratchet_state2.as_ref(), - deleted_state2: None, - }, - ) - .map_err(|e| ReceiveError::StorageError(e))?; + CompareAndSwap::new( + &new_ratchet_state, + Some(&state.ratchet_state1), + true, + &state.ratchet_state1, + state.ratchet_state2.as_ref(), + false, + true, + ), + ); + if !result.map_err(ReceiveError::StorageError)? { + return Err(fault!(OutOfSequence, true, session, true)); + } let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); let mut kek_send = Zeroizing::new([0u8; HASHLEN]); @@ -1534,18 +1555,22 @@ pub(crate) fn received_k2_trans>( .ok_or_else(|| fault!(InvalidPacket, true, session, true))?; let new_ratchet_state = create_ratchet_state(hmac, &noise, state.ratchet_state1.chain_len); - app.save_ratchet_state( + let result = app.save_ratchet_state( &session.s_remote, &session.session_data, - RatchetUpdate { - state1: &new_ratchet_state, - state2: None, - state1_was_just_added: true, - deleted_state1: Some(&state.ratchet_state1), - deleted_state2: state.ratchet_state2.as_ref(), - }, - ) - .map_err(|e| ReceiveError::StorageError(e))?; + CompareAndSwap::new( + &new_ratchet_state, + None, + true, + &state.ratchet_state1, + state.ratchet_state2.as_ref(), + true, + true, + ), + ); + if !result.map_err(ReceiveError::StorageError)? { + return Err(fault!(OutOfSequence, true, session, true)); + } let mut kek_recv = Zeroizing::new([0u8; HASHLEN]); let mut kek_send = Zeroizing::new([0u8; HASHLEN]); diff --git a/performance/src/zssp.rs b/performance/src/zssp.rs index f50ec15..8825834 100644 --- a/performance/src/zssp.rs +++ b/performance/src/zssp.rs @@ -173,16 +173,25 @@ impl Context { .restore_by_identity(&static_remote_key, &session_data, None) .map_err(OpenError::StorageError)? .unwrap_or_default(); - self.open_with_ratchet(app, send, mtu, static_remote_key, session_data, identity, ratchet_states) + self.open_with_ratchet( + app, + send, + mtu, + static_remote_key, + session_data, + identity, + ratchet_states, + ) } /// Create a new session and send initialization packets to Bob, our remote peer. /// This function will use the specified `ratchet_states` to connect to Bob, as opposed to /// calling `app.restore_by_identity`. This can be used to open a session with a specific /// one-time-password using `RatchetStates::new_otp_states()`, or in situations where it is - /// desireable to avoid having Alice call `app.restore_by_identity`. + /// desireable to avoid having Alice call `app.restore_by_identity`. Keep in mind that + /// `save_ratchet_state` likely will eventually be called to delete this ratchet state. /// /// If using a one-time-password, a call to `app.initiator_disallows_downgrade` on the created - /// session **MUST** return `true`. Otherwise the remote peer would be able to avoid having + /// session **must** return `true`. Otherwise the remote peer would be able to avoid having /// to demonstrate knowledge of the otp by requesting a ratchet downgrade. /// Only after the created session is established may a call to /// `app.initiator_disallows_downgrade` return false. @@ -546,7 +555,7 @@ impl Context { return Err(fault!(InvalidPacket, true)); } - let mut buffer = ArrayVec::::new(); + let mut buffer = ArrayVec::::new(); let assembled_packet = if fragment_count > 1 { let mut next_service_time = self.0.unassociated_defrag_cache.lock().unwrap().assemble( &nonce, @@ -578,9 +587,7 @@ 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_SIZE != assembled_packet.len() { return Err(fault!(InvalidPacket, true)); } // Process recv challenge layer. diff --git a/reference/examples/extensive_test.rs b/reference/examples/extensive_test.rs index c5306ab..02496e7 100644 --- a/reference/examples/extensive_test.rs +++ b/reference/examples/extensive_test.rs @@ -6,6 +6,7 @@ * https://www.zerotier.com/ */ +use std::collections::hash_map::Entry; use std::collections::HashMap; use std::iter::ExactSizeIterator; use std::str::FromStr; @@ -18,7 +19,7 @@ use rand_core::OsRng; use rand_core::RngCore; use zssp_proto::application::{ - AcceptAction, ApplicationLayer, CryptoLayer, RatchetState, RatchetStates, RatchetUpdate, Settings, RATCHET_SIZE, + AcceptAction, ApplicationLayer, CompareAndSwap, CryptoLayer, RatchetState, RatchetStates, Settings, RATCHET_SIZE, }; use zssp_proto::crypto::P384KeyPair; use zssp_proto::crypto_impl::{ @@ -105,21 +106,37 @@ impl ApplicationLayer for &mut TestApplication { &mut self, remote_static_key: &P384CratePublicKey, session_data: &u128, - update_data: RatchetUpdate<'_>, - ) -> Result<(), std::io::Error> { - self.ratchets.peer_map.insert(*session_data, update_data.to_states()); + update_data: CompareAndSwap<'_>, + ) -> Result { + let mut ratchets = &mut self.ratchets; + match ratchets.peer_map.entry(*session_data) { + Entry::Occupied(mut entry) => { + if update_data.compare(&entry.get()) { + entry.insert(update_data.to_new_states()); + } else { + return Ok(false); + } + } + Entry::Vacant(entry) => { + if update_data.cur_is_initial_states() { + entry.insert(update_data.to_new_states()); + } else { + return Ok(false); + } + } + } if let Some(rf) = update_data.added_fingerprint() { - self.ratchets.rf_map.insert(*rf, update_data.state1.clone()); - println!("[{}] new ratchet #{}", self.name, update_data.state1.chain_len()); + ratchets.rf_map.insert(*rf, update_data.new_state1.clone()); + println!("[{}] new ratchet #{}", self.name, update_data.new_state1.chain_len()); } if let Some(rf) = update_data.deleted_fingerprint1() { - self.ratchets.rf_map.remove(rf); + ratchets.rf_map.remove(rf); } if let Some(rf) = update_data.deleted_fingerprint2() { - self.ratchets.rf_map.remove(rf); + ratchets.rf_map.remove(rf); } - Ok(()) + Ok(true) } fn time(&mut self) -> i64 { diff --git a/reference/examples/ping_pong.rs b/reference/examples/ping_pong.rs index cb007d7..85b633e 100644 --- a/reference/examples/ping_pong.rs +++ b/reference/examples/ping_pong.rs @@ -12,7 +12,7 @@ use std::sync::Arc; use std::time::Instant; use zssp_proto::application::{ - AcceptAction, ApplicationLayer, CryptoLayer, RatchetState, RatchetStates, RatchetUpdate, RATCHET_SIZE, + AcceptAction, ApplicationLayer, CompareAndSwap, CryptoLayer, RatchetState, RatchetStates, RATCHET_SIZE, }; use zssp_proto::crypto::{rand_core::OsRng, P384KeyPair}; use zssp_proto::crypto_impl::{ @@ -81,9 +81,9 @@ impl ApplicationLayer for &mut MyApp { &mut self, remote_static_key: &P384CratePublicKey, session_data: &(), - update_data: RatchetUpdate<'_>, - ) -> Result<(), std::io::Error> { - Ok(()) + update_data: CompareAndSwap<'_>, + ) -> Result { + Ok(true) } fn time(&mut self) -> i64 { diff --git a/reference/src/application.rs b/reference/src/application.rs index 5758acc..67c0151 100644 --- a/reference/src/application.rs +++ b/reference/src/application.rs @@ -213,11 +213,16 @@ pub trait ApplicationLayer: Sized { remote_static_key: &::PublicKey, session_data: &::SessionData, ) -> Result, std::io::Error>; - /// 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. + /// Atomically compare-and-swap (a.k.a. compare-exchange) `update` to storage. /// - /// If this returns `Err(IoError)`, the packet which triggered this function to be called will be + /// If `update.cur_state1` and `update.cur_state2` are currently in storage, they must + /// be swapped with `update.new_state1` and `update.new_state2`. + /// Otherwise, storage must remain unchanged. + /// + /// `Ok(true)` must be returned if the compare-and-swap was successful. + /// `Ok(false)` must be returned if comparison failed and the swap was cancelled. + /// + /// 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. /// @@ -229,12 +234,20 @@ pub trait ApplicationLayer: Sized { /// This function may also save state to volatile storage, in which case all peers which connect /// to us will have to allow downgrade across the board. /// Otherwise, when we restart, we will not be allowed to reconnect. + /// + /// # Security + /// Implementations must not perform comparison operations (equals, less than, etc.) directly + /// on two ratchet keys. Instead, comparison operations must be performed indirectly + /// upon their ratchet fingerprints. If two ratchet states have the same ratchet fingerprint, + /// it should be assumed that they also have the same ratchet key. + /// + /// The implementations of `PartialEq` for `RatchetState` and `RatchetStates` do this by default. fn save_ratchet_state( &mut self, remote_static_key: &::PublicKey, session_data: &::SessionData, - update_data: RatchetUpdate<'_>, - ) -> Result<(), std::io::Error>; + update_data: CompareAndSwap<'_>, + ) -> 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 diff --git a/reference/src/context.rs b/reference/src/context.rs index e6cbd58..e45b0fe 100644 --- a/reference/src/context.rs +++ b/reference/src/context.rs @@ -11,7 +11,7 @@ use crate::challenge::ChallengeContext; use crate::crypto::{AES_256_KEY_SIZE, AES_GCM_NONCE_SIZE}; use crate::fragmentation::{send_with_fragmentation, DefragBuffer}; use crate::proto::*; -use crate::result::{byzantine_fault, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; +use crate::result::{fault, OpenError, ReceiveError, ReceiveOk, SendError, SessionEvent}; use crate::zeta::*; #[cfg(feature = "logging")] use crate::LogEvent::*; @@ -161,24 +161,24 @@ impl Context { 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)); + return Err(fault!(OutOfSequence, false)); } if c >= COUNTER_WINDOW_MAX_SKIP_AHEAD { - return Err(byzantine_fault!(ExpiredCounter, true)); + return Err(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)); + return Err(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)); + return Err(fault!(InvalidPacket, false)); } else { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } })?; if let Some((pn, mut assembled_packet)) = result { @@ -295,7 +295,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. }; drop(zeta); Ok(ReceiveOk::Session(session, ret)) @@ -315,7 +315,7 @@ impl Context { if p == PACKET_TYPE_HANDSHAKE_COMPLETION && c == 0 { Ok(()) } else { - Err(byzantine_fault!(InvalidPacket, true)) + Err(fault!(InvalidPacket, true)) } })?; if let Some((_, assembled_packet)) = result { @@ -351,7 +351,7 @@ impl Context { } 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)) + Err(fault!(UnknownLocalKeyId, false)) } } } else { @@ -365,7 +365,7 @@ impl Context { if p == PACKET_TYPE_HANDSHAKE_HELLO || p == PACKET_TYPE_CHALLENGE { Ok(()) } else { - Err(byzantine_fault!(InvalidPacket, true)) + Err(fault!(InvalidPacket, true)) } }, )?; @@ -394,7 +394,7 @@ impl Context { None, ); // If we issue a challenge the first hello packet will always fail. - return Err(byzantine_fault!(FailedAuth, false)); + return Err(fault!(FailedAuth, false)); } else if let Ok(true) = result { log!(app, X1SucceededChallenge); } @@ -423,7 +423,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_be_bytes(assembled_packet[..KID_SIZE].try_into().unwrap())) @@ -439,9 +439,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)) } } else { Ok(ReceiveOk::Unassociated) diff --git a/reference/src/fragmentation.rs b/reference/src/fragmentation.rs index 626fc69..34a847b 100644 --- a/reference/src/fragmentation.rs +++ b/reference/src/fragmentation.rs @@ -7,7 +7,7 @@ use zeroize::Zeroizing; use crate::application::CryptoLayer; use crate::crypto::{Aes256Prp, AES_256_KEY_SIZE}; use crate::proto::*; -use crate::result::{byzantine_fault, ReceiveError}; +use crate::result::{fault, ReceiveError}; /// Corresponds to Figure 13 found in Section 6. fn create_fragment_header( @@ -94,7 +94,7 @@ impl DefragBuffer { ) -> Result)>, ReceiveError> { use crate::result::FaultType::*; if raw_fragment.len() < MIN_PACKET_SIZE { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } if let Some(hk_recv) = self.hk_recv.as_ref() { @@ -109,7 +109,7 @@ impl DefragBuffer { let fragment_no = raw_fragment[FRAGMENT_NO_IDX] as usize; let fragment_count = raw_fragment[FRAGMENT_COUNT_IDX] as usize; if fragment_no >= fragment_count || fragment_count > MAX_FRAGMENTS { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } let n = raw_fragment[PACKET_NONCE_START..HEADER_SIZE].try_into().unwrap(); @@ -125,7 +125,7 @@ impl DefragBuffer { || raw_fragment.len() > buffer.fragment_max_size { // Some parts of the protocol can cause duplicate fragments to be sent. - return Err(byzantine_fault!(InvalidPacket, false)); + return Err(fault!(InvalidPacket, false)); } buffer.fragments[fragment_no] = Some(raw_fragment); buffer.total += 1; diff --git a/reference/src/proto.rs b/reference/src/proto.rs index 65947e9..67d9c13 100644 --- a/reference/src/proto.rs +++ b/reference/src/proto.rs @@ -112,9 +112,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_MAX_SIZE: usize = HANDSHAKE_HELLO_MIN_SIZE + RATCHET_SIZE + RATCHET_SIZE; +pub(crate) const HANDSHAKE_HELLO_SIZE: usize = + KID_SIZE + P384_PUBLIC_KEY_SIZE + KYBER_PUBLIC_KEY_SIZE + AES_GCM_TAG_SIZE + 2 * RATCHET_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; diff --git a/reference/src/ratchet_state.rs b/reference/src/ratchet_state.rs index fc2c85d..9c37054 100644 --- a/reference/src/ratchet_state.rs +++ b/reference/src/ratchet_state.rs @@ -12,31 +12,24 @@ use crate::proto::*; /// Corresponds to the Ratchet Key and Ratchet Fingerprint described in Section 3. #[derive(Clone, Eq)] pub struct RatchetState { - key: Zeroizing<[u8; RATCHET_SIZE]>, - fingerprint: Option>, - chain_len: u64, + pub(crate) key: Zeroizing<[u8; RATCHET_SIZE]>, + pub(crate) fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, + pub(crate) chain_len: u64, } impl PartialEq for RatchetState { fn eq(&self, other: &Self) -> bool { - 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) + self.fingerprint.eq(&other.fingerprint) & (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())) - } + state.write(self.fingerprint.as_ref()) } } impl RatchetState { /// Creates a new ratchet state from the given ratchet key, ratchet fingerprint and chain length. pub fn new(key: Zeroizing<[u8; RATCHET_SIZE]>, fingerprint: Zeroizing<[u8; RATCHET_SIZE]>, chain_len: u64) -> Self { - RatchetState { key, fingerprint: Some(fingerprint), chain_len } + RatchetState { key, fingerprint, chain_len } } /// Creates a new ratchet state from the given ratchet key, ratchet fingerprint and chain length. /// @@ -45,28 +38,10 @@ impl RatchetState { 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)), + fingerprint: Zeroizing::new(fingerprint), chain_len, } } - /// The ratchet key for this ratchet state. This is directly mixed into the master secret of a - /// session and so is very sensitive. All operations upon a ratchet key must be implemented - /// in constant time. The user should prefer to do nothing with the ratchet key besides copying - /// it to or from a storage device. - /// - /// If `fingerprint` returns `None` then this is the "empty" ratchet state and the key will be - /// all zeros. - pub fn key(&self) -> &[u8; RATCHET_SIZE] { - &self.key - } - /// Ratchet keys and fingerprints are "chained together", where each set is derived from the - /// previous set. - /// - /// This function outputs the total length of that chain, as in the total number of previous - /// ratchet states that this ratchet state was derived from. - pub fn chain_len(&self) -> u64 { - self.chain_len - } /// Creates a new "empty" ratchet state, where the ratchet fingerprint is the /// empty string, the ratchet key is all zeros, and the chain length is 0. /// @@ -74,7 +49,7 @@ impl RatchetState { pub fn empty() -> Self { RatchetState { key: Zeroizing::new([0u8; RATCHET_SIZE]), - fingerprint: None, + fingerprint: Zeroizing::new([0u8; RATCHET_SIZE]), chain_len: 0, } } @@ -84,7 +59,7 @@ impl RatchetState { pub fn new_from_otp(otp: &[u8]) -> RatchetState { let mut buffer = Vec::new(); buffer.push(1); - buffer.extend(LABEL_OTP_TO_RATCHET); + buffer.extend(*LABEL_OTP_TO_RATCHET); buffer.push(0x00); buffer.extend((1024u16).to_be_bytes()); let r1 = Hmac::hmac(otp, &buffer); @@ -96,15 +71,15 @@ impl RatchetState { 1, ) } - /// Returns true if this is the "empty" ratchet state, where the ratchet fingerprint is the - /// empty string, the ratchet key is all zeros, and the chain length is 0. - pub fn is_empty(&self) -> bool { - self.fingerprint.is_none() - } - /// Checks if the fingerprint of this ratchet state equals the fingerprint contained in argument - /// `rf`. Uses constant time equality. - pub fn fingerprint_eq(&self, rf: &[u8; RATCHET_SIZE]) -> bool { - self.fingerprint.as_ref().map_or(false, |rf0| secure_eq(rf0, rf)) + /// The ratchet key for this ratchet state. This is directly mixed into the master secret of a + /// session and so is very sensitive. All operations upon a ratchet key must be implemented + /// in constant time. The user should prefer to do nothing with the ratchet key besides copying + /// it to or from a storage device. + /// + /// If `fingerprint` returns `None` then this is the "empty" ratchet state and the key will be + /// all zeros. + pub fn key(&self) -> &[u8; RATCHET_SIZE] { + &self.key } /// The ratchet fingerprint for this ratchet state. /// @@ -115,8 +90,43 @@ impl RatchetState { /// but the security of ZSSP can survive having this value leaked. /// Operations on a ratchet fingerprint should be implemented in constant time, /// but it is ok if they are not. - pub fn fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { - self.fingerprint.as_deref() + pub fn fingerprint(&self) -> &[u8; RATCHET_SIZE] { + &self.fingerprint + } + /// Ratchet keys and fingerprints are "chained together", where each set is derived from the + /// previous set. + /// + /// This function outputs the total length of that chain, as in the total number of previous + /// ratchet states that this ratchet state was derived from. + pub fn chain_len(&self) -> u64 { + self.chain_len + } + /// Returns true if this is the "empty" ratchet state, where the ratchet fingerprint is the + /// empty string, the ratchet key is all zeros, and the chain length is 0. + pub fn is_empty(&self) -> bool { + secure_eq(self.fingerprint(), &[0u8; RATCHET_SIZE]) + } + /// Checks if the fingerprint of this ratchet state equals the + /// fingerprint contained in argument `rf`. + /// + /// Uses constant time equality. + pub fn fingerprint_eq(&self, rf: &[u8; RATCHET_SIZE]) -> bool { + secure_eq(self.fingerprint(), rf) + } + /// Returns true if the fingerprint of argument `this` equals the fingerprint represented by + /// argument `rf`. + /// If `this` is `None`, `this` is considered to be "null", as in a ratchet state that does not + /// exist or has been deleted. + /// If `rf` is `None`, `rf` is considered to be "null" as well. + /// If `rf` is `Some(None)`, `rf` is considered to be the empty ratchet fingerprint. + /// + /// Uses constant time equality. + pub fn fingerprint_eq_nullable(this: Option<&Self>, rf: Option<&[u8; RATCHET_SIZE]>) -> bool { + match (this, rf) { + (Some(this), Some(rf)) => this.fingerprint_eq(rf), + (None, None) => true, + _ => false, + } } } impl Default for RatchetState { @@ -134,7 +144,8 @@ impl Default for RatchetState { pub struct RatchetStates { /// The first ratchet state from the pair. pub state1: RatchetState, - /// The second ratchet state from the pair. It can, and usually will be `None`. + /// The second ratchet state from the pair. + /// It can, and usually will be `None`, which means that this ratchet state is "null". pub state2: Option, } impl RatchetStates { @@ -169,7 +180,6 @@ impl Default for RatchetStates { Self::new_initial_states() } } - /// A set of references to ratchet states specifying how a remote peer's persistent storage should /// be updated. This struct is designed to provide any and all potentially needed data for /// maintaining a store of these ratchet states. It should be straightforward to commit these updates @@ -178,69 +188,138 @@ impl Default for RatchetStates { /// There will only be up to two ratchet states saved to storage at a time per peer. /// Every time a third ratchet state is generated, a previous ratchet state will be deleted. /// -/// These are sensitive values should they ought to be securely stored, with restricted read-write -/// permissions if stored on disk. +/// As the name implies, these updates should be applied as one atomic compare-and-swap operation. +/// If the two ratchet states currently in storage equal `cur_state1` and `cur_state2`, +/// then `new_state1` and `new_state2` should overwrite them. +/// If not, then storage must not be modified. /// -/// To prevent desync or resource leakage, these updates should be committed atomically. If that is -/// not possible, then new ratchet states should be written before old ratchet states are deleted -#[derive(Clone, Copy)] -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>, +/// Ratchet keys must never be checked for equality. Instead, if two ratchet states have equal +/// ratchet fingerprints, it should be assumed that they also have equal ratchet keys. +/// This policy exists to reduce the security impact of timing and other side-channel attacks. +/// +/// These are sensitive values and ought to be securely stored, with restricted read-write +/// permissions if stored on disk. +#[derive(Clone)] +pub struct CompareAndSwap<'a> { + /// The ratchet state to store in the first slot. + pub new_state1: &'a RatchetState, + /// The ratchet state to store in the second slot. + /// A value of `None` implies that the second slot should be set to null. + pub new_state2: Option<&'a RatchetState>, + /// This field is `true` if and only if `new_state1 != cur_state1` and `new_state1 != cur_state2`. + /// + /// This implies whether `state1` is a brand new ratchet state, or if it was previously saved. + pub new_state1_was_just_added: bool, + /// The ratchet state that we expect to see in the first slot. + /// If this value is not currently stored in the first slot, the entire update must be aborted. + pub cur_state1: &'a RatchetState, + /// The ratchet state that we expect to see in the second slot. + /// A value of `None` implies we expect the second slot to be set to "null". + /// + /// If this value is not currently stored in the second slot, the entire update must be aborted. + pub cur_state2: Option<&'a RatchetState>, + /// This field is `true` if and only if `cur_state1 != new_state1` and `cur_state1 != new_state2`. + pub cur_state1_was_just_deleted: bool, + /// This field is `true` if and only if `cur_state2 != new_state1` and `cur_state2 != new_state2`. + pub cur_state2_was_just_deleted: bool, } -impl<'a> RatchetUpdate<'a> { - /// Returns the final `RatchetStates` that should be the only thing saved after this update is - /// fully committed. Future calls to `ApplicationLayer::restore_by_identity` should return this struct. - pub fn to_states(&self) -> RatchetStates { - RatchetStates::new(self.state1.clone(), self.state2.cloned()) +impl<'a> CompareAndSwap<'a> { + pub(crate) fn new( + new_state1: &'a RatchetState, + new_state2: Option<&'a RatchetState>, + new_state1_was_just_added: bool, + cur_state1: &'a RatchetState, + cur_state2: Option<&'a RatchetState>, + cur_state1_was_just_deleted: bool, + cur_state2_was_just_deleted: bool, + ) -> Self { + Self { + new_state1, + new_state2, + new_state1_was_just_added, + cur_state1, + cur_state2, + cur_state1_was_just_deleted, + cur_state2_was_just_deleted, + } + } + /// Returns the final `RatchetStates` that must be swapped into storage if this update is + /// fully committed. + /// Future calls to `ApplicationLayer::restore_by_identity` should return this struct. + pub fn to_new_states(&self) -> RatchetStates { + RatchetStates::new(self.new_state1.clone(), self.new_state2.cloned()) + } + /// Returns the `RatchetStates` that is expected to currently be in storage for this peer. + /// This value must be compared with the current value in storage, and if they are equal, + /// the return value of `CompareAndSwap::to_new_states` must overwrite it. + pub fn to_cur_states(&self) -> RatchetStates { + RatchetStates::new(self.cur_state1.clone(), self.cur_state2.cloned()) } /// If this update specifies adding a brand new ratchet fingerprint, this function will return it. /// The returned ratchet fingerprint will always be the ratchet fingerprint of field `state1`. /// - /// If a fingerprint is returned then it is guaranteed that `state1_was_just_added` will be true. + /// If a fingerprint is returned then it is guaranteed that `state1_was_just_added` will be `true`. pub fn added_fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { - if self.state1_was_just_added { - self.state1.fingerprint() - } else { - None - } + self.new_state1_was_just_added.then_some(self.new_state1.fingerprint()) } - /// If this updated specifies to delete an old ratchet fingerprint, this function will return it. - /// The returned ratchet fingerprint will always be the ratchet fingerprint of field - /// `deleted_state1`. + /// If this updated specifies to delete `cur_state1`, and `cur_state1` has a non-zero ratchet + /// fingerprint, this function will return the ratchet fingerprint. + /// + /// If `cur_state1` is not being deleted (i.e. `cur_state1 == new_state1` or + /// `cur_state1 == new_state2`), then this will return `None`. /// /// There may be a second ratchet fingerprint to be deleted, which function /// `deleted_fingerprint2` will return. pub fn deleted_fingerprint1(&self) -> Option<&[u8; RATCHET_SIZE]> { - if let Some(rs) = &self.deleted_state1 { - rs.fingerprint() - } else { - None + if self.cur_state1_was_just_deleted { + if !self.cur_state1.is_empty() { + return Some(self.cur_state1.fingerprint()); + } } + None } - /// If this updated specifies to delete two ratchet fingerprints, this function will return the - /// second one. `deleted_fingerprint1` will return the first one. - /// The returned ratchet fingerprint will always be the ratchet fingerprint of field - /// `deleted_state2`. + /// If this updated specifies to delete `cur_state2`, and `cur_state2` has a non-zero ratchet + /// fingerprint, this function will return the ratchet fingerprint. /// - /// It is exceptionally rare that there will be more than one ratchet fingerprint to be deleted. - /// Care should be taken to make sure that this update will still be correctly committed in the - /// rare event that this returns `Some`. + /// If `cur_state2` is not being deleted (i.e. `cur_state2 == new_state1` or + /// `cur_state2 == new_state2`), then this will return `None`. + /// + /// There may be a second ratchet fingerprint to be deleted, which function + /// `deleted_fingerprint1` will return. pub fn deleted_fingerprint2(&self) -> Option<&[u8; RATCHET_SIZE]> { - if let Some(rs) = &self.deleted_state2 { - rs.fingerprint() - } else { - None + if self.cur_state2_was_just_deleted { + if let Some(rf) = self.cur_state2 { + if !rf.is_empty() { + return Some(rf.fingerprint()); + } + } } + None + } + /// Returns true if the currently stored ratchet states are expected to be the initial ratchet + /// states. This is the default value for a peer's ratchet states in the event they could not + /// be found in storage. + /// + /// If a peer could not be found in storage, and this function returns true, + /// then the peer should be added to storage with the new ratchet states specified by this + /// `CompareAndSwap` struct. + pub fn cur_is_initial_states(&self) -> bool { + self.cur_state1.is_empty() && self.cur_state2.is_none() + } + /// Compares the ratchet fingerprints of `cur_state1` and `cur_state2` with `rf1` and `rf2`. + /// If they are equal this function will return `true`. + /// + /// If this function returns `true`, then the implementation may proceed to swap out the ratchet + /// states these fingerprints come from with `new_state1` and `new_state2`. + pub fn compare_fingerprints(&self, rf1: &[u8; RATCHET_SIZE], rf2: Option<&[u8; RATCHET_SIZE]>) -> bool { + self.cur_state1.fingerprint_eq(rf1) & RatchetState::fingerprint_eq_nullable(self.cur_state2, rf2) + } + /// Compares `cur_state1` and `cur_state2` with `other.state1` and `other.state2`. + /// If they are equal this function will return `true`. + /// + /// If this function returns `true`, then the implementation may proceed to swap `other.state1` + /// and `other.state2` with `new_state1` and `new_state2`. + pub fn compare(&self, other: &RatchetStates) -> bool { + self.cur_state1.eq(&other.state1) & self.cur_state2.eq(&other.state2.as_ref()) } } diff --git a/reference/src/result.rs b/reference/src/result.rs index 4fa54f3..380f15d 100644 --- a/reference/src/result.rs +++ b/reference/src/result.rs @@ -166,7 +166,7 @@ pub enum ReceiveError { WriteError(std::io::Error), } -macro_rules! byzantine_fault { +macro_rules! fault { ($name:expr, $unnatural:ident) => { ReceiveError::ByzantineFault(crate::result::ByzantineFault { #[cfg(feature = "debug")] @@ -178,7 +178,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/reference/src/zeta.rs b/reference/src/zeta.rs index 3c333db..b5ba5ac 100644 --- a/reference/src/zeta.rs +++ b/reference/src/zeta.rs @@ -7,13 +7,13 @@ use std::sync::{Arc, Weak}; use rand_core::RngCore; use zeroize::Zeroizing; -use crate::application::{ApplicationLayer, CryptoLayer, RatchetState, RatchetStates, RatchetUpdate}; +use crate::application::{ApplicationLayer, CompareAndSwap, CryptoLayer, RatchetState, RatchetStates}; use crate::challenge::{gen_null_response, respond_to_challenge_in_place}; use crate::context::{log, ContextInner, SessionMap}; use crate::crypto::*; use crate::fragmentation::DefragBuffer; use crate::proto::*; -use crate::result::{byzantine_fault, FaultType, OpenError, ReceiveError, SendError}; +use crate::result::{fault, FaultType, OpenError, ReceiveError, SendError}; use crate::symmetric_state::SymmetricState; #[cfg(feature = "logging")] use crate::LogEvent::*; @@ -265,12 +265,8 @@ fn create_a1_state( noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), i, &mut x1); // Process message pattern 1 payload. let i = x1.len(); - if let Some(rf) = ratchet_state1.fingerprint() { - x1.extend(rf.as_ref()); - } - if let Some(Some(rf)) = ratchet_state2.map(|rs| rs.fingerprint()) { - x1.extend(rf.as_ref()); - } + x1.extend(ratchet_state1.fingerprint()); + x1.extend(ratchet_state2.map_or(&[0u8; RATCHET_SIZE], |rs| rs.fingerprint())); noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 1), i, &mut x1); let c = u64::from_be_bytes(x1[x1.len() - 8..].try_into().unwrap()); @@ -365,23 +361,23 @@ 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)); + if HANDSHAKE_HELLO_SIZE != x1.len() { + 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 mut noise = SymmetricState::::initialize(PROTOCOL_NAME_NOISE_XK); let mut i = 0; // Noise process prologue. let j = i + KID_SIZE; noise.mix_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(fault!(InvalidPacket, true))?; noise.mix_hash(&ctx.s_secret.public_key_bytes()); i = j; // Process message pattern 1 e token. - let e_remote = noise.read_e(&mut i, &x1).ok_or(byzantine_fault!(FailedAuth, true))?; + let e_remote = noise.read_e(&mut i, &x1).ok_or(fault!(FailedAuth, true))?; // Process message pattern 1 es token. noise.mix_dh(&ctx.s_secret, &e_remote); // Process message pattern 1 e1 token. @@ -389,40 +385,52 @@ pub(crate) fn received_x1_trans>( let k = j + AES_GCM_TAG_SIZE; let tag = x1[j..k].try_into().unwrap(); if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 0), &mut x1[i..j], tag) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let e1_start = i; let e1_end = j; i = k; // Process message pattern 1 payload. - let k = x1.len(); - let j = k - AES_GCM_TAG_SIZE; + let j = i + RATCHET_SIZE + RATCHET_SIZE; + let k = j + AES_GCM_TAG_SIZE; let tag = x1[j..k].try_into().unwrap(); if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_HELLO, 1), &mut x1[i..j], tag) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } + debug_assert_eq!(k, x1.len()); + let rf1 = &x1[i..i + RATCHET_SIZE]; + let rf2 = &x1[i + RATCHET_SIZE..j]; let mut ratchet_state = None; - while i + RATCHET_SIZE <= j { - let rf = (&x1[i..i + RATCHET_SIZE]).try_into().unwrap(); - match app.restore_by_fingerprint(rf) { + if !secure_eq(rf1, &[0u8; RATCHET_SIZE]) { + match app.restore_by_fingerprint(rf1.try_into().unwrap()) { Ok(None) => {} Ok(Some(rs)) => { ratchet_state = Some(rs); - break; } Err(e) => return Err(ReceiveError::StorageError(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)); + if ratchet_state.is_none() { + if !secure_eq(rf2, &[0u8; RATCHET_SIZE]) { + match app.restore_by_fingerprint(rf1.try_into().unwrap()) { + Ok(None) => {} + Ok(Some(rs)) => { + ratchet_state = Some(rs); + } + Err(e) => return Err(ReceiveError::StorageError(e)), + } } - RatchetState::empty() - }; + if ratchet_state.is_none() { + if app.hello_requires_recognized_ratchet() { + return Err(fault!(FailedAuth, true)); + } + } + } + // If we get to this point and haven't found a full ratchet state, + // set it to the empty ratchet state. + let ratchet_state = ratchet_state.unwrap_or_default(); + let (hk_send, hk_recv) = noise.get_ask(LABEL_HEADER_KEY); let mut x2 = Vec::new(); @@ -437,7 +445,7 @@ pub(crate) fn received_x1_trans>( (&x1[e1_start..e1_end]).try_into().unwrap(), ) .map(|(ct, secret)| (ct, Zeroizing::new(secret))) - .ok_or(byzantine_fault!(FailedAuth, true))?; + .ok_or(fault!(FailedAuth, true))?; x2.extend(ekem1); noise.encrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), i, &mut x2); noise.mix_key(ekem1_secret.as_ref()); @@ -493,14 +501,14 @@ pub(crate) fn received_x2_trans>( // <- e, ee, ekem1, psk // -> s, se if HANDSHAKE_RESPONSE_SIZE != x2.len() { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } if Some(kid) != zeta.key_ref(true).recv.kid { - return Err(byzantine_fault!(UnknownLocalKeyId, true)); + return Err(fault!(UnknownLocalKeyId, true)); } let (_, c) = from_nonce(&n); if c >= 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 result = (|| { @@ -508,7 +516,7 @@ pub(crate) fn received_x2_trans>( let mut noise = noise.clone(); let mut i = 0; // Process message pattern 2 e token. - let e_remote = noise.read_e(&mut i, &x2).ok_or(byzantine_fault!(FailedAuth, true))?; + let e_remote = noise.read_e(&mut i, &x2).ok_or(fault!(FailedAuth, true))?; // Process message pattern 2 ee token. noise.mix_dh(e_secret, &e_remote); // Process message pattern 2 ekem1 token. @@ -516,12 +524,12 @@ pub(crate) fn received_x2_trans>( let k = j + AES_GCM_TAG_SIZE; let tag = x2[j..k].try_into().unwrap(); if !noise.decrypt_and_hash_in_place(to_nonce(PACKET_TYPE_HANDSHAKE_RESPONSE, 0), &mut x2[i..j], tag) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let ekem1_secret = e1_secret .decapsulate((&x2[i..j]).try_into().unwrap()) .map(Zeroizing::new) - .ok_or(byzantine_fault!(FailedAuth, true))?; + .ok_or(fault!(FailedAuth, true))?; noise.mix_key(ekem1_secret.as_ref()); drop(ekem1_secret); i = k; @@ -568,7 +576,7 @@ pub(crate) fn received_x2_trans>( } } - let (kid_send, mut noise) = result.ok_or(byzantine_fault!(FailedAuth, true))?; + let (kid_send, mut noise) = result.ok_or(fault!(FailedAuth, true))?; let mut x3 = Vec::new(); // Process message pattern 3 s token. @@ -585,24 +593,26 @@ pub(crate) fn received_x2_trans>( let (rk, rf) = noise.get_ask(LABEL_RATCHET_STATE); let new_ratchet_state = RatchetState::new(rk, rf, chain_len + 1); - let (ratchet_to_preserve, ratchet_to_delete) = if ratchet_i == 1 { - (Some(&zeta.ratchet_state1), zeta.ratchet_state2.as_ref()) + let ratchet_to_preserve = if ratchet_i == 1 { + Some(&zeta.ratchet_state1) } else { - (zeta.ratchet_state2.as_ref(), Some(&zeta.ratchet_state1)) + zeta.ratchet_state2.as_ref() }; let result = app.save_ratchet_state( &zeta.s_remote, &zeta.session_data, - RatchetUpdate { - state1: &new_ratchet_state, - state2: ratchet_to_preserve, - state1_was_just_added: true, - deleted_state1: ratchet_to_delete, - deleted_state2: None, - }, + CompareAndSwap::new( + &new_ratchet_state, + ratchet_to_preserve, + true, + &zeta.ratchet_state1, + zeta.ratchet_state2.as_ref(), + ratchet_i == 2, + ratchet_i == 1, + ), ); - if let Err(e) = result { - return Err(ReceiveError::StorageError(e)); + if !result.map_err(ReceiveError::StorageError)? { + return Err(fault!(OutOfSequence, true)); } let (kek_recv, kek_send) = noise.get_ask(LABEL_KEX_KEY); @@ -626,7 +636,7 @@ pub(crate) fn received_x2_trans>( Ok(packet) } else { - Err(byzantine_fault!(FailedAuth, true)) + Err(fault!(FailedAuth, true)) } })(); match &result { @@ -672,10 +682,10 @@ pub(crate) fn received_x3_trans>( use FaultType::*; // -> s, se if !(HANDSHAKE_COMPLETION_MIN_SIZE..=HANDSHAKE_COMPLETION_MAX_SIZE).contains(&x3.len()) { - 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 mut noise = zeta.noise.clone(); @@ -685,10 +695,9 @@ pub(crate) fn received_x3_trans>( 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)); + return Err(fault!(FailedAuth, true)); } - let s_remote = - C::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()).ok_or(byzantine_fault!(FailedAuth, true))?; + let s_remote = C::PublicKey::from_bytes((&x3[i..j]).try_into().unwrap()).ok_or(fault!(FailedAuth, true))?; i = k; // Process message pattern 3 se token. noise.mix_dh(&zeta.e_secret, &s_remote); @@ -697,7 +706,7 @@ pub(crate) fn received_x3_trans>( 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)); + return Err(fault!(FailedAuth, true)); } let identity_start = i; let identity_end = j; @@ -727,13 +736,13 @@ pub(crate) fn received_x3_trans>( 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() { + if !responder_disallows_downgrade && zeta.ratchet_state.is_empty() { should_warn_missing_ratchet = true; } else { if !responder_silently_rejects { send(&create_reject(), Some(&zeta.hk_send)) } - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } } @@ -743,16 +752,10 @@ pub(crate) fn received_x3_trans>( let result = app.save_ratchet_state( &s_remote, &session_data, - RatchetUpdate { - state1: &new_ratchet_state, - state2: None, - state1_was_just_added: true, - deleted_state1: Some(&state1), - deleted_state2: state2.as_ref(), - }, + CompareAndSwap::new(&new_ratchet_state, None, true, &state1, state2.as_ref(), true, true), ); - if let Err(e) = result { - return Err(ReceiveError::StorageError(e)); + if !result.map_err(ReceiveError::StorageError)? { + return Err(fault!(OutOfSequence, true)); } let mut c1 = Vec::new(); @@ -772,7 +775,7 @@ pub(crate) fn received_x3_trans>( 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)), + Occupied(_) => return Err(fault!(OutOfSequence, false)), Vacant(entry) => entry, }; let session = Arc::new(Session(RefCell::new(Zeta { @@ -807,7 +810,7 @@ pub(crate) fn received_x3_trans>( if !responder_silently_rejects { send(&create_reject(), Some(&zeta.hk_send)) } - Err(byzantine_fault!(FailedAuth, true)) + Err(fault!(FailedAuth, true)) } } /// Corresponds to Transition Algorithm 5 found in Section 4.3. @@ -823,7 +826,7 @@ pub(crate) fn received_c1_trans>( use FaultType::*; if c1.len() != KEY_CONFIRMATION_SIZE { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } let is_other = if Some(kid) == zeta.key_ref(true).recv.kid { true @@ -832,7 +835,7 @@ pub(crate) fn received_c1_trans>( } else { // Some key confirmation may have arrived extremely delayed. // It is unlikely but possible. - return Err(byzantine_fault!(OutOfSequence, false)); + return Err(fault!(OutOfSequence, false)); }; let specified_key = zeta @@ -840,14 +843,14 @@ pub(crate) fn received_c1_trans>( .recv .kek .as_ref() - .ok_or(byzantine_fault!(OutOfSequence, true))?; + .ok_or(fault!(OutOfSequence, true))?; let tag = c1[..].try_into().unwrap(); if !C::Aead::decrypt_in_place(specified_key, &n, None, &mut [], tag) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); if !zeta.update_counter_window(c) { - return Err(byzantine_fault!(ExpiredCounter, true)); + return Err(fault!(ExpiredCounter, true)); } let just_establised = is_other && matches!(&zeta.beta, ZetaAutomata::A3 { .. }); @@ -857,16 +860,19 @@ pub(crate) fn received_c1_trans>( let result = app.save_ratchet_state( &zeta.s_remote, &zeta.session_data, - RatchetUpdate { - state1: &zeta.ratchet_state1, - state2: None, - state1_was_just_added: false, - deleted_state1: zeta.ratchet_state2.as_ref(), - deleted_state2: None, - }, + CompareAndSwap::new( + &zeta.ratchet_state1, + None, + false, + &zeta.ratchet_state1, + zeta.ratchet_state2.as_ref(), + false, + true, + ), ); - if let Err(e) = result { - return Err(ReceiveError::StorageError(e)); + if !result.map_err(ReceiveError::StorageError)? { + zeta.expire(); + return Err(fault!(OutOfSequence, true)); } } @@ -880,7 +886,7 @@ pub(crate) fn received_c1_trans>( } let c2 = Vec::new(); if !send_control::(zeta, PACKET_TYPE_ACK, c2, send) { - return Err(byzantine_fault!(OutOfSequence, true)); + return Err(fault!(OutOfSequence, true)); } Ok(just_establised) @@ -898,24 +904,24 @@ pub(crate) fn received_c2_trans>( use FaultType::*; if c2.len() != ACKNOWLEDGEMENT_SIZE { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(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)); + return Err(fault!(UnknownLocalKeyId, false)); } if !matches!(&zeta.beta, ZetaAutomata::S1) { // Some acknowledgement may have arrived extremely delayed. - return Err(byzantine_fault!(OutOfSequence, false)); + return Err(fault!(OutOfSequence, false)); } let tag = c2[..].try_into().unwrap(); if !C::Aead::decrypt_in_place(zeta.key_ref(false).recv.kek.as_ref().unwrap(), &n, None, &mut [], tag) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); if !zeta.update_counter_window(c) { - return Err(byzantine_fault!(ExpiredCounter, true)); + return Err(fault!(ExpiredCounter, true)); } let r = rng.borrow_mut().next_u64() % C::SETTINGS.rekey_time_max_jitter; @@ -935,19 +941,19 @@ pub(crate) fn received_d_trans( use FaultType::*; if d.len() != SESSION_REJECTED_SIZE { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(fault!(InvalidPacket, true)); } if Some(kid) != zeta.key_ref(true).recv.kid || !matches!(&zeta.beta, ZetaAutomata::A3 { .. }) { - return Err(byzantine_fault!(OutOfSequence, true)); + return Err(fault!(OutOfSequence, true)); } let tag = d[..].try_into().unwrap(); if !C::Aead::decrypt_in_place(zeta.key_ref(true).recv.kek.as_ref().unwrap(), &n, None, &mut [], tag) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); if !zeta.update_counter_window(c) { - return Err(byzantine_fault!(ExpiredCounter, true)); + return Err(fault!(ExpiredCounter, true)); } zeta.expire(); @@ -1103,11 +1109,11 @@ 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)); } if Some(kid) != zeta.key_ref(false).recv.kid { // Some rekey packet may have arrived extremely delayed. - return Err(byzantine_fault!(UnknownLocalKeyId, false)); + return Err(fault!(UnknownLocalKeyId, false)); } let should_rekey_as_bob = match &zeta.beta { ZetaAutomata::S2 { .. } => true, @@ -1116,7 +1122,7 @@ pub(crate) fn received_k1_trans>( }; if !should_rekey_as_bob { // Some rekey packet may have arrived extremely delayed. - return Err(byzantine_fault!(OutOfSequence, false)); + return Err(fault!(OutOfSequence, false)); } let i = k1.len() - AES_GCM_TAG_SIZE; @@ -1128,11 +1134,11 @@ pub(crate) fn received_k1_trans>( &mut k1[..i], &tag, ) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); if !zeta.update_counter_window(c) { - return Err(byzantine_fault!(ExpiredCounter, true)); + return Err(fault!(ExpiredCounter, true)); } k1.truncate(i); @@ -1145,7 +1151,7 @@ pub(crate) fn received_k1_trans>( // Process message pattern 1 psk0 token. noise.mix_key_and_hash(zeta.ratchet_state1.key()); // 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(&mut i, &k1).ok_or(fault!(FailedAuth, true))?; // Process message pattern 1 es token. noise.mix_dh(s_secret, &e_remote); // Process message pattern 1 ss token. @@ -1155,10 +1161,10 @@ pub(crate) fn received_k1_trans>( 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)); + return Err(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(fault!(FailedAuth, true))?; let mut k2 = Vec::new(); // Process message pattern 2 e token. @@ -1178,16 +1184,18 @@ pub(crate) fn received_k1_trans>( 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, - deleted_state1: zeta.ratchet_state2.as_ref(), - deleted_state2: None, - }, + CompareAndSwap::new( + &new_ratchet_state, + Some(&zeta.ratchet_state1), + true, + &zeta.ratchet_state1, + zeta.ratchet_state2.as_ref(), + false, + true, + ), ); - if let Err(e) = result { - return Err(ReceiveError::StorageError(e)); + if !result.map_err(ReceiveError::StorageError)? { + return Err(fault!(OutOfSequence, true)); } let (kek_send, kek_recv) = noise.get_ask(LABEL_KEX_KEY); let (nk_send, nk_recv) = noise.split(); @@ -1226,15 +1234,15 @@ pub(crate) fn received_k2_trans>( use FaultType::*; // <- e, ee, se if k2.len() != REKEY_SIZE { - return Err(byzantine_fault!(InvalidPacket, true)); + return Err(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)); + return Err(fault!(UnknownLocalKeyId, false)); } if !matches!(&zeta.beta, ZetaAutomata::R1 { .. }) { // Some rekey packet may have arrived extremely delayed. - return Err(byzantine_fault!(OutOfSequence, false)); + return Err(fault!(OutOfSequence, false)); } let i = k2.len() - AES_GCM_TAG_SIZE; @@ -1246,11 +1254,11 @@ pub(crate) fn received_k2_trans>( &mut k2[..i], &tag, ) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let (_, c) = from_nonce(&n); if !zeta.update_counter_window(c) { - return Err(byzantine_fault!(ExpiredCounter, true)); + return Err(fault!(ExpiredCounter, true)); } k2.truncate(i); let result = (|| { @@ -1258,7 +1266,7 @@ pub(crate) fn received_k2_trans>( 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))?; + let e_remote = noise.read_e(&mut i, &k2).ok_or(fault!(FailedAuth, true))?; // Process message pattern 2 ee token. noise.mix_dh(e_secret, &e_remote); // Process message pattern 2 se token. @@ -1268,26 +1276,28 @@ pub(crate) fn received_k2_trans>( 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)); + return Err(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(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, - deleted_state1: Some(&zeta.ratchet_state1), - deleted_state2: zeta.ratchet_state2.as_ref(), - }, + CompareAndSwap::new( + &new_ratchet_state, + None, + true, + &zeta.ratchet_state1, + zeta.ratchet_state2.as_ref(), + true, + true, + ), ); - if let Err(e) = result { - return Err(ReceiveError::StorageError(e)); + if !result.map_err(ReceiveError::StorageError)? { + return Err(fault!(OutOfSequence, true)); } let (kek_recv, kek_send) = noise.get_ask(LABEL_KEX_KEY); let (nk_recv, nk_send) = noise.split(); @@ -1363,7 +1373,7 @@ pub(crate) fn received_payload_in_place( use FaultType::*; if payload.len() < AES_GCM_TAG_SIZE { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(fault!(FailedAuth, true)); } let is_other = if Some(kid) == zeta.key_ref(true).recv.kid { true @@ -1372,21 +1382,21 @@ pub(crate) fn received_payload_in_place( } 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)); + return Err(fault!(OutOfSequence, false)); }; let i = payload.len() - AES_GCM_TAG_SIZE; let specified_key = zeta.key_ref(is_other).recv.nk.as_ref(); - let specified_key = specified_key.ok_or(byzantine_fault!(OutOfSequence, true))?; + let specified_key = specified_key.ok_or(fault!(OutOfSequence, true))?; let tag = payload[i..].try_into().unwrap(); if !C::Aead::decrypt_in_place(specified_key, &n, None, &mut payload[..i], &tag) { - return Err(byzantine_fault!(FailedAuth, true)); + return Err(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)); + return Err(fault!(ExpiredCounter, true)); } payload.truncate(i); diff --git a/whitepaper/zssp.pdf b/whitepaper/zssp.pdf index 3a17d5d40b69ad54c481e9867bc14810b641a86e..be71c712bfddb798ca5d028ac676eba35b65336a 100644 GIT binary patch delta 69217 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 delta 66695 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 Zv8be?C^e1C#K_RX+>lFE)z#mP3ji68OltrD diff --git a/whitepaper/zssp.tex b/whitepaper/zssp.tex index 2dc4a9d..ebd55a0 100644 --- a/whitepaper/zssp.tex +++ b/whitepaper/zssp.tex @@ -214,13 +214,13 @@ So Alice and Bob will need to allow potentially the last two derived ratchet key To minimize the number of ratchet keys Alice and Bob have to store in memory, the final step of every key exchange in ZSSP is to delete the previous ratchet key, once both peers are certain the other peer has derived the new one. So usually Alice and Bob will only have and use the most recently derived ratchet key, but if a peer disappears during key exchange, Alice and Bob will have up to the last two ratchet keys, and will be able to coordinate using the one they are certain both peers have access to. -If this is the first time Alice and Bob are communicating, Alice will send an empty string instead of a ratchet fingerprint to signal this fact. Both Alice and Bob understand this to mean they ought to complete the key exchange with a ratchet key of all zeros. When Alice reveals their identity in the third message of Noise XK, Bob will have to check that it is indeed true that Alice has never communicated with Bob before, and they don't already have a ratchet key and fingerprint. Similarly, if Alice does use a valid ratchet fingerprint and key, Bob must check if the pair is actually associated with Alice, and isn't being stolen from a different peer. +If this is the first time Alice and Bob are communicating, Alice will send a string of all zeros instead of a ratchet fingerprint to signal this fact. Both Alice and Bob understand this to mean they ought to complete the key exchange with a ratchet key of all zeros. When Alice reveals their identity in the third message of Noise XK, Bob will have to check that it is indeed true that Alice has never communicated with Bob before, and they don't already have a ratchet key and fingerprint. Similarly, if Alice does use a valid ratchet fingerprint and key, Bob must check if the pair is actually associated with Alice, and isn't being stolen from a different peer. Since the ratchet fingerprint is included in the first message of Noise XK, with weak forward secrecy, there is some concern it could violate identity hiding. However, since the ratchet fingerprint is a constantly rotating ASK, information about the communicating peers can only be leaked by it if the same ratchet fingerprint is used twice. In normal operation, not only will the ratchet fingerprint only be used once, but it will be deleted with the ratchet key when the key exchange is completed, providing replay protection. Furthermore, if the same ratchet fingerprint is used more than once, all this will reveal is that the same pair of peers are initiating a session with each other, and only if Bob's private key is compromised. -Actually this protocol give ZSSP a lot of useful properties. For one thing Bob can require Alice to include a recognized, nonempty ratchet fingerprint in their first message. This is how ZSSP can achieve ``Silence is Golden'' despite the fact that the first Noise XK packet is usually anonymous and replayable. It also allows Bob to authenticate Alice's identity immediately, as opposed to waiting for Alice's next message. This protocol also is integral to how Alice and Bob are able to coordinate the ratchet key management described in \sectionref{sec:key_management} without any additional round trips. +Actually this protocol give ZSSP a lot of useful properties. For one thing Bob can require Alice to include a recognized, non-zero ratchet fingerprint in their first message. This is how ZSSP can achieve ``Silence is Golden'' despite the fact that the first Noise XK packet is usually anonymous and replayable. It also allows Bob to authenticate Alice's identity immediately, as opposed to waiting for Alice's next message. This protocol also is integral to how Alice and Bob are able to coordinate the ratchet key management described in \sectionref{sec:key_management} without any additional round trips. -If Alice has corrupted their storage, Alice can either send the empty string or send whatever corrupted ratchet fingerprint they still have to Bob. If Bob sees the empty string, and Bob is in opportunistic mode, Bob completes the key exchange with the zero ratchet key, making it possible for Alice to connect. If Alice sent a corrupted ratchet fingerprint, or Bob is the one who corrupted their storage, and if Bob is in opportunistic mode, Bob will ignore the unrecognized ratchet fingerprint and instead uses the zero ratchet key as the PSK. When Alice receives Bob's reply, if Alice is in opportunistic mode, Alice will decrypt Bob's reply with both the ratchet key they intended to use and the zero ratchet key, and go with the one that successfully decrypts the reply. This allows Alice and Bob to reset their ratchet keys in such a way that an attacker cannot perform a downgrade attack without at least one peer's static private key. And if Alice and Bob are in persistent mode, a downgrade attack is simply not possible. +If Alice has corrupted their storage, Alice can either send zeros or send whatever corrupted ratchet fingerprint they still have to Bob. If Bob sees zeros instead of a ratchet fingerprint, and Bob is in opportunistic mode, Bob completes the key exchange with the zero ratchet key, making it possible for Alice to connect. If Alice sent a corrupted ratchet fingerprint, or Bob is the one who corrupted their storage, and if Bob is in opportunistic mode, Bob will ignore the unrecognized ratchet fingerprint and instead uses the zero ratchet key as the PSK. When Alice receives Bob's reply, if Alice is in opportunistic mode, Alice will decrypt Bob's reply with both the ratchet key they intended to use and the zero ratchet key, and go with the one that successfully decrypts the reply. This allows Alice and Bob to reset their ratchet keys in such a way that an attacker cannot perform a downgrade attack without at least one peer's static private key. And if Alice and Bob are in persistent mode, a downgrade attack is simply not possible. \section{Zeta Key Exchange} @@ -271,8 +271,8 @@ The next section will be dedicated to describing ZKE in explicit detail. We will \wordbox[tlr]{2}{\texttt{e1} (1568 bytes)} \\ \bitbox[blr]{2}{} & \bitbox{2}{\texttt{e1\_tag} (16 bytes)} \\ \begin{rightwordgroup}{Noise XK\\Payload} - \wordbox{2}{$\texttt{rf}_1$ (optional 32 bytes)} \\ - \wordbox{2}{$\texttt{rf}_2$ (optional 32 bytes)} \\ + \wordbox{2}{$\texttt{rf}_1$ (32 bytes)} \\ + \wordbox{2}{$\texttt{rf}_2$ (32 bytes)} \\ \wordbox{1}{\texttt{rf\_tag} (16 bytes)} \end{rightwordgroup} \\ \end{bytefield} @@ -542,7 +542,9 @@ Given $(x, g^x)$ and $(y, g^y)$, two output keypairs from $\DHGEN()$, a \emph{ke \subsection{Transition Algorithms}\label{sec:trans_alg} -With the necessary background established, we can now define the final component of the Zeta state machine, the state transition algorithms. As is described in \definitionref{def:state_machine}, each state transition within $\zeta$ has a corresponding algorithm that must be computed. All of these algorithms are described in this section. They have access to a collection of global variables, defined below. +With the necessary background established, we can now define the final component of the Zeta state machine, the state transition algorithms. Anyone looking to implement the Zeta state machine must read this section in its entireity. Many paragraphs contain rules not represented within the diagrams or algorithm psuedocode. Each rule qualified with \emph{must} must be implemented. + +As is described in \definitionref{def:state_machine}, each state transition within $\zeta$ has a corresponding algorithm. These algorithms \emph{must} be computed atomically with the state transition. If the algorithm fails or aborts, then the corresponding state transition \emph{must} not occur. All of these algorithms are described in this section. They have access to a collection of global variables, defined below. \begin{itemize} \item Persistent ratchet keys $\texttt{rk}_1, \texttt{rk}_2$, to be used as a handshake PSK @@ -562,16 +564,20 @@ With the necessary background established, we can now define the final component \item Confirmed key index $\texttt{i}$, used so peers know the most recently confirmed Noise key. \end{itemize} -All of these variables are independent \emph{per instance of} $\zeta$. So each instance of $\zeta$ stores a unique and independent set of these variables, and they are never shared between separate instances of $\zeta$. The persistent state variables $\texttt{rk}$ and $\texttt{rf}$ are a partial exception to this rule, as they can be accessed by multiple instances of $\zeta$ over time. However each of these instances must be associated with a single, unique remote peer, and two instances of $\zeta$ must never access the same set of persistent state variables at the same time. +All of these variables, except $\texttt{rk}$ and $\texttt{rf}$, are independent \emph{per instance of} $\zeta$. So each instance of $\zeta$ stores a unique and independent set of these variables, and they are never shared between separate instances of $\zeta$. -What constitutes a ``single, unique remote peer'' is up to the upper protocol to decide. Usually remote peers are identified and differentiated by their static public keys ($g^u$ or $g^v$). However ZKE does not strictly enforce a one-to-one relationship between static public keys and identity specifically so the upper protocol is able to rotate the static public keys of peers. +Variables $\texttt{rk}$ and $\texttt{rf}$ are stored on a persistent storage device, and can be accessed by multiple instances of $\zeta$ over time. Implementations \emph{must} guarantee each instance of persistent variables $\texttt{rk}$ and $\texttt{rf}$ is associated with a single, unique remote peer. When implementing $\zeta$, each instance of $\zeta$ \emph{must} store a local copy of $\texttt{rf}$. Writes to $\texttt{rk}$ and $\texttt{rf}$ \emph{must} be batched together as one atomic operation. Implementations \emph{must} check that the local copy of $\texttt{rf}$ is consistent with $\texttt{rf}$ prior to every write. If they are inconsistent, then the entire write \emph{must} be cancelled, and $\zeta$ \emph{must} immediately abort the transition algorithm which triggered the write. Then, $\zeta$ \emph{must} be deleted (i.e. $\zeta$ \emph{must} perform the timeout transition, $\tau$). This policy guarantees that if multiple, concurrent instances of $\zeta$ exist for the same remote peer, only one of them will be allowed to write to $\texttt{rk}$ and $\texttt{rf}$, while the others will be deleted. + +It \emph{must} be infeasible to recover a non-zero value of $\texttt{rk}$ or $\texttt{rf}$ based on the amount of time read and write operations on $\texttt{rk}$ or $\texttt{rf}$ take. To this end, implementations must not directly operate on ratchet keys (check for equality, create a search index, etc.). Instead, these operations must be performed indirectly upon their ratchet fingerprints. This is to ensure that, in the event a side-channel attack is found, only the ratchet fingerprint can be leaked. + +What constitutes a ``single, unique remote peer'' is up to the upper protocol to decide. Usually remote peers are identified and differentiated by their static public keys ($g^u$ or $g^v$). However ZKE does not strictly enforce a one-to-one relationship between static public keys and identity specifically so the upper protocol is able to rotate the static public keys of peers. There \emph{must} be a well-defined equality relation between peers. \begin{definition}[H][Session Counter] Every instance of $\zeta$ contains a single monotonically increasing counter, that is initialized to 0. We will use the notation $\COUNTER()$ to represent incrementing this counter by 1 and returning the previous counter value. So the very first call to $\COUNTER()$ within $\zeta$ will return 0, the second call will return 1, and so on. - It is assumed that that output of $\COUNTER()$, the counter value, will be used as an AES-GCM nonce. When a remote peer decrypts an AES-GCM ciphertext that uses a counter value nonce, they must first statefully check that this counter has never been received before, and fail authentication if it has been replayed. + It is assumed that that output of $\COUNTER()$, the counter value, will be used as an AES-GCM nonce. When a remote peer decrypts an AES-GCM ciphertext that uses a counter value nonce, they \emph{must} first statefully check that this counter has never been received before, and fail authentication if it has been replayed. \end{definition} ZKE uses a 64-bit (8 byte) counter, but AES-GCM uses a 96-bit (12 byte) nonce/IV. The Noise protocol does specify that, in order to use a counter value as an AES nonce, it should be encoded as a big-endian integer with 4 bytes of zero padding to the left of it. However, for good reason, we have decided to utilize the rightmost byte of that 4 byte padding to store the packet type number, as depicted in \figureref{fig:nonce}. This accomplishes two goals. First it explicitly authenticates the packet type of a packet, which makes it infeasible for an attacker to change the packet type field of a header without triggering an authentication failure. We could have used the additional authentication data parameter of AES-GCM to accomplish this, but the Noise protocol already makes extensive use of this parameter. Second, this nonce construction reduces the risk of catastrophic nonce reuse due to implementation error, specifically during key exchanges. While a correctly implemented counter would be sufficient for security, this adds an extra layer of defence. Given a packet type number $p$, and a counter value $c$, we will use the notation $p||c$ to represent this nonce construction. @@ -587,16 +593,15 @@ ZKE uses a 64-bit (8 byte) counter, but AES-GCM uses a 96-bit (12 byte) nonce/IV \end{figure} \begin{definition}[H][Key Id Generation] - \algn{KID-Gen} is a stateful algorithm that outputs a locally unique, uniform random 32-bit string. The output of this algorithm will be used as the \emph{key id} for a session key, allowing the local peer to multiplex many keys for many remote peers simultaneously. The output of $\algn{KID-Gen}()$ must be locally unique from all other key ids currently in use. $\algn{KID-Gen}()$ may only repeat an output if that output is currently not being used as a key id. + \algn{KID-Gen} is a stateful algorithm that outputs a locally unique, uniform random 32-bit string. The output of this algorithm will be used as the \emph{key id} for a session key, allowing the local peer to multiplex many keys for many remote peers simultaneously. The output of $\algn{KID-Gen}()$ \emph{must} be locally unique from all other key ids currently in use. $\algn{KID-Gen}()$ may only repeat an output if that output is currently not being used as a key id. \end{definition} - \begin{definition}[H][Persistent State] The variables $\texttt{rk}$ and $\texttt{rf}$ are considered the \emph{persistent state} of ZKE. The values stored in these variables are not lost when a session ends, and can be \emph{restored} at any time to initiate a new session. - $\texttt{rk}$ is a tuple of up to two 256-bit \emph{ratchet keys}. Similarly, $\texttt{rf}$ is a tuple of up to two 256-bit \emph{ratchet fingerprints}. $\texttt{rk}$ is initialized to $(0^{256}, \bot)$ and $\texttt{rf}$ is initialized to $(\varepsilon, \bot)$. + $\texttt{rk}$ is a tuple of up to two 256-bit \emph{ratchet keys}. Similarly, $\texttt{rf}$ is a tuple of up to two 256-bit \emph{ratchet fingerprints}. Both $\texttt{rk}$ and $\texttt{rf}$ are initialized to $(0^{256}, \bot)$. - If two peers are in persistent mode, and wish to connect for the first time, they may use a preshared one time password, $p$, to do so. $p$ must be a random string with at least 256-bits of entropy. If a one time password is being used then $r\gets \KDF(p, \texttt{"ZSSP\_OTP\_TO\_RATCHET"}, \varepsilon, 2)$ must be computed. Both peers then initialize $\texttt{rk}$ to $(r_1, \bot)$ and $\texttt{rf}$ to $(r_2, \bot)$. $p$ must only be used once; A fresh instance of $p$ must be generated for every new pair of peers who wish to connect. + If two peers are in persistent mode, and wish to connect for the first time, they may use a preshared one time password, $p$, to do so. $p$ \emph{must} be a random string with at least 256-bits of entropy. If a one time password is being used then $r\gets \KDF(p, \texttt{"ZSSP\_OTP\_TO\_RATCHET"}, \varepsilon, 2)$ \emph{must} be computed. Both peers then initialize $\texttt{rk}$ to $(r_1, \bot)$ and $\texttt{rf}$ to $(r_2, \bot)$. $p$ \emph{must} only be used once; A fresh instance of $p$ \emph{must} be generated for every new pair of peers who wish to connect. \end{definition} $\bot$ represents the ``null'' value. When a variable is set to $\bot$, it means whatever was previous stored within the variable is being permanently deleted from memory. All variables are initialized to $\bot$ unless otherwise stated. $\varepsilon$ is the empty string. @@ -666,7 +671,11 @@ Prior to the execution of any of the following algorithms, $\zeta$ will check if \State $X_1\texttt{.key\_id} \gets \texttt{kid}^1_1$ \State $X_1\texttt{.e} \gets g^x$ \State $X_1\texttt{.e1} \gets \AEAD(k_4, 0||0, h_3, e_{pub})$ - \State $X_1\texttt{.rf} \gets \AEAD(k_4, 0||1, h_5, \texttt{rf})$ + \If{$\texttt{rf}_2=\bot$} + \State $X_1\texttt{.rf} \gets \AEAD(k_4, 0||1, h_5,\,\texttt{rf}_1||0^{256})$ + \Else + \State $X_1\texttt{.rf} \gets \AEAD(k_4, 0||1, h_5,\,\texttt{rf}_1||\texttt{rf}_2)$ + \EndIf \State $\texttt{hk} \gets \KDF(h_6, \texttt{"ASKH"}, c_4, 2)$ \State $c \gets X_1\texttt{.rf\_tag}\ \text{(last 8 bytes)}$ \State $\textbf{Send } (0,\ 0||c,\ X_1)$ @@ -686,15 +695,15 @@ Including the ratchet fingerprint in the Hello packet does not compromise identi \EndIf \State $\textbf{Decrypt } X_1$ according to Noise XK \State $\texttt{rf}' \gets X_1\texttt{.rf}_1$ - \State $\textbf{Restore } \texttt{rk}' \text{ based on } \texttt{rf}'$ + \State $\textbf{Restore } \texttt{rk}' \text{ based on } \texttt{rf}'$ (or $\texttt{rk}'\gets \bot$ if $\texttt{rf}' = 0^{256}$) \If{$\texttt{rk}' = \bot$} \State $\texttt{rf}' \gets X_1\texttt{.rf}_2$ - \State $\textbf{Restore } \texttt{rk}' \text{ based on } \texttt{rf}'$ + \State $\textbf{Restore } \texttt{rk}' \text{ based on } \texttt{rf}'$ (or $\texttt{rk}'\gets \bot$ if $\texttt{rf}' = 0^{256}$) \If{$\texttt{rk}' = \bot$} \If{$\pi_1=1$} \State \textbf{Fail authentication} \Else - \State $\texttt{rf}' \gets \varepsilon$ + \State $\texttt{rf}' \gets 0^{256}$ \State $\texttt{rk}' \gets 0^{256}$ \EndIf \EndIf @@ -719,12 +728,12 @@ Including the ratchet fingerprint in the Hello packet does not compromise identi \If{$c \geq 2^{24} \orb c \neq X_2\texttt{.key\_id\_tag}$ (last 3 bytes)} \State \textbf{Fail authentication} \EndIf - \State $\textbf{Decrypt } X_2$ according to Noise XK using PSK $\texttt{rk}_1$ \State $i \gets 1$ - \If{$X_2$ failed authentication} - \State $\textbf{Decrypt } X_2$ according to Noise XK using PSK $\texttt{rk}_2$ + \State $\textbf{Decrypt } X_2$ according to Noise XK using PSK $\texttt{rk}_1$ + \If{$X_2$ failed to decrypt} \State $i \gets 2$ - \If{$X_2$ failed authentication} + \State $\textbf{Decrypt } X_2$ according to Noise XK using PSK $\texttt{rk}_2$ + \If{$X_2$ failed to decrypt} \If{$\pi_2=1$} \State \textbf{Fail authentication} \Else @@ -736,10 +745,10 @@ Including the ratchet fingerprint in the Hello packet does not compromise identi \State $\texttt{kid}^1_2 \gets X_2\texttt{.key\_id}$ \State $X_3\texttt{.s} \gets \AEAD(k_{10}, 2||1, h_{11}, g^u)$ \State $X_3\texttt{.identity} \gets \AEAD(k_{13}, 2||0, h_{12}, \texttt{identity})$ - \State $\texttt{kek}^1 \gets \KDF(h_{14}, \texttt{"ASKK"}, c_{13}, 2)$ - \State $\texttt{nk}^1 \gets \KDF(\varepsilon, L, c_{13}, 2)$ \State $(\texttt{rk}_2, \texttt{rf}_2) \gets (\texttt{rk}_i, \texttt{rf}_i)$ \State $(\texttt{rk}_1, \texttt{rf}_1) \gets \KDF(h_{14}, \texttt{"ASKR"}, c_{13}, 2)$ + \State $\texttt{kek}^1 \gets \KDF(h_{14}, \texttt{"ASKK"}, c_{13}, 2)$ + \State $\texttt{nk}^1 \gets \KDF(\varepsilon, L, c_{13}, 2)$ \State $\texttt{r} \gets 1$ \State $\texttt{i} \gets 0$ \State $\textbf{Send } (\texttt{kid}^1_2,\ 2||0,\ X_3)$ @@ -748,7 +757,7 @@ Including the ratchet fingerprint in the Hello packet does not compromise identi Packet types $X_2$ and $K_1$ represent the second to last message of Noise XK and Noise KK. The remote peer will not have derived the Noise key yet, so if this peer attempted to encrypt with this key immediately, there is a risk its packets will arrive out of order and be undecryptable. Receiving a valid $C_1$ packet from the remote peer confirms that they have successfully derived the correct Noise key, which signals it can now be reliably used for encryption. -Similarly, the new ratchet key and fingerprint will not have been derived by the remote peer yet. So this peer will have to save these keys to persistent storage, but not yet delete the previous ratchet key and fingerprint until the remote peer sends a key confirmation. This means that for a brief period of time Alice has two sets of ratchet keys and fingerprints saved, instead of just one. We want to make sure Alice and Bob never have more than two of these keys saved at a time. So during the Noise XK handshake if Alice has two sets of keys and fingerprints, they must send both fingerprints to Bob, and delete the key and fingerprint that Bob chooses not to use as the PSK. +Similarly, the new ratchet key and fingerprint will not have been derived by the remote peer yet. So this peer will have to save these keys to persistent storage, but not yet delete the previous ratchet key and fingerprint until the remote peer sends a key confirmation. This means that for a brief period of time Alice has two sets of ratchet keys and fingerprints saved, instead of just one. We want to make sure Alice and Bob never have more than two of these keys saved at a time. So during the Noise XK handshake if Alice has two sets of keys and fingerprints, they \emph{must} send both fingerprints to Bob, and delete the key and fingerprint that Bob chooses not to use as the PSK. \begin{algorithm}[H] \caption{Transition $\delta(B_2, X_3)=S_1$ -- Bob receives the final packet of Noise XK. Input $\algn{Accept}$ is a function provided by the upper protocol that takes as input Alice's identity and outputs security flags $(\pi_3, \pi_4)$, $\pi_3$ can be $\bot$. If $\pi_3=\bot$, Bob will reject Alice's session. If $\pi_3=1$, and Bob is aware of a ratchet key Alice should have, but Alice is not connecting with it, Bob will reject Alice's session (similar to when $\pi_2=1$). If Bob rejects Alice's session and $\pi_4 = 0$, Bob will send a packet to Alice explicitly rejecting their session.}\label{alg:recv_x3} @@ -760,8 +769,8 @@ Similarly, the new ratchet key and fingerprint will not have been derived by the \State $(\pi_3, \pi_4) \gets \algn{Accept}(X_3\texttt{.s}, X_3\texttt{.identity})$ \If{$\pi_3 \neq \bot$} \State $\textbf{Restore } (\texttt{rk}, \texttt{rf}) \text{ based on } (X_3\texttt{.s}, X_3\texttt{.identity})$ - \If{$(\texttt{rf}', \texttt{rk}') \neq (\texttt{rf}_1, \texttt{rk}_1) \andb (\texttt{rf}', \texttt{rk}') \neq (\texttt{rf}_2, \texttt{rk}_2)$} - \If{$\pi_3 = 0 \andb \texttt{rf}' = \varepsilon$} + \If{$\texttt{rf}' \neq \texttt{rf}_1 \andb \texttt{rf}' \neq \texttt{rf}_2$} + \If{$\pi_3 = 0 \andb \texttt{rf}' = 0^{256}$} \State \textbf{Warn} the upper protocol that Alice does not have the ratchet key \Else \If{$\pi_4 = 0$} @@ -771,9 +780,9 @@ Similarly, the new ratchet key and fingerprint will not have been derived by the \State \textbf{Fail authentication} \EndIf \EndIf - \State $\texttt{nk}^1 \gets \KDF(\varepsilon, L, c_{13}, 2)$ \State $(\texttt{rk}_2, \texttt{rf}_2) \gets \bot$ \State $(\texttt{rk}_1, \texttt{rf}_1) \gets \KDF(h_{14}, \texttt{"ASKR"}, c_{13}, 2)$ + \State $\texttt{nk}^1 \gets \KDF(\varepsilon, L, c_{13}, 2)$ \State $\texttt{r} \gets 2$ \State $\texttt{i} \gets 1$ \State $C_1\texttt{.kek\_tag} \gets \AEAD(\texttt{kek}^1_1, 3||c, \varepsilon, \varepsilon)$ @@ -788,12 +797,14 @@ Similarly, the new ratchet key and fingerprint will not have been derived by the \end{algorithmic} \end{algorithm} -When packet $X_3$ is received, Bob might refuse to start a session with Alice based on their static key and identity. The preferred way for Bob to do this is to simply do nothing, and ignore Alice's packets. This would follow the ``Silence is Golden" principle of security. However there are some applications where, usually for UX reasons, Alice needs to be able tell apart having a bad connection with Bob from Bob rejecting Alice's identity. If Bob always goes silent Alice has no reliable way to tell these two situations apart. Since both Bob and Alice have the key exchange key at this point in the handshake, it was a very natural and secure extension of the protocol to allow Bob to notify Alice that the session has been refused. +If, when function $\algn{Accept}$ is called, any other instance of $\zeta$ exists with the same remote peer, then $\algn{Accept}$ \emph{must} be implemented to do one of the following: Either the other instance of $\zeta$ is deleted, or $\algn{Accept}$ returns $\pi_3=\bot$ to reject the new session. Instances of $\zeta$ in state $B_2$ \emph{must} be ignored in regards to the prior rule, since the identity of the remote peer will not have been confirmed and might not be known. Keep in mind that this rule implies that if Alice attempts to open multiple sessions with Bob at once, only one of them will be accepted. + +When packet $X_3$ is received, Bob might refuse to start a session with Alice based on their static key and identity. The preferred way for Bob to do this is to simply do nothing, and ignore Alice's packets. This would follow the ``Silence is Golden" principle of security. However there are some applications where, usually for UX reasons, Alice needs to be able tell apart having a bad connection with Bob from Bob rejecting Alice's identity. If Bob always goes silent, Alice has no reliable way to tell these two situations apart. This is the reason why packet $D$ is created and sent at the bottom of \algorithmref{alg:recv_x3}. Receipt of packet $D$ implies Bob has explicitly rejected a session with Alice. Packet types $X_3$ and $K_2$ represent the final messages of Noise XK and Noise KK, respectively. The peer who receives these packets can be sure that the remote peer has derived an identical Noise key already, and so the new key can be reliably used immediately. \begin{algorithm}[H] - \caption{Transition $\delta(A_3, C_1)=S_2$ and $\delta(R_2, C_1)=S_2$ -- A key confirmation for either the Noise XK initial handshake or Noise KK rekeying has been received. This peer must send an Acknowledgement, \figureref{packet:key_conf}, to let the remote peer know that it has been received.}\label{alg:key_conf} + \caption{Transition $\delta(A_3, C_1)=S_2$ and $\delta(R_2, C_1)=S_2$ -- A key confirmation for either the Noise XK initial handshake or Noise KK rekeying has been received. This peer \emph{must} send an Acknowledgement, \figureref{packet:key_conf}, to let the remote peer know that it has been received.}\label{alg:key_conf} \begin{algorithmic} \Require $(\texttt{kid}^{\texttt{i} + 1}_{\texttt{r}},\ 3||c,\ C_1)$ \State $\textbf{Decrypt } C_1$ using $\texttt{kek}^{\texttt{i} + 1}_{\texttt{r}}$ @@ -820,7 +831,6 @@ Packet types $C_2$ and $D$ have trivial transition algorithms. They merely decry \end{algorithmic} \end{algorithm} - \begin{algorithm}[H] \caption{Transition $\delta(S_2, K_1)=R_2$ and $\delta(R_1, K_1)=R_2$ -- Bob has received Alice's request to rekey and sends a Rekey Completion packet, \figureref{packet:rekey}, in reply. Bob has now finished Noise KK and has obtained a new Noise key. A new pair of key ids are generated so Bob can tell apart packets encrypted with the new key from those encrypted with the previous key.} \begin{algorithmic} @@ -833,28 +843,27 @@ Packet types $C_2$ and $D$ have trivial transition algorithms. They merely decry \State $K_2\texttt{.key\_id} \gets \AEAD(k_{10}', 6||0, h_{8}, \texttt{kid}^{\texttt{i} + 1}_\texttt{r})$ \State $c \gets \COUNTER()$ \State $K_2 \gets \AEAD(\texttt{kek}^\texttt{i}_{3 - \texttt{r}}, 6||c, \varepsilon, K_2)$ + \State $(\texttt{rk}_2, \texttt{rf}_2) \gets (\texttt{rk}_1, \texttt{rf}_1)$ + \State $(\texttt{rk}_1, \texttt{rf}_1) \gets \KDF(h_{11}', \texttt{"ASKR"}, c_{10}', 2)$ \State $\texttt{kek}^{\texttt{i} - 1} \gets \bot$ \State $(\texttt{kek}^{\texttt{i} + 1}_{3 - \texttt{r}}, \texttt{kek}^{\texttt{i} + 1}_\texttt{r}) \gets \KDF(h_{11}', \texttt{"ASKK"}, c_{10}', 2)$ \State $\texttt{nk}^{\texttt{i} - 1} \gets \bot$ \State $(\texttt{nk}^{\texttt{i} + 1}_{3 - \texttt{r}}, \texttt{nk}^{\texttt{i} + 1}_\texttt{r}) \gets \KDF(\varepsilon, L, c_{10}', 2)$ - \State $(\texttt{rk}_2, \texttt{rf}_2) \gets (\texttt{rk}_1, \texttt{rf}_1)$ - \State $(\texttt{rk}_1, \texttt{rf}_1) \gets \KDF(h_{11}', \texttt{"ASKR"}, c_{10}', 2)$ \State $\textbf{Send } (\texttt{kid}^\texttt{i}_{3 - \texttt{r}},\ 6||c,\ K_2)$ \end{algorithmic} \end{algorithm} - \begin{algorithm}[H] \caption{Transition $\delta(R_1, K_2)=S_1$ -- Alice has received Bob's reply and so can also finish Noise KK. They send a Key Confirmation, \figureref{packet:key_conf}, to signal the completion of the handshake. The second to last Noise key is deleted for forward secrecy.} \begin{algorithmic} \Require $(\texttt{kid}^\texttt{i}_\texttt{r},\ 6||c,\ K_2)$ \State $\textbf{Decrypt } K_2$ using $\texttt{kek}^\texttt{i}_\texttt{r}$, and Noise KK \State $\texttt{kid}^{\texttt{i} + 1}_{3 - \texttt{r}} \gets K_2\texttt{.key\_id}$ + \State $(\texttt{rk}_1, \texttt{rf}_1) \gets \KDF(h_{11}', \texttt{"ASKR"}, c_{10}', 2)$ \State $\texttt{kek}^{\texttt{i} - 1} \gets \bot$ \State $(\texttt{kek}^{\texttt{i} + 1}_\texttt{r}, \texttt{kek}^{\texttt{i} + 1}_{3 - \texttt{r}}) \gets \KDF(h_{11}', \texttt{"ASKK"}, c_{10}', 2)$ \State $\texttt{nk}^{\texttt{i} - 1} \gets \bot$ \State $(\texttt{nk}^{\texttt{i} + 1}_\texttt{r}, \texttt{nk}^{\texttt{i} + 1}_{3 - \texttt{r}}) \gets \KDF(\varepsilon, L, c_{10}', 2)$ - \State $(\texttt{rk}_1, \texttt{rf}_1) \gets \KDF(h_{11}', \texttt{"ASKR"}, c_{10}', 2)$ \State $\texttt{i} \gets \texttt{i} + 1$ \State $c \gets \COUNTER()$ \State $C_1\texttt{.kek\_tag} \gets \AEAD(\texttt{kek}^\texttt{i}_{3 - \texttt{r}}, 3||c, \varepsilon, \varepsilon)$ From 33a81e4708662dcc51793523085862dff6e59e71 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 17 Jan 2024 17:44:14 -0500 Subject: [PATCH 07/16] improved AES implementation --- performance/src/crypto/aes.rs | 9 +-- performance/src/crypto_impl/openssl.rs | 107 +++++++++++++------------ performance/src/zeta.rs | 32 ++++---- 3 files changed, 79 insertions(+), 69 deletions(-) diff --git a/performance/src/crypto/aes.rs b/performance/src/crypto/aes.rs index 6b0dec4..9460744 100644 --- a/performance/src/crypto/aes.rs +++ b/performance/src/crypto/aes.rs @@ -49,15 +49,10 @@ pub trait Aes256Dec: Sized + Send + Sync { pub trait AesGcmEncContext { fn encrypt(&mut self, input: &[u8], output: &mut [u8]); - - fn finish(self) -> [u8; AES_GCM_TAG_SIZE]; } pub trait AesGcmDecContext { fn decrypt_in_place(&mut self, data: &mut [u8]); - - #[must_use] - fn finish(self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; } /// A trait for implementing AES-GCM-256 in a way that allows for extremely high throughput. @@ -86,6 +81,10 @@ pub trait HighThroughputAesGcmPool: Send + Sync { /// `nonce` must be set as the AEAD nonce. /// There is no additional associated data to be used. fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> Self::DecContext<'a>; + + fn finish_enc<'a>(&'a self, enc: Self::EncContext<'a>) -> [u8; AES_GCM_TAG_SIZE]; + #[must_use] + fn finish_dec<'a>(&'a self, dec: Self::DecContext<'a>, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; } /// A trait for implementing AES-GCM-256 to handle the more varied, but much lower throughput diff --git a/performance/src/crypto_impl/openssl.rs b/performance/src/crypto_impl/openssl.rs index 852cb1e..84f765c 100644 --- a/performance/src/crypto_impl/openssl.rs +++ b/performance/src/crypto_impl/openssl.rs @@ -1,9 +1,11 @@ use std::{ ptr::{self, NonNull}, - sync::{Mutex, MutexGuard}, + sync::Mutex, }; +use arrayvec::ArrayVec; use openssl_sys::*; +use zeroize::Zeroizing; use crate::crypto::*; @@ -149,84 +151,89 @@ impl Aes256Dec for OpenSSLAes256Dec { } } -pub struct OpenSSLAesGcmEnc<'a>(MutexGuard<'a, OpenSSLCtx>); -impl<'a> AesGcmEncContext for OpenSSLAesGcmEnc<'a> { +pub struct OpenSSLAesGcmEnc(OpenSSLCtx); +impl AesGcmEncContext for OpenSSLAesGcmEnc { fn encrypt(&mut self, input: &[u8], output: &mut [u8]) { unsafe { assert!(self.0.update::(input, output.as_mut_ptr())) }; } - - fn finish(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 OpenSSLAesGcmDec<'a>(MutexGuard<'a, OpenSSLCtx>); -impl<'a> AesGcmDecContext for OpenSSLAesGcmDec<'a> { +pub struct OpenSSLAesGcmDec(OpenSSLCtx); +impl AesGcmDecContext for OpenSSLAesGcmDec { fn decrypt_in_place(&mut self, data: &mut [u8]) { let p = data.as_mut_ptr(); unsafe { assert!(self.0.update::(data, p)) }; } - - fn finish(self, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool { - unsafe { self.0.set_tag(tag) && self.0.finalize::() } - } } /// A pool of OpenSSL AES-GCM ciphers. pub struct OpenSSLAesGcmPool { - enc: [Mutex; 8], - dec: [Mutex; 8], + enc: Mutex>, + dec: Mutex>, + enc_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, + dec_key: Zeroizing<[u8; AES_256_KEY_SIZE]>, } unsafe impl Send for OpenSSLAesGcmPool {} unsafe impl Sync for OpenSSLAesGcmPool {} impl HighThroughputAesGcmPool for OpenSSLAesGcmPool { - type EncContext<'a> = OpenSSLAesGcmEnc<'a>; + type EncContext<'a> = OpenSSLAesGcmEnc; - type DecContext<'a> = OpenSSLAesGcmDec<'a>; + type DecContext<'a> = OpenSSLAesGcmDec; fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self { + Self { + enc: Default::default(), + dec: Default::default(), + enc_key: Zeroizing::new(*encrypt_key), + dec_key: Zeroizing::new(*decrypt_key), + } + } + + fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmEnc { + let ctx = self.enc.lock().unwrap().pop(); unsafe { - OpenSSLAesGcmPool { - enc: std::array::from_fn(|_| { - 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 = 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); - Mutex::new(ctx) - }), + if let Some(ctx) = ctx { + assert!(ctx.cipher_init::(ptr::null(), ptr::null(), nonce.as_ptr())); + OpenSSLAesGcmEnc(ctx) + } else { + let ctx = OpenSSLCtx::new().unwrap(); + 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); + OpenSSLAesGcmEnc(ctx) + } + } + } + fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmDec { + let ctx = self.dec.lock().unwrap().pop(); + unsafe { + if let Some(ctx) = ctx { + assert!(ctx.cipher_init::(ptr::null(), ptr::null(), nonce.as_ptr())); + OpenSSLAesGcmDec(ctx) + } else { + let ctx = OpenSSLCtx::new().unwrap(); + 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); + OpenSSLAesGcmDec(ctx) } } } - fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmEnc<'a> { - let i = u64::from_be_bytes(nonce[4..].try_into().unwrap()); - let g = self.enc[(i as usize) % self.enc.len()].lock().unwrap(); + fn finish_enc<'a>(&'a self, ctx: OpenSSLAesGcmEnc) -> [u8; AES_GCM_TAG_SIZE] { + let mut output = [0u8; AES_GCM_TAG_SIZE]; unsafe { - assert!(g.cipher_init::(ptr::null(), ptr::null(), nonce.as_ptr())); + assert!(ctx.0.finalize::()); + assert!(ctx.0.get_tag(&mut output)); } - OpenSSLAesGcmEnc(g) + let _ = self.enc.lock().unwrap().try_push(ctx.0); + output } - - fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmDec<'a> { - 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())); - } - OpenSSLAesGcmDec(g) + fn finish_dec<'a>(&'a self, ctx: OpenSSLAesGcmDec, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool { + let output = unsafe { ctx.0.set_tag(tag) && ctx.0.finalize::() }; + let _ = self.dec.lock().unwrap().try_push(ctx.0); + output } } diff --git a/performance/src/zeta.rs b/performance/src/zeta.rs index 7536419..3388fd8 100644 --- a/performance/src/zeta.rs +++ b/performance/src/zeta.rs @@ -1653,15 +1653,6 @@ 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_ne_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); let tagged_payload_len = payload.len() + AES_GCM_TAG_SIZE; @@ -1672,6 +1663,16 @@ pub(crate) fn send_payload( return Err(DataTooLarge); } + debug_assert!(matches!( + &state.beta, + ZetaAutomata::S1 | ZetaAutomata::S2 | ZetaAutomata::R1 { .. } | ZetaAutomata::R2 { .. } + )); + + let key = state.key_ref(false); + let kid_send = key.send.kid.ok_or(SessionNotEstablished)?.get().to_ne_bytes(); + let cipher_pool = key.nk.as_ref().ok_or(SessionNotEstablished)?; + let mut cipher = cipher_pool.start_enc(&nonce); + let mut header = [0u8; HEADER_SIZE]; header[..KID_SIZE].copy_from_slice(&kid_send); header[FRAGMENT_COUNT_IDX] = fragment_count as u8; @@ -1691,6 +1692,9 @@ pub(crate) fn send_payload( state.hk_send.encrypt_in_place(header_auth.try_into().unwrap()); if !send.send_frag(&mut mtu_sized_buffer[..HEADER_SIZE + fragment_len]) { + // We need to give the cipher back to the pool instead of dropping it, + // so it can do memory cleanup. + cipher_pool.finish_enc(cipher); return Ok(false); } i = j; @@ -1704,7 +1708,8 @@ pub(crate) fn send_payload( mtu_sized_buffer[FRAGMENT_NO_IDX] = fragment_no as u8; 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()); + mtu_sized_buffer[HEADER_SIZE + payload_rem..HEADER_SIZE + fragment_len] + .copy_from_slice(&cipher_pool.finish_enc(cipher)); let header_auth = &mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]; state.hk_send.encrypt_in_place(header_auth.try_into().unwrap()); @@ -1751,9 +1756,8 @@ pub(crate) fn receive_payload_in_place( return Err(fault!(UnknownLocalKeyId, true, session)); }; - let mut cipher = specified_key - .ok_or_else(|| fault!(OutOfSequence, true, session))? - .start_dec(nonce); + let cipher_pool = specified_key.ok_or_else(|| fault!(OutOfSequence, true, session))?; + let mut cipher = cipher_pool.start_dec(nonce); let (_, c) = from_nonce(nonce); // NOTE: This only works because we check the size of every received fragment in the receive @@ -1768,7 +1772,7 @@ pub(crate) fn receive_payload_in_place( 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_pool.finish_dec(cipher, (&fragment[tag_idx..]).try_into().unwrap()) { return Err(fault!(FailedAuth, true, session)); } From 2ae43b5ac5bc47ba6e828cac2e78c89b0803fb23 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 17 Jan 2024 17:47:41 -0500 Subject: [PATCH 08/16] cargo clippy --- performance/src/crypto_impl/openssl.rs | 8 ++++---- performance/src/ratchet_state.rs | 6 ++---- performance/src/zeta.rs | 6 ++---- reference/src/ratchet_state.rs | 6 ++---- reference/src/zeta.rs | 8 +++----- 5 files changed, 13 insertions(+), 21 deletions(-) diff --git a/performance/src/crypto_impl/openssl.rs b/performance/src/crypto_impl/openssl.rs index 84f765c..97da58a 100644 --- a/performance/src/crypto_impl/openssl.rs +++ b/performance/src/crypto_impl/openssl.rs @@ -190,7 +190,7 @@ impl HighThroughputAesGcmPool for OpenSSLAesGcmPool { } } - fn start_enc<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmEnc { + fn start_enc(&self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmEnc { let ctx = self.enc.lock().unwrap().pop(); unsafe { if let Some(ctx) = ctx { @@ -205,7 +205,7 @@ impl HighThroughputAesGcmPool for OpenSSLAesGcmPool { } } } - fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmDec { + fn start_dec(&self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmDec { let ctx = self.dec.lock().unwrap().pop(); unsafe { if let Some(ctx) = ctx { @@ -221,7 +221,7 @@ impl HighThroughputAesGcmPool for OpenSSLAesGcmPool { } } - fn finish_enc<'a>(&'a self, ctx: OpenSSLAesGcmEnc) -> [u8; AES_GCM_TAG_SIZE] { + fn finish_enc(&self, ctx: OpenSSLAesGcmEnc) -> [u8; AES_GCM_TAG_SIZE] { let mut output = [0u8; AES_GCM_TAG_SIZE]; unsafe { assert!(ctx.0.finalize::()); @@ -230,7 +230,7 @@ impl HighThroughputAesGcmPool for OpenSSLAesGcmPool { let _ = self.enc.lock().unwrap().try_push(ctx.0); output } - fn finish_dec<'a>(&'a self, ctx: OpenSSLAesGcmDec, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool { + fn finish_dec(&self, ctx: OpenSSLAesGcmDec, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool { let output = unsafe { ctx.0.set_tag(tag) && ctx.0.finalize::() }; let _ = self.dec.lock().unwrap().try_push(ctx.0); output diff --git a/performance/src/ratchet_state.rs b/performance/src/ratchet_state.rs index b41faef..3f07290 100644 --- a/performance/src/ratchet_state.rs +++ b/performance/src/ratchet_state.rs @@ -274,10 +274,8 @@ impl<'a> CompareAndSwap<'a> { /// There may be a second ratchet fingerprint to be deleted, which function /// `deleted_fingerprint2` will return. pub fn deleted_fingerprint1(&self) -> Option<&[u8; RATCHET_SIZE]> { - if self.cur_state1_was_just_deleted { - if !self.cur_state1.is_empty() { - return Some(self.cur_state1.fingerprint()); - } + if self.cur_state1_was_just_deleted && !self.cur_state1.is_empty() { + return Some(self.cur_state1.fingerprint()); } None } diff --git a/performance/src/zeta.rs b/performance/src/zeta.rs index 3388fd8..85c5dc0 100644 --- a/performance/src/zeta.rs +++ b/performance/src/zeta.rs @@ -490,10 +490,8 @@ pub(crate) fn received_x1_trans>( Err(e) => return Err(ReceiveError::StorageError(e)), } } - if ratchet_state.is_none() { - if app.hello_requires_recognized_ratchet() { - return Err(fault!(FailedAuth, true)); - } + if ratchet_state.is_none() && app.hello_requires_recognized_ratchet() { + return Err(fault!(FailedAuth, true)); } } // If we get to this point and haven't found a full ratchet state, diff --git a/reference/src/ratchet_state.rs b/reference/src/ratchet_state.rs index 9c37054..204852e 100644 --- a/reference/src/ratchet_state.rs +++ b/reference/src/ratchet_state.rs @@ -271,10 +271,8 @@ impl<'a> CompareAndSwap<'a> { /// There may be a second ratchet fingerprint to be deleted, which function /// `deleted_fingerprint2` will return. pub fn deleted_fingerprint1(&self) -> Option<&[u8; RATCHET_SIZE]> { - if self.cur_state1_was_just_deleted { - if !self.cur_state1.is_empty() { - return Some(self.cur_state1.fingerprint()); - } + if self.cur_state1_was_just_deleted && !self.cur_state1.is_empty() { + return Some(self.cur_state1.fingerprint()); } None } diff --git a/reference/src/zeta.rs b/reference/src/zeta.rs index b5ba5ac..776e194 100644 --- a/reference/src/zeta.rs +++ b/reference/src/zeta.rs @@ -115,7 +115,7 @@ impl SymmetricState { self.mix_key(&pub_key); e_secret } - fn read_e(&mut self, i: &mut usize, packet: &Vec) -> Option { + fn read_e(&mut self, i: &mut usize, packet: &[u8]) -> Option { let j = *i + P384_PUBLIC_KEY_SIZE; let pub_key = &packet[*i..j]; self.mix_hash(pub_key); @@ -421,10 +421,8 @@ pub(crate) fn received_x1_trans>( Err(e) => return Err(ReceiveError::StorageError(e)), } } - if ratchet_state.is_none() { - if app.hello_requires_recognized_ratchet() { - return Err(fault!(FailedAuth, true)); - } + if ratchet_state.is_none() && app.hello_requires_recognized_ratchet() { + return Err(fault!(FailedAuth, true)); } } // If we get to this point and haven't found a full ratchet state, From 13a5c39c08f7ba2b91f6b1d2af19e0ce1c016ae4 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 18 Jan 2024 16:02:58 -0500 Subject: [PATCH 09/16] documented everything --- performance/examples/benchmark.rs | 1 - performance/src/crypto/aes.rs | 73 +++++++++++++++++---- performance/src/crypto_impl/mod.rs | 16 ++++- performance/src/crypto_impl/openssl.rs | 88 ++++++++++++++++---------- performance/src/lib.rs | 33 +++++++++- performance/src/zeta.rs | 8 +-- reference/src/lib.rs | 15 +++-- 7 files changed, 174 insertions(+), 60 deletions(-) diff --git a/performance/examples/benchmark.rs b/performance/examples/benchmark.rs index edb77cd..220c46d 100644 --- a/performance/examples/benchmark.rs +++ b/performance/examples/benchmark.rs @@ -53,7 +53,6 @@ impl AsRef<[u8]> for PooledVec { #[allow(unused)] impl DefaultCrypto for TestApplication { type SessionData = (); - type LookupData = (); type IncomingPacketBuffer = PooledVec; } diff --git a/performance/src/crypto/aes.rs b/performance/src/crypto/aes.rs index 9460744..96a06cc 100644 --- a/performance/src/crypto/aes.rs +++ b/performance/src/crypto/aes.rs @@ -47,26 +47,27 @@ pub trait Aes256Dec: Sized + 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]); -} - -pub trait AesGcmDecContext { - fn decrypt_in_place(&mut self, data: &mut [u8]); -} /// A trait for implementing AES-GCM-256 in a way that allows for extremely high throughput. /// One instance of this trait is created whenever a new pair of noise keys are created, -/// and it handles all data encryption that passes through that session. +/// and it handles all data encryption and decryption that passes through that session. +/// +/// It is highly recommended to implement this trait such that encryption and decryption +/// are hardware accelerated and parallelized. +/// ZSSP's throughput is near 90% determined by this trait. /// /// Instances must securely delete their keys when dropped. pub trait HighThroughputAesGcmPool: Send + Sync { - type EncContext<'a>: AesGcmEncContext - where - Self: 'a; - type DecContext<'a>: AesGcmDecContext - where - Self: 'a; + /// This type represents the state needed to stream a single plaintext for + /// AES-GCM Authenticated Encryption. + /// It should be set to whatever object, struct, handle or pointer your chosen library + /// uses for its stream encryption API. + type EncContext<'a> where Self: 'a; + /// This type represents the state needed to stream a single ciphertext for + /// AES-GCM Authenticated Decryption. + /// It should be set to whatever object, struct, handle or pointer your chosen library + /// uses for its stream decryption API. + type DecContext<'a> where Self: 'a; /// Create a new instance of this trait. /// `encrypt_key` must be used as the encryption key. @@ -82,7 +83,38 @@ pub trait HighThroughputAesGcmPool: Send + Sync { /// There is no additional associated data to be used. fn start_dec<'a>(&'a self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> Self::DecContext<'a>; + /// Stream-encrypt `input` using the specified encryption context `enc`, and write the + /// resulting ciphertext to `output`. + /// + /// `input` and `output` are guaranteed to have the same length. + /// + /// Be sure to update the internal state of `enc` so the authentication tag can be correctly + /// computed. + fn encrypt<'a>(&'a self, enc: &mut Self::EncContext<'a> , input: &[u8], output: &mut [u8]); + + /// Stream-decrypt `data` using the specified encryption context `dec`. + /// The resulting plaintext should be written back into `data`. + /// + /// Be sure to update the internal state of `dec` so the authentication tag can be correctly + /// computed. + fn decrypt_in_place<'a>(&'a self, dec: &mut Self::DecContext<'a>, data: &mut [u8]); + + /// Finish streaming to `enc` and output the resulting authentication tag. + /// + /// Perform any pooling or cleanup required on `enc`. + /// For many libraries it is much faster to return `enc` to some pool to be reused + /// by `start_enc`, rather than dropping it. + /// Be sure to perform your own benchmark. fn finish_enc<'a>(&'a self, enc: Self::EncContext<'a>) -> [u8; AES_GCM_TAG_SIZE]; + /// Finish streaming to `dec` and check that the expected authentication tag matches `tag`. + /// Make sure that comparing `tag` to the expected authentication tag is performed + /// in constant-time. Many libraries provide functions which will do this for you. + /// Output `true` only if `tag` is correct. + /// + /// Afterwards, perform any pooling or cleanup required on `dec`. + /// For many libraries it is much faster to return `dec` to some pool to be reused + /// by `start_dec`, rather than dropping it. + /// Be sure to perform your own benchmark. #[must_use] fn finish_dec<'a>(&'a self, dec: Self::DecContext<'a>, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool; } @@ -90,12 +122,25 @@ pub trait HighThroughputAesGcmPool: Send + Sync { /// A trait for implementing AES-GCM-256 to handle the more varied, but much lower throughput /// requirements of a Noise handshake. pub trait LowThroughputAesGcm { + /// A pure function (no side effects) that implements AESGCM AEAD enryption. + /// + /// Encryption must be performed on `data` in-place. + /// The initial plaintext of `data` must be overwriten with its ciphertext. + /// + /// The resulting GCM authentication tag must be returned. 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]; + /// A pure function (no side effects) that implements AESGCM AEAD decryption. + /// + /// Encryption must be performed on `data` in-place. + /// The initial ciphertext of `data` must be overwriten with its plaintext. + /// + /// This function must check that the expected authentication tag matches `tag`, + /// and only return `true` if they match. This must be done in constant-time. #[must_use] fn decrypt_in_place( key: &[u8; AES_256_KEY_SIZE], diff --git a/performance/src/crypto_impl/mod.rs b/performance/src/crypto_impl/mod.rs index acf86e2..5402aef 100644 --- a/performance/src/crypto_impl/mod.rs +++ b/performance/src/crypto_impl/mod.rs @@ -35,10 +35,22 @@ pub use openssl::*; #[cfg(feature = "openssl-sys")] pub use openssl_sys; +/// Any type which implements this trait will also auto-implement the `CryptoLayer` trait, +/// using the default set of cryptography implementations. +/// +/// If you want to use this library, and don't care which specific implementations it uses, +/// then implement this trait instead of the `CryptoLayer` trait. #[cfg(feature = "default-crypto")] pub trait DefaultCrypto { + /// Type for arbitrary opaque object for use by the application that is attached to + /// each session. type SessionData; - type LookupData; + /// 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: AsMut<[u8]> + AsRef<[u8]>; } #[cfg(feature = "default-crypto")] @@ -53,8 +65,8 @@ impl crate::application::CryptoLayer for C { type PublicKey = CrateP384PublicKey; type KeyPair = CrateP384KeyPair; type Kem = CrateKyber1024PrivateKey; + type FingerprintData = (); type SessionData = C::SessionData; - type FingerprintData = C::LookupData; type IncomingPacketBuffer = C::IncomingPacketBuffer; } diff --git a/performance/src/crypto_impl/openssl.rs b/performance/src/crypto_impl/openssl.rs index 97da58a..546bc54 100644 --- a/performance/src/crypto_impl/openssl.rs +++ b/performance/src/crypto_impl/openssl.rs @@ -9,6 +9,8 @@ use zeroize::Zeroizing; use crate::crypto::*; +/// A wrapper for a `EVP_CIPHER_CTX` that will free itself on drop. +/// Users are encouraged to not use one of these directly. pub struct OpenSSLCtx(NonNull); impl Drop for OpenSSLCtx { fn drop(&mut self) { @@ -23,6 +25,8 @@ impl OpenSSLCtx { unsafe { Some(OpenSSLCtx(NonNull::new(EVP_CIPHER_CTX_new())?)) } } + /// Initialize a cipher context for encryption or decryption using the specified `key` and `iv`. + /// If `key` is null then the previous key assigned to this context will be used. pub unsafe fn cipher_init( &self, t: *const openssl_sys::EVP_CIPHER, @@ -38,7 +42,15 @@ impl OpenSSLCtx { // 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 } - + /// Stream a portion of text to be encrypted or decrypted. + /// `input` will be the input to the cipher stream, and the resulting plaintext or ciphertext + /// will be written to output. Both buffers must be of size `len`. + /// + /// If `output == input`, then the operation will be performed "in-place". + /// `output` and `input` must not overlap otherwise. + /// + /// If `output` is null, then `input` will be treated as AAD rather than plaintext or ciphertext. + /// `input` must not be null. pub unsafe fn update(&self, input: &[u8], output: *mut u8) -> bool { let evp_f = if ENCRYPT { EVP_EncryptUpdate @@ -57,6 +69,9 @@ impl OpenSSLCtx { ) > 0 } + + /// Finish encryption or decryption. + /// If performing decryption this will return whether the set tag is correct. pub unsafe fn finalize(&self) -> bool { let evp_f = if ENCRYPT { EVP_EncryptFinal_ex @@ -68,6 +83,8 @@ impl OpenSSLCtx { evp_f(self.0.as_ptr(), ptr::null_mut(), &mut outl) > 0 } + /// Retreive the authentication tag from this context. + /// This must be called after `finalize` is called. pub unsafe fn get_tag(&self, tag: &mut [u8]) -> bool { EVP_CIPHER_CTX_ctrl( self.0.as_ptr(), @@ -76,6 +93,10 @@ impl OpenSSLCtx { tag.as_mut_ptr() as *mut _, ) > 0 } + + /// Set the authentication tag that was assigned to the input ciphertext. + /// Once set, OpenSLL will check whether it matches the expected authentication tag + /// produced by decryption. #[allow(unused)] pub unsafe fn set_tag(&self, tag: &[u8]) -> bool { EVP_CIPHER_CTX_ctrl( @@ -85,11 +106,17 @@ impl OpenSSLCtx { tag.as_ptr() as *mut _, ) > 0 } + /// Returns the raw pointer to the `EVP_CIPHER_CTX` + /// object used internally with OpenSSL. + /// + /// This function is guaranteed to return a non-null pointer. pub fn as_ptr(&self) -> *mut openssl_sys::EVP_CIPHER_CTX { self.0.as_ptr() } } +/// An `OpenSSLCtx` wrapped in a mutex for thread-safety. +/// This `OpenSSLCtx` struct only supports AES256 block operations, and implements `OpenSSLAes256Enc`. pub struct OpenSSLAes256Enc(Mutex); unsafe impl Send for OpenSSLAes256Enc {} unsafe impl Sync for OpenSSLAes256Enc {} @@ -120,6 +147,8 @@ impl Aes256Enc for OpenSSLAes256Enc { unsafe { assert!(ctx.update::(block, ptr)) } } } +/// An `OpenSSLCtx` wrapped in a mutex for thread-safety. +/// This `OpenSSLCtx` struct only supports AES256 block operations, and implements `OpenSSLAes256Dec`. pub struct OpenSSLAes256Dec(Mutex); unsafe impl Send for OpenSSLAes256Dec {} unsafe impl Sync for OpenSSLAes256Dec {} @@ -151,21 +180,6 @@ impl Aes256Dec for OpenSSLAes256Dec { } } -pub struct OpenSSLAesGcmEnc(OpenSSLCtx); -impl AesGcmEncContext for OpenSSLAesGcmEnc { - fn encrypt(&mut self, input: &[u8], output: &mut [u8]) { - unsafe { assert!(self.0.update::(input, output.as_mut_ptr())) }; - } -} - -pub struct OpenSSLAesGcmDec(OpenSSLCtx); -impl AesGcmDecContext for OpenSSLAesGcmDec { - fn decrypt_in_place(&mut self, data: &mut [u8]) { - let p = data.as_mut_ptr(); - unsafe { assert!(self.0.update::(data, p)) }; - } -} - /// A pool of OpenSSL AES-GCM ciphers. pub struct OpenSSLAesGcmPool { enc: Mutex>, @@ -177,9 +191,9 @@ unsafe impl Send for OpenSSLAesGcmPool {} unsafe impl Sync for OpenSSLAesGcmPool {} impl HighThroughputAesGcmPool for OpenSSLAesGcmPool { - type EncContext<'a> = OpenSSLAesGcmEnc; + type EncContext<'a> = OpenSSLCtx; - type DecContext<'a> = OpenSSLAesGcmDec; + type DecContext<'a> = OpenSSLCtx; fn new(encrypt_key: &[u8; AES_256_KEY_SIZE], decrypt_key: &[u8; AES_256_KEY_SIZE]) -> Self { Self { @@ -190,53 +204,63 @@ impl HighThroughputAesGcmPool for OpenSSLAesGcmPool { } } - fn start_enc(&self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmEnc { + fn start_enc(&self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLCtx { let ctx = self.enc.lock().unwrap().pop(); unsafe { if let Some(ctx) = ctx { assert!(ctx.cipher_init::(ptr::null(), ptr::null(), nonce.as_ptr())); - OpenSSLAesGcmEnc(ctx) + ctx } else { let ctx = OpenSSLCtx::new().unwrap(); 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); - OpenSSLAesGcmEnc(ctx) + ctx } } } - fn start_dec(&self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLAesGcmDec { + fn start_dec(&self, nonce: &[u8; AES_GCM_NONCE_SIZE]) -> OpenSSLCtx { let ctx = self.dec.lock().unwrap().pop(); unsafe { if let Some(ctx) = ctx { assert!(ctx.cipher_init::(ptr::null(), ptr::null(), nonce.as_ptr())); - OpenSSLAesGcmDec(ctx) + ctx } else { let ctx = OpenSSLCtx::new().unwrap(); 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); - OpenSSLAesGcmDec(ctx) + ctx } } } - fn finish_enc(&self, ctx: OpenSSLAesGcmEnc) -> [u8; AES_GCM_TAG_SIZE] { + fn encrypt(&self, ctx: &mut OpenSSLCtx, input: &[u8], output: &mut [u8]) { + unsafe { assert!(ctx.update::(input, output.as_mut_ptr())) }; + } + fn decrypt_in_place(&self, ctx: &mut OpenSSLCtx, data: &mut [u8]) { + let p = data.as_mut_ptr(); + unsafe { assert!(ctx.update::(data, p)) }; + } + + fn finish_enc(&self, ctx: OpenSSLCtx) -> [u8; AES_GCM_TAG_SIZE] { let mut output = [0u8; AES_GCM_TAG_SIZE]; unsafe { - assert!(ctx.0.finalize::()); - assert!(ctx.0.get_tag(&mut output)); + assert!(ctx.finalize::()); + assert!(ctx.get_tag(&mut output)); } - let _ = self.enc.lock().unwrap().try_push(ctx.0); + let _ = self.enc.lock().unwrap().try_push(ctx); output } - fn finish_dec(&self, ctx: OpenSSLAesGcmDec, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool { - let output = unsafe { ctx.0.set_tag(tag) && ctx.0.finalize::() }; - let _ = self.dec.lock().unwrap().try_push(ctx.0); + fn finish_dec(&self, ctx: OpenSSLCtx, tag: &[u8; AES_GCM_TAG_SIZE]) -> bool { + let output = unsafe { ctx.set_tag(tag) && ctx.finalize::() }; + let _ = self.dec.lock().unwrap().try_push(ctx); output } } - +/// An empty struct which implements `LowThroughputAesGcm` using OpenSSL. +/// +/// It is just a namespace and wrapper for OpenSSL. pub struct OpenSSLAesGcm; impl LowThroughputAesGcm for OpenSSLAesGcm { fn encrypt_in_place( diff --git a/performance/src/lib.rs b/performance/src/lib.rs index 4e2f78c..d4e151c 100644 --- a/performance/src/lib.rs +++ b/performance/src/lib.rs @@ -36,9 +36,34 @@ //! - **KBKDF**: Key mixing, sub-key derivation //! - **AES-256**: Single block encryption of header to harden packet fragmentation protocol //! - **AES-256-GCM**: Authenticated encryption -//#![warn(missing_docs, rust_2018_idioms)] +#![warn(missing_docs, rust_2018_idioms)] #![allow(clippy::too_many_arguments, clippy::type_complexity, clippy::assertions_on_constants)] + +/// A collection of implementation-independent traits for the various specific cryptographic +/// algorithms ZSSP depends on. +/// +/// Each trait is very specific about the semantics of the algorithms and the lengths of their +/// inputs and outputs. +/// This is to enforced a basic sanity-check upon anyone trying to connect this library to +/// someone else's implementation. +/// +/// You should not use this module to implement your own crypto from scratch. +/// +/// The `crypto_impl` module contains implementations of these traits in terms of popular Rust +/// crates. pub mod crypto; +/// A module containing optional implementations of the ZSSP `crypto` traits in terms of popular +/// Rust crates. Some of these crates are not thoroughly audited, so use at your own risk. +/// +/// The current version of the AES crate does not support stream encryption. For this reason +/// ZSSP's AES trait is implemented with OpenSSL instead. It has been optimized for +/// hardware accelerated, parrallel encryption and decryption. +/// +/// Note that none of these crates are FIPS certified, meaning a build of ZSSP using them will not +/// be FIPS compliant. However lack of FIPS compliance by no means implies lack of security or lack +/// of confidence. +/// +/// This module contains the trait implementations as well as re-exports of those crates. pub mod crypto_impl; mod antireplay; @@ -58,8 +83,14 @@ mod symmetric_state; mod zeta; mod zssp; +/// An abstraction over OS and use-case specific resources and queries. +/// This allows this library to be platform independent. +/// A user of this library will need to implement the `ApplicationLayer` trait. pub mod application; +/// This module contains several ZSSP constants that a user might want to know for key management, +/// or in order to avoid "data too large" or "mtu too small" errors. pub mod proto; +/// The collection of the major return types for ZSSP. pub mod result; pub use crate::log_event::*; diff --git a/performance/src/zeta.rs b/performance/src/zeta.rs index 85c5dc0..2cf58da 100644 --- a/performance/src/zeta.rs +++ b/performance/src/zeta.rs @@ -1684,7 +1684,7 @@ pub(crate) fn send_payload( mtu_sized_buffer[..HEADER_SIZE].copy_from_slice(&header); mtu_sized_buffer[FRAGMENT_NO_IDX] = fragment_no as u8; let fragment_start = &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + fragment_len]; - cipher.encrypt(&payload[i..j], fragment_start); + cipher_pool.encrypt(&mut cipher, &payload[i..j], fragment_start); let header_auth = &mut mtu_sized_buffer[HEADER_AUTH_START..HEADER_AUTH_END]; state.hk_send.encrypt_in_place(header_auth.try_into().unwrap()); @@ -1705,7 +1705,7 @@ pub(crate) fn send_payload( mtu_sized_buffer[..HEADER_SIZE].copy_from_slice(&header); mtu_sized_buffer[FRAGMENT_NO_IDX] = fragment_no as u8; let fragment_start = &mut mtu_sized_buffer[HEADER_SIZE..HEADER_SIZE + payload_rem]; - cipher.encrypt(&payload[i..], fragment_start); + cipher_pool.encrypt(&mut cipher, &payload[i..], fragment_start); mtu_sized_buffer[HEADER_SIZE + payload_rem..HEADER_SIZE + fragment_len] .copy_from_slice(&cipher_pool.finish_enc(cipher)); @@ -1763,12 +1763,12 @@ pub(crate) fn receive_payload_in_place( 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); + cipher_pool.decrypt_in_place(&mut cipher, fragment); } 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]); + cipher_pool.decrypt_in_place(&mut cipher, &mut fragment[..tag_idx]); if !cipher_pool.finish_dec(cipher, (&fragment[tag_idx..]).try_into().unwrap()) { return Err(fault!(FailedAuth, true, session)); diff --git a/reference/src/lib.rs b/reference/src/lib.rs index 0d656e0..5467573 100644 --- a/reference/src/lib.rs +++ b/reference/src/lib.rs @@ -50,18 +50,21 @@ mod symmetric_state; mod zeta; /// An abstraction over OS and use-case specific resources and queries. -/// This allows this library to be platform independent, but a user of this library must implement -/// the `ApplicationLayer` trait. +/// This allows this library to be platform independent. +/// A user of this library will need to implement the `ApplicationLayer` trait. pub mod application; /// A collection of implementation-independent traits for the various specific cryptographic /// algorithms ZSSP depends on. /// -/// Each trait is hyper-specific about the semantics of the algorithms and the lengths of their +/// Each trait is very specific about the semantics of the algorithms and the lengths of their /// inputs and outputs. -/// This is to enforced a basic sanity-check upon anyone trying to use their own implementations. +/// This is to enforced a basic sanity-check upon anyone trying to connect this library to +/// someone else's implementation. +/// +/// You should not use this module to implement your own crypto from scratch. /// /// The `crypto_impl` module contains implementations of these traits in terms of popular Rust -/// implementations of these algorithms. +/// crates. pub mod crypto; /// A module containing optional implementations of the ZSSP `crypto` traits in terms of popular /// Rust crates. Some of these crates are not thoroughly audited, so use at your own risk. @@ -72,7 +75,7 @@ pub mod crypto; /// /// This module contains the trait implementations as well as re-exports of those crates. pub mod crypto_impl; -/// The collection of major return types of this library. +/// The collection of the major return types for ZSSP. pub mod result; pub use context::Context; From 136a7f3e75eb0e57a009e3e01fe1b0499c72263e Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 19 Jan 2024 10:24:43 -0500 Subject: [PATCH 10/16] improved tests --- performance/examples/basic_test.rs | 18 ++++++++++++------ performance/src/proto.rs | 2 ++ reference/examples/extensive_test.rs | 21 +++++++++++++++++---- reference/src/context.rs | 1 + reference/src/proto.rs | 2 +- 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/performance/examples/basic_test.rs b/performance/examples/basic_test.rs index 37cf5ba..bcec39c 100644 --- a/performance/examples/basic_test.rs +++ b/performance/examples/basic_test.rs @@ -227,8 +227,6 @@ fn alice_main( } } } - //} else if OsRng.next_u32() | 1 > 0 { - // let _ = recursive_out.send(pkt); } } else { break; @@ -326,8 +324,6 @@ fn bob_main( } } } - //} else if OsRng.next_u32() | 1 > 0 { - // let _ = recursive_out.try_send(pkt); } } @@ -419,6 +415,16 @@ fn main() { } #[test] -fn test_main() { - core(2, u32::MAX / 2) +fn test_50() { + core(10, u32::MAX / 2) +} + +#[test] +fn test_75() { + core(10, u32::MAX / 4 * 3) +} + +#[test] +fn test_99() { + core(10, u32::MAX / 100 * 99) } diff --git a/performance/src/proto.rs b/performance/src/proto.rs index 2435b34..648f838 100644 --- a/performance/src/proto.rs +++ b/performance/src/proto.rs @@ -184,4 +184,6 @@ pub(crate) const MAX_UNASSOCIATED_FRAGMENTS: usize = 32 * 32; /// Excludes the size of headers for fragmentation. pub(crate) const MAX_UNASSOCIATED_PACKET_SIZE: usize = HANDSHAKE_HELLO_CHALLENGE_SIZE; +/// This number determines how many defragmentation buffers are created per session. +/// Each defragmentation buffer handles one packet at a time. pub(crate) const SESSION_MAX_FRAGMENTS_OOO: usize = 64; diff --git a/reference/examples/extensive_test.rs b/reference/examples/extensive_test.rs index 02496e7..45abac5 100644 --- a/reference/examples/extensive_test.rs +++ b/reference/examples/extensive_test.rs @@ -219,7 +219,8 @@ fn alice_main( } } } - } else if OsRng.next_u32() | 1 > 0 { + } else if (OsRng.next_u32() & 1) > 0 { + // There is a 50% chance that a packet will be re-ordered instead of dropped. let _ = recursive_out.send(pkt); } } else { @@ -307,7 +308,8 @@ fn bob_main( } } } - } else if OsRng.next_u32() | 1 > 0 { + } else if (OsRng.next_u32() & 1) > 0 { + // There is a 50% chance that a packet will be re-ordered instead of dropped. let _ = recursive_out.try_send(pkt); } } @@ -393,7 +395,18 @@ fn main() { core(60 * 60, packet_success_rate) } + #[test] -fn test_main() { - core(2, u32::MAX / 2) +fn test_50() { + core(10, u32::MAX / 2) +} + +#[test] +fn test_75() { + core(10, u32::MAX / 4 * 3) +} + +#[test] +fn test_99() { + core(10, u32::MAX / 100 * 99) } diff --git a/reference/src/context.rs b/reference/src/context.rs index e45b0fe..ce4d096 100644 --- a/reference/src/context.rs +++ b/reference/src/context.rs @@ -169,6 +169,7 @@ impl Context { Ok(()) } else if PACKET_TYPE_USES_COUNTER_RANGE.contains(&p) { if !zeta.check_counter_window(c) { + println!("{} {} {}", c, p, kid_recv); // The counter window has finite memory and so will occasionally give // false positives on very out-of-order packets. return Err(fault!(ExpiredCounter, false)); diff --git a/reference/src/proto.rs b/reference/src/proto.rs index 67d9c13..4499edc 100644 --- a/reference/src/proto.rs +++ b/reference/src/proto.rs @@ -88,7 +88,7 @@ pub(crate) const HARD_EXPIRATION: u64 = u64::MAX; /// 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; From c218cc3409dcb78e201c8eafb37757ca3ead27b4 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 19 Jan 2024 10:25:37 -0500 Subject: [PATCH 11/16] upped version number --- performance/Cargo.toml | 2 +- reference/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/performance/Cargo.toml b/performance/Cargo.toml index f63d0b6..8fbc4bf 100644 --- a/performance/Cargo.toml +++ b/performance/Cargo.toml @@ -3,7 +3,7 @@ authors = ["ZeroTier, Inc. ", "Adam Ierymenko ", "Adam Ierymenko Date: Fri, 19 Jan 2024 10:26:33 -0500 Subject: [PATCH 12/16] cargo fmt --- performance/src/crypto/aes.rs | 11 +++++++---- performance/src/crypto_impl/openssl.rs | 1 - performance/src/zeta.rs | 2 +- reference/examples/extensive_test.rs | 1 - 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/performance/src/crypto/aes.rs b/performance/src/crypto/aes.rs index 96a06cc..7fc91fe 100644 --- a/performance/src/crypto/aes.rs +++ b/performance/src/crypto/aes.rs @@ -47,7 +47,6 @@ pub trait Aes256Dec: Sized + Send + Sync { fn decrypt_in_place(&self, block: &mut [u8; AES_256_BLOCK_SIZE]); } - /// A trait for implementing AES-GCM-256 in a way that allows for extremely high throughput. /// One instance of this trait is created whenever a new pair of noise keys are created, /// and it handles all data encryption and decryption that passes through that session. @@ -62,12 +61,16 @@ pub trait HighThroughputAesGcmPool: Send + Sync { /// AES-GCM Authenticated Encryption. /// It should be set to whatever object, struct, handle or pointer your chosen library /// uses for its stream encryption API. - type EncContext<'a> where Self: 'a; + type EncContext<'a> + where + Self: 'a; /// This type represents the state needed to stream a single ciphertext for /// AES-GCM Authenticated Decryption. /// It should be set to whatever object, struct, handle or pointer your chosen library /// uses for its stream decryption API. - type DecContext<'a> where Self: 'a; + type DecContext<'a> + where + Self: 'a; /// Create a new instance of this trait. /// `encrypt_key` must be used as the encryption key. @@ -90,7 +93,7 @@ pub trait HighThroughputAesGcmPool: Send + Sync { /// /// Be sure to update the internal state of `enc` so the authentication tag can be correctly /// computed. - fn encrypt<'a>(&'a self, enc: &mut Self::EncContext<'a> , input: &[u8], output: &mut [u8]); + fn encrypt<'a>(&'a self, enc: &mut Self::EncContext<'a>, input: &[u8], output: &mut [u8]); /// Stream-decrypt `data` using the specified encryption context `dec`. /// The resulting plaintext should be written back into `data`. diff --git a/performance/src/crypto_impl/openssl.rs b/performance/src/crypto_impl/openssl.rs index 546bc54..37ef12c 100644 --- a/performance/src/crypto_impl/openssl.rs +++ b/performance/src/crypto_impl/openssl.rs @@ -69,7 +69,6 @@ impl OpenSSLCtx { ) > 0 } - /// Finish encryption or decryption. /// If performing decryption this will return whether the set tag is correct. pub unsafe fn finalize(&self) -> bool { diff --git a/performance/src/zeta.rs b/performance/src/zeta.rs index 2cf58da..bd8f7d2 100644 --- a/performance/src/zeta.rs +++ b/performance/src/zeta.rs @@ -490,7 +490,7 @@ pub(crate) fn received_x1_trans>( Err(e) => return Err(ReceiveError::StorageError(e)), } } - if ratchet_state.is_none() && app.hello_requires_recognized_ratchet() { + if ratchet_state.is_none() && app.hello_requires_recognized_ratchet() { return Err(fault!(FailedAuth, true)); } } diff --git a/reference/examples/extensive_test.rs b/reference/examples/extensive_test.rs index 45abac5..4d66619 100644 --- a/reference/examples/extensive_test.rs +++ b/reference/examples/extensive_test.rs @@ -395,7 +395,6 @@ fn main() { core(60 * 60, packet_success_rate) } - #[test] fn test_50() { core(10, u32::MAX / 2) From 6522e40d34da1587ff1712112fbc23e5fef4eaea Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 19 Jan 2024 10:36:45 -0500 Subject: [PATCH 13/16] fixed import --- performance/src/crypto_impl/openssl.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/performance/src/crypto_impl/openssl.rs b/performance/src/crypto_impl/openssl.rs index 37ef12c..9a396db 100644 --- a/performance/src/crypto_impl/openssl.rs +++ b/performance/src/crypto_impl/openssl.rs @@ -1,7 +1,5 @@ -use std::{ - ptr::{self, NonNull}, - sync::Mutex, -}; +use std::ptr::{self, NonNull}; +use std::sync::Mutex; use arrayvec::ArrayVec; use openssl_sys::*; From 15fbf7b65d69eb80c0f9c985f0f0e39b14a15556 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 19 Jan 2024 10:53:45 -0500 Subject: [PATCH 14/16] added tests to drone --- .drone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index df2348c..4b0b898 100644 --- a/.drone.yml +++ b/.drone.yml @@ -16,7 +16,7 @@ steps: from_secret: codecov_token commands: - cargo build - - CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='coverage/cargo-test-%p-%m.profraw' cargo test + - CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='coverage/cargo-test-%p-%m.profraw' cargo test --all-targets - mkdir -p target/coverage - grcov . --binary-path ./target/debug/deps/ -s . -t lcov --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/tests.lcov - codecov -B ${DRONE_BRANCH} -b ${DRONE_BUILD_NUMBER} -f target/coverage/tests.lcov -F amd64 @@ -42,7 +42,7 @@ steps: from_secret: codecov_token commands: - cargo build - - CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='coverage/cargo-test-%p-%m.profraw' cargo test + - CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='coverage/cargo-test-%p-%m.profraw' cargo test --all-targets - mkdir -p target/coverage - grcov . --binary-path ./target/debug/deps/ -s . -t lcov --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/tests.lcov - codecov -B ${DRONE_BRANCH} -b ${DRONE_BUILD_NUMBER} -f target/coverage/tests.lcov -F arm64 From 7aec2f2e56d6610a91cf425220c3bd2f694900fc Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 19 Jan 2024 11:07:57 -0500 Subject: [PATCH 15/16] fixed drone tests --- .drone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 4b0b898..272c09f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -16,7 +16,7 @@ steps: from_secret: codecov_token commands: - cargo build - - CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='coverage/cargo-test-%p-%m.profraw' cargo test --all-targets + - CARGO_INCREMENTAL=0 RUSTFLAGS='--all-targets -Cinstrument-coverage' LLVM_PROFILE_FILE='coverage/cargo-test-%p-%m.profraw' cargo test - mkdir -p target/coverage - grcov . --binary-path ./target/debug/deps/ -s . -t lcov --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/tests.lcov - codecov -B ${DRONE_BRANCH} -b ${DRONE_BUILD_NUMBER} -f target/coverage/tests.lcov -F amd64 @@ -42,7 +42,7 @@ steps: from_secret: codecov_token commands: - cargo build - - CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='coverage/cargo-test-%p-%m.profraw' cargo test --all-targets + - CARGO_INCREMENTAL=0 RUSTFLAGS='--all-targets -Cinstrument-coverage' LLVM_PROFILE_FILE='coverage/cargo-test-%p-%m.profraw' cargo test - mkdir -p target/coverage - grcov . --binary-path ./target/debug/deps/ -s . -t lcov --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/tests.lcov - codecov -B ${DRONE_BRANCH} -b ${DRONE_BUILD_NUMBER} -f target/coverage/tests.lcov -F arm64 From eed14a8928a6332e48682271e391e9a113e76d0c Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Fri, 19 Jan 2024 13:02:57 -0500 Subject: [PATCH 16/16] fixed drone --- .drone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 272c09f..4b0b898 100644 --- a/.drone.yml +++ b/.drone.yml @@ -16,7 +16,7 @@ steps: from_secret: codecov_token commands: - cargo build - - CARGO_INCREMENTAL=0 RUSTFLAGS='--all-targets -Cinstrument-coverage' LLVM_PROFILE_FILE='coverage/cargo-test-%p-%m.profraw' cargo test + - CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='coverage/cargo-test-%p-%m.profraw' cargo test --all-targets - mkdir -p target/coverage - grcov . --binary-path ./target/debug/deps/ -s . -t lcov --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/tests.lcov - codecov -B ${DRONE_BRANCH} -b ${DRONE_BUILD_NUMBER} -f target/coverage/tests.lcov -F amd64 @@ -42,7 +42,7 @@ steps: from_secret: codecov_token commands: - cargo build - - CARGO_INCREMENTAL=0 RUSTFLAGS='--all-targets -Cinstrument-coverage' LLVM_PROFILE_FILE='coverage/cargo-test-%p-%m.profraw' cargo test + - CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='coverage/cargo-test-%p-%m.profraw' cargo test --all-targets - mkdir -p target/coverage - grcov . --binary-path ./target/debug/deps/ -s . -t lcov --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/tests.lcov - codecov -B ${DRONE_BRANCH} -b ${DRONE_BUILD_NUMBER} -f target/coverage/tests.lcov -F arm64