From fd8377bce203317fcc9d3b8fc471c7cf1f6563b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 17 Aug 2020 15:34:05 +0200 Subject: [PATCH] crypto: Add device wrappers so that the verification can be started with a device. --- matrix_sdk/src/client.rs | 49 ++++++++----------- matrix_sdk/src/device.rs | 80 ++++++++++++++++++++++++++++++++ matrix_sdk/src/lib.rs | 11 +++-- matrix_sdk_base/src/client.rs | 35 +++++--------- matrix_sdk_base/src/lib.rs | 4 +- matrix_sdk_crypto/src/device.rs | 65 +++++++++++++++++++++++++- matrix_sdk_crypto/src/lib.rs | 2 +- matrix_sdk_crypto/src/machine.rs | 39 ++++++++-------- 8 files changed, 205 insertions(+), 80 deletions(-) create mode 100644 matrix_sdk/src/device.rs diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 9375410f..6114d56a 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -38,7 +38,7 @@ use tracing::{error, info, instrument}; use matrix_sdk_base::{BaseClient, BaseClientConfig, Room, Session, StateStore}; #[cfg(feature = "encryption")] -use matrix_sdk_base::{CryptoStoreError, Device, UserDevices}; +use matrix_sdk_base::CryptoStoreError; use matrix_sdk_common::{ api::r0::{ @@ -88,7 +88,11 @@ use crate::{ }; #[cfg(feature = "encryption")] -use crate::{identifiers::DeviceId, sas::Sas}; +use crate::{ + device::{Device, UserDevices}, + identifiers::DeviceId, + sas::Sas, +}; const DEFAULT_SYNC_TIMEOUT: Duration = Duration::from_secs(30); @@ -1431,31 +1435,6 @@ impl Client { }) } - /// Start a interactive verification with the given `Device`. - /// - /// # Arguments - /// - /// * `device` - The device which we would like to start an interactive - /// verification with. - /// - /// Returns a `Sas` object that represents the interactive verification flow. - #[cfg(feature = "encryption")] - #[cfg_attr(feature = "docs", doc(cfg(encryption)))] - pub async fn start_verification(&self, device: Device) -> Result { - let (sas, request) = self - .base_client - .start_verification(device) - .await - .ok_or(Error::AuthenticationRequired)?; - - self.send_to_device(request).await?; - - Ok(Sas { - inner: sas, - http_client: self.http_client.clone(), - }) - } - /// Get a specific device of a user. /// /// # Arguments @@ -1488,7 +1467,12 @@ impl Client { #[cfg(feature = "encryption")] #[cfg_attr(feature = "docs", doc(cfg(encryption)))] pub async fn get_device(&self, user_id: &UserId, device_id: &DeviceId) -> Option { - self.base_client.get_device(user_id, device_id).await + let device = self.base_client.get_device(user_id, device_id).await?; + + Some(Device { + inner: device, + http_client: self.http_client.clone(), + }) } /// Get a map holding all the devices of an user. @@ -1502,7 +1486,7 @@ impl Client { /// /// # Example /// - /// ``` + /// ```no_run /// # use std::convert::TryFrom; /// # use matrix_sdk::{Client, identifiers::UserId}; /// # use url::Url; @@ -1524,7 +1508,12 @@ impl Client { &self, user_id: &UserId, ) -> StdResult { - self.base_client.get_user_devices(user_id).await + let devices = self.base_client.get_user_devices(user_id).await?; + + Ok(UserDevices { + inner: devices, + http_client: self.http_client.clone(), + }) } } diff --git a/matrix_sdk/src/device.rs b/matrix_sdk/src/device.rs new file mode 100644 index 00000000..275deef1 --- /dev/null +++ b/matrix_sdk/src/device.rs @@ -0,0 +1,80 @@ +// 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::{DeviceWrap, UserDevicesWrap}; +use matrix_sdk_common::{ + api::r0::to_device::send_event_to_device::Request as ToDeviceRequest, identifiers::DeviceId, +}; + +use crate::{error::Result, http_client::HttpClient, Sas}; + +#[derive(Clone, Debug)] +/// A device represents a E2EE capable client of an user. +pub struct Device { + pub(crate) inner: DeviceWrap, + pub(crate) http_client: HttpClient, +} + +impl Device { + /// Start a interactive verification with this `Device` + /// + /// Returns a `Sas` object that represents the interactive verification flow. + pub async fn start_verification(&self) -> Result { + let (sas, request) = self.inner.start_verification(); + let request = ToDeviceRequest { + event_type: request.event_type, + txn_id: &request.txn_id, + messages: request.messages, + }; + + self.http_client.send(request).await?; + + Ok(Sas { + inner: sas, + http_client: self.http_client.clone(), + }) + } +} + +/// A read only view over all devices belonging to a user. +#[derive(Debug)] +pub struct UserDevices { + pub(crate) inner: UserDevicesWrap, + pub(crate) http_client: HttpClient, +} + +impl UserDevices { + /// Get the specific device with the given device id. + pub fn get(&self, device_id: &DeviceId) -> Option { + self.inner.get(device_id).map(|d| Device { + inner: d, + http_client: self.http_client.clone(), + }) + } + + /// Iterator over all the device ids of the user devices. + pub fn keys(&self) -> impl Iterator { + self.inner.keys() + } + + /// Iterator over all the devices of the user devices. + pub fn devices(&self) -> impl Iterator + '_ { + let client = self.http_client.clone(); + + self.inner.devices().map(move |d| Device { + inner: d.clone(), + http_client: client.clone(), + }) + } +} diff --git a/matrix_sdk/src/lib.rs b/matrix_sdk/src/lib.rs index 730b242c..f6e81259 100644 --- a/matrix_sdk/src/lib.rs +++ b/matrix_sdk/src/lib.rs @@ -39,12 +39,12 @@ #[cfg(not(target_arch = "wasm32"))] pub use matrix_sdk_base::JsonStore; +#[cfg(feature = "encryption")] +#[cfg_attr(feature = "docs", doc(cfg(encryption)))] +pub use matrix_sdk_base::TrustState; pub use matrix_sdk_base::{ CustomEvent, Error as BaseError, EventEmitter, Room, RoomState, Session, StateStore, SyncRoom, }; -#[cfg(feature = "encryption")] -#[cfg_attr(feature = "docs", doc(cfg(encryption)))] -pub use matrix_sdk_base::{Device, TrustState}; #[cfg(feature = "messages")] #[cfg_attr(feature = "docs", doc(cfg(messages)))] @@ -58,10 +58,15 @@ mod error; mod http_client; mod request_builder; +#[cfg(feature = "encryption")] +mod device; #[cfg(feature = "encryption")] mod sas; pub use client::{Client, ClientConfig, SyncSettings}; +#[cfg(feature = "encryption")] +#[cfg_attr(feature = "docs", doc(cfg(encryption)))] +pub use device::Device; pub use error::{Error, Result}; pub use http_client::HttpSend; pub use request_builder::{ diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index b9f96fe4..655a8d41 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -53,7 +53,7 @@ use matrix_sdk_common::{ }; #[cfg(feature = "encryption")] use matrix_sdk_crypto::{ - CryptoStore, CryptoStoreError, Device, DeviceStore, OlmError, OlmMachine, Sas, UserDevices, + CryptoStore, CryptoStoreError, DeviceWrap, OlmError, OlmMachine, Sas, UserDevicesWrap, }; use zeroize::Zeroizing; @@ -1874,24 +1874,6 @@ impl BaseClient { .and_then(|o| o.get_verification(flow_id)) } - /// Start a interactive verification with the given `Device`. - /// - /// # Arguments - /// - /// * `device` - The device which we would like to start an interactive - /// verification with. - /// - /// Returns a `Sas` object and a to-device request that needs to be sent out. - #[cfg(feature = "encryption")] - #[cfg_attr(feature = "docs", doc(cfg(encryption)))] - pub async fn start_verification(&self, device: Device) -> Option<(Sas, OwnedToDeviceRequest)> { - self.olm - .lock() - .await - .as_ref() - .map(|o| o.start_verification(device)) - } - /// Get a specific device of a user. /// /// # Arguments @@ -1922,7 +1904,7 @@ impl BaseClient { /// ``` #[cfg(feature = "encryption")] #[cfg_attr(feature = "docs", doc(cfg(encryption)))] - pub async fn get_device(&self, user_id: &UserId, device_id: &DeviceId) -> Option { + pub async fn get_device(&self, user_id: &UserId, device_id: &DeviceId) -> Option { let olm = self.olm.lock().await; olm.as_ref()?.get_device(user_id, device_id).await } @@ -1936,9 +1918,14 @@ impl BaseClient { /// /// * `user_id` - The unique id of the user that the devices belong to. /// + /// # Panics + /// + /// Panics if the client hasn't been logged in and the crypto layer thus + /// hasn't been initialized. + /// /// # Example /// - /// ``` + /// ```no_run /// # use std::convert::TryFrom; /// # use matrix_sdk_base::BaseClient; /// # use matrix_sdk_common::identifiers::UserId; @@ -1958,12 +1945,14 @@ impl BaseClient { pub async fn get_user_devices( &self, user_id: &UserId, - ) -> StdResult { + ) -> StdResult { let olm = self.olm.lock().await; + if let Some(olm) = olm.as_ref() { Ok(olm.get_user_devices(user_id).await?) } else { - Ok(DeviceStore::new().user_devices(user_id)) + // TODO remove this panic. + panic!("The client hasn't been logged in") } } } diff --git a/matrix_sdk_base/src/lib.rs b/matrix_sdk_base/src/lib.rs index 18617146..633aa3f4 100644 --- a/matrix_sdk_base/src/lib.rs +++ b/matrix_sdk_base/src/lib.rs @@ -56,7 +56,9 @@ pub use state::{AllRooms, ClientState}; #[cfg(feature = "encryption")] #[cfg_attr(feature = "docs", doc(cfg(encryption)))] -pub use matrix_sdk_crypto::{CryptoStoreError, Device, Sas, TrustState, UserDevices}; +pub use matrix_sdk_crypto::{ + CryptoStoreError, Device, DeviceWrap, Sas, TrustState, UserDevicesWrap, +}; #[cfg(feature = "messages")] #[cfg_attr(feature = "docs", doc(cfg(messages)))] diff --git a/matrix_sdk_crypto/src/device.rs b/matrix_sdk_crypto/src/device.rs index 9e4b88eb..5b4850ec 100644 --- a/matrix_sdk_crypto/src/device.rs +++ b/matrix_sdk_crypto/src/device.rs @@ -15,6 +15,7 @@ use std::{ collections::BTreeMap, convert::TryFrom, + ops::Deref, sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -23,7 +24,9 @@ use std::{ use atomic::Atomic; use matrix_sdk_common::{ - api::r0::keys::SignedKey, + api::r0::{ + keys::SignedKey, to_device::send_event_to_device::IncomingRequest as OwnedToDeviceRequest, + }, encryption::DeviceKeys, identifiers::{DeviceId, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, UserId}, }; @@ -32,7 +35,9 @@ use serde_json::{json, Value}; #[cfg(test)] use super::{Account, OlmMachine}; -use crate::{error::SignatureError, verify_json}; +use crate::{ + error::SignatureError, verification::VerificationMachine, verify_json, Sas, UserDevices, +}; /// A device represents a E2EE capable client of an user. #[derive(Debug, Clone)] @@ -47,6 +52,62 @@ pub struct Device { trust_state: Arc>, } +#[derive(Debug, Clone)] +/// A device represents a E2EE capable client of an user. +pub struct DeviceWrap { + pub(crate) inner: Device, + pub(crate) verification_machine: VerificationMachine, +} + +impl Deref for DeviceWrap { + type Target = Device; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DeviceWrap { + /// Start a interactive verification with this `Device` + /// + /// Returns a `Sas` object and to-device request that needs to be sent out. + pub fn start_verification(&self) -> (Sas, OwnedToDeviceRequest) { + self.verification_machine.start_sas(self.inner.clone()) + } +} + +/// A read only view over all devices belonging to a user. +#[derive(Debug)] +pub struct UserDevicesWrap { + pub(crate) inner: UserDevices, + pub(crate) verification_machine: VerificationMachine, +} + +impl UserDevicesWrap { + /// Get the specific device with the given device id. + pub fn get(&self, device_id: &DeviceId) -> Option { + self.inner.get(device_id).map(|d| DeviceWrap { + inner: d, + verification_machine: self.verification_machine.clone(), + }) + } + + /// Iterator over all the device ids of the user devices. + pub fn keys(&self) -> impl Iterator { + self.inner.keys() + } + + /// Iterator over all the devices of the user devices. + pub fn devices(&self) -> impl Iterator + '_ { + let machine = self.verification_machine.clone(); + + self.inner.devices().map(move |d| DeviceWrap { + inner: d.clone(), + verification_machine: machine.clone(), + }) + } +} + #[derive(Debug, Clone, Copy, PartialEq)] /// The trust state of a device. pub enum TrustState { diff --git a/matrix_sdk_crypto/src/lib.rs b/matrix_sdk_crypto/src/lib.rs index 4eedc065..6147b978 100644 --- a/matrix_sdk_crypto/src/lib.rs +++ b/matrix_sdk_crypto/src/lib.rs @@ -37,7 +37,7 @@ mod store; mod user_identity; mod verification; -pub use device::{Device, TrustState}; +pub use device::{Device, DeviceWrap, TrustState, UserDevicesWrap}; pub use error::{MegolmError, OlmError}; pub use machine::{OlmMachine, OneTimeKeys}; pub use memory_stores::{DeviceStore, GroupSessionStore, SessionStore, UserDevices}; diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 0d4d6833..1a23513f 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -53,7 +53,7 @@ use matrix_sdk_common::{ #[cfg(feature = "sqlite_cryptostore")] use super::store::sqlite::SqliteStore; use super::{ - device::Device, + device::{Device, DeviceWrap, UserDevicesWrap}, error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult}, olm::{ Account, EncryptionSettings, GroupSessionKey, IdentityKeys, InboundGroupSession, @@ -61,7 +61,7 @@ use super::{ }, store::{memorystore::MemoryStore, Result as StoreResult}, verification::{Sas, VerificationMachine}, - CryptoStore, UserDevices, + CryptoStore, }; /// A map from the algorithm and device id to a one-time key. @@ -1136,18 +1136,6 @@ impl OlmMachine { self.verification_machine.get_sas(flow_id) } - /// Start a interactive verification with the given `Device`. - /// - /// # Arguments - /// - /// * `device` - The device which we would like to start an interactive - /// verification with. - /// - /// Returns a `Sas` object and to-device request that needs to be sent out. - pub fn start_verification(&self, device: Device) -> (Sas, OwnedToDeviceRequest) { - self.verification_machine.start_sas(device) - } - /// Handle a sync response and update the internal state of the Olm machine. /// /// This will decrypt to-device events but will not touch events in the room @@ -1342,12 +1330,18 @@ impl OlmMachine { /// println!("{:?}", device); /// # }); /// ``` - pub async fn get_device(&self, user_id: &UserId, device_id: &DeviceId) -> Option { - self.store + pub async fn get_device(&self, user_id: &UserId, device_id: &DeviceId) -> Option { + let device = self + .store .get_device(user_id, device_id) .await .ok() - .flatten() + .flatten()?; + + Some(DeviceWrap { + inner: device, + verification_machine: self.verification_machine.clone(), + }) } /// Get a map holding all the devices of an user. @@ -1373,8 +1367,13 @@ impl OlmMachine { /// } /// # }); /// ``` - pub async fn get_user_devices(&self, user_id: &UserId) -> StoreResult { - self.store.get_user_devices(user_id).await + pub async fn get_user_devices(&self, user_id: &UserId) -> StoreResult { + let devices = self.store.get_user_devices(user_id).await?; + + Ok(UserDevicesWrap { + inner: devices, + verification_machine: self.verification_machine.clone(), + }) } } @@ -1999,7 +1998,7 @@ pub(crate) mod test { assert!(!bob_device.is_trusted()); - let (alice_sas, request) = alice.start_verification(bob_device.clone()); + let (alice_sas, request) = bob_device.start_verification(); let mut event = request_to_event(alice.user_id(), &request); bob.handle_verification_event(&mut event).await;