matrix_sdk_crypto/verification/sas/
mod.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
15mod helpers;
16mod inner_sas;
17mod sas_state;
18
19use std::sync::Arc;
20
21use as_variant::as_variant;
22use eyeball::{ObservableWriteGuard, SharedObservable};
23use futures_core::Stream;
24use futures_util::StreamExt;
25use inner_sas::InnerSas;
26use ruma::{
27    api::client::keys::upload_signatures::v3::Request as SignatureUploadRequest,
28    events::{
29        key::verification::{cancel::CancelCode, start::SasV1Content, ShortAuthenticationString},
30        AnyMessageLikeEventContent, AnyToDeviceEventContent,
31    },
32    DeviceId, OwnedEventId, OwnedRoomId, OwnedTransactionId, RoomId, TransactionId, UserId,
33};
34pub use sas_state::AcceptedProtocols;
35use tracing::{debug, error, trace};
36
37use super::{
38    cache::RequestInfo,
39    event_enums::{AnyVerificationContent, OutgoingContent, OwnedAcceptContent, StartContent},
40    requests::RequestHandle,
41    CancelInfo, FlowId, IdentitiesBeingVerified, VerificationResult,
42};
43use crate::{
44    identities::{DeviceData, UserIdentityData},
45    olm::StaticAccountData,
46    store::CryptoStoreError,
47    types::requests::{OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest},
48    Emoji,
49};
50
51/// Short authentication string object.
52#[derive(Clone, Debug)]
53pub struct Sas {
54    inner: SharedObservable<InnerSas>,
55    account: StaticAccountData,
56    identities_being_verified: IdentitiesBeingVerified,
57    flow_id: Arc<FlowId>,
58    we_started: bool,
59    request_handle: Option<RequestHandle>,
60}
61
62#[derive(Debug, Clone, Copy)]
63enum State {
64    Created,
65    Started,
66    Accepted,
67    WeAccepted,
68    KeyReceived,
69    KeySent,
70    KeysExchanged,
71    Confirmed,
72    MacReceived,
73    WaitingForDone,
74    Done,
75    Cancelled,
76}
77
78impl From<&InnerSas> for State {
79    fn from(value: &InnerSas) -> Self {
80        match value {
81            InnerSas::Created(_) => Self::Created,
82            InnerSas::Started(_) => Self::Started,
83            InnerSas::Accepted(_) => Self::Accepted,
84            InnerSas::WeAccepted(_) => Self::WeAccepted,
85            InnerSas::KeyReceived(_) => Self::KeyReceived,
86            InnerSas::KeySent(_) => Self::KeySent,
87            InnerSas::KeysExchanged(_) => Self::KeysExchanged,
88            InnerSas::Confirmed(_) => Self::Confirmed,
89            InnerSas::MacReceived(_) => Self::MacReceived,
90            InnerSas::WaitingForDone(_) => Self::WaitingForDone,
91            InnerSas::Done(_) => Self::Done,
92            InnerSas::Cancelled(_) => Self::Cancelled,
93        }
94    }
95}
96
97/// The short auth string for the emoji method of SAS verification.
98#[derive(Debug, Clone)]
99pub struct EmojiShortAuthString {
100    /// A list of seven indices that should be used for the SAS verification.
101    ///
102    /// The indices can be put into the emoji table in the [spec] to figure out
103    /// the symbols and descriptions.
104    ///
105    /// If you have a table of [translated descriptions] for the emojis you will
106    /// want to use this field.
107    ///
108    /// [spec]: https://spec.matrix.org/unstable/client-server-api/#sas-method-emoji
109    /// [translated descriptions]: https://github.com/matrix-org/matrix-doc/blob/master/data-definitions/
110    pub indices: [u8; 7],
111
112    /// A list of seven emojis that should be used for the SAS verification.
113    pub emojis: [Emoji; 7],
114}
115
116/// An Enum describing the state the SAS verification is in.
117#[derive(Debug, Clone)]
118pub enum SasState {
119    /// The verification has been created, the protocols that should be used
120    /// have been proposed to the other party.
121    Created {
122        /// The protocols that were proposed in the `m.key.verification.start`
123        /// event.
124        protocols: SasV1Content,
125    },
126    /// The verification has been started, the other party proposed the
127    /// protocols that should be used and that can be accepted.
128    Started {
129        /// The protocols that were proposed in the `m.key.verification.start`
130        /// event.
131        protocols: SasV1Content,
132    },
133    /// The verification has been accepted and both sides agreed to a set of
134    /// protocols that will be used for the verification process.
135    Accepted {
136        /// The protocols that were accepted in the `m.key.verification.accept`
137        /// event.
138        accepted_protocols: AcceptedProtocols,
139    },
140    /// The public keys have been exchanged and the short auth string can be
141    /// presented to the user.
142    KeysExchanged {
143        /// The emojis that represent the short auth string, will be `None` if
144        /// the emoji SAS method wasn't part of the [`AcceptedProtocols`].
145        emojis: Option<EmojiShortAuthString>,
146        /// The list of decimals that represent the short auth string.
147        decimals: (u16, u16, u16),
148    },
149    /// The verification process has been confirmed from our side, we're waiting
150    /// for the other side to confirm as well.
151    Confirmed,
152    /// The verification process has been successfully concluded.
153    Done {
154        /// The list of devices that has been verified.
155        verified_devices: Vec<DeviceData>,
156        /// The list of user identities that has been verified.
157        verified_identities: Vec<UserIdentityData>,
158    },
159    /// The verification process has been cancelled.
160    Cancelled(CancelInfo),
161}
162
163impl PartialEq for SasState {
164    fn eq(&self, other: &Self) -> bool {
165        matches!(
166            (self, other),
167            (Self::Created { .. }, Self::Created { .. })
168                | (Self::Started { .. }, Self::Started { .. })
169                | (Self::Accepted { .. }, Self::Accepted { .. })
170                | (Self::KeysExchanged { .. }, Self::KeysExchanged { .. })
171                | (Self::Confirmed, Self::Confirmed)
172                | (Self::Done { .. }, Self::Done { .. })
173                | (Self::Cancelled(_), Self::Cancelled(_))
174        )
175    }
176}
177
178impl From<&InnerSas> for SasState {
179    fn from(value: &InnerSas) -> Self {
180        match value {
181            InnerSas::Created(s) => {
182                Self::Created { protocols: s.state.protocol_definitions.to_owned() }
183            }
184            InnerSas::Started(s) => {
185                Self::Started { protocols: s.state.protocol_definitions.to_owned() }
186            }
187            InnerSas::Accepted(s) => {
188                Self::Accepted { accepted_protocols: s.state.accepted_protocols.to_owned() }
189            }
190            InnerSas::WeAccepted(s) => {
191                Self::Accepted { accepted_protocols: s.state.accepted_protocols.to_owned() }
192            }
193            InnerSas::KeySent(s) => {
194                Self::Accepted { accepted_protocols: s.state.accepted_protocols.to_owned() }
195            }
196            InnerSas::KeyReceived(s) => {
197                Self::Accepted { accepted_protocols: s.state.accepted_protocols.to_owned() }
198            }
199            InnerSas::KeysExchanged(s) => {
200                let emojis = value.supports_emoji().then(|| {
201                    let emojis = s.get_emoji();
202                    let indices = s.get_emoji_index();
203
204                    EmojiShortAuthString { emojis, indices }
205                });
206
207                let decimals = s.get_decimal();
208
209                Self::KeysExchanged { emojis, decimals }
210            }
211            InnerSas::MacReceived(s) => {
212                let emojis = value.supports_emoji().then(|| {
213                    let emojis = s.get_emoji();
214                    let indices = s.get_emoji_index();
215
216                    EmojiShortAuthString { emojis, indices }
217                });
218
219                let decimals = s.get_decimal();
220
221                Self::KeysExchanged { emojis, decimals }
222            }
223            InnerSas::Confirmed(_) => Self::Confirmed,
224            InnerSas::WaitingForDone(_) => Self::Confirmed,
225            InnerSas::Done(s) => Self::Done {
226                verified_devices: s.verified_devices().to_vec(),
227                verified_identities: s.verified_identities().to_vec(),
228            },
229            InnerSas::Cancelled(c) => Self::Cancelled(c.state.as_ref().clone().into()),
230        }
231    }
232}
233
234impl Sas {
235    /// Get our own user id.
236    pub fn user_id(&self) -> &UserId {
237        &self.account.user_id
238    }
239
240    /// Get our own device ID.
241    pub fn device_id(&self) -> &DeviceId {
242        &self.account.device_id
243    }
244
245    /// Get the user id of the other side.
246    pub fn other_user_id(&self) -> &UserId {
247        self.identities_being_verified.other_user_id()
248    }
249
250    /// Get the device ID of the other side.
251    pub fn other_device_id(&self) -> &DeviceId {
252        self.identities_being_verified.other_device_id()
253    }
254
255    /// Get the device of the other user.
256    pub fn other_device(&self) -> &DeviceData {
257        self.identities_being_verified.other_device()
258    }
259
260    /// Get the unique ID that identifies this SAS verification flow.
261    pub fn flow_id(&self) -> &FlowId {
262        &self.flow_id
263    }
264
265    /// Get the room id if the verification is happening inside a room.
266    pub fn room_id(&self) -> Option<&RoomId> {
267        as_variant!(self.flow_id(), FlowId::InRoom(r, _) => r)
268    }
269
270    /// Does this verification flow support displaying emoji for the short
271    /// authentication string.
272    pub fn supports_emoji(&self) -> bool {
273        self.inner.read().supports_emoji()
274    }
275
276    /// Did this verification flow start from a verification request.
277    pub fn started_from_request(&self) -> bool {
278        self.inner.read().started_from_request()
279    }
280
281    /// Is this a verification that is verifying one of our own devices.
282    pub fn is_self_verification(&self) -> bool {
283        self.identities_being_verified.is_self_verification()
284    }
285
286    /// Have we confirmed that the short auth string matches.
287    pub fn have_we_confirmed(&self) -> bool {
288        self.inner.read().have_we_confirmed()
289    }
290
291    /// Has the verification been accepted by both parties.
292    pub fn has_been_accepted(&self) -> bool {
293        self.inner.read().has_been_accepted()
294    }
295
296    /// Get info about the cancellation if the verification flow has been
297    /// cancelled.
298    pub fn cancel_info(&self) -> Option<CancelInfo> {
299        as_variant!(&*self.inner.read(), InnerSas::Cancelled(c) => {
300            c.state.as_ref().clone().into()
301        })
302    }
303
304    /// Did we initiate the verification flow.
305    pub fn we_started(&self) -> bool {
306        self.we_started
307    }
308
309    #[cfg(test)]
310    #[allow(dead_code)]
311    pub(crate) fn set_creation_time(&self, time: ruma::time::Instant) {
312        self.inner.update(|inner| {
313            inner.set_creation_time(time);
314        });
315    }
316
317    fn start_helper(
318        flow_id: FlowId,
319        identities: IdentitiesBeingVerified,
320        we_started: bool,
321        request_handle: Option<RequestHandle>,
322        short_auth_strings: Option<Vec<ShortAuthenticationString>>,
323    ) -> (Sas, OutgoingContent) {
324        let (inner, content) = InnerSas::start(
325            identities.store.account.clone(),
326            identities.device_being_verified.clone(),
327            identities.own_identity.clone(),
328            identities.identity_being_verified.clone(),
329            flow_id.clone(),
330            request_handle.is_some(),
331            short_auth_strings,
332        );
333
334        let account = identities.store.account.clone();
335
336        (
337            Sas {
338                inner: SharedObservable::new(inner),
339                account,
340                identities_being_verified: identities,
341                flow_id: flow_id.into(),
342                we_started,
343                request_handle,
344            },
345            content,
346        )
347    }
348
349    /// Start a new SAS auth flow with the given device.
350    ///
351    /// # Arguments
352    ///
353    /// * `account` - Our own account.
354    ///
355    /// * `other_device` - The other device which we are going to verify.
356    ///
357    /// Returns the new `Sas` object and a `StartEventContent` that needs to be
358    /// sent out through the server to the other device.
359    pub(crate) fn start(
360        identities: IdentitiesBeingVerified,
361        transaction_id: OwnedTransactionId,
362        we_started: bool,
363        request_handle: Option<RequestHandle>,
364        short_auth_strings: Option<Vec<ShortAuthenticationString>>,
365    ) -> (Sas, OutgoingContent) {
366        let flow_id = FlowId::ToDevice(transaction_id);
367
368        Self::start_helper(flow_id, identities, we_started, request_handle, short_auth_strings)
369    }
370
371    /// Start a new SAS auth flow with the given device inside the given room.
372    ///
373    /// # Arguments
374    ///
375    /// * `account` - Our own account.
376    ///
377    /// * `other_device` - The other device which we are going to verify.
378    ///
379    /// Returns the new `Sas` object and a `StartEventContent` that needs to be
380    /// sent out through the server to the other device.
381    #[allow(clippy::too_many_arguments)]
382    pub(crate) fn start_in_room(
383        flow_id: OwnedEventId,
384        room_id: OwnedRoomId,
385        identities: IdentitiesBeingVerified,
386        we_started: bool,
387        request_handle: RequestHandle,
388    ) -> (Sas, OutgoingContent) {
389        let flow_id = FlowId::InRoom(room_id, flow_id);
390        Self::start_helper(flow_id, identities, we_started, Some(request_handle), None)
391    }
392
393    /// Create a new Sas object from a m.key.verification.start request.
394    ///
395    /// # Arguments
396    ///
397    /// * `account` - Our own account.
398    ///
399    /// * `other_device` - The other device which we are going to verify.
400    ///
401    /// * `event` - The m.key.verification.start event that was sent to us by
402    ///   the other side.
403    pub(crate) fn from_start_event(
404        flow_id: FlowId,
405        content: &StartContent<'_>,
406        identities: IdentitiesBeingVerified,
407        request_handle: Option<RequestHandle>,
408        we_started: bool,
409    ) -> Result<Sas, OutgoingContent> {
410        let inner = InnerSas::from_start_event(
411            identities.store.account.clone(),
412            identities.device_being_verified.clone(),
413            flow_id.clone(),
414            content,
415            identities.own_identity.clone(),
416            identities.identity_being_verified.clone(),
417            request_handle.is_some(),
418        )?;
419
420        let account = identities.store.account.clone();
421
422        Ok(Sas {
423            inner: SharedObservable::new(inner),
424            account,
425            identities_being_verified: identities,
426            flow_id: flow_id.into(),
427            we_started,
428            request_handle,
429        })
430    }
431
432    /// Accept the SAS verification.
433    ///
434    /// This does nothing if the verification was already accepted, otherwise it
435    /// returns an `AcceptEventContent` that needs to be sent out.
436    pub fn accept(&self) -> Option<OutgoingVerificationRequest> {
437        let protocols = as_variant!(self.state(), SasState::Started { protocols } => protocols)?;
438        let settings = AcceptSettings { allowed_methods: protocols.short_authentication_string };
439        self.accept_with_settings(settings)
440    }
441
442    /// Accept the SAS verification customizing the accept method.
443    ///
444    /// This does nothing if the verification was already accepted, otherwise it
445    /// returns an `AcceptEventContent` that needs to be sent out.
446    ///
447    /// Specify a function modifying the attributes of the accept request.
448    pub fn accept_with_settings(
449        &self,
450        settings: AcceptSettings,
451    ) -> Option<OutgoingVerificationRequest> {
452        let old_state = self.state_debug();
453
454        let request = {
455            let mut guard = self.inner.write();
456            let sas: InnerSas = (*guard).clone();
457            let methods = settings.allowed_methods;
458
459            if let Some((sas, content)) = sas.accept(methods) {
460                ObservableWriteGuard::set(&mut guard, sas);
461
462                Some(match content {
463                    OwnedAcceptContent::ToDevice(c) => {
464                        let content = AnyToDeviceEventContent::KeyVerificationAccept(c);
465                        self.content_to_request(&content).into()
466                    }
467                    OwnedAcceptContent::Room(room_id, content) => RoomMessageRequest {
468                        room_id,
469                        txn_id: TransactionId::new(),
470                        content: AnyMessageLikeEventContent::KeyVerificationAccept(content),
471                    }
472                    .into(),
473                })
474            } else {
475                None
476            }
477        };
478
479        let new_state = self.state_debug();
480
481        trace!(
482            flow_id = self.flow_id().as_str(),
483            ?old_state,
484            ?new_state,
485            "Accepted SAS verification"
486        );
487
488        request
489    }
490
491    /// Confirm the Sas verification.
492    ///
493    /// This confirms that the short auth strings match on both sides.
494    ///
495    /// Does nothing if we're not in a state where we can confirm the short auth
496    /// string, otherwise returns a `MacEventContent` that needs to be sent to
497    /// the server.
498    pub async fn confirm(
499        &self,
500    ) -> Result<(Vec<OutgoingVerificationRequest>, Option<SignatureUploadRequest>), CryptoStoreError>
501    {
502        let (contents, done) = {
503            let mut guard = self.inner.write();
504
505            let sas: InnerSas = (*guard).clone();
506            let (sas, contents) = sas.confirm();
507
508            ObservableWriteGuard::set(&mut guard, sas);
509            (contents, guard.is_done())
510        };
511
512        let mac_requests = contents
513            .into_iter()
514            .map(|c| match c {
515                OutgoingContent::ToDevice(c) => self.content_to_request(&c).into(),
516                OutgoingContent::Room(r, c) => {
517                    RoomMessageRequest { room_id: r, txn_id: TransactionId::new(), content: c }
518                        .into()
519                }
520            })
521            .collect::<Vec<_>>();
522
523        if !mac_requests.is_empty() {
524            trace!(
525                user_id = ?self.other_user_id(),
526                device_id = ?self.other_device_id(),
527                "Confirming SAS verification"
528            )
529        }
530
531        if done {
532            match self.mark_as_done().await? {
533                VerificationResult::Cancel(c) => {
534                    Ok((self.cancel_with_code(c).into_iter().collect(), None))
535                }
536                VerificationResult::Ok => Ok((mac_requests, None)),
537                VerificationResult::SignatureUpload(r) => Ok((mac_requests, Some(r))),
538            }
539        } else {
540            Ok((mac_requests, None))
541        }
542    }
543
544    pub(crate) async fn mark_as_done(&self) -> Result<VerificationResult, CryptoStoreError> {
545        self.identities_being_verified
546            .mark_as_done(self.verified_devices().as_deref(), self.verified_identities().as_deref())
547            .await
548    }
549
550    /// Cancel the verification.
551    ///
552    /// This cancels the verification with the `CancelCode::User`.
553    ///
554    /// Returns None if the `Sas` object is already in a canceled state,
555    /// otherwise it returns a request that needs to be sent out.
556    pub fn cancel(&self) -> Option<OutgoingVerificationRequest> {
557        self.cancel_with_code(CancelCode::User)
558    }
559
560    /// Cancel the verification.
561    ///
562    /// This cancels the verification with given `CancelCode`.
563    ///
564    /// **Note**: This method should generally not be used, the [`cancel()`]
565    /// method should be preferred. The SDK will automatically cancel with the
566    /// appropriate cancel code, user initiated cancellations should only cancel
567    /// with the `CancelCode::User`
568    ///
569    /// Returns None if the `Sas` object is already in a canceled state,
570    /// otherwise it returns a request that needs to be sent out.
571    ///
572    /// [`cancel()`]: #method.cancel
573    pub fn cancel_with_code(&self, code: CancelCode) -> Option<OutgoingVerificationRequest> {
574        let content = {
575            let mut guard = self.inner.write();
576
577            if let Some(request) = &self.request_handle {
578                request.cancel_with_code(&code);
579            }
580
581            let sas: InnerSas = (*guard).clone();
582            let (sas, content) = sas.cancel(true, code);
583            ObservableWriteGuard::set(&mut guard, sas);
584
585            content.map(|c| match c {
586                OutgoingContent::Room(room_id, content) => {
587                    RoomMessageRequest { room_id, txn_id: TransactionId::new(), content }.into()
588                }
589                OutgoingContent::ToDevice(c) => self.content_to_request(&c).into(),
590            })
591        };
592
593        content
594    }
595
596    pub(crate) fn cancel_if_timed_out(&self) -> Option<OutgoingVerificationRequest> {
597        if self.is_cancelled() || self.is_done() {
598            None
599        } else if self.timed_out() {
600            self.cancel_with_code(CancelCode::Timeout)
601        } else {
602            None
603        }
604    }
605
606    /// Has the SAS verification flow timed out.
607    pub fn timed_out(&self) -> bool {
608        self.inner.read().timed_out()
609    }
610
611    /// Are we in a state where we can show the short auth string.
612    pub fn can_be_presented(&self) -> bool {
613        self.inner.read().can_be_presented()
614    }
615
616    /// Is the SAS flow done.
617    pub fn is_done(&self) -> bool {
618        self.inner.read().is_done()
619    }
620
621    /// Is the SAS flow canceled.
622    pub fn is_cancelled(&self) -> bool {
623        self.inner.read().is_cancelled()
624    }
625
626    /// Get the emoji version of the short auth string.
627    ///
628    /// Returns None if we can't yet present the short auth string, otherwise
629    /// seven tuples containing the emoji and description.
630    pub fn emoji(&self) -> Option<[Emoji; 7]> {
631        self.inner.read().emoji()
632    }
633
634    /// Get the index of the emoji representing the short auth string
635    ///
636    /// Returns None if we can't yet present the short auth string, otherwise
637    /// seven u8 numbers in the range from 0 to 63 inclusive which can be
638    /// converted to an emoji using the
639    /// [relevant spec entry](https://spec.matrix.org/unstable/client-server-api/#sas-method-emoji).
640    pub fn emoji_index(&self) -> Option<[u8; 7]> {
641        self.inner.read().emoji_index()
642    }
643
644    /// Get the decimal version of the short auth string.
645    ///
646    /// Returns None if we can't yet present the short auth string, otherwise a
647    /// tuple containing three 4-digit integers that represent the short auth
648    /// string.
649    pub fn decimals(&self) -> Option<(u16, u16, u16)> {
650        self.inner.read().decimals()
651    }
652
653    /// Listen for changes in the SAS verification process.
654    ///
655    /// The changes are presented as a stream of [`SasState`] values.
656    ///
657    /// This method can be used to react to changes in the state of the
658    /// verification process, or rather the method can be used to handle
659    /// each step of the verification process.
660    ///
661    /// # Flowchart
662    ///
663    /// The flow of the verification process is pictured bellow. Please note
664    /// that the process can be cancelled at each step of the process.
665    /// Either side can cancel the process.
666    ///
667    /// ```text
668    ///                ┌───────┐
669    ///                │Created│
670    ///                └───┬───┘
671    ///                    │
672    ///                ┌───⌄───┐
673    ///                │Started│
674    ///                └───┬───┘
675    ///                    │
676    ///               ┌────⌄───┐
677    ///               │Accepted│
678    ///               └────┬───┘
679    ///                    │
680    ///            ┌───────⌄──────┐
681    ///            │Keys Exchanged│
682    ///            └───────┬──────┘
683    ///                    │
684    ///            ________⌄________
685    ///           ╱                 ╲       ┌─────────┐
686    ///          ╱   Does the short  ╲______│Cancelled│
687    ///          ╲ auth string match ╱ no   └─────────┘
688    ///           ╲_________________╱
689    ///                    │yes
690    ///                    │
691    ///               ┌────⌄────┐
692    ///               │Confirmed│
693    ///               └────┬────┘
694    ///                    │
695    ///                ┌───⌄───┐
696    ///                │  Done │
697    ///                └───────┘
698    /// ```
699    ///
700    /// # Examples
701    ///
702    /// ```no_run
703    /// use futures_util::{Stream, StreamExt};
704    /// use matrix_sdk_crypto::{Sas, SasState};
705    ///
706    /// # async {
707    /// # let sas: Sas = unimplemented!();
708    /// let mut stream = sas.changes();
709    ///
710    /// while let Some(state) = stream.next().await {
711    ///     match state {
712    ///         SasState::KeysExchanged { emojis, decimals: _ } => {
713    ///             let emojis =
714    ///                 emojis.expect("We only support emoji verification");
715    ///             println!("Do these emojis match {emojis:#?}");
716    ///
717    ///             // Ask the user to confirm or cancel here.
718    ///         }
719    ///         SasState::Done { .. } => {
720    ///             let device = sas.other_device();
721    ///
722    ///             println!(
723    ///                 "Successfully verified device {} {} {:?}",
724    ///                 device.user_id(),
725    ///                 device.device_id(),
726    ///                 device.local_trust_state()
727    ///             );
728    ///
729    ///             break;
730    ///         }
731    ///         SasState::Cancelled(cancel_info) => {
732    ///             println!(
733    ///                 "The verification has been cancelled, reason: {}",
734    ///                 cancel_info.reason()
735    ///             );
736    ///             break;
737    ///         }
738    ///         SasState::Created { .. }
739    ///         | SasState::Started { .. }
740    ///         | SasState::Accepted { .. }
741    ///         | SasState::Confirmed => (),
742    ///     }
743    /// }
744    /// # anyhow::Ok(()) };
745    /// ```
746    pub fn changes(&self) -> impl Stream<Item = SasState> {
747        self.inner.subscribe().map(|s| (&s).into())
748    }
749
750    /// Get the current state of the verification process.
751    pub fn state(&self) -> SasState {
752        (&*self.inner.read()).into()
753    }
754
755    fn state_debug(&self) -> State {
756        (&*self.inner.read()).into()
757    }
758
759    pub(crate) fn receive_any_event(
760        &self,
761        sender: &UserId,
762        content: &AnyVerificationContent<'_>,
763    ) -> Option<(OutgoingContent, Option<RequestInfo>)> {
764        let old_state = self.state_debug();
765
766        let content = {
767            let mut guard = self.inner.write();
768            let sas: InnerSas = (*guard).clone();
769            let (sas, content) = sas.receive_any_event(sender, content);
770
771            ObservableWriteGuard::set(&mut guard, sas);
772
773            content
774        };
775
776        let new_state = self.state_debug();
777        trace!(
778            flow_id = self.flow_id().as_str(),
779            ?old_state,
780            ?new_state,
781            "SAS received an event and changed its state"
782        );
783
784        content
785    }
786
787    pub(crate) fn mark_request_as_sent(&self, request_id: &TransactionId) {
788        let old_state = self.state_debug();
789
790        {
791            let mut guard = self.inner.write();
792
793            let sas: InnerSas = (*guard).clone();
794
795            if let Some(sas) = sas.mark_request_as_sent(request_id) {
796                ObservableWriteGuard::set(&mut guard, sas);
797            } else {
798                error!(
799                    flow_id = self.flow_id().as_str(),
800                    ?request_id,
801                    "Tried to mark a request as sent, but the request ID didn't match"
802                );
803            }
804        };
805
806        let new_state = self.state_debug();
807
808        debug!(
809            flow_id = self.flow_id().as_str(),
810            ?old_state,
811            ?new_state,
812            ?request_id,
813            "Marked a SAS verification HTTP request as sent"
814        );
815    }
816
817    pub(crate) fn verified_devices(&self) -> Option<Arc<[DeviceData]>> {
818        self.inner.read().verified_devices()
819    }
820
821    pub(crate) fn verified_identities(&self) -> Option<Arc<[UserIdentityData]>> {
822        self.inner.read().verified_identities()
823    }
824
825    pub(crate) fn content_to_request(&self, content: &AnyToDeviceEventContent) -> ToDeviceRequest {
826        ToDeviceRequest::with_id(
827            self.other_user_id(),
828            self.other_device_id().to_owned(),
829            content,
830            TransactionId::new(),
831        )
832    }
833}
834
835/// Customize the accept-reply for a verification process
836#[derive(Debug)]
837pub struct AcceptSettings {
838    allowed_methods: Vec<ShortAuthenticationString>,
839}
840
841impl Default for AcceptSettings {
842    /// All methods are allowed
843    fn default() -> Self {
844        Self {
845            allowed_methods: vec![
846                ShortAuthenticationString::Decimal,
847                ShortAuthenticationString::Emoji,
848            ],
849        }
850    }
851}
852
853impl AcceptSettings {
854    /// Create settings restricting the allowed SAS methods
855    ///
856    /// # Arguments
857    ///
858    /// * `methods` - The methods this client allows at most
859    pub fn with_allowed_methods(methods: Vec<ShortAuthenticationString>) -> Self {
860        Self { allowed_methods: methods }
861    }
862}
863
864#[cfg(test)]
865mod tests {
866    use std::sync::Arc;
867
868    use assert_matches::assert_matches;
869    use assert_matches2::assert_let;
870    use matrix_sdk_test::async_test;
871    use ruma::{
872        device_id,
873        events::key::verification::{accept::AcceptMethod, ShortAuthenticationString},
874        user_id, DeviceId, TransactionId, UserId,
875    };
876    use tokio::sync::Mutex;
877
878    use super::Sas;
879    use crate::{
880        olm::PrivateCrossSigningIdentity,
881        store::{CryptoStoreWrapper, MemoryStore},
882        verification::{
883            event_enums::{AcceptContent, KeyContent, MacContent, OutgoingContent, StartContent},
884            VerificationStore,
885        },
886        Account, DeviceData, SasState,
887    };
888
889    fn alice_id() -> &'static UserId {
890        user_id!("@alice:example.org")
891    }
892
893    fn alice_device_id() -> &'static DeviceId {
894        device_id!("JLAFKJWSCS")
895    }
896
897    fn bob_id() -> &'static UserId {
898        user_id!("@bob:example.org")
899    }
900
901    fn bob_device_id() -> &'static DeviceId {
902        device_id!("BOBDEVICE")
903    }
904
905    fn machine_pair_test_helper() -> (VerificationStore, DeviceData, VerificationStore, DeviceData)
906    {
907        let alice = Account::with_device_id(alice_id(), alice_device_id());
908        let alice_device = DeviceData::from_account(&alice);
909
910        let bob = Account::with_device_id(bob_id(), bob_device_id());
911        let bob_device = DeviceData::from_account(&bob);
912
913        let alice_store = VerificationStore {
914            account: alice.static_data.clone(),
915            inner: Arc::new(CryptoStoreWrapper::new(
916                alice.user_id(),
917                alice_device_id(),
918                MemoryStore::new(),
919            )),
920            private_identity: Mutex::new(PrivateCrossSigningIdentity::empty(alice_id())).into(),
921        };
922
923        let bob_store = MemoryStore::new();
924        bob_store.save_devices(vec![alice_device.clone()]);
925
926        let bob_store = VerificationStore {
927            account: bob.static_data.clone(),
928            inner: Arc::new(CryptoStoreWrapper::new(bob.user_id(), bob_device_id(), bob_store)),
929            private_identity: Mutex::new(PrivateCrossSigningIdentity::empty(bob_id())).into(),
930        };
931
932        (alice_store, alice_device, bob_store, bob_device)
933    }
934
935    #[async_test]
936    async fn test_sas_wrapper_full() {
937        let (alice_store, alice_device, bob_store, bob_device) = machine_pair_test_helper();
938
939        let identities = alice_store.get_identities(bob_device).await.unwrap();
940
941        let (alice, content) = Sas::start(identities, TransactionId::new(), true, None, None);
942
943        assert_matches!(alice.state(), SasState::Created { .. });
944
945        let flow_id = alice.flow_id().to_owned();
946        let content = StartContent::try_from(&content).unwrap();
947
948        let identities = bob_store.get_identities(alice_device).await.unwrap();
949        let bob = Sas::from_start_event(flow_id, &content, identities, None, false).unwrap();
950
951        assert_matches!(bob.state(), SasState::Started { .. });
952
953        let request = bob.accept().unwrap();
954
955        let content = OutgoingContent::try_from(request).unwrap();
956        let content = AcceptContent::try_from(&content).unwrap();
957
958        let (content, request_info) =
959            alice.receive_any_event(bob.user_id(), &content.into()).unwrap();
960
961        assert_matches!(alice.state(), SasState::Accepted { .. });
962        assert_matches!(bob.state(), SasState::Accepted { .. });
963        assert!(!alice.can_be_presented());
964        assert!(!bob.can_be_presented());
965
966        alice.mark_request_as_sent(&request_info.unwrap().request_id);
967
968        let content = KeyContent::try_from(&content).unwrap();
969        let (content, request_info) =
970            bob.receive_any_event(alice.user_id(), &content.into()).unwrap();
971        assert!(!bob.can_be_presented());
972        assert_matches!(bob.state(), SasState::Accepted { .. });
973        bob.mark_request_as_sent(&request_info.unwrap().request_id);
974
975        assert!(bob.can_be_presented());
976        assert_matches!(bob.state(), SasState::KeysExchanged { .. });
977
978        let content = KeyContent::try_from(&content).unwrap();
979        alice.receive_any_event(bob.user_id(), &content.into());
980        assert_matches!(alice.state(), SasState::KeysExchanged { .. });
981        assert!(alice.can_be_presented());
982
983        assert_eq!(alice.emoji().unwrap(), bob.emoji().unwrap());
984        assert_eq!(alice.decimals().unwrap(), bob.decimals().unwrap());
985
986        let mut requests = alice.confirm().await.unwrap().0;
987        assert_matches!(alice.state(), SasState::Confirmed);
988        assert!(requests.len() == 1);
989        let request = requests.pop().unwrap();
990        let content = OutgoingContent::try_from(request).unwrap();
991        let content = MacContent::try_from(&content).unwrap();
992        bob.receive_any_event(alice.user_id(), &content.into());
993        assert_matches!(bob.state(), SasState::KeysExchanged { .. });
994
995        let mut requests = bob.confirm().await.unwrap().0;
996        assert_matches!(bob.state(), SasState::Done { .. });
997        assert!(requests.len() == 1);
998        let request = requests.pop().unwrap();
999        let content = OutgoingContent::try_from(request).unwrap();
1000        let content = MacContent::try_from(&content).unwrap();
1001        alice.receive_any_event(bob.user_id(), &content.into());
1002
1003        assert!(alice.verified_devices().unwrap().contains(alice.other_device()));
1004        assert!(bob.verified_devices().unwrap().contains(bob.other_device()));
1005        assert_matches!(alice.state(), SasState::Done { .. });
1006        assert_matches!(bob.state(), SasState::Done { .. });
1007    }
1008
1009    #[async_test]
1010    async fn test_sas_with_restricted_methods() {
1011        let (alice_store, alice_device, bob_store, bob_device) = machine_pair_test_helper();
1012        let identities = alice_store.get_identities(bob_device).await.unwrap();
1013
1014        let short_auth_strings = vec![ShortAuthenticationString::Decimal];
1015        let (alice, content) =
1016            Sas::start(identities, TransactionId::new(), true, None, Some(short_auth_strings));
1017
1018        let flow_id = alice.flow_id().to_owned();
1019        let content = StartContent::try_from(&content).unwrap();
1020
1021        let identities = bob_store.get_identities(alice_device).await.unwrap();
1022        let bob = Sas::from_start_event(flow_id, &content, identities, None, false).unwrap();
1023
1024        let request = bob.accept().unwrap();
1025
1026        let content = OutgoingContent::try_from(request).unwrap();
1027        let content = AcceptContent::try_from(&content).unwrap();
1028        assert_let!(AcceptMethod::SasV1(content) = content.method());
1029
1030        assert!(content.short_authentication_string.contains(&ShortAuthenticationString::Decimal));
1031        assert!(!content.short_authentication_string.contains(&ShortAuthenticationString::Emoji));
1032    }
1033}