matrix-sdk: Rework the public API for answering verifications

This commit is contained in:
Damir Jelić 2021-06-17 12:17:11 +02:00
parent baee5b2d11
commit 3cf843d24f
11 changed files with 426 additions and 162 deletions

View file

@ -11,11 +11,12 @@ use matrix_sdk::{
self,
events::{room::message::MessageType, AnySyncMessageEvent, AnySyncRoomEvent, AnyToDeviceEvent},
identifiers::UserId,
Client, LoopCtrl, Sas, SyncSettings,
verification::{SasVerification, Verification},
Client, LoopCtrl, SyncSettings,
};
use url::Url;
async fn wait_for_confirmation(client: Client, sas: Sas) {
async fn wait_for_confirmation(client: Client, sas: SasVerification) {
println!("Does the emoji match: {:?}", sas.emoji());
let mut input = String::new();
@ -34,7 +35,7 @@ async fn wait_for_confirmation(client: Client, sas: Sas) {
}
}
fn print_result(sas: &Sas) {
fn print_result(sas: &SasVerification) {
let device = sas.other_device();
println!(
@ -80,37 +81,35 @@ async fn login(
for event in response.to_device.events.iter().filter_map(|e| e.deserialize().ok()) {
match event {
AnyToDeviceEvent::KeyVerificationStart(e) => {
let sas = client
.get_verification(&e.sender, &e.content.transaction_id)
.await
.expect("Sas object wasn't created");
println!(
"Starting verification with {} {}",
&sas.other_device().user_id(),
&sas.other_device().device_id()
);
print_devices(&e.sender, client).await;
sas.accept().await.unwrap();
if let Some(Verification::SasV1(sas)) =
client.get_verification(&e.sender, &e.content.transaction_id).await
{
println!(
"Starting verification with {} {}",
&sas.other_device().user_id(),
&sas.other_device().device_id()
);
print_devices(&e.sender, client).await;
sas.accept().await.unwrap();
}
}
AnyToDeviceEvent::KeyVerificationKey(e) => {
let sas = client
.get_verification(&e.sender, &e.content.transaction_id)
.await
.expect("Sas object wasn't created");
tokio::spawn(wait_for_confirmation((*client).clone(), sas));
if let Some(Verification::SasV1(sas)) =
client.get_verification(&e.sender, &e.content.transaction_id).await
{
tokio::spawn(wait_for_confirmation((*client).clone(), sas));
}
}
AnyToDeviceEvent::KeyVerificationMac(e) => {
let sas = client
.get_verification(&e.sender, &e.content.transaction_id)
.await
.expect("Sas object wasn't created");
if sas.is_done() {
print_result(&sas);
print_devices(&e.sender, client).await;
if let Some(Verification::SasV1(sas)) =
client.get_verification(&e.sender, &e.content.transaction_id).await
{
if sas.is_done() {
print_result(&sas);
print_devices(&e.sender, client).await;
}
}
}
@ -140,28 +139,28 @@ async fn login(
}
}
AnySyncMessageEvent::KeyVerificationKey(e) => {
let sas = client
if let Some(Verification::SasV1(sas)) = client
.get_verification(
&e.sender,
e.content.relation.event_id.as_str(),
)
.await
.expect("Sas object wasn't created");
tokio::spawn(wait_for_confirmation((*client).clone(), sas));
{
tokio::spawn(wait_for_confirmation((*client).clone(), sas));
}
}
AnySyncMessageEvent::KeyVerificationMac(e) => {
let sas = client
if let Some(Verification::SasV1(sas)) = client
.get_verification(
&e.sender,
e.content.relation.event_id.as_str(),
)
.await
.expect("Sas object wasn't created");
if sas.is_done() {
print_result(&sas);
print_devices(&e.sender, client).await;
{
if sas.is_done() {
print_result(&sas);
print_devices(&e.sender, client).await;
}
}
}
_ => (),

View file

@ -126,8 +126,7 @@ use ruma::{
#[cfg(feature = "encryption")]
use crate::{
device::{Device, UserDevices},
sas::Sas,
verification_request::VerificationRequest,
verification::{QrVerification, SasVerification, Verification, VerificationRequest},
};
use crate::{
error::HttpError,
@ -2182,14 +2181,19 @@ impl Client {
Ok(response)
}
/// Get a `Sas` verification object with the given flow id.
/// Get a verification object with the given flow id.
#[cfg(feature = "encryption")]
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Sas> {
self.base_client
.get_verification(user_id, flow_id)
.await
.map(|sas| Sas { inner: sas, client: self.clone() })
pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
let olm = self.base_client.olm_machine().await?;
olm.get_verification(user_id, flow_id).map(|v| match v {
matrix_sdk_base::crypto::Verification::SasV1(s) => {
SasVerification { inner: s, client: self.clone() }.into()
}
matrix_sdk_base::crypto::Verification::QrV1(qr) => {
QrVerification { inner: qr, client: self.clone() }.into()
}
})
}
/// Get a `VerificationRequest` object for the given user with the given
@ -2697,6 +2701,22 @@ impl Client {
let request = whoami::Request::new();
self.send(request, None).await
}
pub(crate) async fn send_verification_request(
&self,
request: matrix_sdk_base::crypto::OutgoingVerificationRequest,
) -> Result<()> {
match request {
matrix_sdk_base::crypto::OutgoingVerificationRequest::ToDevice(t) => {
self.send_to_device(&t).await?;
}
matrix_sdk_base::crypto::OutgoingVerificationRequest::InRoom(r) => {
self.room_send_helper(&r).await?;
}
}
Ok(())
}
}
#[cfg(test)]

View file

@ -20,7 +20,7 @@ use matrix_sdk_base::crypto::{
};
use ruma::{DeviceId, DeviceIdBox};
use crate::{error::Result, Client, Sas};
use crate::{error::Result, verification::SasVerification, Client};
#[derive(Clone, Debug)]
/// A device represents a E2EE capable client of an user.
@ -62,11 +62,11 @@ impl Device {
/// let verification = device.start_verification().await.unwrap();
/// # });
/// ```
pub async fn start_verification(&self) -> Result<Sas> {
pub async fn start_verification(&self) -> Result<SasVerification> {
let (sas, request) = self.inner.start_verification().await?;
self.client.send_to_device(&request).await?;
Ok(Sas { inner: sas, client: self.client.clone() })
Ok(SasVerification { inner: sas, client: self.client.clone() })
}
/// Is the device trusted.

View file

@ -115,9 +115,7 @@ mod room_member;
#[cfg(feature = "encryption")]
mod device;
#[cfg(feature = "encryption")]
mod sas;
#[cfg(feature = "encryption")]
mod verification_request;
pub mod verification;
pub use client::{Client, ClientConfig, LoopCtrl, RequestConfig, SyncSettings};
#[cfg(feature = "encryption")]
@ -127,12 +125,5 @@ pub use error::{Error, HttpError, Result};
pub use event_handler::{CustomEvent, EventHandler};
pub use http_client::HttpSend;
pub use room_member::RoomMember;
#[cfg(feature = "encryption")]
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
pub use sas::Sas;
#[cfg(feature = "encryption")]
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
pub use verification_request::VerificationRequest;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION");

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.
//! Interactive verification for E2EE capable users and devices in Matrix.
//!
//! The SDK supports interactive verification of devices and users, this module
//! contains types that model and support different verification flows.
//!
//! A verification flow usually starts its life as a [VerificationRequest], the
//! request can then be accepted, or it needs to be accepted by the other side
//! of the verification flow.
//!
//! Once both sides have agreed to pereform the verification, and the
//! [VerificationRequest::is_ready()] method returns true, the verification can
//! transition into one of the supported verification flows:
//!
//! * [SasVerification] - Interactive verification using a short authentication
//! string.
//! * [QrVerification] - Interactive verification using QR codes.
mod qrcode;
mod requests;
mod sas;
pub use qrcode::QrVerification;
pub use requests::VerificationRequest;
pub use sas::SasVerification;
/// An enum over the different verification types the SDK supports.
#[derive(Debug, Clone)]
pub enum Verification {
/// The `m.sas.v1` verification variant.
SasV1(SasVerification),
/// The `m.qr_code.*.v1` verification variant.
QrV1(QrVerification),
}
impl Verification {
/// Try to deconstruct this verification enum into a SAS verification.
pub fn sas(self) -> Option<SasVerification> {
if let Verification::SasV1(sas) = self {
Some(sas)
} else {
None
}
}
/// Try to deconstruct this verification enum into a QR code verification.
pub fn qr(self) -> Option<QrVerification> {
if let Verification::QrV1(qr) = self {
Some(qr)
} else {
None
}
}
/// Has this verification finished.
pub fn is_done(&self) -> bool {
match self {
Verification::SasV1(s) => s.is_done(),
Verification::QrV1(qr) => qr.is_done(),
}
}
/// Has the verification been cancelled.
pub fn is_cancelled(&self) -> bool {
match self {
Verification::SasV1(s) => s.is_cancelled(),
Verification::QrV1(qr) => qr.is_cancelled(),
}
}
/// Get our own user id.
pub fn own_user_id(&self) -> &ruma::UserId {
match self {
Verification::SasV1(v) => v.own_user_id(),
Verification::QrV1(v) => v.own_user_id(),
}
}
/// Get the user id of the other user participating in this verification
/// flow.
pub fn other_user_id(&self) -> &ruma::UserId {
match self {
Verification::SasV1(v) => v.inner.other_user_id(),
Verification::QrV1(v) => v.inner.other_user_id(),
}
}
/// Is this a verification that is veryfying one of our own devices.
pub fn is_self_verification(&self) -> bool {
match self {
Verification::SasV1(v) => v.is_self_verification(),
Verification::QrV1(v) => v.is_self_verification(),
}
}
}
impl From<SasVerification> for Verification {
fn from(sas: SasVerification) -> Self {
Self::SasV1(sas)
}
}
impl From<QrVerification> for Verification {
fn from(qr: QrVerification) -> Self {
Self::QrV1(qr)
}
}

View file

@ -0,0 +1,87 @@
// 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 matrix_sdk_base::crypto::{
matrix_qrcode::{qrcode::QrCode, EncodingError},
QrVerification as BaseQrVerification,
};
use ruma::UserId;
use crate::{Client, Result};
/// An object controlling QR code style key verification flows.
#[derive(Debug, Clone)]
pub struct QrVerification {
pub(crate) inner: BaseQrVerification,
pub(crate) client: Client,
}
impl QrVerification {
/// Get our own user id.
pub fn own_user_id(&self) -> &UserId {
self.inner.user_id()
}
/// Is this a verification that is veryfying one of our own devices.
pub fn is_self_verification(&self) -> bool {
self.inner.is_self_verification()
}
/// Has this verification finished.
pub fn is_done(&self) -> bool {
self.inner.is_done()
}
/// Has the verification been cancelled.
pub fn is_cancelled(&self) -> bool {
self.inner.is_cancelled()
}
/// Generate a QR code object that is representing this verification flow.
///
/// The `QrCode` can then be rendered as an image or as an unicode string.
///
/// The [`to_bytes()`](#method.to_bytes) method can be used to instead
/// output the raw bytes that should be encoded as a QR code.
pub fn to_qr_code(&self) -> std::result::Result<QrCode, EncodingError> {
self.inner.to_qr_code()
}
/// Generate a the raw bytes that should be encoded as a QR code is
/// representing this verification flow.
///
/// The [`to_qr_code()`](#method.to_qr_code) method can be used to instead
/// output a `QrCode` object that can be rendered.
pub fn to_bytes(&self) -> std::result::Result<Vec<u8>, EncodingError> {
self.inner.to_bytes()
}
/// Confirm that the other side has scanned our QR code.
pub async fn confirm(&self) -> Result<()> {
if let Some(request) = self.inner.confirm_scanning() {
self.client.send_verification_request(request).await?;
}
Ok(())
}
/// Abort the verification flow and notify the other side that we did so.
pub async fn cancel(&self) -> Result<()> {
if let Some(request) = self.inner.cancel() {
self.client.send_verification_request(request).await?;
}
Ok(())
}
}

View file

@ -0,0 +1,132 @@
// 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 matrix_sdk_base::crypto::VerificationRequest as BaseVerificationRequest;
use ruma::events::key::verification::VerificationMethod;
use super::{QrVerification, SasVerification};
use crate::{Client, Result};
/// An object controlling the interactive verification flow.
#[derive(Debug, Clone)]
pub struct VerificationRequest {
pub(crate) inner: BaseVerificationRequest,
pub(crate) client: Client,
}
impl VerificationRequest {
/// Has this verification finished.
pub fn is_done(&self) -> bool {
self.inner.is_done()
}
/// Has the verification been cancelled.
pub fn is_cancelled(&self) -> bool {
self.inner.is_cancelled()
}
/// Get our own user id.
pub fn own_user_id(&self) -> &ruma::UserId {
self.inner.own_user_id()
}
/// Has the verification request been answered by another device.
pub fn is_passive(&self) -> bool {
self.inner.is_passive()
}
/// Is the verification request ready to start a verification flow.
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
/// Get the user id of the other user participating in this verification
/// flow.
pub fn other_user_id(&self) -> &ruma::UserId {
self.inner.other_user()
}
/// Is this a verification that is veryfying one of our own devices.
pub fn is_self_verification(&self) -> bool {
self.inner.is_self_verification()
}
/// Get the supported verification methods of the other side.
///
/// Will be present only if the other side requested the verification or if
/// we're in the ready state.
pub fn their_supported_methods(&self) -> Option<Vec<VerificationMethod>> {
self.inner.their_supported_methods()
}
/// Accept the verification request.
///
/// This method will accept the request and signal that it supports the
/// `m.sas.v1`, the `m.qr_code.show.v1`, and `m.reciprocate.v1` method.
///
/// If QR code scanning should be supported or QR code showing shouldn't be
/// supported the [`accept_with_methods()`] method should be used instead.
///
/// [`accept_with_methods()`]: #method.accept_with_methods
pub async fn accept(&self) -> Result<()> {
if let Some(request) = self.inner.accept() {
self.client.send_verification_request(request).await?;
}
Ok(())
}
/// Accept the verification request signaling that our client supports the
/// given verification methods.
///
/// # Arguments
///
/// * `methods` - The methods that we should advertise as supported by us.
pub async fn accept_with_methods(&self, methods: Vec<VerificationMethod>) -> Result<()> {
if let Some(request) = self.inner.accept_with_methods(methods) {
self.client.send_verification_request(request).await?;
}
Ok(())
}
/// Generate a QR code
pub async fn generate_qr_code(&self) -> Result<Option<QrVerification>> {
Ok(self
.inner
.generate_qr_code()
.await?
.map(|qr| QrVerification { inner: qr, client: self.client.clone() }))
}
/// Transition from this verification request into a SAS verification flow.
pub async fn start_sas(&self) -> Result<Option<SasVerification>> {
if let Some((sas, request)) = self.inner.start_sas().await? {
self.client.send_verification_request(request).await?;
Ok(Some(SasVerification { inner: sas, client: self.client.clone() }))
} else {
Ok(None)
}
}
/// Cancel the verification request
pub async fn cancel(&self) -> Result<()> {
if let Some(request) = self.inner.cancel() {
self.client.send_verification_request(request).await?;
}
Ok(())
}
}

View file

@ -12,21 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use matrix_sdk_base::crypto::{
AcceptSettings, OutgoingVerificationRequest, ReadOnlyDevice, Sas as BaseSas,
};
use matrix_sdk_base::crypto::{AcceptSettings, ReadOnlyDevice, Sas as BaseSas};
use ruma::UserId;
use crate::{error::Result, Client};
/// An object controlling the interactive verification flow.
#[derive(Debug, Clone)]
pub struct Sas {
pub struct SasVerification {
pub(crate) inner: BaseSas,
pub(crate) client: Client,
}
impl Sas {
impl SasVerification {
/// Accept the interactive verification flow.
pub async fn accept(&self) -> Result<()> {
self.accept_with_settings(Default::default()).await
@ -45,7 +43,7 @@ impl Sas {
/// # use futures::executor::block_on;
/// # use url::Url;
/// # use ruma::identifiers::user_id;
/// use matrix_sdk::Sas;
/// use matrix_sdk::verification::SasVerification;
/// use matrix_sdk_base::crypto::AcceptSettings;
/// use matrix_sdk::events::key::verification::ShortAuthenticationString;
/// # let homeserver = Url::parse("http://example.com").unwrap();
@ -56,6 +54,8 @@ impl Sas {
/// let sas = client
/// .get_verification(&user_id, flow_id)
/// .await
/// .unwrap()
/// .sas()
/// .unwrap();
///
/// let only_decimal = AcceptSettings::with_allowed_methods(
@ -65,15 +65,8 @@ impl Sas {
/// # });
/// ```
pub async fn accept_with_settings(&self, settings: AcceptSettings) -> Result<()> {
if let Some(req) = self.inner.accept_with_settings(settings) {
match req {
OutgoingVerificationRequest::ToDevice(r) => {
self.client.send_to_device(&r).await?;
}
OutgoingVerificationRequest::InRoom(r) => {
self.client.room_send_helper(&r).await?;
}
}
if let Some(request) = self.inner.accept_with_settings(settings) {
self.client.send_verification_request(request).await?;
}
Ok(())
}
@ -82,16 +75,8 @@ impl Sas {
pub async fn confirm(&self) -> Result<()> {
let (request, signature) = self.inner.confirm().await?;
match request {
Some(OutgoingVerificationRequest::InRoom(r)) => {
self.client.room_send_helper(&r).await?;
}
Some(OutgoingVerificationRequest::ToDevice(r)) => {
self.client.send_to_device(&r).await?;
}
None => (),
if let Some(request) = request {
self.client.send_verification_request(request).await?;
}
if let Some(s) = signature {
@ -104,14 +89,7 @@ impl Sas {
/// Cancel the interactive verification flow.
pub async fn cancel(&self) -> Result<()> {
if let Some(request) = self.inner.cancel() {
match request {
OutgoingVerificationRequest::ToDevice(r) => {
self.client.send_to_device(&r).await?;
}
OutgoingVerificationRequest::InRoom(r) => {
self.client.room_send_helper(&r).await?;
}
}
self.client.send_verification_request(request).await?;
}
Ok(())
@ -138,6 +116,11 @@ impl Sas {
self.inner.is_done()
}
/// Are we in a state where we can show the short auth string.
pub fn can_be_presented(&self) -> bool {
self.inner.can_be_presented()
}
/// Is the verification process canceled.
pub fn is_cancelled(&self) -> bool {
self.inner.is_cancelled()

View file

@ -1,49 +0,0 @@
// Copyright 2020 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use matrix_sdk_base::crypto::{
OutgoingVerificationRequest, VerificationRequest as BaseVerificationRequest,
};
use crate::{Client, Result};
/// An object controlling the interactive verification flow.
#[derive(Debug, Clone)]
pub struct VerificationRequest {
pub(crate) inner: BaseVerificationRequest,
pub(crate) client: Client,
}
impl VerificationRequest {
/// Accept the verification request
pub async fn accept(&self) -> Result<()> {
if let Some(request) = self.inner.accept() {
match request {
OutgoingVerificationRequest::ToDevice(r) => {
self.client.send_to_device(&r).await?;
}
OutgoingVerificationRequest::InRoom(r) => {
self.client.room_send_helper(&r).await?;
}
};
}
Ok(())
}
/// Cancel the verification request
pub async fn cancel(&self) -> Result<()> {
todo!()
}
}

View file

@ -36,7 +36,7 @@ use matrix_sdk_common::{locks::Mutex, uuid::Uuid};
use matrix_sdk_crypto::{
store::{CryptoStore, CryptoStoreError},
Device, EncryptionSettings, IncomingResponse, MegolmError, OlmError, OlmMachine,
OutgoingRequest, Sas, ToDeviceRequest, UserDevices,
OutgoingRequest, ToDeviceRequest, UserDevices,
};
#[cfg(feature = "encryption")]
use ruma::{
@ -1202,26 +1202,6 @@ impl BaseClient {
}
}
/// Get a `Sas` verification object with the given flow id.
///
/// # Arguments
///
/// * `flow_id` - The unique id that identifies a interactive verification
/// flow. For in-room verifications this will be the event id of the
/// *m.key.verification.request* event that started the flow, for the
/// to-device verification flows this will be the transaction id of the
/// *m.key.verification.start* event.
#[cfg(feature = "encryption")]
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Sas> {
self.olm
.lock()
.await
.as_ref()
.and_then(|o| o.get_verification(user_id, flow_id).map(|v| v.sas_v1()))
.flatten()
}
/// Get a specific device of a user.
///
/// # Arguments

View file

@ -720,6 +720,7 @@ impl SledStore {
.map(|u| {
u.map_err(StoreError::Sled).and_then(|(key, value)| {
self.deserialize_event(&value)
// TODO remove this unwrapping
.map(|receipt| {
(decode_key_value(&key, 3).unwrap().try_into().unwrap(), receipt)
})