feat(sdk): Expose the cross signing user identities

This patch adds support to fetch cross signing user identities from the
crypto store, those can now be used to request verifications and inspect
the master keys of users.
master
Damir Jelić 2021-09-08 13:42:40 +02:00
parent f80e4b3f06
commit db1efcd1fc
6 changed files with 842 additions and 68 deletions

View File

@ -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" }

View File

@ -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<ruma::api::client::r0::config::set_global_account_data::Response> {
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<Option<room::Joined>> {
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<Option<crate::identities::UserIdentity>, 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<room::Joined> {
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

View File

@ -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<SasVerification> {
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<VerificationMethod>,
) -> Result<VerificationRequest> {
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<SasVerification> {
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,

View File

@ -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),
}

View File

@ -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<Joined>) -> 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<VerificationRequest, RequestVerificationError> {
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<VerificationMethod>,
) -> Result<VerificationRequest, RequestVerificationError> {
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<OwnUserIdentity> for UserIdentities {
fn from(i: OwnUserIdentity) -> Self {
Self::Own(i)
}
}
impl From<OtherUserIdentity> 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<RwLock<Option<Joined>>>,
}
impl OwnUserIdentity {
async fn request_verification(
&self,
methods: Option<Vec<VerificationMethod>>,
) -> Result<VerificationRequest, RequestVerificationError> {
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<Vec<VerificationMethod>>,
) -> Result<VerificationRequest, RequestVerificationError> {
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(())
}
}

View File

@ -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;