From 7bd55a6467a123f2ea463fb45633ed12d56960df Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 21 Nov 2023 15:02:05 -0500 Subject: [PATCH] added better fingerprint support --- performance/examples/basic_test.rs | 7 +++- performance/examples/benchmark.rs | 5 ++- performance/src/application.rs | 64 ++++++++++++++++++++++++++++-- performance/src/crypto_impl/mod.rs | 2 + performance/src/frag_cache.rs | 1 + performance/src/zeta.rs | 12 ++++-- 6 files changed, 80 insertions(+), 11 deletions(-) diff --git a/performance/examples/basic_test.rs b/performance/examples/basic_test.rs index e3d6f03..ea749d4 100644 --- a/performance/examples/basic_test.rs +++ b/performance/examples/basic_test.rs @@ -63,6 +63,7 @@ impl CryptoLayer for TestApplication { type SessionData = u128; type IncomingPacketBuffer = Vec; + type FingerprintData = (); } #[allow(unused)] impl ApplicationLayer for &TestApplication { @@ -82,6 +83,7 @@ impl ApplicationLayer for &TestApplication { &mut self, remote_static_key: &CrateP384PublicKey, identity: &[u8], + _: Option<&()>, ) -> AcceptAction { AcceptAction { session_data: Some(1), @@ -93,15 +95,16 @@ impl ApplicationLayer for &TestApplication { fn restore_by_fingerprint( &mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE], - ) -> Result, std::io::Error> { + ) -> Result, std::io::Error> { let ratchets = self.ratchets.lock().unwrap(); - Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned()) + Ok(ratchets.rf_map.get(ratchet_fingerprint).cloned().map(|r| (r, ()))) } fn restore_by_identity( &mut self, remote_static_key: &CrateP384PublicKey, session_data: &u128, + _: Option<&()>, ) -> Result, std::io::Error> { let ratchets = self.ratchets.lock().unwrap(); Ok(ratchets.peer_map.get(session_data).cloned()) diff --git a/performance/examples/benchmark.rs b/performance/examples/benchmark.rs index c0dd19d..513c486 100644 --- a/performance/examples/benchmark.rs +++ b/performance/examples/benchmark.rs @@ -53,6 +53,7 @@ impl AsRef<[u8]> for PooledVec { #[allow(unused)] impl DefaultCrypto for TestApplication { type SessionData = (); + type LookupData = (); type IncomingPacketBuffer = PooledVec; } @@ -76,6 +77,7 @@ impl ApplicationLayer for &TestApplication { &mut self, remote_static_key: &CrateP384PublicKey, identity: &[u8], + _: Option<&()>, ) -> AcceptAction { AcceptAction { session_data: Some(()), @@ -87,7 +89,7 @@ impl ApplicationLayer for &TestApplication { fn restore_by_fingerprint( &mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE], - ) -> Result, std::io::Error> { + ) -> Result, std::io::Error> { Ok(None) } @@ -95,6 +97,7 @@ impl ApplicationLayer for &TestApplication { &mut self, remote_static_key: &CrateP384PublicKey, session_data: &(), + _: Option<&()>, ) -> Result, std::io::Error> { Ok(None) } diff --git a/performance/src/application.rs b/performance/src/application.rs index c128a0f..250edde 100644 --- a/performance/src/application.rs +++ b/performance/src/application.rs @@ -153,6 +153,22 @@ pub trait CryptoLayer: Sized { /// each session. type SessionData; + /// Type for arbitrary opaque object that is attached to a new connection attempt if Alice sends + /// us a ratchet fingerprint recognized by `restore_by_fingerprint`. + /// + /// If Alice continues to connect + /// with us, then this object will be passed to `check_accept_session` and `restore_by_identity`. + /// This is useful if the ratchet fingerprint was derived from a one-time password, in which + /// case `FingerprintData` can contain metadata regarding the one-time password. This can be + /// used by `check_accept_session` and `restore_by_identity` to perform additional + /// authentication checks, such as validating the one-time password as an invitation code. + /// + /// `FingerprintData` can also be used with extreme caution to cache database resources that can + /// speed up the expected future calls to `check_accept_session` and `restore_by_identity`. + /// If this is done, the implementor is required in `check_accept_session` to verify that the + /// cached resources in `FingerprintData` indeed belong to the specified remote peer. + type FingerprintData; + /// Data type for incoming packet buffers. /// /// This can be something like `Vec` or `Box<[u8]>` or it can be something like a pooled @@ -190,6 +206,9 @@ pub trait ApplicationLayer: Sized { /// If this function is configured to always return true, it means peers will not be able to /// connect to us unless they had a prior-established ratchet key with us. This is the best way /// for the paranoid to enforce a manual allow-list. + /// + /// Corresponds to the "Hello Requires Recognized Ratchet, π_1" security flag of Transition + /// Algorithm 2 within the ZSSP whitepaper. fn hello_requires_recognized_ratchet(&mut self) -> bool; /// This function is called if we, as Alice, attempted to open a session with Bob using a /// non-empty ratchet key, but Bob does not have this ratchet key and wants to downgrade @@ -206,15 +225,29 @@ pub trait ApplicationLayer: Sized { /// least one party is misconfigured and got their ratchet keys corrupted or lost, or Bob has /// been compromised and is being impersonated. An attacker must at least have Bob's private /// static key to be able to ask Alice to downgrade. + /// + /// Corresponds to the "Initiator Disallows Downgrade, π_2" security flag of Transition + /// Algorithm 3 within the ZSSP whitepaper. fn initiator_disallows_downgrade(&mut self, session: &Arc>) -> bool; /// Function to accept sessions after final negotiation. - /// The second argument is the identity that the remote peer sent us. The application - /// must verify this identity is associated with the remote peer's static key. + /// + /// The implementor must verify that three arguments, `remote_static_key`, `identity` and + /// optionally `fingerprint_data` all belong to the same remote peer, using whatever definition + /// of "same remote peer" that the upper protocol chooses. + /// `fingerprint_data` is an opaque type that is only `Some` if Alice sent us a ratchet + /// fingerprint that was successfully restored by `restore_by_fingerprint`. /// /// To prevent desync, if this function specifies that we should connect, no other open session /// with the same remote peer must exist. Drop or call expire on any pre-existing sessions /// before returning. - fn check_accept_session(&mut self, remote_static_key: &Crypto::PublicKey, identity: &[u8]) -> AcceptAction; + /// + /// Corresponds to the **Accept** call of Transition Algorithm 4 within the ZSSP whitepaper. + fn check_accept_session( + &mut self, + remote_static_key: &Crypto::PublicKey, + identity: &[u8], + fingerprint_data: Option<&Crypto::FingerprintData>, + ) -> AcceptAction; /// Lookup a specific ratchet state based on its ratchet fingerprint. /// This function will be called whenever Alice attempts to connect to us with a non-empty @@ -222,10 +255,24 @@ pub trait ApplicationLayer: Sized { /// /// If a ratchet state with a matching fingerprint could not be found, this function should /// return `Ok(None)`. + /// + /// This function can also return an opaque `FingerprintData` object. If Alice continues to connect + /// with us, then this object will be passed to `check_accept_session` and `restore_by_identity`. + /// This is useful if the ratchet fingerprint was derived from a one-time password, in which + /// case `FingerprintData` can contain metadata regarding the one-time password. This can be + /// used by `check_accept_session` and `restore_by_identity` to perform additional + /// authentication checks, such as validating the one-time password as an invitation code. + /// + /// `FingerprintData` can also be used with extreme caution to cache database resources that can + /// speed up the expected future calls to `check_accept_session` and `restore_by_identity`. + /// If this is done, the implementor is required in `check_accept_session` to verify that the + /// cached resources in `FingerprintData` indeed belong to the specified remote peer. + /// + /// Corresponds to the **Restore** call of Transition Algorithm 2 within the ZSSP whitepaper. fn restore_by_fingerprint( &mut self, ratchet_fingerprint: &[u8; RATCHET_SIZE], - ) -> Result, std::io::Error>; + ) -> Result, std::io::Error>; /// Lookup the specific ratchet states based on the identity of the peer being communicated with. /// This function will be called whenever Alice attempts to open a session, or Bob attempts /// to verify Alice's identity. @@ -240,10 +287,13 @@ pub trait ApplicationLayer: Sized { /// This function is not responsible for deciding whether or not to connect to this remote peer. /// Filtering peers should be done by the caller to `Context::open` as well as by the /// function `ApplicationLayer::check_accept_session`. + /// + /// Corresponds to the **Restore** call of Transition Algorithm 1 and 4 within the ZSSP whitepaper. fn restore_by_identity( &mut self, remote_static_key: &Crypto::PublicKey, session_data: &Crypto::SessionData, + fingerprint_data: Option<&Crypto::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. @@ -306,12 +356,18 @@ pub struct AcceptAction { pub session_data: Option, /// Whether or not we will accept a connection with the remote peer when they do not have a /// ratchet key that we think they should have. + /// + /// Corresponds to the "Responder Disallows Downgrade, π_3" security flag of Transition + /// Algorithm 4 within the ZSSP whitepaper. pub responder_disallows_downgrade: bool, /// Whether or not to send an explicit rejection packet to the remote peer if we do not create /// a session with them. /// /// This field will not be used if `session_data` is `Some` and the remote peer passes all other /// authentication checks. + /// + /// Corresponds to the "Responder Silently Rejects, π_4" security flag of Transition + /// Algorithm 4 within the ZSSP whitepaper. pub responder_silently_rejects: bool, } diff --git a/performance/src/crypto_impl/mod.rs b/performance/src/crypto_impl/mod.rs index 96254a5..acf86e2 100644 --- a/performance/src/crypto_impl/mod.rs +++ b/performance/src/crypto_impl/mod.rs @@ -38,6 +38,7 @@ pub use openssl_sys; #[cfg(feature = "default-crypto")] pub trait DefaultCrypto { type SessionData; + type LookupData; type IncomingPacketBuffer: AsMut<[u8]> + AsRef<[u8]>; } #[cfg(feature = "default-crypto")] @@ -54,5 +55,6 @@ impl crate::application::CryptoLayer for C { type Kem = CrateKyber1024PrivateKey; type SessionData = C::SessionData; + type FingerprintData = C::LookupData; type IncomingPacketBuffer = C::IncomingPacketBuffer; } diff --git a/performance/src/frag_cache.rs b/performance/src/frag_cache.rs index 6e3789b..8f52d54 100644 --- a/performance/src/frag_cache.rs +++ b/performance/src/frag_cache.rs @@ -257,6 +257,7 @@ fn test_cache() { type Kem = CrateKyber1024PrivateKey; type SessionData = (); + type FingerprintData = (); type IncomingPacketBuffer = Vec; } diff --git a/performance/src/zeta.rs b/performance/src/zeta.rs index 4247274..e55c76e 100644 --- a/performance/src/zeta.rs +++ b/performance/src/zeta.rs @@ -68,6 +68,7 @@ pub(crate) struct MutableState { /// Corresponds to State B_2 of the Zeta State Machine found in Section 4.1 - Definition 3. pub(crate) struct StateB2 { ratchet_state: RatchetState, + lookup_data: Option, kid_send: NonZeroU32, pub kid_recv: NonZeroU32, pub hk_send: Zeroizing<[u8; AES_256_KEY_SIZE]>, @@ -331,7 +332,7 @@ pub(crate) fn trans_to_a1>( send: impl FnOnce(&mut [u8], Option<&Crypto::PrpEnc>), ) -> Result<(Arc>, Option), OpenError> { let RatchetStates { state1, state2 } = app - .restore_by_identity(&s_remote, &session_data) + .restore_by_identity(&s_remote, &session_data, None) .map_err(OpenError::StorageError)? .unwrap_or_default(); @@ -472,11 +473,13 @@ pub(crate) fn received_x1_trans {} - Ok(Some(rs)) => { + Ok(Some((rs, data))) => { + lookup_data = Some(data); ratchet_state = Some(rs); break; } @@ -551,6 +554,7 @@ pub(crate) fn received_x1_trans { let RatchetStates { state1, state2 } = rss.unwrap_or_default();