Skip to content

Commit

Permalink
Merge pull request #133 from matrix-org/rav/ratchet_key_logging
Browse files Browse the repository at this point in the history
Log ratchet keys in `olm::Session::debug`
  • Loading branch information
richvdh authored Mar 19, 2024
2 parents ba49d17 + 0da438b commit 7a1887f
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 9 deletions.
89 changes: 81 additions & 8 deletions src/olm/session/double_ratchet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -23,20 +25,28 @@ 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`<sub>`i`,`j`</sub> (so that we can
/// advance to the next one), and also the current root key `R`<sub>`i`</sub>
/// and our most recent ratchet key `T`<sub>`i`</sub> (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`<sub>`i`</sub> which was sent to us in the message, and the remote root
/// key `R`<sub>`i`</sub>.
#[derive(Serialize, Deserialize, Clone)]
#[serde(transparent)]
pub(super) struct DoubleRatchet {
inner: DoubleRatchetState,
}

impl DoubleRatchet {
pub fn chain_index(&self) -> Option<u64> {
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) => {
Expand All @@ -59,13 +69,16 @@ 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();

let root_key = RootKey::new(root_key);
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,
};
Expand All @@ -77,6 +90,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,
}
Expand Down Expand Up @@ -113,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")]
Expand All @@ -133,6 +158,10 @@ impl From<ActiveDoubleRatchet> 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,
Expand All @@ -144,12 +173,45 @@ 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,
}
}
}

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.
///
/// 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`<sub>`i`</sub> and our own
/// ratchet key `T`<sub>`i`</sub>, this is `T`<sub>`i-1`</sub>.
///
/// `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<RemoteRatchetKey>,

active_ratchet: Ratchet,
symmetric_key_ratchet: ChainKey,
}
Expand All @@ -172,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()
}
}
2 changes: 1 addition & 1 deletion src/olm/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
23 changes: 23 additions & 0 deletions src/olm/session/ratchet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`<sub>`i`</sub>.
#[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);
Expand Down Expand Up @@ -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`<sub>`i`</sub>, and our own ratchet key `T`<sub>`i`</sub>.
#[derive(Serialize, Deserialize, Clone)]
pub(super) struct Ratchet {
root_key: RootKey,
Expand Down
18 changes: 18 additions & 0 deletions src/olm/session/receiver_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`<sub>`i`</sub> 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`<sub>`i`,`j`</sub>.
///
/// As we receive more messages on the chain, this is ratcheted forward,
/// producing a new chain key `C`<sub>`i`,`j+1`</sub>, from which we can
/// derive a new message key `M`<sub>`i`,`j+1`</sub>.
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,
}

Expand Down
14 changes: 14 additions & 0 deletions src/olm/session/root_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,27 @@ 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`<sub>`i`</sub> is calculated each time the conversation
/// changes direction, based on the previous root key `R`<sub>`i-1`</sub> and
/// the previous and new [ratchet keys](RatchetKey) `T`<sub>`i-1`</sub>,
/// `T`<sub>`i`</sub>. It is used only to calculate the *next* root key
/// `R`<sub>`i+1`</sub> and [chain key](ChainKey) `C`<sub>`i+1`</sub>.
///
/// 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)]
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 {
Expand Down

0 comments on commit 7a1887f

Please sign in to comment.