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