matrix_sdk_test/test_json/
keys_query_sets.rs

1use std::{collections::BTreeMap, default::Default};
2
3use insta::{assert_json_snapshot, with_settings};
4use ruma::{
5    api::client::keys::get_keys::v3::Response as KeyQueryResponse,
6    device_id,
7    encryption::{CrossSigningKey, DeviceKeys, KeyUsage},
8    serde::Raw,
9    user_id, CanonicalJsonValue, CrossSigningKeyId, CrossSigningOrDeviceSignatures,
10    CrossSigningOrDeviceSigningKeyId, DeviceId, OwnedBase64PublicKey,
11    OwnedBase64PublicKeyOrDeviceId, OwnedDeviceId, OwnedUserId, SigningKeyAlgorithm, UserId,
12};
13use serde_json::{json, Value};
14use vodozemac::{Curve25519PublicKey, Ed25519PublicKey, Ed25519SecretKey, Ed25519Signature};
15
16use super::keys_query::{keys_query, master_keys, KeysQueryUser};
17use crate::{
18    ruma_response_from_json, ruma_response_to_json,
19    test_json::keys_query::{device_keys_payload, self_signing_keys},
20};
21
22/// A test helper for building test data sets for `/keys/query` response objects
23/// ([`KeyQueryResponse`]).
24///
25/// # Examples
26///
27/// A simple case with no cross-signing identity and a single device:
28///
29/// ```
30/// # use ruma::{device_id, owned_user_id};
31/// # use vodozemac::{Curve25519PublicKey, Ed25519SecretKey};
32/// # use matrix_sdk_test::test_json::keys_query_sets::{KeyQueryResponseTemplate, KeyQueryResponseTemplateDeviceOptions};
33///
34/// // Note that (almost) any 32-byte sequence can be used as a private Ed25519 or Curve25519 key.
35/// // You can also use an arbitrary 32-byte sequence as a *public* key though of course you will
36/// // not know the private key it corresponds to (if indeed there is one).
37///
38/// let template = KeyQueryResponseTemplate::new(owned_user_id!("@alice:localhost"))
39///     .with_device(
40///         device_id!("TESTDEVICE"),
41///         &Curve25519PublicKey::from(b"curvepubcurvepubcurvepubcurvepub".to_owned()),
42///         &Ed25519SecretKey::from_slice(b"device12device12device12device12"),
43///         KeyQueryResponseTemplateDeviceOptions::new(),
44///     );
45///
46/// let response = template.build_response();
47/// ```
48///
49/// A more complex case, with cross-signing keys and a signed device:
50///
51/// ```
52/// # use ruma::{device_id, owned_user_id, user_id};
53/// # use vodozemac::{Curve25519PublicKey, Ed25519SecretKey};
54/// # use matrix_sdk_test::test_json::keys_query_sets::{KeyQueryResponseTemplate, KeyQueryResponseTemplateDeviceOptions};
55///
56/// let template = KeyQueryResponseTemplate::new(owned_user_id!("@me:localhost"))
57///     // add cross-signing keys
58///     .with_cross_signing_keys(
59///         Ed25519SecretKey::from_slice(b"master12master12master12master12"),
60///         Ed25519SecretKey::from_slice(b"self1234self1234self1234self1234"),
61///         Ed25519SecretKey::from_slice(b"user1234user1234user1234user1234"),
62///     )
63///     // add verification from another user
64///     .with_user_verification_signature(
65///         user_id!("@them:localhost"),
66///         &Ed25519SecretKey::from_slice(b"otheruser12otheruser12otheruser1"),
67///     )
68///     // add signed device
69///     .with_device(
70///         device_id!("SECUREDEVICE"),
71///         &Curve25519PublicKey::from(b"curvepubcurvepubcurvepubcurvepub".to_owned()),
72///         &Ed25519SecretKey::from_slice(b"device12device12device12device12"),
73///         KeyQueryResponseTemplateDeviceOptions::new().verified(true),
74///     );
75///
76/// let response = template.build_response();
77/// ```
78pub struct KeyQueryResponseTemplate {
79    /// The User ID of the user that this test data is about.
80    user_id: OwnedUserId,
81
82    /// The user's private master cross-signing key, once it has been set via
83    /// [`KeyQueryResponseTemplate::with_cross_signing_keys`].
84    master_cross_signing_key: Option<Ed25519SecretKey>,
85
86    /// The user's private self-signing key, once it has been set via
87    /// [`KeyQueryResponseTemplate::with_cross_signing_keys`].
88    self_signing_key: Option<Ed25519SecretKey>,
89
90    /// The user's private user-signing key, once it has been set via
91    /// [`KeyQueryResponseTemplate::with_cross_signing_keys`].
92    user_signing_key: Option<Ed25519SecretKey>,
93
94    /// The structured representation of the user's public master cross-signing
95    /// key, ready for return in the `/keys/query` response.
96    ///
97    /// This starts off as `None`, but is populated with the correct
98    /// object when the master key is set. It accumulates additional
99    /// signatures when the key is cross-signed
100    /// via [`KeyQueryResponseTemplate::with_user_verification_signature`].
101    master_cross_signing_key_json: Option<CrossSigningKey>,
102
103    /// The JSON object containing the public, signed, device keys, added via
104    /// [`KeyQueryResponseTemplate::with_device`].
105    device_keys: BTreeMap<OwnedDeviceId, Raw<DeviceKeys>>,
106}
107
108impl KeyQueryResponseTemplate {
109    /// Create a new [`KeyQueryResponseTemplate`] for the given user.
110    pub fn new(user_id: OwnedUserId) -> Self {
111        KeyQueryResponseTemplate {
112            user_id,
113            master_cross_signing_key: None,
114            self_signing_key: None,
115            user_signing_key: None,
116            master_cross_signing_key_json: None,
117            device_keys: Default::default(),
118        }
119    }
120
121    /// Add a set of cross-signing keys to the data to be returned.
122    ///
123    /// The private keys must be provided here so that signatures can be
124    /// correctly calculated.
125    pub fn with_cross_signing_keys(
126        mut self,
127        master_cross_signing_key: Ed25519SecretKey,
128        self_signing_key: Ed25519SecretKey,
129        user_signing_key: Ed25519SecretKey,
130    ) -> Self {
131        let master_public_key = master_cross_signing_key.public_key();
132        self.master_cross_signing_key = Some(master_cross_signing_key);
133        self.self_signing_key = Some(self_signing_key);
134        self.user_signing_key = Some(user_signing_key);
135
136        // For the master key, we build the CrossSigningKey object upfront, so that we
137        // can start to accumulate signatures. For the other keys, we generate
138        // the JSON representation on-demand.
139        self.master_cross_signing_key_json =
140            Some(self.signed_cross_signing_key(&master_public_key, KeyUsage::Master));
141
142        self
143    }
144
145    /// Add a device to the data to be returned.
146    ///
147    /// As well as a device ID and public Curve25519 device key, the *private*
148    /// Ed25519 device key must be provided so that the signature can be
149    /// calculated.
150    ///
151    /// The device can optionally be signed by the self-signing key by calling
152    /// [`KeyResponseTemplateDeviceOptions::verified(true)`] on the `options`
153    /// object.
154    pub fn with_device(
155        mut self,
156        device_id: &DeviceId,
157        curve25519_public_key: &Curve25519PublicKey,
158        ed25519_secret_key: &Ed25519SecretKey,
159        options: KeyQueryResponseTemplateDeviceOptions,
160    ) -> Self {
161        let mut device_keys = json!({
162            "algorithms": [
163                "m.olm.v1.curve25519-aes-sha2",
164                "m.megolm.v1.aes-sha2"
165            ],
166            "device_id": device_id.to_owned(),
167            "keys": {
168                format!("curve25519:{device_id}"): curve25519_public_key.to_base64(),
169                format!("ed25519:{device_id}"): ed25519_secret_key.public_key().to_base64(),
170            },
171            "signatures": {},
172            "user_id": self.user_id.clone(),
173        });
174
175        if options.dehydrated {
176            device_keys["dehydrated"] = Value::Bool(true);
177        }
178
179        sign_json(&mut device_keys, ed25519_secret_key, &self.user_id, device_id.as_str());
180        if options.verified {
181            let ssk = self
182                .self_signing_key
183                .as_ref()
184                .expect("must call with_cross_signing_keys() before creating cross-signed device");
185            sign_json(&mut device_keys, ssk, &self.user_id, &ssk.public_key().to_base64());
186        }
187
188        let raw_device_keys = serde_json::from_value(device_keys).unwrap();
189        self.device_keys.insert(device_id.to_owned(), raw_device_keys);
190        self
191    }
192
193    /// Add the signature from another user to our master key, as would happen
194    /// if that user had verified us.
195    pub fn with_user_verification_signature(
196        mut self,
197        signing_user_id: &UserId,
198        signing_user_user_signing_key: &Ed25519SecretKey,
199    ) -> Self {
200        let master_key = self.master_cross_signing_key_json.as_mut().expect(
201            "must call with_cross_signing_key() before calling 'with_user_verification_signature'",
202        );
203        sign_cross_signing_key(master_key, signing_user_user_signing_key, signing_user_id);
204        self
205    }
206
207    /// Build a `/keys/query` response containing this user's data.
208    pub fn build_response(&self) -> KeyQueryResponse {
209        let mut response = KeyQueryResponse::default();
210
211        if !self.device_keys.is_empty() {
212            response.device_keys =
213                BTreeMap::from([(self.user_id.clone(), self.device_keys.clone())]);
214        }
215
216        if let Some(master_key) = &self.master_cross_signing_key_json {
217            response.master_keys.insert(
218                self.user_id.clone(),
219                Raw::new(master_key).expect("unable to serialize msk"),
220            );
221        }
222
223        if let Some(self_signing_key) = &self.self_signing_key {
224            let ssk = self
225                .signed_cross_signing_key(&self_signing_key.public_key(), KeyUsage::SelfSigning);
226            response
227                .self_signing_keys
228                .insert(self.user_id.clone(), Raw::new(&ssk).expect("unable to serialize ssk"));
229        }
230
231        if let Some(user_signing_key) = &self.user_signing_key {
232            let usk = self
233                .signed_cross_signing_key(&user_signing_key.public_key(), KeyUsage::UserSigning);
234            response
235                .user_signing_keys
236                .insert(self.user_id.clone(), Raw::new(&usk).expect("unable to serialize usk"));
237        }
238
239        response
240    }
241
242    /// Build a [`CrossSigningKey`] structure for part of a `/keys/query`
243    /// response.
244    ///
245    /// Such a structure represents one of the three public cross-signing keys,
246    /// and is always signed by (at least) our master key.
247    ///
248    /// # Arguments
249    ///
250    /// - `public_key`: the public Ed25519 key to be returned by `/keys/query`.
251    /// - `key_usage`: an indicator of whether this will be the master,
252    ///   user-signing, or self-signing key.
253    fn signed_cross_signing_key(
254        &self,
255        public_key: &Ed25519PublicKey,
256        key_usage: KeyUsage,
257    ) -> CrossSigningKey {
258        let public_key_base64 = OwnedBase64PublicKey::with_bytes(public_key.as_bytes());
259        let mut key = CrossSigningKey::new(
260            self.user_id.clone(),
261            vec![key_usage],
262            BTreeMap::from([(
263                CrossSigningKeyId::from_parts(SigningKeyAlgorithm::Ed25519, &public_key_base64),
264                public_key_base64.to_string(),
265            )]),
266            CrossSigningOrDeviceSignatures::new(),
267        );
268
269        // Sign with our master key.
270        let master_key = self
271            .master_cross_signing_key
272            .as_ref()
273            .expect("must set master key before calling `signed_cross_signing_key`");
274        sign_cross_signing_key(&mut key, master_key, &self.user_id);
275
276        key
277    }
278}
279
280/// Options which control the addition of a device to a
281/// [`KeyQueryResponseTemplate`], via [`KeyQueryResponseTemplate::with_device`].
282#[derive(Default)]
283pub struct KeyQueryResponseTemplateDeviceOptions {
284    verified: bool,
285    dehydrated: bool,
286}
287
288impl KeyQueryResponseTemplateDeviceOptions {
289    /// Creates a blank new set of options ready for configuration.
290    ///
291    /// All options are initially set to `false`.
292    pub fn new() -> Self {
293        Self::default()
294    }
295
296    /// Sets the option for whether the device will be verified (i.e., signed by
297    /// the self-signing key).
298    pub fn verified(mut self, verified: bool) -> Self {
299        self.verified = verified;
300        self
301    }
302
303    /// Sets the option for whether the device will be marked as "dehydrated",
304    /// as per [MSC3814].
305    ///
306    /// [MSC3814]: https://github.com/matrix-org/matrix-spec-proposals/pull/3814
307    pub fn dehydrated(mut self, dehydrated: bool) -> Self {
308        self.dehydrated = dehydrated;
309        self
310    }
311}
312
313/// This set of keys/query response was generated using a local synapse.
314///
315/// The current user is `@me:localhost`, the private part of the
316/// cross-signing keys have been exported using the console with the
317/// following snippet:  `await mxMatrixClientPeg.get().getCrypto().
318/// olmMachine.exportCrossSigningKeys()`.
319///
320/// They are imported in the test here in order to verify user signatures.
321///
322/// * `@me:localhost` is the current user mxId.
323///
324/// * `@dan:localhost` is a user with cross-signing enabled, with 2 devices. One
325///   device (`JHPUERYQUW`) is self signed by @dan, but not the other one
326///   (`FRGNMZVOKA`). `@me` has verified `@dan`, can be seen because `@dan`
327///   master key has a signature by `@me` ssk
328///
329/// * `@dave` is a user that has not enabled cross-signing. And has one device
330///   (`HVCXJTHMBM`).
331///
332///
333/// * `@good` is a user with cross-signing enabled, with 2 devices. The 2
334///   devices are properly signed by `@good` (i.e were self-verified by @good)
335pub struct KeyDistributionTestData {}
336
337impl KeyDistributionTestData {
338    pub const MASTER_KEY_PRIVATE_EXPORT: &'static str =
339        "9kquJqAtEUoTXljh5W2QSsCm4FH9WvWzIkDkIMUsM2k";
340    pub const SELF_SIGNING_KEY_PRIVATE_EXPORT: &'static str =
341        "QifnGfudByh/GpBgJYEMzq7/DGbp6fZjp58faQj3n1M";
342    pub const USER_SIGNING_KEY_PRIVATE_EXPORT: &'static str =
343        "zQSosK46giUFs2ACsaf32bA7drcIXbmViyEt+TLfloI";
344
345    /// Current user's private user-signing key, as an [`Ed25519SecretKey`].
346    pub fn me_private_user_signing_key() -> Ed25519SecretKey {
347        Ed25519SecretKey::from_base64(Self::USER_SIGNING_KEY_PRIVATE_EXPORT).unwrap()
348    }
349
350    /// Current user keys query response containing the cross-signing keys
351    pub fn me_keys_query_response() -> KeyQueryResponse {
352        let builder = KeyQueryResponseTemplate::new(Self::me_id().to_owned())
353            .with_cross_signing_keys(
354                Ed25519SecretKey::from_base64(Self::MASTER_KEY_PRIVATE_EXPORT).unwrap(),
355                Ed25519SecretKey::from_base64(Self::SELF_SIGNING_KEY_PRIVATE_EXPORT).unwrap(),
356                Self::me_private_user_signing_key(),
357            );
358
359        let response = builder.build_response();
360        with_settings!({sort_maps => true}, {
361            assert_json_snapshot!(
362                "KeyDistributionTestData__me_keys_query_response",
363                ruma_response_to_json(response.clone()),
364            );
365        });
366        response
367    }
368
369    /// Dan's (base64-encoded) private master cross-signing key.
370    const DAN_PRIVATE_MASTER_CROSS_SIGNING_KEY: &'static str =
371        "QGZo39k199RM0NYvPvFNXBspc5llftHWKKHqEi25q0U";
372
373    /// Dan's (base64-encoded) private self-signing key.
374    const DAN_PRIVATE_SELF_SIGNING_KEY: &'static str =
375        "0ES1HO5VXpy/BsXxadwsk6QcwH/ci99KkV9ZlPakHlU";
376
377    /// Dan's (base64-encoded) private user-signing key.
378    const DAN_PRIVATE_USER_SIGNING_KEY: &'static str =
379        "vSdfrHJO8sZH/54r1uCg8BE0CdcDVGkPQNOu7Ej8BBs";
380
381    /// Dan has cross-signing set up; one device is cross-signed (`JHPUERYQUW`),
382    /// but not the other one (`FRGNMZVOKA`).
383    ///
384    /// `@dan`'s identity is signed by `@me`'s identity (Alice trusts Dan).
385    pub fn dan_keys_query_response() -> KeyQueryResponse {
386        let response = Self::dan_keys_query_response_common();
387        with_settings!({sort_maps => true}, {
388            assert_json_snapshot!(
389                "KeyDistributionTestData__dan_keys_query_response",
390                ruma_response_to_json(response.clone()),
391            );
392        });
393        response
394    }
395
396    /// Same as `dan_keys_query_response` but `FRGNMZVOKA` was removed.
397    pub fn dan_keys_query_response_device_loggedout() -> KeyQueryResponse {
398        let mut response = Self::dan_keys_query_response_common();
399        response
400            .device_keys
401            .get_mut(Self::dan_id())
402            .unwrap()
403            .remove(Self::dan_unsigned_device_id());
404
405        with_settings!({sort_maps => true}, {
406            assert_json_snapshot!(
407                "KeyDistributionTestData__dan_keys_query_response_device_loggedout",
408                ruma_response_to_json(response.clone()),
409            );
410        });
411        response
412    }
413
414    /// Common helper for [`Self::dan_keys_query_response`] and
415    /// [`Self::dan_keys_query_response_device_loggedout`].
416    ///
417    /// Returns the full response, including both devices, without writing the
418    /// snapshot.
419    fn dan_keys_query_response_common() -> KeyQueryResponse {
420        let builder = KeyQueryResponseTemplate::new(Self::dan_id().to_owned())
421            .with_cross_signing_keys(
422                Ed25519SecretKey::from_base64(Self::DAN_PRIVATE_MASTER_CROSS_SIGNING_KEY).unwrap(),
423                Ed25519SecretKey::from_base64(Self::DAN_PRIVATE_SELF_SIGNING_KEY).unwrap(),
424                Ed25519SecretKey::from_base64(Self::DAN_PRIVATE_USER_SIGNING_KEY).unwrap(),
425            )
426            .with_user_verification_signature(Self::me_id(), &Self::me_private_user_signing_key());
427
428        // Add signed device JHPUERYQUW
429        let builder = builder.with_device(
430            Self::dan_signed_device_id(),
431            &Curve25519PublicKey::from_base64("PBo2nKbink/HxgzMrBftGPogsD0d47LlIMsViTpCRn4")
432                .unwrap(),
433            &Ed25519SecretKey::from_base64("yzj53Kccfqx2yx9lcTwaRfPZX+7jU19harsDWWu5YnM").unwrap(),
434            KeyQueryResponseTemplateDeviceOptions::new().verified(true),
435        );
436
437        // Add unsigned device FRGNMZVOKA
438        let builder = builder.with_device(
439            Self::dan_unsigned_device_id(),
440            &Curve25519PublicKey::from_base64("Hc/BC/xyQIEnScyZkEk+ilDMfOARxHMFoEcggPqqRw4")
441                .unwrap(),
442            &Ed25519SecretKey::from_base64("/SlFtNKxTPN+i4pHzSPWZ1Oc6ymMB33sS32GXZkaLos").unwrap(),
443            KeyQueryResponseTemplateDeviceOptions::new(),
444        );
445
446        builder.build_response()
447    }
448
449    /// Dave is a user that has not enabled cross-signing
450    pub fn dave_keys_query_response() -> KeyQueryResponse {
451        let data = json!({
452            "device_keys": {
453                "@dave:localhost": {
454                    "HVCXJTHMBM": {
455                        "algorithms": [
456                            "m.olm.v1.curve25519-aes-sha2",
457                            "m.megolm.v1.aes-sha2"
458                        ],
459                        "device_id": "HVCXJTHMBM",
460                        "keys": {
461                            "curve25519:HVCXJTHMBM": "0GPOoQwhAGVu1lIvOZway3/XjdxVNHEi5z/4by8TzxU",
462                            "ed25519:HVCXJTHMBM": "/4ZzD1Ou70/Ojj5aaPqBopCN8SzQpKM7itiWZ/07fXc"
463                        },
464                        "signatures": {
465                            "@dave:localhost": {
466                                "ed25519:HVCXJTHMBM": "b1DV7xN2My2oXbZVVtTeJR9hzXIg1Cx4h+W51+tVq5GAoSYtrWR31PyKPROk28CvQ9Pu++/jdomaW7/oYPxoCg",
467                            }
468                        },
469                        "user_id": "@dave:localhost",
470                    }
471                }
472            }
473        });
474
475        ruma_response_from_json(&data)
476    }
477
478    /// Good is a user that has all his devices correctly cross-signed
479    pub fn good_keys_query_response() -> KeyQueryResponse {
480        let data = json!({
481            "device_keys": {
482                "@good:localhost": {
483                    "JAXGBVZYLA": {
484                        "algorithms": [
485                            "m.olm.v1.curve25519-aes-sha2",
486                            "m.megolm.v1.aes-sha2"
487                        ],
488                        "device_id": "JAXGBVZYLA",
489                        "keys": {
490                            "curve25519:JAXGBVZYLA": "a4vWxnHUKvELfB7WYLCW07vEbwybZReyKReWHxQhgW0",
491                            "ed25519:JAXGBVZYLA": "m22nVxqJK72iph+FhOMqX/MDd7AoF9BJ033MlMLnDCg"
492                        },
493                        "signatures": {
494                            "@good:localhost": {
495                                "ed25519:JAXGBVZYLA": "EXKQiXNKjWSE76WxF8TUvxjCyw/qsV27gcbsgpSN1zzHzGzVdY1Qr4EB8t/76SL5rZP/9hqcAvqPSJW/N7iKCg",
496                                "ed25519:YwQVBWn2sA5lLqp/dQsNk7fiYOuQuQhujefOPjejc+U": "sXJUXKE7hqXnsNbqlzS/1MGlGmeJU54v6/UMWAs+6bCzOFUC1+uqU1KlzfmpsVG3MKxR4r/ZLZdxoKVfUuQMAA"
497                            }
498                        },
499                        "user_id": "@good:localhost"
500                    },
501                    "ZGLCFWEPCY": {
502                        "algorithms": [
503                            "m.olm.v1.curve25519-aes-sha2",
504                            "m.megolm.v1.aes-sha2"
505                        ],
506                        "device_id": "ZGLCFWEPCY",
507                        "keys": {
508                            "curve25519:ZGLCFWEPCY": "kfcIEf6ZRgTP184yuIJYabfsBFsGXiVQE/cyW9qYnQA",
509                            "ed25519:ZGLCFWEPCY": "WLSA1tSe0eOZCeESH5WMb9cp3AgRZzm4ooSud+NwcEw"
510                        },
511                        "signatures": {
512                            "@good:localhost": {
513                                "ed25519:ZGLCFWEPCY": "AVXFgHk/QcAbOVBF5Xu4OW+03CZKBs2qAYh0fjIA49r+X+aX7QIKrbRyXU/ictPBLMpj1yXF+2J5vwR/KQYVCA",
514                                "ed25519:YwQVBWn2sA5lLqp/dQsNk7fiYOuQuQhujefOPjejc+U": "VZk70FWiYN/YSwGykt2CygcOl1bq2D+dVSSKBL5GA5uHXxt6ypDlYvtWprM1l7re3llp5j105MevsjQ+2sWmCw"
515                            }
516                        },
517                        "user_id": "@good:localhost"
518                    }
519                }
520            },
521            "failures": {},
522            "master_keys": {
523                "@good:localhost": {
524                    "keys": {
525                        "ed25519:5vTK2S2wVXo4xGT4BhcwpINVjRLjorkkJgCjnrHgtl8": "5vTK2S2wVXo4xGT4BhcwpINVjRLjorkkJgCjnrHgtl8"
526                    },
527                    "signatures": {
528                        "@good:localhost": {
529                            "ed25519:5vTK2S2wVXo4xGT4BhcwpINVjRLjorkkJgCjnrHgtl8": "imAhrTIlPuf6hNqlbcSUnC2ndZPk5NwQLzbi9kZ8nmnPGjmv39f4U4Vh/KiweqQnI4ActGpcYyM7k9S2Ef8/CQ",
530                            "ed25519:HPNYOQGUEE": "6w3egsvd+oVPCclef+hF1CfFMZrGTf/plFvPU5iP69WNw4w0UPAKSV1jOzh7Wv4LVGX5O3afjA9DG+O7aHZmBw"
531                        }
532                    },
533                    "usage": [
534                        "master"
535                    ],
536                    "user_id": "@good:localhost"
537                }
538            },
539            "self_signing_keys": {
540                "@good:localhost": {
541                    "keys": {
542                        "ed25519:YwQVBWn2sA5lLqp/dQsNk7fiYOuQuQhujefOPjejc+U": "YwQVBWn2sA5lLqp/dQsNk7fiYOuQuQhujefOPjejc+U"
543                    },
544                    "signatures": {
545                        "@good:localhost": {
546                            "ed25519:5vTK2S2wVXo4xGT4BhcwpINVjRLjorkkJgCjnrHgtl8": "2AyR8lovFv8J1DwPwdCsAM9Tw877QhaVHmVkPopsmSokS2fst8LDQtsg/PiftVc+74NGz5tnYIMDxn4BjAisAg"
547                        }
548                    },
549                    "usage": [
550                        "self_signing"
551                    ],
552                    "user_id": "@good:localhost"
553                }
554            },
555            "user_signing_keys": {
556                "@good:localhost": {
557                    "keys": {
558                        "ed25519:u1PwO3/a/HTnN9IF7BVa2dJQ7bc00J22eNS0vM4FjTA": "u1PwO3/a/HTnN9IF7BVa2dJQ7bc00J22eNS0vM4FjTA"
559                    },
560                    "signatures": {
561                        "@good:localhost": {
562                            "ed25519:5vTK2S2wVXo4xGT4BhcwpINVjRLjorkkJgCjnrHgtl8": "88v9/Z3TJeY2lsu3cFQaEuhHH5ixjJs22ALQRKY+O6VPGCT/BAzH6kUb7teinFfpvQjoXN3t5fVJxbP9mVlxDg"
563                        }
564                    },
565                    "usage": [
566                        "user_signing"
567                    ],
568                    "user_id": "@good:localhost"
569                }
570            }
571        });
572
573        ruma_response_from_json(&data)
574    }
575
576    pub fn me_id() -> &'static UserId {
577        user_id!("@me:localhost")
578    }
579
580    pub fn me_device_id() -> &'static DeviceId {
581        device_id!("ABCDEFGH")
582    }
583
584    pub fn dan_unsigned_device_id() -> &'static DeviceId {
585        device_id!("FRGNMZVOKA")
586    }
587
588    pub fn dan_signed_device_id() -> &'static DeviceId {
589        device_id!("JHPUERYQUW")
590    }
591
592    pub fn dave_device_id() -> &'static DeviceId {
593        device_id!("HVCXJTHMBM")
594    }
595
596    pub fn dan_id() -> &'static UserId {
597        user_id!("@dan:localhost")
598    }
599
600    pub fn dave_id() -> &'static UserId {
601        user_id!("@dave:localhost")
602    }
603
604    pub fn good_id() -> &'static UserId {
605        user_id!("@good:localhost")
606    }
607}
608
609/// A set of keys query to test identity changes,
610/// For user @bob, several payloads with no identities then identity A and B.
611pub struct IdentityChangeDataSet {}
612
613impl IdentityChangeDataSet {
614    pub fn user_id() -> &'static UserId {
615        // All 3 bobs have the same user id
616        assert_eq!(KeysQueryUser::bob_a().user_id, KeysQueryUser::bob_b().user_id);
617        assert_eq!(KeysQueryUser::bob_a().user_id, KeysQueryUser::bob_c().user_id);
618
619        KeysQueryUser::bob_a().user_id
620    }
621
622    pub fn device_a() -> &'static DeviceId {
623        KeysQueryUser::bob_a().device_id
624    }
625
626    pub fn device_b() -> &'static DeviceId {
627        KeysQueryUser::bob_b().device_id
628    }
629
630    pub fn device_c() -> &'static DeviceId {
631        KeysQueryUser::bob_c().device_id
632    }
633
634    pub fn master_signing_keys_a() -> Value {
635        master_keys(&KeysQueryUser::bob_a())
636    }
637
638    pub fn self_signing_keys_a() -> Value {
639        self_signing_keys(&KeysQueryUser::bob_a())
640    }
641
642    /// A key query with an identity (Ia), and a first device `GYKSNAWLVK`
643    /// signed by Ia.
644    pub fn key_query_with_identity_a() -> KeyQueryResponse {
645        keys_query(&KeysQueryUser::bob_a(), &[])
646    }
647
648    pub fn master_signing_keys_b() -> Value {
649        master_keys(&KeysQueryUser::bob_b())
650    }
651
652    pub fn self_signing_keys_b() -> Value {
653        self_signing_keys(&KeysQueryUser::bob_b())
654    }
655
656    pub fn device_keys_payload_2_signed_by_b() -> Value {
657        device_keys_payload(&KeysQueryUser::bob_b())
658    }
659
660    /// A key query with a new identity (Ib) and a new device `ATWKQFSFRN`.
661    /// `ATWKQFSFRN` is signed with the new identity but `GYKSNAWLVK` is still
662    /// signed by the old identity (Ia).
663    pub fn key_query_with_identity_b() -> KeyQueryResponse {
664        keys_query(&KeysQueryUser::bob_b(), &[KeysQueryUser::bob_a()])
665    }
666
667    /// A key query with no identity and a new device `OPABMDDXGX` (not
668    /// cross-signed).
669    pub fn key_query_with_identity_no_identity() -> KeyQueryResponse {
670        keys_query(&KeysQueryUser::bob_c(), &[KeysQueryUser::bob_a(), KeysQueryUser::bob_b()])
671    }
672}
673
674/// A set of `/keys/query` responses that were initially created to simulate
675/// when a user that was verified reset his keys and became unverified.
676///
677/// The local user (as returned by [`VerificationViolationTestData::own_id`]) is
678/// `@alice:localhost`. There are 2 other users: `@bob:localhost` (returned by
679/// [`VerificationViolationTestData::bob_id`]), and `@carol:localhost` (returned
680/// by [`VerificationViolationTestData::carol_id`]).
681///
682/// We provide two `/keys/query` responses for each of Bob and Carol: one signed
683/// by Alice, and one not signed.
684///
685/// Bob and Carol each have 2 devices, one signed by the owning user, and
686/// another one not cross-signed.
687///
688/// The `/keys/query` responses were generated using a local synapse.
689pub struct VerificationViolationTestData {}
690
691impl VerificationViolationTestData {
692    /// Secret part of Alice's master cross-signing key.
693    ///
694    /// Exported from Element-Web with the following console snippet:
695    ///
696    /// ```javascript
697    /// (await mxMatrixClientPeg.get().getCrypto().olmMachine.exportCrossSigningKeys()).masterKey
698    /// ```
699    pub const MASTER_KEY_PRIVATE_EXPORT: &'static str =
700        "bSa0nVTocZArMzL7OLmeFUIVF4ycp64rrkVMgqOYg6Y";
701
702    /// Secret part of Alice's self cross-signing key.
703    ///
704    /// Exported from Element-Web with the following console snippet:
705    ///
706    /// ```javascript
707    /// (await mxMatrixClientPeg.get().getCrypto().olmMachine.exportCrossSigningKeys()).self_signing_key
708    /// ```
709    pub const SELF_SIGNING_KEY_PRIVATE_EXPORT: &'static str =
710        "MQ7b3MDXvOEMDvIOWkuH1XCNUyqBLqbdd1bT00p8HPU";
711
712    /// Secret part of Alice's user cross-signing key.
713    ///
714    /// Exported from Element-Web with the following console snippet:
715    ///
716    /// ```javascript
717    /// (await mxMatrixClientPeg.get().getCrypto().olmMachine.exportCrossSigningKeys()).userSigningKey
718    /// ```
719    pub const USER_SIGNING_KEY_PRIVATE_EXPORT: &'static str =
720        "v77s+TlT5/NbcQym2B7Rwf20HOAhyInF2p1ZUYDPtow";
721
722    /// Alice's user ID.
723    ///
724    /// Alice is the "local user" for this test data set.
725    pub fn own_id() -> &'static UserId {
726        user_id!("@alice:localhost")
727    }
728
729    /// Bob's user ID.
730    pub fn bob_id() -> &'static UserId {
731        user_id!("@bob:localhost")
732    }
733
734    /// Carol's user ID.
735    pub fn carol_id() -> &'static UserId {
736        user_id!("@carol:localhost")
737    }
738
739    /// `/keys/query` response for Alice, containing the public cross-signing
740    /// keys.
741    pub fn own_keys_query_response_1() -> KeyQueryResponse {
742        let builder = KeyQueryResponseTemplate::new(Self::own_id().to_owned())
743            .with_cross_signing_keys(
744                Ed25519SecretKey::from_base64(Self::MASTER_KEY_PRIVATE_EXPORT).unwrap(),
745                Ed25519SecretKey::from_base64(Self::SELF_SIGNING_KEY_PRIVATE_EXPORT).unwrap(),
746                Ed25519SecretKey::from_base64(Self::USER_SIGNING_KEY_PRIVATE_EXPORT).unwrap(),
747            );
748
749        let response = builder.build_response();
750        with_settings!({sort_maps => true}, {
751            assert_json_snapshot!(
752                "VerificationViolationTestData__own_keys_query_response_1",
753                ruma_response_to_json(response.clone()),
754            );
755        });
756        response
757    }
758
759    /// A second `/keys/query` response for Alice, containing a *different* set
760    /// of public cross-signing keys.
761    ///
762    /// This response was lifted from the test data set from `matrix-js-sdk`.
763    pub fn own_keys_query_response_2() -> KeyQueryResponse {
764        let data = json!({
765            "master_keys": {
766                "@alice:localhost": {
767                    "keys": { "ed25519:J+5An10v1vzZpAXTYFokD1/PEVccFnLC61EfRXit0UY": "J+5An10v1vzZpAXTYFokD1/PEVccFnLC61EfRXit0UY" },
768                    "user_id": "@alice:localhost",
769                    "usage": [ "master" ]
770                }
771            },
772            "self_signing_keys": {
773                "@alice:localhost": {
774                    "keys": { "ed25519:aU2+2CyXQTCuDcmWW0EL2bhJ6PdjFW2LbAsbHqf02AY": "aU2+2CyXQTCuDcmWW0EL2bhJ6PdjFW2LbAsbHqf02AY" },
775                    "user_id": "@alice:localhost",
776                    "usage": [ "self_signing" ],
777                    "signatures": {
778                        "@alice:localhost": {
779                            "ed25519:J+5An10v1vzZpAXTYFokD1/PEVccFnLC61EfRXit0UY": "XfhYEhZmOs8BJdb3viatILBZ/bElsHXEW28V4tIaY5CxrBR0YOym3yZHWmRmypXessHZAKOhZn3yBMXzdajyCw"
780                        }
781                    }
782                }
783            },
784            "user_signing_keys": {
785                "@alice:localhost": {
786                    "keys": { "ed25519:g5TC/zjQXyZYuDLZv7a41z5fFVrXpYPypG//AFQj8hY": "g5TC/zjQXyZYuDLZv7a41z5fFVrXpYPypG//AFQj8hY" },
787                    "user_id": "@alice:localhost",
788                    "usage": [ "user_signing" ],
789                    "signatures": {
790                        "@alice:localhost": {
791                            "ed25519:J+5An10v1vzZpAXTYFokD1/PEVccFnLC61EfRXit0UY": "6AkD1XM2H0/ebgP9oBdMKNeft7uxsrb0XN1CsjjHgeZCvCTMmv3BHlLiT/Hzy4fe8H+S1tr484dcXN/PIdnfDA"
792                        }
793                    }
794                }
795            }
796        });
797
798        ruma_response_from_json(&data)
799    }
800
801    /// Device ID of the device returned by [`Self::own_unsigned_device_keys`].
802    pub fn own_unsigned_device_id() -> OwnedDeviceId {
803        Self::own_unsigned_device_keys().0
804    }
805
806    /// Device-keys response for a device belonging to Alice, which has *not*
807    /// been signed by her identity. This can be used as part of a
808    /// `/keys/query` response.
809    ///
810    /// For convenience, returns a tuple `(<device id>, <device keys>)`. The
811    /// device id is also returned by [`Self::own_unsigned_device_id`].
812    pub fn own_unsigned_device_keys() -> (OwnedDeviceId, Raw<DeviceKeys>) {
813        let json = json!({
814             "algorithms": [
815                 "m.olm.v1.curve25519-aes-sha2",
816                 "m.megolm.v1.aes-sha2"
817             ],
818             "device_id": "AHIVRZICJK",
819             "keys": {
820                 "curve25519:AHIVRZICJK": "3U73fbymtt6sn/H+5UYHiQxN2HfDmxzOMYZ+3JyPT2E",
821                 "ed25519:AHIVRZICJK": "I0NV5nJYmnH+f5py4Fz2tdCeSKUChaaXV7m4UOq9bis"
822             },
823             "signatures": {
824                 "@alice:localhost": {
825                     "ed25519:AHIVRZICJK": "HIs13b2GizN8gdZrYLWs9KZbcmKubXE+O4716Uow513e84JO8REy53OX4TDdoBfmVhPiZg5CIRrUDH7JxY4wAQ"
826                 }
827             },
828             "user_id": "@alice:localhost",
829             "unsigned": {
830                 "device_display_name": "Element - dbg Android"
831             }
832        });
833        (device_id!("AHIVRZICJK").to_owned(), serde_json::from_value(json).unwrap())
834    }
835
836    /// Device ID of the device returned by [`Self::own_signed_device_keys`].
837    pub fn own_signed_device_id() -> OwnedDeviceId {
838        Self::own_signed_device_keys().0
839    }
840
841    /// Device-keys response for a device belonging to Alice, which has been
842    /// signed by her identity. This can be used as part of a `/keys/query`
843    /// response.
844    ///
845    /// For convenience, returns a tuple `(<device id>, <device keys>)`. The
846    /// device id is also returned by [`Self::own_signed_device_id`].
847    pub fn own_signed_device_keys() -> (OwnedDeviceId, Raw<DeviceKeys>) {
848        let json = json!({
849            "algorithms": [
850                "m.olm.v1.curve25519-aes-sha2",
851                "m.megolm.v1.aes-sha2"
852            ],
853            "device_id": "LCNRWQAVWK",
854            "keys": {
855                "curve25519:LCNRWQAVWK": "fULFq9I6uYmsdDwRFU76wc43RqF7TVGvlWvKXhSrsS4",
856                "ed25519:LCNRWQAVWK": "F7E0EF0lzVJN31cnetLdeBuNvZ8jQqkUzt8/nGD9M/E"
857            },
858            "signatures": {
859                "@alice:localhost": {
860                    "ed25519:LCNRWQAVWK": "8kLsN76ytGRuHKMgIARaOds29QrPRzQ6Px+FOLsYK/ATmx5IVd65MpSh2pGjLAaPsSGWR1WLbBTq/LZtcpjTDQ",
861                    "ed25519:WXLer0esHUanp8DCeu2Be0xB5ms9aKFFBrCFl50COjw": "lo4Vuuu+WvPt1hnOCv30iS1y/cF7DljfFZYF3ib5JH/6iPZTW4jYdlmWo4a7hDf0fb2pu3EFnghYMr7vVx41Aw"
862                }
863            },
864            "user_id": "@alice:localhost",
865            "unsigned": {
866                "device_display_name": "develop.element.io: Chrome on macOS"
867            }
868        });
869        (device_id!("LCNRWQAVWK").to_owned(), serde_json::from_value(json).unwrap())
870    }
871
872    /// `/keys/query` response for Bob, signed by Alice's identity.
873    ///
874    /// Contains Bob's cross-signing identity, and two devices:
875    /// [`Self::bob_device_1_id`] (signed by the cross-signing identity), and
876    /// [`Self::bob_device_2_id`] (not cross-signed).
877    pub fn bob_keys_query_response_signed() -> KeyQueryResponse {
878        let data = json!({
879            "device_keys": {
880                "@bob:localhost": {
881                    "RLZGZIHKMP": {
882                        "algorithms": [
883                            "m.olm.v1.curve25519-aes-sha2",
884                            "m.megolm.v1.aes-sha2"
885                        ],
886                        "device_id": "RLZGZIHKMP",
887                        "keys": {
888                            "curve25519:RLZGZIHKMP": "Zd8uO9Rr1PtqNno3//ybeUZ3JuqFtm17TQTWW0f47AU",
889                            "ed25519:RLZGZIHKMP": "kH+Zn2m7LPES/XLOyVvnf8t4Byfj3mAbngHptHZFzk0"
890                        },
891                        "signatures": {
892                            "@bob:localhost": {
893                                "ed25519:RLZGZIHKMP": "w4MOkDiD+4XatQrRzGrcaqwVmiZrAjxmaIA8aSuzQveD2SJ2eVZq3OSpqx6QRUbG/gkkZxGmY13PkS/iAOv0AA",
894                                "ed25519:e8JFSrW8LW3UK6SSXh2ZESUzptFbapr28/+WqndD+Xk": "ki+cV0EVe5cYXnzqU078qy1qu2rnaxaBQU+KwyvPpEUotNTXjWKUOJfxast42d5tjI5vsI5aiQ6XkYfjBJ74Bw"
895                            }
896                        },
897                        "user_id": "@bob:localhost",
898                        "unsigned": {}
899                    },
900                    "XCYNVRMTER": {
901                        "algorithms": [
902                            "m.olm.v1.curve25519-aes-sha2",
903                            "m.megolm.v1.aes-sha2"
904                        ],
905                        "device_id": "XCYNVRMTER",
906                        "keys": {
907                            "curve25519:XCYNVRMTER": "xGKYkFcHGlJ+I1yiefPyZu1EY8i2h1eed5uk3PAW6GA",
908                            "ed25519:XCYNVRMTER": "EsU8MJzTYE+/VJs1K9HkGqb8UXCByPioynGrV28WocU"
909                        },
910                        "signatures": {
911                            "@bob:localhost": {
912                                "ed25519:XCYNVRMTER": "yZ7cpaoA+0rRx+bmklsP1iAd0eGPH6gsdywC11VE98/mrcbeFuxjQVn39Ds7h+vmciu5GRzwWgDgv+6go6FHAQ",
913                                // Remove the cross-signature
914                                // "ed25519:e8JFSrW8LW3UK6SSXh2ZESUzptFbapr28/+WqndD+Xk": "xYnGmU9FEdoavB5P743gx3xbEy29tlfRX5lT3JO0dWhHdsP+muqBXUYMBl1RRFeZtIE0GYc9ORb6Yf88EdeoCw"
915                            }
916                        },
917                        "user_id": "@bob:localhost",
918                        "unsigned": {},
919                    },
920                }
921            },
922            "failures": {},
923            "master_keys": {
924                "@bob:localhost": {
925                    "keys": {
926                        "ed25519:xZPyb4hxM8zaedDFz5m8HsDpX1fknd/V/69THLhNX9I": "xZPyb4hxM8zaedDFz5m8HsDpX1fknd/V/69THLhNX9I"
927                    },
928                    "signatures": {
929                        "@bob:localhost": {
930                            "ed25519:RLZGZIHKMP": "5bHLrx0HwYsNRtd65s1a1wVGlwgJU8yb8cq/Qbq04o9nVdQuY8+woQVWq9nxk59u6QFZIpFdVjXsuTPkDJLsBA",
931                            "ed25519:xZPyb4hxM8zaedDFz5m8HsDpX1fknd/V/69THLhNX9I": "NA+cLNIPpmECcBIcmAH5l1K4IDXI6Xss1VmU8TZ04AYQSAh/2sv7NixEBO1/Raz0nErzkOl8gpRswHbHv1p7Dw"
932                        },
933                        "@alice:localhost": {
934                            "ed25519:MXob/N/bYI7U2655O1/AI9NOX1245RnE03Nl4Hvf+u0": "n3X6afWYoSywqBpPlaDfQ2BNjl3ez5AzxEVwaB5/KEAzgwsq5B2qBW9N5uZaNWEq5M3JBrh0doj1FgUg4R3yBQ"
935                        }
936                    },
937                    "usage": [
938                        "master"
939                    ],
940                    "user_id": "@bob:localhost"
941                }
942            },
943            "self_signing_keys": {
944                "@bob:localhost": {
945                    "keys": {
946                        "ed25519:e8JFSrW8LW3UK6SSXh2ZESUzptFbapr28/+WqndD+Xk": "e8JFSrW8LW3UK6SSXh2ZESUzptFbapr28/+WqndD+Xk"
947                    },
948                    "signatures": {
949                        "@bob:localhost": {
950                            "ed25519:xZPyb4hxM8zaedDFz5m8HsDpX1fknd/V/69THLhNX9I": "kkGZHLY18jyqXs412VB31u6vxijbaBgVrIMR/LBAFULhTZk6HGH951N6NxMZnYHyH0sFaQhsl4DUqt7XthBHBQ"
951                        }
952                    },
953                    "usage": [
954                        "self_signing"
955                    ],
956                    "user_id": "@bob:localhost"
957                }
958            },
959            "user_signing_keys": {}
960        });
961
962        ruma_response_from_json(&data)
963    }
964
965    /// Device ID of Bob's first device.
966    ///
967    /// This device is cross-signed in [`Self::bob_keys_query_response_signed`]
968    /// but not in [`Self::bob_keys_query_response_rotated`].
969    pub fn bob_device_1_id() -> &'static DeviceId {
970        device_id!("RLZGZIHKMP")
971    }
972
973    /// Device ID of Bob's second device.
974    ///
975    /// This device is cross-signed in [`Self::bob_keys_query_response_rotated`]
976    /// but not in [`Self::bob_keys_query_response_signed`].
977    pub fn bob_device_2_id() -> &'static DeviceId {
978        device_id!("XCYNVRMTER")
979    }
980
981    /// `/keys/query` response for Bob, signed by Alice's identity.
982    ///
983    /// In contrast to [`Self::bob_keys_query_response_signed`], Bob has a new
984    /// cross-signing identity, which is **not** signed by Alice.
985    /// As well as the new identity, still contains the two devices
986    /// [`Self::bob_device_1_id`] (signed only by the *old* cross-signing
987    /// identity), and [`Self::bob_device_2_id`] (properly signed by the new
988    /// identity).
989    pub fn bob_keys_query_response_rotated() -> KeyQueryResponse {
990        let data = json!({
991            "device_keys": {
992                "@bob:localhost": {
993                    "RLZGZIHKMP": {
994                        "algorithms": [
995                            "m.olm.v1.curve25519-aes-sha2",
996                            "m.megolm.v1.aes-sha2"
997                        ],
998                        "device_id": "RLZGZIHKMP",
999                        "keys": {
1000                            "curve25519:RLZGZIHKMP": "Zd8uO9Rr1PtqNno3//ybeUZ3JuqFtm17TQTWW0f47AU",
1001                            "ed25519:RLZGZIHKMP": "kH+Zn2m7LPES/XLOyVvnf8t4Byfj3mAbngHptHZFzk0"
1002                        },
1003                        "signatures": {
1004                            "@bob:localhost": {
1005                                "ed25519:RLZGZIHKMP": "w4MOkDiD+4XatQrRzGrcaqwVmiZrAjxmaIA8aSuzQveD2SJ2eVZq3OSpqx6QRUbG/gkkZxGmY13PkS/iAOv0AA",
1006                                // "ed25519:At1ai1VUZrCncCI7V7fEAJmBShfpqZ30xRzqcEjTjdc": "rg3b3DovN3VztdcKyOcOlIGQxmm+8VC9+ImuXdgug/kPSi7QcljwOtjnk4LMkHexB3xVzB0ANcyNjbJ2cJuYBg",
1007                                "ed25519:e8JFSrW8LW3UK6SSXh2ZESUzptFbapr28/+WqndD+Xk": "ki+cV0EVe5cYXnzqU078qy1qu2rnaxaBQU+KwyvPpEUotNTXjWKUOJfxast42d5tjI5vsI5aiQ6XkYfjBJ74Bw"
1008                            }
1009                        },
1010                        "user_id": "@bob:localhost",
1011                        "unsigned": {
1012                            "device_display_name": "develop.element.io: Chrome on macOS"
1013                        }
1014                    },
1015                    "XCYNVRMTER": {
1016                        "algorithms": [
1017                            "m.olm.v1.curve25519-aes-sha2",
1018                            "m.megolm.v1.aes-sha2"
1019                        ],
1020                        "device_id": "XCYNVRMTER",
1021                        "keys": {
1022                            "curve25519:XCYNVRMTER": "xGKYkFcHGlJ+I1yiefPyZu1EY8i2h1eed5uk3PAW6GA",
1023                            "ed25519:XCYNVRMTER": "EsU8MJzTYE+/VJs1K9HkGqb8UXCByPioynGrV28WocU"
1024                        },
1025                        "signatures": {
1026                            "@bob:localhost": {
1027                                "ed25519:XCYNVRMTER": "yZ7cpaoA+0rRx+bmklsP1iAd0eGPH6gsdywC11VE98/mrcbeFuxjQVn39Ds7h+vmciu5GRzwWgDgv+6go6FHAQ",
1028                                "ed25519:e8JFSrW8LW3UK6SSXh2ZESUzptFbapr28/+WqndD+Xk": "xYnGmU9FEdoavB5P743gx3xbEy29tlfRX5lT3JO0dWhHdsP+muqBXUYMBl1RRFeZtIE0GYc9ORb6Yf88EdeoCw",
1029                                "ed25519:NWoyMF4Ox8PEj+8l1e70zuIUg0D+wL9wtcj1KhWL0Bc": "2ieX8z+oW9JhdyIIkTDsQ2o5VWxcO6dOgeyPbRwbAL6Q8J6xujzYSIi568UAlPt+wg+RkNLshneexCPNMgSiDQ"
1030                            }
1031                        },
1032                        "user_id": "@bob:localhost",
1033                        "unsigned": {
1034                            "device_display_name": "app.element.io: Chrome on mac"
1035                        }
1036                    }
1037                }
1038            },
1039            "failures": {},
1040            "master_keys": {
1041                "@bob:localhost": {
1042                    "keys": {
1043                        "ed25519:xaFlsDqlDRRy7Idtt1dW9mdhH/gvvax34q+HxepjNWY": "xaFlsDqlDRRy7Idtt1dW9mdhH/gvvax34q+HxepjNWY"
1044                    },
1045                    "signatures": {
1046                        "@bob:localhost": {
1047                            "ed25519:XCYNVRMTER": "K1aPl+GtcNi8yDqn1zvKIJMg3PFLQkwoXJeFJMmct4SA2SiQIl1S2x1bDTC3kQ4/LA7ULiQgKlxkXdQVf2GZDw",
1048                            "ed25519:xaFlsDqlDRRy7Idtt1dW9mdhH/gvvax34q+HxepjNWY": "S5vw8moiPudKhmF1qIv3/ehbZ7uohJbcQaLcOV+DDh9iC/YX0UqnaGn1ZYWJpIN7Kxe2ZWCBwzp35DOVZKfxBw"
1049                        }
1050                    },
1051                    "usage": [
1052                        "master"
1053                    ],
1054                    "user_id": "@bob:localhost"
1055                }
1056            },
1057            "self_signing_keys": {
1058                "@bob:localhost": {
1059                    "keys": {
1060                        "ed25519:NWoyMF4Ox8PEj+8l1e70zuIUg0D+wL9wtcj1KhWL0Bc": "NWoyMF4Ox8PEj+8l1e70zuIUg0D+wL9wtcj1KhWL0Bc"
1061                    },
1062                    "signatures": {
1063                        "@bob:localhost": {
1064                            "ed25519:xaFlsDqlDRRy7Idtt1dW9mdhH/gvvax34q+HxepjNWY": "rwQIkR7JbZOrwGrmkW9QzFlK+lMjRDHVcGVlYNS/zVeDyvWxD0WFHcmy4p/LSgJDyrVt+th7LH7Bj+Ed/EGvCw"
1065                        }
1066                    },
1067                    "usage": [
1068                        "self_signing"
1069                    ],
1070                    "user_id": "@bob:localhost"
1071                }
1072            },
1073            "user_signing_keys": {}
1074        });
1075
1076        ruma_response_from_json(&data)
1077    }
1078
1079    /// Device ID of Carol's signed device.
1080    ///
1081    /// The device is returned as part of
1082    /// [`Self::carol_keys_query_response_signed`] and
1083    /// [`Self::carol_keys_query_response_unsigned`].
1084    pub fn carol_signed_device_id() -> &'static DeviceId {
1085        device_id!("JBRBCHOFDZ")
1086    }
1087
1088    /// Device ID of Carol's unsigned device.
1089    ///
1090    /// The device is returned as part of
1091    /// [`Self::carol_keys_query_response_signed`] and
1092    /// [`Self::carol_keys_query_response_unsigned`].
1093    pub fn carol_unsigned_device_id() -> &'static DeviceId {
1094        device_id!("BAZAPVEHGA")
1095    }
1096
1097    /// Device-keys payload for Carol's unsigned device
1098    /// ([`Self::carol_unsigned_device_id`]).
1099    ///
1100    /// Notice that there is no SSK signature in the `signatures` field.
1101    fn device_1_keys_payload_carol() -> Value {
1102        json!({
1103            "algorithms": [
1104                "m.olm.v1.curve25519-aes-sha2",
1105                "m.megolm.v1.aes-sha2"
1106            ],
1107            "device_id": "BAZAPVEHGA",
1108            "keys": {
1109                "curve25519:BAZAPVEHGA": "/mCcWJb5mtNGPC7m4iQeW8gVJB4nG8z/z2QQXzzNijw",
1110                "ed25519:BAZAPVEHGA": "MLSoOlk27qcS/2O9Etp6XwgF8j+UT06yy/ypSeE9JRA"
1111            },
1112            "signatures": {
1113                "@carol:localhost": {
1114                    "ed25519:BAZAPVEHGA": "y2+Z0ofRRoNMj864SoAcNEXRToYVeiARu39CO0Vj2GcSIxlpR7B8K1wDYV4luP4gOL1t1tPgJPXL1WO//AHHCw",
1115                }
1116            },
1117            "user_id": "@carol:localhost"
1118        })
1119    }
1120
1121    /// Device-keys payload for Carol's signed device
1122    /// ([`Self::carol_signed_device_id`]).
1123    fn device_2_keys_payload_carol() -> Value {
1124        json!({
1125            "algorithms": [
1126                "m.olm.v1.curve25519-aes-sha2",
1127                "m.megolm.v1.aes-sha2"
1128            ],
1129            "device_id": "JBRBCHOFDZ",
1130            "keys": {
1131                "curve25519:JBRBCHOFDZ": "900HdrlfxlH8yMSmEQ3C32uVyXCuxKs5oPKS/wUgzVQ",
1132                "ed25519:JBRBCHOFDZ": "BOINY06uroLYscHUq0e0FmUo/W0LC4/fsIPkZQe71NY"
1133            },
1134            "signatures": {
1135                "@carol:localhost": {
1136                    "ed25519:JBRBCHOFDZ": "MmSJS3yEdeuseiLTDCQwImZBPNFMdhhkAFjRZZrIONoGFR0AMSzgLtx/nSgXP8RwVxpycvb6OAqvSk2toK3PDg",
1137                    "ed25519:ZOMWgk5LAogkwDEdZl9Rv7FRGu0nGbeLtMHx6anzhQs": "VtoxmPn/BQVDlpEHPEI2wPUlruUX9m2zV3FChNkRyEEWur4St27WA1He8BwjVRiiT0bdUnVH3xfmucoV9UnbDA"
1138                }
1139            },
1140            "user_id": "@carol:localhost",
1141        })
1142    }
1143
1144    /// Device-keys payload for Carol's SSK.
1145    fn ssk_payload_carol() -> Value {
1146        json!({
1147            "@carol:localhost": {
1148                "keys": {
1149                    "ed25519:ZOMWgk5LAogkwDEdZl9Rv7FRGu0nGbeLtMHx6anzhQs": "ZOMWgk5LAogkwDEdZl9Rv7FRGu0nGbeLtMHx6anzhQs"
1150                },
1151                "signatures": {
1152                    "@carol:localhost": {
1153                        "ed25519:itnwUCRfBPW08IrmBks9MTp/Qm5AJ2WNca13ptIZF8U": "thjR1/kxHADXqLqxc4Q3OZhAaLq7SPL96LNCGVGN64OYAJ5yG1cpqAXBiBCUaBUTdRTb0ys601RR8djPdTK/BQ"
1154                    }
1155                },
1156                "usage": [
1157                    "self_signing"
1158                ],
1159                "user_id": "@carol:localhost"
1160            }
1161        })
1162    }
1163
1164    /// `/keys/query` response for Carol, not yet verified by any other
1165    /// user.
1166    ///
1167    /// Contains Carol's cross-signing identity, and two devices:
1168    /// [`Self::carol_signed_device_id`] (signed by the cross-signing
1169    /// identity), and [`Self::carol_unsigned_device_id`]
1170    /// (not cross-signed).
1171    pub fn carol_keys_query_response_unsigned() -> KeyQueryResponse {
1172        let data = json!({
1173            "device_keys": {
1174                "@carol:localhost": {
1175                    "BAZAPVEHGA": Self::device_1_keys_payload_carol(),
1176                    "JBRBCHOFDZ": Self::device_2_keys_payload_carol()
1177                }
1178            },
1179            "failures": {},
1180            "master_keys": {
1181                "@carol:localhost": {
1182                    "keys": {
1183                        "ed25519:itnwUCRfBPW08IrmBks9MTp/Qm5AJ2WNca13ptIZF8U": "itnwUCRfBPW08IrmBks9MTp/Qm5AJ2WNca13ptIZF8U"
1184                    },
1185                    "signatures": {
1186                        "@carol:localhost": {
1187                            "ed25519:JBRBCHOFDZ": "eRA4jRSszQVuYpMtHTBuWGLEzcdUojyCW4/XKHRIQ2solv7iTC/MWES6I20YrHJa7H82CVoyNxS1Y3AwttBbCg",
1188                            "ed25519:itnwUCRfBPW08IrmBks9MTp/Qm5AJ2WNca13ptIZF8U": "e3r5L+JLv6FB8+Tt4BlIbz4wk2qPeMoKL1uR079qZzYMvtKoWGK9p000cZIhA5R1Tl7buQ9ODUfizued8g3TAg"
1189                        },
1190                        // Omit the signature from Alice's USK
1191                        // "@alice:localhost": {
1192                        //     "ed25519:MXob/N/bYI7U2655O1/AI9NOX1245RnE03Nl4Hvf+u0": "yfRUvkaVg3KizC/HDXcuP4+gtYhxgzr8X916Wt4GRXjj4qhDjsCkf8mYZ7x4lcEXzRkYql5KelabgVzP12qmAA"
1193                        // }
1194                    },
1195                    "usage": [
1196                        "master"
1197                    ],
1198                    "user_id": "@carol:localhost"
1199                }
1200            },
1201            "self_signing_keys": Self::ssk_payload_carol(),
1202            "user_signing_keys": {}
1203        });
1204
1205        ruma_response_from_json(&data)
1206    }
1207
1208    /// `/keys/query` response for Carol, signed by Alice.
1209    ///
1210    /// Contains the same data as [`Self::carol_keys_query_response_unsigned`],
1211    /// but Carol's identity is now signed by Alice's user-signing key.
1212    pub fn carol_keys_query_response_signed() -> KeyQueryResponse {
1213        let data = json!({
1214            "device_keys": {
1215                "@carol:localhost": {
1216                    "BAZAPVEHGA": Self::device_1_keys_payload_carol(),
1217                    "JBRBCHOFDZ": Self::device_2_keys_payload_carol()
1218                }
1219            },
1220            "failures": {},
1221            "master_keys": {
1222                "@carol:localhost": {
1223                    "keys": {
1224                        "ed25519:itnwUCRfBPW08IrmBks9MTp/Qm5AJ2WNca13ptIZF8U": "itnwUCRfBPW08IrmBks9MTp/Qm5AJ2WNca13ptIZF8U"
1225                    },
1226                    "signatures": {
1227                        "@carol:localhost": {
1228                            "ed25519:JBRBCHOFDZ": "eRA4jRSszQVuYpMtHTBuWGLEzcdUojyCW4/XKHRIQ2solv7iTC/MWES6I20YrHJa7H82CVoyNxS1Y3AwttBbCg",
1229                            "ed25519:itnwUCRfBPW08IrmBks9MTp/Qm5AJ2WNca13ptIZF8U": "e3r5L+JLv6FB8+Tt4BlIbz4wk2qPeMoKL1uR079qZzYMvtKoWGK9p000cZIhA5R1Tl7buQ9ODUfizued8g3TAg"
1230                        },
1231                        "@alice:localhost": {
1232                            "ed25519:MXob/N/bYI7U2655O1/AI9NOX1245RnE03Nl4Hvf+u0": "yfRUvkaVg3KizC/HDXcuP4+gtYhxgzr8X916Wt4GRXjj4qhDjsCkf8mYZ7x4lcEXzRkYql5KelabgVzP12qmAA"
1233                        }
1234                    },
1235                    "usage": [
1236                        "master"
1237                    ],
1238                    "user_id": "@carol:localhost"
1239                }
1240            },
1241            "self_signing_keys": Self::ssk_payload_carol(),
1242            "user_signing_keys": {}
1243        });
1244
1245        ruma_response_from_json(&data)
1246    }
1247}
1248
1249/// A set of keys query to test identity changes,
1250/// For user @malo, that performed an identity change with the same device.
1251pub struct MaloIdentityChangeDataSet {}
1252
1253impl MaloIdentityChangeDataSet {
1254    pub fn user_id() -> &'static UserId {
1255        user_id!("@malo:localhost")
1256    }
1257
1258    pub fn device_id() -> &'static DeviceId {
1259        device_id!("NZFSPBRLDO")
1260    }
1261
1262    /// @malo's keys before their identity change
1263    pub fn initial_key_query() -> KeyQueryResponse {
1264        let data = json!({
1265            "device_keys": {
1266                "@malo:localhost": {
1267                    "NZFSPBRLDO": {
1268                        "algorithms": [
1269                            "m.olm.v1.curve25519-aes-sha2",
1270                            "m.megolm.v1.aes-sha2"
1271                        ],
1272                        "device_id": "NZFSPBRLDO",
1273                        "keys": {
1274                            "curve25519:NZFSPBRLDO": "L3jdbw42+9i+K7LPjAY+kmqG9nr2n/U0ow8hEbLCoCs",
1275                            "ed25519:NZFSPBRLDO": "VDJt3xI4SzrgQkuE3sEIauluaXawx3wWoWOynPI8Zko"
1276                        },
1277                        "signatures": {
1278                            "@malo:localhost": {
1279                                "ed25519:NZFSPBRLDO": "lmtbdrJ5xBweo677Fg2qrSHsRi4R3x2WNlvSNJY6Zbg0R5lJS9syN2HZw/irL9PA644GYm4QM/t+DX0grnn+BQ",
1280                                "ed25519:+wbxNfSuDrch1jKuydQmEf4qlA4u4NgwqNXNuLVwug8": "Ql1fq+SvVDx+8mjNMzSaR0hBCEkdPirbs2+BK0gwsIH1zkuMADnBoNWP7LJiKo/EO9gnpiCzyQQgI4e9pIVPDA"
1281                            }
1282                        },
1283                        "user_id": "@malo:localhost",
1284                        "unsigned": {}
1285                    }
1286                }
1287            },
1288            "failures": {},
1289            "master_keys": {
1290                "@malo:localhost": {
1291                    "keys": {
1292                        "ed25519:WBxliSP29guYr4ux0MW6otRe3V/wOLXXElpOcOmpdlE": "WBxliSP29guYr4ux0MW6otRe3V/wOLXXElpOcOmpdlE"
1293                    },
1294                    "signatures": {
1295                        "@malo:localhost": {
1296                            "ed25519:NZFSPBRLDO": "crJcXqFpEHRM8KNUw419XrVFaHoM8/kV4ebgpuuIiD9wfX0AhHE2iGRGpKzsrVCqne9k181/uN0sgDMpK2y4Aw",
1297                            "ed25519:WBxliSP29guYr4ux0MW6otRe3V/wOLXXElpOcOmpdlE": "/xwFF5AC3GhkpvJ449Srh8kNQS6CXAxQMmBpQvPEHx5BHPXJ08u2ZDd1EPYY4zk4QsePk+tEYu8gDnB0bggHCA"
1298                        }
1299                    },
1300                    "usage": [
1301                        "master"
1302                    ],
1303                    "user_id": "@malo:localhost"
1304                }
1305            },
1306            "self_signing_keys": {
1307                "@malo:localhost": {
1308                    "keys": {
1309                        "ed25519:+wbxNfSuDrch1jKuydQmEf4qlA4u4NgwqNXNuLVwug8": "+wbxNfSuDrch1jKuydQmEf4qlA4u4NgwqNXNuLVwug8"
1310                    },
1311                    "signatures": {
1312                        "@malo:localhost": {
1313                            "ed25519:WBxliSP29guYr4ux0MW6otRe3V/wOLXXElpOcOmpdlE": "sSGQ6ny6aXtIvgKPGOYJzcmnNDSkbaJFVRe9wekOry7EaiWf2l28MkGTUBt4cPoRiMkNjuRBupNEARqHF72sAQ"
1314                        }
1315                    },
1316                    "usage": [
1317                        "self_signing"
1318                    ],
1319                    "user_id": "@malo:localhost"
1320                }
1321            },
1322            "user_signing_keys": {},
1323        });
1324
1325        ruma_response_from_json(&data)
1326    }
1327
1328    /// @malo's keys after their identity change
1329    pub fn updated_key_query() -> KeyQueryResponse {
1330        let data = json!({
1331            "device_keys": {
1332                "@malo:localhost": {
1333                    "NZFSPBRLDO": {
1334                        "algorithms": [
1335                            "m.olm.v1.curve25519-aes-sha2",
1336                            "m.megolm.v1.aes-sha2"
1337                        ],
1338                        "device_id": "NZFSPBRLDO",
1339                        "keys": {
1340                            "curve25519:NZFSPBRLDO": "L3jdbw42+9i+K7LPjAY+kmqG9nr2n/U0ow8hEbLCoCs",
1341                            "ed25519:NZFSPBRLDO": "VDJt3xI4SzrgQkuE3sEIauluaXawx3wWoWOynPI8Zko"
1342                        },
1343                        "signatures": {
1344                            "@malo:localhost": {
1345                                "ed25519:NZFSPBRLDO": "lmtbdrJ5xBweo677Fg2qrSHsRi4R3x2WNlvSNJY6Zbg0R5lJS9syN2HZw/irL9PA644GYm4QM/t+DX0grnn+BQ",
1346                                "ed25519:+wbxNfSuDrch1jKuydQmEf4qlA4u4NgwqNXNuLVwug8": "Ql1fq+SvVDx+8mjNMzSaR0hBCEkdPirbs2+BK0gwsIH1zkuMADnBoNWP7LJiKo/EO9gnpiCzyQQgI4e9pIVPDA",
1347                                "ed25519:8my6+zgnzEP0ZqmQFyvscJh7isHlf8lxBmHg+fzdJkE": "OvqDE7C2mrHxjwNyMIEz+m/AO6I6lM5HoPYY2bvLjrJJDOF5sJOtw4JoYiCWyt90ZIWsbEqmfbazrblLD50tCg"
1348                            }
1349                        },
1350                        "user_id": "@malo:localhost",
1351                        "unsigned": {}
1352                    }
1353                }
1354            },
1355            "failures": {},
1356            "master_keys": {
1357                "@malo:localhost": {
1358                    "keys": {
1359                        "ed25519:dv2Mk7bFlRtP/0oSZpB01Ouc5frCXKfG8Bn9YrFxbxU": "dv2Mk7bFlRtP/0oSZpB01Ouc5frCXKfG8Bn9YrFxbxU"
1360                    },
1361                    "signatures": {
1362                        "@malo:localhost": {
1363                            "ed25519:NZFSPBRLDO": "2Ye96l4srBSWskNQszuMpea1r97rFoUyfNqegvu/hGeP47w0OVvqYuNtZRNwqb7TMS7aPEn6l9lhWEk7v06wCg",
1364                            "ed25519:dv2Mk7bFlRtP/0oSZpB01Ouc5frCXKfG8Bn9YrFxbxU": "btkxAJpJeVtc9wgBmeHUI9QDpojd6ddLxK11E3403KoTQtP6Mnr5GsVdQr1HJToG7PG4k4eEZGWxVZr1GPndAA"
1365                        }
1366                    },
1367                    "usage": [
1368                        "master"
1369                    ],
1370                    "user_id": "@malo:localhost"
1371                }
1372            },
1373            "self_signing_keys": {
1374                "@malo:localhost": {
1375                    "keys": {
1376                        "ed25519:8my6+zgnzEP0ZqmQFyvscJh7isHlf8lxBmHg+fzdJkE": "8my6+zgnzEP0ZqmQFyvscJh7isHlf8lxBmHg+fzdJkE"
1377                    },
1378                    "signatures": {
1379                        "@malo:localhost": {
1380                            "ed25519:dv2Mk7bFlRtP/0oSZpB01Ouc5frCXKfG8Bn9YrFxbxU": "KJt0y1p8v8RGLGk2wUyCMbX1irXJqup/mdRuG/cxJxs24BZhDMyIzyGrGXnWq2gx3I4fKIMtFPi/ecxf92ePAQ"
1381                        }
1382                    },
1383                    "usage": [
1384                        "self_signing"
1385                    ],
1386                    "user_id": "@malo:localhost"
1387                }
1388            },
1389            "user_signing_keys": {}
1390        });
1391
1392        ruma_response_from_json(&data)
1393    }
1394}
1395
1396/// Calculate the signature for a JSON object, without adding that signature to
1397/// the object.
1398///
1399/// # Arguments
1400///
1401/// * `value` - the JSON object to be signed.
1402/// * `signing_key` - the Ed25519 key to sign with.
1403fn calculate_json_signature(mut value: Value, signing_key: &Ed25519SecretKey) -> Ed25519Signature {
1404    // strip `unsigned` and any existing signatures
1405    let json_object = value.as_object_mut().expect("value must be object");
1406    json_object.remove("signatures");
1407    json_object.remove("unsigned");
1408
1409    let canonical_json: CanonicalJsonValue =
1410        value.try_into().expect("could not convert to canonicaljson");
1411
1412    // do the signing
1413    signing_key.sign(canonical_json.to_string().as_ref())
1414}
1415
1416/// Add a signature to a JSON object, following the Matrix JSON-signing spec (https://spec.matrix.org/v1.12/appendices/#signing-details).
1417///
1418/// # Arguments
1419///
1420/// * `value` - the JSON object to be signed.
1421/// * `signing_key` - the Ed25519 key to sign with.
1422/// * `user_id` - the user doing the signing. This will be used to add the
1423///   signature to the object.
1424/// * `key_identifier` - the name of the key being used to sign with,
1425///   *excluding* the `ed25519` prefix.
1426///
1427/// # Panics
1428///
1429/// If the JSON value passed in is not an object, or contains a non-object
1430/// `signatures` property.
1431fn sign_json(
1432    value: &mut Value,
1433    signing_key: &Ed25519SecretKey,
1434    user_id: &UserId,
1435    key_identifier: &str,
1436) {
1437    let signature = calculate_json_signature(value.clone(), signing_key);
1438
1439    let value_obj = value.as_object_mut().expect("value must be object");
1440
1441    let signatures_obj = value_obj
1442        .entry("signatures")
1443        .or_insert_with(|| serde_json::Map::new().into())
1444        .as_object_mut()
1445        .expect("signatures key must be object");
1446
1447    let user_signatures_obj = signatures_obj
1448        .entry(user_id.to_string())
1449        .or_insert_with(|| serde_json::Map::new().into())
1450        .as_object_mut()
1451        .expect("signatures keys must be object");
1452
1453    user_signatures_obj.insert(format!("ed25519:{key_identifier}"), signature.to_base64().into());
1454}
1455
1456/// Add a signature to a [`CrossSigningKey`] object.
1457///
1458/// This is similar to [`sign_json`], but operates on the deserialized
1459/// [`CrossSigningKey`] object rather than the serialized JSON.
1460///
1461/// # Arguments
1462///
1463/// * `value` - the [`CrossSigningKey`] object to be signed.
1464/// * `signing_key` - the Ed25519 key to sign with.
1465/// * `user_id` - the user doing the signing. This will be used to add the
1466///   signature to the object.
1467fn sign_cross_signing_key(
1468    value: &mut CrossSigningKey,
1469    signing_key: &Ed25519SecretKey,
1470    user_id: &UserId,
1471) {
1472    let key_json = serde_json::to_value(value.clone()).unwrap();
1473    let signature = calculate_json_signature(key_json, signing_key);
1474
1475    // Poke the signature into the struct
1476    let signing_key_id: OwnedBase64PublicKeyOrDeviceId =
1477        OwnedBase64PublicKey::with_bytes(signing_key.public_key().as_bytes()).into();
1478
1479    value.signatures.insert_signature(
1480        user_id.to_owned(),
1481        CrossSigningOrDeviceSigningKeyId::from_parts(SigningKeyAlgorithm::Ed25519, &signing_key_id),
1482        signature.to_base64(),
1483    );
1484}