Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Session verification incoming request support #4153

Merged
merged 11 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bindings/matrix-sdk-crypto-ffi/src/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ impl VerificationRequest {
RustVerificationRequestState::Ready {
their_methods,
our_methods,
other_device_id: _,
other_device_data: _,
} => VerificationRequestState::Ready {
their_methods: their_methods.iter().map(|m| m.to_string()).collect(),
our_methods: our_methods.iter().map(|m| m.to_string()).collect(),
Expand Down
4 changes: 2 additions & 2 deletions bindings/matrix-sdk-ffi/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,10 @@ impl Client {
let session_verification_controller: Arc<
tokio::sync::RwLock<Option<SessionVerificationController>>,
> = Default::default();
let ctrl = session_verification_controller.clone();
let controller = session_verification_controller.clone();

sdk_client.add_event_handler(move |ev: AnyToDeviceEvent| async move {
if let Some(session_verification_controller) = &*ctrl.clone().read().await {
if let Some(session_verification_controller) = &*controller.clone().read().await {
session_verification_controller.process_to_device_message(ev).await;
} else {
debug!("received to-device message, but verification controller isn't ready");
Expand Down
248 changes: 171 additions & 77 deletions bindings/matrix-sdk-ffi/src/session_verification.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
use std::sync::{Arc, RwLock};

use anyhow::Context as _;
use futures_util::StreamExt;
use matrix_sdk::{
encryption::{
identities::UserIdentity,
verification::{SasState, SasVerification, VerificationRequest},
verification::{SasState, SasVerification, VerificationRequest, VerificationRequestState},
Encryption,
},
ruma::events::{key::verification::VerificationMethod, AnyToDeviceEvent},
};
use ruma::UserId;
use tracing::{error, info};

use super::RUNTIME;
use crate::error::ClientError;
Expand Down Expand Up @@ -37,8 +38,20 @@ pub enum SessionVerificationData {
Decimals { values: Vec<u16> },
}

/// Details about the incoming verification request
#[derive(Debug, uniffi::Record)]
pub struct SessionVerificationRequestDetails {
sender_id: String,
flow_id: String,
device_id: String,
display_name: Option<String>,
/// First time this device was seen in milliseconds since epoch.
first_seen_timestamp: u64,
}

#[matrix_sdk_ffi_macros::export(callback_interface)]
pub trait SessionVerificationControllerDelegate: Sync + Send {
fn did_receive_verification_request(&self, details: SessionVerificationRequestDetails);
fn did_accept_verification_request(&self);
fn did_start_sas_verification(&self);
fn did_receive_verification_data(&self, data: SessionVerificationData);
Expand All @@ -60,80 +73,131 @@ pub struct SessionVerificationController {

#[matrix_sdk_ffi_macros::export]
impl SessionVerificationController {
pub async fn is_verified(&self) -> Result<bool, ClientError> {
stefanceriu marked this conversation as resolved.
Show resolved Hide resolved
let device =
self.encryption.get_own_device().await?.context("Our own device is missing")?;
pub fn set_delegate(&self, delegate: Option<Box<dyn SessionVerificationControllerDelegate>>) {
Copy link
Member Author

@stefanceriu stefanceriu Oct 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered switching away from a delegate and towards a listener but the public api changes are so small that I don't really think it's worth it at this point. Let's reassess when we start thinking about user verification.

*self.delegate.write().unwrap() = delegate;
}

/// Set this particular request as the currently active one and register for
/// events pertaining it.
/// * `sender_id` - The user requesting verification.
/// * `flow_id` - - The ID that uniquely identifies the verification flow.
pub async fn acknowledge_verification_request(
&self,
sender_id: String,
flow_id: String,
poljar marked this conversation as resolved.
Show resolved Hide resolved
) -> Result<(), ClientError> {
let sender_id = UserId::parse(sender_id.clone())?;

let verification_request = self
.encryption
.get_verification_request(&sender_id, flow_id)
.await
.ok_or(ClientError::new("Unknown session verification request"))?;

*self.verification_request.write().unwrap() = Some(verification_request.clone());

Ok(device.is_cross_signed_by_owner())
RUNTIME.spawn(Self::listen_to_verification_request_changes(
verification_request,
self.sas_verification.clone(),
self.delegate.clone(),
));

Ok(())
}

pub fn set_delegate(&self, delegate: Option<Box<dyn SessionVerificationControllerDelegate>>) {
*self.delegate.write().unwrap() = delegate;
/// Accept the previously acknowledged verification request
pub async fn accept_verification_request(&self) -> Result<(), ClientError> {
let verification_request = self.verification_request.read().unwrap().clone();

if let Some(verification_request) = verification_request {
let methods = vec![VerificationMethod::SasV1];
verification_request.accept_with_methods(methods).await?;
}

Ok(())
}

/// Request verification for the current device
pub async fn request_verification(&self) -> Result<(), ClientError> {
let methods = vec![VerificationMethod::SasV1];
let verification_request = self
.user_identity
.request_verification_with_methods(methods)
.await
.map_err(anyhow::Error::from)?;
*self.verification_request.write().unwrap() = Some(verification_request);

*self.verification_request.write().unwrap() = Some(verification_request.clone());

RUNTIME.spawn(Self::listen_to_verification_request_changes(
verification_request,
self.sas_verification.clone(),
self.delegate.clone(),
));

Ok(())
}

/// Transition the current verification request into a SAS verification
/// flow.
pub async fn start_sas_verification(&self) -> Result<(), ClientError> {
let verification_request = self.verification_request.read().unwrap().clone();

if let Some(verification) = verification_request {
match verification.start_sas().await {
Ok(Some(verification)) => {
*self.sas_verification.write().unwrap() = Some(verification.clone());
let Some(verification_request) = verification_request else {
return Err(ClientError::new("Verification request missing."));
};

if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_start_sas_verification()
}
match verification_request.start_sas().await {
Ok(Some(verification)) => {
*self.sas_verification.write().unwrap() = Some(verification.clone());

let delegate = self.delegate.clone();
RUNTIME.spawn(Self::listen_to_changes(delegate, verification));
if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_start_sas_verification()
}
_ => {
if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_fail()
}

let delegate = self.delegate.clone();
RUNTIME.spawn(Self::listen_to_sas_verification_changes(verification, delegate));
}
_ => {
if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_fail()
}
}
}

Ok(())
}

/// Confirm that the short auth strings match on both sides.
pub async fn approve_verification(&self) -> Result<(), ClientError> {
let sas_verification = self.sas_verification.read().unwrap().clone();
if let Some(sas_verification) = sas_verification {
sas_verification.confirm().await?;
}

Ok(())
let Some(sas_verification) = sas_verification else {
return Err(ClientError::new("SAS verification missing"));
};

Ok(sas_verification.confirm().await?)
}

/// Reject the short auth string
pub async fn decline_verification(&self) -> Result<(), ClientError> {
let sas_verification = self.sas_verification.read().unwrap().clone();
if let Some(sas_verification) = sas_verification {
sas_verification.mismatch().await?;
}

Ok(())
let Some(sas_verification) = sas_verification else {
return Err(ClientError::new("SAS verification missing"));
};

Ok(sas_verification.mismatch().await?)
}

/// Cancel the current verification request
pub async fn cancel_verification(&self) -> Result<(), ClientError> {
let verification_request = self.verification_request.read().unwrap().clone();
if let Some(verification) = verification_request {
verification.cancel().await?;
}

Ok(())
let Some(verification_request) = verification_request else {
return Err(ClientError::new("Verification request missing."));
};

Ok(verification_request.cancel().await?)
}
}

Expand All @@ -149,58 +213,88 @@ impl SessionVerificationController {
}

pub(crate) async fn process_to_device_message(&self, event: AnyToDeviceEvent) {
match event {
// TODO: Use the changes stream for this as well once we expose
// VerificationRequest::changes() in the main crate.
AnyToDeviceEvent::KeyVerificationStart(event) => {
if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) {
return;
}
if let Some(verification) = self
.encryption
.get_verification(
self.user_identity.user_id(),
event.content.transaction_id.as_str(),
)
.await
{
if let Some(sas_verification) = verification.sas() {
*self.sas_verification.write().unwrap() = Some(sas_verification.clone());

if sas_verification.accept().await.is_ok() {
if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_start_sas_verification()
}

let delegate = self.delegate.clone();
RUNTIME.spawn(Self::listen_to_changes(delegate, sas_verification));
} else if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_fail()
}
}
}
if let AnyToDeviceEvent::KeyVerificationRequest(event) = event {
info!("Received verification request: {:}", event.sender);

let Some(request) = self
.encryption
.get_verification_request(&event.sender, &event.content.transaction_id)
.await
else {
error!("Failed retrieving verification request");
return;
};

if !request.is_self_verification() {
info!("Received non-self verification request. Ignoring.");
return;
}
AnyToDeviceEvent::KeyVerificationReady(event) => {
if !self.is_transaction_id_valid(event.content.transaction_id.to_string()) {
return;
}

if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_accept_verification_request()
}
let VerificationRequestState::Requested { other_device_data, .. } = request.state()
else {
error!("Received key verification event but the request is in the wrong state.");
return;
};

if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_receive_verification_request(SessionVerificationRequestDetails {
sender_id: request.other_user_id().into(),
flow_id: request.flow_id().into(),
device_id: other_device_data.device_id().into(),
display_name: other_device_data.display_name().map(str::to_string),
first_seen_timestamp: other_device_data.first_time_seen_ts().get().into(),
});
}
_ => (),
}
}

fn is_transaction_id_valid(&self, transaction_id: String) -> bool {
match &*self.verification_request.read().unwrap() {
Some(verification) => verification.flow_id() == transaction_id,
None => false,
async fn listen_to_verification_request_changes(
verification_request: VerificationRequest,
sas_verification: Arc<RwLock<Option<SasVerification>>>,
delegate: Delegate,
) {
let mut stream = verification_request.changes();

while let Some(state) = stream.next().await {
match state {
VerificationRequestState::Transitioned { verification } => {
let Some(verification) = verification.sas() else {
error!("Invalid, non-sas verification flow. Returning.");
return;
};

*sas_verification.write().unwrap() = Some(verification.clone());

if verification.accept().await.is_ok() {
stefanceriu marked this conversation as resolved.
Show resolved Hide resolved
if let Some(delegate) = &*delegate.read().unwrap() {
delegate.did_start_sas_verification()
}

let delegate = delegate.clone();
RUNTIME.spawn(Self::listen_to_sas_verification_changes(
verification,
delegate,
));
} else if let Some(delegate) = &*delegate.read().unwrap() {
delegate.did_fail()
}
}
VerificationRequestState::Ready { .. } => {
if let Some(delegate) = &*delegate.read().unwrap() {
delegate.did_accept_verification_request()
}
}
VerificationRequestState::Cancelled(..) => {
if let Some(delegate) = &*delegate.read().unwrap() {
delegate.did_cancel();
}
}
_ => {}
}
}
}

async fn listen_to_changes(delegate: Delegate, sas: SasVerification) {
async fn listen_to_sas_verification_changes(sas: SasVerification, delegate: Delegate) {
let mut stream = sas.changes();

while let Some(state) = stream.next().await {
Expand Down
Loading
Loading