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