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}