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    DeviceKeyAlgorithm, DeviceKeyId, OwnedDeviceId, OwnedDeviceKeyId, OwnedUserId,
26    serde::{JsonCastable, Raw},
27};
28use serde::{Deserialize, Serialize};
29use serde_json::{Value, value::to_raw_value};
30use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
31
32use super::{EventEncryptionAlgorithm, Signatures};
33use crate::{
34    SignatureError,
35    olm::{SignedJsonObject, VerifyJson},
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)
123            .and_then(|k| if let DeviceKey::Curve25519(k) = k { Some(*k) } else { None })
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)
129            .and_then(|k| if let DeviceKey::Ed25519(k) = k { Some(*k) } else { None })
130    }
131
132    /// Verify that the given object has been signed by the Ed25519 key in this
133    /// `DeviceKeys`.
134    pub fn has_signed(&self, signed_object: &impl SignedJsonObject) -> Result<(), SignatureError> {
135        let key = self.ed25519_key().ok_or(SignatureError::MissingSigningKey)?;
136        let key_id = &DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, &self.device_id);
137        key.verify_json(&self.user_id, key_id, signed_object)
138    }
139
140    /// Verify that this `DeviceKeys` structure contains a correct
141    /// self-signature.
142    pub fn check_self_signature(&self) -> Result<(), SignatureError> {
143        self.has_signed(self)
144    }
145}
146
147impl JsonCastable<DeviceKeys> for ruma::encryption::DeviceKeys {}
148
149/// Additional data added to device key information by intermediate servers.
150#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
151pub struct UnsignedDeviceInfo {
152    /// The display name which the user set on the device.
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub device_display_name: Option<String>,
155
156    #[serde(flatten)]
157    other: BTreeMap<String, Value>,
158}
159
160impl UnsignedDeviceInfo {
161    /// Creates an empty `UnsignedDeviceInfo`.
162    pub fn new() -> Self {
163        Default::default()
164    }
165
166    /// Checks whether all fields are empty / `None`.
167    pub fn is_empty(&self) -> bool {
168        self.device_display_name.is_none()
169    }
170}
171
172/// An enum over the different key types a device can have.
173///
174/// Currently devices have a curve25519 and ed25519 keypair. The keys transport
175/// format is a base64 encoded string, any unknown key type will be left as such
176/// a string.
177#[derive(Clone, Debug, PartialEq, Eq)]
178pub enum DeviceKey {
179    /// The curve25519 device key.
180    Curve25519(Curve25519PublicKey),
181    /// The ed25519 device key.
182    Ed25519(Ed25519PublicKey),
183    /// An unknown device key.
184    Unknown(String),
185}
186
187impl DeviceKey {
188    /// Convert the `DeviceKey` into a base64 encoded string.
189    pub fn to_base64(&self) -> String {
190        match self {
191            DeviceKey::Curve25519(k) => k.to_base64(),
192            DeviceKey::Ed25519(k) => k.to_base64(),
193            DeviceKey::Unknown(k) => k.to_owned(),
194        }
195    }
196}
197
198impl From<Curve25519PublicKey> for DeviceKey {
199    fn from(val: Curve25519PublicKey) -> Self {
200        DeviceKey::Curve25519(val)
201    }
202}
203
204impl From<Ed25519PublicKey> for DeviceKey {
205    fn from(val: Ed25519PublicKey) -> Self {
206        DeviceKey::Ed25519(val)
207    }
208}
209
210/// A de/serialization helper for [`DeviceKeys`] which maps the `keys` to/from
211/// [`DeviceKey`]s.
212#[derive(Clone, Debug, Deserialize, Serialize)]
213struct DeviceKeyHelper {
214    pub user_id: OwnedUserId,
215    pub device_id: OwnedDeviceId,
216    pub algorithms: Vec<EventEncryptionAlgorithm>,
217    pub keys: BTreeMap<OwnedDeviceKeyId, String>,
218    #[serde(default, skip_serializing_if = "JsOption::is_undefined")]
219    pub dehydrated: JsOption<bool>,
220    pub signatures: Signatures,
221    #[serde(default, skip_serializing_if = "UnsignedDeviceInfo::is_empty")]
222    pub unsigned: UnsignedDeviceInfo,
223    #[serde(flatten)]
224    other: BTreeMap<String, Value>,
225}
226
227impl TryFrom<DeviceKeyHelper> for DeviceKeys {
228    type Error = vodozemac::KeyError;
229
230    fn try_from(value: DeviceKeyHelper) -> Result<Self, Self::Error> {
231        let keys: Result<BTreeMap<OwnedDeviceKeyId, DeviceKey>, vodozemac::KeyError> = value
232            .keys
233            .into_iter()
234            .map(|(k, v)| {
235                let key = match k.algorithm() {
236                    DeviceKeyAlgorithm::Ed25519 => {
237                        DeviceKey::Ed25519(Ed25519PublicKey::from_base64(&v)?)
238                    }
239                    DeviceKeyAlgorithm::Curve25519 => {
240                        DeviceKey::Curve25519(Curve25519PublicKey::from_base64(&v)?)
241                    }
242                    _ => DeviceKey::Unknown(v),
243                };
244
245                Ok((k, key))
246            })
247            .collect();
248
249        Ok(Self {
250            user_id: value.user_id,
251            device_id: value.device_id,
252            algorithms: value.algorithms,
253            keys: keys?,
254            dehydrated: value.dehydrated,
255            signatures: value.signatures,
256            unsigned: value.unsigned,
257            other: value.other,
258        })
259    }
260}
261
262impl From<DeviceKeys> for DeviceKeyHelper {
263    fn from(value: DeviceKeys) -> Self {
264        let keys: BTreeMap<OwnedDeviceKeyId, String> =
265            value.keys.into_iter().map(|(k, v)| (k, v.to_base64())).collect();
266
267        Self {
268            user_id: value.user_id,
269            device_id: value.device_id,
270            algorithms: value.algorithms,
271            keys,
272            dehydrated: value.dehydrated,
273            signatures: value.signatures,
274            unsigned: value.unsigned,
275            other: value.other,
276        }
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use std::str::FromStr;
283
284    use ruma::{OwnedDeviceKeyId, device_id, user_id};
285    use serde_json::json;
286    use vodozemac::{Curve25519PublicKey, Curve25519SecretKey};
287
288    use super::DeviceKeys;
289
290    #[test]
291    fn serialization() {
292        let json = json!({
293          "algorithms": vec![
294              "m.olm.v1.curve25519-aes-sha2",
295              "m.megolm.v1.aes-sha2"
296          ],
297          "device_id": "BNYQQWUMXO",
298          "user_id": "@example:localhost",
299          "keys": {
300              "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
301              "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
302          },
303          "signatures": {
304              "@example:localhost": {
305                  "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
306              }
307          },
308          "unsigned": {
309              "device_display_name": "Alice's mobile phone",
310              "other_data": "other_value"
311          },
312
313          "other_data": "other_value"
314        });
315
316        let device_keys: DeviceKeys =
317            serde_json::from_value(json.clone()).expect("Can't deserialize device keys");
318
319        assert_eq!(device_keys.user_id, user_id!("@example:localhost"));
320        assert_eq!(&device_keys.device_id, device_id!("BNYQQWUMXO"));
321
322        let serialized = serde_json::to_value(device_keys).expect("Can't reserialize device keys");
323
324        assert_eq!(json, serialized);
325    }
326
327    #[test]
328    fn test_check_self_signature() {
329        // A correctly-signed set of keys
330        let mut device_keys: DeviceKeys = serde_json::from_value(json!({
331          "algorithms": vec![
332              "m.olm.v1.curve25519-aes-sha2",
333              "m.megolm.v1.aes-sha2"
334          ],
335          "device_id": "BNYQQWUMXO",
336          "user_id": "@example:localhost",
337          "keys": {
338              "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
339              "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
340          },
341          "signatures": {
342              "@example:localhost": {
343                  "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
344              }
345          },
346          "unsigned": {
347              "device_display_name": "Alice's mobile phone"
348          }
349        })).expect("Can't deserialize device keys");
350
351        device_keys.check_self_signature().expect("Self-signature check failed");
352
353        // Change one of the fields and verify that the signature check fails.
354        let new_curve_key = Curve25519SecretKey::new();
355        let key_id = OwnedDeviceKeyId::from_str("curve25519:BNYQQWUMXO").unwrap();
356        device_keys.keys.insert(key_id, Curve25519PublicKey::from(&new_curve_key).into());
357
358        assert!(device_keys.check_self_signature().is_err());
359    }
360}