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