// Copyright 2021 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::sync::{Arc, Mutex}; use matrix_qrcode::{ qrcode::QrCode, EncodingError, QrVerificationData, SelfVerificationData, SelfVerificationNoMasterKey, VerificationData, }; use matrix_sdk_common::uuid::Uuid; use ruma::{ api::client::r0::keys::upload_signatures::Request as SignatureUploadRequest, events::{ key::verification::{ cancel::CancelCode, done::{DoneEventContent, DoneToDeviceEventContent}, start::{ self, ReciprocateV1Content, StartEventContent, StartMethod, StartToDeviceEventContent, }, Relation, }, AnyMessageEventContent, AnyToDeviceEventContent, }, DeviceIdBox, DeviceKeyAlgorithm, UserId, }; use thiserror::Error; use super::{ event_enums::{DoneContent, OutgoingContent, OwnedStartContent, StartContent}, Cancelled, Done, FlowId, IdentitiesBeingVerified, VerificationResult, }; use crate::{ olm::{PrivateCrossSigningIdentity, ReadOnlyAccount}, store::CryptoStore, CryptoStoreError, OutgoingVerificationRequest, ReadOnlyDevice, RoomMessageRequest, ToDeviceRequest, UserIdentities, }; const SECRET_SIZE: usize = 16; /// An error for the different failure modes that can happen during the /// validation of a scanned QR code. #[derive(Debug, Error)] pub enum ScanError { /// An IO error inside the crypto store happened during the validation of /// the QR code scan. #[error(transparent)] Store(#[from] CryptoStoreError), /// A key mismatch happened during the validation of the QR code scan. #[error("The keys that are being verified didn't match (expected {expected}, found {found})")] KeyMismatch { expected: String, found: String }, /// One of the users that is participating in this verification doesn't have /// a valid cross signing identity. #[error("The user {0} is missing a valid cross signing identity")] MissingCrossSigningIdentity(UserId), /// The device of the user that is participating in this verification /// doesn't have a valid device key. #[error("The user's {0} device {1} is not E2E capable")] MissingDeviceKeys(UserId, DeviceIdBox), /// The ID uniquely identifying this verification flow didn't match to the /// one that has been scanned. #[error("The unique verification flow id did not match (expected {expected}, found {found})")] FlowIdMismatch { expected: String, found: String }, } /// An object controlling QR code style key verification flows. #[derive(Clone)] pub struct QrVerification { flow_id: FlowId, store: Arc, inner: Arc, state: Arc>, identities: IdentitiesBeingVerified, } impl std::fmt::Debug for QrVerification { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("QrVerification") .field("flow_id", &self.flow_id) .field("inner", self.inner.as_ref()) .field("state", &self.state.lock().unwrap()) .finish() } } impl QrVerification { /// Has the QR verification been scanned by the other side. /// /// When the verification object is in this state it's required that the /// user confirms that the other side has scanned the QR code. pub fn is_scanned(&self) -> bool { matches!(&*self.state.lock().unwrap(), InnerState::Scanned(_)) } /// Get our own user id. pub fn user_id(&self) -> &UserId { self.identities.user_id() } /// Get the user id of the other user that is participating in this /// verification flow. pub fn other_user_id(&self) -> &UserId { self.identities.other_user_id() } /// Has the verification flow completed. pub fn is_done(&self) -> bool { matches!(&*self.state.lock().unwrap(), InnerState::Done(_)) } /// Has the verification flow been cancelled. pub fn is_cancelled(&self) -> bool { matches!(&*self.state.lock().unwrap(), InnerState::Cancelled(_)) } /// Is this a verification that is veryfying one of our own devices pub fn is_self_verification(&self) -> bool { self.identities.is_self_verification() } /// Get the unique ID that identifies this QR code verification flow. pub fn flow_id(&self) -> &FlowId { &self.flow_id } /// Generate a QR code object that is representing this verification flow. /// /// The `QrCode` can then be rendered as an image or as an unicode string. /// /// The [`to_bytes()`](#method.to_bytes) method can be used to instead /// output the raw bytes that should be encoded as a QR code. pub fn to_qr_code(&self) -> Result { self.inner.to_qr_code() } /// Generate a the raw bytes that should be encoded as a QR code is /// representing this verification flow. /// /// The [`to_qr_code()`](#method.to_qr_code) method can be used to instead /// output a `QrCode` object that can be rendered. pub fn to_bytes(&self) -> Result, EncodingError> { self.inner.to_bytes() } /// Cancel the verification flow. pub fn cancel(&self) -> Option { self.cancel_with_code(CancelCode::User).map(|c| self.content_to_request(c)) } /// Notify the other side that we have successfully scanned the QR code and /// that the QR verification flow can start. /// /// This will return some `OutgoingContent` if the object is in the correct /// state to start the verification flow, otherwise `None`. pub fn reciprocate(&self) -> Option { match &*self.state.lock().unwrap() { InnerState::Reciprocated(s) => Some(s.as_content(self.flow_id())), InnerState::Created(_) | InnerState::Scanned(_) | InnerState::Confirmed(_) | InnerState::Done(_) | InnerState::Cancelled(_) => None, } } /// Confirm that the other side has scanned our QR code. pub fn confirm_scanning(&self) -> Option { let mut state = self.state.lock().unwrap(); match &*state { InnerState::Scanned(s) => { let new_state = s.clone().confirm_scanning(); let content = new_state.as_content(&self.flow_id); *state = InnerState::Confirmed(new_state); Some(self.content_to_request(content)) } InnerState::Created(_) | InnerState::Cancelled(_) | InnerState::Confirmed(_) | InnerState::Reciprocated(_) | InnerState::Done(_) => None, } } fn content_to_request(&self, content: OutgoingContent) -> OutgoingVerificationRequest { match content { OutgoingContent::Room(room_id, content) => { RoomMessageRequest { room_id, txn_id: Uuid::new_v4(), content }.into() } OutgoingContent::ToDevice(c) => ToDeviceRequest::new( self.identities.other_user_id(), self.identities.other_device_id().to_owned(), c, ) .into(), } } fn cancel_with_code(&self, code: CancelCode) -> Option { let new_state = QrState::::new(code); let content = new_state.as_content(self.flow_id()); let mut state = self.state.lock().unwrap(); match &*state { InnerState::Confirmed(_) | InnerState::Created(_) | InnerState::Scanned(_) | InnerState::Reciprocated(_) | InnerState::Done(_) => { *state = InnerState::Cancelled(new_state); Some(content) } InnerState::Cancelled(_) => None, } } async fn mark_as_done( &self, new_state: QrState, ) -> Result< (Option, Option), CryptoStoreError, > { let (devices, identities) = new_state.verified_identities(); let mut new_state = InnerState::Done(new_state); let (content, request) = match self.identities.mark_as_done(Some(&devices), Some(&identities)).await? { VerificationResult::Ok => (None, None), VerificationResult::Cancel(c) => { let canceled = QrState::::new(c); let content = canceled.as_content(self.flow_id()); new_state = InnerState::Cancelled(canceled); (Some(content), None) } VerificationResult::SignatureUpload(s) => (None, Some(s)), }; *self.state.lock().unwrap() = new_state; Ok((content.map(|c| self.content_to_request(c)), request)) } pub(crate) async fn receive_done( &self, content: &DoneContent<'_>, ) -> Result< (Option, Option), CryptoStoreError, > { let state = (*self.state.lock().unwrap()).clone(); Ok(match state { InnerState::Confirmed(s) => { let (verified_device, verified_identity) = match &*self.inner { QrVerificationData::Verification(_) => { (None, self.identities.identity_being_verified.as_ref()) } QrVerificationData::SelfVerification(_) => { (Some(&self.identities.device_being_verified), None) } QrVerificationData::SelfVerificationNoMasterKey(_) => { (None, self.identities.identity_being_verified.as_ref()) } }; let new_state = s.clone().into_done(content, verified_device, verified_identity); self.mark_as_done(new_state).await? } InnerState::Reciprocated(s) => { let (verified_device, verified_identity) = match &*self.inner { QrVerificationData::Verification(_) => { (None, self.identities.identity_being_verified.as_ref()) } QrVerificationData::SelfVerification(_) => { (None, self.identities.identity_being_verified.as_ref()) } QrVerificationData::SelfVerificationNoMasterKey(_) => { (Some(&self.identities.device_being_verified), None) } }; let new_state = s.clone().into_done(content, verified_device, verified_identity); let content = Some(new_state.as_content(self.flow_id())); let (cancel_content, request) = self.mark_as_done(new_state).await?; if cancel_content.is_some() { (cancel_content, request) } else { (content.map(|c| self.content_to_request(c)), request) } } InnerState::Created(_) | InnerState::Scanned(_) | InnerState::Done(_) | InnerState::Cancelled(_) => (None, None), }) } pub(crate) fn receive_reciprocation( &self, content: &StartContent, ) -> Option { let mut state = self.state.lock().unwrap(); match &*state { InnerState::Created(s) => match s.clone().receive_reciprocate(content) { Ok(s) => { *state = InnerState::Scanned(s); None } Err(s) => { let content = s.as_content(self.flow_id()); *state = InnerState::Cancelled(s); Some(self.content_to_request(content)) } }, InnerState::Confirmed(_) | InnerState::Scanned(_) | InnerState::Reciprocated(_) | InnerState::Done(_) | InnerState::Cancelled(_) => None, } } fn generate_secret() -> String { let mut shared_secret = [0u8; SECRET_SIZE]; getrandom::getrandom(&mut shared_secret) .expect("Can't generate randomness for the shared secret"); crate::utilities::encode(shared_secret) } pub(crate) fn new_self( store: Arc, flow_id: FlowId, own_master_key: String, other_device_key: String, identities: IdentitiesBeingVerified, ) -> Self { let secret = Self::generate_secret(); let inner: QrVerificationData = SelfVerificationData::new( flow_id.as_str().to_owned(), own_master_key, other_device_key, secret, ) .into(); Self::new_helper(store, flow_id, inner, identities) } pub(crate) fn new_self_no_master( account: ReadOnlyAccount, store: Arc, flow_id: FlowId, own_master_key: String, identities: IdentitiesBeingVerified, ) -> QrVerification { let secret = Self::generate_secret(); let inner: QrVerificationData = SelfVerificationNoMasterKey::new( flow_id.as_str().to_owned(), account.identity_keys().ed25519().to_string(), own_master_key, secret, ) .into(); Self::new_helper(store, flow_id, inner, identities) } pub(crate) fn new_cross( store: Arc, flow_id: FlowId, own_master_key: String, other_master_key: String, identities: IdentitiesBeingVerified, ) -> Self { let secret = Self::generate_secret(); let event_id = if let FlowId::InRoom(_, e) = &flow_id { e.to_owned() } else { panic!("A verification between users is only valid in a room"); }; let inner: QrVerificationData = VerificationData::new(event_id, own_master_key, other_master_key, secret).into(); Self::new_helper(store, flow_id, inner, identities) } pub(crate) async fn from_scan( store: Arc, own_account: ReadOnlyAccount, private_identity: PrivateCrossSigningIdentity, other_user_id: UserId, other_device_id: DeviceIdBox, flow_id: FlowId, qr_code: QrVerificationData, ) -> Result { if flow_id.as_str() != qr_code.flow_id() { return Err(ScanError::FlowIdMismatch { expected: flow_id.as_str().to_owned(), found: qr_code.flow_id().to_owned(), }); } let own_identity = store.get_user_identity(own_account.user_id()).await?.ok_or_else(|| { ScanError::MissingCrossSigningIdentity(own_account.user_id().to_owned()) })?; let other_identity = store .get_user_identity(&other_user_id) .await? .ok_or_else(|| ScanError::MissingCrossSigningIdentity(other_user_id.clone()))?; let other_device = store.get_device(&other_user_id, &other_device_id).await?.ok_or_else(|| { ScanError::MissingDeviceKeys(other_user_id.clone(), other_device_id.clone()) })?; let check_master_key = |key, identity: &UserIdentities| { let master_key = identity.master_key().get_first_key().ok_or_else(|| { ScanError::MissingCrossSigningIdentity(identity.user_id().clone()) })?; if key != master_key { Err(ScanError::KeyMismatch { expected: master_key.to_owned(), found: qr_code.first_key().to_owned(), }) } else { Ok(()) } }; let identities = match qr_code { QrVerificationData::Verification(_) => { check_master_key(qr_code.first_key(), &other_identity)?; check_master_key(qr_code.second_key(), &own_identity)?; IdentitiesBeingVerified { private_identity, store: store.clone(), device_being_verified: other_device, identity_being_verified: Some(other_identity), } } QrVerificationData::SelfVerification(_) => { check_master_key(qr_code.first_key(), &other_identity)?; if qr_code.second_key() != own_account.identity_keys().ed25519() { return Err(ScanError::KeyMismatch { expected: own_account.identity_keys().ed25519().to_owned(), found: qr_code.second_key().to_owned(), }); } IdentitiesBeingVerified { private_identity, store: store.clone(), device_being_verified: other_device, identity_being_verified: Some(other_identity), } } QrVerificationData::SelfVerificationNoMasterKey(_) => { let device_key = other_device.get_key(DeviceKeyAlgorithm::Ed25519).ok_or_else(|| { ScanError::MissingDeviceKeys(other_user_id.clone(), other_device_id.clone()) })?; if qr_code.first_key() != device_key { return Err(ScanError::KeyMismatch { expected: device_key.to_owned(), found: qr_code.first_key().to_owned(), }); } check_master_key(qr_code.second_key(), &other_identity)?; IdentitiesBeingVerified { private_identity, store: store.clone(), device_being_verified: other_device, identity_being_verified: None, } } }; let secret = qr_code.secret().to_owned(); Ok(Self { store, flow_id, inner: qr_code.into(), state: Mutex::new(InnerState::Reciprocated(QrState { state: Reciprocated { secret, own_device_id: own_account.device_id().to_owned() }, })) .into(), identities, }) } fn new_helper( store: Arc, flow_id: FlowId, inner: QrVerificationData, identities: IdentitiesBeingVerified, ) -> Self { let secret = inner.secret().to_owned(); Self { store, flow_id, inner: inner.into(), state: Mutex::new(InnerState::Created(QrState { state: Created { secret } })).into(), identities, } } } #[derive(Debug, Clone)] enum InnerState { Created(QrState), Scanned(QrState), Confirmed(QrState), Reciprocated(QrState), Done(QrState), Cancelled(QrState), } #[derive(Clone, Debug)] struct QrState { state: S, } #[derive(Clone, Debug)] struct Created { secret: String, } #[derive(Clone, Debug)] struct Scanned {} #[derive(Clone, Debug)] struct Confirmed {} #[derive(Clone, Debug)] struct Reciprocated { own_device_id: DeviceIdBox, secret: String, } impl Reciprocated { fn as_content(&self, flow_id: &FlowId) -> OutgoingContent { let content = ReciprocateV1Content::new(self.secret.clone()); let method = StartMethod::ReciprocateV1(content); let content: OwnedStartContent = match flow_id { FlowId::ToDevice(t) => { StartToDeviceEventContent::new(self.own_device_id.clone(), t.clone(), method).into() } FlowId::InRoom(r, e) => ( r.clone(), StartEventContent::new( self.own_device_id.clone(), method, Relation::new(e.clone()), ), ) .into(), }; content.into() } } impl QrState { fn confirm_scanning(self) -> QrState { QrState { state: Confirmed {} } } } impl QrState { fn new(cancel_code: CancelCode) -> Self { QrState { state: Cancelled::new(cancel_code) } } fn as_content(&self, flow_id: &FlowId) -> OutgoingContent { self.state.as_content(flow_id) } } impl QrState { fn receive_reciprocate( self, content: &StartContent, ) -> Result, QrState> { match content.method() { start::StartMethod::ReciprocateV1(m) => { // TODO use constant time eq here. if self.state.secret == m.secret { Ok(QrState { state: Scanned {} }) } else { Err(QrState::::new(CancelCode::KeyMismatch)) } } _ => Err(QrState::::new(CancelCode::UnknownMethod)), } } } impl QrState { fn as_content(&self, flow_id: &FlowId) -> OutgoingContent { self.state.as_content(flow_id) } fn verified_identities(&self) -> (Arc<[ReadOnlyDevice]>, Arc<[UserIdentities]>) { (self.state.verified_devices.clone(), self.state.verified_master_keys.clone()) } } impl QrState { fn into_done( self, _: &DoneContent, verified_device: Option<&ReadOnlyDevice>, verified_identity: Option<&UserIdentities>, ) -> QrState { let devices: Vec<_> = verified_device.into_iter().cloned().collect(); let identities: Vec<_> = verified_identity.into_iter().cloned().collect(); QrState { state: Done { verified_devices: devices.into(), verified_master_keys: identities.into(), }, } } fn as_content(&self, flow_id: &FlowId) -> OutgoingContent { match flow_id { FlowId::ToDevice(t) => AnyToDeviceEventContent::KeyVerificationDone( DoneToDeviceEventContent::new(t.to_owned()), ) .into(), FlowId::InRoom(r, e) => ( r.to_owned(), AnyMessageEventContent::KeyVerificationDone(DoneEventContent::new(Relation::new( e.to_owned(), ))), ) .into(), } } } impl QrState { fn as_content(&self, flow_id: &FlowId) -> OutgoingContent { self.state.as_content(flow_id) } fn into_done( self, _: &DoneContent, verified_device: Option<&ReadOnlyDevice>, verified_identity: Option<&UserIdentities>, ) -> QrState { let devices: Vec<_> = verified_device.into_iter().cloned().collect(); let identities: Vec<_> = verified_identity.into_iter().cloned().collect(); QrState { state: Done { verified_devices: devices.into(), verified_master_keys: identities.into(), }, } } } #[cfg(test)] mod test { use std::{convert::TryFrom, sync::Arc}; use matrix_qrcode::QrVerificationData; use matrix_sdk_test::async_test; use ruma::{event_id, room_id, user_id, DeviceIdBox, UserId}; use crate::{ olm::{PrivateCrossSigningIdentity, ReadOnlyAccount}, store::{Changes, CryptoStore, MemoryStore}, verification::{ event_enums::{DoneContent, OutgoingContent, StartContent}, FlowId, IdentitiesBeingVerified, }, QrVerification, ReadOnlyDevice, }; fn user_id() -> UserId { user_id!("@example:localhost") } fn memory_store() -> Arc { Arc::new(MemoryStore::new()) } fn device_id() -> DeviceIdBox { "DEVICEID".into() } #[async_test] async fn test_verification_creation() { let store = memory_store(); let account = ReadOnlyAccount::new(&user_id(), &device_id()); let private_identity = PrivateCrossSigningIdentity::new(user_id()).await; let flow_id = FlowId::ToDevice("test_transaction".to_owned()); let device_key = account.identity_keys().ed25519().to_owned(); let master_key = private_identity.master_public_key().await.unwrap(); let master_key = master_key.get_first_key().unwrap().to_owned(); let alice_device = ReadOnlyDevice::from_account(&account).await; let identities = IdentitiesBeingVerified { private_identity, store: store.clone(), device_being_verified: alice_device, identity_being_verified: None, }; let verification = QrVerification::new_self_no_master( account, store.clone(), flow_id.clone(), master_key.clone(), identities.clone(), ); assert_eq!(verification.inner.first_key(), &device_key); assert_eq!(verification.inner.second_key(), &master_key); let verification = QrVerification::new_self( store.clone(), flow_id, master_key.clone(), device_key.clone(), identities.clone(), ); assert_eq!(verification.inner.first_key(), &master_key); assert_eq!(verification.inner.second_key(), &device_key); let bob_identity = PrivateCrossSigningIdentity::new(user_id!("@bob:example")).await; let bob_master_key = bob_identity.master_public_key().await.unwrap(); let bob_master_key = bob_master_key.get_first_key().unwrap().to_owned(); let flow_id = FlowId::InRoom(room_id!("!test:example"), event_id!("$EVENTID")); let verification = QrVerification::new_cross( store.clone(), flow_id, master_key.clone(), bob_master_key.clone(), identities, ); assert_eq!(verification.inner.first_key(), &master_key); assert_eq!(verification.inner.second_key(), &bob_master_key); } #[async_test] async fn test_reciprocate_receival() { let test = |flow_id: FlowId| async move { let alice_account = ReadOnlyAccount::new(&user_id(), &device_id()); let store = memory_store(); let bob_account = ReadOnlyAccount::new(alice_account.user_id(), "BOBDEVICE".into()); let private_identity = PrivateCrossSigningIdentity::new(user_id()).await; let identity = private_identity.to_public_identity().await.unwrap(); let master_key = private_identity.master_public_key().await.unwrap(); let master_key = master_key.get_first_key().unwrap().to_owned(); let alice_device = ReadOnlyDevice::from_account(&alice_account).await; let bob_device = ReadOnlyDevice::from_account(&bob_account).await; let mut changes = Changes::default(); changes.identities.new.push(identity.clone().into()); changes.devices.new.push(bob_device.clone()); store.save_changes(changes).await.unwrap(); let identities = IdentitiesBeingVerified { private_identity: PrivateCrossSigningIdentity::empty( alice_account.user_id().to_owned(), ), store: store.clone(), device_being_verified: alice_device.clone(), identity_being_verified: Some(identity.clone().into()), }; let alice_verification = QrVerification::new_self_no_master( alice_account.clone(), store, flow_id.clone(), master_key.clone(), identities, ); let bob_store = memory_store(); let mut changes = Changes::default(); changes.identities.new.push(identity.into()); changes.devices.new.push(alice_device.clone()); bob_store.save_changes(changes).await.unwrap(); let qr_code = alice_verification.to_bytes().unwrap(); let qr_code = QrVerificationData::from_bytes(qr_code).unwrap(); let bob_verification = QrVerification::from_scan( bob_store, bob_account, private_identity, alice_account.user_id().to_owned(), alice_account.device_id().to_owned(), flow_id, qr_code, ) .await .unwrap(); let content = bob_verification.reciprocate().unwrap(); let content = StartContent::try_from(&content).unwrap(); alice_verification.receive_reciprocation(&content); let request = alice_verification.confirm_scanning().unwrap(); let content = OutgoingContent::from(request); let content = DoneContent::try_from(&content).unwrap(); assert!(!alice_verification.is_done()); assert!(!bob_verification.is_done()); let (request, _) = bob_verification.receive_done(&content).await.unwrap(); let content = OutgoingContent::from(request.unwrap()); let content = DoneContent::try_from(&content).unwrap(); alice_verification.receive_done(&content).await.unwrap(); assert!(alice_verification.is_done()); assert!(bob_verification.is_done()); let identity = alice_verification .store .get_user_identity(alice_account.user_id()) .await .unwrap() .unwrap(); let identity = identity.own().unwrap(); assert!(!bob_device.is_locally_trusted()); assert!(alice_device.is_locally_trusted()); assert!(identity.is_verified()); }; let flow_id = FlowId::ToDevice("test_transaction".to_owned()); test(flow_id).await; let flow_id = FlowId::InRoom(room_id!("!test:example"), event_id!("$EVENTID")); test(flow_id).await; } }