Skip to main content

matrix_sdk_crypto/verification/
qrcode.rs

1// Copyright 2021 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 std::sync::Arc;
16
17use as_variant::as_variant;
18use eyeball::{ObservableWriteGuard, SharedObservable};
19use futures_core::Stream;
20use futures_util::StreamExt;
21use matrix_sdk_qrcode::{
22    EncodingError, QrVerificationData, SelfVerificationData, SelfVerificationNoMasterKey,
23    VerificationData, qrcode::QrCode,
24};
25use rand::{Rng, rng};
26use ruma::{
27    DeviceId, OwnedDeviceId, OwnedUserId, RoomId, TransactionId, UserId,
28    api::client::keys::upload_signatures::v3::Request as SignatureUploadRequest,
29    events::{
30        AnyMessageLikeEventContent, AnyToDeviceEventContent,
31        key::verification::{
32            cancel::CancelCode,
33            done::{KeyVerificationDoneEventContent, ToDeviceKeyVerificationDoneEventContent},
34            start::{
35                KeyVerificationStartEventContent, ReciprocateV1Content, StartMethod,
36                ToDeviceKeyVerificationStartEventContent,
37            },
38        },
39        relation::Reference,
40    },
41    serde::Base64,
42};
43use thiserror::Error;
44use tracing::{debug, trace};
45use vodozemac::Ed25519PublicKey;
46
47use super::{
48    CancelInfo, Cancelled, Done, FlowId, IdentitiesBeingVerified, VerificationResult,
49    VerificationStore,
50    event_enums::{CancelContent, DoneContent, OutgoingContent, OwnedStartContent, StartContent},
51    requests::RequestHandle,
52};
53use crate::{
54    CryptoStoreError, DeviceData, UserIdentityData,
55    types::requests::{OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest},
56};
57
58const SECRET_SIZE: usize = 16;
59
60/// An error for the different failure modes that can happen during the
61/// validation of a scanned QR code.
62#[derive(Debug, Error)]
63pub enum ScanError {
64    /// An IO error inside the crypto store happened during the validation of
65    /// the QR code scan.
66    #[error(transparent)]
67    Store(#[from] CryptoStoreError),
68    /// A key mismatch happened during the validation of the QR code scan.
69    #[error("The keys that are being verified didn't match (expected {expected}, found {found})")]
70    KeyMismatch {
71        /// The expected ed25519 key.
72        expected: String,
73        /// The ed25519 key that we got.
74        found: String,
75    },
76    /// One of the users that is participating in this verification doesn't have
77    /// a valid cross signing identity.
78    #[error("The user {0} is missing a valid cross signing identity")]
79    MissingCrossSigningIdentity(OwnedUserId),
80    /// The device of the user that is participating in this verification
81    /// doesn't have a valid device key.
82    #[error("The user's {0} device {1} is not E2E capable")]
83    MissingDeviceKeys(OwnedUserId, OwnedDeviceId),
84    /// The ID uniquely identifying this verification flow didn't match to the
85    /// one that has been scanned.
86    #[error("The unique verification flow id did not match (expected {expected}, found {found})")]
87    FlowIdMismatch {
88        /// The expected verification flow id.
89        expected: String,
90        /// The verification flow id that we instead got.
91        found: String,
92    },
93}
94
95/// An Enum describing the state the QrCode verification is in.
96#[derive(Debug, Clone)]
97pub enum QrVerificationState {
98    /// The QR verification has been started.
99    ///
100    /// We have received the other device's details (from the
101    /// `m.key.verification.request` or `m.key.verification.ready`) and
102    /// established the shared secret, so can display the QR code.
103    ///
104    /// Note that despite the name of this state, we have not yet sent or
105    /// received an `m.key.verification.start` message.
106    Started,
107    /// The QR verification has been scanned by the other side.
108    Scanned,
109    /// We have confirmed the other side's scan of the QR code.
110    Confirmed,
111    /// We have successfully scanned the QR code and are able to send a
112    /// reciprocation event.
113    ///
114    /// Call `QrVerification::reciprocate` to build the reciprocation message.
115    ///
116    /// Note that, despite the name of this state, we have not necessarily yet
117    /// sent the `m.reciprocate.v1` message.
118    Reciprocated,
119    /// The verification process has been successfully concluded.
120    Done {
121        /// The list of devices that has been verified.
122        verified_devices: Vec<DeviceData>,
123        /// The list of user identities that has been verified.
124        verified_identities: Vec<UserIdentityData>,
125    },
126    /// The verification process has been cancelled.
127    Cancelled(CancelInfo),
128}
129
130impl From<&InnerState> for QrVerificationState {
131    fn from(value: &InnerState) -> Self {
132        match value {
133            InnerState::Created(_) => Self::Started,
134            InnerState::Scanned(_) => Self::Scanned,
135            InnerState::Confirmed(_) => Self::Confirmed,
136            InnerState::Reciprocated(_) => Self::Reciprocated,
137            InnerState::Done(s) => Self::Done {
138                verified_devices: s.state.verified_devices.to_vec(),
139                verified_identities: s.state.verified_master_keys.to_vec(),
140            },
141            InnerState::Cancelled(s) => Self::Cancelled(s.state.to_owned().into()),
142        }
143    }
144}
145
146/// An object controlling QR code style key verification flows.
147#[derive(Clone)]
148pub struct QrVerification {
149    flow_id: FlowId,
150    inner: Arc<QrVerificationData>,
151    state: SharedObservable<InnerState>,
152    identities: IdentitiesBeingVerified,
153    request_handle: Option<RequestHandle>,
154    we_started: bool,
155}
156
157#[cfg(not(tarpaulin_include))]
158impl std::fmt::Debug for QrVerification {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        f.debug_struct("QrVerification")
161            .field("flow_id", &self.flow_id)
162            .field("inner", &self.inner)
163            .field("state", &self.state)
164            .finish()
165    }
166}
167
168impl QrVerification {
169    /// Has the QR verification been scanned by the other side.
170    ///
171    /// When the verification object is in this state it's required that the
172    /// user confirms that the other side has scanned the QR code.
173    pub fn has_been_scanned(&self) -> bool {
174        matches!(*self.state.read(), InnerState::Scanned(_))
175    }
176
177    /// Has the scanning of the QR code been confirmed by us.
178    pub fn has_been_confirmed(&self) -> bool {
179        matches!(*self.state.read(), InnerState::Confirmed(_))
180    }
181
182    /// Get our own user id.
183    pub fn user_id(&self) -> &UserId {
184        self.identities.user_id()
185    }
186
187    /// Get the user id of the other user that is participating in this
188    /// verification flow.
189    pub fn other_user_id(&self) -> &UserId {
190        self.identities.other_user_id()
191    }
192
193    /// Get the device ID of the other side.
194    pub fn other_device_id(&self) -> &DeviceId {
195        self.identities.other_device_id()
196    }
197
198    /// Get the device of the other user.
199    pub fn other_device(&self) -> &DeviceData {
200        self.identities.other_device()
201    }
202
203    /// Did we initiate the verification request
204    pub fn we_started(&self) -> bool {
205        self.we_started
206    }
207
208    /// Get info about the cancellation if the verification flow has been
209    /// cancelled.
210    pub fn cancel_info(&self) -> Option<CancelInfo> {
211        as_variant!(&*self.state.read(), InnerState::Cancelled(c) => {
212            c.state.clone().into()
213        })
214    }
215
216    /// Has the verification flow completed.
217    pub fn is_done(&self) -> bool {
218        matches!(*self.state.read(), InnerState::Done(_))
219    }
220
221    /// Has the verification flow been cancelled.
222    pub fn is_cancelled(&self) -> bool {
223        matches!(*self.state.read(), InnerState::Cancelled(_))
224    }
225
226    /// Is this a verification that is verifying one of our own devices
227    pub fn is_self_verification(&self) -> bool {
228        self.identities.is_self_verification()
229    }
230
231    /// Have we successfully scanned the QR code and are able to send a
232    /// reciprocation event.
233    pub fn reciprocated(&self) -> bool {
234        matches!(*self.state.read(), InnerState::Reciprocated(_))
235    }
236
237    /// Get the unique ID that identifies this QR code verification flow.
238    pub fn flow_id(&self) -> &FlowId {
239        &self.flow_id
240    }
241
242    /// Get the room id if the verification is happening inside a room.
243    pub fn room_id(&self) -> Option<&RoomId> {
244        match self.flow_id() {
245            FlowId::ToDevice(_) => None,
246            FlowId::InRoom(r, _) => Some(r),
247        }
248    }
249
250    /// Generate a QR code object that is representing this verification flow.
251    ///
252    /// The `QrCode` can then be rendered as an image or as an unicode string.
253    ///
254    /// The [`to_bytes()`](#method.to_bytes) method can be used to instead
255    /// output the raw bytes that should be encoded as a QR code.
256    pub fn to_qr_code(&self) -> Result<QrCode, EncodingError> {
257        self.inner.to_qr_code()
258    }
259
260    /// Generate a the raw bytes that should be encoded as a QR code is
261    /// representing this verification flow.
262    ///
263    /// The [`to_qr_code()`](#method.to_qr_code) method can be used to instead
264    /// output a `QrCode` object that can be rendered.
265    pub fn to_bytes(&self) -> Result<Vec<u8>, EncodingError> {
266        self.inner.to_bytes()
267    }
268
269    /// Cancel the verification flow.
270    pub fn cancel(&self) -> Option<OutgoingVerificationRequest> {
271        self.cancel_with_code(CancelCode::User)
272    }
273
274    /// Cancel the verification.
275    ///
276    /// This cancels the verification with given `CancelCode`.
277    ///
278    /// **Note**: This method should generally not be used, the [`cancel()`]
279    /// method should be preferred. The SDK will automatically cancel with the
280    /// appropriate cancel code, user initiated cancellations should only cancel
281    /// with the `CancelCode::User`
282    ///
283    /// Returns None if the `Sas` object is already in a canceled state,
284    /// otherwise it returns a request that needs to be sent out.
285    ///
286    /// [`cancel()`]: #method.cancel
287    pub fn cancel_with_code(&self, code: CancelCode) -> Option<OutgoingVerificationRequest> {
288        let mut state = self.state.write();
289
290        if let Some(request) = &self.request_handle {
291            request.cancel_with_code(&code);
292        }
293
294        let new_state = QrState::<Cancelled>::new(true, code);
295        let content = new_state.as_content(self.flow_id());
296
297        match &*state {
298            InnerState::Confirmed(_)
299            | InnerState::Created(_)
300            | InnerState::Scanned(_)
301            | InnerState::Reciprocated(_)
302            | InnerState::Done(_) => {
303                ObservableWriteGuard::set(&mut state, InnerState::Cancelled(new_state));
304                Some(self.content_to_request(content))
305            }
306            InnerState::Cancelled(_) => None,
307        }
308    }
309
310    /// Notify the other side that we have successfully scanned the QR code and
311    /// that the QR verification flow can start.
312    ///
313    /// This will return some `OutgoingContent` if the object is in the correct
314    /// state to start the verification flow, otherwise `None`.
315    pub fn reciprocate(&self) -> Option<OutgoingVerificationRequest> {
316        match &*self.state.read() {
317            InnerState::Reciprocated(s) => {
318                Some(self.content_to_request(s.as_content(self.flow_id())))
319            }
320            InnerState::Created(_)
321            | InnerState::Scanned(_)
322            | InnerState::Confirmed(_)
323            | InnerState::Done(_)
324            | InnerState::Cancelled(_) => None,
325        }
326    }
327
328    /// Confirm that the other side has scanned our QR code.
329    pub fn confirm_scanning(&self) -> Option<OutgoingVerificationRequest> {
330        debug!("User confirmed other side scanned our QR code");
331        let mut state = self.state.write();
332
333        match &*state {
334            InnerState::Scanned(s) => {
335                let new_state = s.clone().confirm_scanning();
336                let content = new_state.as_content(&self.flow_id);
337                ObservableWriteGuard::set(&mut state, InnerState::Confirmed(new_state));
338
339                Some(self.content_to_request(content))
340            }
341            InnerState::Created(_)
342            | InnerState::Cancelled(_)
343            | InnerState::Confirmed(_)
344            | InnerState::Reciprocated(_)
345            | InnerState::Done(_) => None,
346        }
347    }
348
349    fn content_to_request(&self, content: OutgoingContent) -> OutgoingVerificationRequest {
350        match content {
351            OutgoingContent::Room(room_id, content) => {
352                RoomMessageRequest { room_id, txn_id: TransactionId::new(), content }.into()
353            }
354            OutgoingContent::ToDevice(c) => ToDeviceRequest::with_id(
355                self.identities.other_user_id(),
356                self.identities.other_device_id().to_owned(),
357                &c,
358                TransactionId::new(),
359            )
360            .into(),
361        }
362    }
363
364    async fn mark_as_done(
365        &self,
366        new_state: QrState<Done>,
367    ) -> Result<
368        (Option<OutgoingVerificationRequest>, Option<SignatureUploadRequest>),
369        CryptoStoreError,
370    > {
371        let (devices, identities) = new_state.verified_identities();
372
373        let mut new_state = InnerState::Done(new_state);
374
375        let (content, request) =
376            match self.identities.mark_as_done(Some(&devices), Some(&identities)).await? {
377                VerificationResult::Ok => (None, None),
378                VerificationResult::Cancel(c) => {
379                    let canceled = QrState::<Cancelled>::new(false, c);
380                    let content = canceled.as_content(self.flow_id());
381                    new_state = InnerState::Cancelled(canceled);
382                    (Some(content), None)
383                }
384                VerificationResult::SignatureUpload(s) => (None, Some(s)),
385            };
386
387        self.state.set(new_state);
388        Ok((content.map(|c| self.content_to_request(c)), request))
389    }
390
391    pub(crate) async fn receive_done(
392        &self,
393        content: &DoneContent<'_>,
394    ) -> Result<
395        (Option<OutgoingVerificationRequest>, Option<SignatureUploadRequest>),
396        CryptoStoreError,
397    > {
398        let state = self.state.get();
399
400        Ok(match state {
401            InnerState::Confirmed(s) => {
402                let (verified_device, verified_identity) = match &*self.inner {
403                    QrVerificationData::Verification(_) => {
404                        (None, self.identities.identity_being_verified.as_ref())
405                    }
406                    QrVerificationData::SelfVerification(_) => {
407                        (Some(&self.identities.device_being_verified), None)
408                    }
409                    QrVerificationData::SelfVerificationNoMasterKey(_) => {
410                        (None, self.identities.identity_being_verified.as_ref())
411                    }
412                };
413
414                let new_state = s.clone().into_done(content, verified_device, verified_identity);
415                self.mark_as_done(new_state).await?
416            }
417            InnerState::Reciprocated(s) => {
418                let (verified_device, verified_identity) = match &*self.inner {
419                    QrVerificationData::Verification(_) => {
420                        (None, self.identities.identity_being_verified.as_ref())
421                    }
422                    QrVerificationData::SelfVerification(_) => {
423                        (None, self.identities.identity_being_verified.as_ref())
424                    }
425                    QrVerificationData::SelfVerificationNoMasterKey(_) => {
426                        (Some(&self.identities.device_being_verified), None)
427                    }
428                };
429
430                let new_state = s.clone().into_done(content, verified_device, verified_identity);
431                let content = Some(new_state.as_content(self.flow_id()));
432                let (cancel_content, request) = self.mark_as_done(new_state).await?;
433
434                if cancel_content.is_some() {
435                    (cancel_content, request)
436                } else {
437                    (content.map(|c| self.content_to_request(c)), request)
438                }
439            }
440            InnerState::Created(_)
441            | InnerState::Scanned(_)
442            | InnerState::Done(_)
443            | InnerState::Cancelled(_) => (None, None),
444        })
445    }
446
447    pub(crate) fn receive_reciprocation(
448        &self,
449        content: &StartContent<'_>,
450    ) -> Option<OutgoingVerificationRequest> {
451        let mut state = self.state.write();
452
453        match &*state {
454            InnerState::Created(s) => match s.clone().receive_reciprocate(content) {
455                Ok(s) => {
456                    ObservableWriteGuard::set(&mut state, InnerState::Scanned(s));
457                    None
458                }
459                Err(s) => {
460                    let content = s.as_content(self.flow_id());
461                    ObservableWriteGuard::set(&mut state, InnerState::Cancelled(s));
462                    Some(self.content_to_request(content))
463                }
464            },
465            InnerState::Confirmed(_)
466            | InnerState::Scanned(_)
467            | InnerState::Reciprocated(_)
468            | InnerState::Done(_)
469            | InnerState::Cancelled(_) => None,
470        }
471    }
472
473    pub(crate) fn receive_cancel(&self, sender: &UserId, content: &CancelContent<'_>) {
474        if sender == self.other_user_id() {
475            let mut state = self.state.write();
476
477            let new_state = match &*state {
478                InnerState::Created(s) => s.clone().into_cancelled(content),
479                InnerState::Scanned(s) => s.clone().into_cancelled(content),
480                InnerState::Confirmed(s) => s.clone().into_cancelled(content),
481                InnerState::Reciprocated(s) => s.clone().into_cancelled(content),
482                InnerState::Done(_) | InnerState::Cancelled(_) => return,
483            };
484
485            trace!(
486                ?sender,
487                code = content.cancel_code().as_str(),
488                "Cancelling a QR verification, other user has cancelled"
489            );
490
491            ObservableWriteGuard::set(&mut state, InnerState::Cancelled(new_state));
492        }
493    }
494
495    fn generate_secret() -> Base64 {
496        let mut shared_secret = vec![0u8; SECRET_SIZE];
497        let mut rng = rng();
498        rng.fill_bytes(&mut shared_secret);
499
500        Base64::new(shared_secret)
501    }
502
503    pub(crate) fn new_self(
504        flow_id: FlowId,
505        own_master_key: Ed25519PublicKey,
506        other_device_key: Ed25519PublicKey,
507        identities: IdentitiesBeingVerified,
508        we_started: bool,
509        request_handle: Option<RequestHandle>,
510    ) -> Self {
511        let secret = Self::generate_secret();
512
513        let inner: QrVerificationData = SelfVerificationData::new(
514            flow_id.as_str().to_owned(),
515            own_master_key,
516            other_device_key,
517            secret,
518        )
519        .into();
520
521        Self::new_helper(flow_id, inner, identities, we_started, request_handle)
522    }
523
524    pub(crate) fn new_self_no_master(
525        store: VerificationStore,
526        flow_id: FlowId,
527        own_master_key: Ed25519PublicKey,
528        identities: IdentitiesBeingVerified,
529        we_started: bool,
530        request_handle: Option<RequestHandle>,
531    ) -> QrVerification {
532        let secret = Self::generate_secret();
533
534        let inner: QrVerificationData = SelfVerificationNoMasterKey::new(
535            flow_id.as_str().to_owned(),
536            store.account.identity_keys.ed25519,
537            own_master_key,
538            secret,
539        )
540        .into();
541
542        Self::new_helper(flow_id, inner, identities, we_started, request_handle)
543    }
544
545    pub(crate) fn new_cross(
546        flow_id: FlowId,
547        own_master_key: Ed25519PublicKey,
548        other_master_key: Ed25519PublicKey,
549        identities: IdentitiesBeingVerified,
550        we_started: bool,
551        request_handle: Option<RequestHandle>,
552    ) -> Self {
553        let secret = Self::generate_secret();
554
555        let inner: QrVerificationData = VerificationData::new(
556            flow_id.as_str().to_owned(),
557            own_master_key,
558            other_master_key,
559            secret,
560        )
561        .into();
562
563        Self::new_helper(flow_id, inner, identities, we_started, request_handle)
564    }
565
566    pub(crate) async fn from_scan(
567        store: VerificationStore,
568        other_user_id: OwnedUserId,
569        other_device_id: OwnedDeviceId,
570        flow_id: FlowId,
571        qr_code: QrVerificationData,
572        we_started: bool,
573        request_handle: Option<RequestHandle>,
574    ) -> Result<Self, ScanError> {
575        if flow_id.as_str() != qr_code.flow_id() {
576            return Err(ScanError::FlowIdMismatch {
577                expected: flow_id.as_str().to_owned(),
578                found: qr_code.flow_id().to_owned(),
579            });
580        }
581
582        let other_device =
583            store.get_device(&other_user_id, &other_device_id).await?.ok_or_else(|| {
584                ScanError::MissingDeviceKeys(other_user_id.clone(), other_device_id.clone())
585            })?;
586
587        let identities = store.get_identities(other_device).await?;
588
589        let own_identity = identities
590            .own_identity
591            .as_ref()
592            .ok_or_else(|| ScanError::MissingCrossSigningIdentity(store.account.user_id.clone()))?;
593
594        let other_identity = identities
595            .identity_being_verified
596            .as_ref()
597            .ok_or_else(|| ScanError::MissingCrossSigningIdentity(other_user_id.clone()))?;
598
599        let check_master_key = |key, identity: &UserIdentityData| {
600            let master_key = identity.master_key().get_first_key().ok_or_else(|| {
601                ScanError::MissingCrossSigningIdentity(identity.user_id().to_owned())
602            })?;
603
604            if key != master_key {
605                Err(ScanError::KeyMismatch {
606                    expected: master_key.to_base64(),
607                    found: qr_code.first_key().to_base64(),
608                })
609            } else {
610                Ok(())
611            }
612        };
613
614        match qr_code {
615            QrVerificationData::Verification(_) => {
616                check_master_key(qr_code.first_key(), other_identity)?;
617                check_master_key(qr_code.second_key(), &own_identity.to_owned().into())?;
618            }
619            QrVerificationData::SelfVerification(_) => {
620                check_master_key(qr_code.first_key(), other_identity)?;
621                if qr_code.second_key() != store.account.identity_keys.ed25519 {
622                    return Err(ScanError::KeyMismatch {
623                        expected: store.account.identity_keys.ed25519.to_base64(),
624                        found: qr_code.second_key().to_base64(),
625                    });
626                }
627            }
628            QrVerificationData::SelfVerificationNoMasterKey(_) => {
629                let device_key =
630                    identities.device_being_verified.ed25519_key().ok_or_else(|| {
631                        ScanError::MissingDeviceKeys(other_user_id.clone(), other_device_id.clone())
632                    })?;
633
634                if qr_code.first_key() != device_key {
635                    return Err(ScanError::KeyMismatch {
636                        expected: device_key.to_base64(),
637                        found: qr_code.first_key().to_base64(),
638                    });
639                }
640                check_master_key(qr_code.second_key(), other_identity)?;
641            }
642        }
643
644        let secret = qr_code.secret().to_owned();
645        let own_device_id = store.account.device_id.clone();
646
647        Ok(Self {
648            flow_id,
649            inner: qr_code.into(),
650            state: SharedObservable::new(InnerState::Reciprocated(QrState {
651                state: Reciprocated { secret, own_device_id },
652            })),
653            identities,
654            we_started,
655            request_handle,
656        })
657    }
658
659    fn new_helper(
660        flow_id: FlowId,
661        inner: QrVerificationData,
662        identities: IdentitiesBeingVerified,
663        we_started: bool,
664        request_handle: Option<RequestHandle>,
665    ) -> Self {
666        let secret = inner.secret().to_owned();
667
668        Self {
669            flow_id,
670            inner: inner.into(),
671            state: SharedObservable::new(InnerState::Created(QrState {
672                state: Created { secret },
673            })),
674            identities,
675            we_started,
676            request_handle,
677        }
678    }
679
680    /// Listen for changes in the QrCode verification process.
681    ///
682    /// The changes are presented as a stream of [`QrVerificationState`] values.
683    pub fn changes(&self) -> impl Stream<Item = QrVerificationState> + use<> {
684        self.state.subscribe().map(|s| (&s).into())
685    }
686
687    /// Get the current state the verification process is in.
688    ///
689    /// To listen to changes to the [`QrVerificationState`] use the
690    /// [`QrVerification::changes`] method.
691    pub fn state(&self) -> QrVerificationState {
692        (&*self.state.read()).into()
693    }
694}
695
696#[derive(Debug, Clone)]
697enum InnerState {
698    /// We have received the other device's details (from the
699    /// `m.key.verification.request` or `m.key.verification.ready`) and
700    /// established the shared secret, so can display the QR code.
701    Created(QrState<Created>),
702
703    /// The other side has scanned our QR code and sent an
704    /// `m.key.verification.start` message with `method: m.reciprocate.v1` with
705    /// matching shared secret.
706    Scanned(QrState<Scanned>),
707
708    /// Our user has confirmed that the other device scanned successfully. We
709    /// have sent an `m.key.verification.done`.
710    Confirmed(QrState<Confirmed>),
711
712    /// We have scanned the other side's QR code and are able to send a
713    /// `m.key.verification.start` message with `method: m.reciprocate.v1`.
714    ///
715    /// Call `QrVerification::reciprocate` to build the start message.
716    ///
717    /// Note that, despite the name of this state, we have not necessarily yet
718    /// sent the `m.reciprocate.v1` message.
719    Reciprocated(QrState<Reciprocated>),
720
721    /// Verification complete: we have received an `m.key.verification.done`
722    /// from the other side.
723    Done(QrState<Done>),
724
725    /// Verification cancelled or failed.
726    Cancelled(QrState<Cancelled>),
727}
728
729#[derive(Clone, Debug)]
730struct QrState<S: Clone> {
731    state: S,
732}
733
734impl<S: Clone> QrState<S> {
735    pub fn into_cancelled(self, content: &CancelContent<'_>) -> QrState<Cancelled> {
736        QrState { state: Cancelled::new(false, content.cancel_code().to_owned()) }
737    }
738}
739
740#[derive(Clone, Debug)]
741struct Created {
742    secret: Base64,
743}
744
745#[derive(Clone, Debug)]
746struct Scanned {}
747
748#[derive(Clone, Debug)]
749struct Confirmed {}
750
751#[derive(Clone, Debug)]
752struct Reciprocated {
753    own_device_id: OwnedDeviceId,
754    secret: Base64,
755}
756
757impl Reciprocated {
758    fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
759        let content = ReciprocateV1Content::new(self.secret.clone());
760        let method = StartMethod::ReciprocateV1(content);
761
762        let content: OwnedStartContent = match flow_id {
763            FlowId::ToDevice(t) => ToDeviceKeyVerificationStartEventContent::new(
764                self.own_device_id.clone(),
765                t.clone(),
766                method,
767            )
768            .into(),
769            FlowId::InRoom(r, e) => (
770                r.clone(),
771                KeyVerificationStartEventContent::new(
772                    self.own_device_id.clone(),
773                    method,
774                    Reference::new(e.clone()),
775                ),
776            )
777                .into(),
778        };
779
780        content.into()
781    }
782}
783
784impl QrState<Scanned> {
785    fn confirm_scanning(self) -> QrState<Confirmed> {
786        QrState { state: Confirmed {} }
787    }
788}
789
790impl QrState<Cancelled> {
791    fn new(cancelled_by_us: bool, cancel_code: CancelCode) -> Self {
792        QrState { state: Cancelled::new(cancelled_by_us, cancel_code) }
793    }
794
795    fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
796        self.state.as_content(flow_id)
797    }
798}
799
800impl QrState<Created> {
801    fn receive_reciprocate(
802        self,
803        content: &StartContent<'_>,
804    ) -> Result<QrState<Scanned>, QrState<Cancelled>> {
805        match content.method() {
806            StartMethod::ReciprocateV1(m) => {
807                // TODO use constant time eq here.
808                if self.state.secret == m.secret {
809                    Ok(QrState { state: Scanned {} })
810                } else {
811                    Err(QrState::<Cancelled>::new(false, CancelCode::KeyMismatch))
812                }
813            }
814            _ => Err(QrState::<Cancelled>::new(false, CancelCode::UnknownMethod)),
815        }
816    }
817}
818
819impl QrState<Done> {
820    fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
821        self.state.as_content(flow_id)
822    }
823
824    fn verified_identities(&self) -> (Arc<[DeviceData]>, Arc<[UserIdentityData]>) {
825        (self.state.verified_devices.clone(), self.state.verified_master_keys.clone())
826    }
827}
828
829impl QrState<Confirmed> {
830    fn into_done(
831        self,
832        _: &DoneContent<'_>,
833        verified_device: Option<&DeviceData>,
834        verified_identity: Option<&UserIdentityData>,
835    ) -> QrState<Done> {
836        let devices: Vec<_> = verified_device.into_iter().cloned().collect();
837        let identities: Vec<_> = verified_identity.into_iter().cloned().collect();
838
839        QrState {
840            state: Done {
841                verified_devices: devices.into(),
842                verified_master_keys: identities.into(),
843            },
844        }
845    }
846
847    fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
848        match flow_id {
849            FlowId::ToDevice(t) => AnyToDeviceEventContent::KeyVerificationDone(
850                ToDeviceKeyVerificationDoneEventContent::new(t.to_owned()),
851            )
852            .into(),
853            FlowId::InRoom(r, e) => (
854                r.to_owned(),
855                AnyMessageLikeEventContent::KeyVerificationDone(
856                    KeyVerificationDoneEventContent::new(Reference::new(e.to_owned())),
857                ),
858            )
859                .into(),
860        }
861    }
862}
863
864impl QrState<Reciprocated> {
865    fn as_content(&self, flow_id: &FlowId) -> OutgoingContent {
866        self.state.as_content(flow_id)
867    }
868
869    fn into_done(
870        self,
871        _: &DoneContent<'_>,
872        verified_device: Option<&DeviceData>,
873        verified_identity: Option<&UserIdentityData>,
874    ) -> QrState<Done> {
875        let devices: Vec<_> = verified_device.into_iter().cloned().collect();
876        let identities: Vec<_> = verified_identity.into_iter().cloned().collect();
877
878        QrState {
879            state: Done {
880                verified_devices: devices.into(),
881                verified_master_keys: identities.into(),
882            },
883        }
884    }
885}
886
887#[cfg(test)]
888mod tests {
889    use std::sync::Arc;
890
891    use assert_matches::assert_matches;
892    use matrix_sdk_qrcode::QrVerificationData;
893    use matrix_sdk_test::async_test;
894    use ruma::{
895        DeviceId, UserId, device_id, owned_event_id, owned_room_id, owned_user_id, user_id,
896    };
897    use tokio::sync::Mutex;
898
899    use crate::{
900        DeviceData, QrVerification, QrVerificationState,
901        olm::{Account, PrivateCrossSigningIdentity},
902        store::{CryptoStoreWrapper, MemoryStore, types::Changes},
903        verification::{
904            FlowId, VerificationStore,
905            event_enums::{DoneContent, OutgoingContent, StartContent},
906        },
907    };
908
909    fn user_id() -> &'static UserId {
910        user_id!("@example:localhost")
911    }
912
913    fn memory_store(user_id: &UserId, device_id: &DeviceId) -> Arc<CryptoStoreWrapper> {
914        Arc::new(CryptoStoreWrapper::new(user_id, device_id, MemoryStore::new()))
915    }
916
917    fn device_id() -> &'static DeviceId {
918        device_id!("DEVICEID")
919    }
920
921    #[async_test]
922    async fn test_verification_creation() {
923        let account = Account::with_device_id(user_id(), device_id());
924        let store = memory_store(account.user_id(), account.device_id());
925
926        let private_identity = PrivateCrossSigningIdentity::new(user_id().to_owned());
927        let master_key = private_identity.master_public_key().await.unwrap();
928        let master_key = master_key.get_first_key().unwrap().to_owned();
929
930        let store = VerificationStore {
931            account: account.static_data.clone(),
932            inner: store,
933            private_identity: Mutex::new(private_identity).into(),
934        };
935
936        let flow_id = FlowId::ToDevice("test_transaction".into());
937
938        let device_key = account.static_data.identity_keys.ed25519;
939        let alice_device = DeviceData::from_account(&account);
940
941        let identities = store.get_identities(alice_device).await.unwrap();
942
943        let verification = QrVerification::new_self_no_master(
944            store.clone(),
945            flow_id.clone(),
946            master_key,
947            identities.clone(),
948            false,
949            None,
950        );
951
952        assert_matches!(verification.state(), QrVerificationState::Started);
953        assert_eq!(verification.inner.first_key(), device_key);
954        assert_eq!(verification.inner.second_key(), master_key);
955
956        let verification = QrVerification::new_self(
957            flow_id,
958            master_key,
959            device_key,
960            identities.clone(),
961            false,
962            None,
963        );
964
965        assert_matches!(verification.state(), QrVerificationState::Started);
966        assert_eq!(verification.inner.first_key(), master_key);
967        assert_eq!(verification.inner.second_key(), device_key);
968
969        let bob_identity = PrivateCrossSigningIdentity::new(owned_user_id!("@bob:example"));
970        let bob_master_key = bob_identity.master_public_key().await.unwrap();
971        let bob_master_key = bob_master_key.get_first_key().unwrap().to_owned();
972
973        let flow_id = FlowId::InRoom(owned_room_id!("!test:example"), owned_event_id!("$EVENTID"));
974
975        let verification =
976            QrVerification::new_cross(flow_id, master_key, bob_master_key, identities, false, None);
977
978        assert_matches!(verification.state(), QrVerificationState::Started);
979        assert_eq!(verification.inner.first_key(), master_key);
980        assert_eq!(verification.inner.second_key(), bob_master_key);
981    }
982
983    #[async_test]
984    async fn test_reciprocate_receival() {
985        let test = |flow_id: FlowId| async move {
986            let alice_account = Account::with_device_id(user_id(), device_id());
987            let store = memory_store(alice_account.user_id(), alice_account.device_id());
988
989            let private_identity = PrivateCrossSigningIdentity::new(user_id().to_owned());
990
991            let store = VerificationStore {
992                account: alice_account.static_data.clone(),
993                inner: store,
994                private_identity: Mutex::new(private_identity).into(),
995            };
996
997            let bob_account =
998                Account::with_device_id(alice_account.user_id(), device_id!("BOBDEVICE"));
999
1000            let private_identity = PrivateCrossSigningIdentity::new(user_id().to_owned());
1001            let identity = private_identity.to_public_identity().await.unwrap();
1002
1003            let master_key = private_identity.master_public_key().await.unwrap();
1004            let master_key = master_key.get_first_key().unwrap().to_owned();
1005
1006            let alice_device = DeviceData::from_account(&alice_account);
1007            let bob_device = DeviceData::from_account(&bob_account);
1008
1009            let mut changes = Changes::default();
1010            changes.identities.new.push(identity.clone().into());
1011            changes.devices.new.push(bob_device.clone());
1012            store.save_changes(changes).await.unwrap();
1013
1014            let identities = store.get_identities(alice_device.clone()).await.unwrap();
1015
1016            let alice_verification = QrVerification::new_self_no_master(
1017                store,
1018                flow_id.clone(),
1019                master_key,
1020                identities,
1021                false,
1022                None,
1023            );
1024            assert_matches!(alice_verification.state(), QrVerificationState::Started);
1025
1026            let bob_store = memory_store(bob_account.user_id(), bob_account.device_id());
1027
1028            let private_identity = PrivateCrossSigningIdentity::new(user_id().to_owned());
1029            let bob_store = VerificationStore {
1030                account: bob_account.static_data.clone(),
1031                inner: bob_store,
1032                private_identity: Mutex::new(private_identity).into(),
1033            };
1034
1035            let mut changes = Changes::default();
1036            changes.identities.new.push(identity.into());
1037            changes.devices.new.push(alice_device.clone());
1038            bob_store.save_changes(changes).await.unwrap();
1039
1040            let qr_code = alice_verification.to_bytes().unwrap();
1041            let qr_code = QrVerificationData::from_bytes(qr_code).unwrap();
1042
1043            let bob_verification = QrVerification::from_scan(
1044                bob_store,
1045                alice_account.user_id().to_owned(),
1046                alice_account.device_id().to_owned(),
1047                flow_id,
1048                qr_code,
1049                false,
1050                None,
1051            )
1052            .await
1053            .unwrap();
1054
1055            let request = bob_verification.reciprocate().unwrap();
1056            assert_matches!(bob_verification.state(), QrVerificationState::Reciprocated);
1057
1058            let content = OutgoingContent::try_from(request).unwrap();
1059            let content = StartContent::try_from(&content).unwrap();
1060
1061            alice_verification.receive_reciprocation(&content);
1062            assert_matches!(alice_verification.state(), QrVerificationState::Scanned);
1063
1064            let request = alice_verification.confirm_scanning().unwrap();
1065            assert_matches!(alice_verification.state(), QrVerificationState::Confirmed);
1066
1067            let content = OutgoingContent::try_from(request).unwrap();
1068            let content = DoneContent::try_from(&content).unwrap();
1069
1070            assert!(!alice_verification.is_done());
1071            assert!(!bob_verification.is_done());
1072
1073            let (request, _) = bob_verification.receive_done(&content).await.unwrap();
1074            let content = OutgoingContent::try_from(request.unwrap()).unwrap();
1075            let content = DoneContent::try_from(&content).unwrap();
1076            alice_verification.receive_done(&content).await.unwrap();
1077
1078            assert_matches!(alice_verification.state(), QrVerificationState::Done { .. });
1079            assert_matches!(bob_verification.state(), QrVerificationState::Done { .. });
1080            assert!(alice_verification.is_done());
1081            assert!(bob_verification.is_done());
1082
1083            let identity = alice_verification
1084                .identities
1085                .store
1086                .get_user_identity(alice_account.user_id())
1087                .await
1088                .unwrap()
1089                .unwrap();
1090
1091            let identity = identity.own().unwrap();
1092
1093            assert!(!bob_device.is_locally_trusted());
1094            assert!(alice_device.is_locally_trusted());
1095            assert!(identity.is_verified());
1096        };
1097
1098        let flow_id = FlowId::ToDevice("test_transaction".into());
1099        test(flow_id).await;
1100
1101        let flow_id = FlowId::InRoom(owned_room_id!("!test:example"), owned_event_id!("$EVENTID"));
1102        test(flow_id).await;
1103    }
1104}