matrix_sdk_crypto/types/
room_history.rs

1/*
2Copyright 2025 The Matrix.org Foundation C.I.C.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17//! Types for sharing encrypted room history, per [MSC4268].
18//!
19//! [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
20
21use std::fmt::Debug;
22
23use ruma::{DeviceKeyAlgorithm, OwnedRoomId};
24use serde::{Deserialize, Serialize};
25use vodozemac::{megolm::ExportedSessionKey, Curve25519PublicKey};
26
27use super::RoomKeyExport;
28use crate::{
29    olm::ExportedRoomKey,
30    types::{
31        deserialize_curve_key, events::room_key_withheld::RoomKeyWithheldContent,
32        serialize_curve_key, EventEncryptionAlgorithm, SigningKeys,
33    },
34};
35#[cfg(doc)]
36use crate::{olm::InboundGroupSession, types::events::room_key::RoomKeyContent};
37
38/// A bundle of historic room keys, for sharing encrypted room history, per
39/// [MSC4268].
40///
41/// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
42#[derive(Deserialize, Serialize, Debug, Default)]
43pub struct RoomKeyBundle {
44    /// Keys that we are sharing with the recipient.
45    pub room_keys: Vec<HistoricRoomKey>,
46
47    /// Keys that we are *not* sharing with the recipient.
48    pub withheld: Vec<RoomKeyWithheldContent>,
49}
50
51impl RoomKeyBundle {
52    /// Returns true if there is nothing of value in this bundle.
53    pub fn is_empty(&self) -> bool {
54        self.room_keys.is_empty() && self.withheld.is_empty()
55    }
56}
57
58/// An [`InboundGroupSession`] for sharing as part of a [`RoomKeyBundle`].
59///
60/// Note: unlike a room key received via an `m.room_key` message (i.e., a
61/// [`RoomKeyContent`]), we have no direct proof that the original sender
62/// actually created this session; rather, we have to take the word of
63/// whoever sent us this key bundle.
64#[derive(Deserialize, Serialize)]
65pub struct HistoricRoomKey {
66    /// The encryption algorithm that the session uses.
67    pub algorithm: EventEncryptionAlgorithm,
68
69    /// The room where the session is used.
70    pub room_id: OwnedRoomId,
71
72    /// The Curve25519 key of the device which initiated the session originally,
73    /// according to the device that sent us this key.
74    #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
75    pub sender_key: Curve25519PublicKey,
76
77    /// The ID of the session that the key is for.
78    pub session_id: String,
79
80    /// The key for the session.
81    pub session_key: ExportedSessionKey,
82
83    /// The Ed25519 key of the device which initiated the session originally,
84    /// according to the device that sent us this key.
85    #[serde(default)]
86    pub sender_claimed_keys: SigningKeys<DeviceKeyAlgorithm>,
87}
88
89impl Debug for HistoricRoomKey {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        f.debug_struct("SharedRoomKey")
92            .field("algorithm", &self.algorithm)
93            .field("room_id", &self.room_id)
94            .field("sender_key", &self.sender_key)
95            .field("session_id", &self.session_id)
96            .field("sender_claimed_keys", &self.sender_claimed_keys)
97            .finish_non_exhaustive()
98    }
99}
100
101impl RoomKeyExport for &HistoricRoomKey {
102    fn room_id(&self) -> &ruma::RoomId {
103        &self.room_id
104    }
105
106    fn session_id(&self) -> &str {
107        &self.session_id
108    }
109
110    fn sender_key(&self) -> Curve25519PublicKey {
111        self.sender_key
112    }
113}
114
115impl From<ExportedRoomKey> for HistoricRoomKey {
116    fn from(exported_room_key: ExportedRoomKey) -> Self {
117        let ExportedRoomKey {
118            algorithm,
119            room_id,
120            sender_key,
121            session_id,
122            session_key,
123            sender_claimed_keys,
124            shared_history: _,
125            forwarding_curve25519_key_chain: _,
126        } = exported_room_key;
127        HistoricRoomKey {
128            algorithm,
129            room_id,
130            sender_key,
131            session_id,
132            session_key,
133            sender_claimed_keys,
134        }
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use insta::assert_debug_snapshot;
141    use ruma::{owned_room_id, DeviceKeyAlgorithm};
142    use vodozemac::{
143        megolm::ExportedSessionKey, Curve25519PublicKey, Curve25519SecretKey, Ed25519SecretKey,
144    };
145
146    use crate::types::{room_history::HistoricRoomKey, EventEncryptionAlgorithm};
147
148    #[test]
149    fn test_historic_room_key_debug() {
150        let key = HistoricRoomKey {
151            algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
152            room_id: owned_room_id!("!room:id"),
153            sender_key: Curve25519PublicKey::from(&Curve25519SecretKey::from_slice(b"abcdabcdabcdabcdabcdabcdabcdabcd")),
154            session_id: "id1234".to_owned(),
155            session_key: ExportedSessionKey::from_base64("AQAAAAC2XHVzsMBKs4QCRElJ92CJKyGtknCSC8HY7cQ7UYwndMKLQAejXLh5UA0l6s736mgctcUMNvELScUWrObdflrHo+vth/gWreXOaCnaSxmyjjKErQwyIYTkUfqbHy40RJfEesLwnN23on9XAkch/iy8R2+Jz7B8zfG01f2Ow2SxPQFnAndcO1ZSD2GmXgedy6n4B20MWI1jGP2wiexOWbFS").unwrap(),
156            sender_claimed_keys: vec![(DeviceKeyAlgorithm::Ed25519, Ed25519SecretKey::from_slice(b"abcdabcdabcdabcdabcdabcdabcdabcd").public_key().into())].into_iter().collect(),
157        };
158
159        assert_debug_snapshot!(key);
160    }
161}