From fd14ded072a6def1fe17ab81e4678d4293d89b0c Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Thu, 13 Jul 2023 07:05:46 -0400 Subject: [PATCH] updated docs --- src/applicationlayer.rs | 185 ++++++++++++++++++++++------------------ src/lib.rs | 2 +- src/zssp.rs | 30 +++---- 3 files changed, 116 insertions(+), 101 deletions(-) diff --git a/src/applicationlayer.rs b/src/applicationlayer.rs index e688b72..3658be2 100644 --- a/src/applicationlayer.rs +++ b/src/applicationlayer.rs @@ -114,45 +114,18 @@ pub trait ApplicationLayer: Sized { /// It will be dropped as soon as the session is established. type LocalIdentityBlob: AsRef<[u8]>; - /// Save the given ratchet state to persistent storage. - /// A ratchet state consists of a ratchet number, a ratchet fingerprint, and a ratchet key. + /// This function will be called whenever Alice's initial Hello packet contains the zero ratchet + /// fingerprint. Brand new peers will always connect to Bob with the zero ratchet key, but from + /// then on they should be using non-zero ratchet keys. /// - /// Ratchet states are identified by their ratchet number, the latest ratchet state with a - /// specific ratchet number should overwrite any previous ratchet state with the same number. - /// Only the `ratchet_number` and `ratchet_number - 1` ratchet states should be saved to persistent - /// storage, when a new one is saved the `ratchet_number - 2` ratchet state should be deleted. - /// - /// The `last_confirmed_ratchet_number` specifies which of the two saved ratchet states should - /// be used if this local peer needs to re-open this session (i.e. after a system restart). - /// This number should also be saved to persistent storage. - /// - /// A ratchet fingerprint is a 32 byte string unique for each ratchet key, it should be - /// possible to quickly look up the `ratchet_key` from just its `ratchet_fingerprint`. - /// - /// If this returns `Err(())`, the packet which triggered this function to be called will be - /// dropped, and no session state will be mutated, preserving synchronization. The remote peer - /// will eventually resend that packet and so this function will be called again. - /// - /// If persistent storage is supported, this function should not return until the ratchet state - /// is saved, otherwise it is possible, albeit unlikely, for a sudden restart of the local - /// machine to put our ratchet state out of sync with the remote peer. If this happens the only - /// fix is to restart the entire ratchet chain from zero. - /// - /// This function may also save state to volatile storage, or potentially not even save it at - /// all, in which case all peers which connect to us will always have to allow us to downgrade - /// the ratchet chain to zero. Otherwise they might not consider us to be "authentic". + /// If this returns false, we will attempt to connect to Alice with the zero ratchet key. + /// If this returns true, Alice's connection will be silently dropped. + /// If this function is configured to always return true, it means peers will not be able to + /// connect to us unless they had a prior-established ratchet key with us. This is the best way + /// for the paranoid to enforce a manual allow-list. #[allow(unused)] - fn save_ratchet_state( - &self, - alice_s_public: &Self::PublicKey, - application_data: &Self::Data, - ratchet_action: SaveRatchetAction, - latest_ratchet_number: u64, - latest_ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], - latest_ratchet_key: &[u8; RATCHET_KEY_SIZE], - current_time: i64, - ) -> Result<(), ()> { - Ok(()) + fn hello_requires_recognized_ratchet(&self, current_time: i64) -> bool { + false } /// This function is called if we, as Alice, attempted to open a session with Bob using a /// non-zero ratchet key, but Bob does not have this ratchet key and wants to downgrade @@ -173,75 +146,111 @@ pub trait ApplicationLayer: Sized { /// If Alice does decide to reconnect without a ratchet key, be sure to generate some warning /// that something has gone wrong and Bob could not be fully authenticated. #[allow(unused)] - fn allow_downgrade(&self, session: &Arc>, current_time: i64) -> bool { - true + fn initiator_disallows_downgrade(&self, session: &Arc>, current_time: i64) -> bool { + false } /// Lookup a specific ratchet key based on its ratchet fingerprint. /// This function will be called whenever Alice attempts to connect to us with a non-zero - /// ratchet key. + /// ratchet fingerprint. /// - /// If the ratchet key was found, the function should return `RatchetAction::Found`. This will + /// If the ratchet key was found, the function should return `RestoreAction::RestoreRatchet`. This will /// cause us to connect to Alice using the returned ratchet number and ratchet key. - /// We don't know Alice's static identity at this point in the handshake, so a - /// `RemotePeerIdentifier` must be returned, so when Alice does send their identity we - /// can verify it matches what we expect. /// /// If the ratchet key could not be found, the application may choose between returning - /// `RatchetAction::Downgrade` or `RatchetAction::Ignore`. - /// If `RatchetAction::Downgrade` is returned we will attempt to convince Alice to downgrade + /// `RatchetAction::DowngradeRatchet` or `RatchetAction::FailAuthentication`. + /// If `RatchetAction::DowngradeRatchet` is returned we will attempt to convince Alice to downgrade /// to the zero ratchet key, restarting the ratchet chain. - /// If `RatchetAction::Ignore` is returned Alice's connection will be silently dropped. + /// If `RatchetAction::FailAuthentication` is returned Alice's connection will be silently dropped. #[allow(unused)] - fn lookup_ratchet(&self, ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], current_time: i64) -> Result { - Ok(GetRatchetAction::Downgrade) + fn restore_ratchet(&self, ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], current_time: i64) -> Result { + Ok(RestoreAction::DowngradeRatchet) } - /// This function will be called whenever Alice's initial Hello packet contains the zero ratchet - /// key. Brand new peers will always connect to Bob with the zero ratchet key, but from then on - /// they should be using non-zero ratchet keys. + /// Atomically save the given ratchet key, fingerprint and number to persistent storage. /// - /// If this returns true, we will attempt to connect to Alice with the zero ratchet key. - /// If this returns false, Alice's connection will be silently dropped. - /// If this function is configured to always return false, it means peers will not be able to - /// connect to us unless they had a prior-established ratchet key with us. This is the best way - /// for the paranoid to enforce a manual allow-list. + /// See the documentation of `SaveAction` for more details on how to save them to storage, + /// and how to handle any pre-existing ratchet keys, fingerprints and numbers. + /// + /// If this returns `Err(())`, the packet which triggered this function to be called will be + /// dropped, and no session state will be mutated, preserving synchronization. The remote peer + /// will eventually resend that packet and so this function will be called again. + /// + /// If persistent storage is supported, this function should not return until the ratchet state + /// is saved, otherwise it is possible, albeit unlikely, for a sudden restart of the local + /// machine to put our ratchet state out of sync with the remote peer. If this happens the only + /// fix is to reset both ratchet keys to zero. + /// + /// This function may also save state to volatile storage, in which case all peers which connect + /// to us will have to allow downgrade + /// (`initiator_disallows_downgrade` returns false and/or`restore_ratchet` returns `DowngradeRatchet`). + /// Otherwise, when we restart, we will not be allowed to reconnect. #[allow(unused)] - fn allow_zero_ratchet(&self, current_time: i64) -> bool { - true + fn save_ratchet_state( + &self, + remote_static_key: &Self::PublicKey, + application_data: &Self::Data, + ratchet_action: SaveAction, + latest_ratchet_number: u64, + latest_ratchet_fingerprint: &[u8; RATCHET_FINGERPRINT_SIZE], + latest_ratchet_key: &[u8; RATCHET_KEY_SIZE], + current_time: i64, + ) -> Result<(), ()> { + Ok(()) } #[allow(unused)] #[inline] fn event_log<'a>(&self, event: LogEvent<'a, Self>, current_time: i64) {} } -pub enum GetRatchetAction { - Found(u64, [u8; RATCHET_KEY_SIZE]), - Downgrade, - Ignore, +pub enum RestoreAction { + RestoreRatchet(u64, [u8; RATCHET_KEY_SIZE]), + DowngradeRatchet, + FailAuthentication, } -/// Only 2 ratchet states may be saved at one time. -/// If a 3rd ratchet state needs to be saved the 1st should be deleted, if it was not already deleted. -/// The "previous ratchet state" may be the zero ratchet state. -pub enum SaveRatchetAction { - /// Save the given new ratchet state and set the previous saved ratchet state as - /// the confirmed ratchet state, if it was not already. - /// If there are currently two saved states delete the oldest state and replace it with this one. +/// Ratchet keys and fingerprints should be saved *per remote peer*. It is up to the application to +/// enforce separate storage for each remote peer based on `remote_static_key` and `application_data`. +/// +/// Only up to 2 ratchet keys and fingerprints may be saved at one time. +/// If a 3rd needs to be saved the 1st should be deleted, if it was not already deleted. +pub enum SaveAction { + /// Save the given `latest_ratchet_fingerprint` and `latest_ratchet_key`, + /// but do not update the confirmed ratchet number. + /// + /// If a ratchet key and fingerprint already exist with ratchet number `latest_ratchet_number`, + /// then `latest_ratchet_fingerprint` and `latest_ratchet_key` should overwrite them. + /// + /// Keep the previous ratchet state saved and searchable until it is explicitly deleted. + /// If there are two saved ratchet keys and fingerprints, replace the oldest pair with + /// the new pair. SaveAsUnconfirmed, - /// Save the given new ratchet state and set it as the confirmed ratchet state. Keep the previous - /// ratchet state saved and searchable until it is explicitly deleted. - /// If there are currently two saved states delete the oldest state and replace it with this one. + /// Save the given `latest_ratchet_fingerprint` and `latest_ratchet_key`, + /// and set the confirmed ratchet number to `latest_ratchet_number`. + /// The confirmed ratchet number should be set equal to `latest_ratchet_number`. + /// + /// If a ratchet key and fingerprint already exist with ratchet number `latest_ratchet_number`, + /// then `latest_ratchet_fingerprint` and `latest_ratchet_key` should overwrite them. + /// + /// Keep the previous ratchet state saved and searchable until it is explicitly deleted. + /// If there are two saved ratchet keys and fingerprints, replace the oldest pair with + /// the new pair. SaveAsConfirmed, - /// The given ratchet state will be identical to that saved during a previous call to - /// `SaveAsUnconfirmedAndConfirmPrevious`. Set the given ratchet state as the - /// confirmed ratchet state and permanently delete the previous ratchet state. + /// Set the confirmed ratchet number to `latest_ratchet_number`, + /// and permanently delete the previous (oldest) ratchet key and fingerprint. + /// + /// The given `latest_ratchet_fingerprint` and `latest_ratchet_key` will be identical to those + /// saved during a prior `SaveAsUnconfirmed` call. + /// These two must be the only saved ratchet key and fingerprint when this call completes. ConfirmLatestAndDeletePrevious, - /// The given ratchet state will be identical to that saved during a previous call to - /// `SaveAsConfirmed`. Permanently delete the previous ratchet state, as in delete the ratchet - /// state with ratchet number one less than the given ratchet state. + /// Permanently delete the previous (oldest) ratchet key and fingerprint. + /// + /// The given `latest_ratchet_fingerprint` and `latest_ratchet_key` will be identical to those + /// saved during a prior `SaveAsConfirmed` call. + /// These two must be the only saved ratchet key and fingerprint when this call completes. DeletePrevious, } -use SaveRatchetAction::*; -impl SaveRatchetAction { - /// If this is true then this is the first time the latest ratchet state has ever been seen, - /// so it ought to be immediately saved. +use SaveAction::*; +impl SaveAction { + /// If this is true then the given `latest_ratchet_fingerprint` and `latest_ratchet_key` + /// must be saved to permanent storage. + /// `latest_ratchet_key` should be searchable using `latest_ratchet_fingerprint`. pub fn save_latest(&self) -> bool { match self { SaveAsUnconfirmed => true, @@ -249,8 +258,9 @@ impl SaveRatchetAction { _ => false, } } - /// If this is true then only the latest ratchet state should be saved. - /// Any previous ratchet states should be deleted now. + /// If this is true then the previous ratchet key and fingerprint should be deleted. + /// If `latest_ratchet_number > 1`, then the previous ratchet key and fingerprint will have + /// ratchet number `latest_ratchet_number - 1`. pub fn delete_previous(&self) -> bool { match self { ConfirmLatestAndDeletePrevious => true, @@ -258,6 +268,11 @@ impl SaveRatchetAction { _ => false, } } + /// If this is true then set the confirmed ratchet number to `latest_ratchet_number`. + /// + /// The confirmed ratchet number is a single 64-bit number saved to persistent storage that denotes its + /// associated ratchet key is "confirmed". Only the confirmed ratchet key should be used to + /// `open` a new session. pub fn confirm_latest(&self) -> bool { match self { ConfirmLatestAndDeletePrevious => true, diff --git a/src/lib.rs b/src/lib.rs index c2221ad..16a3afa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,7 @@ mod symmetric_state; mod zssp; pub mod error; -pub use crate::applicationlayer::{ApplicationLayer, GetRatchetAction, SaveRatchetAction}; +pub use crate::applicationlayer::{ApplicationLayer, RestoreAction, SaveAction}; pub use crate::log_event::LogEvent; pub use crate::proto::{MAX_IDENTITY_BLOB_SIZE, MIN_PACKET_SIZE, MIN_TRANSPORT_MTU, RATCHET_FINGERPRINT_SIZE, RATCHET_KEY_SIZE}; pub use crate::zssp::{AcceptSessionAction, Context, ContextInner, IncomingSessionAction, ReceiveResult, Session, SessionEvent}; diff --git a/src/zssp.rs b/src/zssp.rs index b1de9c0..dd750d6 100644 --- a/src/zssp.rs +++ b/src/zssp.rs @@ -1015,18 +1015,18 @@ impl Context { let (header_b2a_key, header_a2b_key) = noise_ck.get_ask2(hmac, LABEL_HEADER_KEY, &noise_h_ee1p); // Get ratchet key. let noise_pattern1: &mut NoiseXKPattern1 = byte_array_as_proto_buffer_mut(message); - use crate::GetRatchetAction::*; + use crate::RestoreAction::*; let (sent_zero, ratchet_number, ratchet_key) = if noise_pattern1.ratchet_fingerprint == [0u8; RATCHET_FINGERPRINT_SIZE] { - if app.allow_zero_ratchet(current_time) { - (true, 0, [0u8; RATCHET_KEY_SIZE]) - } else { + if app.hello_requires_recognized_ratchet(current_time) { return Ok(ReceiveResult::Rejected); + } else { + (true, 0, [0u8; RATCHET_KEY_SIZE]) } } else { - match app.lookup_ratchet(&noise_pattern1.ratchet_fingerprint, current_time) { - Ok(Found(ratchet_number, ratchet_key)) => (false, ratchet_number, ratchet_key), - Ok(Downgrade) => (false, 0, [0u8; RATCHET_KEY_SIZE]), - Ok(Ignore) => return Ok(ReceiveResult::Rejected), + match app.restore_ratchet(&noise_pattern1.ratchet_fingerprint, current_time) { + Ok(RestoreRatchet(ratchet_number, ratchet_key)) => (false, ratchet_number, ratchet_key), + Ok(DowngradeRatchet) => (false, 0, [0u8; RATCHET_KEY_SIZE]), + Ok(FailAuthentication) => return Err(byzantine_fault!(FaultType::FailedAuthentication, false)), Err(()) => return Err(ReceiveError::RatchetIoError), } }; @@ -1265,7 +1265,7 @@ impl Context { break; } } else { - if i > 0 || !app.allow_downgrade(&session, current_time) { + if i > 0 || app.initiator_disallows_downgrade(&session, current_time) { break; } // If auth failed maybe Bob wants to downgrade the ratchet, @@ -1322,7 +1322,7 @@ impl Context { let result = app.save_ratchet_state( &session.remote_s_public_key, &session.application_data, - SaveRatchetAction::SaveAsUnconfirmed, + SaveAction::SaveAsUnconfirmed, new_ratchet_number, new_ratchet_fingerprint.as_ref(), new_ratchet_key.as_ref(), @@ -1493,7 +1493,7 @@ impl Context { let result = app.save_ratchet_state( &remote_s_public_key, &application_data, - SaveRatchetAction::SaveAsConfirmed, + SaveAction::SaveAsConfirmed, new_ratchet_number, new_ratchet_fingerprint.as_ref(), new_ratchet_key.as_ref(), @@ -1834,7 +1834,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let result = app.save_ratchet_state( &session.remote_s_public_key, &session.application_data, - SaveRatchetAction::ConfirmLatestAndDeletePrevious, + SaveAction::ConfirmLatestAndDeletePrevious, ratchet.0, ratchet.1.as_ref(), ratchet.2.as_ref(), @@ -1878,7 +1878,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let result = app.save_ratchet_state( &session.remote_s_public_key, &session.application_data, - SaveRatchetAction::DeletePrevious, + SaveAction::DeletePrevious, state.ratchet_number, state.ratchet_fingerprint.as_ref(), state.ratchet_key.as_ref(), @@ -1992,7 +1992,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let result = app.save_ratchet_state( &session.remote_s_public_key, &session.application_data, - SaveRatchetAction::SaveAsUnconfirmed, + SaveAction::SaveAsUnconfirmed, new_ratchet_number, new_ratchet_fingerprint.as_ref(), new_ratchet_key.as_ref(), @@ -2091,7 +2091,7 @@ fn receive_control_fragment<'a, Application: ApplicationLayer, SendFn: FnMut(&mu let result = app.save_ratchet_state( &session.remote_s_public_key, &session.application_data, - SaveRatchetAction::SaveAsConfirmed, + SaveAction::SaveAsConfirmed, new_ratchet_number, new_ratchet_fingerprint.as_ref(), new_ratchet_key.as_ref(),