Skip to main content

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