crypto: Add device wrappers so that the verification can be started with a device.

master
Damir Jelić 2020-08-17 15:34:05 +02:00
parent 9e609a0fdf
commit fd8377bce2
8 changed files with 205 additions and 80 deletions

View File

@ -38,7 +38,7 @@ use tracing::{error, info, instrument};
use matrix_sdk_base::{BaseClient, BaseClientConfig, Room, Session, StateStore}; use matrix_sdk_base::{BaseClient, BaseClientConfig, Room, Session, StateStore};
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
use matrix_sdk_base::{CryptoStoreError, Device, UserDevices}; use matrix_sdk_base::CryptoStoreError;
use matrix_sdk_common::{ use matrix_sdk_common::{
api::r0::{ api::r0::{
@ -88,7 +88,11 @@ use crate::{
}; };
#[cfg(feature = "encryption")] #[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); 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<Sas> {
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. /// Get a specific device of a user.
/// ///
/// # Arguments /// # Arguments
@ -1488,7 +1467,12 @@ impl Client {
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
#[cfg_attr(feature = "docs", doc(cfg(encryption)))] #[cfg_attr(feature = "docs", doc(cfg(encryption)))]
pub async fn get_device(&self, user_id: &UserId, device_id: &DeviceId) -> Option<Device> { pub async fn get_device(&self, user_id: &UserId, device_id: &DeviceId) -> Option<Device> {
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. /// Get a map holding all the devices of an user.
@ -1502,7 +1486,7 @@ impl Client {
/// ///
/// # Example /// # Example
/// ///
/// ``` /// ```no_run
/// # use std::convert::TryFrom; /// # use std::convert::TryFrom;
/// # use matrix_sdk::{Client, identifiers::UserId}; /// # use matrix_sdk::{Client, identifiers::UserId};
/// # use url::Url; /// # use url::Url;
@ -1524,7 +1508,12 @@ impl Client {
&self, &self,
user_id: &UserId, user_id: &UserId,
) -> StdResult<UserDevices, CryptoStoreError> { ) -> StdResult<UserDevices, CryptoStoreError> {
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(),
})
} }
} }

80
matrix_sdk/src/device.rs Normal file
View File

@ -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<Sas> {
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<Device> {
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<Item = &DeviceId> {
self.inner.keys()
}
/// Iterator over all the devices of the user devices.
pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
let client = self.http_client.clone();
self.inner.devices().map(move |d| Device {
inner: d.clone(),
http_client: client.clone(),
})
}
}

View File

@ -39,12 +39,12 @@
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub use matrix_sdk_base::JsonStore; 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::{ pub use matrix_sdk_base::{
CustomEvent, Error as BaseError, EventEmitter, Room, RoomState, Session, StateStore, SyncRoom, 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(feature = "messages")]
#[cfg_attr(feature = "docs", doc(cfg(messages)))] #[cfg_attr(feature = "docs", doc(cfg(messages)))]
@ -58,10 +58,15 @@ mod error;
mod http_client; mod http_client;
mod request_builder; mod request_builder;
#[cfg(feature = "encryption")]
mod device;
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
mod sas; mod sas;
pub use client::{Client, ClientConfig, SyncSettings}; 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 error::{Error, Result};
pub use http_client::HttpSend; pub use http_client::HttpSend;
pub use request_builder::{ pub use request_builder::{

View File

@ -53,7 +53,7 @@ use matrix_sdk_common::{
}; };
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
use matrix_sdk_crypto::{ use matrix_sdk_crypto::{
CryptoStore, CryptoStoreError, Device, DeviceStore, OlmError, OlmMachine, Sas, UserDevices, CryptoStore, CryptoStoreError, DeviceWrap, OlmError, OlmMachine, Sas, UserDevicesWrap,
}; };
use zeroize::Zeroizing; use zeroize::Zeroizing;
@ -1874,24 +1874,6 @@ impl BaseClient {
.and_then(|o| o.get_verification(flow_id)) .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. /// Get a specific device of a user.
/// ///
/// # Arguments /// # Arguments
@ -1922,7 +1904,7 @@ impl BaseClient {
/// ``` /// ```
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
#[cfg_attr(feature = "docs", doc(cfg(encryption)))] #[cfg_attr(feature = "docs", doc(cfg(encryption)))]
pub async fn get_device(&self, user_id: &UserId, device_id: &DeviceId) -> Option<Device> { pub async fn get_device(&self, user_id: &UserId, device_id: &DeviceId) -> Option<DeviceWrap> {
let olm = self.olm.lock().await; let olm = self.olm.lock().await;
olm.as_ref()?.get_device(user_id, device_id).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. /// * `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 /// # Example
/// ///
/// ``` /// ```no_run
/// # use std::convert::TryFrom; /// # use std::convert::TryFrom;
/// # use matrix_sdk_base::BaseClient; /// # use matrix_sdk_base::BaseClient;
/// # use matrix_sdk_common::identifiers::UserId; /// # use matrix_sdk_common::identifiers::UserId;
@ -1958,12 +1945,14 @@ impl BaseClient {
pub async fn get_user_devices( pub async fn get_user_devices(
&self, &self,
user_id: &UserId, user_id: &UserId,
) -> StdResult<UserDevices, CryptoStoreError> { ) -> StdResult<UserDevicesWrap, CryptoStoreError> {
let olm = self.olm.lock().await; let olm = self.olm.lock().await;
if let Some(olm) = olm.as_ref() { if let Some(olm) = olm.as_ref() {
Ok(olm.get_user_devices(user_id).await?) Ok(olm.get_user_devices(user_id).await?)
} else { } else {
Ok(DeviceStore::new().user_devices(user_id)) // TODO remove this panic.
panic!("The client hasn't been logged in")
} }
} }
} }

View File

@ -56,7 +56,9 @@ pub use state::{AllRooms, ClientState};
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
#[cfg_attr(feature = "docs", doc(cfg(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(feature = "messages")]
#[cfg_attr(feature = "docs", doc(cfg(messages)))] #[cfg_attr(feature = "docs", doc(cfg(messages)))]

View File

@ -15,6 +15,7 @@
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
convert::TryFrom, convert::TryFrom,
ops::Deref,
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
Arc, Arc,
@ -23,7 +24,9 @@ use std::{
use atomic::Atomic; use atomic::Atomic;
use matrix_sdk_common::{ use matrix_sdk_common::{
api::r0::keys::SignedKey, api::r0::{
keys::SignedKey, to_device::send_event_to_device::IncomingRequest as OwnedToDeviceRequest,
},
encryption::DeviceKeys, encryption::DeviceKeys,
identifiers::{DeviceId, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, UserId}, identifiers::{DeviceId, DeviceKeyAlgorithm, DeviceKeyId, EventEncryptionAlgorithm, UserId},
}; };
@ -32,7 +35,9 @@ use serde_json::{json, Value};
#[cfg(test)] #[cfg(test)]
use super::{Account, OlmMachine}; 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. /// A device represents a E2EE capable client of an user.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -47,6 +52,62 @@ pub struct Device {
trust_state: Arc<Atomic<TrustState>>, trust_state: Arc<Atomic<TrustState>>,
} }
#[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<DeviceWrap> {
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<Item = &DeviceId> {
self.inner.keys()
}
/// Iterator over all the devices of the user devices.
pub fn devices(&self) -> impl Iterator<Item = DeviceWrap> + '_ {
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)] #[derive(Debug, Clone, Copy, PartialEq)]
/// The trust state of a device. /// The trust state of a device.
pub enum TrustState { pub enum TrustState {

View File

@ -37,7 +37,7 @@ mod store;
mod user_identity; mod user_identity;
mod verification; mod verification;
pub use device::{Device, TrustState}; pub use device::{Device, DeviceWrap, TrustState, UserDevicesWrap};
pub use error::{MegolmError, OlmError}; pub use error::{MegolmError, OlmError};
pub use machine::{OlmMachine, OneTimeKeys}; pub use machine::{OlmMachine, OneTimeKeys};
pub use memory_stores::{DeviceStore, GroupSessionStore, SessionStore, UserDevices}; pub use memory_stores::{DeviceStore, GroupSessionStore, SessionStore, UserDevices};

View File

@ -53,7 +53,7 @@ use matrix_sdk_common::{
#[cfg(feature = "sqlite_cryptostore")] #[cfg(feature = "sqlite_cryptostore")]
use super::store::sqlite::SqliteStore; use super::store::sqlite::SqliteStore;
use super::{ use super::{
device::Device, device::{Device, DeviceWrap, UserDevicesWrap},
error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult}, error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult},
olm::{ olm::{
Account, EncryptionSettings, GroupSessionKey, IdentityKeys, InboundGroupSession, Account, EncryptionSettings, GroupSessionKey, IdentityKeys, InboundGroupSession,
@ -61,7 +61,7 @@ use super::{
}, },
store::{memorystore::MemoryStore, Result as StoreResult}, store::{memorystore::MemoryStore, Result as StoreResult},
verification::{Sas, VerificationMachine}, verification::{Sas, VerificationMachine},
CryptoStore, UserDevices, CryptoStore,
}; };
/// A map from the algorithm and device id to a one-time key. /// 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) 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. /// 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 /// This will decrypt to-device events but will not touch events in the room
@ -1342,12 +1330,18 @@ impl OlmMachine {
/// println!("{:?}", device); /// println!("{:?}", device);
/// # }); /// # });
/// ``` /// ```
pub async fn get_device(&self, user_id: &UserId, device_id: &DeviceId) -> Option<Device> { pub async fn get_device(&self, user_id: &UserId, device_id: &DeviceId) -> Option<DeviceWrap> {
self.store let device = self
.store
.get_device(user_id, device_id) .get_device(user_id, device_id)
.await .await
.ok() .ok()
.flatten() .flatten()?;
Some(DeviceWrap {
inner: device,
verification_machine: self.verification_machine.clone(),
})
} }
/// Get a map holding all the devices of an user. /// 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<UserDevices> { pub async fn get_user_devices(&self, user_id: &UserId) -> StoreResult<UserDevicesWrap> {
self.store.get_user_devices(user_id).await 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()); 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); let mut event = request_to_event(alice.user_id(), &request);
bob.handle_verification_event(&mut event).await; bob.handle_verification_event(&mut event).await;