matrix_sdk_crypto/types/
device_keys.rs

1// Copyright 2020 Karl Linderhed.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21use std::collections::BTreeMap;
22
23use js_option::JsOption;
24use ruma::{
25    serde::{JsonCastable, Raw},
26    DeviceKeyAlgorithm, DeviceKeyId, OwnedDeviceId, OwnedDeviceKeyId, OwnedUserId,
27};
28use serde::{Deserialize, Serialize};
29use serde_json::{value::to_raw_value, Value};
30use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
31
32use super::{EventEncryptionAlgorithm, Signatures};
33
34/// Represents a Matrix cryptographic device
35///
36/// This struct models a Matrix cryptographic device involved in end-to-end
37/// encrypted messaging, specifically for to-device communication. It aligns
38/// with the [`DeviceKeys` struct][device_keys_spec] in the Matrix
39/// Specification, encapsulating essential elements such as the public device
40/// identity keys.
41///
42/// See also [`ruma::encryption::DeviceKeys`] which is similar, but slightly
43/// less comprehensive (it lacks some fields, and  the `keys` are represented as
44/// base64 strings rather than type-safe [`DeviceKey`]s). We always use this
45/// struct to build `/keys/upload` requests and to deserialize `/keys/query`
46/// responses.
47///
48/// [device_keys_spec]: https://spec.matrix.org/v1.10/client-server-api/#_matrixclientv3keysupload_devicekeys
49#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
50#[serde(try_from = "DeviceKeyHelper", into = "DeviceKeyHelper")]
51pub struct DeviceKeys {
52    /// The ID of the user the device belongs to.
53    ///
54    /// Must match the user ID used when logging in.
55    pub user_id: OwnedUserId,
56
57    /// The ID of the device these keys belong to.
58    ///
59    /// Must match the device ID used when logging in.
60    pub device_id: OwnedDeviceId,
61
62    /// The encryption algorithms supported by this device.
63    pub algorithms: Vec<EventEncryptionAlgorithm>,
64
65    /// Public identity keys.
66    pub keys: BTreeMap<OwnedDeviceKeyId, DeviceKey>,
67
68    /// Signatures for the device key object.
69    pub signatures: Signatures,
70
71    /// Whether the device is a dehydrated device or not
72    #[serde(default, skip_serializing_if = "JsOption::is_undefined")]
73    pub dehydrated: JsOption<bool>,
74
75    /// Additional data added to the device key information by intermediate
76    /// servers, and not covered by the signatures.
77    #[serde(default, skip_serializing_if = "UnsignedDeviceInfo::is_empty")]
78    pub unsigned: UnsignedDeviceInfo,
79
80    #[serde(flatten)]
81    other: BTreeMap<String, Value>,
82}
83
84impl DeviceKeys {
85    /// Creates a new `DeviceKeys` from the given user id, device ID,
86    /// algorithms, keys and signatures.
87    pub fn new(
88        user_id: OwnedUserId,
89        device_id: OwnedDeviceId,
90        algorithms: Vec<EventEncryptionAlgorithm>,
91        keys: BTreeMap<OwnedDeviceKeyId, DeviceKey>,
92        signatures: Signatures,
93    ) -> Self {
94        Self {
95            user_id,
96            device_id,
97            algorithms,
98            keys,
99            signatures,
100            dehydrated: JsOption::Undefined,
101            unsigned: Default::default(),
102            other: BTreeMap::new(),
103        }
104    }
105
106    /// Serialize the device keys key into a Raw version.
107    pub fn to_raw<T>(&self) -> Raw<T> {
108        Raw::from_json(to_raw_value(&self).expect("Couldn't serialize device keys"))
109    }
110
111    /// Get the key of the given key algorithm belonging to this device.
112    pub fn get_key(&self, algorithm: DeviceKeyAlgorithm) -> Option<&DeviceKey> {
113        self.keys.get(&DeviceKeyId::from_parts(algorithm, &self.device_id))
114    }
115
116    /// Get the Curve25519 key of the given device.
117    pub fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
118        self.get_key(DeviceKeyAlgorithm::Curve25519).and_then(|k| {
119            if let DeviceKey::Curve25519(k) = k {
120                Some(*k)
121            } else {
122                None
123            }
124        })
125    }
126
127    /// Get the Ed25519 key of the given device.
128    pub fn ed25519_key(&self) -> Option<Ed25519PublicKey> {
129        self.get_key(DeviceKeyAlgorithm::Ed25519).and_then(|k| {
130            if let DeviceKey::Ed25519(k) = k {
131                Some(*k)
132            } else {
133                None
134            }
135        })
136    }
137}
138
139impl JsonCastable<DeviceKeys> for ruma::encryption::DeviceKeys {}
140
141/// Additional data added to device key information by intermediate servers.
142#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
143pub struct UnsignedDeviceInfo {
144    /// The display name which the user set on the device.
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub device_display_name: Option<String>,
147
148    #[serde(flatten)]
149    other: BTreeMap<String, Value>,
150}
151
152impl UnsignedDeviceInfo {
153    /// Creates an empty `UnsignedDeviceInfo`.
154    pub fn new() -> Self {
155        Default::default()
156    }
157
158    /// Checks whether all fields are empty / `None`.
159    pub fn is_empty(&self) -> bool {
160        self.device_display_name.is_none()
161    }
162}
163
164/// An enum over the different key types a device can have.
165///
166/// Currently devices have a curve25519 and ed25519 keypair. The keys transport
167/// format is a base64 encoded string, any unknown key type will be left as such
168/// a string.
169#[derive(Clone, Debug, PartialEq, Eq)]
170pub enum DeviceKey {
171    /// The curve25519 device key.
172    Curve25519(Curve25519PublicKey),
173    /// The ed25519 device key.
174    Ed25519(Ed25519PublicKey),
175    /// An unknown device key.
176    Unknown(String),
177}
178
179impl DeviceKey {
180    /// Convert the `DeviceKey` into a base64 encoded string.
181    pub fn to_base64(&self) -> String {
182        match self {
183            DeviceKey::Curve25519(k) => k.to_base64(),
184            DeviceKey::Ed25519(k) => k.to_base64(),
185            DeviceKey::Unknown(k) => k.to_owned(),
186        }
187    }
188}
189
190impl From<Curve25519PublicKey> for DeviceKey {
191    fn from(val: Curve25519PublicKey) -> Self {
192        DeviceKey::Curve25519(val)
193    }
194}
195
196impl From<Ed25519PublicKey> for DeviceKey {
197    fn from(val: Ed25519PublicKey) -> Self {
198        DeviceKey::Ed25519(val)
199    }
200}
201
202/// A de/serialization helper for [`DeviceKeys`] which maps the `keys` to/from
203/// [`DeviceKey`]s.
204#[derive(Clone, Debug, Deserialize, Serialize)]
205struct DeviceKeyHelper {
206    pub user_id: OwnedUserId,
207    pub device_id: OwnedDeviceId,
208    pub algorithms: Vec<EventEncryptionAlgorithm>,
209    pub keys: BTreeMap<OwnedDeviceKeyId, String>,
210    #[serde(default, skip_serializing_if = "JsOption::is_undefined")]
211    pub dehydrated: JsOption<bool>,
212    pub signatures: Signatures,
213    #[serde(default, skip_serializing_if = "UnsignedDeviceInfo::is_empty")]
214    pub unsigned: UnsignedDeviceInfo,
215    #[serde(flatten)]
216    other: BTreeMap<String, Value>,
217}
218
219impl TryFrom<DeviceKeyHelper> for DeviceKeys {
220    type Error = vodozemac::KeyError;
221
222    fn try_from(value: DeviceKeyHelper) -> Result<Self, Self::Error> {
223        let keys: Result<BTreeMap<OwnedDeviceKeyId, DeviceKey>, vodozemac::KeyError> = value
224            .keys
225            .into_iter()
226            .map(|(k, v)| {
227                let key = match k.algorithm() {
228                    DeviceKeyAlgorithm::Ed25519 => {
229                        DeviceKey::Ed25519(Ed25519PublicKey::from_base64(&v)?)
230                    }
231                    DeviceKeyAlgorithm::Curve25519 => {
232                        DeviceKey::Curve25519(Curve25519PublicKey::from_base64(&v)?)
233                    }
234                    _ => DeviceKey::Unknown(v),
235                };
236
237                Ok((k, key))
238            })
239            .collect();
240
241        Ok(Self {
242            user_id: value.user_id,
243            device_id: value.device_id,
244            algorithms: value.algorithms,
245            keys: keys?,
246            dehydrated: value.dehydrated,
247            signatures: value.signatures,
248            unsigned: value.unsigned,
249            other: value.other,
250        })
251    }
252}
253
254impl From<DeviceKeys> for DeviceKeyHelper {
255    fn from(value: DeviceKeys) -> Self {
256        let keys: BTreeMap<OwnedDeviceKeyId, String> =
257            value.keys.into_iter().map(|(k, v)| (k, v.to_base64())).collect();
258
259        Self {
260            user_id: value.user_id,
261            device_id: value.device_id,
262            algorithms: value.algorithms,
263            keys,
264            dehydrated: value.dehydrated,
265            signatures: value.signatures,
266            unsigned: value.unsigned,
267            other: value.other,
268        }
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use ruma::{device_id, user_id};
275    use serde_json::json;
276
277    use super::DeviceKeys;
278
279    #[test]
280    fn serialization() {
281        let json = json!({
282          "algorithms": vec![
283              "m.olm.v1.curve25519-aes-sha2",
284              "m.megolm.v1.aes-sha2"
285          ],
286          "device_id": "BNYQQWUMXO",
287          "user_id": "@example:localhost",
288          "keys": {
289              "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
290              "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
291          },
292          "signatures": {
293              "@example:localhost": {
294                  "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
295              }
296          },
297          "unsigned": {
298              "device_display_name": "Alice's mobile phone",
299              "other_data": "other_value"
300          },
301
302          "other_data": "other_value"
303        });
304
305        let device_keys: DeviceKeys =
306            serde_json::from_value(json.clone()).expect("Can't deserialize device keys");
307
308        assert_eq!(device_keys.user_id, user_id!("@example:localhost"));
309        assert_eq!(&device_keys.device_id, device_id!("BNYQQWUMXO"));
310
311        let serialized = serde_json::to_value(device_keys).expect("Can't reserialize device keys");
312
313        assert_eq!(json, serialized);
314    }
315}