1pub mod add_backup_keys;
4pub mod add_backup_keys_for_room;
5pub mod add_backup_keys_for_session;
6pub mod create_backup_version;
7pub mod delete_backup_keys;
8pub mod delete_backup_keys_for_room;
9pub mod delete_backup_keys_for_session;
10pub mod delete_backup_version;
11pub mod get_backup_info;
12pub mod get_backup_keys;
13pub mod get_backup_keys_for_room;
14pub mod get_backup_keys_for_session;
15pub mod get_latest_backup_info;
16pub mod update_backup_version;
17
18use std::{borrow::Cow, collections::BTreeMap};
19
20use js_int::UInt;
21use ruma_common::{
22 CrossSigningOrDeviceSignatures,
23 serde::{Base64, JsonObject, Raw, from_raw_json_value},
24};
25use serde::{Deserialize, Deserializer, Serialize};
26use serde_json::{Value as JsonValue, value::RawValue as RawJsonValue};
27
28#[derive(Clone, Debug, Serialize, Deserialize)]
30#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
31pub struct RoomKeyBackup {
32 pub sessions: BTreeMap<String, Raw<KeyBackupData>>,
34}
35
36impl RoomKeyBackup {
37 pub fn new(sessions: BTreeMap<String, Raw<KeyBackupData>>) -> Self {
39 Self { sessions }
40 }
41}
42
43#[derive(Clone, Debug, Serialize)]
45#[serde(tag = "algorithm", content = "auth_data")]
46#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
47pub enum BackupAlgorithm {
48 #[serde(rename = "m.megolm_backup.v1.curve25519-aes-sha2")]
50 MegolmBackupV1Curve25519AesSha2(MegolmBackupV1Curve25519AesSha2AuthData),
51
52 #[doc(hidden)]
53 #[serde(untagged)]
54 _Custom(CustomBackupAlgorithm),
55}
56
57impl BackupAlgorithm {
58 pub fn algorithm(&self) -> &str {
60 match self {
61 Self::MegolmBackupV1Curve25519AesSha2(_) => "m.megolm_backup.v1.curve25519-aes-sha2",
62 Self::_Custom(c) => &c.algorithm,
63 }
64 }
65
66 pub fn auth_data(&self) -> Cow<'_, JsonObject> {
71 fn serialize<T: Serialize>(obj: &T) -> JsonObject {
72 match serde_json::to_value(obj).expect("backup data serialization to succeed") {
73 JsonValue::Object(obj) => obj,
74 _ => panic!("all backup data types must serialize to objects"),
75 }
76 }
77
78 match self {
79 Self::MegolmBackupV1Curve25519AesSha2(d) => Cow::Owned(serialize(d)),
80 Self::_Custom(c) => Cow::Borrowed(&c.auth_data),
81 }
82 }
83}
84
85impl<'de> Deserialize<'de> for BackupAlgorithm {
86 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
87 where
88 D: Deserializer<'de>,
89 {
90 #[derive(Deserialize)]
91 struct BackupAlgorithmDeHelper {
92 algorithm: String,
93 auth_data: Box<RawJsonValue>,
94 }
95
96 let BackupAlgorithmDeHelper { algorithm, auth_data } =
97 BackupAlgorithmDeHelper::deserialize(deserializer)?;
98
99 Ok(match algorithm.as_ref() {
100 "m.megolm_backup.v1.curve25519-aes-sha2" => {
101 Self::MegolmBackupV1Curve25519AesSha2(from_raw_json_value(&auth_data)?)
102 }
103 _ => Self::_Custom(CustomBackupAlgorithm {
104 algorithm,
105 auth_data: from_raw_json_value(&auth_data)?,
106 }),
107 })
108 }
109}
110
111impl From<MegolmBackupV1Curve25519AesSha2AuthData> for BackupAlgorithm {
112 fn from(value: MegolmBackupV1Curve25519AesSha2AuthData) -> Self {
113 Self::MegolmBackupV1Curve25519AesSha2(value)
114 }
115}
116
117#[derive(Clone, Debug, Serialize, Deserialize)]
119#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
120pub struct MegolmBackupV1Curve25519AesSha2AuthData {
121 pub public_key: Base64,
123
124 pub signatures: CrossSigningOrDeviceSignatures,
126}
127
128impl MegolmBackupV1Curve25519AesSha2AuthData {
129 pub fn new(public_key: Base64) -> Self {
131 Self { public_key, signatures: Default::default() }
132 }
133}
134
135#[doc(hidden)]
137#[derive(Clone, Debug, Serialize)]
138pub struct CustomBackupAlgorithm {
139 algorithm: String,
141
142 auth_data: JsonObject,
144}
145
146#[derive(Clone, Debug, Serialize, Deserialize)]
151#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
152pub struct KeyBackupData {
153 pub first_message_index: UInt,
155
156 pub forwarded_count: UInt,
158
159 pub is_verified: bool,
161
162 pub session_data: EncryptedSessionData,
164}
165
166#[derive(Debug)]
171#[allow(clippy::exhaustive_structs)]
172pub struct KeyBackupDataInit {
173 pub first_message_index: UInt,
175
176 pub forwarded_count: UInt,
178
179 pub is_verified: bool,
181
182 pub session_data: EncryptedSessionData,
184}
185
186impl From<KeyBackupDataInit> for KeyBackupData {
187 fn from(init: KeyBackupDataInit) -> Self {
188 let KeyBackupDataInit { first_message_index, forwarded_count, is_verified, session_data } =
189 init;
190 Self { first_message_index, forwarded_count, is_verified, session_data }
191 }
192}
193
194#[derive(Clone, Debug, Serialize, Deserialize)]
199#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
200pub struct EncryptedSessionData {
201 pub ephemeral: Base64,
203
204 pub ciphertext: Base64,
206
207 pub mac: Base64,
209}
210
211#[derive(Debug)]
216#[allow(clippy::exhaustive_structs)]
217pub struct EncryptedSessionDataInit {
218 pub ephemeral: Base64,
220
221 pub ciphertext: Base64,
223
224 pub mac: Base64,
226}
227
228impl From<EncryptedSessionDataInit> for EncryptedSessionData {
229 fn from(init: EncryptedSessionDataInit) -> Self {
230 let EncryptedSessionDataInit { ephemeral, ciphertext, mac } = init;
231 Self { ephemeral, ciphertext, mac }
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use std::borrow::Cow;
238
239 use assert_matches2::{assert_let, assert_matches};
240 use ruma_common::{
241 SigningKeyAlgorithm, SigningKeyId, canonical_json::assert_to_canonical_json_eq,
242 owned_user_id, serde::Base64,
243 };
244 use serde_json::{Value as JsonValue, from_value as from_json_value, json};
245
246 use super::{BackupAlgorithm, MegolmBackupV1Curve25519AesSha2AuthData};
247
248 #[test]
249 fn megolm_v1_backup_algorithm_serialize_roundtrip() {
250 let json = json!({
251 "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
252 "auth_data": {
253 "public_key": "YWJjZGVm",
254 "signatures": {
255 "@alice:example.org": {
256 "ed25519:DEVICEID": "signature",
257 },
258 },
259 },
260 });
261
262 let mut backup_algorithm =
263 MegolmBackupV1Curve25519AesSha2AuthData::new(Base64::new(b"abcdef".to_vec()));
264 backup_algorithm.signatures.insert_signature(
265 owned_user_id!("@alice:example.org"),
266 SigningKeyId::from_parts(SigningKeyAlgorithm::Ed25519, "DEVICEID".into()),
267 "signature".to_owned(),
268 );
269 assert_to_canonical_json_eq!(BackupAlgorithm::from(backup_algorithm), json.clone());
270
271 assert_let!(
272 Ok(BackupAlgorithm::MegolmBackupV1Curve25519AesSha2(auth_data)) = from_json_value(json)
273 );
274 assert_eq!(auth_data.public_key.as_bytes(), b"abcdef");
275 let user_signatures =
276 auth_data.signatures.get(&owned_user_id!("@alice:example.org")).unwrap();
277
278 let mut user_signatures_iter = user_signatures.iter();
279 let (key_id, signature) = user_signatures_iter.next().unwrap();
280 assert_eq!(key_id, "ed25519:DEVICEID");
281 assert_eq!(signature, "signature");
282 assert_matches!(user_signatures_iter.next(), None);
283 }
284
285 #[test]
286 fn custom_backup_algorithm_serialize_roundtrip() {
287 let json = json!({
288 "algorithm": "local.dev.unknown_algorithm",
289 "auth_data": {
290 "foo": "bar",
291 "signatures": {
292 "ed25519:DEVICEID": "signature",
293 },
294 },
295 });
296
297 let backup_algorithm = from_json_value::<BackupAlgorithm>(json.clone()).unwrap();
298 assert_eq!(backup_algorithm.algorithm(), "local.dev.unknown_algorithm");
299 assert_let!(Cow::Borrowed(auth_data) = backup_algorithm.auth_data());
300
301 assert_let!(Some(JsonValue::String(foo)) = auth_data.get("foo"));
302 assert_eq!(foo, "bar");
303 assert_let!(Some(JsonValue::Object(signatures)) = auth_data.get("signatures"));
304 assert_let!(Some(JsonValue::String(signature)) = signatures.get("ed25519:DEVICEID"));
305 assert_eq!(signature, "signature");
306
307 assert_to_canonical_json_eq!(backup_algorithm, json);
308 }
309}