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