matrix_sdk/encryption/verification/
sas.rs

1// Copyright 2020 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use futures_core::Stream;
16use matrix_sdk_base::crypto::{
17    AcceptSettings, CancelInfo, DeviceData, Emoji, Sas as BaseSas, SasState,
18};
19use ruma::{events::key::verification::cancel::CancelCode, RoomId, UserId};
20
21use crate::{error::Result, Client};
22
23/// An object controlling the short auth string verification flow.
24#[derive(Debug, Clone)]
25pub struct SasVerification {
26    pub(crate) inner: BaseSas,
27    pub(crate) client: Client,
28}
29
30impl SasVerification {
31    /// Accept the interactive verification flow.
32    pub async fn accept(&self) -> Result<()> {
33        self.accept_with_settings(Default::default()).await
34    }
35
36    /// Accept the interactive verification flow with specific settings.
37    ///
38    /// # Arguments
39    ///
40    /// * `settings` - specific customizations to the verification flow.
41    ///
42    /// # Examples
43    ///
44    /// ```no_run
45    /// # use matrix_sdk::Client;
46    /// # use url::Url;
47    /// # use ruma::user_id;
48    /// use matrix_sdk::{
49    ///     encryption::verification::{AcceptSettings, SasVerification},
50    ///     ruma::events::key::verification::ShortAuthenticationString,
51    /// };
52    ///
53    /// # let flow_id = "someID";
54    /// # let user_id = user_id!("@alice:example");
55    /// # async {
56    /// # let homeserver = Url::parse("http://example.com")?;
57    /// # let client = Client::new(homeserver).await?;
58    /// let sas = client
59    ///     .encryption()
60    ///     .get_verification(&user_id, flow_id)
61    ///     .await
62    ///     .and_then(|v| v.sas());
63    ///
64    /// if let Some(sas) = sas {
65    ///     let only_decimal = AcceptSettings::with_allowed_methods(vec![
66    ///         ShortAuthenticationString::Decimal,
67    ///     ]);
68    ///
69    ///     sas.accept_with_settings(only_decimal).await?;
70    /// }
71    /// # anyhow::Ok(()) };
72    /// ```
73    pub async fn accept_with_settings(&self, settings: AcceptSettings) -> Result<()> {
74        if let Some(request) = self.inner.accept_with_settings(settings) {
75            self.client.send_verification_request(request).await?;
76        }
77        Ok(())
78    }
79
80    /// Confirm that the short auth strings match on both sides.
81    pub async fn confirm(&self) -> Result<()> {
82        let (requests, signature) = self.inner.confirm().await?;
83
84        for request in requests {
85            self.client.send_verification_request(request).await?;
86        }
87
88        if let Some(s) = signature {
89            self.client.send(s).await?;
90        }
91
92        Ok(())
93    }
94
95    /// Cancel the interactive verification flow because the short auth strings
96    /// didn't match on both sides.
97    pub async fn mismatch(&self) -> Result<()> {
98        if let Some(request) = self.inner.cancel_with_code(CancelCode::MismatchedSas) {
99            self.client.send_verification_request(request).await?;
100        }
101
102        Ok(())
103    }
104
105    /// Cancel the interactive verification flow.
106    pub async fn cancel(&self) -> Result<()> {
107        if let Some(request) = self.inner.cancel() {
108            self.client.send_verification_request(request).await?;
109        }
110
111        Ok(())
112    }
113
114    /// Get the emoji version of the short auth string.
115    ///
116    /// # Examples
117    ///
118    /// ```no_run
119    /// # use matrix_sdk::Client;
120    /// # use url::Url;
121    /// # use ruma::user_id;
122    /// use matrix_sdk::{
123    ///     encryption::verification::{AcceptSettings, SasVerification},
124    ///     ruma::events::key::verification::ShortAuthenticationString,
125    /// };
126    ///
127    /// # let flow_id = "someID";
128    /// # let user_id = user_id!("@alice:example");
129    /// # async {
130    /// # let homeserver = Url::parse("http://example.com")?;
131    /// # let client = Client::new(homeserver).await?;
132    /// let sas_verification = client
133    ///     .encryption()
134    ///     .get_verification(&user_id, flow_id)
135    ///     .await
136    ///     .and_then(|v| v.sas());
137    ///
138    /// if let Some(emojis) = sas_verification.and_then(|s| s.emoji()) {
139    ///     let emoji_string = emojis
140    ///         .iter()
141    ///         .map(|e| format!("{:^12}", e.symbol))
142    ///         .collect::<Vec<_>>()
143    ///         .join("");
144    ///
145    ///     let description = emojis
146    ///         .iter()
147    ///         .map(|e| format!("{:^12}", e.description))
148    ///         .collect::<Vec<_>>()
149    ///         .join("");
150    ///
151    ///     println!("Do the emojis match?\n{emoji_string}\n{description}");
152    /// }
153    /// # anyhow::Ok(()) };
154    /// ```
155    pub fn emoji(&self) -> Option<[Emoji; 7]> {
156        self.inner.emoji()
157    }
158
159    /// Get the decimal version of the short auth string.
160    pub fn decimals(&self) -> Option<(u16, u16, u16)> {
161        self.inner.decimals()
162    }
163
164    /// Does this verification flow support emoji for the short authentication
165    /// string.
166    pub fn supports_emoji(&self) -> bool {
167        self.inner.supports_emoji()
168    }
169
170    /// Is the verification process done.
171    pub fn is_done(&self) -> bool {
172        self.inner.is_done()
173    }
174
175    /// Are we in a state where we can show the short auth string.
176    pub fn can_be_presented(&self) -> bool {
177        self.inner.can_be_presented()
178    }
179
180    /// Did we initiate the verification flow.
181    pub fn we_started(&self) -> bool {
182        self.inner.we_started()
183    }
184
185    /// Get info about the cancellation if the verification flow has been
186    /// cancelled.
187    pub fn cancel_info(&self) -> Option<CancelInfo> {
188        self.inner.cancel_info()
189    }
190
191    /// Is the verification process canceled.
192    pub fn is_cancelled(&self) -> bool {
193        self.inner.is_cancelled()
194    }
195
196    /// Get the other users device that we're verifying.
197    pub fn other_device(&self) -> &DeviceData {
198        self.inner.other_device()
199    }
200
201    /// Did this verification flow start from a verification request.
202    pub fn started_from_request(&self) -> bool {
203        self.inner.started_from_request()
204    }
205
206    /// Is this a verification that is verifying one of our own devices.
207    pub fn is_self_verification(&self) -> bool {
208        self.inner.is_self_verification()
209    }
210
211    /// Get our own user id.
212    pub fn own_user_id(&self) -> &UserId {
213        self.inner.user_id()
214    }
215
216    /// Get the user id of the other user participating in this verification
217    /// flow.
218    pub fn other_user_id(&self) -> &UserId {
219        self.inner.other_user_id()
220    }
221
222    /// Listen for changes in the SAS verification process.
223    ///
224    /// The changes are presented as a stream of [`SasState`] values.
225    ///
226    /// This method can be used to react to changes in the state of the
227    /// verification process, or rather the method can be used to handle
228    /// each step of the verification process.
229    ///
230    /// # Flowchart
231    ///
232    /// The flow of the verification process is pictured bellow. Please note
233    /// that the process can be cancelled at each step of the process.
234    /// Either side can cancel the process.
235    ///
236    /// ```text
237    ///                ┌───────┐
238    ///                │Created│
239    ///                └───┬───┘
240    ///                    │
241    ///                ┌───⌄───┐
242    ///                │Started│
243    ///                └───┬───┘
244    ///                    │
245    ///               ┌────⌄───┐
246    ///               │Accepted│
247    ///               └────┬───┘
248    ///                    │
249    ///            ┌───────⌄──────┐
250    ///            │Keys Exchanged│
251    ///            └───────┬──────┘
252    ///                    │
253    ///            ________⌄________
254    ///           ╱                 ╲       ┌─────────┐
255    ///          ╱   Does the short  ╲______│Cancelled│
256    ///          ╲ auth string match ╱ no   └─────────┘
257    ///           ╲_________________╱
258    ///                    │yes
259    ///                    │
260    ///               ┌────⌄────┐
261    ///               │Confirmed│
262    ///               └────┬────┘
263    ///                    │
264    ///                ┌───⌄───┐
265    ///                │  Done │
266    ///                └───────┘
267    /// ```
268    /// # Examples
269    ///
270    /// ```no_run
271    /// use futures_util::{Stream, StreamExt};
272    /// use matrix_sdk::encryption::verification::{SasState, SasVerification};
273    ///
274    /// # async {
275    /// # let sas: SasVerification = unimplemented!();
276    /// # let user_confirmed = false;
277    /// let mut stream = sas.changes();
278    ///
279    /// while let Some(state) = stream.next().await {
280    ///     match state {
281    ///         SasState::KeysExchanged { emojis, decimals: _ } => {
282    ///             let emojis =
283    ///                 emojis.expect("We only support emoji verification");
284    ///             println!("Do these emojis match {emojis:#?}");
285    ///
286    ///             // Ask the user to confirm or cancel here.
287    ///             if user_confirmed {
288    ///                 sas.confirm().await?;
289    ///             } else {
290    ///                 sas.cancel().await?;
291    ///             }
292    ///         }
293    ///         SasState::Done { .. } => {
294    ///             let device = sas.other_device();
295    ///
296    ///             println!(
297    ///                 "Successfully verified device {} {} {:?}",
298    ///                 device.user_id(),
299    ///                 device.device_id(),
300    ///                 device.local_trust_state()
301    ///             );
302    ///
303    ///             break;
304    ///         }
305    ///         SasState::Cancelled(cancel_info) => {
306    ///             println!(
307    ///                 "The verification has been cancelled, reason: {}",
308    ///                 cancel_info.reason()
309    ///             );
310    ///             break;
311    ///         }
312    ///         SasState::Created { .. }
313    ///         | SasState::Started { .. }
314    ///         | SasState::Accepted { .. }
315    ///         | SasState::Confirmed => (),
316    ///     }
317    /// }
318    /// # anyhow::Ok(()) };
319    /// ```
320    pub fn changes(&self) -> impl Stream<Item = SasState> {
321        self.inner.changes()
322    }
323
324    /// Get the current state the verification process is in.
325    ///
326    /// To listen to changes to the [`SasState`] use the
327    /// [`SasVerification::changes`] method.
328    pub fn state(&self) -> SasState {
329        self.inner.state()
330    }
331
332    /// Get the room ID, if the verification is happening inside a room.
333    pub fn room_id(&self) -> Option<&RoomId> {
334        self.inner.room_id()
335    }
336}