From 916272cd4070cfa5801301834d6fa5dac3a91914 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 14 Mar 2024 13:32:45 +0000 Subject: [PATCH 1/3] Document a number of the olm structs --- src/olm/session/double_ratchet.rs | 26 ++++++++++++++++++++++++++ src/olm/session/ratchet.rs | 23 +++++++++++++++++++++++ src/olm/session/receiver_chain.rs | 18 ++++++++++++++++++ src/olm/session/root_key.rs | 14 ++++++++++++++ 4 files changed, 81 insertions(+) diff --git a/src/olm/session/double_ratchet.rs b/src/olm/session/double_ratchet.rs index d76820dc..6061cb6f 100644 --- a/src/olm/session/double_ratchet.rs +++ b/src/olm/session/double_ratchet.rs @@ -23,6 +23,21 @@ use super::{ }; use crate::olm::{messages::Message, shared_secret::Shared3DHSecret}; +/// The sender side of a double-ratchet implementation. +/// +/// While we are encrypting messages, we are in the "active" state. Here we need +/// to keep track of the latest chain key `C``i`,`j` (so that we can +/// advance to the next one), and also the current root key `R``i` +/// and our most recent ratchet key `T``i` (so that we can calculate +/// the *next* root key). +/// +/// Once we receive a message, we transition to the "inactive" state. Since we +/// don't handle decryption here (that's done in [`ReceiverChain`]), we don't +/// need to keep track of the sender's chain key. All we need is enough state so +/// that we can calculate the next root key once we start encrypting again: +/// specifically, the public part of the other side's ratchet key +/// `T``i` which was sent to us in the message, and the remote root +/// key `R``i`. #[derive(Serialize, Deserialize, Clone)] #[serde(transparent)] pub(super) struct DoubleRatchet { @@ -59,6 +74,8 @@ impl DoubleRatchet { self.next_message_key().encrypt_truncated_mac(plaintext) } + /// Create a new `DoubleRatchet` instance, based on a newly-calculated + /// shared secret. pub fn active(shared_secret: Shared3DHSecret) -> Self { let (root_key, chain_key) = shared_secret.expand(); @@ -133,6 +150,10 @@ impl From for DoubleRatchetState { } } +/// State of the sender-side ratchet when we have received a new chain from the +/// other side, and have not yet created a new chain of our own. +/// +/// See [`DoubleRatchet`] for more explanation. #[derive(Serialize, Deserialize, Clone)] struct InactiveDoubleRatchet { root_key: RemoteRootKey, @@ -148,6 +169,11 @@ impl InactiveDoubleRatchet { } } +/// State of the sender-side ratchet while we are in "encryption" mode: we are +/// encrypting our own messages and have not yet received any messages which +/// were created since we started this chain. +/// +/// See [`DoubleRatchet`] for more explanation. #[derive(Serialize, Deserialize, Clone)] struct ActiveDoubleRatchet { active_ratchet: Ratchet, diff --git a/src/olm/session/ratchet.rs b/src/olm/session/ratchet.rs index a1a6b4e7..41b88a25 100644 --- a/src/olm/session/ratchet.rs +++ b/src/olm/session/ratchet.rs @@ -24,13 +24,30 @@ use super::{ }; use crate::{types::Curve25519SecretKey, Curve25519PublicKey}; +/// A ratchet key which we created ourselves. +/// +/// A new ratchet key is created each time the conversation changes direction, +/// and used to calculate the [root key](RootKey) for the new sender chain. +/// The public part of the sender's ratchet key is sent to the recipient in each +/// message. +/// +/// Since this is *our own* key, we have both the secret and public parts of the +/// key. +/// +/// The [Olm spec](https://gitlab.matrix.org/matrix-org/olm/blob/master/docs/olm.md) refers to +/// ratchet keys as `T``i`. #[derive(Serialize, Deserialize, Clone)] #[serde(transparent)] pub(super) struct RatchetKey(Curve25519SecretKey); +/// The public part of a [`RatchetKey`]. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct RatchetPublicKey(Curve25519PublicKey); +/// A ratchet key which was created by the other side. +/// +/// See [`RatchetKey`] for explanation about ratchet keys in general. Since this +/// is the other side's key, we have only the public part of the key. #[derive(Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, Decode)] #[serde(transparent)] pub struct RemoteRatchetKey(Curve25519PublicKey); @@ -93,6 +110,12 @@ impl From<&RatchetKey> for RatchetPublicKey { } } +/// Information about the root key ratchet, while our sender chain is active. +/// +/// We only have one of these while the double ratchet is "active" - ie, while +/// we are encrypting messages. It stores the information necessary to calculate +/// the *next* root key; in particular, the root key of our active chain +/// `R``i`, and our own ratchet key `T``i`. #[derive(Serialize, Deserialize, Clone)] pub(super) struct Ratchet { root_key: RootKey, diff --git a/src/olm/session/receiver_chain.rs b/src/olm/session/receiver_chain.rs index 96b2c1f4..f27a34b2 100644 --- a/src/olm/session/receiver_chain.rs +++ b/src/olm/session/receiver_chain.rs @@ -88,10 +88,28 @@ impl FoundMessageKey<'_> { } } +/// The receiver side data on a single chain in the double ratchet. +/// +/// Contains the data needed to decrypt a message sent to a given chain. #[derive(Serialize, Deserialize, Clone)] pub(super) struct ReceiverChain { + /// The sender's ratchet key `T``i` for this chain. + /// + /// We store this mostly so that we can identify which chain to use to + /// decrypt a given message. ratchet_key: RemoteRatchetKey, + + /// The chain key `C``i`,`j`. + /// + /// As we receive more messages on the chain, this is ratcheted forward, + /// producing a new chain key `C``i`,`j+1`, from which we can + /// derive a new message key `M``i`,`j+1`. hkdf_ratchet: RemoteChainKey, + + /// A stash of message keys which have not yet been used to decrypt a + /// message. + /// + /// This allows us to handle out-of-order messages. skipped_message_keys: MessageKeyStore, } diff --git a/src/olm/session/root_key.rs b/src/olm/session/root_key.rs index 859624ab..3c71d50c 100644 --- a/src/olm/session/root_key.rs +++ b/src/olm/session/root_key.rs @@ -24,6 +24,16 @@ use super::{ const ADVANCEMENT_SEED: &[u8; 11] = b"OLM_RATCHET"; +/// A root key for one of our own sender chains. +/// +/// A new root key `R``i` is calculated each time the conversation +/// changes direction, based on the previous root key `R``i-1` and +/// the previous and new [ratchet keys](RatchetKey) `T``i-1`, +/// `T``i`. It is used only to calculate the *next* root key +/// `R``i+1` and [chain key](ChainKey) `C``i+1`. +/// +/// This struct holds the root key corresponding to chains where we are the +/// sender. See also [`RemoteRootKey`]. #[derive(Serialize, Deserialize, Clone, Zeroize)] #[serde(transparent)] #[zeroize(drop)] @@ -31,6 +41,10 @@ pub(crate) struct RootKey { pub key: Box<[u8; 32]>, } +/// A root key for one of the other side's sender chains. +/// +/// See [`RootKey`] for information on root keys. This struct holds the root key +/// corresponding to chains where the other side is the sender. #[derive(Serialize, Deserialize, Clone, Zeroize)] #[zeroize(drop)] pub(crate) struct RemoteRootKey { From f75d8c99f5a754edd5b823a1b51a8eb1000c1747 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Tue, 19 Mar 2024 11:29:06 +0000 Subject: [PATCH 2/3] Keep track of parent ratchet key for active ratchet In order to diagnose issues with the DH ratchet, we could do with logging the *parent* ratchet of an active double ratchet. Part of https://github.com/matrix-org/vodozemac/issues/125 --- src/olm/session/double_ratchet.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/olm/session/double_ratchet.rs b/src/olm/session/double_ratchet.rs index 6061cb6f..c59f1c22 100644 --- a/src/olm/session/double_ratchet.rs +++ b/src/olm/session/double_ratchet.rs @@ -83,6 +83,7 @@ impl DoubleRatchet { let chain_key = ChainKey::new(chain_key); let ratchet = ActiveDoubleRatchet { + parent_ratchet_key: None, // First chain in a session lacks parent ratchet key active_ratchet: Ratchet::new(root_key), symmetric_key_ratchet: chain_key, }; @@ -94,6 +95,7 @@ impl DoubleRatchet { pub fn from_ratchet_and_chain_key(ratchet: Ratchet, chain_key: ChainKey) -> Self { Self { inner: ActiveDoubleRatchet { + parent_ratchet_key: None, // libolm pickle did not record parent ratchet key active_ratchet: ratchet, symmetric_key_ratchet: chain_key, } @@ -165,7 +167,11 @@ impl InactiveDoubleRatchet { let (root_key, chain_key, ratchet_key) = self.root_key.advance(&self.ratchet_key); let active_ratchet = Ratchet::new_with_ratchet_key(root_key, ratchet_key); - ActiveDoubleRatchet { active_ratchet, symmetric_key_ratchet: chain_key } + ActiveDoubleRatchet { + parent_ratchet_key: Some(self.ratchet_key), + active_ratchet, + symmetric_key_ratchet: chain_key, + } } } @@ -176,6 +182,22 @@ impl InactiveDoubleRatchet { /// See [`DoubleRatchet`] for more explanation. #[derive(Serialize, Deserialize, Clone)] struct ActiveDoubleRatchet { + /// The other side's most recent ratchet key, which was used to calculate + /// the root key in `active_ratchet` and the chain key in + /// `symmetric_key_ratchet`. + /// + /// If `active_ratchet` contains root key `R``i` and our own + /// ratchet key `T``i`, this is `T``i-1`. + /// + /// `None` means "unknown", either because this session has been restored + /// from a pickle which did not record the parent session key, or because + /// this is the first chain in the session. + /// + /// This is not required to implement the algorithm: it is maintained solely + /// for diagnostic output. + #[serde(default)] + parent_ratchet_key: Option, + active_ratchet: Ratchet, symmetric_key_ratchet: ChainKey, } From 0da438b6ec667ff737ff6a7736968727653f60b6 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Tue, 19 Mar 2024 11:35:31 +0000 Subject: [PATCH 3/3] `olm::Session.debug`: more details of sendinging ratchet Include the latest public ratchet keys in the debug output. --- src/olm/session/double_ratchet.rs | 39 +++++++++++++++++++++++++------ src/olm/session/mod.rs | 2 +- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/olm/session/double_ratchet.rs b/src/olm/session/double_ratchet.rs index c59f1c22..3d6e45fb 100644 --- a/src/olm/session/double_ratchet.rs +++ b/src/olm/session/double_ratchet.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::{Debug, Formatter}; + use serde::{Deserialize, Serialize}; use super::{ @@ -45,13 +47,6 @@ pub(super) struct DoubleRatchet { } impl DoubleRatchet { - pub fn chain_index(&self) -> Option { - match &self.inner { - DoubleRatchetState::Inactive(_) => None, - DoubleRatchetState::Active(r) => Some(r.symmetric_key_ratchet.index()), - } - } - pub fn next_message_key(&mut self) -> MessageKey { match &mut self.inner { DoubleRatchetState::Inactive(ratchet) => { @@ -132,6 +127,17 @@ impl DoubleRatchet { } } +impl Debug for DoubleRatchet { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut dbg = f.debug_tuple("DoubleRatchet"); + match &self.inner { + DoubleRatchetState::Inactive(r) => dbg.field(r), + DoubleRatchetState::Active(r) => dbg.field(r), + }; + dbg.finish() + } +} + #[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "snake_case")] #[serde(tag = "type")] @@ -175,6 +181,14 @@ impl InactiveDoubleRatchet { } } +impl Debug for InactiveDoubleRatchet { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("InactiveDoubleRatchet") + .field("ratchet_key", &self.ratchet_key) + .finish_non_exhaustive() + } +} + /// State of the sender-side ratchet while we are in "encryption" mode: we are /// encrypting our own messages and have not yet received any messages which /// were created since we started this chain. @@ -220,3 +234,14 @@ impl ActiveDoubleRatchet { self.symmetric_key_ratchet.create_message_key(self.ratchet_key()) } } + +impl Debug for ActiveDoubleRatchet { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let active_ratchet_public_key: RatchetPublicKey = self.active_ratchet.ratchet_key().into(); + f.debug_struct("ActiveDoubleRatchet") + .field("parent_ratchet_key", &self.parent_ratchet_key) + .field("ratchet_key", &active_ratchet_public_key) + .field("chain_index", &self.symmetric_key_ratchet.index()) + .finish_non_exhaustive() + } +} diff --git a/src/olm/session/mod.rs b/src/olm/session/mod.rs index 7125bc5d..6160b2ea 100644 --- a/src/olm/session/mod.rs +++ b/src/olm/session/mod.rs @@ -158,7 +158,7 @@ impl Debug for Session { f.debug_struct("Session") .field("session_id", &self.session_id()) - .field("sending_chain_index", &sending_ratchet.chain_index()) + .field("sending_ratchet", &sending_ratchet) .field("receiving_chains", &receiving_chains.inner) .field("config", config) .finish_non_exhaustive()