diff --git a/matrix_sdk/Cargo.toml b/matrix_sdk/Cargo.toml index 7da4dc56..dbcac6f8 100644 --- a/matrix_sdk/Cargo.toml +++ b/matrix_sdk/Cargo.toml @@ -93,6 +93,7 @@ version = "3.0.2" features = ["wasm-bindgen"] [dev-dependencies] +anyhow = "1.0" dirs = "3.0.2" matches = "0.1.8" matrix-sdk-test = { version = "0.3.0", path = "../matrix_sdk_test" } diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 9ac552fb..272c49e3 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -62,7 +62,7 @@ use mime::{self, Mime}; use rand::{thread_rng, Rng}; use reqwest::header::InvalidHeaderValue; #[cfg(feature = "encryption")] -use ruma::events::{AnyMessageEvent, AnyRoomEvent, AnySyncMessageEvent}; +use ruma::events::{AnyMessageEvent, AnyRoomEvent, AnySyncMessageEvent, EventType}; use ruma::{ api::{client::r0::push::get_notifications::Notification, SendAccessToken}, events::AnyMessageEventContent, @@ -74,7 +74,7 @@ use tokio::{net::TcpListener, sync::oneshot}; #[cfg(feature = "sso_login")] use tokio_stream::wrappers::TcpListenerStream; #[cfg(feature = "encryption")] -use tracing::debug; +use tracing::{debug, trace}; use tracing::{error, info, instrument, warn}; use url::Url; #[cfg(feature = "sso_login")] @@ -140,8 +140,8 @@ use ruma::{ #[cfg(feature = "encryption")] use crate::{ - device::{Device, UserDevices}, error::RoomKeyImportError, + identities::{Device, UserDevices}, verification::{QrVerification, SasVerification, Verification, VerificationRequest}, }; use crate::{ @@ -2377,6 +2377,85 @@ impl Client { } } + #[cfg(feature = "encryption")] + #[cfg_attr(feature = "docs", doc(cfg(encryption)))] + async fn send_account_data( + &self, + content: ruma::events::AnyGlobalAccountDataEventContent, + ) -> Result { + let own_user = + self.user_id().await.ok_or_else(|| Error::from(HttpError::AuthenticationRequired))?; + let data = serde_json::value::to_raw_value(&content)?; + + let request = ruma::api::client::r0::config::set_global_account_data::Request::new( + &data, + ruma::events::EventContent::event_type(&content), + &own_user, + ); + + Ok(self.send(request, None).await?) + } + + #[cfg(feature = "encryption")] + #[cfg_attr(feature = "docs", doc(cfg(encryption)))] + pub(crate) async fn create_dm_room(&self, user_id: UserId) -> Result> { + use ruma::{ + api::client::r0::room::create_room::RoomPreset, + events::AnyGlobalAccountDataEventContent, + }; + + const SYNC_WAIT_TIME: Duration = Duration::from_secs(3); + + // First we create the DM room, where we invite the user and tell the + // invitee that the room should be a DM. + let invite = &[user_id.clone()]; + + let request = assign!( + ruma::api::client::r0::room::create_room::Request::new(), + { + invite, + is_direct: true, + preset: Some(RoomPreset::TrustedPrivateChat), + } + ); + + let response = self.send(request, None).await?; + + // Now we need to mark the room as a DM for ourselves, we fetch the + // existing `m.direct` event and append the room to the list of DMs we + // have with this user. + let mut content = self + .store() + .get_account_data_event(EventType::Direct) + .await? + .map(|e| e.deserialize()) + .transpose()? + .and_then(|e| { + if let AnyGlobalAccountDataEventContent::Direct(c) = e.content() { + Some(c) + } else { + None + } + }) + .unwrap_or_else(|| ruma::events::direct::DirectEventContent(BTreeMap::new())); + + content.entry(user_id.to_owned()).or_default().push(response.room_id.to_owned()); + + // TODO We should probably save the fact that we need to send this out + // because otherwise we might end up in a state where we have a DM that + // isn't marked as one. + self.send_account_data(AnyGlobalAccountDataEventContent::Direct(content)).await?; + + // If the room is already in our store, fetch it, otherwise wait for a + // sync to be done which should put the room into our store. + if let Some(room) = self.get_joined_room(&response.room_id) { + Ok(Some(room)) + } else { + self.sync_beat.listen().wait_timeout(SYNC_WAIT_TIME); + Ok(self.get_joined_room(&response.room_id)) + } + } + /// Claim one-time keys creating new Olm sessions. /// /// # Arguments @@ -2516,7 +2595,7 @@ impl Client { /// /// println!("{:?}", device.verified()); /// - /// let verification = device.start_verification().await.unwrap(); + /// let verification = device.request_verification().await.unwrap(); /// # }); /// ``` #[cfg(feature = "encryption")] @@ -2531,6 +2610,61 @@ impl Client { Ok(device.map(|d| Device { inner: d, client: self.clone() })) } + /// Get a E2EE identity of an user. + /// + /// # Arguments + /// + /// * `user_id` - The unique id of the user that the identity belongs to. + /// + /// Returns a `UserIdentity` if one is found and the crypto store + /// didn't throw an error. + /// + /// This will always return None if the client hasn't been logged in. + /// + /// # Example + /// + /// ```no_run + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{Client, ruma::UserId}; + /// # use url::Url; + /// # use futures::executor::block_on; + /// # let alice = UserId::try_from("@alice:example.org").unwrap(); + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// # let client = Client::new(homeserver).unwrap(); + /// # block_on(async { + /// let user = client.get_user_identity(&alice).await?; + /// + /// if let Some(user) = user { + /// println!("{:?}", user.verified()); + /// + /// let verification = user.request_verification().await?; + /// } + /// # anyhow::Result::<()>::Ok(()) }); + /// ``` + #[cfg(feature = "encryption")] + #[cfg_attr(feature = "docs", doc(cfg(encryption)))] + pub async fn get_user_identity( + &self, + user_id: &UserId, + ) -> StdResult, CryptoStoreError> { + use crate::identities::UserIdentity; + + if let Some(olm) = self.base_client.olm_machine().await { + let identity = olm.get_identity(user_id).await?; + + Ok(identity.map(|i| match i { + matrix_sdk_base::crypto::UserIdentities::Own(i) => { + UserIdentity::new_own(self.clone(), i) + } + matrix_sdk_base::crypto::UserIdentities::Other(i) => { + UserIdentity::new(self.clone(), i, self.get_dm_room(user_id)) + } + })) + } else { + Ok(None) + } + } + /// Get the status of the private cross signing keys. /// /// This can be used to check which private cross signing keys we have @@ -2545,6 +2679,20 @@ impl Client { } } + #[cfg(feature = "encryption")] + #[cfg_attr(feature = "docs", doc(cfg(encryption)))] + fn get_dm_room(&self, user_id: &UserId) -> Option { + let rooms = self.joined_rooms(); + let room_pairs: Vec<_> = + rooms.iter().map(|r| (r.room_id().to_owned(), r.direct_target())).collect(); + trace!(rooms =? room_pairs, "Finding direct room"); + + let room = rooms.into_iter().find(|r| r.direct_target().as_ref() == Some(user_id)); + + trace!(room =? room, "Found room"); + room + } + /// Create and upload a new cross signing identity. /// /// # Arguments diff --git a/matrix_sdk/src/device.rs b/matrix_sdk/src/identities/devices.rs similarity index 55% rename from matrix_sdk/src/device.rs rename to matrix_sdk/src/identities/devices.rs index 3d50a460..fb89ee56 100644 --- a/matrix_sdk/src/device.rs +++ b/matrix_sdk/src/identities/devices.rs @@ -20,14 +20,19 @@ use matrix_sdk_base::crypto::{ }; use ruma::{events::key::verification::VerificationMethod, DeviceId, DeviceIdBox}; +use super::ManualVerifyError; use crate::{ error::Result, verification::{SasVerification, VerificationRequest}, Client, }; +/// A device represents a E2EE capable client or device of an user. +/// +/// A device is backed by [device keys] that are uploaded to the server. +/// +/// [device keys]: https://spec.matrix.org/unstable/client-server-api/#device-keys #[derive(Clone, Debug)] -/// A device represents a E2EE capable client of an user. pub struct Device { pub(crate) inner: BaseDevice, pub(crate) client: Client, @@ -42,49 +47,13 @@ impl Deref for Device { } impl Device { - /// Start a interactive verification with this `Device` + /// Request an interactive verification with this `Device`. /// - /// Returns a `Sas` object that represents the interactive verification - /// flow. - /// - /// This method has been deprecated in the spec and the - /// [`request_verification()`] method should be used instead. - /// - /// # Examples - /// - /// ```no_run - /// # use std::convert::TryFrom; - /// # use matrix_sdk::{Client, ruma::UserId}; - /// # use url::Url; - /// # use futures::executor::block_on; - /// # let alice = UserId::try_from("@alice:example.org").unwrap(); - /// # let homeserver = Url::parse("http://example.com").unwrap(); - /// # let client = Client::new(homeserver).unwrap(); - /// # block_on(async { - /// let device = client.get_device(&alice, "DEVICEID".into()) - /// .await - /// .unwrap() - /// .unwrap(); - /// - /// let verification = device.start_verification().await.unwrap(); - /// # }); - /// ``` - /// - /// [`request_verification()`]: #method.request_verification - pub async fn start_verification(&self) -> Result { - let (sas, request) = self.inner.start_verification().await?; - self.client.send_to_device(&request).await?; - - Ok(SasVerification { inner: sas, client: self.client.clone() }) - } - - /// Request an interacitve verification with this `Device` - /// - /// Returns a `VerificationRequest` object and a to-device request that - /// needs to be sent out. + /// Returns a [`VerificationRequest`] object that can be used to control the + /// verification flow. /// /// The default methods that are supported are `m.sas.v1` and - /// `m.qr_code.show.v1`, if this isn't desireable the + /// `m.qr_code.show.v1`, if this isn't desirable the /// [`request_verification_with_methods()`] method can be used to override /// this. /// @@ -99,13 +68,12 @@ impl Device { /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # let client = Client::new(homeserver).unwrap(); /// # block_on(async { - /// let device = client.get_device(&alice, "DEVICEID".into()) - /// .await - /// .unwrap() - /// .unwrap(); + /// let device = client.get_device(&alice, "DEVICEID".into()).await?; /// - /// let verification = device.request_verification().await.unwrap(); - /// # }); + /// if let Some(device) = device { + /// let verification = device.request_verification().await?; + /// } + /// # anyhow::Result::<()>::Ok(()) }); /// ``` /// /// [`request_verification_with_methods()`]: @@ -117,14 +85,19 @@ impl Device { Ok(VerificationRequest { inner: verification, client: self.client.clone() }) } - /// Request an interacitve verification with this `Device` + /// Request an interactive verification with this `Device`. /// - /// Returns a `VerificationRequest` object and a to-device request that - /// needs to be sent out. + /// Returns a [`VerificationRequest`] object that can be used to control the + /// verification flow. /// /// # Arguments /// - /// * `methods` - The verification methods that we want to support. + /// * `methods` - The verification methods that we want to support. Must be + /// non-empty. + /// + /// # Panics + /// + /// This method will panic if `methods` is empty. /// /// # Examples /// @@ -143,30 +116,157 @@ impl Device { /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # let client = Client::new(homeserver).unwrap(); /// # block_on(async { - /// let device = client.get_device(&alice, "DEVICEID".into()) - /// .await - /// .unwrap() - /// .unwrap(); + /// let device = client.get_device(&alice, "DEVICEID".into()).await?; /// /// // We don't want to support showing a QR code, we only support SAS /// // verification /// let methods = vec![VerificationMethod::SasV1]; /// - /// let verification = device.request_verification_with_methods(methods).await.unwrap(); - /// # }); + /// if let Some(device) = device { + /// let verification = device.request_verification_with_methods(methods).await?; + /// } + /// # anyhow::Result::<()>::Ok(()) }); /// ``` pub async fn request_verification_with_methods( &self, methods: Vec, ) -> Result { + if methods.is_empty() { + panic!("The list of verification methods can't be non-empty"); + } + let (verification, request) = self.inner.request_verification_with_methods(methods).await; self.client.send_verification_request(request).await?; Ok(VerificationRequest { inner: verification, client: self.client.clone() }) } - /// Is the device considered to be verified, either by locally trusting it - /// or using cross signing. + /// Start an interactive verification with this [`Device`] + /// + /// Returns a [`SasVerification`] object that represents the interactive + /// verification flow. + /// + /// This method has been deprecated in the spec and the + /// [`request_verification()`] method should be used instead. + /// + /// # Examples + /// + /// ```no_run + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{Client, ruma::UserId}; + /// # use url::Url; + /// # use futures::executor::block_on; + /// # let alice = UserId::try_from("@alice:example.org").unwrap(); + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// # let client = Client::new(homeserver).unwrap(); + /// # block_on(async { + /// let device = client.get_device(&alice, "DEVICEID".into()).await?; + /// + /// if let Some(device) = device { + /// let verification = device.start_verification().await?; + /// } + /// # anyhow::Result::<()>::Ok(()) }); + /// ``` + /// + /// [`request_verification()`]: #method.request_verification + #[deprecated( + since = "0.4.0", + note = "directly starting a verification is deprecated in the spec. \ + Users should instead use request_verification()" + )] + pub async fn start_verification(&self) -> Result { + let (sas, request) = self.inner.start_verification().await?; + self.client.send_to_device(&request).await?; + + Ok(SasVerification { inner: sas, client: self.client.clone() }) + } + + /// Manually verify this device. + /// + /// This method will attempt to sign the device using our private cross + /// signing key. + /// + /// This method will always fail if the device belongs to someone else, we + /// can only sign our own devices. + /// + /// It can also fail if we don't have the private part of our self-signing + /// key. + /// + /// The state of our private cross signing keys can be inspected using the + /// [`Client::cross_signing_status()`] method. + /// + /// [`Client::cross_signing_status()`]: crate::Client::cross_signing_status + /// + /// # Examples + /// + /// ```no_run + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{ + /// # Client, + /// # ruma::{ + /// # UserId, + /// # events::key::verification::VerificationMethod, + /// # } + /// # }; + /// # use url::Url; + /// # use futures::executor::block_on; + /// # let alice = UserId::try_from("@alice:example.org").unwrap(); + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// # let client = Client::new(homeserver).unwrap(); + /// # block_on(async { + /// let device = client.get_device(&alice, "DEVICEID".into()).await?; + /// + /// if let Some(device) = device { + /// device.verify().await?; + /// } + /// # anyhow::Result::<()>::Ok(()) }); + /// ``` + pub async fn verify(&self) -> std::result::Result<(), ManualVerifyError> { + let request = self.inner.verify().await?; + self.client.send(request, None).await?; + + Ok(()) + } + + /// Is the device considered to be verified. + /// + /// A device is considered to be verified, either if it's locally trusted, + /// or if it's signed by the appropriate cross signing key. + /// + /// If the device belongs to our own userk, the device needs to be signed by + /// our self-signing key and our own user identity needs to be verified. + /// + /// If the device belongs to some other user, the device needs to be signed + /// by the users signing key and the user identity of the user needs to be + /// verified. + //// # Examples + /// + /// ```no_run + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{ + /// # Client, + /// # ruma::{ + /// # UserId, + /// # events::key::verification::VerificationMethod, + /// # } + /// # }; + /// # use url::Url; + /// # use futures::executor::block_on; + /// # let alice = UserId::try_from("@alice:example.org").unwrap(); + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// # let client = Client::new(homeserver).unwrap(); + /// # block_on(async { + /// let user = client.get_user_identity(&alice).await?; + /// + /// if let Some(user) = user { + /// if user.verified() { + /// println!("User {} is verified", user.user_id().as_str()); + /// } else { + /// println!("User {} is not verified", user.user_id().as_str()); + /// } + /// } + /// # anyhow::Result::<()>::Ok(()) }); + /// ``` pub fn verified(&self) -> bool { self.inner.verified() } @@ -187,7 +287,7 @@ impl Device { } } -/// A read only view over all devices belonging to a user. +/// The collection of all the [`Device`]s a user has. #[derive(Debug)] pub struct UserDevices { pub(crate) inner: BaseUserDevices, diff --git a/matrix_sdk/src/identities/mod.rs b/matrix_sdk/src/identities/mod.rs new file mode 100644 index 00000000..f627d286 --- /dev/null +++ b/matrix_sdk/src/identities/mod.rs @@ -0,0 +1,120 @@ +// 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. + +//! Cryptographic identities used in Matrix. +//! +//! There are two types of cryptographic identities in Matrix. +//! +//! 1. Devices, which are backed by [device keys], they represent each +//! individual log in by an E2EE capable Matrix client. We represent devices +//! using the [`Device`] struct. +//! +//! 2. User identities, which are backed by [cross signing keys]. The user +//! identity represent a unique E2EE capable identity of any given user. This +//! identity is generally created and uploaded to the server by the first E2EE +//! capable client the user logs in with. We represent user identities using the +//! [`UserIdentity`] struct. +//! +//! A [`Device`] or an [`UserIdentity`] can be used to inspect the public keys +//! of the device or identity, or it can be used to initiate a interactive +//! verification flow. They can also be manually marked as verified. +//! +//! # Examples +//! +//! Verifying a device is pretty straightforward: +//! +//! ```no_run +//! # use std::convert::TryFrom; +//! # use matrix_sdk::{Client, ruma::UserId}; +//! # use url::Url; +//! # use futures::executor::block_on; +//! # let alice = UserId::try_from("@alice:example.org").unwrap(); +//! # let homeserver = Url::parse("http://example.com").unwrap(); +//! # let client = Client::new(homeserver).unwrap(); +//! # block_on(async { +//! let device = client.get_device(&alice, "DEVICEID".into()).await?; +//! +//! if let Some(device) = device { +//! // Let's request the device to be verified. +//! let verification = device.request_verification().await?; +//! +//! // Actually this is taking too long. +//! verification.cancel().await?; +//! +//! // Let's just mark it as verified. +//! device.verify().await?; +//! } +//! # anyhow::Result::<()>::Ok(()) }); +//! ``` +//! +//! Verifying a user identity works largely the same: +//! +//! ```no_run +//! # use std::convert::TryFrom; +//! # use matrix_sdk::{Client, ruma::UserId}; +//! # use url::Url; +//! # use futures::executor::block_on; +//! # let alice = UserId::try_from("@alice:example.org").unwrap(); +//! # let homeserver = Url::parse("http://example.com").unwrap(); +//! # let client = Client::new(homeserver).unwrap(); +//! # block_on(async { +//! let user = client.get_user_identity(&alice).await?; +//! +//! if let Some(user) = user { +//! // Let's request the user to be verified. +//! let verification = user.request_verification().await?; +//! +//! // Actually this is taking too long. +//! verification.cancel().await?; +//! +//! // Let's just mark it as verified. +//! user.verify().await?; +//! } +//! # anyhow::Result::<()>::Ok(()) }); +//! ``` +//! +//! [cross signing keys]: https://spec.matrix.org/unstable/client-server-api/#cross-signing +//! [device keys]: https://spec.matrix.org/unstable/client-server-api/#device-keys + +mod devices; +mod users; + +pub use devices::{Device, UserDevices}; +pub use matrix_sdk_base::crypto::MasterPubkey; +pub use users::UserIdentity; + +/// Error for the manual verification step, when we manually sign users or +/// devices. +#[derive(thiserror::Error, Debug)] +pub enum ManualVerifyError { + /// Error that happens when we try to upload the user or device signature. + #[error(transparent)] + Http(#[from] crate::HttpError), + /// Error that happens when we try to sign the user or device. + #[error(transparent)] + Signature(#[from] matrix_sdk_base::crypto::SignatureError), +} + +/// Error when requesting a verification. +#[derive(thiserror::Error, Debug)] +pub enum RequestVerificationError { + /// An ordinary error coming from the SDK, i.e. when we fail to send out a + /// HTTP request or if there's an error with the storage layer. + #[error(transparent)] + Sdk(#[from] crate::Error), + /// Verifying other users requires having a DM open with them, this error + /// signals that we didn't have a DM and that we failed to create one. + #[error("Couldn't create a DM with user {0} where the verification should take place")] + RoomCreation(ruma::UserId), +} diff --git a/matrix_sdk/src/identities/users.rs b/matrix_sdk/src/identities/users.rs new file mode 100644 index 00000000..5cf55332 --- /dev/null +++ b/matrix_sdk/src/identities/users.rs @@ -0,0 +1,406 @@ +// 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::{result::Result, sync::Arc}; + +use matrix_sdk_base::{ + crypto::{ + MasterPubkey, OwnUserIdentity as InnerOwnUserIdentity, UserIdentity as InnerUserIdentity, + }, + locks::RwLock, +}; +use ruma::{ + events::{ + key::verification::VerificationMethod, + room::message::{MessageEventContent, MessageType}, + AnyMessageEventContent, + }, + UserId, +}; + +use super::{ManualVerifyError, RequestVerificationError}; +use crate::{room::Joined, verification::VerificationRequest, Client}; + +/// A struct representing a E2EE capable identity of a user. +/// +/// The identity is backed by public [cross signing] keys that users upload. +/// +/// [cross signing]: https://spec.matrix.org/unstable/client-server-api/#cross-signing +#[derive(Debug, Clone)] +pub struct UserIdentity { + inner: UserIdentities, +} + +impl UserIdentity { + pub(crate) fn new_own(client: Client, identity: InnerOwnUserIdentity) -> Self { + let identity = OwnUserIdentity { inner: identity, client }; + + Self { inner: identity.into() } + } + + pub(crate) fn new(client: Client, identity: InnerUserIdentity, room: Option) -> Self { + let identity = OtherUserIdentity { + inner: identity, + client, + direct_message_room: RwLock::new(room).into(), + }; + + Self { inner: identity.into() } + } + + /// The ID of the user this E2EE identity belongs to. + pub fn user_id(&self) -> &UserId { + match &self.inner { + UserIdentities::Own(i) => i.inner.user_id(), + UserIdentities::Other(i) => i.inner.user_id(), + } + } + + /// Request an interacitve verification with this `UserIdentity`. + /// + /// Returns a [`VerificationRequest`] object that can be used to control the + /// verification flow. + /// + /// This will send out a `m.key.verification.request` event to all the E2EE + /// capable devices we have if we're requesting verification with our own + /// user identity or will send out the event to a DM we share with the user. + /// + /// If we don't share a DM with this user one will be created before the + /// event gets sent out. + /// + /// The default methods that are supported are `m.sas.v1` and + /// `m.qr_code.show.v1`, if this isn't desirable the + /// [`request_verification_with_methods()`] method can be used to override + /// this. + /// + /// # Examples + /// + /// ```no_run + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{Client, ruma::UserId}; + /// # use url::Url; + /// # let alice = UserId::try_from("@alice:example.org").unwrap(); + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// # let client = Client::new(homeserver).unwrap(); + /// # futures::executor::block_on(async { + /// let user = client.get_user_identity(&alice).await?; + /// + /// if let Some(user) = user { + /// let verification = user.request_verification().await?; + /// } + /// + /// # anyhow::Result::<()>::Ok(()) }); + /// ``` + /// + /// [`request_verification_with_methods()`]: + /// #method.request_verification_with_methods + pub async fn request_verification( + &self, + ) -> Result { + match &self.inner { + UserIdentities::Own(i) => i.request_verification(None).await, + UserIdentities::Other(i) => i.request_verification(None).await, + } + } + + /// Request an interacitve verification with this `UserIdentity`. + /// + /// Returns a [`VerificationRequest`] object that can be used to control the + /// verification flow. + /// + /// This methods behaves the same way as [`request_verification()`], + /// but the advertised verification methods can be manually selected. + /// + /// # Arguments + /// + /// * `methods` - The verification methods that we want to support. Must be + /// non-empty. + /// + /// # Panics + /// + /// This method will panic if `methods` is empty. + /// + /// # Examples + /// + /// ```no_run + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{ + /// # Client, + /// # ruma::{ + /// # UserId, + /// # events::key::verification::VerificationMethod, + /// # } + /// # }; + /// # use url::Url; + /// # use futures::executor::block_on; + /// # let alice = UserId::try_from("@alice:example.org").unwrap(); + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// # let client = Client::new(homeserver).unwrap(); + /// # block_on(async { + /// let user = client.get_user_identity(&alice).await?; + /// + /// // We don't want to support showing a QR code, we only support SAS + /// // verification + /// let methods = vec![VerificationMethod::SasV1]; + /// + /// if let Some(user) = user { + /// let verification = user.request_verification_with_methods(methods).await?; + /// } + /// # anyhow::Result::<()>::Ok(()) }); + /// ``` + /// + /// [`request_verification()`]: #method.request_verification + pub async fn request_verification_with_methods( + &self, + methods: Vec, + ) -> Result { + if methods.is_empty() { + panic!("The list of verification methods can't be non-empty"); + } + + match &self.inner { + UserIdentities::Own(i) => i.request_verification(Some(methods)).await, + UserIdentities::Other(i) => i.request_verification(Some(methods)).await, + } + } + + /// Manually verify this [`UserIdentity`]. + /// + /// This method will attempt to sign the user identity using our private + /// cross signing key. Verifying can fail if we don't have the private + /// part of our user-signing key. + /// + /// The state of our private cross signing keys can be inspected using the + /// [`Client::cross_signing_status()`] method. + /// + /// [`Client::cross_signing_status()`]: crate::Client::cross_signing_status + /// + /// # Examples + /// + /// ```no_run + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{ + /// # Client, + /// # ruma::{ + /// # UserId, + /// # events::key::verification::VerificationMethod, + /// # } + /// # }; + /// # use url::Url; + /// # use futures::executor::block_on; + /// # let alice = UserId::try_from("@alice:example.org").unwrap(); + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// # let client = Client::new(homeserver).unwrap(); + /// # block_on(async { + /// let user = client.get_user_identity(&alice).await?; + /// + /// if let Some(user) = user { + /// user.verify().await?; + /// } + /// # anyhow::Result::<()>::Ok(()) }); + /// ``` + pub async fn verify(&self) -> Result<(), ManualVerifyError> { + match &self.inner { + UserIdentities::Own(i) => i.verify().await, + UserIdentities::Other(i) => i.verify().await, + } + } + + /// Is the user identity considered to be verified. + /// + /// A user identity is considered to be verified if it has been signed by + /// our user-signing key, if the identity belongs to another user, or if we + /// locally marked it as verified, if the user identity belongs to us. + /// + /// If the identity belongs to another user, our own user identity needs to + /// be verified as well for the identity to be considered to be verified. + /// + /// # Examples + /// + /// ```no_run + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{ + /// # Client, + /// # ruma::{ + /// # UserId, + /// # events::key::verification::VerificationMethod, + /// # } + /// # }; + /// # use url::Url; + /// # use futures::executor::block_on; + /// # let alice = UserId::try_from("@alice:example.org").unwrap(); + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// # let client = Client::new(homeserver).unwrap(); + /// # block_on(async { + /// let user = client.get_user_identity(&alice).await?; + /// + /// if let Some(user) = user { + /// if user.verified() { + /// println!("User {} is verified", user.user_id().as_str()); + /// } else { + /// println!("User {} is not verified", user.user_id().as_str()); + /// } + /// } + /// # anyhow::Result::<()>::Ok(()) }); + /// ``` + pub fn verified(&self) -> bool { + match &self.inner { + UserIdentities::Own(i) => i.inner.is_verified(), + UserIdentities::Other(i) => i.inner.verified(), + } + } + + /// Get the public part of the master key of this user identity. + /// + /// # Examples + /// + /// ```no_run + /// # use std::convert::TryFrom; + /// # use matrix_sdk::{ + /// # Client, + /// # ruma::{ + /// # UserId, + /// # events::key::verification::VerificationMethod, + /// # } + /// # }; + /// # use url::Url; + /// # use futures::executor::block_on; + /// # let alice = UserId::try_from("@alice:example.org").unwrap(); + /// # let homeserver = Url::parse("http://example.com").unwrap(); + /// # let client = Client::new(homeserver).unwrap(); + /// # block_on(async { + /// let user = client.get_user_identity(&alice).await?; + /// + /// if let Some(user) = user { + /// // Let's verify the user after we confirm that the master key + /// // matches what we expect, for this we fetch the first public key we + /// // can find, there's currently only a single key allowed so this is + /// // fine. + /// if user.master_key().get_first_key() == Some("MyMasterKey") { + /// println!( + /// "Master keys match for user {}, marking the user as verified", + /// user.user_id().as_str(), + /// ); + /// user.verify().await?; + /// } else { + /// println!("Master keys don't match for user {}", user.user_id().as_str()); + /// } + /// } + /// # anyhow::Result::<()>::Ok(()) }); + /// ``` + pub fn master_key(&self) -> &MasterPubkey { + match &self.inner { + UserIdentities::Own(i) => i.inner.master_key(), + UserIdentities::Other(i) => i.inner.master_key(), + } + } +} + +#[derive(Debug, Clone)] +enum UserIdentities { + Own(OwnUserIdentity), + Other(OtherUserIdentity), +} + +impl From for UserIdentities { + fn from(i: OwnUserIdentity) -> Self { + Self::Own(i) + } +} + +impl From for UserIdentities { + fn from(i: OtherUserIdentity) -> Self { + Self::Other(i) + } +} + +#[derive(Debug, Clone)] +struct OwnUserIdentity { + pub(crate) inner: InnerOwnUserIdentity, + pub(crate) client: Client, +} + +#[derive(Debug, Clone)] +struct OtherUserIdentity { + pub(crate) inner: InnerUserIdentity, + pub(crate) client: Client, + pub(crate) direct_message_room: Arc>>, +} + +impl OwnUserIdentity { + async fn request_verification( + &self, + methods: Option>, + ) -> Result { + let (verification, request) = if let Some(methods) = methods { + self.inner + .request_verification_with_methods(methods) + .await + .map_err(crate::Error::from)? + } else { + self.inner.request_verification().await.map_err(crate::Error::from)? + }; + + self.client.send_verification_request(request).await?; + + Ok(VerificationRequest { inner: verification, client: self.client.clone() }) + } + + async fn verify(&self) -> Result<(), ManualVerifyError> { + let request = self.inner.verify().await?; + self.client.send(request, None).await?; + + Ok(()) + } +} + +impl OtherUserIdentity { + async fn request_verification( + &self, + methods: Option>, + ) -> Result { + let content = self.inner.verification_request_content(methods.clone()).await; + + let room = if let Some(room) = self.direct_message_room.read().await.as_ref() { + room.clone() + } else if let Some(room) = + self.client.create_dm_room(self.inner.user_id().to_owned()).await? + { + room + } else { + return Err(RequestVerificationError::RoomCreation(self.inner.user_id().to_owned())); + }; + + let response = room + .send( + AnyMessageEventContent::RoomMessage(MessageEventContent::new( + MessageType::VerificationRequest(content), + )), + None, + ) + .await?; + + let verification = + self.inner.request_verification(room.room_id(), &response.event_id, methods).await; + + Ok(VerificationRequest { inner: verification, client: self.client.clone() }) + } + + async fn verify(&self) -> Result<(), ManualVerifyError> { + let request = self.inner.verify().await?; + self.client.send(request, None).await?; + + Ok(()) + } +} diff --git a/matrix_sdk/src/lib.rs b/matrix_sdk/src/lib.rs index 47cf6661..e7a5497f 100644 --- a/matrix_sdk/src/lib.rs +++ b/matrix_sdk/src/lib.rs @@ -99,14 +99,13 @@ pub mod room; mod room_member; #[cfg(feature = "encryption")] -mod device; +#[cfg_attr(feature = "docs", doc(cfg(encryption)))] +pub mod identities; #[cfg(feature = "encryption")] +#[cfg_attr(feature = "docs", doc(cfg(encryption)))] pub mod verification; pub use client::{Client, ClientConfig, LoopCtrl, RequestConfig, SyncSettings}; -#[cfg(feature = "encryption")] -#[cfg_attr(feature = "docs", doc(cfg(encryption)))] -pub use device::Device; pub use error::{Error, HttpError, HttpResult, Result}; pub use http_client::HttpSend; pub use room_member::RoomMember;