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