From 7198b0daba29c22af0be67f758fc247d1b3800b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 9 Dec 2020 17:18:23 +0100 Subject: [PATCH] crypto: WIP key verification request handling. --- matrix_sdk/examples/emoji_verification.rs | 44 ++- matrix_sdk/src/client.rs | 14 + matrix_sdk/src/lib.rs | 2 + matrix_sdk/src/verification_request.rs | 41 +++ matrix_sdk_common/Cargo.toml | 2 +- matrix_sdk_crypto/src/lib.rs | 2 +- matrix_sdk_crypto/src/machine.rs | 18 +- matrix_sdk_crypto/src/verification/machine.rs | 64 ++++- matrix_sdk_crypto/src/verification/mod.rs | 2 + .../src/verification/requests.rs | 262 ++++++++++++++++++ matrix_sdk_crypto/src/verification/sas/mod.rs | 4 +- .../src/verification/sas/sas_state.rs | 9 +- 12 files changed, 439 insertions(+), 25 deletions(-) create mode 100644 matrix_sdk/src/verification_request.rs create mode 100644 matrix_sdk_crypto/src/verification/requests.rs diff --git a/matrix_sdk/examples/emoji_verification.rs b/matrix_sdk/examples/emoji_verification.rs index c903ec19..04f1375c 100644 --- a/matrix_sdk/examples/emoji_verification.rs +++ b/matrix_sdk/examples/emoji_verification.rs @@ -1,9 +1,20 @@ -use std::{env, io, process::exit}; +use std::{ + env, io, + process::exit, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; use url::Url; use matrix_sdk::{ - self, events::AnyToDeviceEvent, identifiers::UserId, Client, ClientConfig, LoopCtrl, Sas, - SyncSettings, + self, + events::{ + room::message::MessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent, AnyToDeviceEvent, + }, + identifiers::UserId, + Client, ClientConfig, LoopCtrl, Sas, SyncSettings, }; async fn wait_for_confirmation(client: Client, sas: Sas) { @@ -68,10 +79,13 @@ async fn login( .await?; let client_ref = &client; + let initial_sync = Arc::new(AtomicBool::from(true)); + let initial_ref = &initial_sync; client .sync_with_callback(SyncSettings::new(), |response| async move { let client = &client_ref; + let initial = &initial_ref; for event in &response.to_device.events { let e = event @@ -118,6 +132,30 @@ async fn login( } } + if !initial.load(Ordering::SeqCst) { + for (_room_id, room_info) in response.rooms.join { + for event in room_info.timeline.events { + if let Ok(AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(m))) = + event.deserialize() + { + if let MessageEventContent::VerificationRequest(_) = &m.content { + let request = client + .get_verification_request(&m.event_id) + .await + .expect("Request object wasn't created"); + + request + .accept() + .await + .expect("Can't accept verification request"); + } + } + } + } + } + + initial.store(false, Ordering::SeqCst); + LoopCtrl::Continue }) .await; diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index e199e2df..3d695be4 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -118,6 +118,7 @@ use matrix_sdk_common::{ use crate::{ http_client::{client_with_config, HttpClient, HttpSend}, + verification_request::VerificationRequest, Error, EventEmitter, OutgoingRequest, Result, }; @@ -1894,6 +1895,19 @@ impl Client { }) } + /// Get a `VerificationRequest` object with the given flow id. + #[cfg(feature = "encryption")] + #[cfg_attr(feature = "docs", doc(cfg(encryption)))] + pub async fn get_verification_request(&self, flow_id: &EventId) -> Option { + let olm = self.base_client.olm_machine().await?; + + olm.get_verification_request(flow_id) + .map(|r| VerificationRequest { + inner: r, + client: self.clone(), + }) + } + /// Get a specific device of a user. /// /// # Arguments diff --git a/matrix_sdk/src/lib.rs b/matrix_sdk/src/lib.rs index 3cb65f27..2c18b393 100644 --- a/matrix_sdk/src/lib.rs +++ b/matrix_sdk/src/lib.rs @@ -88,6 +88,8 @@ mod http_client; mod device; #[cfg(feature = "encryption")] mod sas; +#[cfg(feature = "encryption")] +mod verification_request; pub use client::{Client, ClientConfig, LoopCtrl, SyncSettings}; #[cfg(feature = "encryption")] diff --git a/matrix_sdk/src/verification_request.rs b/matrix_sdk/src/verification_request.rs new file mode 100644 index 00000000..88c5ddd0 --- /dev/null +++ b/matrix_sdk/src/verification_request.rs @@ -0,0 +1,41 @@ +// Copyright 2020 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 matrix_sdk_base::{ + crypto::VerificationRequest as BaseVerificationRequest, events::AnyMessageEventContent, +}; + +use crate::{Client, Result}; + +/// An object controling the interactive verification flow. +#[derive(Debug, Clone)] +pub struct VerificationRequest { + pub(crate) inner: BaseVerificationRequest, + pub(crate) client: Client, +} + +impl VerificationRequest { + /// Accept the interactive verification flow. + pub async fn accept(&self) -> Result<()> { + if let Some(content) = self.inner.accept() { + let content = AnyMessageEventContent::KeyVerificationReady(content); + + self.client + .room_send(self.inner.room_id(), content, None) + .await?; + } + + Ok(()) + } +} diff --git a/matrix_sdk_common/Cargo.toml b/matrix_sdk_common/Cargo.toml index d3c6dcb7..2067d1f4 100644 --- a/matrix_sdk_common/Cargo.toml +++ b/matrix_sdk_common/Cargo.toml @@ -21,7 +21,7 @@ js_int = "0.1.9" [dependencies.ruma] version = "0.0.1" git = "https://github.com/ruma/ruma" -rev = "e8882fe8142d7b55ed4c8ccc6150946945f9e237" +rev = "1a4e9aa20abff4786dbb91a19fb72c1dfa4410a7" features = ["client-api", "unstable-pre-spec"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/matrix_sdk_crypto/src/lib.rs b/matrix_sdk_crypto/src/lib.rs index f37cf97e..fd31a94a 100644 --- a/matrix_sdk_crypto/src/lib.rs +++ b/matrix_sdk_crypto/src/lib.rs @@ -53,4 +53,4 @@ pub(crate) use olm::ReadOnlyAccount; pub use requests::{ IncomingResponse, KeysQueryRequest, OutgoingRequest, OutgoingRequests, ToDeviceRequest, }; -pub use verification::Sas; +pub use verification::{Sas, VerificationRequest}; diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 2cdab741..b290bf54 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -36,7 +36,8 @@ use matrix_sdk_common::{ ToDeviceEvent, }, identifiers::{ - DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UserId, + DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, EventId, RoomId, + UserId, }, js_int::UInt, locks::Mutex, @@ -44,8 +45,6 @@ use matrix_sdk_common::{ Raw, }; -#[cfg(feature = "sqlite_cryptostore")] -use crate::store::sqlite::SqliteStore; use crate::{ error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult}, identities::{Device, IdentityManager, UserDevices}, @@ -64,6 +63,8 @@ use crate::{ verification::{Sas, VerificationMachine}, ToDeviceRequest, }; +#[cfg(feature = "sqlite_cryptostore")] +use crate::{store::sqlite::SqliteStore, verification::VerificationRequest}; /// State machine implementation of the Olm/Megolm encryption protocol used for /// Matrix end to end encryption. @@ -767,6 +768,11 @@ impl OlmMachine { self.verification_machine.get_sas(flow_id) } + /// Get a verification request object with the given flow id. + pub fn get_verification_request(&self, flow_id: &EventId) -> Option { + self.verification_machine.get_request(flow_id) + } + async fn update_one_time_key_count(&self, key_count: &BTreeMap) { self.account.update_uploaded_key_count(key_count).await; } @@ -924,6 +930,12 @@ impl OlmMachine { // TODO set the encryption info on the event (is it verified, was it // decrypted, sender key...) + if let Ok(e) = decrypted_event.deserialize() { + self.verification_machine + .receive_room_event(room_id, &e) + .await?; + } + Ok(decrypted_event) } diff --git a/matrix_sdk_crypto/src/verification/machine.rs b/matrix_sdk_crypto/src/verification/machine.rs index c81344c3..5efa5b5d 100644 --- a/matrix_sdk_crypto/src/verification/machine.rs +++ b/matrix_sdk_crypto/src/verification/machine.rs @@ -16,16 +16,23 @@ use std::sync::Arc; use dashmap::DashMap; -use matrix_sdk_common::locks::Mutex; -use tracing::{trace, warn}; +use tracing::{info, trace, warn}; use matrix_sdk_common::{ - events::{AnyToDeviceEvent, AnyToDeviceEventContent}, - identifiers::{DeviceId, UserId}, + events::{ + room::message::MessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent, + AnyToDeviceEvent, AnyToDeviceEventContent, + }, + identifiers::{DeviceId, EventId, RoomId, UserId}, + locks::Mutex, uuid::Uuid, }; -use super::sas::{content_to_request, Sas, VerificationResult}; +use super::{ + requests::VerificationRequest, + sas::{content_to_request, Sas, VerificationResult}, +}; + use crate::{ olm::PrivateCrossSigningIdentity, requests::{OutgoingRequest, ToDeviceRequest}, @@ -39,6 +46,7 @@ pub struct VerificationMachine { private_identity: Arc>, pub(crate) store: Arc>, verifications: Arc>, + requests: Arc>, outgoing_to_device_messages: Arc>, } @@ -52,8 +60,9 @@ impl VerificationMachine { account, private_identity: identity, store, - verifications: Arc::new(DashMap::new()), - outgoing_to_device_messages: Arc::new(DashMap::new()), + verifications: DashMap::new().into(), + requests: DashMap::new().into(), + outgoing_to_device_messages: DashMap::new().into(), } } @@ -84,6 +93,11 @@ impl VerificationMachine { Ok((sas, request)) } + pub fn get_request(&self, flow_id: &EventId) -> Option { + #[allow(clippy::map_clone)] + self.requests.get(flow_id).map(|s| s.clone()) + } + pub fn get_sas(&self, transaction_id: &str) -> Option { #[allow(clippy::map_clone)] self.verifications.get(transaction_id).map(|s| s.clone()) @@ -106,7 +120,7 @@ impl VerificationMachine { self.outgoing_to_device_messages.insert(request_id, request); } - fn receive_event_helper(&self, sas: &Sas, event: &mut AnyToDeviceEvent) { + fn receive_event_helper(&self, sas: &Sas, event: &AnyToDeviceEvent) { if let Some(c) = sas.receive_event(event) { self.queue_up_content(sas.other_user_id(), sas.other_device_id(), c); } @@ -141,10 +155,40 @@ impl VerificationMachine { } } - pub async fn receive_event( + pub async fn receive_room_event( &self, - event: &mut AnyToDeviceEvent, + room_id: &RoomId, + event: &AnySyncRoomEvent, ) -> Result<(), CryptoStoreError> { + match event { + AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(m)) => { + if let MessageEventContent::VerificationRequest(r) = &m.content { + if self.account.user_id() == &r.to { + info!( + "Received a new verification request from {} {}", + m.sender, r.from_device + ); + + let request = VerificationRequest::from_request_event( + room_id, + self.account.user_id(), + self.account.device_id(), + &m.sender, + &m.event_id, + r, + ); + + self.requests.insert(m.event_id.clone(), request); + } + } + } + _ => (), + } + + Ok(()) + } + + pub async fn receive_event(&self, event: &AnyToDeviceEvent) -> Result<(), CryptoStoreError> { trace!("Received a key verification event {:?}", event); match event { diff --git a/matrix_sdk_crypto/src/verification/mod.rs b/matrix_sdk_crypto/src/verification/mod.rs index d93dc8ec..0e170a35 100644 --- a/matrix_sdk_crypto/src/verification/mod.rs +++ b/matrix_sdk_crypto/src/verification/mod.rs @@ -13,9 +13,11 @@ // limitations under the License. mod machine; +mod requests; mod sas; pub use machine::VerificationMachine; +pub use requests::VerificationRequest; pub use sas::{Sas, VerificationResult}; #[cfg(test)] diff --git a/matrix_sdk_crypto/src/verification/requests.rs b/matrix_sdk_crypto/src/verification/requests.rs new file mode 100644 index 00000000..8e201fda --- /dev/null +++ b/matrix_sdk_crypto/src/verification/requests.rs @@ -0,0 +1,262 @@ +// Copyright 2020 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. + +#![allow(dead_code)] + +use std::sync::{Arc, Mutex}; + +use matrix_sdk_common::{ + api::r0::message::send_message_event::Response as RoomMessageResponse, + events::{ + key::verification::{ready::ReadyEventContent, Relation, VerificationMethod}, + room::message::KeyVerificationRequestEventContent, + }, + identifiers::{DeviceId, DeviceIdBox, EventId, RoomId, UserId}, +}; + +const SUPPORTED_METHODS: &[VerificationMethod] = &[VerificationMethod::MSasV1]; + +#[derive(Clone, Debug)] +/// TODO +pub struct VerificationRequest { + inner: Arc>, + room_id: Arc, +} + +impl VerificationRequest { + pub(crate) fn from_request_event( + room_id: &RoomId, + own_user_id: &UserId, + own_device_id: &DeviceId, + sender: &UserId, + event_id: &EventId, + content: &KeyVerificationRequestEventContent, + ) -> Self { + Self { + inner: Arc::new(Mutex::new(InnerRequest::Requested( + RequestState::from_request_event( + own_user_id, + own_device_id, + sender, + event_id, + content, + ), + ))), + room_id: room_id.clone().into(), + } + } + + /// The room id where the verification is happening. + pub fn room_id(&self) -> &RoomId { + &self.room_id + } + + /// Accept the verification request. + pub fn accept(&self) -> Option { + self.inner.lock().unwrap().accept() + } +} + +#[derive(Debug)] +enum InnerRequest { + Created(RequestState), + Sent(RequestState), + Requested(RequestState), + Ready(RequestState), + Accepted(RequestState), + Passive(RequestState), +} + +impl InnerRequest { + fn accept(&mut self) -> Option { + if let InnerRequest::Requested(s) = self { + let (state, content) = s.clone().accept(); + *self = InnerRequest::Accepted(state); + + Some(content) + } else { + None + } + } +} + +#[derive(Clone, Debug)] +struct RequestState { + /// Our own user id. + pub own_user_id: UserId, + + /// Our own device id. + pub own_device_id: DeviceIdBox, + + /// The id of the user which is participating in this verification request. + pub other_user_id: UserId, + + /// The verification request state we are in. + state: S, +} + +#[derive(Clone, Debug)] +struct Created {} + +impl RequestState { + fn as_content(&self) -> KeyVerificationRequestEventContent { + KeyVerificationRequestEventContent { + body: format!( + "{} is requesting to verify your key, but your client does not \ + support in-chat key verification. You will need to use legacy \ + key verification to verify keys.", + self.own_user_id + ), + methods: SUPPORTED_METHODS.to_vec(), + from_device: self.own_device_id.clone(), + to: self.other_user_id.clone(), + } + } + + fn into_sent(self, response: &RoomMessageResponse) -> RequestState { + RequestState { + own_user_id: self.own_user_id, + own_device_id: self.own_device_id, + other_user_id: self.other_user_id, + state: Sent { + methods: SUPPORTED_METHODS.to_vec(), + flow_id: response.event_id.clone(), + } + .into(), + } + } +} + +#[derive(Clone, Debug)] +struct Sent { + /// The verification methods supported by the sender. + pub methods: Vec, + + /// The event id of our `m.key.verification.request` event which acts as an + /// unique id identifying this verification flow. + pub flow_id: EventId, +} + +impl RequestState { + fn into_ready(self, _sender: &UserId, content: &ReadyEventContent) -> RequestState { + // TODO check the flow id, and that the methods match what we suggested. + RequestState { + own_user_id: self.own_user_id, + own_device_id: self.own_device_id, + other_user_id: self.other_user_id, + state: Ready { + methods: content.methods.to_owned(), + other_device_id: content.from_device.clone(), + flow_id: self.state.flow_id.clone(), + } + .into(), + } + } +} + +#[derive(Clone, Debug)] +struct Requested { + /// The verification methods supported by the sender. + pub methods: Vec, + + /// The event id of the `m.key.verification.request` event which acts as an + /// unique id identifying this verification flow. + pub flow_id: EventId, + + /// The device id of the device that responded to the verification request. + pub other_device_id: DeviceIdBox, +} + +impl RequestState { + fn from_request_event( + own_user_id: &UserId, + own_device_id: &DeviceId, + sender: &UserId, + event_id: &EventId, + content: &KeyVerificationRequestEventContent, + ) -> RequestState { + // TODO only create this if we suport the methods + RequestState { + own_user_id: own_user_id.clone(), + own_device_id: own_device_id.into(), + other_user_id: sender.clone(), + state: Requested { + methods: content.methods.clone(), + flow_id: event_id.clone(), + other_device_id: content.from_device.clone(), + } + .into(), + } + } + + fn accept(self) -> (RequestState, ReadyEventContent) { + // TODO let the user pick a method here. + let state = RequestState { + own_user_id: self.own_user_id, + own_device_id: self.own_device_id.clone(), + other_user_id: self.other_user_id, + state: Accepted { + methods: self.state.methods.clone(), + other_device_id: self.state.other_device_id.clone(), + flow_id: self.state.flow_id.clone(), + }, + }; + + let content = ReadyEventContent { + from_device: self.own_device_id, + methods: self.state.methods, + relation: Relation { + event_id: self.state.flow_id, + }, + }; + + (state, content) + } +} + +#[derive(Clone, Debug)] +struct Ready { + /// The verification methods supported by the sender. + pub methods: Vec, + + /// The device id of the device that responded to the verification request. + pub other_device_id: DeviceIdBox, + + /// The event id of the `m.key.verification.request` event which acts as an + /// unique id identifying this verification flow. + pub flow_id: EventId, +} + +#[derive(Clone, Debug)] +struct Accepted { + /// The verification methods that were accepted + pub methods: Vec, + + /// The device id of the device that responded to the verification request. + pub other_device_id: DeviceIdBox, + + /// The event id of the `m.key.verification.request` event which acts as an + /// unique id identifying this verification flow. + pub flow_id: EventId, +} + +#[derive(Clone, Debug)] +struct Passive { + /// The device id of the device that responded to the verification request. + pub other_device_id: DeviceIdBox, + + /// The event id of the `m.key.verification.request` event which acts as an + /// unique id identifying this verification flow. + pub flow_id: EventId, +} diff --git a/matrix_sdk_crypto/src/verification/sas/mod.rs b/matrix_sdk_crypto/src/verification/sas/mod.rs index d6f1322f..6ee7f2bb 100644 --- a/matrix_sdk_crypto/src/verification/sas/mod.rs +++ b/matrix_sdk_crypto/src/verification/sas/mod.rs @@ -514,7 +514,7 @@ impl Sas { pub(crate) fn receive_event( &self, - event: &mut AnyToDeviceEvent, + event: &AnyToDeviceEvent, ) -> Option { let mut guard = self.inner.lock().unwrap(); let sas: InnerSas = (*guard).clone(); @@ -628,7 +628,7 @@ impl InnerSas { fn receive_event( self, - event: &mut AnyToDeviceEvent, + event: &AnyToDeviceEvent, ) -> (InnerSas, Option) { match event { AnyToDeviceEvent::KeyVerificationAccept(e) => { diff --git a/matrix_sdk_crypto/src/verification/sas/sas_state.rs b/matrix_sdk_crypto/src/verification/sas/sas_state.rs index ab136cae..8ddd0e9c 100644 --- a/matrix_sdk_crypto/src/verification/sas/sas_state.rs +++ b/matrix_sdk_crypto/src/verification/sas/sas_state.rs @@ -14,7 +14,6 @@ use std::{ convert::TryFrom, - mem, sync::{Arc, Mutex}, time::{Duration, Instant}, }; @@ -498,14 +497,14 @@ impl SasState { /// anymore. pub fn into_key_received( self, - event: &mut ToDeviceEvent, + event: &ToDeviceEvent, ) -> Result, SasState> { self.check_event(&event.sender, &event.content.transaction_id) .map_err(|c| self.clone().cancel(c))?; let accepted_protocols = AcceptedProtocols::default(); - let their_pubkey = mem::take(&mut event.content.key); + let their_pubkey = event.content.key.clone(); self.inner .lock() @@ -539,7 +538,7 @@ impl SasState { /// anymore. pub fn into_key_received( self, - event: &mut ToDeviceEvent, + event: &ToDeviceEvent, ) -> Result, SasState> { self.check_event(&event.sender, &event.content.transaction_id) .map_err(|c| self.clone().cancel(c))?; @@ -549,7 +548,7 @@ impl SasState { if self.state.commitment != commitment { Err(self.cancel(CancelCode::InvalidMessage)) } else { - let their_pubkey = mem::take(&mut event.content.key); + let their_pubkey = event.content.key.clone(); self.inner .lock()