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