matrix_sdk_crypto/types/
backup.rs

1// Copyright 2022 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::BTreeMap;
16
17use ruma::serde::JsonCastable;
18use serde::{Deserialize, Serialize};
19use serde_json::Value;
20use vodozemac::Curve25519PublicKey;
21
22use super::{deserialize_curve_key, serialize_curve_key, Signatures};
23
24/// Auth data for the `m.megolm_backup.v1.curve25519-aes-sha2` backup algorithm
25/// as defined in the [spec].
26///
27/// [spec]: https://spec.matrix.org/unstable/client-server-api/#backup-algorithm-mmegolm_backupv1curve25519-aes-sha2
28#[derive(Clone, Debug, Serialize, Deserialize)]
29pub struct MegolmV1AuthData {
30    ///The Curve25519 public key used to encrypt the backups.
31    #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
32    pub public_key: Curve25519PublicKey,
33    /// *Optional.* Signatures of the auth_data, as Signed JSON.
34    #[serde(default)]
35    pub signatures: Signatures,
36    #[serde(flatten)]
37    extra: BTreeMap<String, Value>,
38}
39
40impl MegolmV1AuthData {
41    // Create a new [`MegolmV1AuthData`] from a public Curve25519 key and a
42    // [`Signatures`] map.
43    pub(crate) fn new(public_key: Curve25519PublicKey, signatures: Signatures) -> Self {
44        Self { public_key, signatures, extra: Default::default() }
45    }
46}
47
48/// Information pertaining to a room key backup. Can be used to upload a new
49/// backup version as defined in the [spec].
50///
51/// [spec]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3room_keysversion
52#[derive(Clone, Debug, Deserialize)]
53#[serde(try_from = "BackupInfoHelper")]
54pub enum RoomKeyBackupInfo {
55    /// The `m.megolm_backup.v1.curve25519-aes-sha2` variant of a backup.
56    MegolmBackupV1Curve25519AesSha2(MegolmV1AuthData),
57    /// Any other unknown backup variant.
58    Other {
59        /// The algorithm of the unknown backup variant.
60        algorithm: String,
61        /// The auth data of the unknown backup variant.
62        auth_data: BTreeMap<String, Value>,
63    },
64}
65
66impl JsonCastable<ruma::api::client::backup::BackupAlgorithm> for RoomKeyBackupInfo {}
67
68impl JsonCastable<RoomKeyBackupInfo> for ruma::api::client::backup::BackupAlgorithm {}
69
70#[derive(Clone, Debug, Serialize, Deserialize)]
71struct BackupInfoHelper {
72    algorithm: String,
73    auth_data: Value,
74}
75
76impl TryFrom<BackupInfoHelper> for RoomKeyBackupInfo {
77    type Error = serde_json::Error;
78
79    fn try_from(value: BackupInfoHelper) -> Result<Self, Self::Error> {
80        Ok(match value.algorithm.as_str() {
81            "m.megolm_backup.v1.curve25519-aes-sha2" => {
82                let data: MegolmV1AuthData = serde_json::from_value(value.auth_data)?;
83                RoomKeyBackupInfo::MegolmBackupV1Curve25519AesSha2(data)
84            }
85            _ => RoomKeyBackupInfo::Other {
86                algorithm: value.algorithm,
87                auth_data: serde_json::from_value(value.auth_data)?,
88            },
89        })
90    }
91}
92
93impl Serialize for RoomKeyBackupInfo {
94    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
95    where
96        S: serde::Serializer,
97    {
98        let helper = match self {
99            RoomKeyBackupInfo::MegolmBackupV1Curve25519AesSha2(d) => BackupInfoHelper {
100                algorithm: "m.megolm_backup.v1.curve25519-aes-sha2".to_owned(),
101                auth_data: serde_json::to_value(d).map_err(serde::ser::Error::custom)?,
102            },
103            RoomKeyBackupInfo::Other { algorithm, auth_data } => BackupInfoHelper {
104                algorithm: algorithm.to_owned(),
105                auth_data: serde_json::to_value(auth_data.clone())
106                    .map_err(serde::ser::Error::custom)?,
107            },
108        };
109
110        helper.serialize(serializer)
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use std::collections::BTreeMap;
117
118    use assert_matches::assert_matches;
119    use insta::{assert_json_snapshot, with_settings};
120    use ruma::{user_id, DeviceKeyAlgorithm, KeyId};
121    use serde_json::{json, Value};
122    use vodozemac::{Curve25519PublicKey, Ed25519Signature};
123
124    use super::RoomKeyBackupInfo;
125    use crate::types::{MegolmV1AuthData, Signature, Signatures};
126
127    #[test]
128    fn serialization() {
129        let json = json!({
130            "algorithm": "m.megolm_backup.v2",
131            "auth_data": {
132                "some": "data"
133            }
134        });
135
136        let deserialized: RoomKeyBackupInfo = serde_json::from_value(json.clone()).unwrap();
137        assert_matches!(deserialized, RoomKeyBackupInfo::Other { algorithm: _, auth_data: _ });
138
139        let serialized = serde_json::to_value(deserialized).unwrap();
140        assert_eq!(json, serialized);
141
142        let json = json!({
143            "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
144            "auth_data": {
145                "public_key":"XjhWTCjW7l59pbfx9tlCBQolfnIQWARoKOzjTOPSlWM",
146                "signatures": {
147                    "@alice:example.org": {
148                        "ed25519:deviceid": "signature"
149                    }
150                }
151            }
152        });
153
154        let deserialized: RoomKeyBackupInfo = serde_json::from_value(json.clone()).unwrap();
155        assert_matches!(deserialized, RoomKeyBackupInfo::MegolmBackupV1Curve25519AesSha2(_));
156
157        let serialized = serde_json::to_value(deserialized).unwrap();
158        assert_eq!(json, serialized);
159    }
160
161    #[test]
162    fn snapshot_room_key_backup_info() {
163        let info = RoomKeyBackupInfo::MegolmBackupV1Curve25519AesSha2(MegolmV1AuthData {
164            public_key: Curve25519PublicKey::from_bytes([2u8; 32]),
165            signatures: Signatures(BTreeMap::from([(
166                user_id!("@alice:localhost").to_owned(),
167                BTreeMap::from([(
168                    KeyId::from_parts(DeviceKeyAlgorithm::Ed25519, "ABCDEFG".into()),
169                    Ok(Signature::from(Ed25519Signature::from_slice(&[0u8; 64]).unwrap())),
170                )]),
171            )])),
172            extra: BTreeMap::from([("foo".to_owned(), Value::from("bar"))]),
173        });
174
175        with_settings!({sort_maps =>true}, {
176            assert_json_snapshot!(info)
177        });
178
179        let info = RoomKeyBackupInfo::Other {
180            algorithm: "caesar.cipher".to_owned(),
181            auth_data: BTreeMap::from([("foo".to_owned(), Value::from("bar"))]),
182        };
183
184        with_settings!({sort_maps =>true}, {
185            assert_json_snapshot!(info);
186        })
187    }
188}