Skip to main content

matrix_sdk_crypto/identities/
device.rs

1// Copyright 2020 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16    collections::{BTreeMap, HashMap},
17    ops::Deref,
18    sync::{
19        Arc,
20        atomic::{AtomicBool, Ordering},
21    },
22};
23
24use matrix_sdk_common::locks::RwLock;
25use ruma::{
26    DeviceId, DeviceKeyAlgorithm, DeviceKeyId, MilliSecondsSinceUnixEpoch, OwnedDeviceId,
27    OwnedDeviceKeyId, UInt, UserId,
28    api::client::keys::upload_signatures::v3::Request as SignatureUploadRequest,
29    events::{AnyToDeviceEventContent, key::verification::VerificationMethod},
30    serde::Raw,
31};
32use serde::{Deserialize, Serialize};
33use serde_json::Value;
34use tracing::{instrument, trace};
35use vodozemac::{Curve25519PublicKey, Ed25519PublicKey, olm::SessionConfig};
36
37use super::{atomic_bool_deserializer, atomic_bool_serializer};
38#[cfg(any(test, feature = "testing", doc))]
39use crate::OlmMachine;
40#[cfg(feature = "experimental-x509-identity-verification")]
41use crate::x509::X509Verifier;
42use crate::{
43    Account, Sas, VerificationRequest,
44    error::{MismatchedIdentityKeysError, OlmError, OlmResult, SignatureError},
45    identities::{OwnUserIdentityData, UserIdentityData},
46    olm::{InboundGroupSession, OutboundGroupSession, Session, ShareInfo, VerifyJson},
47    session_manager::{CollectStrategy, withheld_code_for_device_for_share_strategy},
48    store::{
49        CryptoStoreWrapper, Result as StoreResult,
50        caches::SequenceNumber,
51        types::{Changes, DeviceChanges},
52    },
53    types::{
54        DeviceKey, DeviceKeys, EventEncryptionAlgorithm, Signatures, SignedKey,
55        events::{
56            EventType, forwarded_room_key::ForwardedRoomKeyContent,
57            room::encrypted::ToDeviceEncryptedEventContent,
58        },
59        requests::{OutgoingVerificationRequest, ToDeviceRequest},
60    },
61    verification::VerificationMachine,
62};
63
64pub enum MaybeEncryptedRoomKey {
65    Encrypted {
66        // `Box` the session to reduce the size of `Encrypted`.
67        used_session: Box<Session>,
68        // `Box` the session to reduce the size of `Encrypted`.
69        share_info: Box<ShareInfo>,
70        message: Raw<AnyToDeviceEventContent>,
71    },
72    /// We could not encrypt a message to this device because there is no active
73    /// Olm session.
74    MissingSession,
75}
76
77/// A read-only version of a `Device`.
78#[derive(Clone, Serialize, Deserialize)]
79pub struct DeviceData {
80    #[serde(alias = "inner")]
81    pub(crate) device_keys: Arc<DeviceKeys>,
82    #[serde(
83        serialize_with = "atomic_bool_serializer",
84        deserialize_with = "atomic_bool_deserializer"
85    )]
86    deleted: Arc<AtomicBool>,
87    trust_state: Arc<RwLock<LocalTrust>>,
88    /// Flag remembering if we successfully sent an `m.no_olm` withheld code to
89    /// this device.
90    #[serde(
91        default,
92        serialize_with = "atomic_bool_serializer",
93        deserialize_with = "atomic_bool_deserializer"
94    )]
95    withheld_code_sent: Arc<AtomicBool>,
96    /// First time this device was seen in milliseconds since epoch. Default to
97    /// epoch for migration purpose.
98    #[serde(default = "default_timestamp")]
99    first_time_seen_ts: MilliSecondsSinceUnixEpoch,
100    /// The number of times the device has tried to unwedge Olm sessions with
101    /// us.
102    #[serde(default)]
103    pub(crate) olm_wedging_index: SequenceNumber,
104}
105
106fn default_timestamp() -> MilliSecondsSinceUnixEpoch {
107    MilliSecondsSinceUnixEpoch(UInt::default())
108}
109
110#[cfg(not(tarpaulin_include))]
111impl std::fmt::Debug for DeviceData {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        f.debug_struct("DeviceData")
114            .field("user_id", &self.user_id())
115            .field("device_id", &self.device_id())
116            .field("display_name", &self.display_name())
117            .field("keys", self.keys())
118            .field("deleted", &self.deleted.load(Ordering::SeqCst))
119            .field("trust_state", &self.trust_state)
120            .field("withheld_code_sent", &self.withheld_code_sent)
121            .finish()
122    }
123}
124
125/// A device represents a E2EE capable client of an user.
126#[derive(Clone)]
127pub struct Device {
128    pub(crate) inner: DeviceData,
129    pub(crate) verification_machine: VerificationMachine,
130    pub(crate) own_identity: Option<OwnUserIdentityData>,
131    pub(crate) device_owner_identity: Option<UserIdentityData>,
132    #[cfg(feature = "experimental-x509-identity-verification")]
133    pub(crate) x509_verifier: Option<X509Verifier>,
134}
135
136#[cfg(not(tarpaulin_include))]
137impl std::fmt::Debug for Device {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        f.debug_struct("Device").field("device", &self.inner).finish()
140    }
141}
142
143impl Deref for Device {
144    type Target = DeviceData;
145
146    fn deref(&self) -> &Self::Target {
147        &self.inner
148    }
149}
150
151impl Device {
152    /// Start a interactive verification with this `Device`
153    ///
154    /// Returns a `Sas` object and a to-device request that needs to be sent
155    /// out.
156    ///
157    /// This method has been deprecated in the spec and the
158    /// [`request_verification()`] method should be used instead.
159    ///
160    /// [`request_verification()`]: #method.request_verification
161    pub async fn start_verification(&self) -> StoreResult<(Sas, ToDeviceRequest)> {
162        let (sas, request) = self.verification_machine.start_sas(self.inner.clone()).await?;
163
164        if let OutgoingVerificationRequest::ToDevice(r) = request {
165            Ok((sas, r))
166        } else {
167            panic!("Invalid verification request type");
168        }
169    }
170
171    /// Is this our own device?
172    pub fn is_our_own_device(&self) -> bool {
173        let own_ed25519_key = self.verification_machine.store.account.identity_keys.ed25519;
174        let own_curve25519_key = self.verification_machine.store.account.identity_keys.curve25519;
175
176        self.user_id() == self.verification_machine.own_user_id()
177            && self.device_id() == self.verification_machine.own_device_id()
178            && self.ed25519_key().is_some_and(|k| k == own_ed25519_key)
179            && self.curve25519_key().is_some_and(|k| k == own_curve25519_key)
180    }
181
182    /// Does the given `InboundGroupSession` belong to this device?
183    ///
184    /// An `InboundGroupSession` is exchanged between devices as an Olm
185    /// encrypted `m.room_key` event. This method determines if this `Device`
186    /// can be confirmed as the creator and owner of the `m.room_key`.
187    pub fn is_owner_of_session(
188        &self,
189        session: &InboundGroupSession,
190    ) -> Result<bool, MismatchedIdentityKeysError> {
191        if session.has_been_imported() {
192            // An imported room key means that we did not receive the room key
193            // as a `m.room_key` event when the room key was initially
194            // exchanged.
195            //
196            // This could mean a couple of things:
197            //      1. We received the room key as a `m.forwarded_room_key`.
198            //      2. We imported the room key through a file export.
199            //      3. We imported the room key through a backup.
200            //
201            // To be certain that a `Device` is the owner of a room key we need
202            // to have a proof that the `Curve25519` key of this `Device` was
203            // used to initially exchange the room key. This proof is provided
204            // by the Olm decryption step, see below for further clarification.
205            //
206            // Each of the above room key methods that receive room keys do not
207            // contain this proof and we received only a claim that the room key
208            // is tied to a `Curve25519` key.
209            //
210            // Since there's no way to verify that the claim is true, we say
211            // that we don't know that the room key belongs to this device.
212            Ok(false)
213        } else if let Some(key) =
214            session.signing_keys().get(&DeviceKeyAlgorithm::Ed25519).and_then(|k| k.ed25519())
215        {
216            // Room keys are received as an `m.room.encrypted` to-device message
217            // using the `m.olm` algorithm. Upon decryption of the
218            // `m.room.encrypted` to-device message, the decrypted content will
219            // contain also an `Ed25519` public key[1].
220            //
221            // The inclusion of this key means that the `Curve25519` key of the
222            // `Device` and Olm `Session`, established using the DH
223            // authentication of the double ratchet, "binds" the `Ed25519` key
224            // of the `Device`. In other words, it prevents an attack in which
225            // Mallory publishes Bob's public `Curve25519` key as her own, and
226            // subsequently forwards an Olm message she received from Bob to
227            // Alice, claiming that she, Mallory, originated the Olm message
228            // (leading Alice to believe that Mallory also sent the messages in
229            // the subsequent Megolm session).
230            //
231            // On the other hand, the `Ed25519` key binds the `Curve25519` key
232            // using a signature which is uploaded to the server as
233            // `device_keys` and downloaded by us using a `/keys/query` request.
234            //
235            // A `Device` is considered to be the owner of a room key iff:
236            //
237            // 1. The `Curve25519` key that was used to establish the Olm
238            //    `Session` that was used to decrypt the to-device message is
239            //    binding the `Ed25519` key of this `Device` via the content of
240            //    the to-device message, and:
241            // 2. The `Ed25519` key of this device has signed a `device_keys`
242            //    object that contains the `Curve25519` key from step 1.
243            //
244            // We don't need to check the signature of the `Device` here, since
245            // we don't accept a `Device` unless it has a valid `Ed25519`
246            // signature.
247            //
248            // We do check that the `Curve25519` that was used to decrypt the
249            // event carrying the `m.room_key` and the `Ed25519` key that was
250            // part of the decrypted content matches the keys found in this
251            // `Device`.
252            //
253            // ```text
254            //                                              ┌───────────────────────┐
255            //                                              │ EncryptedToDeviceEvent│
256            //                                              └───────────────────────┘
257            //                                                         │
258            //    ┌──────────────────────────────────┐                 │
259            //    │              Device              │                 ▼
260            //    ├──────────────────────────────────┤        ┌──────────────────┐
261            //    │            Device Keys           │        │      Session     │
262            //    ├────────────────┬─────────────────┤        ├──────────────────┤
263            //    │   Ed25519 Key  │  Curve25519 Key │◄──────►│  Curve25519 Key  │
264            //    └────────────────┴─────────────────┘        └──────────────────┘
265            //            ▲                                            │
266            //            │                                            │
267            //            │                                            │ Decrypt
268            //            │                                            │
269            //            │                                            ▼
270            //            │                                 ┌───────────────────────┐
271            //            │                                 │  DecryptedOlmV1Event  │
272            //            │                                 ├───────────────────────┤
273            //            │                                 │         keys          │
274            //            │                                 ├───────────────────────┤
275            //            └────────────────────────────────►│       Ed25519 Key     │
276            //                                              └───────────────────────┘
277            // ```
278            //
279            // [1]: https://spec.matrix.org/v1.5/client-server-api/#molmv1curve25519-aes-sha2
280            let ed25519_comparison = self.ed25519_key().map(|k| k == key);
281            let curve25519_comparison = self.curve25519_key().map(|k| k == session.sender_key());
282
283            match (ed25519_comparison, curve25519_comparison) {
284                // If we have any of the keys but they don't turn out to match,
285                // refuse to decrypt instead.
286                (_, Some(false)) | (Some(false), _) => Err(MismatchedIdentityKeysError {
287                    key_ed25519: key.into(),
288                    device_ed25519: self.ed25519_key().map(Into::into),
289                    key_curve25519: session.sender_key().into(),
290                    device_curve25519: self.curve25519_key().map(Into::into),
291                }),
292                // If both keys match, we have ourselves an owner.
293                (Some(true), Some(true)) => Ok(true),
294                // In the remaining cases, the device is missing at least one of
295                // the required identity keys, so we default to a negative
296                // answer.
297                _ => Ok(false),
298            }
299        } else {
300            Ok(false)
301        }
302    }
303
304    /// Is this device cross signed by its owner?
305    pub fn is_cross_signed_by_owner(&self) -> bool {
306        self.device_owner_identity
307            .as_ref()
308            .is_some_and(|owner_identity| self.inner.is_cross_signed_by_owner(owner_identity))
309    }
310
311    /// Is the device owner verified by us?
312    pub fn is_device_owner_verified(&self) -> bool {
313        self.device_owner_identity.as_ref().is_some_and(|id| match id {
314            UserIdentityData::Own(own_identity) => own_identity.is_verified(),
315            UserIdentityData::Other(other_identity) => other_identity.is_verified(
316                self.own_identity.as_ref(),
317                #[cfg(feature = "experimental-x509-identity-verification")]
318                self.x509_verifier.as_ref(),
319            ),
320        })
321    }
322
323    /// Request an interactive verification with this `Device`.
324    ///
325    /// Returns a `VerificationRequest` object and a to-device request that
326    /// needs to be sent out.
327    pub fn request_verification(&self) -> (VerificationRequest, OutgoingVerificationRequest) {
328        self.request_verification_helper(None)
329    }
330
331    /// Request an interactive verification with this `Device`.
332    ///
333    /// Returns a `VerificationRequest` object and a to-device request that
334    /// needs to be sent out.
335    ///
336    /// # Arguments
337    ///
338    /// - `methods` - The verification methods that we want to support.
339    pub fn request_verification_with_methods(
340        &self,
341        methods: Vec<VerificationMethod>,
342    ) -> (VerificationRequest, OutgoingVerificationRequest) {
343        self.request_verification_helper(Some(methods))
344    }
345
346    fn request_verification_helper(
347        &self,
348        methods: Option<Vec<VerificationMethod>>,
349    ) -> (VerificationRequest, OutgoingVerificationRequest) {
350        self.verification_machine.request_to_device_verification(
351            self.user_id(),
352            vec![self.device_id().to_owned()],
353            methods,
354        )
355    }
356
357    /// Get the most recently created session that belongs to this device.
358    pub(crate) async fn get_most_recent_session(&self) -> OlmResult<Option<Session>> {
359        self.inner.get_most_recent_session(self.verification_machine.store.inner()).await
360    }
361
362    /// Is this device considered to be verified.
363    ///
364    /// This method returns true if either [`is_locally_trusted()`] returns true
365    /// or if [`is_cross_signing_trusted()`] returns true.
366    ///
367    /// [`is_locally_trusted()`]: #method.is_locally_trusted
368    /// [`is_cross_signing_trusted()`]: #method.is_cross_signing_trusted
369    pub fn is_verified(&self) -> bool {
370        self.inner.is_verified(
371            &self.own_identity,
372            &self.device_owner_identity,
373            #[cfg(feature = "experimental-x509-identity-verification")]
374            self.x509_verifier.as_ref(),
375        )
376    }
377
378    /// Is this device considered to be verified using cross signing.
379    pub fn is_cross_signing_trusted(&self) -> bool {
380        self.inner.is_cross_signing_trusted(
381            &self.own_identity,
382            &self.device_owner_identity,
383            #[cfg(feature = "experimental-x509-identity-verification")]
384            self.x509_verifier.as_ref(),
385        )
386    }
387
388    /// Manually verify this device.
389    ///
390    /// This method will attempt to sign the device using our private cross
391    /// signing key.
392    ///
393    /// This method will always fail if the device belongs to someone else, we
394    /// can only sign our own devices.
395    ///
396    /// It can also fail if we don't have the private part of our self-signing
397    /// key.
398    ///
399    /// Returns a request that needs to be sent out for the device to be marked
400    /// as verified.
401    pub async fn verify(&self) -> Result<SignatureUploadRequest, SignatureError> {
402        if self.user_id() == self.verification_machine.own_user_id() {
403            Ok(self
404                .verification_machine
405                .store
406                .private_identity
407                .lock()
408                .await
409                .sign_device(&self.inner)
410                .await?)
411        } else {
412            Err(SignatureError::UserIdMismatch)
413        }
414    }
415
416    /// Set the local trust state of the device to the given state.
417    ///
418    /// This won't affect any cross signing trust state, this only sets a flag
419    /// marking to have the given trust state.
420    ///
421    /// # Arguments
422    ///
423    /// - `trust_state` - The new trust state that should be set for the device.
424    pub async fn set_local_trust(&self, trust_state: LocalTrust) -> StoreResult<()> {
425        self.inner.set_trust_state(trust_state);
426
427        let changes = Changes {
428            devices: DeviceChanges { changed: vec![self.inner.clone()], ..Default::default() },
429            ..Default::default()
430        };
431
432        self.verification_machine.store.save_changes(changes).await
433    }
434
435    /// Encrypt the given content for this `Device`.
436    ///
437    /// # Arguments
438    ///
439    /// - `event_type` - The type of the event that should be encrypted.
440    /// - `content` - The content of the event that should be encrypted.
441    ///
442    /// # Returns
443    ///
444    /// On success, a tuple `(session, content, message_id)`, where `session` is
445    /// the Olm [`Session`] that was used to encrypt the content, `content` is
446    /// the content for the `m.room.encrypted` to-device event, and `message_id`
447    /// is the newly-minted message ID stored within the content.
448    ///
449    /// If an Olm session has not already been established with this device,
450    /// returns `Err(OlmError::MissingSession)`.
451    pub(crate) async fn encrypt(
452        &self,
453        event_type: &str,
454        content: impl Serialize,
455    ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>, String)> {
456        self.inner.encrypt(self.verification_machine.store.inner(), event_type, content).await
457    }
458
459    /// Encrypt the given inbound group session as a forwarded room key for this
460    /// device.
461    pub async fn encrypt_room_key_for_forwarding(
462        &self,
463        session: InboundGroupSession,
464        message_index: Option<u32>,
465    ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>)> {
466        let content: ForwardedRoomKeyContent = {
467            let export = if let Some(index) = message_index {
468                session.export_at_index(index).await
469            } else {
470                session.export().await
471            };
472
473            export.try_into()?
474        };
475
476        let event_type = content.event_type().to_owned();
477
478        self.encrypt(&event_type, content)
479            .await
480            .map(|(session, message, _message_id)| (session, message))
481    }
482
483    /// Encrypt an event for this device.
484    ///
485    /// Beware that the 1-to-1 session must be established prior to this call by
486    /// using the [`OlmMachine::get_missing_sessions`] method.
487    ///
488    /// Notable limitation: The caller is responsible for sending the encrypted
489    /// event to the target device, this encryption method supports out-of-order
490    /// messages to a certain extent (2000 messages), if multiple messages are
491    /// encrypted using this method they should be sent in the same order as
492    /// they are encrypted.
493    ///
494    /// _Note_: To instead encrypt an event meant for a room use the
495    /// [`OlmMachine::encrypt_room_event()`] method instead.
496    ///
497    /// # Arguments
498    ///
499    /// - `event_type` - The type of the event to be sent.
500    /// - `content` - The content of the event to be sent. This should be a type
501    ///   that implements the `Serialize` trait.
502    /// - `share_strategy` - The share strategy to use to determine whether we
503    ///   should encrypt to the device.
504    ///
505    /// # Returns
506    ///
507    /// The encrypted raw content to be shared with your preferred transport
508    /// layer (usually to-device), [`OlmError::MissingSession`] if there is no
509    /// established session with the device.
510    pub async fn encrypt_event_raw(
511        &self,
512        event_type: &str,
513        content: &Value,
514        share_strategy: CollectStrategy,
515    ) -> OlmResult<Raw<ToDeviceEncryptedEventContent>> {
516        if let Some(withheld_code) = withheld_code_for_device_for_share_strategy(
517            &self.inner,
518            share_strategy,
519            &self.own_identity,
520            &self.device_owner_identity,
521            #[cfg(feature = "experimental-x509-identity-verification")]
522            self.x509_verifier.as_ref(),
523        )
524        .await?
525        {
526            return Err(OlmError::Withheld(withheld_code));
527        }
528
529        let (used_session, raw_encrypted, _message_id) = self.encrypt(event_type, content).await?;
530
531        // Persist the used session
532        self.verification_machine
533            .store
534            .save_changes(Changes { sessions: vec![used_session], ..Default::default() })
535            .await?;
536
537        Ok(raw_encrypted)
538    }
539
540    /// True if this device is an [MSC3814](https://github.com/matrix-org/matrix-spec-proposals/pull/3814) dehydrated device.
541    pub fn is_dehydrated(&self) -> bool {
542        self.inner.is_dehydrated()
543    }
544}
545
546/// A read only view over all devices belonging to a user.
547#[derive(Debug)]
548pub struct UserDevices {
549    pub(crate) inner: HashMap<OwnedDeviceId, DeviceData>,
550    pub(crate) verification_machine: VerificationMachine,
551    pub(crate) own_identity: Option<OwnUserIdentityData>,
552    pub(crate) device_owner_identity: Option<UserIdentityData>,
553    #[cfg(feature = "experimental-x509-identity-verification")]
554    pub(crate) x509_verifier: Option<X509Verifier>,
555}
556
557impl UserDevices {
558    /// Get the specific device with the given device ID.
559    pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
560        self.inner.get(device_id).map(|d| Device {
561            inner: d.clone(),
562            verification_machine: self.verification_machine.clone(),
563            own_identity: self.own_identity.clone(),
564            device_owner_identity: self.device_owner_identity.clone(),
565            #[cfg(feature = "experimental-x509-identity-verification")]
566            x509_verifier: self.x509_verifier.clone(),
567        })
568    }
569
570    fn own_user_id(&self) -> &UserId {
571        self.verification_machine.own_user_id()
572    }
573
574    fn own_device_id(&self) -> &DeviceId {
575        self.verification_machine.own_device_id()
576    }
577
578    /// Iterator over the data of all the user's devices, excluding our own
579    /// device.
580    fn other_devices(&self) -> impl Iterator<Item = &DeviceData> {
581        self.inner.values().filter(|d| {
582            !(d.user_id() == self.own_user_id() && d.device_id() == self.own_device_id())
583        })
584    }
585
586    /// Returns true if there is at least one device of this user that is
587    /// considered to be verified, false otherwise.
588    ///
589    /// This won't consider your own device as verified, as your own device is
590    /// always implicitly verified.
591    pub fn is_any_verified(&self) -> bool {
592        self.other_devices().any(|d| {
593            d.is_verified(
594                &self.own_identity,
595                &self.device_owner_identity,
596                #[cfg(feature = "experimental-x509-identity-verification")]
597                self.x509_verifier.as_ref(),
598            )
599        })
600    }
601
602    /// Returns true if the user has a device, other than our own, that we
603    /// could run an interactive verification against.
604    ///
605    /// To be a candidate for verification, the device must be cross-signed by
606    /// its owner, support Olm (have a Curve25519 key), and not be a
607    /// [dehydrated device].
608    ///
609    /// Unlike [`UserDevices::is_any_verified`], this does not require us to
610    /// trust the owner's identity, only that the owner has signed the device,
611    /// so it can be used from a device that has not itself been verified yet.
612    ///
613    /// [dehydrated device]: https://github.com/matrix-org/matrix-spec-proposals/pull/3814
614    pub fn has_other_devices_to_verify_against(&self) -> bool {
615        let Some(owner_identity) = &self.device_owner_identity else {
616            return false;
617        };
618
619        self.other_devices().any(|d| {
620            d.is_cross_signed_by_owner(owner_identity)
621                && d.curve25519_key().is_some()
622                && !d.is_dehydrated()
623        })
624    }
625
626    /// Iterator over all the device ids of the user devices.
627    pub fn keys(&self) -> impl Iterator<Item = &DeviceId> {
628        self.inner.keys().map(Deref::deref)
629    }
630
631    /// Iterator over all the devices of the user devices.
632    pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
633        self.inner.values().map(move |d| Device {
634            inner: d.clone(),
635            verification_machine: self.verification_machine.clone(),
636            own_identity: self.own_identity.clone(),
637            device_owner_identity: self.device_owner_identity.clone(),
638            #[cfg(feature = "experimental-x509-identity-verification")]
639            x509_verifier: self.x509_verifier.clone(),
640        })
641    }
642}
643
644/// The local trust state of a device.
645#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
646#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
647pub enum LocalTrust {
648    /// The device has been verified and is trusted.
649    Verified = 0,
650    /// The device been blacklisted from communicating.
651    BlackListed = 1,
652    /// The trust state of the device is being ignored.
653    Ignored = 2,
654    /// The trust state is unset.
655    Unset = 3,
656}
657
658impl From<i64> for LocalTrust {
659    fn from(state: i64) -> Self {
660        match state {
661            0 => LocalTrust::Verified,
662            1 => LocalTrust::BlackListed,
663            2 => LocalTrust::Ignored,
664            3 => LocalTrust::Unset,
665            _ => LocalTrust::Unset,
666        }
667    }
668}
669
670impl DeviceData {
671    /// Create a new Device, this constructor skips signature verification of
672    /// the keys, `TryFrom` should be used for completely new devices we
673    /// receive.
674    pub fn new(device_keys: DeviceKeys, trust_state: LocalTrust) -> Self {
675        Self {
676            device_keys: device_keys.into(),
677            trust_state: Arc::new(RwLock::new(trust_state)),
678            deleted: Arc::new(AtomicBool::new(false)),
679            withheld_code_sent: Arc::new(AtomicBool::new(false)),
680            first_time_seen_ts: MilliSecondsSinceUnixEpoch::now(),
681            olm_wedging_index: Default::default(),
682        }
683    }
684
685    /// The user id of the device owner.
686    pub fn user_id(&self) -> &UserId {
687        &self.device_keys.user_id
688    }
689
690    /// The unique ID of the device.
691    pub fn device_id(&self) -> &DeviceId {
692        &self.device_keys.device_id
693    }
694
695    /// Get the human readable name of the device.
696    pub fn display_name(&self) -> Option<&str> {
697        self.device_keys.unsigned.device_display_name.as_deref()
698    }
699
700    /// Get the key of the given key algorithm belonging to this device.
701    pub fn get_key(&self, algorithm: DeviceKeyAlgorithm) -> Option<&DeviceKey> {
702        self.device_keys.get_key(algorithm)
703    }
704
705    /// Get the Curve25519 key of the given device.
706    pub fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
707        self.device_keys.curve25519_key()
708    }
709
710    /// Get the Ed25519 key of the given device.
711    pub fn ed25519_key(&self) -> Option<Ed25519PublicKey> {
712        self.device_keys.ed25519_key()
713    }
714
715    /// Get a map containing all the device keys.
716    pub fn keys(&self) -> &BTreeMap<OwnedDeviceKeyId, DeviceKey> {
717        &self.device_keys.keys
718    }
719
720    /// Get a map containing all the device signatures.
721    pub fn signatures(&self) -> &Signatures {
722        &self.device_keys.signatures
723    }
724
725    /// Get the trust state of the device.
726    pub fn local_trust_state(&self) -> LocalTrust {
727        *self.trust_state.read()
728    }
729
730    /// Is the device locally marked as trusted.
731    pub fn is_locally_trusted(&self) -> bool {
732        self.local_trust_state() == LocalTrust::Verified
733    }
734
735    /// Is the device locally marked as blacklisted.
736    ///
737    /// Blacklisted devices won't receive any group sessions.
738    pub fn is_blacklisted(&self) -> bool {
739        self.local_trust_state() == LocalTrust::BlackListed
740    }
741
742    /// Set the trust state of the device to the given state.
743    ///
744    /// Note: This should only done in the crypto store where the trust state
745    /// can be stored.
746    pub(crate) fn set_trust_state(&self, state: LocalTrust) {
747        *self.trust_state.write() = state;
748    }
749
750    pub(crate) fn mark_withheld_code_as_sent(&self) {
751        self.withheld_code_sent.store(true, Ordering::Relaxed)
752    }
753
754    /// Returns true if the `m.no_olm` withheld code was already sent to this
755    /// device.
756    pub fn was_withheld_code_sent(&self) -> bool {
757        self.withheld_code_sent.load(Ordering::Relaxed)
758    }
759
760    /// Get the list of algorithms this device supports.
761    pub fn algorithms(&self) -> &[EventEncryptionAlgorithm] {
762        &self.device_keys.algorithms
763    }
764
765    /// Does this device support any of our known Olm encryption algorithms.
766    pub fn supports_olm(&self) -> bool {
767        #[cfg(feature = "experimental-algorithms")]
768        {
769            self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
770                || self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
771        }
772
773        #[cfg(not(feature = "experimental-algorithms"))]
774        {
775            self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
776        }
777    }
778
779    /// Find and return the most recently created Olm [`Session`] we are sharing
780    /// with this device.
781    pub(crate) async fn get_most_recent_session(
782        &self,
783        store: &CryptoStoreWrapper,
784    ) -> OlmResult<Option<Session>> {
785        if let Some(sender_key) = self.curve25519_key() {
786            if let Some(sessions) = store.get_sessions(&sender_key.to_base64()).await? {
787                let mut sessions = sessions.lock().await;
788                sessions.sort_by_key(|s| s.creation_time);
789
790                Ok(sessions.last().cloned())
791            } else {
792                Ok(None)
793            }
794        } else {
795            Ok(None)
796        }
797    }
798
799    /// Does this device support the olm.v2.curve25519-aes-sha2 encryption
800    /// algorithm.
801    #[cfg(feature = "experimental-algorithms")]
802    pub fn supports_olm_v2(&self) -> bool {
803        self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
804    }
805
806    /// Get the optimal `SessionConfig` for this device.
807    pub fn olm_session_config(&self) -> SessionConfig {
808        #[cfg(feature = "experimental-algorithms")]
809        if self.supports_olm_v2() {
810            SessionConfig::version_2()
811        } else {
812            SessionConfig::version_1()
813        }
814
815        #[cfg(not(feature = "experimental-algorithms"))]
816        SessionConfig::version_1()
817    }
818
819    /// Is the device deleted.
820    pub fn is_deleted(&self) -> bool {
821        self.deleted.load(Ordering::Relaxed)
822    }
823
824    pub(crate) fn is_verified(
825        &self,
826        own_identity: &Option<OwnUserIdentityData>,
827        device_owner: &Option<UserIdentityData>,
828        #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
829            &X509Verifier,
830        >,
831    ) -> bool {
832        self.is_locally_trusted()
833            || self.is_cross_signing_trusted(
834                own_identity,
835                device_owner,
836                #[cfg(feature = "experimental-x509-identity-verification")]
837                x509_verifier,
838            )
839    }
840
841    pub(crate) fn is_cross_signing_trusted(
842        &self,
843        own_identity: &Option<OwnUserIdentityData>,
844        device_owner: &Option<UserIdentityData>,
845        #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
846            &X509Verifier,
847        >,
848    ) -> bool {
849        device_owner.as_ref().is_some_and(|device_identity| match device_identity {
850            UserIdentityData::Own(_) => own_identity.as_ref().is_some_and(|own_identity| {
851                own_identity.is_verified() && own_identity.is_device_signed(self)
852            }),
853
854            // If it's a device from someone else, first check that our user has
855            // verified the other user (either by cross-signing their identity,
856            // or via a valid X.509 signature on their master key) and then
857            // check if the other user has signed this device.
858            UserIdentityData::Other(device_identity) => {
859                device_identity.is_verified(
860                    own_identity.as_ref(),
861                    #[cfg(feature = "experimental-x509-identity-verification")]
862                    x509_verifier,
863                ) && device_identity.is_device_signed(self)
864            }
865        })
866    }
867
868    pub(crate) fn is_cross_signed_by_owner(
869        &self,
870        device_owner_identity: &UserIdentityData,
871    ) -> bool {
872        match device_owner_identity {
873            // If it's one of our own devices, just check that we signed the
874            // device.
875            UserIdentityData::Own(identity) => identity.is_device_signed(self),
876            // If it's a device from someone else, check if the other user has
877            // signed this device.
878            UserIdentityData::Other(device_identity) => device_identity.is_device_signed(self),
879        }
880    }
881
882    /// Encrypt the given content for this device.
883    ///
884    /// # Arguments
885    ///
886    /// - `store` - The crypto store. Used to find an established Olm session
887    ///   for this device.
888    /// - `event_type` - The type of the event that should be encrypted.
889    /// - `content` - The content of the event that should be encrypted.
890    ///
891    /// # Returns
892    ///
893    /// On success, a tuple `(session, content, message_id)`, where `session` is
894    /// the Olm [`Session`] that was used to encrypt the content, `content` is
895    /// the content for the `m.room.encrypted` to-device event, and `message_id`
896    /// is the newly-minted message ID stored within the content.
897    ///
898    /// If an Olm session has not already been established with this device,
899    /// returns `Err(OlmError::MissingSession)`.
900    #[instrument(
901        skip_all,
902        fields(
903            recipient = ?self.user_id(),
904            recipient_device = ?self.device_id(),
905            recipient_key = ?self.curve25519_key(),
906            event_type,
907            message_id,
908        ))
909    ]
910    pub(crate) async fn encrypt(
911        &self,
912        store: &CryptoStoreWrapper,
913        event_type: &str,
914        content: impl Serialize,
915    ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>, String)> {
916        #[cfg(not(target_family = "wasm"))]
917        let message_id = ulid::Ulid::generate().to_string();
918        #[cfg(target_family = "wasm")]
919        let message_id = ruma::TransactionId::new().to_string();
920
921        tracing::Span::current().record("message_id", &message_id);
922
923        let session = self.get_most_recent_session(store).await?;
924
925        if let Some(mut session) = session {
926            let message =
927                session.encrypt(self, event_type, content, Some(message_id.clone())).await?;
928
929            Ok((session, message, message_id))
930        } else {
931            trace!("Trying to encrypt an event for a device, but no Olm session is found.");
932            Err(OlmError::MissingSession)
933        }
934    }
935
936    pub(crate) async fn maybe_encrypt_room_key(
937        &self,
938        store: &CryptoStoreWrapper,
939        session: OutboundGroupSession,
940    ) -> OlmResult<MaybeEncryptedRoomKey> {
941        let content = session.as_content().await;
942        let message_index = session.message_index().await;
943        let event_type = content.event_type().to_owned();
944
945        match self.encrypt(store, &event_type, content).await {
946            Ok((session, encrypted, _)) => Ok(MaybeEncryptedRoomKey::Encrypted {
947                share_info: Box::new(ShareInfo::new_shared(
948                    session.sender_key().to_owned(),
949                    message_index,
950                    self.olm_wedging_index,
951                )),
952                used_session: Box::new(session),
953                message: encrypted.cast(),
954            }),
955
956            Err(OlmError::MissingSession) => Ok(MaybeEncryptedRoomKey::MissingSession),
957            Err(e) => Err(e),
958        }
959    }
960
961    /// Update a device with a new device keys struct.
962    ///
963    /// Returns `true` if any changes were made to the data.
964    pub(crate) fn update_device(
965        &mut self,
966        device_keys: &DeviceKeys,
967    ) -> Result<bool, SignatureError> {
968        device_keys.check_self_signature()?;
969
970        if self.user_id() != device_keys.user_id || self.device_id() != device_keys.device_id {
971            Err(SignatureError::UserIdMismatch)
972        } else if self.ed25519_key() != device_keys.ed25519_key() {
973            Err(SignatureError::SigningKeyChanged(
974                self.ed25519_key().map(Box::new),
975                device_keys.ed25519_key().map(Box::new),
976            ))
977        } else if self.device_keys.as_ref() != device_keys {
978            trace!(
979                user_id = ?self.user_id(),
980                device_id = ?self.device_id(),
981                keys = ?self.keys(),
982                "Updated a device",
983            );
984
985            self.device_keys = device_keys.clone().into();
986
987            Ok(true)
988        } else {
989            // no changes needed
990            Ok(false)
991        }
992    }
993
994    /// Return the device keys
995    pub fn as_device_keys(&self) -> &DeviceKeys {
996        &self.device_keys
997    }
998
999    /// Check if the given JSON is signed by this device key.
1000    ///
1001    /// This method should only be used if an object's signature needs to be
1002    /// checked multiple times, and you'd like to avoid performing the
1003    /// canonicalization step each time.
1004    ///
1005    /// **Note**: Use this method with caution, the `canonical_json` needs to be
1006    /// correctly canonicalized and make sure that the object you are checking
1007    /// the signature for is allowed to be signed by a device.
1008    pub(crate) fn has_signed_raw(
1009        &self,
1010        signatures: &Signatures,
1011        canonical_json: &str,
1012    ) -> Result<(), SignatureError> {
1013        let key = self.ed25519_key().ok_or(SignatureError::MissingSigningKey)?;
1014        let user_id = self.user_id();
1015        let key_id = &DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, self.device_id());
1016
1017        key.verify_canonicalized_json(user_id, key_id, signatures, canonical_json)
1018    }
1019
1020    pub(crate) fn verify_one_time_key(
1021        &self,
1022        one_time_key: &SignedKey,
1023    ) -> Result<(), SignatureError> {
1024        self.device_keys.has_signed(one_time_key)
1025    }
1026
1027    /// Mark the device as deleted.
1028    pub(crate) fn mark_as_deleted(&self) {
1029        self.deleted.store(true, Ordering::Relaxed);
1030    }
1031
1032    #[cfg(any(test, feature = "testing"))]
1033    #[allow(dead_code)]
1034    /// Generate the Device from a reference of an OlmMachine.
1035    pub async fn from_machine_test_helper(
1036        machine: &OlmMachine,
1037    ) -> Result<DeviceData, crate::CryptoStoreError> {
1038        Ok(DeviceData::from_account(&*machine.store().cache().await?.account().await?))
1039    }
1040
1041    /// Create [`DeviceData`] from an [`Account`].
1042    ///
1043    /// We will have our own device data in the store once we receive a
1044    /// `/keys/query` response, but this is useful to create it before we
1045    /// receive such a response.
1046    ///
1047    /// It also makes it easier to check that the server doesn't lie about our
1048    /// own device.
1049    ///
1050    /// _Don't_ use this after we received a `/keys/query` response, other
1051    /// users/devices might add signatures to our own device, which can't be
1052    /// replicated locally.
1053    pub fn from_account(account: &Account) -> DeviceData {
1054        let device_keys = account.device_keys();
1055        let mut device = DeviceData::try_from(&device_keys)
1056            .expect("Creating a device from our own account should always succeed");
1057        device.first_time_seen_ts = account.creation_local_time();
1058
1059        device
1060    }
1061
1062    /// Get the local timestamp of when this device was first persisted, in
1063    /// milliseconds since epoch (client local time).
1064    pub fn first_time_seen_ts(&self) -> MilliSecondsSinceUnixEpoch {
1065        self.first_time_seen_ts
1066    }
1067
1068    /// True if this device is an [MSC3814](https://github.com/matrix-org/matrix-spec-proposals/pull/3814) dehydrated device.
1069    pub fn is_dehydrated(&self) -> bool {
1070        self.device_keys.dehydrated.unwrap_or(false)
1071    }
1072}
1073
1074impl TryFrom<&DeviceKeys> for DeviceData {
1075    type Error = SignatureError;
1076
1077    fn try_from(device_keys: &DeviceKeys) -> Result<Self, Self::Error> {
1078        device_keys.check_self_signature()?;
1079        Ok(Self {
1080            device_keys: device_keys.clone().into(),
1081            deleted: Arc::new(AtomicBool::new(false)),
1082            trust_state: Arc::new(RwLock::new(LocalTrust::Unset)),
1083            withheld_code_sent: Arc::new(AtomicBool::new(false)),
1084            first_time_seen_ts: MilliSecondsSinceUnixEpoch::now(),
1085            olm_wedging_index: Default::default(),
1086        })
1087    }
1088}
1089
1090impl PartialEq for DeviceData {
1091    fn eq(&self, other: &Self) -> bool {
1092        self.user_id() == other.user_id() && self.device_id() == other.device_id()
1093    }
1094}
1095
1096/// Testing Facilities for Device Management
1097#[cfg(any(test, feature = "testing"))]
1098#[allow(dead_code)]
1099pub(crate) mod testing {
1100    use serde_json::json;
1101
1102    use crate::{identities::DeviceData, types::DeviceKeys};
1103
1104    /// Generate default DeviceKeys for tests
1105    pub fn device_keys() -> DeviceKeys {
1106        let device_keys = json!({
1107          "algorithms": vec![
1108              "m.olm.v1.curve25519-aes-sha2",
1109              "m.megolm.v1.aes-sha2"
1110          ],
1111          "device_id": "BNYQQWUMXO",
1112          "user_id": "@example:localhost",
1113          "keys": {
1114              "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
1115              "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
1116          },
1117          "signatures": {
1118              "@example:localhost": {
1119                  "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
1120              }
1121          },
1122          "unsigned": {
1123              "device_display_name": "Alice's mobile phone"
1124          }
1125        });
1126
1127        serde_json::from_value(device_keys).unwrap()
1128    }
1129
1130    /// Generate default [`DeviceData`] for tests
1131    pub fn get_device() -> DeviceData {
1132        let device_keys = device_keys();
1133        DeviceData::try_from(&device_keys).unwrap()
1134    }
1135}
1136
1137#[cfg(test)]
1138pub(crate) mod tests {
1139    use ruma::{MilliSecondsSinceUnixEpoch, user_id};
1140    use serde_json::json;
1141    use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
1142
1143    use super::testing::{device_keys, get_device};
1144    use crate::{DeviceData, identities::LocalTrust};
1145
1146    #[test]
1147    fn create_a_device() {
1148        let now = MilliSecondsSinceUnixEpoch::now();
1149        let user_id = user_id!("@example:localhost");
1150        let device_id = "BNYQQWUMXO";
1151
1152        let device = get_device();
1153
1154        assert_eq!(user_id, device.user_id());
1155        assert_eq!(device_id, device.device_id());
1156        assert_eq!(device.algorithms().len(), 2);
1157        assert_eq!(LocalTrust::Unset, device.local_trust_state());
1158        assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1159        assert_eq!(
1160            device.curve25519_key().unwrap(),
1161            Curve25519PublicKey::from_base64("xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc")
1162                .unwrap(),
1163        );
1164        assert_eq!(
1165            device.ed25519_key().unwrap(),
1166            Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap(),
1167        );
1168
1169        let then = MilliSecondsSinceUnixEpoch::now();
1170
1171        assert!(device.first_time_seen_ts() >= now);
1172        assert!(device.first_time_seen_ts() <= then);
1173    }
1174
1175    #[test]
1176    fn update_a_device() {
1177        let mut device = get_device();
1178
1179        assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1180
1181        let display_name = "Alice's work computer".to_owned();
1182
1183        let mut device_keys = device_keys();
1184        device_keys.unsigned.device_display_name = Some(display_name.clone());
1185        assert!(device.update_device(&device_keys).unwrap());
1186        assert_eq!(&display_name, device.display_name().as_ref().unwrap());
1187
1188        // A second call to `update_device` with the same data should return
1189        // `false`.
1190        assert!(!device.update_device(&device_keys).unwrap());
1191    }
1192
1193    #[test]
1194    #[allow(clippy::redundant_clone)]
1195    fn delete_a_device() {
1196        let device = get_device();
1197        assert!(!device.is_deleted());
1198
1199        let device_clone = device.clone();
1200
1201        device.mark_as_deleted();
1202        assert!(device.is_deleted());
1203        assert!(device_clone.is_deleted());
1204    }
1205
1206    #[test]
1207    fn deserialize_device() {
1208        let user_id = user_id!("@example:localhost");
1209        let device_id = "BNYQQWUMXO";
1210
1211        let device = json!({
1212            "inner": {
1213                "user_id": user_id,
1214                "device_id": device_id,
1215                "algorithms": ["m.olm.v1.curve25519-aes-sha2","m.megolm.v1.aes-sha2"],
1216                "keys": {
1217                    "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
1218                    "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
1219                },
1220                "signatures": {
1221                    "@example:localhost": {
1222                        "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
1223                    }
1224                },
1225                "unsigned": {
1226                    "device_display_name": "Alice's mobile phone"
1227                }
1228            },
1229            "deleted": false,
1230            "trust_state": "Verified",
1231            "withheld_code_sent": false,
1232            "first_time_seen_ts": 1696931068314u64
1233        });
1234
1235        let device: DeviceData =
1236            serde_json::from_value(device).expect("We should be able to deserialize our device");
1237
1238        assert_eq!(user_id, device.user_id());
1239        assert_eq!(device_id, device.device_id());
1240        assert_eq!(device.algorithms().len(), 2);
1241        assert_eq!(LocalTrust::Verified, device.local_trust_state());
1242        assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1243        assert_eq!(
1244            device.curve25519_key().unwrap(),
1245            Curve25519PublicKey::from_base64("xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc")
1246                .unwrap(),
1247        );
1248        assert_eq!(
1249            device.ed25519_key().unwrap(),
1250            Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap(),
1251        );
1252    }
1253
1254    /// A device signed by its owner's self-signing key becomes trusted when the
1255    /// owner's identity is verified via X.509, even though we never
1256    /// cross-signed the identity ourselves.
1257    #[cfg(feature = "experimental-x509-identity-verification")]
1258    #[matrix_sdk_test::async_test]
1259    async fn test_x509_verified_owner_confers_device_trust() {
1260        use std::sync::Arc;
1261
1262        use ruma::device_id;
1263
1264        use crate::{
1265            machine::test_helpers::create_signed_device_of_unverified_user,
1266            olm::{Account, PrivateCrossSigningIdentity},
1267            x509::{
1268                RustRawX509Signer, RustRawX509Verifier, X509Signer, X509Verifier,
1269                tests::{ca_cert, cert_and_key_with_email_signed_by},
1270            },
1271        };
1272
1273        // Given Alice's identity is signed with an X.509 certificate chaining
1274        // to a CA...
1275        let (ca_certificate, ca_signing_key) = ca_cert();
1276        let (certificate, signing_key) =
1277            cert_and_key_with_email_signed_by("alice@hs.co", &ca_certificate, &ca_signing_key);
1278        let x509_signer = X509Signer::new(Arc::new(
1279            RustRawX509Signer::new_from_pem_data(&certificate.pem(), &signing_key.serialize_pem())
1280                .unwrap(),
1281        ));
1282        let account = Account::with_device_id(user_id!("@alice:hs.co"), device_id!("ALICEDEV"));
1283        let alice_private_identity =
1284            PrivateCrossSigningIdentity::for_account(&account, Some(&x509_signer)).await.unwrap();
1285
1286        // ...and her device is signed by her self-signing key, but we have not
1287        // cross-signed her identity ourselves.
1288        let mut device =
1289            create_signed_device_of_unverified_user(account.device_keys(), &alice_private_identity)
1290                .await;
1291        assert!(device.is_cross_signed_by_owner());
1292
1293        // Without a verifier for the CA, the device is not trusted.
1294        assert!(!device.is_cross_signing_trusted());
1295
1296        // With a verifier trusting the CA, the device is trusted.
1297        device.x509_verifier = Some(X509Verifier::new(Arc::new(
1298            RustRawX509Verifier::new_from_pem_data(&ca_certificate.pem()).unwrap(),
1299        )));
1300        assert!(device.is_cross_signing_trusted());
1301        assert!(device.is_verified());
1302
1303        // A verifier trusting a *different* CA does not trust the device.
1304        let (wrong_ca_certificate, _) = ca_cert();
1305        device.x509_verifier = Some(X509Verifier::new(Arc::new(
1306            RustRawX509Verifier::new_from_pem_data(&wrong_ca_certificate.pem()).unwrap(),
1307        )));
1308        assert!(!device.is_cross_signing_trusted());
1309    }
1310}