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: Box::new(AnyMessageLikeEventContent::KeyVerificationAccept(
471                            content,
472                        )),
473                    }
474                    .into(),
475                })
476            } else {
477                None
478            }
479        };
480
481        let new_state = self.state_debug();
482
483        trace!(
484            flow_id = self.flow_id().as_str(),
485            ?old_state,
486            ?new_state,
487            "Accepted SAS verification"
488        );
489
490        request
491    }
492
493    /// Confirm the Sas verification.
494    ///
495    /// This confirms that the short auth strings match on both sides.
496    ///
497    /// Does nothing if we're not in a state where we can confirm the short auth
498    /// string, otherwise returns a `MacEventContent` that needs to be sent to
499    /// the server.
500    pub async fn confirm(
501        &self,
502    ) -> Result<(Vec<OutgoingVerificationRequest>, Option<SignatureUploadRequest>), CryptoStoreError>
503    {
504        let (contents, done) = {
505            let mut guard = self.inner.write();
506
507            let sas: InnerSas = (*guard).clone();
508            let (sas, contents) = sas.confirm();
509
510            ObservableWriteGuard::set(&mut guard, sas);
511            (contents, guard.is_done())
512        };
513
514        let mac_requests = contents
515            .into_iter()
516            .map(|c| match c {
517                OutgoingContent::ToDevice(c) => self.content_to_request(&c).into(),
518                OutgoingContent::Room(r, c) => {
519                    RoomMessageRequest { room_id: r, txn_id: TransactionId::new(), content: c }
520                        .into()
521                }
522            })
523            .collect::<Vec<_>>();
524
525        if !mac_requests.is_empty() {
526            trace!(
527                user_id = ?self.other_user_id(),
528                device_id = ?self.other_device_id(),
529                "Confirming SAS verification"
530            )
531        }
532
533        if done {
534            match self.mark_as_done().await? {
535                VerificationResult::Cancel(c) => {
536                    Ok((self.cancel_with_code(c).into_iter().collect(), None))
537                }
538                VerificationResult::Ok => Ok((mac_requests, None)),
539                VerificationResult::SignatureUpload(r) => Ok((mac_requests, Some(r))),
540            }
541        } else {
542            Ok((mac_requests, None))
543        }
544    }
545
546    pub(crate) async fn mark_as_done(&self) -> Result<VerificationResult, CryptoStoreError> {
547        self.identities_being_verified
548            .mark_as_done(self.verified_devices().as_deref(), self.verified_identities().as_deref())
549            .await
550    }
551
552    /// Cancel the verification.
553    ///
554    /// This cancels the verification with the `CancelCode::User`.
555    ///
556    /// Returns None if the `Sas` object is already in a canceled state,
557    /// otherwise it returns a request that needs to be sent out.
558    pub fn cancel(&self) -> Option<OutgoingVerificationRequest> {
559        self.cancel_with_code(CancelCode::User)
560    }
561
562    /// Cancel the verification.
563    ///
564    /// This cancels the verification with given `CancelCode`.
565    ///
566    /// **Note**: This method should generally not be used, the [`cancel()`]
567    /// method should be preferred. The SDK will automatically cancel with the
568    /// appropriate cancel code, user initiated cancellations should only cancel
569    /// with the `CancelCode::User`
570    ///
571    /// Returns None if the `Sas` object is already in a canceled state,
572    /// otherwise it returns a request that needs to be sent out.
573    ///
574    /// [`cancel()`]: #method.cancel
575    pub fn cancel_with_code(&self, code: CancelCode) -> Option<OutgoingVerificationRequest> {
576        let content = {
577            let mut guard = self.inner.write();
578
579            if let Some(request) = &self.request_handle {
580                request.cancel_with_code(&code);
581            }
582
583            let sas: InnerSas = (*guard).clone();
584            let (sas, content) = sas.cancel(true, code);
585            ObservableWriteGuard::set(&mut guard, sas);
586
587            content.map(|c| match c {
588                OutgoingContent::Room(room_id, content) => {
589                    RoomMessageRequest { room_id, txn_id: TransactionId::new(), content }.into()
590                }
591                OutgoingContent::ToDevice(c) => self.content_to_request(&c).into(),
592            })
593        };
594
595        content
596    }
597
598    pub(crate) fn cancel_if_timed_out(&self) -> Option<OutgoingVerificationRequest> {
599        if self.is_cancelled() || self.is_done() {
600            None
601        } else if self.timed_out() {
602            self.cancel_with_code(CancelCode::Timeout)
603        } else {
604            None
605        }
606    }
607
608    /// Has the SAS verification flow timed out.
609    pub fn timed_out(&self) -> bool {
610        self.inner.read().timed_out()
611    }
612
613    /// Are we in a state where we can show the short auth string.
614    pub fn can_be_presented(&self) -> bool {
615        self.inner.read().can_be_presented()
616    }
617
618    /// Is the SAS flow done.
619    pub fn is_done(&self) -> bool {
620        self.inner.read().is_done()
621    }
622
623    /// Is the SAS flow canceled.
624    pub fn is_cancelled(&self) -> bool {
625        self.inner.read().is_cancelled()
626    }
627
628    /// Get the emoji version of the short auth string.
629    ///
630    /// Returns None if we can't yet present the short auth string, otherwise
631    /// seven tuples containing the emoji and description.
632    pub fn emoji(&self) -> Option<[Emoji; 7]> {
633        self.inner.read().emoji()
634    }
635
636    /// Get the index of the emoji representing the short auth string
637    ///
638    /// Returns None if we can't yet present the short auth string, otherwise
639    /// seven u8 numbers in the range from 0 to 63 inclusive which can be
640    /// converted to an emoji using the
641    /// [relevant spec entry](https://spec.matrix.org/unstable/client-server-api/#sas-method-emoji).
642    pub fn emoji_index(&self) -> Option<[u8; 7]> {
643        self.inner.read().emoji_index()
644    }
645
646    /// Get the decimal version of the short auth string.
647    ///
648    /// Returns None if we can't yet present the short auth string, otherwise a
649    /// tuple containing three 4-digit integers that represent the short auth
650    /// string.
651    pub fn decimals(&self) -> Option<(u16, u16, u16)> {
652        self.inner.read().decimals()
653    }
654
655    /// Listen for changes in the SAS verification process.
656    ///
657    /// The changes are presented as a stream of [`SasState`] values.
658    ///
659    /// This method can be used to react to changes in the state of the
660    /// verification process, or rather the method can be used to handle
661    /// each step of the verification process.
662    ///
663    /// # Flowchart
664    ///
665    /// The flow of the verification process is pictured bellow. Please note
666    /// that the process can be cancelled at each step of the process.
667    /// Either side can cancel the process.
668    ///
669    /// ```text
670    ///                ┌───────┐
671    ///                │Created│
672    ///                └───┬───┘
673    ///                    │
674    ///                ┌───⌄───┐
675    ///                │Started│
676    ///                └───┬───┘
677    ///                    │
678    ///               ┌────⌄───┐
679    ///               │Accepted│
680    ///               └────┬───┘
681    ///                    │
682    ///            ┌───────⌄──────┐
683    ///            │Keys Exchanged│
684    ///            └───────┬──────┘
685    ///                    │
686    ///            ________⌄________
687    ///           ╱                 ╲       ┌─────────┐
688    ///          ╱   Does the short  ╲______│Cancelled│
689    ///          ╲ auth string match ╱ no   └─────────┘
690    ///           ╲_________________╱
691    ///                    │yes
692    ///                    │
693    ///               ┌────⌄────┐
694    ///               │Confirmed│
695    ///               └────┬────┘
696    ///                    │
697    ///                ┌───⌄───┐
698    ///                │  Done │
699    ///                └───────┘
700    /// ```
701    ///
702    /// # Examples
703    ///
704    /// ```no_run
705    /// use futures_util::{Stream, StreamExt};
706    /// use matrix_sdk_crypto::{Sas, SasState};
707    ///
708    /// # async {
709    /// # let sas: Sas = unimplemented!();
710    /// let mut stream = sas.changes();
711    ///
712    /// while let Some(state) = stream.next().await {
713    ///     match state {
714    ///         SasState::KeysExchanged { emojis, decimals: _ } => {
715    ///             let emojis =
716    ///                 emojis.expect("We only support emoji verification");
717    ///             println!("Do these emojis match {emojis:#?}");
718    ///
719    ///             // Ask the user to confirm or cancel here.
720    ///         }
721    ///         SasState::Done { .. } => {
722    ///             let device = sas.other_device();
723    ///
724    ///             println!(
725    ///                 "Successfully verified device {} {} {:?}",
726    ///                 device.user_id(),
727    ///                 device.device_id(),
728    ///                 device.local_trust_state()
729    ///             );
730    ///
731    ///             break;
732    ///         }
733    ///         SasState::Cancelled(cancel_info) => {
734    ///             println!(
735    ///                 "The verification has been cancelled, reason: {}",
736    ///                 cancel_info.reason()
737    ///             );
738    ///             break;
739    ///         }
740    ///         SasState::Created { .. }
741    ///         | SasState::Started { .. }
742    ///         | SasState::Accepted { .. }
743    ///         | SasState::Confirmed => (),
744    ///     }
745    /// }
746    /// # anyhow::Ok(()) };
747    /// ```
748    pub fn changes(&self) -> impl Stream<Item = SasState> {
749        self.inner.subscribe().map(|s| (&s).into())
750    }
751
752    /// Get the current state of the verification process.
753    pub fn state(&self) -> SasState {
754        (&*self.inner.read()).into()
755    }
756
757    fn state_debug(&self) -> State {
758        (&*self.inner.read()).into()
759    }
760
761    pub(crate) fn receive_any_event(
762        &self,
763        sender: &UserId,
764        content: &AnyVerificationContent<'_>,
765    ) -> Option<(OutgoingContent, Option<RequestInfo>)> {
766        let old_state = self.state_debug();
767
768        let content = {
769            let mut guard = self.inner.write();
770            let sas: InnerSas = (*guard).clone();
771            let (sas, content) = sas.receive_any_event(sender, content);
772
773            ObservableWriteGuard::set(&mut guard, sas);
774
775            content
776        };
777
778        let new_state = self.state_debug();
779        trace!(
780            flow_id = self.flow_id().as_str(),
781            ?old_state,
782            ?new_state,
783            "SAS received an event and changed its state"
784        );
785
786        content
787    }
788
789    pub(crate) fn mark_request_as_sent(&self, request_id: &TransactionId) {
790        let old_state = self.state_debug();
791
792        {
793            let mut guard = self.inner.write();
794
795            let sas: InnerSas = (*guard).clone();
796
797            if let Some(sas) = sas.mark_request_as_sent(request_id) {
798                ObservableWriteGuard::set(&mut guard, sas);
799            } else {
800                error!(
801                    flow_id = self.flow_id().as_str(),
802                    ?request_id,
803                    "Tried to mark a request as sent, but the request ID didn't match"
804                );
805            }
806        };
807
808        let new_state = self.state_debug();
809
810        debug!(
811            flow_id = self.flow_id().as_str(),
812            ?old_state,
813            ?new_state,
814            ?request_id,
815            "Marked a SAS verification HTTP request as sent"
816        );
817    }
818
819    pub(crate) fn verified_devices(&self) -> Option<Arc<[DeviceData]>> {
820        self.inner.read().verified_devices()
821    }
822
823    pub(crate) fn verified_identities(&self) -> Option<Arc<[UserIdentityData]>> {
824        self.inner.read().verified_identities()
825    }
826
827    pub(crate) fn content_to_request(&self, content: &AnyToDeviceEventContent) -> ToDeviceRequest {
828        ToDeviceRequest::with_id(
829            self.other_user_id(),
830            self.other_device_id().to_owned(),
831            content,
832            TransactionId::new(),
833        )
834    }
835}
836
837/// Customize the accept-reply for a verification process
838#[derive(Debug)]
839pub struct AcceptSettings {
840    allowed_methods: Vec<ShortAuthenticationString>,
841}
842
843impl Default for AcceptSettings {
844    /// All methods are allowed
845    fn default() -> Self {
846        Self {
847            allowed_methods: vec![
848                ShortAuthenticationString::Decimal,
849                ShortAuthenticationString::Emoji,
850            ],
851        }
852    }
853}
854
855impl AcceptSettings {
856    /// Create settings restricting the allowed SAS methods
857    ///
858    /// # Arguments
859    ///
860    /// * `methods` - The methods this client allows at most
861    pub fn with_allowed_methods(methods: Vec<ShortAuthenticationString>) -> Self {
862        Self { allowed_methods: methods }
863    }
864}
865
866#[cfg(test)]
867mod tests {
868    use std::sync::Arc;
869
870    use assert_matches::assert_matches;
871    use assert_matches2::assert_let;
872    use matrix_sdk_test::async_test;
873    use ruma::{
874        device_id,
875        events::key::verification::{accept::AcceptMethod, ShortAuthenticationString},
876        user_id, DeviceId, TransactionId, UserId,
877    };
878    use tokio::sync::Mutex;
879
880    use super::Sas;
881    use crate::{
882        olm::PrivateCrossSigningIdentity,
883        store::{CryptoStoreWrapper, MemoryStore},
884        verification::{
885            event_enums::{AcceptContent, KeyContent, MacContent, OutgoingContent, StartContent},
886            VerificationStore,
887        },
888        Account, DeviceData, SasState,
889    };
890
891    fn alice_id() -> &'static UserId {
892        user_id!("@alice:example.org")
893    }
894
895    fn alice_device_id() -> &'static DeviceId {
896        device_id!("JLAFKJWSCS")
897    }
898
899    fn bob_id() -> &'static UserId {
900        user_id!("@bob:example.org")
901    }
902
903    fn bob_device_id() -> &'static DeviceId {
904        device_id!("BOBDEVICE")
905    }
906
907    fn machine_pair_test_helper() -> (VerificationStore, DeviceData, VerificationStore, DeviceData)
908    {
909        let alice = Account::with_device_id(alice_id(), alice_device_id());
910        let alice_device = DeviceData::from_account(&alice);
911
912        let bob = Account::with_device_id(bob_id(), bob_device_id());
913        let bob_device = DeviceData::from_account(&bob);
914
915        let alice_store = VerificationStore {
916            account: alice.static_data.clone(),
917            inner: Arc::new(CryptoStoreWrapper::new(
918                alice.user_id(),
919                alice_device_id(),
920                MemoryStore::new(),
921            )),
922            private_identity: Mutex::new(PrivateCrossSigningIdentity::empty(alice_id())).into(),
923        };
924
925        let bob_store = MemoryStore::new();
926        bob_store.save_devices(vec![alice_device.clone()]);
927
928        let bob_store = VerificationStore {
929            account: bob.static_data.clone(),
930            inner: Arc::new(CryptoStoreWrapper::new(bob.user_id(), bob_device_id(), bob_store)),
931            private_identity: Mutex::new(PrivateCrossSigningIdentity::empty(bob_id())).into(),
932        };
933
934        (alice_store, alice_device, bob_store, bob_device)
935    }
936
937    #[async_test]
938    async fn test_sas_wrapper_full() {
939        let (alice_store, alice_device, bob_store, bob_device) = machine_pair_test_helper();
940
941        let identities = alice_store.get_identities(bob_device).await.unwrap();
942
943        let (alice, content) = Sas::start(identities, TransactionId::new(), true, None, None);
944
945        assert_matches!(alice.state(), SasState::Created { .. });
946
947        let flow_id = alice.flow_id().to_owned();
948        let content = StartContent::try_from(&content).unwrap();
949
950        let identities = bob_store.get_identities(alice_device).await.unwrap();
951        let bob = Sas::from_start_event(flow_id, &content, identities, None, false).unwrap();
952
953        assert_matches!(bob.state(), SasState::Started { .. });
954
955        let request = bob.accept().unwrap();
956
957        let content = OutgoingContent::try_from(request).unwrap();
958        let content = AcceptContent::try_from(&content).unwrap();
959
960        let (content, request_info) =
961            alice.receive_any_event(bob.user_id(), &content.into()).unwrap();
962
963        assert_matches!(alice.state(), SasState::Accepted { .. });
964        assert_matches!(bob.state(), SasState::Accepted { .. });
965        assert!(!alice.can_be_presented());
966        assert!(!bob.can_be_presented());
967
968        alice.mark_request_as_sent(&request_info.unwrap().request_id);
969
970        let content = KeyContent::try_from(&content).unwrap();
971        let (content, request_info) =
972            bob.receive_any_event(alice.user_id(), &content.into()).unwrap();
973        assert!(!bob.can_be_presented());
974        assert_matches!(bob.state(), SasState::Accepted { .. });
975        bob.mark_request_as_sent(&request_info.unwrap().request_id);
976
977        assert!(bob.can_be_presented());
978        assert_matches!(bob.state(), SasState::KeysExchanged { .. });
979
980        let content = KeyContent::try_from(&content).unwrap();
981        alice.receive_any_event(bob.user_id(), &content.into());
982        assert_matches!(alice.state(), SasState::KeysExchanged { .. });
983        assert!(alice.can_be_presented());
984
985        assert_eq!(alice.emoji().unwrap(), bob.emoji().unwrap());
986        assert_eq!(alice.decimals().unwrap(), bob.decimals().unwrap());
987
988        let mut requests = alice.confirm().await.unwrap().0;
989        assert_matches!(alice.state(), SasState::Confirmed);
990        assert!(requests.len() == 1);
991        let request = requests.pop().unwrap();
992        let content = OutgoingContent::try_from(request).unwrap();
993        let content = MacContent::try_from(&content).unwrap();
994        bob.receive_any_event(alice.user_id(), &content.into());
995        assert_matches!(bob.state(), SasState::KeysExchanged { .. });
996
997        let mut requests = bob.confirm().await.unwrap().0;
998        assert_matches!(bob.state(), SasState::Done { .. });
999        assert!(requests.len() == 1);
1000        let request = requests.pop().unwrap();
1001        let content = OutgoingContent::try_from(request).unwrap();
1002        let content = MacContent::try_from(&content).unwrap();
1003        alice.receive_any_event(bob.user_id(), &content.into());
1004
1005        assert!(alice.verified_devices().unwrap().contains(alice.other_device()));
1006        assert!(bob.verified_devices().unwrap().contains(bob.other_device()));
1007        assert_matches!(alice.state(), SasState::Done { .. });
1008        assert_matches!(bob.state(), SasState::Done { .. });
1009    }
1010
1011    #[async_test]
1012    async fn test_sas_with_restricted_methods() {
1013        let (alice_store, alice_device, bob_store, bob_device) = machine_pair_test_helper();
1014        let identities = alice_store.get_identities(bob_device).await.unwrap();
1015
1016        let short_auth_strings = vec![ShortAuthenticationString::Decimal];
1017        let (alice, content) =
1018            Sas::start(identities, TransactionId::new(), true, None, Some(short_auth_strings));
1019
1020        let flow_id = alice.flow_id().to_owned();
1021        let content = StartContent::try_from(&content).unwrap();
1022
1023        let identities = bob_store.get_identities(alice_device).await.unwrap();
1024        let bob = Sas::from_start_event(flow_id, &content, identities, None, false).unwrap();
1025
1026        let request = bob.accept().unwrap();
1027
1028        let content = OutgoingContent::try_from(request).unwrap();
1029        let content = AcceptContent::try_from(&content).unwrap();
1030        assert_let!(AcceptMethod::SasV1(content) = content.method());
1031
1032        assert!(content.short_authentication_string.contains(&ShortAuthenticationString::Decimal));
1033        assert!(!content.short_authentication_string.contains(&ShortAuthenticationString::Emoji));
1034    }
1035}