Skip to main content

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