From b77d303748524c97c0bf2a14c9629fffa59763bc Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Tue, 26 Sep 2023 08:40:00 -0400 Subject: [PATCH] updated docs --- performance/src/ratchet_state.rs | 73 +++++++++++++++++++++++++++++--- reference/src/ratchet_state.rs | 44 +++++++++++++------ 2 files changed, 96 insertions(+), 21 deletions(-) diff --git a/performance/src/ratchet_state.rs b/performance/src/ratchet_state.rs index f837029..b12aa38 100644 --- a/performance/src/ratchet_state.rs +++ b/performance/src/ratchet_state.rs @@ -13,9 +13,9 @@ use crate::proto::*; /// Corresponds to the Ratchet Key and Ratchet Fingerprint described in Section 3. #[derive(Clone, Eq)] pub struct RatchetState { - pub key: Zeroizing<[u8; RATCHET_SIZE]>, - pub fingerprint: Option>, - pub chain_len: u64, + pub(crate) key: Zeroizing<[u8; RATCHET_SIZE]>, + pub(crate) fingerprint: Option>, + pub(crate) chain_len: u64, } impl PartialEq for RatchetState { fn eq(&self, other: &Self) -> bool { @@ -35,9 +35,14 @@ impl std::hash::Hash for RatchetState { } } 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 } } + /// Creates a new ratchet state from the given ratchet key, ratchet fingerprint and chain length. + /// + /// The caller should make sure any copies of these values are deleted from memory once they are + /// no longer needed. pub fn new_raw(key: [u8; RATCHET_SIZE], fingerprint: [u8; RATCHET_SIZE], chain_len: u64) -> Self { RatchetState { key: Zeroizing::new(key), @@ -45,6 +50,28 @@ impl RatchetState { 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. + /// + /// This value is the default value of `RatchetState`. pub fn empty() -> Self { RatchetState { key: Zeroizing::new([0u8; RATCHET_SIZE]), @@ -52,6 +79,9 @@ impl RatchetState { chain_len: 0, } } + /// Creates a new ratchet state derived from a one-time-password. If both sides of a session use + /// the same one-time-password then they can use this ratchet state to connect with each other + /// for the first time. pub fn new_from_otp(otp: &[u8]) -> RatchetState { let mut buffer = ArrayVec::::new(); buffer.push(1); @@ -69,12 +99,25 @@ 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 fingerprint for this ratchet state. + /// + /// If this returns `None` then the ratchet fingerprint is the empty string. + /// This is the "empty" ratchet state and the key will be all zeros. + /// + /// The ratchet fingerprint value is sensitive and should be hidden, + /// 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() } @@ -85,23 +128,38 @@ impl Default for RatchetState { } } -/// A pair of ratchet states. +/// An ordered pair of two ratchet states. /// It is expected that an instance of this object will be saved to a storage device per-peer, /// and be restore-able via the `ApplicationLayer` trait. /// /// This corresponds to the possible values of abstract variables `rf` and `rk` found in Section 4.3. #[derive(Clone, PartialEq, Eq, Hash)] pub struct RatchetStates { + /// The first ratchet state from the pair. pub state1: RatchetState, + /// The second ratchet state from the pair. It can, and usually will be `None`. pub state2: Option, } impl RatchetStates { + /// Creates a new pair of ratchet states. The order of the arguments matters, and it should be + /// the same order that was originally given by an instance of the `RatchetUpdate` struct. pub fn new(state1: RatchetState, state2: Option) -> Self { Self { state1, state2 } } + /// Creates a new initial pair of ratchet states, where the first ratchet state is the empty + /// ratchet state and the second is `None`. + /// + /// This value is the default value of `RatchetStates`. pub fn new_initial_states() -> Self { Self { state1: RatchetState::empty(), state2: None } } + /// Creates a new initial pair of ratchet states from a one-time password. + /// The first ratchet state will be derived from this password, while the second will be `None`. + /// + /// If both sides of a session use the same one-time-password then they can use this pair to + /// connect with each other for the first time. This pair can be generated with this function, + /// saved to persistent storage, and eventually restored by the `ApplicationLayer` when we + /// attempt to form a session with the correct peer. pub fn new_otp_states(otp: &[u8]) -> Self { Self { state1: RatchetState::new_from_otp::(otp), @@ -173,9 +231,10 @@ impl<'a> RatchetUpdate<'a> { 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 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`. /// /// 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 diff --git a/reference/src/ratchet_state.rs b/reference/src/ratchet_state.rs index 7206809..fc2c85d 100644 --- a/reference/src/ratchet_state.rs +++ b/reference/src/ratchet_state.rs @@ -170,13 +170,19 @@ impl Default for RatchetStates { } } -/// A set of references to ratchet states specifying how a remote peer's persistent -/// storage should be updated. +/// 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 +/// to anything from an in-memory hashtable to a disk-based database. /// -/// There should be only up to two ratchet states saved to storage at a time per peer. -/// Every time a new ratchet state is generated, a previous ratchet state will be deleted. +/// 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. +/// These are sensitive values should they ought to be securely stored, with restricted read-write +/// permissions if stored on disk. +/// +/// 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. @@ -193,12 +199,15 @@ pub struct RatchetUpdate<'a> { pub deleted_state2: Option<&'a RatchetState>, } impl<'a> RatchetUpdate<'a> { - /// Clones the `state1` and `state2` pair of ratchet states out of this struct and into a - /// `RatchetStates` instance. + /// 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()) } - /// Returns the new ratchet fingerprint that was added during this update, if any. + /// 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. pub fn added_fingerprint(&self) -> Option<&[u8; RATCHET_SIZE]> { if self.state1_was_just_added { self.state1.fingerprint() @@ -206,7 +215,12 @@ impl<'a> RatchetUpdate<'a> { None } } - /// Returns the first ratchet fingerprint that was deleted during this update, if any. + /// 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`. + /// + /// 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() @@ -214,12 +228,14 @@ impl<'a> RatchetUpdate<'a> { None } } - /// Returns the second fingerprint that was deleted during this update, if there was more than - /// one ratchet state deleted during this update. + /// 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`. /// - /// It is extremely rare that this function ever returns `Some`, but it is possible. - /// Users should explicitly test that their implementation is able to correctly delete more than - /// 1 ratchet state at a time, because it is unlikely to occur under normal conditions. + /// 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`. pub fn deleted_fingerprint2(&self) -> Option<&[u8; RATCHET_SIZE]> { if let Some(rs) = &self.deleted_state2 { rs.fingerprint()