matrix_sdk_ffi/
session_verification.rs

1use std::sync::{Arc, RwLock};
2
3use futures_util::StreamExt;
4use matrix_sdk::{
5    encryption::{
6        identities::UserIdentity,
7        verification::{SasState, SasVerification, VerificationRequest, VerificationRequestState},
8        Encryption,
9    },
10    ruma::events::key::verification::VerificationMethod,
11    Account,
12};
13use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
14use ruma::UserId;
15use tracing::{error, warn};
16
17use crate::{
18    client::UserProfile, error::ClientError, runtime::get_runtime_handle, utils::Timestamp,
19};
20
21#[derive(uniffi::Object)]
22pub struct SessionVerificationEmoji {
23    symbol: String,
24    description: String,
25}
26
27#[matrix_sdk_ffi_macros::export]
28impl SessionVerificationEmoji {
29    pub fn symbol(&self) -> String {
30        self.symbol.clone()
31    }
32
33    pub fn description(&self) -> String {
34        self.description.clone()
35    }
36}
37
38#[derive(uniffi::Enum)]
39pub enum SessionVerificationData {
40    Emojis { emojis: Vec<Arc<SessionVerificationEmoji>>, indices: Vec<u8> },
41    Decimals { values: Vec<u16> },
42}
43
44/// Details about the incoming verification request
45#[derive(uniffi::Record)]
46pub struct SessionVerificationRequestDetails {
47    sender_profile: UserProfile,
48    flow_id: String,
49    device_id: String,
50    device_display_name: Option<String>,
51    /// First time this device was seen in milliseconds since epoch.
52    first_seen_timestamp: Timestamp,
53}
54
55#[matrix_sdk_ffi_macros::export(callback_interface)]
56pub trait SessionVerificationControllerDelegate: SyncOutsideWasm + SendOutsideWasm {
57    fn did_receive_verification_request(&self, details: SessionVerificationRequestDetails);
58    fn did_accept_verification_request(&self);
59    fn did_start_sas_verification(&self);
60    fn did_receive_verification_data(&self, data: SessionVerificationData);
61    fn did_fail(&self);
62    fn did_cancel(&self);
63    fn did_finish(&self);
64}
65
66pub type Delegate = Arc<RwLock<Option<Box<dyn SessionVerificationControllerDelegate>>>>;
67
68#[derive(Clone, uniffi::Object)]
69pub struct SessionVerificationController {
70    encryption: Encryption,
71    user_identity: UserIdentity,
72    account: Account,
73    delegate: Delegate,
74    verification_request: Arc<RwLock<Option<VerificationRequest>>>,
75    sas_verification: Arc<RwLock<Option<SasVerification>>>,
76}
77
78#[matrix_sdk_ffi_macros::export]
79impl SessionVerificationController {
80    pub fn set_delegate(&self, delegate: Option<Box<dyn SessionVerificationControllerDelegate>>) {
81        *self.delegate.write().unwrap() = delegate;
82    }
83
84    /// Set this particular request as the currently active one and register for
85    /// events pertaining it.
86    /// * `sender_id` - The user requesting verification.
87    /// * `flow_id` - - The ID that uniquely identifies the verification flow.
88    pub async fn acknowledge_verification_request(
89        &self,
90        sender_id: String,
91        flow_id: String,
92    ) -> Result<(), ClientError> {
93        let sender_id = UserId::parse(sender_id.clone())?;
94
95        let verification_request = self
96            .encryption
97            .get_verification_request(&sender_id, flow_id)
98            .await
99            .ok_or(ClientError::from_str("Unknown session verification request", None))?;
100
101        self.set_ongoing_verification_request(verification_request)
102    }
103
104    /// Accept the previously acknowledged verification request
105    pub async fn accept_verification_request(&self) -> Result<(), ClientError> {
106        let verification_request = self.verification_request.read().unwrap().clone();
107
108        if let Some(verification_request) = verification_request {
109            let methods = vec![VerificationMethod::SasV1];
110            verification_request.accept_with_methods(methods).await?;
111        }
112
113        Ok(())
114    }
115
116    /// Request verification for the current device
117    pub async fn request_device_verification(&self) -> Result<(), ClientError> {
118        let methods = vec![VerificationMethod::SasV1];
119        let verification_request =
120            self.user_identity.request_verification_with_methods(methods).await?;
121
122        self.set_ongoing_verification_request(verification_request)
123    }
124
125    /// Request verification for the given user
126    pub async fn request_user_verification(&self, user_id: String) -> Result<(), ClientError> {
127        let user_id = UserId::parse(user_id)?;
128
129        let user_identity = self
130            .encryption
131            .get_user_identity(&user_id)
132            .await?
133            .ok_or(ClientError::from_str("Unknown user identity", None))?;
134
135        if user_identity.is_verified() {
136            return Err(ClientError::from_str("User is already verified", None));
137        }
138
139        let methods = vec![VerificationMethod::SasV1];
140
141        let verification_request = user_identity.request_verification_with_methods(methods).await?;
142
143        self.set_ongoing_verification_request(verification_request)
144    }
145
146    /// Transition the current verification request into a SAS verification
147    /// flow.
148    pub async fn start_sas_verification(&self) -> Result<(), ClientError> {
149        let verification_request = self.verification_request.read().unwrap().clone();
150
151        let Some(verification_request) = verification_request else {
152            return Err(ClientError::from_str("Verification request missing.", None));
153        };
154
155        match verification_request.start_sas().await {
156            Ok(Some(verification)) => {
157                *self.sas_verification.write().unwrap() = Some(verification.clone());
158
159                if let Some(delegate) = &*self.delegate.read().unwrap() {
160                    delegate.did_start_sas_verification()
161                }
162
163                let delegate = self.delegate.clone();
164                get_runtime_handle()
165                    .spawn(Self::listen_to_sas_verification_changes(verification, delegate));
166            }
167            _ => {
168                if let Some(delegate) = &*self.delegate.read().unwrap() {
169                    delegate.did_fail()
170                }
171            }
172        }
173
174        Ok(())
175    }
176
177    /// Confirm that the short auth strings match on both sides.
178    pub async fn approve_verification(&self) -> Result<(), ClientError> {
179        let sas_verification = self.sas_verification.read().unwrap().clone();
180
181        let Some(sas_verification) = sas_verification else {
182            return Err(ClientError::from_str("SAS verification missing", None));
183        };
184
185        Ok(sas_verification.confirm().await?)
186    }
187
188    /// Reject the short auth string
189    pub async fn decline_verification(&self) -> Result<(), ClientError> {
190        let sas_verification = self.sas_verification.read().unwrap().clone();
191
192        let Some(sas_verification) = sas_verification else {
193            return Err(ClientError::from_str("SAS verification missing", None));
194        };
195
196        Ok(sas_verification.mismatch().await?)
197    }
198
199    /// Cancel the current verification request
200    pub async fn cancel_verification(&self) -> Result<(), ClientError> {
201        let verification_request = self.verification_request.read().unwrap().clone();
202
203        let Some(verification_request) = verification_request else {
204            return Err(ClientError::from_str("Verification request missing.", None));
205        };
206
207        Ok(verification_request.cancel().await?)
208    }
209}
210
211impl SessionVerificationController {
212    pub(crate) fn new(
213        encryption: Encryption,
214        user_identity: UserIdentity,
215        account: Account,
216    ) -> Self {
217        SessionVerificationController {
218            encryption,
219            user_identity,
220            account,
221            delegate: Arc::new(RwLock::new(None)),
222            verification_request: Arc::new(RwLock::new(None)),
223            sas_verification: Arc::new(RwLock::new(None)),
224        }
225    }
226
227    /// Ask the controller to process an incoming request based on the sender
228    /// and flow identifier. It will fetch the request, verify that it's in the
229    /// correct state and then and notify the delegate.
230    pub(crate) async fn process_incoming_verification_request(
231        &self,
232        sender: &UserId,
233        flow_id: impl AsRef<str>,
234    ) {
235        if sender != self.user_identity.user_id() {
236            if let Some(status) = self.encryption.cross_signing_status().await {
237                if !status.is_complete() {
238                    warn!(
239                        "Cannot verify other users until our own device's cross-signing status \
240                         is complete: {status:?}"
241                    );
242                    return;
243                }
244            }
245        }
246
247        let Some(request) = self.encryption.get_verification_request(sender, flow_id).await else {
248            error!("Failed retrieving verification request");
249            return;
250        };
251
252        let VerificationRequestState::Requested { other_device_data, .. } = request.state() else {
253            error!("Received verification request event but the request is in the wrong state.");
254            return;
255        };
256
257        let Ok(sender_profile) = UserProfile::fetch(&self.account, sender).await else {
258            error!("Failed fetching user profile for verification request");
259            return;
260        };
261
262        if let Some(delegate) = &*self.delegate.read().unwrap() {
263            delegate.did_receive_verification_request(SessionVerificationRequestDetails {
264                sender_profile,
265                flow_id: request.flow_id().into(),
266                device_id: other_device_data.device_id().into(),
267                device_display_name: other_device_data.display_name().map(str::to_string),
268                first_seen_timestamp: other_device_data.first_time_seen_ts().into(),
269            });
270        }
271    }
272
273    fn set_ongoing_verification_request(
274        &self,
275        verification_request: VerificationRequest,
276    ) -> Result<(), ClientError> {
277        if let Some(ongoing_verification_request) =
278            self.verification_request.read().unwrap().clone()
279        {
280            if !ongoing_verification_request.is_done()
281                && !ongoing_verification_request.is_cancelled()
282            {
283                return Err(ClientError::from_str(
284                    "There is another verification flow ongoing.",
285                    None,
286                ));
287            }
288        }
289
290        *self.verification_request.write().unwrap() = Some(verification_request.clone());
291
292        get_runtime_handle().spawn(Self::listen_to_verification_request_changes(
293            verification_request,
294            self.sas_verification.clone(),
295            self.delegate.clone(),
296        ));
297
298        Ok(())
299    }
300
301    async fn listen_to_verification_request_changes(
302        verification_request: VerificationRequest,
303        sas_verification: Arc<RwLock<Option<SasVerification>>>,
304        delegate: Delegate,
305    ) {
306        let mut stream = verification_request.changes();
307
308        while let Some(state) = stream.next().await {
309            match state {
310                VerificationRequestState::Transitioned { verification } => {
311                    let Some(verification) = verification.sas() else {
312                        error!("Invalid, non-sas verification flow. Returning.");
313                        return;
314                    };
315
316                    *sas_verification.write().unwrap() = Some(verification.clone());
317
318                    if verification.accept().await.is_ok() {
319                        if let Some(delegate) = &*delegate.read().unwrap() {
320                            delegate.did_start_sas_verification()
321                        }
322
323                        let delegate = delegate.clone();
324                        get_runtime_handle().spawn(Self::listen_to_sas_verification_changes(
325                            verification,
326                            delegate,
327                        ));
328                    } else if let Some(delegate) = &*delegate.read().unwrap() {
329                        delegate.did_fail()
330                    }
331                }
332                VerificationRequestState::Ready { .. } => {
333                    if let Some(delegate) = &*delegate.read().unwrap() {
334                        delegate.did_accept_verification_request()
335                    }
336                }
337                VerificationRequestState::Cancelled(..) => {
338                    if let Some(delegate) = &*delegate.read().unwrap() {
339                        delegate.did_cancel();
340                    }
341                }
342                _ => {}
343            }
344        }
345    }
346
347    async fn listen_to_sas_verification_changes(sas: SasVerification, delegate: Delegate) {
348        let mut stream = sas.changes();
349
350        while let Some(state) = stream.next().await {
351            match state {
352                SasState::KeysExchanged { emojis, decimals } => {
353                    if let Some(delegate) = &*delegate.read().unwrap() {
354                        if let Some(emojis) = emojis {
355                            delegate.did_receive_verification_data(
356                                SessionVerificationData::Emojis {
357                                    emojis: emojis
358                                        .emojis
359                                        .into_iter()
360                                        .map(|emoji| {
361                                            Arc::new(SessionVerificationEmoji {
362                                                symbol: emoji.symbol.to_owned(),
363                                                description: emoji.description.to_owned(),
364                                            })
365                                        })
366                                        .collect(),
367                                    indices: emojis.indices.to_vec(),
368                                },
369                            );
370                        } else {
371                            delegate.did_receive_verification_data(
372                                SessionVerificationData::Decimals {
373                                    values: vec![decimals.0, decimals.1, decimals.2],
374                                },
375                            )
376                        }
377                    }
378                }
379                SasState::Done { .. } => {
380                    if let Some(delegate) = &*delegate.read().unwrap() {
381                        delegate.did_finish()
382                    }
383                    break;
384                }
385                SasState::Cancelled(_cancel_info) => {
386                    // TODO: The cancel_info is usable, we should tell the user why we were
387                    // cancelled.
388                    if let Some(delegate) = &*delegate.read().unwrap() {
389                        delegate.did_cancel()
390                    }
391                    break;
392                }
393                SasState::Created { .. }
394                | SasState::Started { .. }
395                | SasState::Accepted { .. }
396                | SasState::Confirmed => (),
397            }
398        }
399    }
400}