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    /// Returns true if there is at least one devices of this user that is
579    /// considered to be verified, false otherwise.
580    ///
581    /// This won't consider your own device as verified, as your own device is
582    /// always implicitly verified.
583    pub fn is_any_verified(&self) -> bool {
584        self.inner
585            .values()
586            .filter(|d| {
587                !(d.user_id() == self.own_user_id() && d.device_id() == self.own_device_id())
588            })
589            .any(|d| {
590                d.is_verified(
591                    &self.own_identity,
592                    &self.device_owner_identity,
593                    #[cfg(feature = "experimental-x509-identity-verification")]
594                    self.x509_verifier.as_ref(),
595                )
596            })
597    }
598
599    /// Iterator over all the device ids of the user devices.
600    pub fn keys(&self) -> impl Iterator<Item = &DeviceId> {
601        self.inner.keys().map(Deref::deref)
602    }
603
604    /// Iterator over all the devices of the user devices.
605    pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
606        self.inner.values().map(move |d| Device {
607            inner: d.clone(),
608            verification_machine: self.verification_machine.clone(),
609            own_identity: self.own_identity.clone(),
610            device_owner_identity: self.device_owner_identity.clone(),
611            #[cfg(feature = "experimental-x509-identity-verification")]
612            x509_verifier: self.x509_verifier.clone(),
613        })
614    }
615}
616
617/// The local trust state of a device.
618#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
619#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
620pub enum LocalTrust {
621    /// The device has been verified and is trusted.
622    Verified = 0,
623    /// The device been blacklisted from communicating.
624    BlackListed = 1,
625    /// The trust state of the device is being ignored.
626    Ignored = 2,
627    /// The trust state is unset.
628    Unset = 3,
629}
630
631impl From<i64> for LocalTrust {
632    fn from(state: i64) -> Self {
633        match state {
634            0 => LocalTrust::Verified,
635            1 => LocalTrust::BlackListed,
636            2 => LocalTrust::Ignored,
637            3 => LocalTrust::Unset,
638            _ => LocalTrust::Unset,
639        }
640    }
641}
642
643impl DeviceData {
644    /// Create a new Device, this constructor skips signature verification of
645    /// the keys, `TryFrom` should be used for completely new devices we
646    /// receive.
647    pub fn new(device_keys: DeviceKeys, trust_state: LocalTrust) -> Self {
648        Self {
649            device_keys: device_keys.into(),
650            trust_state: Arc::new(RwLock::new(trust_state)),
651            deleted: Arc::new(AtomicBool::new(false)),
652            withheld_code_sent: Arc::new(AtomicBool::new(false)),
653            first_time_seen_ts: MilliSecondsSinceUnixEpoch::now(),
654            olm_wedging_index: Default::default(),
655        }
656    }
657
658    /// The user id of the device owner.
659    pub fn user_id(&self) -> &UserId {
660        &self.device_keys.user_id
661    }
662
663    /// The unique ID of the device.
664    pub fn device_id(&self) -> &DeviceId {
665        &self.device_keys.device_id
666    }
667
668    /// Get the human readable name of the device.
669    pub fn display_name(&self) -> Option<&str> {
670        self.device_keys.unsigned.device_display_name.as_deref()
671    }
672
673    /// Get the key of the given key algorithm belonging to this device.
674    pub fn get_key(&self, algorithm: DeviceKeyAlgorithm) -> Option<&DeviceKey> {
675        self.device_keys.get_key(algorithm)
676    }
677
678    /// Get the Curve25519 key of the given device.
679    pub fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
680        self.device_keys.curve25519_key()
681    }
682
683    /// Get the Ed25519 key of the given device.
684    pub fn ed25519_key(&self) -> Option<Ed25519PublicKey> {
685        self.device_keys.ed25519_key()
686    }
687
688    /// Get a map containing all the device keys.
689    pub fn keys(&self) -> &BTreeMap<OwnedDeviceKeyId, DeviceKey> {
690        &self.device_keys.keys
691    }
692
693    /// Get a map containing all the device signatures.
694    pub fn signatures(&self) -> &Signatures {
695        &self.device_keys.signatures
696    }
697
698    /// Get the trust state of the device.
699    pub fn local_trust_state(&self) -> LocalTrust {
700        *self.trust_state.read()
701    }
702
703    /// Is the device locally marked as trusted.
704    pub fn is_locally_trusted(&self) -> bool {
705        self.local_trust_state() == LocalTrust::Verified
706    }
707
708    /// Is the device locally marked as blacklisted.
709    ///
710    /// Blacklisted devices won't receive any group sessions.
711    pub fn is_blacklisted(&self) -> bool {
712        self.local_trust_state() == LocalTrust::BlackListed
713    }
714
715    /// Set the trust state of the device to the given state.
716    ///
717    /// Note: This should only done in the crypto store where the trust state
718    /// can be stored.
719    pub(crate) fn set_trust_state(&self, state: LocalTrust) {
720        *self.trust_state.write() = state;
721    }
722
723    pub(crate) fn mark_withheld_code_as_sent(&self) {
724        self.withheld_code_sent.store(true, Ordering::Relaxed)
725    }
726
727    /// Returns true if the `m.no_olm` withheld code was already sent to this
728    /// device.
729    pub fn was_withheld_code_sent(&self) -> bool {
730        self.withheld_code_sent.load(Ordering::Relaxed)
731    }
732
733    /// Get the list of algorithms this device supports.
734    pub fn algorithms(&self) -> &[EventEncryptionAlgorithm] {
735        &self.device_keys.algorithms
736    }
737
738    /// Does this device support any of our known Olm encryption algorithms.
739    pub fn supports_olm(&self) -> bool {
740        #[cfg(feature = "experimental-algorithms")]
741        {
742            self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
743                || self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
744        }
745
746        #[cfg(not(feature = "experimental-algorithms"))]
747        {
748            self.algorithms().contains(&EventEncryptionAlgorithm::OlmV1Curve25519AesSha2)
749        }
750    }
751
752    /// Find and return the most recently created Olm [`Session`] we are sharing
753    /// with this device.
754    pub(crate) async fn get_most_recent_session(
755        &self,
756        store: &CryptoStoreWrapper,
757    ) -> OlmResult<Option<Session>> {
758        if let Some(sender_key) = self.curve25519_key() {
759            if let Some(sessions) = store.get_sessions(&sender_key.to_base64()).await? {
760                let mut sessions = sessions.lock().await;
761                sessions.sort_by_key(|s| s.creation_time);
762
763                Ok(sessions.last().cloned())
764            } else {
765                Ok(None)
766            }
767        } else {
768            Ok(None)
769        }
770    }
771
772    /// Does this device support the olm.v2.curve25519-aes-sha2 encryption
773    /// algorithm.
774    #[cfg(feature = "experimental-algorithms")]
775    pub fn supports_olm_v2(&self) -> bool {
776        self.algorithms().contains(&EventEncryptionAlgorithm::OlmV2Curve25519AesSha2)
777    }
778
779    /// Get the optimal `SessionConfig` for this device.
780    pub fn olm_session_config(&self) -> SessionConfig {
781        #[cfg(feature = "experimental-algorithms")]
782        if self.supports_olm_v2() {
783            SessionConfig::version_2()
784        } else {
785            SessionConfig::version_1()
786        }
787
788        #[cfg(not(feature = "experimental-algorithms"))]
789        SessionConfig::version_1()
790    }
791
792    /// Is the device deleted.
793    pub fn is_deleted(&self) -> bool {
794        self.deleted.load(Ordering::Relaxed)
795    }
796
797    pub(crate) fn is_verified(
798        &self,
799        own_identity: &Option<OwnUserIdentityData>,
800        device_owner: &Option<UserIdentityData>,
801        #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
802            &X509Verifier,
803        >,
804    ) -> bool {
805        self.is_locally_trusted()
806            || self.is_cross_signing_trusted(
807                own_identity,
808                device_owner,
809                #[cfg(feature = "experimental-x509-identity-verification")]
810                x509_verifier,
811            )
812    }
813
814    pub(crate) fn is_cross_signing_trusted(
815        &self,
816        own_identity: &Option<OwnUserIdentityData>,
817        device_owner: &Option<UserIdentityData>,
818        #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
819            &X509Verifier,
820        >,
821    ) -> bool {
822        device_owner.as_ref().is_some_and(|device_identity| match device_identity {
823            UserIdentityData::Own(_) => own_identity.as_ref().is_some_and(|own_identity| {
824                own_identity.is_verified() && own_identity.is_device_signed(self)
825            }),
826
827            // If it's a device from someone else, first check that our user has
828            // verified the other user (either by cross-signing their identity,
829            // or via a valid X.509 signature on their master key) and then
830            // check if the other user has signed this device.
831            UserIdentityData::Other(device_identity) => {
832                device_identity.is_verified(
833                    own_identity.as_ref(),
834                    #[cfg(feature = "experimental-x509-identity-verification")]
835                    x509_verifier,
836                ) && device_identity.is_device_signed(self)
837            }
838        })
839    }
840
841    pub(crate) fn is_cross_signed_by_owner(
842        &self,
843        device_owner_identity: &UserIdentityData,
844    ) -> bool {
845        match device_owner_identity {
846            // If it's one of our own devices, just check that we signed the
847            // device.
848            UserIdentityData::Own(identity) => identity.is_device_signed(self),
849            // If it's a device from someone else, check if the other user has
850            // signed this device.
851            UserIdentityData::Other(device_identity) => device_identity.is_device_signed(self),
852        }
853    }
854
855    /// Encrypt the given content for this device.
856    ///
857    /// # Arguments
858    ///
859    /// - `store` - The crypto store. Used to find an established Olm session
860    ///   for this device.
861    /// - `event_type` - The type of the event that should be encrypted.
862    /// - `content` - The content of the event that should be encrypted.
863    ///
864    /// # Returns
865    ///
866    /// On success, a tuple `(session, content, message_id)`, where `session` is
867    /// the Olm [`Session`] that was used to encrypt the content, `content` is
868    /// the content for the `m.room.encrypted` to-device event, and `message_id`
869    /// is the newly-minted message ID stored within the content.
870    ///
871    /// If an Olm session has not already been established with this device,
872    /// returns `Err(OlmError::MissingSession)`.
873    #[instrument(
874        skip_all,
875        fields(
876            recipient = ?self.user_id(),
877            recipient_device = ?self.device_id(),
878            recipient_key = ?self.curve25519_key(),
879            event_type,
880            message_id,
881        ))
882    ]
883    pub(crate) async fn encrypt(
884        &self,
885        store: &CryptoStoreWrapper,
886        event_type: &str,
887        content: impl Serialize,
888    ) -> OlmResult<(Session, Raw<ToDeviceEncryptedEventContent>, String)> {
889        #[cfg(not(target_family = "wasm"))]
890        let message_id = ulid::Ulid::generate().to_string();
891        #[cfg(target_family = "wasm")]
892        let message_id = ruma::TransactionId::new().to_string();
893
894        tracing::Span::current().record("message_id", &message_id);
895
896        let session = self.get_most_recent_session(store).await?;
897
898        if let Some(mut session) = session {
899            let message =
900                session.encrypt(self, event_type, content, Some(message_id.clone())).await?;
901
902            Ok((session, message, message_id))
903        } else {
904            trace!("Trying to encrypt an event for a device, but no Olm session is found.");
905            Err(OlmError::MissingSession)
906        }
907    }
908
909    pub(crate) async fn maybe_encrypt_room_key(
910        &self,
911        store: &CryptoStoreWrapper,
912        session: OutboundGroupSession,
913    ) -> OlmResult<MaybeEncryptedRoomKey> {
914        let content = session.as_content().await;
915        let message_index = session.message_index().await;
916        let event_type = content.event_type().to_owned();
917
918        match self.encrypt(store, &event_type, content).await {
919            Ok((session, encrypted, _)) => Ok(MaybeEncryptedRoomKey::Encrypted {
920                share_info: Box::new(ShareInfo::new_shared(
921                    session.sender_key().to_owned(),
922                    message_index,
923                    self.olm_wedging_index,
924                )),
925                used_session: Box::new(session),
926                message: encrypted.cast(),
927            }),
928
929            Err(OlmError::MissingSession) => Ok(MaybeEncryptedRoomKey::MissingSession),
930            Err(e) => Err(e),
931        }
932    }
933
934    /// Update a device with a new device keys struct.
935    ///
936    /// Returns `true` if any changes were made to the data.
937    pub(crate) fn update_device(
938        &mut self,
939        device_keys: &DeviceKeys,
940    ) -> Result<bool, SignatureError> {
941        device_keys.check_self_signature()?;
942
943        if self.user_id() != device_keys.user_id || self.device_id() != device_keys.device_id {
944            Err(SignatureError::UserIdMismatch)
945        } else if self.ed25519_key() != device_keys.ed25519_key() {
946            Err(SignatureError::SigningKeyChanged(
947                self.ed25519_key().map(Box::new),
948                device_keys.ed25519_key().map(Box::new),
949            ))
950        } else if self.device_keys.as_ref() != device_keys {
951            trace!(
952                user_id = ?self.user_id(),
953                device_id = ?self.device_id(),
954                keys = ?self.keys(),
955                "Updated a device",
956            );
957
958            self.device_keys = device_keys.clone().into();
959
960            Ok(true)
961        } else {
962            // no changes needed
963            Ok(false)
964        }
965    }
966
967    /// Return the device keys
968    pub fn as_device_keys(&self) -> &DeviceKeys {
969        &self.device_keys
970    }
971
972    /// Check if the given JSON is signed by this device key.
973    ///
974    /// This method should only be used if an object's signature needs to be
975    /// checked multiple times, and you'd like to avoid performing the
976    /// canonicalization step each time.
977    ///
978    /// **Note**: Use this method with caution, the `canonical_json` needs to be
979    /// correctly canonicalized and make sure that the object you are checking
980    /// the signature for is allowed to be signed by a device.
981    pub(crate) fn has_signed_raw(
982        &self,
983        signatures: &Signatures,
984        canonical_json: &str,
985    ) -> Result<(), SignatureError> {
986        let key = self.ed25519_key().ok_or(SignatureError::MissingSigningKey)?;
987        let user_id = self.user_id();
988        let key_id = &DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, self.device_id());
989
990        key.verify_canonicalized_json(user_id, key_id, signatures, canonical_json)
991    }
992
993    pub(crate) fn verify_one_time_key(
994        &self,
995        one_time_key: &SignedKey,
996    ) -> Result<(), SignatureError> {
997        self.device_keys.has_signed(one_time_key)
998    }
999
1000    /// Mark the device as deleted.
1001    pub(crate) fn mark_as_deleted(&self) {
1002        self.deleted.store(true, Ordering::Relaxed);
1003    }
1004
1005    #[cfg(any(test, feature = "testing"))]
1006    #[allow(dead_code)]
1007    /// Generate the Device from a reference of an OlmMachine.
1008    pub async fn from_machine_test_helper(
1009        machine: &OlmMachine,
1010    ) -> Result<DeviceData, crate::CryptoStoreError> {
1011        Ok(DeviceData::from_account(&*machine.store().cache().await?.account().await?))
1012    }
1013
1014    /// Create [`DeviceData`] from an [`Account`].
1015    ///
1016    /// We will have our own device data in the store once we receive a
1017    /// `/keys/query` response, but this is useful to create it before we
1018    /// receive such a response.
1019    ///
1020    /// It also makes it easier to check that the server doesn't lie about our
1021    /// own device.
1022    ///
1023    /// _Don't_ use this after we received a `/keys/query` response, other
1024    /// users/devices might add signatures to our own device, which can't be
1025    /// replicated locally.
1026    pub fn from_account(account: &Account) -> DeviceData {
1027        let device_keys = account.device_keys();
1028        let mut device = DeviceData::try_from(&device_keys)
1029            .expect("Creating a device from our own account should always succeed");
1030        device.first_time_seen_ts = account.creation_local_time();
1031
1032        device
1033    }
1034
1035    /// Get the local timestamp of when this device was first persisted, in
1036    /// milliseconds since epoch (client local time).
1037    pub fn first_time_seen_ts(&self) -> MilliSecondsSinceUnixEpoch {
1038        self.first_time_seen_ts
1039    }
1040
1041    /// True if this device is an [MSC3814](https://github.com/matrix-org/matrix-spec-proposals/pull/3814) dehydrated device.
1042    pub fn is_dehydrated(&self) -> bool {
1043        self.device_keys.dehydrated.unwrap_or(false)
1044    }
1045}
1046
1047impl TryFrom<&DeviceKeys> for DeviceData {
1048    type Error = SignatureError;
1049
1050    fn try_from(device_keys: &DeviceKeys) -> Result<Self, Self::Error> {
1051        device_keys.check_self_signature()?;
1052        Ok(Self {
1053            device_keys: device_keys.clone().into(),
1054            deleted: Arc::new(AtomicBool::new(false)),
1055            trust_state: Arc::new(RwLock::new(LocalTrust::Unset)),
1056            withheld_code_sent: Arc::new(AtomicBool::new(false)),
1057            first_time_seen_ts: MilliSecondsSinceUnixEpoch::now(),
1058            olm_wedging_index: Default::default(),
1059        })
1060    }
1061}
1062
1063impl PartialEq for DeviceData {
1064    fn eq(&self, other: &Self) -> bool {
1065        self.user_id() == other.user_id() && self.device_id() == other.device_id()
1066    }
1067}
1068
1069/// Testing Facilities for Device Management
1070#[cfg(any(test, feature = "testing"))]
1071#[allow(dead_code)]
1072pub(crate) mod testing {
1073    use serde_json::json;
1074
1075    use crate::{identities::DeviceData, types::DeviceKeys};
1076
1077    /// Generate default DeviceKeys for tests
1078    pub fn device_keys() -> DeviceKeys {
1079        let device_keys = json!({
1080          "algorithms": vec![
1081              "m.olm.v1.curve25519-aes-sha2",
1082              "m.megolm.v1.aes-sha2"
1083          ],
1084          "device_id": "BNYQQWUMXO",
1085          "user_id": "@example:localhost",
1086          "keys": {
1087              "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
1088              "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
1089          },
1090          "signatures": {
1091              "@example:localhost": {
1092                  "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
1093              }
1094          },
1095          "unsigned": {
1096              "device_display_name": "Alice's mobile phone"
1097          }
1098        });
1099
1100        serde_json::from_value(device_keys).unwrap()
1101    }
1102
1103    /// Generate default [`DeviceData`] for tests
1104    pub fn get_device() -> DeviceData {
1105        let device_keys = device_keys();
1106        DeviceData::try_from(&device_keys).unwrap()
1107    }
1108}
1109
1110#[cfg(test)]
1111pub(crate) mod tests {
1112    use ruma::{MilliSecondsSinceUnixEpoch, user_id};
1113    use serde_json::json;
1114    use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
1115
1116    use super::testing::{device_keys, get_device};
1117    use crate::{DeviceData, identities::LocalTrust};
1118
1119    #[test]
1120    fn create_a_device() {
1121        let now = MilliSecondsSinceUnixEpoch::now();
1122        let user_id = user_id!("@example:localhost");
1123        let device_id = "BNYQQWUMXO";
1124
1125        let device = get_device();
1126
1127        assert_eq!(user_id, device.user_id());
1128        assert_eq!(device_id, device.device_id());
1129        assert_eq!(device.algorithms().len(), 2);
1130        assert_eq!(LocalTrust::Unset, device.local_trust_state());
1131        assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1132        assert_eq!(
1133            device.curve25519_key().unwrap(),
1134            Curve25519PublicKey::from_base64("xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc")
1135                .unwrap(),
1136        );
1137        assert_eq!(
1138            device.ed25519_key().unwrap(),
1139            Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap(),
1140        );
1141
1142        let then = MilliSecondsSinceUnixEpoch::now();
1143
1144        assert!(device.first_time_seen_ts() >= now);
1145        assert!(device.first_time_seen_ts() <= then);
1146    }
1147
1148    #[test]
1149    fn update_a_device() {
1150        let mut device = get_device();
1151
1152        assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1153
1154        let display_name = "Alice's work computer".to_owned();
1155
1156        let mut device_keys = device_keys();
1157        device_keys.unsigned.device_display_name = Some(display_name.clone());
1158        assert!(device.update_device(&device_keys).unwrap());
1159        assert_eq!(&display_name, device.display_name().as_ref().unwrap());
1160
1161        // A second call to `update_device` with the same data should return
1162        // `false`.
1163        assert!(!device.update_device(&device_keys).unwrap());
1164    }
1165
1166    #[test]
1167    #[allow(clippy::redundant_clone)]
1168    fn delete_a_device() {
1169        let device = get_device();
1170        assert!(!device.is_deleted());
1171
1172        let device_clone = device.clone();
1173
1174        device.mark_as_deleted();
1175        assert!(device.is_deleted());
1176        assert!(device_clone.is_deleted());
1177    }
1178
1179    #[test]
1180    fn deserialize_device() {
1181        let user_id = user_id!("@example:localhost");
1182        let device_id = "BNYQQWUMXO";
1183
1184        let device = json!({
1185            "inner": {
1186                "user_id": user_id,
1187                "device_id": device_id,
1188                "algorithms": ["m.olm.v1.curve25519-aes-sha2","m.megolm.v1.aes-sha2"],
1189                "keys": {
1190                    "curve25519:BNYQQWUMXO": "xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc",
1191                    "ed25519:BNYQQWUMXO": "2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4"
1192                },
1193                "signatures": {
1194                    "@example:localhost": {
1195                        "ed25519:BNYQQWUMXO": "kTwMrbsLJJM/uFGOj/oqlCaRuw7i9p/6eGrTlXjo8UJMCFAetoyWzoMcF35vSe4S6FTx8RJmqX6rM7ep53MHDQ"
1196                    }
1197                },
1198                "unsigned": {
1199                    "device_display_name": "Alice's mobile phone"
1200                }
1201            },
1202            "deleted": false,
1203            "trust_state": "Verified",
1204            "withheld_code_sent": false,
1205            "first_time_seen_ts": 1696931068314u64
1206        });
1207
1208        let device: DeviceData =
1209            serde_json::from_value(device).expect("We should be able to deserialize our device");
1210
1211        assert_eq!(user_id, device.user_id());
1212        assert_eq!(device_id, device.device_id());
1213        assert_eq!(device.algorithms().len(), 2);
1214        assert_eq!(LocalTrust::Verified, device.local_trust_state());
1215        assert_eq!("Alice's mobile phone", device.display_name().unwrap());
1216        assert_eq!(
1217            device.curve25519_key().unwrap(),
1218            Curve25519PublicKey::from_base64("xfgbLIC5WAl1OIkpOzoxpCe8FsRDT6nch7NQsOb15nc")
1219                .unwrap(),
1220        );
1221        assert_eq!(
1222            device.ed25519_key().unwrap(),
1223            Ed25519PublicKey::from_base64("2/5LWJMow5zhJqakV88SIc7q/1pa8fmkfgAzx72w9G4").unwrap(),
1224        );
1225    }
1226
1227    /// A device signed by its owner's self-signing key becomes trusted when the
1228    /// owner's identity is verified via X.509, even though we never
1229    /// cross-signed the identity ourselves.
1230    #[cfg(feature = "experimental-x509-identity-verification")]
1231    #[matrix_sdk_test::async_test]
1232    async fn test_x509_verified_owner_confers_device_trust() {
1233        use std::sync::Arc;
1234
1235        use ruma::device_id;
1236
1237        use crate::{
1238            machine::test_helpers::create_signed_device_of_unverified_user,
1239            olm::{Account, PrivateCrossSigningIdentity},
1240            x509::{
1241                RustRawX509Signer, RustRawX509Verifier, X509Signer, X509Verifier,
1242                tests::{ca_cert, cert_and_key_with_email_signed_by},
1243            },
1244        };
1245
1246        // Given Alice's identity is signed with an X.509 certificate chaining
1247        // to a CA...
1248        let (ca_certificate, ca_signing_key) = ca_cert();
1249        let (certificate, signing_key) =
1250            cert_and_key_with_email_signed_by("alice@hs.co", &ca_certificate, &ca_signing_key);
1251        let x509_signer = X509Signer::new(Arc::new(
1252            RustRawX509Signer::new_from_pem_data(&certificate.pem(), &signing_key.serialize_pem())
1253                .unwrap(),
1254        ));
1255        let account = Account::with_device_id(user_id!("@alice:hs.co"), device_id!("ALICEDEV"));
1256        let alice_private_identity =
1257            PrivateCrossSigningIdentity::for_account(&account, Some(&x509_signer)).await.unwrap();
1258
1259        // ...and her device is signed by her self-signing key, but we have not
1260        // cross-signed her identity ourselves.
1261        let mut device =
1262            create_signed_device_of_unverified_user(account.device_keys(), &alice_private_identity)
1263                .await;
1264        assert!(device.is_cross_signed_by_owner());
1265
1266        // Without a verifier for the CA, the device is not trusted.
1267        assert!(!device.is_cross_signing_trusted());
1268
1269        // With a verifier trusting the CA, the device is trusted.
1270        device.x509_verifier = Some(X509Verifier::new(Arc::new(
1271            RustRawX509Verifier::new_from_pem_data(&ca_certificate.pem()).unwrap(),
1272        )));
1273        assert!(device.is_cross_signing_trusted());
1274        assert!(device.is_verified());
1275
1276        // A verifier trusting a *different* CA does not trust the device.
1277        let (wrong_ca_certificate, _) = ca_cert();
1278        device.x509_verifier = Some(X509Verifier::new(Arc::new(
1279            RustRawX509Verifier::new_from_pem_data(&wrong_ca_certificate.pem()).unwrap(),
1280        )));
1281        assert!(!device.is_cross_signing_trusted());
1282    }
1283}