Merge branch 'crypto-improvements' into new-state-store
commit
8857335a7d
|
@ -1,9 +1,20 @@
|
||||||
use std::{env, io, process::exit};
|
use std::{
|
||||||
|
env, io,
|
||||||
|
process::exit,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
self, events::AnyToDeviceEvent, identifiers::UserId, Client, ClientConfig, LoopCtrl, Sas,
|
self,
|
||||||
SyncSettings,
|
events::{
|
||||||
|
room::message::MessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent, AnyToDeviceEvent,
|
||||||
|
},
|
||||||
|
identifiers::UserId,
|
||||||
|
Client, ClientConfig, LoopCtrl, Sas, SyncSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
async fn wait_for_confirmation(client: Client, sas: Sas) {
|
async fn wait_for_confirmation(client: Client, sas: Sas) {
|
||||||
|
@ -68,10 +79,13 @@ async fn login(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let client_ref = &client;
|
let client_ref = &client;
|
||||||
|
let initial_sync = Arc::new(AtomicBool::from(true));
|
||||||
|
let initial_ref = &initial_sync;
|
||||||
|
|
||||||
client
|
client
|
||||||
.sync_with_callback(SyncSettings::new(), |response| async move {
|
.sync_with_callback(SyncSettings::new(), |response| async move {
|
||||||
let client = &client_ref;
|
let client = &client_ref;
|
||||||
|
let initial = &initial_ref;
|
||||||
|
|
||||||
for event in &response.to_device.events {
|
for event in &response.to_device.events {
|
||||||
match event {
|
match event {
|
||||||
|
@ -114,6 +128,53 @@ async fn login(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !initial.load(Ordering::SeqCst) {
|
||||||
|
for (_room_id, room_info) in response.rooms.join {
|
||||||
|
for event in room_info.timeline.events {
|
||||||
|
if let AnySyncRoomEvent::Message(event) = event {
|
||||||
|
match event {
|
||||||
|
AnySyncMessageEvent::RoomMessage(m) => {
|
||||||
|
if let MessageEventContent::VerificationRequest(_) = &m.content
|
||||||
|
{
|
||||||
|
let request = client
|
||||||
|
.get_verification_request(&m.event_id)
|
||||||
|
.await
|
||||||
|
.expect("Request object wasn't created");
|
||||||
|
|
||||||
|
request
|
||||||
|
.accept()
|
||||||
|
.await
|
||||||
|
.expect("Can't accept verification request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnySyncMessageEvent::KeyVerificationKey(e) => {
|
||||||
|
let sas = client
|
||||||
|
.get_verification(&e.content.relation.event_id.as_str())
|
||||||
|
.await
|
||||||
|
.expect("Sas object wasn't created");
|
||||||
|
|
||||||
|
tokio::spawn(wait_for_confirmation((*client).clone(), sas));
|
||||||
|
}
|
||||||
|
AnySyncMessageEvent::KeyVerificationMac(e) => {
|
||||||
|
let sas = client
|
||||||
|
.get_verification(&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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initial.store(false, Ordering::SeqCst);
|
||||||
|
|
||||||
LoopCtrl::Continue
|
LoopCtrl::Continue
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -48,7 +48,7 @@ use matrix_sdk_base::{
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
use matrix_sdk_base::crypto::{
|
use matrix_sdk_base::crypto::{
|
||||||
decrypt_key_export, encrypt_key_export, olm::InboundGroupSession, store::CryptoStoreError,
|
decrypt_key_export, encrypt_key_export, olm::InboundGroupSession, store::CryptoStoreError,
|
||||||
AttachmentEncryptor, OutgoingRequests, ToDeviceRequest,
|
AttachmentEncryptor, OutgoingRequests, RoomMessageRequest, ToDeviceRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Enum controlling if a loop running callbacks should continue or abort.
|
/// Enum controlling if a loop running callbacks should continue or abort.
|
||||||
|
@ -122,6 +122,7 @@ use matrix_sdk_common::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
http_client::{client_with_config, HttpClient, HttpSend},
|
http_client::{client_with_config, HttpClient, HttpSend},
|
||||||
|
verification_request::VerificationRequest,
|
||||||
Error, OutgoingRequest, Result,
|
Error, OutgoingRequest, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1168,6 +1169,17 @@ impl Client {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn room_send_helper(
|
||||||
|
&self,
|
||||||
|
request: &RoomMessageRequest,
|
||||||
|
) -> Result<send_message_event::Response> {
|
||||||
|
let content = request.content.clone();
|
||||||
|
let txn_id = request.txn_id;
|
||||||
|
let room_id = &request.room_id;
|
||||||
|
|
||||||
|
self.room_send(&room_id, content, Some(txn_id)).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Send a room message to the homeserver.
|
/// Send a room message to the homeserver.
|
||||||
///
|
///
|
||||||
/// Returns the parsed response from the server.
|
/// Returns the parsed response from the server.
|
||||||
|
@ -1475,7 +1487,10 @@ impl Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
async fn send_to_device(&self, request: &ToDeviceRequest) -> Result<ToDeviceResponse> {
|
pub(crate) async fn send_to_device(
|
||||||
|
&self,
|
||||||
|
request: &ToDeviceRequest,
|
||||||
|
) -> Result<ToDeviceResponse> {
|
||||||
let txn_id_string = request.txn_id_string();
|
let txn_id_string = request.txn_id_string();
|
||||||
let request = RumaToDeviceRequest::new(
|
let request = RumaToDeviceRequest::new(
|
||||||
request.event_type.clone(),
|
request.event_type.clone(),
|
||||||
|
@ -1757,6 +1772,14 @@ impl Client {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
OutgoingRequests::RoomMessage(request) => {
|
||||||
|
if let Ok(resp) = self.room_send_helper(request).await {
|
||||||
|
self.base_client
|
||||||
|
.mark_request_as_sent(&r.request_id(), &resp)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1911,7 +1934,20 @@ impl Client {
|
||||||
.await
|
.await
|
||||||
.map(|sas| Sas {
|
.map(|sas| Sas {
|
||||||
inner: sas,
|
inner: sas,
|
||||||
http_client: self.http_client.clone(),
|
client: self.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a `VerificationRequest` object with the given flow id.
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
|
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||||
|
pub async fn get_verification_request(&self, flow_id: &EventId) -> Option<VerificationRequest> {
|
||||||
|
let olm = self.base_client.olm_machine().await?;
|
||||||
|
|
||||||
|
olm.get_verification_request(flow_id)
|
||||||
|
.map(|r| VerificationRequest {
|
||||||
|
inner: r,
|
||||||
|
client: self.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1960,7 +1996,7 @@ impl Client {
|
||||||
|
|
||||||
Ok(device.map(|d| Device {
|
Ok(device.map(|d| Device {
|
||||||
inner: d,
|
inner: d,
|
||||||
http_client: self.http_client.clone(),
|
client: self.clone(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2077,7 +2113,7 @@ impl Client {
|
||||||
|
|
||||||
Ok(UserDevices {
|
Ok(UserDevices {
|
||||||
inner: devices,
|
inner: devices,
|
||||||
http_client: self.http_client.clone(),
|
client: self.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2157,8 +2193,8 @@ impl Client {
|
||||||
|
|
||||||
let encrypt = move || -> Result<()> {
|
let encrypt = move || -> Result<()> {
|
||||||
let export: String = encrypt_key_export(&keys, &passphrase, 500_000)?;
|
let export: String = encrypt_key_export(&keys, &passphrase, 500_000)?;
|
||||||
let mut file = std::fs::File::create(path).unwrap();
|
let mut file = std::fs::File::create(path)?;
|
||||||
file.write_all(&export.into_bytes()).unwrap();
|
file.write_all(&export.into_bytes())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2222,6 +2258,7 @@ impl Client {
|
||||||
};
|
};
|
||||||
|
|
||||||
let task = tokio::task::spawn_blocking(decrypt);
|
let task = tokio::task::spawn_blocking(decrypt);
|
||||||
|
// TODO remove this unwrap.
|
||||||
let import = task.await.expect("Task join error").unwrap();
|
let import = task.await.expect("Task join error").unwrap();
|
||||||
|
|
||||||
Ok(olm.import_keys(import).await?)
|
Ok(olm.import_keys(import).await?)
|
||||||
|
|
|
@ -18,18 +18,15 @@ use matrix_sdk_base::crypto::{
|
||||||
store::CryptoStoreError, Device as BaseDevice, LocalTrust, ReadOnlyDevice,
|
store::CryptoStoreError, Device as BaseDevice, LocalTrust, ReadOnlyDevice,
|
||||||
UserDevices as BaseUserDevices,
|
UserDevices as BaseUserDevices,
|
||||||
};
|
};
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::identifiers::{DeviceId, DeviceIdBox};
|
||||||
api::r0::to_device::send_event_to_device::Request as ToDeviceRequest,
|
|
||||||
identifiers::{DeviceId, DeviceIdBox},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{error::Result, http_client::HttpClient, Sas};
|
use crate::{error::Result, Client, Sas};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
/// A device represents a E2EE capable client of an user.
|
/// A device represents a E2EE capable client of an user.
|
||||||
pub struct Device {
|
pub struct Device {
|
||||||
pub(crate) inner: BaseDevice,
|
pub(crate) inner: BaseDevice,
|
||||||
pub(crate) http_client: HttpClient,
|
pub(crate) client: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Device {
|
impl Deref for Device {
|
||||||
|
@ -66,14 +63,11 @@ impl Device {
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn start_verification(&self) -> Result<Sas> {
|
pub async fn start_verification(&self) -> Result<Sas> {
|
||||||
let (sas, request) = self.inner.start_verification().await?;
|
let (sas, request) = self.inner.start_verification().await?;
|
||||||
let txn_id_string = request.txn_id_string();
|
self.client.send_to_device(&request).await?;
|
||||||
let request = ToDeviceRequest::new(request.event_type, &txn_id_string, request.messages);
|
|
||||||
|
|
||||||
self.http_client.send(request).await?;
|
|
||||||
|
|
||||||
Ok(Sas {
|
Ok(Sas {
|
||||||
inner: sas,
|
inner: sas,
|
||||||
http_client: self.http_client.clone(),
|
client: self.client.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +96,7 @@ impl Device {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UserDevices {
|
pub struct UserDevices {
|
||||||
pub(crate) inner: BaseUserDevices,
|
pub(crate) inner: BaseUserDevices,
|
||||||
pub(crate) http_client: HttpClient,
|
pub(crate) client: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserDevices {
|
impl UserDevices {
|
||||||
|
@ -110,7 +104,7 @@ impl UserDevices {
|
||||||
pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
|
pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
|
||||||
self.inner.get(device_id).map(|d| Device {
|
self.inner.get(device_id).map(|d| Device {
|
||||||
inner: d,
|
inner: d,
|
||||||
http_client: self.http_client.clone(),
|
client: self.client.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,11 +115,11 @@ impl UserDevices {
|
||||||
|
|
||||||
/// Iterator over all the devices of the user devices.
|
/// Iterator over all the devices of the user devices.
|
||||||
pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
|
pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
|
||||||
let client = self.http_client.clone();
|
let client = self.client.clone();
|
||||||
|
|
||||||
self.inner.devices().map(move |d| Device {
|
self.inner.devices().map(move |d| Device {
|
||||||
inner: d,
|
inner: d,
|
||||||
http_client: client.clone(),
|
client: client.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,8 @@ mod http_client;
|
||||||
mod device;
|
mod device;
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
mod sas;
|
mod sas;
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
|
mod verification_request;
|
||||||
|
|
||||||
pub use client::{Client, ClientConfig, LoopCtrl, SyncSettings};
|
pub use client::{Client, ClientConfig, LoopCtrl, SyncSettings};
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
|
|
|
@ -12,43 +12,49 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use matrix_sdk_base::crypto::{ReadOnlyDevice, Sas as BaseSas};
|
use matrix_sdk_base::crypto::{OutgoingVerificationRequest, ReadOnlyDevice, Sas as BaseSas};
|
||||||
use matrix_sdk_common::api::r0::to_device::send_event_to_device::Request as ToDeviceRequest;
|
|
||||||
|
|
||||||
use crate::{error::Result, http_client::HttpClient};
|
use crate::{error::Result, Client};
|
||||||
|
|
||||||
/// An object controling the interactive verification flow.
|
/// An object controling the interactive verification flow.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Sas {
|
pub struct Sas {
|
||||||
pub(crate) inner: BaseSas,
|
pub(crate) inner: BaseSas,
|
||||||
pub(crate) http_client: HttpClient,
|
pub(crate) client: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sas {
|
impl Sas {
|
||||||
/// Accept the interactive verification flow.
|
/// Accept the interactive verification flow.
|
||||||
pub async fn accept(&self) -> Result<()> {
|
pub async fn accept(&self) -> Result<()> {
|
||||||
if let Some(req) = self.inner.accept() {
|
if let Some(req) = self.inner.accept() {
|
||||||
let txn_id_string = req.txn_id_string();
|
match req {
|
||||||
let request = ToDeviceRequest::new(req.event_type, &txn_id_string, req.messages);
|
OutgoingVerificationRequest::ToDevice(r) => {
|
||||||
|
self.client.send_to_device(&r).await?;
|
||||||
self.http_client.send(request).await?;
|
}
|
||||||
|
OutgoingVerificationRequest::InRoom(_) => todo!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Confirm that the short auth strings match on both sides.
|
/// Confirm that the short auth strings match on both sides.
|
||||||
pub async fn confirm(&self) -> Result<()> {
|
pub async fn confirm(&self) -> Result<()> {
|
||||||
let (to_device, signature) = self.inner.confirm().await?;
|
let (request, signature) = self.inner.confirm().await?;
|
||||||
|
|
||||||
if let Some(req) = to_device {
|
match request {
|
||||||
let txn_id_string = req.txn_id_string();
|
Some(OutgoingVerificationRequest::InRoom(r)) => {
|
||||||
let request = ToDeviceRequest::new(req.event_type, &txn_id_string, req.messages);
|
self.client.room_send_helper(&r).await?;
|
||||||
|
}
|
||||||
|
|
||||||
self.http_client.send(request).await?;
|
Some(OutgoingVerificationRequest::ToDevice(r)) => {
|
||||||
|
self.client.send_to_device(&r).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
None => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(s) = signature {
|
if let Some(s) = signature {
|
||||||
self.http_client.send(s).await?;
|
self.client.send(s).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -56,12 +62,17 @@ impl Sas {
|
||||||
|
|
||||||
/// Cancel the interactive verification flow.
|
/// Cancel the interactive verification flow.
|
||||||
pub async fn cancel(&self) -> Result<()> {
|
pub async fn cancel(&self) -> Result<()> {
|
||||||
if let Some(req) = self.inner.cancel() {
|
if let Some(request) = self.inner.cancel() {
|
||||||
let txn_id_string = req.txn_id_string();
|
match request {
|
||||||
let request = ToDeviceRequest::new(req.event_type, &txn_id_string, req.messages);
|
OutgoingVerificationRequest::ToDevice(r) => {
|
||||||
|
self.client.send_to_device(&r).await?;
|
||||||
self.http_client.send(request).await?;
|
|
||||||
}
|
}
|
||||||
|
OutgoingVerificationRequest::InRoom(r) => {
|
||||||
|
self.client.room_send_helper(&r).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
// 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::VerificationRequest as BaseVerificationRequest, events::AnyMessageEventContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{Client, Result};
|
||||||
|
|
||||||
|
/// An object controling the interactive verification flow.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VerificationRequest {
|
||||||
|
pub(crate) inner: BaseVerificationRequest,
|
||||||
|
pub(crate) client: Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VerificationRequest {
|
||||||
|
/// Accept the interactive verification flow.
|
||||||
|
pub async fn accept(&self) -> Result<()> {
|
||||||
|
if let Some(content) = self.inner.accept() {
|
||||||
|
let content = AnyMessageEventContent::KeyVerificationReady(content);
|
||||||
|
|
||||||
|
self.client
|
||||||
|
.room_send(self.inner.room_id(), content, None)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,10 @@ use serde_json::value::RawValue as RawJsonValue;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
events::{
|
events::{
|
||||||
|
call::{
|
||||||
|
answer::AnswerEventContent, candidates::CandidatesEventContent,
|
||||||
|
hangup::HangupEventContent, invite::InviteEventContent,
|
||||||
|
},
|
||||||
custom::CustomEventContent,
|
custom::CustomEventContent,
|
||||||
fully_read::FullyReadEventContent,
|
fully_read::FullyReadEventContent,
|
||||||
ignored_user_list::IgnoredUserListEventContent,
|
ignored_user_list::IgnoredUserListEventContent,
|
||||||
|
@ -143,6 +147,12 @@ impl Emitter {
|
||||||
AnySyncMessageEvent::Custom(e) => {
|
AnySyncMessageEvent::Custom(e) => {
|
||||||
self.on_custom_event(room, &CustomEvent::Message(e)).await
|
self.on_custom_event(room, &CustomEvent::Message(e)).await
|
||||||
}
|
}
|
||||||
|
AnySyncMessageEvent::CallInvite(e) => self.on_room_call_invite(room, e).await,
|
||||||
|
AnySyncMessageEvent::CallAnswer(e) => self.on_room_call_answer(room, e).await,
|
||||||
|
AnySyncMessageEvent::CallCandidates(e) => {
|
||||||
|
self.on_room_call_candidates(room, e).await
|
||||||
|
}
|
||||||
|
AnySyncMessageEvent::CallHangup(e) => self.on_room_call_hangup(room, e).await,
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
AnySyncRoomEvent::RedactedState(_event) => {}
|
AnySyncRoomEvent::RedactedState(_event) => {}
|
||||||
|
@ -320,6 +330,19 @@ pub trait EventEmitter: Send + Sync {
|
||||||
_: &SyncMessageEvent<FeedbackEventContent>,
|
_: &SyncMessageEvent<FeedbackEventContent>,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
/// Fires when `Client` receives a `RoomEvent::CallInvite` event
|
||||||
|
async fn on_room_call_invite(&self, _: RoomState, _: &SyncMessageEvent<InviteEventContent>) {}
|
||||||
|
/// Fires when `Client` receives a `RoomEvent::CallAnswer` event
|
||||||
|
async fn on_room_call_answer(&self, _: RoomState, _: &SyncMessageEvent<AnswerEventContent>) {}
|
||||||
|
/// Fires when `Client` receives a `RoomEvent::CallCandidates` event
|
||||||
|
async fn on_room_call_candidates(
|
||||||
|
&self,
|
||||||
|
_: RoomState,
|
||||||
|
_: &SyncMessageEvent<CandidatesEventContent>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
/// Fires when `Client` receives a `RoomEvent::CallHangup` event
|
||||||
|
async fn on_room_call_hangup(&self, _: RoomState, _: &SyncMessageEvent<HangupEventContent>) {}
|
||||||
/// Fires when `Client` receives a `RoomEvent::RoomRedaction` event.
|
/// Fires when `Client` receives a `RoomEvent::RoomRedaction` event.
|
||||||
async fn on_room_redaction(&self, _: RoomState, _: &SyncRedactionEvent) {}
|
async fn on_room_redaction(&self, _: RoomState, _: &SyncRedactionEvent) {}
|
||||||
/// Fires when `Client` receives a `RoomEvent::RoomPowerLevels` event.
|
/// Fires when `Client` receives a `RoomEvent::RoomPowerLevels` event.
|
||||||
|
@ -507,6 +530,34 @@ mod test {
|
||||||
) {
|
) {
|
||||||
self.0.lock().await.push("feedback".to_string())
|
self.0.lock().await.push("feedback".to_string())
|
||||||
}
|
}
|
||||||
|
async fn on_room_call_invite(
|
||||||
|
&self,
|
||||||
|
_: RoomState,
|
||||||
|
_: &SyncMessageEvent<InviteEventContent>,
|
||||||
|
) {
|
||||||
|
self.0.lock().await.push("call invite".to_string())
|
||||||
|
}
|
||||||
|
async fn on_room_call_answer(
|
||||||
|
&self,
|
||||||
|
_: RoomState,
|
||||||
|
_: &SyncMessageEvent<AnswerEventContent>,
|
||||||
|
) {
|
||||||
|
self.0.lock().await.push("call answer".to_string())
|
||||||
|
}
|
||||||
|
async fn on_room_call_candidates(
|
||||||
|
&self,
|
||||||
|
_: RoomState,
|
||||||
|
_: &SyncMessageEvent<CandidatesEventContent>,
|
||||||
|
) {
|
||||||
|
self.0.lock().await.push("call candidates".to_string())
|
||||||
|
}
|
||||||
|
async fn on_room_call_hangup(
|
||||||
|
&self,
|
||||||
|
_: RoomState,
|
||||||
|
_: &SyncMessageEvent<HangupEventContent>,
|
||||||
|
) {
|
||||||
|
self.0.lock().await.push("call hangup".to_string())
|
||||||
|
}
|
||||||
async fn on_room_redaction(&self, _: RoomState, _: &SyncRedactionEvent) {
|
async fn on_room_redaction(&self, _: RoomState, _: &SyncRedactionEvent) {
|
||||||
self.0.lock().await.push("redaction".to_string())
|
self.0.lock().await.push("redaction".to_string())
|
||||||
}
|
}
|
||||||
|
@ -797,4 +848,28 @@ mod test {
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn event_emitter_voip() {
|
||||||
|
let vec = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let test_vec = Arc::clone(&vec);
|
||||||
|
let emitter = Box::new(EvEmitterTest(vec));
|
||||||
|
|
||||||
|
let client = get_client().await;
|
||||||
|
client.add_event_emitter(emitter).await;
|
||||||
|
|
||||||
|
let response = sync_response(SyncResponseFile::Voip);
|
||||||
|
client.receive_sync_response(response).await.unwrap();
|
||||||
|
|
||||||
|
let v = test_vec.lock().await;
|
||||||
|
assert_eq!(
|
||||||
|
v.as_slice(),
|
||||||
|
[
|
||||||
|
"call invite",
|
||||||
|
"call answer",
|
||||||
|
"call candidates",
|
||||||
|
"call hangup",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ js_int = "0.1.9"
|
||||||
[dependencies.ruma]
|
[dependencies.ruma]
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
git = "https://github.com/ruma/ruma"
|
git = "https://github.com/ruma/ruma"
|
||||||
rev = "e8882fe8142d7b55ed4c8ccc6150946945f9e237"
|
rev = "1a4e9aa20abff4786dbb91a19fb72c1dfa4410a7"
|
||||||
features = ["client-api", "unstable-pre-spec"]
|
features = ["client-api", "unstable-pre-spec"]
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
|
|
@ -42,6 +42,7 @@ use tracing::warn;
|
||||||
use crate::{
|
use crate::{
|
||||||
olm::{InboundGroupSession, PrivateCrossSigningIdentity, Session},
|
olm::{InboundGroupSession, PrivateCrossSigningIdentity, Session},
|
||||||
store::{Changes, DeviceChanges},
|
store::{Changes, DeviceChanges},
|
||||||
|
OutgoingVerificationRequest,
|
||||||
};
|
};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::{OlmMachine, ReadOnlyAccount};
|
use crate::{OlmMachine, ReadOnlyAccount};
|
||||||
|
@ -91,9 +92,16 @@ impl Device {
|
||||||
///
|
///
|
||||||
/// Returns a `Sas` object and to-device request that needs to be sent out.
|
/// Returns a `Sas` object and to-device request that needs to be sent out.
|
||||||
pub async fn start_verification(&self) -> StoreResult<(Sas, ToDeviceRequest)> {
|
pub async fn start_verification(&self) -> StoreResult<(Sas, ToDeviceRequest)> {
|
||||||
self.verification_machine
|
let (sas, request) = self
|
||||||
|
.verification_machine
|
||||||
.start_sas(self.inner.clone())
|
.start_sas(self.inner.clone())
|
||||||
.await
|
.await?;
|
||||||
|
|
||||||
|
if let OutgoingVerificationRequest::ToDevice(r) = request {
|
||||||
|
Ok((sas, r))
|
||||||
|
} else {
|
||||||
|
panic!("Invalid verification request type");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the Olm sessions that belong to this device.
|
/// Get the Olm sessions that belong to this device.
|
||||||
|
|
|
@ -51,6 +51,7 @@ pub use machine::OlmMachine;
|
||||||
pub use olm::EncryptionSettings;
|
pub use olm::EncryptionSettings;
|
||||||
pub(crate) use olm::ReadOnlyAccount;
|
pub(crate) use olm::ReadOnlyAccount;
|
||||||
pub use requests::{
|
pub use requests::{
|
||||||
IncomingResponse, KeysQueryRequest, OutgoingRequest, OutgoingRequests, ToDeviceRequest,
|
IncomingResponse, KeysQueryRequest, OutgoingRequest, OutgoingRequests,
|
||||||
|
OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest,
|
||||||
};
|
};
|
||||||
pub use verification::Sas;
|
pub use verification::{Sas, VerificationRequest};
|
||||||
|
|
|
@ -36,7 +36,8 @@ use matrix_sdk_common::{
|
||||||
ToDeviceEvent,
|
ToDeviceEvent,
|
||||||
},
|
},
|
||||||
identifiers::{
|
identifiers::{
|
||||||
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UserId,
|
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, EventId, RoomId,
|
||||||
|
UserId,
|
||||||
},
|
},
|
||||||
js_int::UInt,
|
js_int::UInt,
|
||||||
locks::Mutex,
|
locks::Mutex,
|
||||||
|
@ -44,8 +45,6 @@ use matrix_sdk_common::{
|
||||||
Raw,
|
Raw,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "sqlite_cryptostore")]
|
|
||||||
use crate::store::sqlite::SqliteStore;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult},
|
error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult},
|
||||||
identities::{Device, IdentityManager, UserDevices},
|
identities::{Device, IdentityManager, UserDevices},
|
||||||
|
@ -64,6 +63,8 @@ use crate::{
|
||||||
verification::{Sas, VerificationMachine},
|
verification::{Sas, VerificationMachine},
|
||||||
ToDeviceRequest,
|
ToDeviceRequest,
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "sqlite_cryptostore")]
|
||||||
|
use crate::{store::sqlite::SqliteStore, verification::VerificationRequest};
|
||||||
|
|
||||||
/// State machine implementation of the Olm/Megolm encryption protocol used for
|
/// State machine implementation of the Olm/Megolm encryption protocol used for
|
||||||
/// Matrix end to end encryption.
|
/// Matrix end to end encryption.
|
||||||
|
@ -321,6 +322,7 @@ impl OlmMachine {
|
||||||
}
|
}
|
||||||
|
|
||||||
requests.append(&mut self.outgoing_to_device_requests());
|
requests.append(&mut self.outgoing_to_device_requests());
|
||||||
|
requests.append(&mut self.verification_machine.outgoing_room_message_requests());
|
||||||
requests.append(&mut self.key_request_machine.outgoing_to_device_requests());
|
requests.append(&mut self.key_request_machine.outgoing_to_device_requests());
|
||||||
|
|
||||||
requests
|
requests
|
||||||
|
@ -359,6 +361,9 @@ impl OlmMachine {
|
||||||
IncomingResponse::SignatureUpload(_) => {
|
IncomingResponse::SignatureUpload(_) => {
|
||||||
self.verification_machine.mark_request_as_sent(request_id);
|
self.verification_machine.mark_request_as_sent(request_id);
|
||||||
}
|
}
|
||||||
|
IncomingResponse::RoomMessage(_) => {
|
||||||
|
self.verification_machine.mark_request_as_sent(request_id);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -738,8 +743,8 @@ impl OlmMachine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_verification_event(&self, mut event: &mut AnyToDeviceEvent) {
|
async fn handle_verification_event(&self, event: &AnyToDeviceEvent) {
|
||||||
if let Err(e) = self.verification_machine.receive_event(&mut event).await {
|
if let Err(e) = self.verification_machine.receive_event(&event).await {
|
||||||
error!("Error handling a verification event: {:?}", e);
|
error!("Error handling a verification event: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -767,6 +772,11 @@ impl OlmMachine {
|
||||||
self.verification_machine.get_sas(flow_id)
|
self.verification_machine.get_sas(flow_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a verification request object with the given flow id.
|
||||||
|
pub fn get_verification_request(&self, flow_id: &EventId) -> Option<VerificationRequest> {
|
||||||
|
self.verification_machine.get_request(flow_id)
|
||||||
|
}
|
||||||
|
|
||||||
async fn update_one_time_key_count(&self, key_count: &BTreeMap<DeviceKeyAlgorithm, UInt>) {
|
async fn update_one_time_key_count(&self, key_count: &BTreeMap<DeviceKeyAlgorithm, UInt>) {
|
||||||
self.account.update_uploaded_key_count(key_count).await;
|
self.account.update_uploaded_key_count(key_count).await;
|
||||||
}
|
}
|
||||||
|
@ -869,7 +879,7 @@ impl OlmMachine {
|
||||||
| AnyToDeviceEvent::KeyVerificationMac(..)
|
| AnyToDeviceEvent::KeyVerificationMac(..)
|
||||||
| AnyToDeviceEvent::KeyVerificationRequest(..)
|
| AnyToDeviceEvent::KeyVerificationRequest(..)
|
||||||
| AnyToDeviceEvent::KeyVerificationStart(..) => {
|
| AnyToDeviceEvent::KeyVerificationStart(..) => {
|
||||||
self.handle_verification_event(&mut event).await;
|
self.handle_verification_event(&event).await;
|
||||||
}
|
}
|
||||||
_ => continue,
|
_ => continue,
|
||||||
}
|
}
|
||||||
|
@ -924,6 +934,12 @@ impl OlmMachine {
|
||||||
// TODO set the encryption info on the event (is it verified, was it
|
// TODO set the encryption info on the event (is it verified, was it
|
||||||
// decrypted, sender key...)
|
// decrypted, sender key...)
|
||||||
|
|
||||||
|
if let Ok(e) = decrypted_event.deserialize() {
|
||||||
|
self.verification_machine
|
||||||
|
.receive_room_event(room_id, &e)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(decrypted_event)
|
Ok(decrypted_event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1810,34 +1826,34 @@ pub(crate) mod test {
|
||||||
|
|
||||||
let (alice_sas, request) = bob_device.start_verification().await.unwrap();
|
let (alice_sas, request) = bob_device.start_verification().await.unwrap();
|
||||||
|
|
||||||
let mut event = request_to_event(alice.user_id(), &request);
|
let event = request_to_event(alice.user_id(), &request.into());
|
||||||
bob.handle_verification_event(&mut event).await;
|
bob.handle_verification_event(&event).await;
|
||||||
|
|
||||||
let bob_sas = bob.get_verification(alice_sas.flow_id()).unwrap();
|
let bob_sas = bob.get_verification(alice_sas.flow_id().as_str()).unwrap();
|
||||||
|
|
||||||
assert!(alice_sas.emoji().is_none());
|
assert!(alice_sas.emoji().is_none());
|
||||||
assert!(bob_sas.emoji().is_none());
|
assert!(bob_sas.emoji().is_none());
|
||||||
|
|
||||||
let mut event = bob_sas
|
let event = bob_sas
|
||||||
.accept()
|
.accept()
|
||||||
.map(|r| request_to_event(bob.user_id(), &r))
|
.map(|r| request_to_event(bob.user_id(), &r))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
alice.handle_verification_event(&mut event).await;
|
alice.handle_verification_event(&event).await;
|
||||||
|
|
||||||
let mut event = alice
|
let event = alice
|
||||||
.outgoing_to_device_requests()
|
.outgoing_to_device_requests()
|
||||||
.first()
|
.first()
|
||||||
.map(|r| outgoing_request_to_event(alice.user_id(), r))
|
.map(|r| outgoing_request_to_event(alice.user_id(), r))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
bob.handle_verification_event(&mut event).await;
|
bob.handle_verification_event(&event).await;
|
||||||
|
|
||||||
let mut event = bob
|
let event = bob
|
||||||
.outgoing_to_device_requests()
|
.outgoing_to_device_requests()
|
||||||
.first()
|
.first()
|
||||||
.map(|r| outgoing_request_to_event(bob.user_id(), r))
|
.map(|r| outgoing_request_to_event(bob.user_id(), r))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
alice.handle_verification_event(&mut event).await;
|
alice.handle_verification_event(&event).await;
|
||||||
|
|
||||||
assert!(alice_sas.emoji().is_some());
|
assert!(alice_sas.emoji().is_some());
|
||||||
assert!(bob_sas.emoji().is_some());
|
assert!(bob_sas.emoji().is_some());
|
||||||
|
@ -1845,19 +1861,19 @@ pub(crate) mod test {
|
||||||
assert_eq!(alice_sas.emoji(), bob_sas.emoji());
|
assert_eq!(alice_sas.emoji(), bob_sas.emoji());
|
||||||
assert_eq!(alice_sas.decimals(), bob_sas.decimals());
|
assert_eq!(alice_sas.decimals(), bob_sas.decimals());
|
||||||
|
|
||||||
let mut event = bob_sas
|
let event = bob_sas
|
||||||
.confirm()
|
.confirm()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.0
|
.0
|
||||||
.map(|r| request_to_event(bob.user_id(), &r))
|
.map(|r| request_to_event(bob.user_id(), &r))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
alice.handle_verification_event(&mut event).await;
|
alice.handle_verification_event(&event).await;
|
||||||
|
|
||||||
assert!(!alice_sas.is_done());
|
assert!(!alice_sas.is_done());
|
||||||
assert!(!bob_sas.is_done());
|
assert!(!bob_sas.is_done());
|
||||||
|
|
||||||
let mut event = alice_sas
|
let event = alice_sas
|
||||||
.confirm()
|
.confirm()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -1875,7 +1891,7 @@ pub(crate) mod test {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(!alice_device.is_trusted());
|
assert!(!alice_device.is_trusted());
|
||||||
bob.handle_verification_event(&mut event).await;
|
bob.handle_verification_event(&event).await;
|
||||||
assert!(bob_sas.is_done());
|
assert!(bob_sas.is_done());
|
||||||
assert!(alice_device.is_trusted());
|
assert!(alice_device.is_trusted());
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ use matrix_sdk_common::{
|
||||||
instant::Instant,
|
instant::Instant,
|
||||||
js_int::UInt,
|
js_int::UInt,
|
||||||
locks::Mutex,
|
locks::Mutex,
|
||||||
Raw,
|
CanonicalJsonValue, Raw,
|
||||||
};
|
};
|
||||||
use olm_rs::{
|
use olm_rs::{
|
||||||
account::{IdentityKeys, OlmAccount, OneTimeKeys},
|
account::{IdentityKeys, OlmAccount, OneTimeKeys},
|
||||||
|
@ -743,7 +743,7 @@ impl ReadOnlyAccount {
|
||||||
.or_insert_with(BTreeMap::new)
|
.or_insert_with(BTreeMap::new)
|
||||||
.insert(
|
.insert(
|
||||||
DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, &self.device_id),
|
DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, &self.device_id),
|
||||||
self.sign_json(&json_device_keys).await,
|
self.sign_json(json_device_keys).await,
|
||||||
);
|
);
|
||||||
|
|
||||||
device_keys
|
device_keys
|
||||||
|
@ -770,8 +770,10 @@ impl ReadOnlyAccount {
|
||||||
/// # Panic
|
/// # Panic
|
||||||
///
|
///
|
||||||
/// Panics if the json value can't be serialized.
|
/// Panics if the json value can't be serialized.
|
||||||
pub async fn sign_json(&self, json: &Value) -> String {
|
pub async fn sign_json(&self, json: Value) -> String {
|
||||||
self.sign(&json.to_string()).await
|
let canonical_json: CanonicalJsonValue =
|
||||||
|
json.try_into().expect("Can't canonicalize the json value");
|
||||||
|
self.sign(&canonical_json.to_string()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn signed_one_time_keys_helper(
|
pub(crate) async fn signed_one_time_keys_helper(
|
||||||
|
@ -785,7 +787,7 @@ impl ReadOnlyAccount {
|
||||||
"key": key,
|
"key": key,
|
||||||
});
|
});
|
||||||
|
|
||||||
let signature = self.sign_json(&key_json).await;
|
let signature = self.sign_json(key_json).await;
|
||||||
|
|
||||||
let mut signature_map = BTreeMap::new();
|
let mut signature_map = BTreeMap::new();
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,14 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use dashmap::{DashMap, DashSet};
|
use dashmap::{DashMap, DashSet};
|
||||||
use matrix_sdk_common::{api::r0::to_device::DeviceIdOrAllDevices, uuid::Uuid};
|
use matrix_sdk_common::{
|
||||||
|
api::r0::to_device::DeviceIdOrAllDevices,
|
||||||
|
events::room::{
|
||||||
|
encrypted::{MegolmV1AesSha2Content, MegolmV1AesSha2ContentInit},
|
||||||
|
message::Relation,
|
||||||
|
},
|
||||||
|
uuid::Uuid,
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::min,
|
cmp::min,
|
||||||
fmt,
|
fmt,
|
||||||
|
@ -240,19 +247,31 @@ impl OutboundGroupSession {
|
||||||
"type": content.event_type(),
|
"type": content.event_type(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let relates_to: Option<Relation> = json_content
|
||||||
|
.get("content")
|
||||||
|
.map(|c| {
|
||||||
|
c.get("m.relates_to")
|
||||||
|
.cloned()
|
||||||
|
.map(|r| serde_json::from_value(r).ok())
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.flatten();
|
||||||
|
|
||||||
let plaintext = json_content.to_string();
|
let plaintext = json_content.to_string();
|
||||||
|
|
||||||
let ciphertext = self.encrypt_helper(plaintext).await;
|
let ciphertext = self.encrypt_helper(plaintext).await;
|
||||||
|
|
||||||
EncryptedEventContent::MegolmV1AesSha2(
|
let mut encrypted_content: MegolmV1AesSha2Content = MegolmV1AesSha2ContentInit {
|
||||||
matrix_sdk_common::events::room::encrypted::MegolmV1AesSha2ContentInit {
|
|
||||||
ciphertext,
|
ciphertext,
|
||||||
sender_key: self.account_identity_keys.curve25519().to_owned(),
|
sender_key: self.account_identity_keys.curve25519().to_owned(),
|
||||||
session_id: self.session_id().to_owned(),
|
session_id: self.session_id().to_owned(),
|
||||||
device_id: (&*self.device_id).to_owned(),
|
device_id: (&*self.device_id).to_owned(),
|
||||||
}
|
}
|
||||||
.into(),
|
.into();
|
||||||
)
|
|
||||||
|
encrypted_content.relates_to = relates_to;
|
||||||
|
|
||||||
|
EncryptedEventContent::MegolmV1AesSha2(encrypted_content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the session has expired and if it should be rotated.
|
/// Check if the session has expired and if it should be rotated.
|
||||||
|
|
|
@ -214,7 +214,7 @@ impl PrivateCrossSigningIdentity {
|
||||||
master.cross_signing_key(account.user_id().to_owned(), KeyUsage::Master);
|
master.cross_signing_key(account.user_id().to_owned(), KeyUsage::Master);
|
||||||
let signature = account
|
let signature = account
|
||||||
.sign_json(
|
.sign_json(
|
||||||
&serde_json::to_value(&public_key)
|
serde_json::to_value(&public_key)
|
||||||
.expect("Can't convert own public master key to json"),
|
.expect("Can't convert own public master key to json"),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -23,7 +23,7 @@ use matrix_sdk_common::{
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{json, Error as JsonError, Value};
|
use serde_json::{json, Error as JsonError, Value};
|
||||||
use std::{collections::BTreeMap, sync::Arc};
|
use std::{collections::BTreeMap, convert::TryInto, sync::Arc};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ use matrix_sdk_common::{
|
||||||
api::r0::keys::{CrossSigningKey, KeyUsage},
|
api::r0::keys::{CrossSigningKey, KeyUsage},
|
||||||
identifiers::UserId,
|
identifiers::UserId,
|
||||||
locks::Mutex,
|
locks::Mutex,
|
||||||
|
CanonicalJsonValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -404,8 +405,9 @@ impl Signing {
|
||||||
pub async fn sign_json(&self, mut json: Value) -> Result<Signature, SignatureError> {
|
pub async fn sign_json(&self, mut json: Value) -> Result<Signature, SignatureError> {
|
||||||
let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?;
|
let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?;
|
||||||
let _ = json_object.remove("signatures");
|
let _ = json_object.remove("signatures");
|
||||||
let canonical_json = serde_json::to_string(json_object)?;
|
let canonical_json: CanonicalJsonValue =
|
||||||
Ok(self.sign(&canonical_json).await)
|
json.try_into().expect("Can't canonicalize the json value");
|
||||||
|
Ok(self.sign(&canonical_json.to_string()).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sign(&self, message: &str) -> Signature {
|
pub async fn sign(&self, message: &str) -> Signature {
|
||||||
|
|
|
@ -14,8 +14,12 @@
|
||||||
|
|
||||||
use olm_rs::utility::OlmUtility;
|
use olm_rs::utility::OlmUtility;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use matrix_sdk_common::identifiers::{DeviceKeyAlgorithm, DeviceKeyId, UserId};
|
use matrix_sdk_common::{
|
||||||
|
identifiers::{DeviceKeyAlgorithm, DeviceKeyId, UserId},
|
||||||
|
CanonicalJsonValue,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::error::SignatureError;
|
use crate::error::SignatureError;
|
||||||
|
|
||||||
|
@ -63,11 +67,12 @@ impl Utility {
|
||||||
let unsigned = json_object.remove("unsigned");
|
let unsigned = json_object.remove("unsigned");
|
||||||
let signatures = json_object.remove("signatures");
|
let signatures = json_object.remove("signatures");
|
||||||
|
|
||||||
let canonical_json = serde_json::to_string(json_object)?;
|
let canonical_json: CanonicalJsonValue = json
|
||||||
|
.clone()
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| SignatureError::NotAnObject)?;
|
||||||
|
|
||||||
if let Some(u) = unsigned {
|
let canonical_json: String = canonical_json.to_string();
|
||||||
json_object.insert("unsigned".to_string(), u);
|
|
||||||
}
|
|
||||||
|
|
||||||
let signatures = signatures.ok_or(SignatureError::NoSignatureFound)?;
|
let signatures = signatures.ok_or(SignatureError::NoSignatureFound)?;
|
||||||
let signature_object = signatures
|
let signature_object = signatures
|
||||||
|
@ -81,18 +86,66 @@ impl Utility {
|
||||||
.ok_or(SignatureError::NoSignatureFound)?;
|
.ok_or(SignatureError::NoSignatureFound)?;
|
||||||
let signature = signature.as_str().ok_or(SignatureError::NoSignatureFound)?;
|
let signature = signature.as_str().ok_or(SignatureError::NoSignatureFound)?;
|
||||||
|
|
||||||
let ret = if self
|
let ret = match self
|
||||||
.inner
|
.inner
|
||||||
.ed25519_verify(signing_key, &canonical_json, signature)
|
.ed25519_verify(signing_key, &canonical_json, signature)
|
||||||
.is_ok()
|
|
||||||
{
|
{
|
||||||
Ok(())
|
Ok(_) => Ok(()),
|
||||||
} else {
|
Err(_) => Err(SignatureError::VerificationError),
|
||||||
Err(SignatureError::VerificationError)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?;
|
||||||
|
|
||||||
|
if let Some(u) = unsigned {
|
||||||
|
json_object.insert("unsigned".to_string(), u);
|
||||||
|
}
|
||||||
|
|
||||||
json_object.insert("signatures".to_string(), signatures);
|
json_object.insert("signatures".to_string(), signatures);
|
||||||
|
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::Utility;
|
||||||
|
use matrix_sdk_common::identifiers::{user_id, DeviceKeyAlgorithm, DeviceKeyId};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signature_test() {
|
||||||
|
let mut device_keys = json!({
|
||||||
|
"device_id": "GBEWHQOYGS",
|
||||||
|
"algorithms": [
|
||||||
|
"m.olm.v1.curve25519-aes-sha2",
|
||||||
|
"m.megolm.v1.aes-sha2"
|
||||||
|
],
|
||||||
|
"keys": {
|
||||||
|
"curve25519:GBEWHQOYGS": "F8QhZ0Z1rjtWrQOblMDgZtEX5x1UrG7sZ2Kk3xliNAU",
|
||||||
|
"ed25519:GBEWHQOYGS": "n469gw7zm+KW+JsFIJKnFVvCKU14HwQyocggcCIQgZY"
|
||||||
|
},
|
||||||
|
"signatures": {
|
||||||
|
"@example:localhost": {
|
||||||
|
"ed25519:GBEWHQOYGS": "OlF2REsqjYdAfr04ONx8VS/5cB7KjrWYRlLF4eUm2foAiQL/RAfsjsa2JXZeoOHh6vEualZHbWlod49OewVqBg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unsigned": {
|
||||||
|
"device_display_name": "Weechat-Matrix-rs"
|
||||||
|
},
|
||||||
|
"user_id": "@example:localhost"
|
||||||
|
});
|
||||||
|
|
||||||
|
let signing_key = "n469gw7zm+KW+JsFIJKnFVvCKU14HwQyocggcCIQgZY";
|
||||||
|
|
||||||
|
let utility = Utility::new();
|
||||||
|
|
||||||
|
utility
|
||||||
|
.verify_json(
|
||||||
|
&user_id!("@example:localhost"),
|
||||||
|
&DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, "GBEWHQOYGS".into()),
|
||||||
|
&signing_key,
|
||||||
|
&mut device_keys,
|
||||||
|
)
|
||||||
|
.expect("Can't verify device keys");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use std::{collections::BTreeMap, sync::Arc, time::Duration};
|
use std::{collections::BTreeMap, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::{
|
||||||
|
@ -26,10 +28,11 @@ use matrix_sdk_common::{
|
||||||
upload_signing_keys::Response as SigningKeysUploadResponse,
|
upload_signing_keys::Response as SigningKeysUploadResponse,
|
||||||
CrossSigningKey,
|
CrossSigningKey,
|
||||||
},
|
},
|
||||||
|
message::send_message_event::Response as RoomMessageResponse,
|
||||||
to_device::{send_event_to_device::Response as ToDeviceResponse, DeviceIdOrAllDevices},
|
to_device::{send_event_to_device::Response as ToDeviceResponse, DeviceIdOrAllDevices},
|
||||||
},
|
},
|
||||||
events::EventType,
|
events::{AnyMessageEventContent, EventType},
|
||||||
identifiers::{DeviceIdBox, UserId},
|
identifiers::{DeviceIdBox, RoomId, UserId},
|
||||||
uuid::Uuid,
|
uuid::Uuid,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -120,6 +123,7 @@ pub enum OutgoingRequests {
|
||||||
/// Signature upload request, this request is used after a successful device
|
/// Signature upload request, this request is used after a successful device
|
||||||
/// or user verification is done.
|
/// or user verification is done.
|
||||||
SignatureUpload(SignatureUploadRequest),
|
SignatureUpload(SignatureUploadRequest),
|
||||||
|
RoomMessage(RoomMessageRequest),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -150,12 +154,36 @@ impl From<ToDeviceRequest> for OutgoingRequests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<RoomMessageRequest> for OutgoingRequests {
|
||||||
|
fn from(request: RoomMessageRequest) -> Self {
|
||||||
|
OutgoingRequests::RoomMessage(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<SignatureUploadRequest> for OutgoingRequests {
|
impl From<SignatureUploadRequest> for OutgoingRequests {
|
||||||
fn from(request: SignatureUploadRequest) -> Self {
|
fn from(request: SignatureUploadRequest) -> Self {
|
||||||
OutgoingRequests::SignatureUpload(request)
|
OutgoingRequests::SignatureUpload(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<OutgoingVerificationRequest> for OutgoingRequest {
|
||||||
|
fn from(r: OutgoingVerificationRequest) -> Self {
|
||||||
|
Self {
|
||||||
|
request_id: r.request_id(),
|
||||||
|
request: Arc::new(r.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SignatureUploadRequest> for OutgoingRequest {
|
||||||
|
fn from(r: SignatureUploadRequest) -> Self {
|
||||||
|
Self {
|
||||||
|
request_id: Uuid::new_v4(),
|
||||||
|
request: Arc::new(r.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Enum over all the incoming responses we need to receive.
|
/// Enum over all the incoming responses we need to receive.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum IncomingResponse<'a> {
|
pub enum IncomingResponse<'a> {
|
||||||
|
@ -176,6 +204,7 @@ pub enum IncomingResponse<'a> {
|
||||||
/// The cross signing keys upload response, marking our private cross
|
/// The cross signing keys upload response, marking our private cross
|
||||||
/// signing identity as shared.
|
/// signing identity as shared.
|
||||||
SignatureUpload(&'a SignatureUploadResponse),
|
SignatureUpload(&'a SignatureUploadResponse),
|
||||||
|
RoomMessage(&'a RoomMessageResponse),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a KeysUploadResponse> for IncomingResponse<'a> {
|
impl<'a> From<&'a KeysUploadResponse> for IncomingResponse<'a> {
|
||||||
|
@ -196,6 +225,12 @@ impl<'a> From<&'a ToDeviceResponse> for IncomingResponse<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a RoomMessageResponse> for IncomingResponse<'a> {
|
||||||
|
fn from(response: &'a RoomMessageResponse) -> Self {
|
||||||
|
IncomingResponse::RoomMessage(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a KeysClaimResponse> for IncomingResponse<'a> {
|
impl<'a> From<&'a KeysClaimResponse> for IncomingResponse<'a> {
|
||||||
fn from(response: &'a KeysClaimResponse) -> Self {
|
fn from(response: &'a KeysClaimResponse) -> Self {
|
||||||
IncomingResponse::KeysClaim(response)
|
IncomingResponse::KeysClaim(response)
|
||||||
|
@ -230,3 +265,55 @@ impl OutgoingRequest {
|
||||||
&self.request
|
&self.request
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RoomMessageRequest {
|
||||||
|
/// The room to send the event to.
|
||||||
|
pub room_id: RoomId,
|
||||||
|
|
||||||
|
/// The transaction ID for this event.
|
||||||
|
///
|
||||||
|
/// Clients should generate an ID unique across requests with the
|
||||||
|
/// same access token; it will be used by the server to ensure
|
||||||
|
/// idempotency of requests.
|
||||||
|
pub txn_id: Uuid,
|
||||||
|
|
||||||
|
/// The event content to send.
|
||||||
|
pub content: AnyMessageEventContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum OutgoingVerificationRequest {
|
||||||
|
ToDevice(ToDeviceRequest),
|
||||||
|
InRoom(RoomMessageRequest),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutgoingVerificationRequest {
|
||||||
|
pub fn request_id(&self) -> Uuid {
|
||||||
|
match self {
|
||||||
|
OutgoingVerificationRequest::ToDevice(t) => t.txn_id,
|
||||||
|
OutgoingVerificationRequest::InRoom(r) => r.txn_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ToDeviceRequest> for OutgoingVerificationRequest {
|
||||||
|
fn from(r: ToDeviceRequest) -> Self {
|
||||||
|
OutgoingVerificationRequest::ToDevice(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RoomMessageRequest> for OutgoingVerificationRequest {
|
||||||
|
fn from(r: RoomMessageRequest) -> Self {
|
||||||
|
OutgoingVerificationRequest::InRoom(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<OutgoingVerificationRequest> for OutgoingRequests {
|
||||||
|
fn from(request: OutgoingVerificationRequest) -> Self {
|
||||||
|
match request {
|
||||||
|
OutgoingVerificationRequest::ToDevice(r) => OutgoingRequests::ToDeviceRequest(r),
|
||||||
|
OutgoingVerificationRequest::InRoom(r) => OutgoingRequests::RoomMessage(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,25 +12,32 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::{convert::TryFrom, sync::Arc};
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
|
|
||||||
use matrix_sdk_common::locks::Mutex;
|
use tracing::{info, trace, warn};
|
||||||
use tracing::{trace, warn};
|
|
||||||
|
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::{
|
||||||
events::{AnyToDeviceEvent, AnyToDeviceEventContent},
|
events::{
|
||||||
identifiers::{DeviceId, UserId},
|
room::message::MessageEventContent, AnyMessageEvent, AnySyncMessageEvent, AnySyncRoomEvent,
|
||||||
|
AnyToDeviceEvent,
|
||||||
|
},
|
||||||
|
identifiers::{DeviceId, EventId, RoomId, UserId},
|
||||||
|
locks::Mutex,
|
||||||
uuid::Uuid,
|
uuid::Uuid,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::sas::{content_to_request, Sas, VerificationResult};
|
use super::{
|
||||||
|
requests::VerificationRequest,
|
||||||
|
sas::{content_to_request, OutgoingContent, Sas, VerificationResult},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
olm::PrivateCrossSigningIdentity,
|
olm::PrivateCrossSigningIdentity,
|
||||||
requests::{OutgoingRequest, ToDeviceRequest},
|
requests::OutgoingRequest,
|
||||||
store::{CryptoStore, CryptoStoreError},
|
store::{CryptoStore, CryptoStoreError},
|
||||||
ReadOnlyAccount, ReadOnlyDevice,
|
OutgoingVerificationRequest, ReadOnlyAccount, ReadOnlyDevice, RoomMessageRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -39,7 +46,10 @@ pub struct VerificationMachine {
|
||||||
private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
|
private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
|
||||||
pub(crate) store: Arc<Box<dyn CryptoStore>>,
|
pub(crate) store: Arc<Box<dyn CryptoStore>>,
|
||||||
verifications: Arc<DashMap<String, Sas>>,
|
verifications: Arc<DashMap<String, Sas>>,
|
||||||
|
room_verifications: Arc<DashMap<EventId, Sas>>,
|
||||||
|
requests: Arc<DashMap<EventId, VerificationRequest>>,
|
||||||
outgoing_to_device_messages: Arc<DashMap<Uuid, OutgoingRequest>>,
|
outgoing_to_device_messages: Arc<DashMap<Uuid, OutgoingRequest>>,
|
||||||
|
outgoing_room_messages: Arc<DashMap<Uuid, OutgoingRequest>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VerificationMachine {
|
impl VerificationMachine {
|
||||||
|
@ -52,15 +62,18 @@ impl VerificationMachine {
|
||||||
account,
|
account,
|
||||||
private_identity: identity,
|
private_identity: identity,
|
||||||
store,
|
store,
|
||||||
verifications: Arc::new(DashMap::new()),
|
verifications: DashMap::new().into(),
|
||||||
outgoing_to_device_messages: Arc::new(DashMap::new()),
|
requests: DashMap::new().into(),
|
||||||
|
outgoing_to_device_messages: DashMap::new().into(),
|
||||||
|
room_verifications: DashMap::new().into(),
|
||||||
|
outgoing_room_messages: DashMap::new().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start_sas(
|
pub async fn start_sas(
|
||||||
&self,
|
&self,
|
||||||
device: ReadOnlyDevice,
|
device: ReadOnlyDevice,
|
||||||
) -> Result<(Sas, ToDeviceRequest), CryptoStoreError> {
|
) -> Result<(Sas, OutgoingVerificationRequest), CryptoStoreError> {
|
||||||
let identity = self.store.get_user_identity(device.user_id()).await?;
|
let identity = self.store.get_user_identity(device.user_id()).await?;
|
||||||
let private_identity = self.private_identity.lock().await.clone();
|
let private_identity = self.private_identity.lock().await.clone();
|
||||||
|
|
||||||
|
@ -72,30 +85,56 @@ impl VerificationMachine {
|
||||||
identity,
|
identity,
|
||||||
);
|
);
|
||||||
|
|
||||||
let request = content_to_request(
|
let request = match content.into() {
|
||||||
device.user_id(),
|
OutgoingContent::Room(r, c) => RoomMessageRequest {
|
||||||
device.device_id(),
|
room_id: r,
|
||||||
AnyToDeviceEventContent::KeyVerificationStart(content),
|
txn_id: Uuid::new_v4(),
|
||||||
);
|
content: c,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
OutgoingContent::ToDevice(c) => {
|
||||||
|
let request = content_to_request(device.user_id(), device.device_id(), c);
|
||||||
|
|
||||||
self.verifications
|
self.verifications
|
||||||
.insert(sas.flow_id().to_owned(), sas.clone());
|
.insert(sas.flow_id().as_str().to_owned(), sas.clone());
|
||||||
|
|
||||||
|
request.into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok((sas, request))
|
Ok((sas, request))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_request(&self, flow_id: &EventId) -> Option<VerificationRequest> {
|
||||||
|
#[allow(clippy::map_clone)]
|
||||||
|
self.requests.get(flow_id).map(|s| s.clone())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_sas(&self, transaction_id: &str) -> Option<Sas> {
|
pub fn get_sas(&self, transaction_id: &str) -> Option<Sas> {
|
||||||
|
let sas = if let Ok(e) = EventId::try_from(transaction_id) {
|
||||||
|
#[allow(clippy::map_clone)]
|
||||||
|
self.room_verifications.get(&e).map(|s| s.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if sas.is_some() {
|
||||||
|
sas
|
||||||
|
} else {
|
||||||
#[allow(clippy::map_clone)]
|
#[allow(clippy::map_clone)]
|
||||||
self.verifications.get(transaction_id).map(|s| s.clone())
|
self.verifications.get(transaction_id).map(|s| s.clone())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn queue_up_content(
|
fn queue_up_content(
|
||||||
&self,
|
&self,
|
||||||
recipient: &UserId,
|
recipient: &UserId,
|
||||||
recipient_device: &DeviceId,
|
recipient_device: &DeviceId,
|
||||||
content: AnyToDeviceEventContent,
|
content: OutgoingContent,
|
||||||
) {
|
) {
|
||||||
let request = content_to_request(recipient, recipient_device, content);
|
match content {
|
||||||
|
OutgoingContent::ToDevice(c) => {
|
||||||
|
let request = content_to_request(recipient, recipient_device, c);
|
||||||
let request_id = request.txn_id;
|
let request_id = request.txn_id;
|
||||||
|
|
||||||
let request = OutgoingRequest {
|
let request = OutgoingRequest {
|
||||||
|
@ -106,16 +145,51 @@ impl VerificationMachine {
|
||||||
self.outgoing_to_device_messages.insert(request_id, request);
|
self.outgoing_to_device_messages.insert(request_id, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn receive_event_helper(&self, sas: &Sas, event: &mut AnyToDeviceEvent) {
|
OutgoingContent::Room(r, c) => {
|
||||||
|
let request_id = Uuid::new_v4();
|
||||||
|
|
||||||
|
let request = OutgoingRequest {
|
||||||
|
request: Arc::new(
|
||||||
|
RoomMessageRequest {
|
||||||
|
room_id: r,
|
||||||
|
txn_id: request_id,
|
||||||
|
content: c,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
request_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.outgoing_room_messages.insert(request_id, request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn receive_room_event_helper(&self, sas: &Sas, event: &AnyMessageEvent) {
|
||||||
|
if let Some(c) = sas.receive_room_event(event) {
|
||||||
|
self.queue_up_content(sas.other_user_id(), sas.other_device_id(), c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_event_helper(&self, sas: &Sas, event: &AnyToDeviceEvent) {
|
||||||
if let Some(c) = sas.receive_event(event) {
|
if let Some(c) = sas.receive_event(event) {
|
||||||
self.queue_up_content(sas.other_user_id(), sas.other_device_id(), c);
|
self.queue_up_content(sas.other_user_id(), sas.other_device_id(), c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mark_request_as_sent(&self, uuid: &Uuid) {
|
pub fn mark_request_as_sent(&self, uuid: &Uuid) {
|
||||||
|
self.outgoing_room_messages.remove(uuid);
|
||||||
self.outgoing_to_device_messages.remove(uuid);
|
self.outgoing_to_device_messages.remove(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn outgoing_room_message_requests(&self) -> Vec<OutgoingRequest> {
|
||||||
|
self.outgoing_room_messages
|
||||||
|
.iter()
|
||||||
|
.map(|r| (*r).clone())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn outgoing_to_device_requests(&self) -> Vec<OutgoingRequest> {
|
pub fn outgoing_to_device_requests(&self) -> Vec<OutgoingRequest> {
|
||||||
#[allow(clippy::map_clone)]
|
#[allow(clippy::map_clone)]
|
||||||
self.outgoing_to_device_messages
|
self.outgoing_to_device_messages
|
||||||
|
@ -131,9 +205,9 @@ impl VerificationMachine {
|
||||||
for sas in self.verifications.iter() {
|
for sas in self.verifications.iter() {
|
||||||
if let Some(r) = sas.cancel_if_timed_out() {
|
if let Some(r) = sas.cancel_if_timed_out() {
|
||||||
self.outgoing_to_device_messages.insert(
|
self.outgoing_to_device_messages.insert(
|
||||||
r.txn_id,
|
r.request_id(),
|
||||||
OutgoingRequest {
|
OutgoingRequest {
|
||||||
request_id: r.txn_id,
|
request_id: r.request_id(),
|
||||||
request: Arc::new(r.into()),
|
request: Arc::new(r.into()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -141,10 +215,159 @@ impl VerificationMachine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn receive_event(
|
pub async fn receive_room_event(
|
||||||
&self,
|
&self,
|
||||||
event: &mut AnyToDeviceEvent,
|
room_id: &RoomId,
|
||||||
|
event: &AnySyncRoomEvent,
|
||||||
) -> Result<(), CryptoStoreError> {
|
) -> Result<(), CryptoStoreError> {
|
||||||
|
if let AnySyncRoomEvent::Message(m) = event {
|
||||||
|
// Since these are room events we will get events that we send out on
|
||||||
|
// our own as well.
|
||||||
|
if m.sender() == self.account.user_id() {
|
||||||
|
if let AnySyncMessageEvent::KeyVerificationReady(_e) = m {
|
||||||
|
// TODO if there is a verification request, go into passive
|
||||||
|
// mode since another device is handling this request.
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
match m {
|
||||||
|
AnySyncMessageEvent::RoomMessage(m) => {
|
||||||
|
if let MessageEventContent::VerificationRequest(r) = &m.content {
|
||||||
|
if self.account.user_id() == &r.to {
|
||||||
|
info!(
|
||||||
|
"Received a new verification request from {} {}",
|
||||||
|
m.sender, r.from_device
|
||||||
|
);
|
||||||
|
|
||||||
|
let request = VerificationRequest::from_request_event(
|
||||||
|
self.account.clone(),
|
||||||
|
self.private_identity.lock().await.clone(),
|
||||||
|
self.store.clone(),
|
||||||
|
room_id,
|
||||||
|
&m.sender,
|
||||||
|
&m.event_id,
|
||||||
|
r,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.requests.insert(m.event_id.clone(), request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnySyncMessageEvent::KeyVerificationReady(e) => {
|
||||||
|
if let Some(request) = self.requests.get(&e.content.relation.event_id) {
|
||||||
|
if &e.sender == request.other_user() {
|
||||||
|
// TODO remove this unwrap.
|
||||||
|
request.receive_ready(&e.sender, &e.content).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnySyncMessageEvent::KeyVerificationStart(e) => {
|
||||||
|
info!(
|
||||||
|
"Received a new verification start event from {} {}",
|
||||||
|
e.sender, e.content.from_device
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some((_, request)) = self.requests.remove(&e.content.relation.event_id) {
|
||||||
|
if let Some(d) = self
|
||||||
|
.store
|
||||||
|
.get_device(&e.sender, &e.content.from_device)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
match request.into_started_sas(
|
||||||
|
e,
|
||||||
|
d,
|
||||||
|
self.store.get_user_identity(&e.sender).await?,
|
||||||
|
) {
|
||||||
|
Ok(s) => {
|
||||||
|
info!(
|
||||||
|
"Started a new SAS verification, \
|
||||||
|
automatically accepting because of in-room"
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO remove this unwrap
|
||||||
|
let accept_request = s.accept().unwrap();
|
||||||
|
|
||||||
|
self.room_verifications
|
||||||
|
.insert(e.content.relation.event_id.clone(), s);
|
||||||
|
|
||||||
|
self.outgoing_room_messages
|
||||||
|
.insert(accept_request.request_id(), accept_request.into());
|
||||||
|
}
|
||||||
|
Err(c) => {
|
||||||
|
warn!(
|
||||||
|
"Can't start key verification with {} {}, canceling: {:?}",
|
||||||
|
e.sender, e.content.from_device, c
|
||||||
|
);
|
||||||
|
// self.queue_up_content(&e.sender, &e.content.from_device, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnySyncMessageEvent::KeyVerificationKey(e) => {
|
||||||
|
if let Some(s) = self.room_verifications.get(&e.content.relation.event_id) {
|
||||||
|
self.receive_room_event_helper(
|
||||||
|
&s,
|
||||||
|
&m.clone().into_full_event(room_id.clone()),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
AnySyncMessageEvent::KeyVerificationMac(e) => {
|
||||||
|
if let Some(s) = self.room_verifications.get(&e.content.relation.event_id) {
|
||||||
|
self.receive_room_event_helper(
|
||||||
|
&s,
|
||||||
|
&m.clone().into_full_event(room_id.clone()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnySyncMessageEvent::KeyVerificationDone(e) => {
|
||||||
|
if let Some(s) = self.room_verifications.get(&e.content.relation.event_id) {
|
||||||
|
let content =
|
||||||
|
s.receive_room_event(&m.clone().into_full_event(room_id.clone()));
|
||||||
|
|
||||||
|
if s.is_done() {
|
||||||
|
match s.mark_as_done().await? {
|
||||||
|
VerificationResult::Ok => {
|
||||||
|
if let Some(c) = content {
|
||||||
|
self.queue_up_content(
|
||||||
|
s.other_user_id(),
|
||||||
|
s.other_device_id(),
|
||||||
|
c,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VerificationResult::Cancel(r) => {
|
||||||
|
self.outgoing_to_device_messages
|
||||||
|
.insert(r.request_id(), r.into());
|
||||||
|
}
|
||||||
|
VerificationResult::SignatureUpload(r) => {
|
||||||
|
let request: OutgoingRequest = r.into();
|
||||||
|
|
||||||
|
self.outgoing_to_device_messages
|
||||||
|
.insert(request.request_id, request);
|
||||||
|
|
||||||
|
if let Some(c) = content {
|
||||||
|
self.queue_up_content(
|
||||||
|
s.other_user_id(),
|
||||||
|
s.other_device_id(),
|
||||||
|
c,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn receive_event(&self, event: &AnyToDeviceEvent) -> Result<(), CryptoStoreError> {
|
||||||
trace!("Received a key verification event {:?}", event);
|
trace!("Received a key verification event {:?}", event);
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
|
@ -166,7 +389,7 @@ impl VerificationMachine {
|
||||||
private_identity,
|
private_identity,
|
||||||
d,
|
d,
|
||||||
self.store.clone(),
|
self.store.clone(),
|
||||||
e,
|
e.content.clone(),
|
||||||
self.store.get_user_identity(&e.sender).await?,
|
self.store.get_user_identity(&e.sender).await?,
|
||||||
) {
|
) {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
|
@ -210,9 +433,9 @@ impl VerificationMachine {
|
||||||
VerificationResult::Ok => (),
|
VerificationResult::Ok => (),
|
||||||
VerificationResult::Cancel(r) => {
|
VerificationResult::Cancel(r) => {
|
||||||
self.outgoing_to_device_messages.insert(
|
self.outgoing_to_device_messages.insert(
|
||||||
r.txn_id,
|
r.request_id(),
|
||||||
OutgoingRequest {
|
OutgoingRequest {
|
||||||
request_id: r.txn_id,
|
request_id: r.request_id(),
|
||||||
request: Arc::new(r.into()),
|
request: Arc::new(r.into()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -248,7 +471,6 @@ mod test {
|
||||||
};
|
};
|
||||||
|
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::{
|
||||||
events::AnyToDeviceEventContent,
|
|
||||||
identifiers::{DeviceId, UserId},
|
identifiers::{DeviceId, UserId},
|
||||||
locks::Mutex,
|
locks::Mutex,
|
||||||
};
|
};
|
||||||
|
@ -300,10 +522,11 @@ mod test {
|
||||||
bob_store,
|
bob_store,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
machine
|
machine
|
||||||
.receive_event(&mut wrap_any_to_device_content(
|
.receive_event(&wrap_any_to_device_content(
|
||||||
bob_sas.user_id(),
|
bob_sas.user_id(),
|
||||||
AnyToDeviceEventContent::KeyVerificationStart(start_content),
|
start_content.into(),
|
||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -323,20 +546,20 @@ mod test {
|
||||||
async fn full_flow() {
|
async fn full_flow() {
|
||||||
let (alice_machine, bob) = setup_verification_machine().await;
|
let (alice_machine, bob) = setup_verification_machine().await;
|
||||||
|
|
||||||
let alice = alice_machine.get_sas(bob.flow_id()).unwrap();
|
let alice = alice_machine.get_sas(bob.flow_id().as_str()).unwrap();
|
||||||
|
|
||||||
let mut event = alice
|
let event = alice
|
||||||
.accept()
|
.accept()
|
||||||
.map(|c| wrap_any_to_device_content(alice.user_id(), get_content_from_request(&c)))
|
.map(|c| wrap_any_to_device_content(alice.user_id(), get_content_from_request(&c)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut event = bob
|
let event = bob
|
||||||
.receive_event(&mut event)
|
.receive_event(&event)
|
||||||
.map(|c| wrap_any_to_device_content(bob.user_id(), c))
|
.map(|c| wrap_any_to_device_content(bob.user_id(), c))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(alice_machine.outgoing_to_device_messages.is_empty());
|
assert!(alice_machine.outgoing_to_device_messages.is_empty());
|
||||||
alice_machine.receive_event(&mut event).await.unwrap();
|
alice_machine.receive_event(&event).await.unwrap();
|
||||||
assert!(!alice_machine.outgoing_to_device_messages.is_empty());
|
assert!(!alice_machine.outgoing_to_device_messages.is_empty());
|
||||||
|
|
||||||
let request = alice_machine
|
let request = alice_machine
|
||||||
|
@ -348,33 +571,34 @@ mod test {
|
||||||
let txn_id = *request.request_id();
|
let txn_id = *request.request_id();
|
||||||
|
|
||||||
let r = if let OutgoingRequests::ToDeviceRequest(r) = request.request() {
|
let r = if let OutgoingRequests::ToDeviceRequest(r) = request.request() {
|
||||||
r
|
r.clone()
|
||||||
} else {
|
} else {
|
||||||
panic!("Invalid request type");
|
panic!("Invalid request type");
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut event = wrap_any_to_device_content(alice.user_id(), get_content_from_request(r));
|
let event =
|
||||||
|
wrap_any_to_device_content(alice.user_id(), get_content_from_request(&r.into()));
|
||||||
drop(request);
|
drop(request);
|
||||||
alice_machine.mark_request_as_sent(&txn_id);
|
alice_machine.mark_request_as_sent(&txn_id);
|
||||||
|
|
||||||
assert!(bob.receive_event(&mut event).is_none());
|
assert!(bob.receive_event(&event).is_none());
|
||||||
|
|
||||||
assert!(alice.emoji().is_some());
|
assert!(alice.emoji().is_some());
|
||||||
assert!(bob.emoji().is_some());
|
assert!(bob.emoji().is_some());
|
||||||
|
|
||||||
assert_eq!(alice.emoji(), bob.emoji());
|
assert_eq!(alice.emoji(), bob.emoji());
|
||||||
|
|
||||||
let mut event = wrap_any_to_device_content(
|
let event = wrap_any_to_device_content(
|
||||||
alice.user_id(),
|
alice.user_id(),
|
||||||
get_content_from_request(&alice.confirm().await.unwrap().0.unwrap()),
|
get_content_from_request(&alice.confirm().await.unwrap().0.unwrap()),
|
||||||
);
|
);
|
||||||
bob.receive_event(&mut event);
|
bob.receive_event(&event);
|
||||||
|
|
||||||
let mut event = wrap_any_to_device_content(
|
let event = wrap_any_to_device_content(
|
||||||
bob.user_id(),
|
bob.user_id(),
|
||||||
get_content_from_request(&bob.confirm().await.unwrap().0.unwrap()),
|
get_content_from_request(&bob.confirm().await.unwrap().0.unwrap()),
|
||||||
);
|
);
|
||||||
alice.receive_event(&mut event);
|
alice.receive_event(&event);
|
||||||
|
|
||||||
assert!(alice.is_done());
|
assert!(alice.is_done());
|
||||||
assert!(bob.is_done());
|
assert!(bob.is_done());
|
||||||
|
@ -384,7 +608,7 @@ mod test {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn timing_out() {
|
async fn timing_out() {
|
||||||
let (alice_machine, bob) = setup_verification_machine().await;
|
let (alice_machine, bob) = setup_verification_machine().await;
|
||||||
let alice = alice_machine.get_sas(bob.flow_id()).unwrap();
|
let alice = alice_machine.get_sas(bob.flow_id().as_str()).unwrap();
|
||||||
|
|
||||||
assert!(!alice.timed_out());
|
assert!(!alice.timed_out());
|
||||||
assert!(alice_machine.outgoing_to_device_messages.is_empty());
|
assert!(alice_machine.outgoing_to_device_messages.is_empty());
|
||||||
|
|
|
@ -13,14 +13,19 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
mod machine;
|
mod machine;
|
||||||
|
mod requests;
|
||||||
mod sas;
|
mod sas;
|
||||||
|
|
||||||
pub use machine::VerificationMachine;
|
pub use machine::VerificationMachine;
|
||||||
|
pub use requests::VerificationRequest;
|
||||||
pub use sas::{Sas, VerificationResult};
|
pub use sas::{Sas, VerificationResult};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test {
|
pub(crate) mod test {
|
||||||
use crate::requests::{OutgoingRequest, OutgoingRequests, ToDeviceRequest};
|
use crate::{
|
||||||
|
requests::{OutgoingRequest, OutgoingRequests},
|
||||||
|
OutgoingVerificationRequest,
|
||||||
|
};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::{
|
||||||
|
@ -28,7 +33,12 @@ pub(crate) mod test {
|
||||||
identifiers::UserId,
|
identifiers::UserId,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) fn request_to_event(sender: &UserId, request: &ToDeviceRequest) -> AnyToDeviceEvent {
|
use super::sas::OutgoingContent;
|
||||||
|
|
||||||
|
pub(crate) fn request_to_event(
|
||||||
|
sender: &UserId,
|
||||||
|
request: &OutgoingVerificationRequest,
|
||||||
|
) -> AnyToDeviceEvent {
|
||||||
let content = get_content_from_request(request);
|
let content = get_content_from_request(request);
|
||||||
wrap_any_to_device_content(sender, content)
|
wrap_any_to_device_content(sender, content)
|
||||||
}
|
}
|
||||||
|
@ -38,15 +48,21 @@ pub(crate) mod test {
|
||||||
request: &OutgoingRequest,
|
request: &OutgoingRequest,
|
||||||
) -> AnyToDeviceEvent {
|
) -> AnyToDeviceEvent {
|
||||||
match request.request() {
|
match request.request() {
|
||||||
OutgoingRequests::ToDeviceRequest(r) => request_to_event(sender, r),
|
OutgoingRequests::ToDeviceRequest(r) => request_to_event(sender, &r.clone().into()),
|
||||||
_ => panic!("Unsupported outgoing request"),
|
_ => panic!("Unsupported outgoing request"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn wrap_any_to_device_content(
|
pub(crate) fn wrap_any_to_device_content(
|
||||||
sender: &UserId,
|
sender: &UserId,
|
||||||
content: AnyToDeviceEventContent,
|
content: OutgoingContent,
|
||||||
) -> AnyToDeviceEvent {
|
) -> AnyToDeviceEvent {
|
||||||
|
let content = if let OutgoingContent::ToDevice(c) = content {
|
||||||
|
c
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
match content {
|
match content {
|
||||||
AnyToDeviceEventContent::KeyVerificationKey(c) => {
|
AnyToDeviceEventContent::KeyVerificationKey(c) => {
|
||||||
AnyToDeviceEvent::KeyVerificationKey(ToDeviceEvent {
|
AnyToDeviceEvent::KeyVerificationKey(ToDeviceEvent {
|
||||||
|
@ -77,7 +93,15 @@ pub(crate) mod test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_content_from_request(request: &ToDeviceRequest) -> AnyToDeviceEventContent {
|
pub(crate) fn get_content_from_request(
|
||||||
|
request: &OutgoingVerificationRequest,
|
||||||
|
) -> OutgoingContent {
|
||||||
|
let request = if let OutgoingVerificationRequest::ToDevice(r) = request {
|
||||||
|
r
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
let json: Value = serde_json::from_str(
|
let json: Value = serde_json::from_str(
|
||||||
request
|
request
|
||||||
.messages
|
.messages
|
||||||
|
@ -109,5 +133,6 @@ pub(crate) mod test {
|
||||||
),
|
),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,500 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use matrix_sdk_common::{
|
||||||
|
api::r0::message::send_message_event::Response as RoomMessageResponse,
|
||||||
|
events::{
|
||||||
|
key::verification::{
|
||||||
|
ready::ReadyEventContent, start::StartEventContent, Relation, VerificationMethod,
|
||||||
|
},
|
||||||
|
room::message::KeyVerificationRequestEventContent,
|
||||||
|
MessageEvent, SyncMessageEvent,
|
||||||
|
},
|
||||||
|
identifiers::{DeviceId, DeviceIdBox, EventId, RoomId, UserId},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
olm::{PrivateCrossSigningIdentity, ReadOnlyAccount},
|
||||||
|
store::CryptoStore,
|
||||||
|
ReadOnlyDevice, Sas, UserIdentities,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::sas::OutgoingContent;
|
||||||
|
|
||||||
|
const SUPPORTED_METHODS: &[VerificationMethod] = &[VerificationMethod::MSasV1];
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// TODO
|
||||||
|
pub struct VerificationRequest {
|
||||||
|
inner: Arc<Mutex<InnerRequest>>,
|
||||||
|
account: ReadOnlyAccount,
|
||||||
|
other_user_id: Arc<UserId>,
|
||||||
|
private_cross_signing_identity: PrivateCrossSigningIdentity,
|
||||||
|
store: Arc<Box<dyn CryptoStore>>,
|
||||||
|
room_id: Arc<RoomId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VerificationRequest {
|
||||||
|
/// TODO
|
||||||
|
pub fn new(
|
||||||
|
account: ReadOnlyAccount,
|
||||||
|
private_cross_signing_identity: PrivateCrossSigningIdentity,
|
||||||
|
store: Arc<Box<dyn CryptoStore>>,
|
||||||
|
room_id: Arc<RoomId>,
|
||||||
|
other_user: &UserId,
|
||||||
|
) -> Self {
|
||||||
|
let inner = Mutex::new(InnerRequest::Created(RequestState::new(
|
||||||
|
account.user_id(),
|
||||||
|
account.device_id(),
|
||||||
|
other_user,
|
||||||
|
)))
|
||||||
|
.into();
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
account,
|
||||||
|
private_cross_signing_identity,
|
||||||
|
store,
|
||||||
|
other_user_id: other_user.clone().into(),
|
||||||
|
room_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO
|
||||||
|
pub fn request(&self) -> Option<KeyVerificationRequestEventContent> {
|
||||||
|
match &*self.inner.lock().unwrap() {
|
||||||
|
InnerRequest::Created(c) => Some(c.as_content()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The id of the other user that is participating in this verification
|
||||||
|
/// request.
|
||||||
|
pub fn other_user(&self) -> &UserId {
|
||||||
|
&self.other_user_id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark the request as sent.
|
||||||
|
pub fn mark_as_sent(&self, response: &RoomMessageResponse) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
|
||||||
|
if let InnerRequest::Created(c) = &*inner {
|
||||||
|
*inner = InnerRequest::Sent(c.clone().into_sent(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_request_event(
|
||||||
|
account: ReadOnlyAccount,
|
||||||
|
private_cross_signing_identity: PrivateCrossSigningIdentity,
|
||||||
|
store: Arc<Box<dyn CryptoStore>>,
|
||||||
|
room_id: &RoomId,
|
||||||
|
sender: &UserId,
|
||||||
|
event_id: &EventId,
|
||||||
|
content: &KeyVerificationRequestEventContent,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(Mutex::new(InnerRequest::Requested(
|
||||||
|
RequestState::from_request_event(
|
||||||
|
account.user_id(),
|
||||||
|
account.device_id(),
|
||||||
|
sender,
|
||||||
|
event_id,
|
||||||
|
content,
|
||||||
|
),
|
||||||
|
))),
|
||||||
|
account,
|
||||||
|
other_user_id: sender.clone().into(),
|
||||||
|
private_cross_signing_identity,
|
||||||
|
store,
|
||||||
|
room_id: room_id.clone().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The room id where the verification is happening.
|
||||||
|
pub fn room_id(&self) -> &RoomId {
|
||||||
|
&self.room_id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accept the verification request.
|
||||||
|
pub fn accept(&self) -> Option<ReadyEventContent> {
|
||||||
|
self.inner.lock().unwrap().accept()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn receive_ready(
|
||||||
|
&self,
|
||||||
|
sender: &UserId,
|
||||||
|
content: &ReadyEventContent,
|
||||||
|
) -> Result<(), ()> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
|
||||||
|
if let InnerRequest::Sent(s) = &*inner {
|
||||||
|
*inner = InnerRequest::Ready(s.clone().into_ready(sender, content));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is the verification request ready to start a verification flow.
|
||||||
|
pub fn is_ready(&self) -> bool {
|
||||||
|
matches!(&*self.inner.lock().unwrap(), InnerRequest::Ready(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn into_started_sas(
|
||||||
|
self,
|
||||||
|
event: &SyncMessageEvent<StartEventContent>,
|
||||||
|
device: ReadOnlyDevice,
|
||||||
|
user_identity: Option<UserIdentities>,
|
||||||
|
) -> Result<Sas, OutgoingContent> {
|
||||||
|
match &*self.inner.lock().unwrap() {
|
||||||
|
InnerRequest::Ready(s) => s.clone().into_started_sas(
|
||||||
|
&event.clone().into_full_event(self.room_id().clone()),
|
||||||
|
self.store.clone(),
|
||||||
|
self.account.clone(),
|
||||||
|
self.private_cross_signing_identity.clone(),
|
||||||
|
device,
|
||||||
|
user_identity,
|
||||||
|
),
|
||||||
|
// TODO cancel here since we got a missmatched message or do
|
||||||
|
// nothing?
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum InnerRequest {
|
||||||
|
Created(RequestState<Created>),
|
||||||
|
Sent(RequestState<Sent>),
|
||||||
|
Requested(RequestState<Requested>),
|
||||||
|
Ready(RequestState<Ready>),
|
||||||
|
Passive(RequestState<Passive>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InnerRequest {
|
||||||
|
fn accept(&mut self) -> Option<ReadyEventContent> {
|
||||||
|
if let InnerRequest::Requested(s) = self {
|
||||||
|
let (state, content) = s.clone().accept();
|
||||||
|
*self = InnerRequest::Ready(state);
|
||||||
|
|
||||||
|
Some(content)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_started_sas(
|
||||||
|
self,
|
||||||
|
event: &MessageEvent<StartEventContent>,
|
||||||
|
store: Arc<Box<dyn CryptoStore>>,
|
||||||
|
account: ReadOnlyAccount,
|
||||||
|
private_identity: PrivateCrossSigningIdentity,
|
||||||
|
other_device: ReadOnlyDevice,
|
||||||
|
other_identity: Option<UserIdentities>,
|
||||||
|
) -> Result<Option<Sas>, OutgoingContent> {
|
||||||
|
if let InnerRequest::Ready(s) = self {
|
||||||
|
Ok(Some(s.into_started_sas(
|
||||||
|
event,
|
||||||
|
store,
|
||||||
|
account,
|
||||||
|
private_identity,
|
||||||
|
other_device,
|
||||||
|
other_identity,
|
||||||
|
)?))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct RequestState<S: Clone> {
|
||||||
|
/// Our own user id.
|
||||||
|
pub own_user_id: UserId,
|
||||||
|
|
||||||
|
/// Our own device id.
|
||||||
|
pub own_device_id: DeviceIdBox,
|
||||||
|
|
||||||
|
/// The id of the user which is participating in this verification request.
|
||||||
|
pub other_user_id: UserId,
|
||||||
|
|
||||||
|
/// The verification request state we are in.
|
||||||
|
state: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Created {}
|
||||||
|
|
||||||
|
impl RequestState<Created> {
|
||||||
|
fn new(own_user_id: &UserId, own_device_id: &DeviceId, other_user: &UserId) -> Self {
|
||||||
|
Self {
|
||||||
|
own_user_id: own_user_id.clone(),
|
||||||
|
own_device_id: own_device_id.into(),
|
||||||
|
other_user_id: other_user.clone(),
|
||||||
|
state: Created {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_content(&self) -> KeyVerificationRequestEventContent {
|
||||||
|
KeyVerificationRequestEventContent {
|
||||||
|
body: format!(
|
||||||
|
"{} is requesting to verify your key, but your client does not \
|
||||||
|
support in-chat key verification. You will need to use legacy \
|
||||||
|
key verification to verify keys.",
|
||||||
|
self.own_user_id
|
||||||
|
),
|
||||||
|
methods: SUPPORTED_METHODS.to_vec(),
|
||||||
|
from_device: self.own_device_id.clone(),
|
||||||
|
to: self.other_user_id.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_sent(self, response: &RoomMessageResponse) -> RequestState<Sent> {
|
||||||
|
RequestState {
|
||||||
|
own_user_id: self.own_user_id,
|
||||||
|
own_device_id: self.own_device_id,
|
||||||
|
other_user_id: self.other_user_id,
|
||||||
|
state: Sent {
|
||||||
|
methods: SUPPORTED_METHODS.to_vec(),
|
||||||
|
flow_id: response.event_id.clone(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Sent {
|
||||||
|
/// The verification methods supported by the sender.
|
||||||
|
pub methods: Vec<VerificationMethod>,
|
||||||
|
|
||||||
|
/// The event id of our `m.key.verification.request` event which acts as an
|
||||||
|
/// unique id identifying this verification flow.
|
||||||
|
pub flow_id: EventId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestState<Sent> {
|
||||||
|
fn into_ready(self, _sender: &UserId, content: &ReadyEventContent) -> RequestState<Ready> {
|
||||||
|
// TODO check the flow id, and that the methods match what we suggested.
|
||||||
|
RequestState {
|
||||||
|
own_user_id: self.own_user_id,
|
||||||
|
own_device_id: self.own_device_id,
|
||||||
|
other_user_id: self.other_user_id,
|
||||||
|
state: Ready {
|
||||||
|
methods: content.methods.to_owned(),
|
||||||
|
other_device_id: content.from_device.clone(),
|
||||||
|
flow_id: self.state.flow_id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Requested {
|
||||||
|
/// The verification methods supported by the sender.
|
||||||
|
pub methods: Vec<VerificationMethod>,
|
||||||
|
|
||||||
|
/// The event id of the `m.key.verification.request` event which acts as an
|
||||||
|
/// unique id identifying this verification flow.
|
||||||
|
pub flow_id: EventId,
|
||||||
|
|
||||||
|
/// The device id of the device that responded to the verification request.
|
||||||
|
pub other_device_id: DeviceIdBox,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestState<Requested> {
|
||||||
|
fn from_request_event(
|
||||||
|
own_user_id: &UserId,
|
||||||
|
own_device_id: &DeviceId,
|
||||||
|
sender: &UserId,
|
||||||
|
event_id: &EventId,
|
||||||
|
content: &KeyVerificationRequestEventContent,
|
||||||
|
) -> RequestState<Requested> {
|
||||||
|
// TODO only create this if we suport the methods
|
||||||
|
RequestState {
|
||||||
|
own_user_id: own_user_id.clone(),
|
||||||
|
own_device_id: own_device_id.into(),
|
||||||
|
other_user_id: sender.clone(),
|
||||||
|
state: Requested {
|
||||||
|
methods: content.methods.clone(),
|
||||||
|
flow_id: event_id.clone(),
|
||||||
|
other_device_id: content.from_device.clone(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accept(self) -> (RequestState<Ready>, ReadyEventContent) {
|
||||||
|
let state = RequestState {
|
||||||
|
own_user_id: self.own_user_id,
|
||||||
|
own_device_id: self.own_device_id.clone(),
|
||||||
|
other_user_id: self.other_user_id,
|
||||||
|
state: Ready {
|
||||||
|
methods: self.state.methods.clone(),
|
||||||
|
other_device_id: self.state.other_device_id.clone(),
|
||||||
|
flow_id: self.state.flow_id.clone(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = ReadyEventContent {
|
||||||
|
from_device: self.own_device_id,
|
||||||
|
methods: self.state.methods,
|
||||||
|
relation: Relation {
|
||||||
|
event_id: self.state.flow_id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
(state, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Ready {
|
||||||
|
/// The verification methods supported by the sender.
|
||||||
|
pub methods: Vec<VerificationMethod>,
|
||||||
|
|
||||||
|
/// The device id of the device that responded to the verification request.
|
||||||
|
pub other_device_id: DeviceIdBox,
|
||||||
|
|
||||||
|
/// The event id of the `m.key.verification.request` event which acts as an
|
||||||
|
/// unique id identifying this verification flow.
|
||||||
|
pub flow_id: EventId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestState<Ready> {
|
||||||
|
fn into_started_sas(
|
||||||
|
self,
|
||||||
|
event: &MessageEvent<StartEventContent>,
|
||||||
|
store: Arc<Box<dyn CryptoStore>>,
|
||||||
|
account: ReadOnlyAccount,
|
||||||
|
private_identity: PrivateCrossSigningIdentity,
|
||||||
|
other_device: ReadOnlyDevice,
|
||||||
|
other_identity: Option<UserIdentities>,
|
||||||
|
) -> Result<Sas, OutgoingContent> {
|
||||||
|
Sas::from_start_event(
|
||||||
|
account,
|
||||||
|
private_identity,
|
||||||
|
other_device,
|
||||||
|
store,
|
||||||
|
(event.room_id.clone(), event.content.clone()),
|
||||||
|
other_identity,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_sas(
|
||||||
|
self,
|
||||||
|
_store: Arc<Box<dyn CryptoStore>>,
|
||||||
|
_account: ReadOnlyAccount,
|
||||||
|
_private_identity: PrivateCrossSigningIdentity,
|
||||||
|
_other_device: ReadOnlyDevice,
|
||||||
|
_other_identity: Option<UserIdentities>,
|
||||||
|
) -> (Sas, OutgoingContent) {
|
||||||
|
todo!()
|
||||||
|
// Sas::start_in_room(
|
||||||
|
// account,
|
||||||
|
// private_identity,
|
||||||
|
// other_device,
|
||||||
|
// store,
|
||||||
|
// other_identity,
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Passive {
|
||||||
|
/// The device id of the device that responded to the verification request.
|
||||||
|
pub other_device_id: DeviceIdBox,
|
||||||
|
|
||||||
|
/// The event id of the `m.key.verification.request` event which acts as an
|
||||||
|
/// unique id identifying this verification flow.
|
||||||
|
pub flow_id: EventId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use matrix_sdk_common::{
|
||||||
|
api::r0::message::send_message_event::Response as RoomMessageResponse,
|
||||||
|
identifiers::{event_id, room_id, DeviceIdBox, UserId},
|
||||||
|
};
|
||||||
|
use matrix_sdk_test::async_test;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
olm::{PrivateCrossSigningIdentity, ReadOnlyAccount},
|
||||||
|
store::{CryptoStore, MemoryStore},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::VerificationRequest;
|
||||||
|
|
||||||
|
fn alice_id() -> UserId {
|
||||||
|
UserId::try_from("@alice:example.org").unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alice_device_id() -> DeviceIdBox {
|
||||||
|
"JLAFKJWSCS".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bob_id() -> UserId {
|
||||||
|
UserId::try_from("@bob:example.org").unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bob_device_id() -> DeviceIdBox {
|
||||||
|
"BOBDEVCIE".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn test_request_accepting() {
|
||||||
|
let event_id = event_id!("$1234localhost");
|
||||||
|
let room_id = room_id!("!test:localhost");
|
||||||
|
|
||||||
|
let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id());
|
||||||
|
let alice_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new());
|
||||||
|
let alice_identity = PrivateCrossSigningIdentity::empty(alice_id());
|
||||||
|
|
||||||
|
let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id());
|
||||||
|
let bob_store: Box<dyn CryptoStore> = Box::new(MemoryStore::new());
|
||||||
|
let bob_identity = PrivateCrossSigningIdentity::empty(alice_id());
|
||||||
|
|
||||||
|
let bob_request = VerificationRequest::new(
|
||||||
|
bob,
|
||||||
|
bob_identity,
|
||||||
|
bob_store.into(),
|
||||||
|
room_id.clone().into(),
|
||||||
|
&alice_id(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let content = bob_request.request().unwrap();
|
||||||
|
|
||||||
|
let alice_request = VerificationRequest::from_request_event(
|
||||||
|
alice,
|
||||||
|
alice_identity,
|
||||||
|
alice_store.into(),
|
||||||
|
&room_id,
|
||||||
|
&bob_id(),
|
||||||
|
&event_id,
|
||||||
|
&content,
|
||||||
|
);
|
||||||
|
|
||||||
|
let content = alice_request.accept().unwrap();
|
||||||
|
|
||||||
|
let response = RoomMessageResponse::new(event_id);
|
||||||
|
bob_request.mark_as_sent(&response);
|
||||||
|
|
||||||
|
bob_request.receive_ready(&alice_id(), &content).unwrap();
|
||||||
|
|
||||||
|
assert!(bob_request.is_ready());
|
||||||
|
assert!(alice_request.is_ready());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,278 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use std::{collections::BTreeMap, convert::TryInto};
|
||||||
|
|
||||||
|
use matrix_sdk_common::{
|
||||||
|
events::{
|
||||||
|
key::verification::{
|
||||||
|
accept::{AcceptEventContent, AcceptMethod, AcceptToDeviceEventContent},
|
||||||
|
cancel::{CancelEventContent, CancelToDeviceEventContent},
|
||||||
|
done::DoneEventContent,
|
||||||
|
key::{KeyEventContent, KeyToDeviceEventContent},
|
||||||
|
mac::{MacEventContent, MacToDeviceEventContent},
|
||||||
|
start::{StartEventContent, StartMethod, StartToDeviceEventContent},
|
||||||
|
},
|
||||||
|
AnyMessageEventContent, AnyToDeviceEventContent,
|
||||||
|
},
|
||||||
|
identifiers::RoomId,
|
||||||
|
CanonicalJsonValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::FlowId;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum StartContent {
|
||||||
|
ToDevice(StartToDeviceEventContent),
|
||||||
|
Room(RoomId, StartEventContent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StartContent {
|
||||||
|
pub fn method(&self) -> &StartMethod {
|
||||||
|
match self {
|
||||||
|
StartContent::ToDevice(c) => &c.method,
|
||||||
|
StartContent::Room(_, c) => &c.method,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flow_id(&self) -> FlowId {
|
||||||
|
match self {
|
||||||
|
StartContent::ToDevice(c) => FlowId::ToDevice(c.transaction_id.clone()),
|
||||||
|
StartContent::Room(r, c) => FlowId::InRoom(r.clone(), c.relation.event_id.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn canonical_json(self) -> CanonicalJsonValue {
|
||||||
|
let content = match self {
|
||||||
|
StartContent::ToDevice(c) => serde_json::to_value(c),
|
||||||
|
StartContent::Room(_, c) => serde_json::to_value(c),
|
||||||
|
};
|
||||||
|
|
||||||
|
content
|
||||||
|
.expect("Can't serialize content")
|
||||||
|
.try_into()
|
||||||
|
.expect("Can't canonicalize content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(RoomId, StartEventContent)> for StartContent {
|
||||||
|
fn from(tuple: (RoomId, StartEventContent)) -> Self {
|
||||||
|
StartContent::Room(tuple.0, tuple.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StartToDeviceEventContent> for StartContent {
|
||||||
|
fn from(content: StartToDeviceEventContent) -> Self {
|
||||||
|
StartContent::ToDevice(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum AcceptContent {
|
||||||
|
ToDevice(AcceptToDeviceEventContent),
|
||||||
|
Room(RoomId, AcceptEventContent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AcceptContent {
|
||||||
|
pub fn flow_id(&self) -> FlowId {
|
||||||
|
match self {
|
||||||
|
AcceptContent::ToDevice(c) => FlowId::ToDevice(c.transaction_id.clone()),
|
||||||
|
AcceptContent::Room(r, c) => FlowId::InRoom(r.clone(), c.relation.event_id.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn method(&self) -> &AcceptMethod {
|
||||||
|
match self {
|
||||||
|
AcceptContent::ToDevice(c) => &c.method,
|
||||||
|
AcceptContent::Room(_, c) => &c.method,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AcceptToDeviceEventContent> for AcceptContent {
|
||||||
|
fn from(content: AcceptToDeviceEventContent) -> Self {
|
||||||
|
AcceptContent::ToDevice(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(RoomId, AcceptEventContent)> for AcceptContent {
|
||||||
|
fn from(content: (RoomId, AcceptEventContent)) -> Self {
|
||||||
|
AcceptContent::Room(content.0, content.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum KeyContent {
|
||||||
|
ToDevice(KeyToDeviceEventContent),
|
||||||
|
Room(RoomId, KeyEventContent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyContent {
|
||||||
|
pub fn flow_id(&self) -> FlowId {
|
||||||
|
match self {
|
||||||
|
KeyContent::ToDevice(c) => FlowId::ToDevice(c.transaction_id.clone()),
|
||||||
|
KeyContent::Room(r, c) => FlowId::InRoom(r.clone(), c.relation.event_id.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn public_key(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
KeyContent::ToDevice(c) => &c.key,
|
||||||
|
KeyContent::Room(_, c) => &c.key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<KeyToDeviceEventContent> for KeyContent {
|
||||||
|
fn from(content: KeyToDeviceEventContent) -> Self {
|
||||||
|
KeyContent::ToDevice(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(RoomId, KeyEventContent)> for KeyContent {
|
||||||
|
fn from(content: (RoomId, KeyEventContent)) -> Self {
|
||||||
|
KeyContent::Room(content.0, content.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum MacContent {
|
||||||
|
ToDevice(MacToDeviceEventContent),
|
||||||
|
Room(RoomId, MacEventContent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MacContent {
|
||||||
|
pub fn flow_id(&self) -> FlowId {
|
||||||
|
match self {
|
||||||
|
MacContent::ToDevice(c) => FlowId::ToDevice(c.transaction_id.clone()),
|
||||||
|
MacContent::Room(r, c) => FlowId::InRoom(r.clone(), c.relation.event_id.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mac(&self) -> &BTreeMap<String, String> {
|
||||||
|
match self {
|
||||||
|
MacContent::ToDevice(c) => &c.mac,
|
||||||
|
MacContent::Room(_, c) => &c.mac,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keys(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
MacContent::ToDevice(c) => &c.keys,
|
||||||
|
MacContent::Room(_, c) => &c.keys,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MacToDeviceEventContent> for MacContent {
|
||||||
|
fn from(content: MacToDeviceEventContent) -> Self {
|
||||||
|
MacContent::ToDevice(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(RoomId, MacEventContent)> for MacContent {
|
||||||
|
fn from(content: (RoomId, MacEventContent)) -> Self {
|
||||||
|
MacContent::Room(content.0, content.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum CancelContent {
|
||||||
|
ToDevice(CancelToDeviceEventContent),
|
||||||
|
Room(RoomId, CancelEventContent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(RoomId, CancelEventContent)> for CancelContent {
|
||||||
|
fn from(content: (RoomId, CancelEventContent)) -> Self {
|
||||||
|
CancelContent::Room(content.0, content.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CancelToDeviceEventContent> for CancelContent {
|
||||||
|
fn from(content: CancelToDeviceEventContent) -> Self {
|
||||||
|
CancelContent::ToDevice(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DoneContent {
|
||||||
|
Room(RoomId, DoneEventContent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DoneContent {
|
||||||
|
pub fn flow_id(&self) -> FlowId {
|
||||||
|
match self {
|
||||||
|
DoneContent::Room(r, c) => FlowId::InRoom(r.clone(), c.relation.event_id.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(RoomId, DoneEventContent)> for DoneContent {
|
||||||
|
fn from(content: (RoomId, DoneEventContent)) -> Self {
|
||||||
|
DoneContent::Room(content.0, content.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum OutgoingContent {
|
||||||
|
Room(RoomId, AnyMessageEventContent),
|
||||||
|
ToDevice(AnyToDeviceEventContent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StartContent> for OutgoingContent {
|
||||||
|
fn from(content: StartContent) -> Self {
|
||||||
|
match content {
|
||||||
|
StartContent::Room(r, c) => (r, AnyMessageEventContent::KeyVerificationStart(c)).into(),
|
||||||
|
StartContent::ToDevice(c) => AnyToDeviceEventContent::KeyVerificationStart(c).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CancelContent> for OutgoingContent {
|
||||||
|
fn from(content: CancelContent) -> Self {
|
||||||
|
match content {
|
||||||
|
CancelContent::Room(r, c) => {
|
||||||
|
(r, AnyMessageEventContent::KeyVerificationCancel(c)).into()
|
||||||
|
}
|
||||||
|
CancelContent::ToDevice(c) => AnyToDeviceEventContent::KeyVerificationCancel(c).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<KeyContent> for OutgoingContent {
|
||||||
|
fn from(content: KeyContent) -> Self {
|
||||||
|
match content {
|
||||||
|
KeyContent::Room(r, c) => (r, AnyMessageEventContent::KeyVerificationKey(c)).into(),
|
||||||
|
KeyContent::ToDevice(c) => AnyToDeviceEventContent::KeyVerificationKey(c).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DoneContent> for OutgoingContent {
|
||||||
|
fn from(content: DoneContent) -> Self {
|
||||||
|
match content {
|
||||||
|
DoneContent::Room(r, c) => (r, AnyMessageEventContent::KeyVerificationDone(c)).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AnyToDeviceEventContent> for OutgoingContent {
|
||||||
|
fn from(content: AnyToDeviceEventContent) -> Self {
|
||||||
|
OutgoingContent::ToDevice(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(RoomId, AnyMessageEventContent)> for OutgoingContent {
|
||||||
|
fn from(content: (RoomId, AnyMessageEventContent)) -> Self {
|
||||||
|
OutgoingContent::Room(content.0, content.1)
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,13 +23,14 @@ use matrix_sdk_common::{
|
||||||
api::r0::to_device::DeviceIdOrAllDevices,
|
api::r0::to_device::DeviceIdOrAllDevices,
|
||||||
events::{
|
events::{
|
||||||
key::verification::{
|
key::verification::{
|
||||||
cancel::CancelCode, mac::MacToDeviceEventContent, start::StartToDeviceEventContent,
|
cancel::CancelCode,
|
||||||
|
mac::{MacEventContent, MacToDeviceEventContent},
|
||||||
|
Relation,
|
||||||
},
|
},
|
||||||
AnyToDeviceEventContent, EventType, ToDeviceEvent,
|
AnyToDeviceEventContent, EventType,
|
||||||
},
|
},
|
||||||
identifiers::{DeviceId, DeviceKeyAlgorithm, DeviceKeyId, UserId},
|
identifiers::{DeviceId, DeviceKeyAlgorithm, DeviceKeyId, UserId},
|
||||||
uuid::Uuid,
|
uuid::Uuid,
|
||||||
CanonicalJsonValue,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -38,6 +39,11 @@ use crate::{
|
||||||
ReadOnlyAccount, ToDeviceRequest,
|
ReadOnlyAccount, ToDeviceRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
event_enums::{MacContent, StartContent},
|
||||||
|
sas_state::FlowId,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SasIds {
|
pub struct SasIds {
|
||||||
pub account: ReadOnlyAccount,
|
pub account: ReadOnlyAccount,
|
||||||
|
@ -55,15 +61,12 @@ pub struct SasIds {
|
||||||
///
|
///
|
||||||
/// * `content` - The `m.key.verification.start` event content that started the
|
/// * `content` - The `m.key.verification.start` event content that started the
|
||||||
/// interactive verification process.
|
/// interactive verification process.
|
||||||
pub fn calculate_commitment(public_key: &str, content: &StartToDeviceEventContent) -> String {
|
pub fn calculate_commitment(public_key: &str, content: impl Into<StartContent>) -> String {
|
||||||
let json_content: CanonicalJsonValue = serde_json::to_value(content)
|
let content = content.into().canonical_json();
|
||||||
.expect("Can't serialize content")
|
|
||||||
.try_into()
|
|
||||||
.expect("Can't canonicalize content");
|
|
||||||
|
|
||||||
encode(
|
encode(
|
||||||
Sha256::new()
|
Sha256::new()
|
||||||
.chain(&format!("{}{}", public_key, json_content))
|
.chain(&format!("{}{}", public_key, content))
|
||||||
.finalize(),
|
.finalize(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -187,7 +190,8 @@ pub fn receive_mac_event(
|
||||||
sas: &OlmSas,
|
sas: &OlmSas,
|
||||||
ids: &SasIds,
|
ids: &SasIds,
|
||||||
flow_id: &str,
|
flow_id: &str,
|
||||||
event: &ToDeviceEvent<MacToDeviceEventContent>,
|
sender: &UserId,
|
||||||
|
content: &MacContent,
|
||||||
) -> Result<(Vec<ReadOnlyDevice>, Vec<UserIdentities>), CancelCode> {
|
) -> Result<(Vec<ReadOnlyDevice>, Vec<UserIdentities>), CancelCode> {
|
||||||
let mut verified_devices = Vec::new();
|
let mut verified_devices = Vec::new();
|
||||||
let mut verified_identities = Vec::new();
|
let mut verified_identities = Vec::new();
|
||||||
|
@ -196,25 +200,25 @@ pub fn receive_mac_event(
|
||||||
|
|
||||||
trace!(
|
trace!(
|
||||||
"Received a key.verification.mac event from {} {}",
|
"Received a key.verification.mac event from {} {}",
|
||||||
event.sender,
|
sender,
|
||||||
ids.other_device.device_id()
|
ids.other_device.device_id()
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut keys = event.content.mac.keys().cloned().collect::<Vec<String>>();
|
let mut keys = content.mac().keys().cloned().collect::<Vec<String>>();
|
||||||
keys.sort();
|
keys.sort();
|
||||||
let keys = sas
|
let keys = sas
|
||||||
.calculate_mac(&keys.join(","), &format!("{}KEY_IDS", &info))
|
.calculate_mac(&keys.join(","), &format!("{}KEY_IDS", &info))
|
||||||
.expect("Can't calculate SAS MAC");
|
.expect("Can't calculate SAS MAC");
|
||||||
|
|
||||||
if keys != event.content.keys {
|
if keys != content.keys() {
|
||||||
return Err(CancelCode::KeyMismatch);
|
return Err(CancelCode::KeyMismatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (key_id, key_mac) in &event.content.mac {
|
for (key_id, key_mac) in content.mac() {
|
||||||
trace!(
|
trace!(
|
||||||
"Checking MAC for the key id {} from {} {}",
|
"Checking MAC for the key id {} from {} {}",
|
||||||
key_id,
|
key_id,
|
||||||
event.sender,
|
sender,
|
||||||
ids.other_device.device_id()
|
ids.other_device.device_id()
|
||||||
);
|
);
|
||||||
let key_id: DeviceKeyId = match key_id.as_str().try_into() {
|
let key_id: DeviceKeyId = match key_id.as_str().try_into() {
|
||||||
|
@ -228,6 +232,12 @@ pub fn receive_mac_event(
|
||||||
.calculate_mac(key, &format!("{}{}", info, key_id))
|
.calculate_mac(key, &format!("{}{}", info, key_id))
|
||||||
.expect("Can't calculate SAS MAC")
|
.expect("Can't calculate SAS MAC")
|
||||||
{
|
{
|
||||||
|
trace!(
|
||||||
|
"Successfully verified the device key {} from {}",
|
||||||
|
key_id,
|
||||||
|
sender
|
||||||
|
);
|
||||||
|
|
||||||
verified_devices.push(ids.other_device.clone());
|
verified_devices.push(ids.other_device.clone());
|
||||||
} else {
|
} else {
|
||||||
return Err(CancelCode::KeyMismatch);
|
return Err(CancelCode::KeyMismatch);
|
||||||
|
@ -244,7 +254,7 @@ pub fn receive_mac_event(
|
||||||
trace!(
|
trace!(
|
||||||
"Successfully verified the master key {} from {}",
|
"Successfully verified the master key {} from {}",
|
||||||
key_id,
|
key_id,
|
||||||
event.sender
|
sender
|
||||||
);
|
);
|
||||||
verified_identities.push(identity.clone())
|
verified_identities.push(identity.clone())
|
||||||
} else {
|
} else {
|
||||||
|
@ -256,7 +266,7 @@ pub fn receive_mac_event(
|
||||||
"Key ID {} in MAC event from {} {} doesn't belong to any device \
|
"Key ID {} in MAC event from {} {} doesn't belong to any device \
|
||||||
or user identity",
|
or user identity",
|
||||||
key_id,
|
key_id,
|
||||||
event.sender,
|
sender,
|
||||||
ids.other_device.device_id()
|
ids.other_device.device_id()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -298,12 +308,12 @@ fn extra_mac_info_send(ids: &SasIds, flow_id: &str) -> String {
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// This will panic if the public key of the other side wasn't set.
|
/// This will panic if the public key of the other side wasn't set.
|
||||||
pub fn get_mac_content(sas: &OlmSas, ids: &SasIds, flow_id: &str) -> MacToDeviceEventContent {
|
pub fn get_mac_content(sas: &OlmSas, ids: &SasIds, flow_id: &FlowId) -> MacContent {
|
||||||
let mut mac: BTreeMap<String, String> = BTreeMap::new();
|
let mut mac: BTreeMap<String, String> = BTreeMap::new();
|
||||||
|
|
||||||
let key_id = DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, ids.account.device_id());
|
let key_id = DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, ids.account.device_id());
|
||||||
let key = ids.account.identity_keys().ed25519();
|
let key = ids.account.identity_keys().ed25519();
|
||||||
let info = extra_mac_info_send(ids, flow_id);
|
let info = extra_mac_info_send(ids, flow_id.as_str());
|
||||||
|
|
||||||
mac.insert(
|
mac.insert(
|
||||||
key_id.to_string(),
|
key_id.to_string(),
|
||||||
|
@ -319,11 +329,25 @@ pub fn get_mac_content(sas: &OlmSas, ids: &SasIds, flow_id: &str) -> MacToDevice
|
||||||
.calculate_mac(&keys.join(","), &format!("{}KEY_IDS", &info))
|
.calculate_mac(&keys.join(","), &format!("{}KEY_IDS", &info))
|
||||||
.expect("Can't calculate SAS MAC");
|
.expect("Can't calculate SAS MAC");
|
||||||
|
|
||||||
MacToDeviceEventContent {
|
match flow_id {
|
||||||
transaction_id: flow_id.to_owned(),
|
FlowId::ToDevice(s) => MacToDeviceEventContent {
|
||||||
|
transaction_id: s.to_string(),
|
||||||
keys,
|
keys,
|
||||||
mac,
|
mac,
|
||||||
}
|
}
|
||||||
|
.into(),
|
||||||
|
FlowId::InRoom(r, e) => (
|
||||||
|
r.clone(),
|
||||||
|
MacEventContent {
|
||||||
|
mac,
|
||||||
|
keys,
|
||||||
|
relation: Relation {
|
||||||
|
event_id: e.clone(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the extra info that will be used when we generate bytes for the short
|
/// Get the extra info that will be used when we generate bytes for the short
|
||||||
|
@ -546,7 +570,7 @@ mod test {
|
||||||
});
|
});
|
||||||
|
|
||||||
let content: StartToDeviceEventContent = serde_json::from_value(content).unwrap();
|
let content: StartToDeviceEventContent = serde_json::from_value(content).unwrap();
|
||||||
let calculated_commitment = calculate_commitment(public_key, &content);
|
let calculated_commitment = calculate_commitment(public_key, content);
|
||||||
|
|
||||||
assert_eq!(commitment, &calculated_commitment);
|
assert_eq!(commitment, &calculated_commitment);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,381 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use matrix_sdk_common::{
|
||||||
|
events::{key::verification::cancel::CancelCode, AnyMessageEvent, AnyToDeviceEvent},
|
||||||
|
identifiers::{EventId, RoomId},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
identities::{ReadOnlyDevice, UserIdentities},
|
||||||
|
ReadOnlyAccount,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
event_enums::{AcceptContent, CancelContent, MacContent, OutgoingContent},
|
||||||
|
sas_state::{
|
||||||
|
Accepted, Canceled, Confirmed, Created, Done, FlowId, KeyReceived, MacReceived, SasState,
|
||||||
|
Started, WaitingForDone,
|
||||||
|
},
|
||||||
|
StartContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum InnerSas {
|
||||||
|
Created(SasState<Created>),
|
||||||
|
Started(SasState<Started>),
|
||||||
|
Accepted(SasState<Accepted>),
|
||||||
|
KeyRecieved(SasState<KeyReceived>),
|
||||||
|
Confirmed(SasState<Confirmed>),
|
||||||
|
MacReceived(SasState<MacReceived>),
|
||||||
|
WaitingForDone(SasState<WaitingForDone>),
|
||||||
|
WaitingForDoneUnconfirmed(SasState<WaitingForDone>),
|
||||||
|
Done(SasState<Done>),
|
||||||
|
Canceled(SasState<Canceled>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InnerSas {
|
||||||
|
pub fn start(
|
||||||
|
account: ReadOnlyAccount,
|
||||||
|
other_device: ReadOnlyDevice,
|
||||||
|
other_identity: Option<UserIdentities>,
|
||||||
|
) -> (InnerSas, StartContent) {
|
||||||
|
let sas = SasState::<Created>::new(account, other_device, other_identity);
|
||||||
|
let content = sas.as_content();
|
||||||
|
(InnerSas::Created(sas), content)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_in_room(
|
||||||
|
event_id: EventId,
|
||||||
|
room_id: RoomId,
|
||||||
|
account: ReadOnlyAccount,
|
||||||
|
other_device: ReadOnlyDevice,
|
||||||
|
other_identity: Option<UserIdentities>,
|
||||||
|
) -> (InnerSas, StartContent) {
|
||||||
|
let sas = SasState::<Created>::new_in_room(
|
||||||
|
room_id,
|
||||||
|
event_id,
|
||||||
|
account,
|
||||||
|
other_device,
|
||||||
|
other_identity,
|
||||||
|
);
|
||||||
|
let content = sas.as_content();
|
||||||
|
(InnerSas::Created(sas), content)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_start_event(
|
||||||
|
account: ReadOnlyAccount,
|
||||||
|
other_device: ReadOnlyDevice,
|
||||||
|
content: impl Into<StartContent>,
|
||||||
|
other_identity: Option<UserIdentities>,
|
||||||
|
) -> Result<InnerSas, CancelContent> {
|
||||||
|
match SasState::<Started>::from_start_event(account, other_device, other_identity, content)
|
||||||
|
{
|
||||||
|
Ok(s) => Ok(InnerSas::Started(s)),
|
||||||
|
Err(s) => Err(s.as_content()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn accept(&self) -> Option<AcceptContent> {
|
||||||
|
if let InnerSas::Started(s) = self {
|
||||||
|
Some(s.as_content())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn set_creation_time(&mut self, time: Instant) {
|
||||||
|
match self {
|
||||||
|
InnerSas::Created(s) => s.set_creation_time(time),
|
||||||
|
InnerSas::Started(s) => s.set_creation_time(time),
|
||||||
|
InnerSas::Canceled(s) => s.set_creation_time(time),
|
||||||
|
InnerSas::Accepted(s) => s.set_creation_time(time),
|
||||||
|
InnerSas::KeyRecieved(s) => s.set_creation_time(time),
|
||||||
|
InnerSas::Confirmed(s) => s.set_creation_time(time),
|
||||||
|
InnerSas::MacReceived(s) => s.set_creation_time(time),
|
||||||
|
InnerSas::Done(s) => s.set_creation_time(time),
|
||||||
|
InnerSas::WaitingForDone(s) => s.set_creation_time(time),
|
||||||
|
InnerSas::WaitingForDoneUnconfirmed(s) => s.set_creation_time(time),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel(self, code: CancelCode) -> (InnerSas, Option<CancelContent>) {
|
||||||
|
let sas = match self {
|
||||||
|
InnerSas::Created(s) => s.cancel(code),
|
||||||
|
InnerSas::Started(s) => s.cancel(code),
|
||||||
|
InnerSas::Accepted(s) => s.cancel(code),
|
||||||
|
InnerSas::KeyRecieved(s) => s.cancel(code),
|
||||||
|
InnerSas::MacReceived(s) => s.cancel(code),
|
||||||
|
_ => return (self, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = sas.as_content();
|
||||||
|
|
||||||
|
(InnerSas::Canceled(sas), Some(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn confirm(self) -> (InnerSas, Option<MacContent>) {
|
||||||
|
match self {
|
||||||
|
InnerSas::KeyRecieved(s) => {
|
||||||
|
let sas = s.confirm();
|
||||||
|
let content = sas.as_content();
|
||||||
|
(InnerSas::Confirmed(sas), Some(content))
|
||||||
|
}
|
||||||
|
InnerSas::MacReceived(s) => {
|
||||||
|
if s.is_dm_verification() {
|
||||||
|
let sas = s.confirm_and_wait_for_done();
|
||||||
|
let content = sas.as_content();
|
||||||
|
|
||||||
|
(InnerSas::WaitingForDoneUnconfirmed(sas), Some(content))
|
||||||
|
} else {
|
||||||
|
let sas = s.confirm();
|
||||||
|
let content = sas.as_content();
|
||||||
|
|
||||||
|
(InnerSas::Done(sas), Some(content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (self, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn receive_room_event(
|
||||||
|
self,
|
||||||
|
event: &AnyMessageEvent,
|
||||||
|
) -> (InnerSas, Option<OutgoingContent>) {
|
||||||
|
match event {
|
||||||
|
AnyMessageEvent::KeyVerificationKey(e) => match self {
|
||||||
|
InnerSas::Accepted(s) => {
|
||||||
|
match s.into_key_received(&e.sender, (e.room_id.clone(), e.content.clone())) {
|
||||||
|
Ok(s) => (InnerSas::KeyRecieved(s), None),
|
||||||
|
Err(s) => {
|
||||||
|
let content = s.as_content();
|
||||||
|
(InnerSas::Canceled(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InnerSas::Started(s) => {
|
||||||
|
match s.into_key_received(&e.sender, (e.room_id.clone(), e.content.clone())) {
|
||||||
|
Ok(s) => {
|
||||||
|
let content = s.as_content();
|
||||||
|
(InnerSas::KeyRecieved(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
Err(s) => {
|
||||||
|
let content = s.as_content();
|
||||||
|
(InnerSas::Canceled(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => (self, None),
|
||||||
|
},
|
||||||
|
AnyMessageEvent::KeyVerificationMac(e) => match self {
|
||||||
|
InnerSas::KeyRecieved(s) => {
|
||||||
|
match s.into_mac_received(&e.sender, (e.room_id.clone(), e.content.clone())) {
|
||||||
|
Ok(s) => (InnerSas::MacReceived(s), None),
|
||||||
|
Err(s) => {
|
||||||
|
let content = s.as_content();
|
||||||
|
(InnerSas::Canceled(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InnerSas::Confirmed(s) => {
|
||||||
|
match s.into_waiting_for_done(&e.sender, (e.room_id.clone(), e.content.clone()))
|
||||||
|
{
|
||||||
|
Ok(s) => {
|
||||||
|
let content = s.done_content();
|
||||||
|
(InnerSas::WaitingForDone(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
Err(s) => {
|
||||||
|
let content = s.as_content();
|
||||||
|
(InnerSas::Canceled(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (self, None),
|
||||||
|
},
|
||||||
|
AnyMessageEvent::KeyVerificationDone(e) => match self {
|
||||||
|
InnerSas::WaitingForDone(s) => {
|
||||||
|
match s.into_done(&e.sender, (e.room_id.clone(), e.content.clone())) {
|
||||||
|
Ok(s) => (InnerSas::Done(s), None),
|
||||||
|
Err(s) => {
|
||||||
|
let content = s.as_content();
|
||||||
|
(InnerSas::Canceled(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InnerSas::WaitingForDoneUnconfirmed(s) => {
|
||||||
|
match s.into_done(&e.sender, (e.room_id.clone(), e.content.clone())) {
|
||||||
|
Ok(s) => {
|
||||||
|
let content = s.done_content();
|
||||||
|
(InnerSas::Done(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
Err(s) => {
|
||||||
|
let content = s.as_content();
|
||||||
|
(InnerSas::Canceled(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => (self, None),
|
||||||
|
},
|
||||||
|
_ => (self, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn receive_event(self, event: &AnyToDeviceEvent) -> (InnerSas, Option<OutgoingContent>) {
|
||||||
|
match event {
|
||||||
|
AnyToDeviceEvent::KeyVerificationAccept(e) => {
|
||||||
|
if let InnerSas::Created(s) = self {
|
||||||
|
match s.into_accepted(&e.sender, e.content.clone()) {
|
||||||
|
Ok(s) => {
|
||||||
|
let content = s.as_content();
|
||||||
|
(InnerSas::Accepted(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
Err(s) => {
|
||||||
|
let content = s.as_content();
|
||||||
|
(InnerSas::Canceled(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(self, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnyToDeviceEvent::KeyVerificationKey(e) => match self {
|
||||||
|
InnerSas::Accepted(s) => match s.into_key_received(&e.sender, e.content.clone()) {
|
||||||
|
Ok(s) => (InnerSas::KeyRecieved(s), None),
|
||||||
|
Err(s) => {
|
||||||
|
let content = s.as_content();
|
||||||
|
(InnerSas::Canceled(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
InnerSas::Started(s) => match s.into_key_received(&e.sender, e.content.clone()) {
|
||||||
|
Ok(s) => {
|
||||||
|
let content = s.as_content();
|
||||||
|
(InnerSas::KeyRecieved(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
Err(s) => {
|
||||||
|
let content = s.as_content();
|
||||||
|
(InnerSas::Canceled(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => (self, None),
|
||||||
|
},
|
||||||
|
AnyToDeviceEvent::KeyVerificationMac(e) => match self {
|
||||||
|
InnerSas::KeyRecieved(s) => match s.into_mac_received(&e.sender, e.content.clone())
|
||||||
|
{
|
||||||
|
Ok(s) => (InnerSas::MacReceived(s), None),
|
||||||
|
Err(s) => {
|
||||||
|
let content = s.as_content();
|
||||||
|
(InnerSas::Canceled(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
InnerSas::Confirmed(s) => match s.into_done(&e.sender, e.content.clone()) {
|
||||||
|
Ok(s) => (InnerSas::Done(s), None),
|
||||||
|
Err(s) => {
|
||||||
|
let content = s.as_content();
|
||||||
|
(InnerSas::Canceled(s), Some(content.into()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => (self, None),
|
||||||
|
},
|
||||||
|
_ => (self, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn can_be_presented(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
InnerSas::KeyRecieved(_) => true,
|
||||||
|
InnerSas::MacReceived(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_done(&self) -> bool {
|
||||||
|
matches!(self, InnerSas::Done(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_canceled(&self) -> bool {
|
||||||
|
matches!(self, InnerSas::Canceled(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn timed_out(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
InnerSas::Created(s) => s.timed_out(),
|
||||||
|
InnerSas::Started(s) => s.timed_out(),
|
||||||
|
InnerSas::Canceled(s) => s.timed_out(),
|
||||||
|
InnerSas::Accepted(s) => s.timed_out(),
|
||||||
|
InnerSas::KeyRecieved(s) => s.timed_out(),
|
||||||
|
InnerSas::Confirmed(s) => s.timed_out(),
|
||||||
|
InnerSas::MacReceived(s) => s.timed_out(),
|
||||||
|
InnerSas::WaitingForDone(s) => s.timed_out(),
|
||||||
|
InnerSas::WaitingForDoneUnconfirmed(s) => s.timed_out(),
|
||||||
|
InnerSas::Done(s) => s.timed_out(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verification_flow_id(&self) -> Arc<FlowId> {
|
||||||
|
match self {
|
||||||
|
InnerSas::Created(s) => s.verification_flow_id.clone(),
|
||||||
|
InnerSas::Started(s) => s.verification_flow_id.clone(),
|
||||||
|
InnerSas::Canceled(s) => s.verification_flow_id.clone(),
|
||||||
|
InnerSas::Accepted(s) => s.verification_flow_id.clone(),
|
||||||
|
InnerSas::KeyRecieved(s) => s.verification_flow_id.clone(),
|
||||||
|
InnerSas::Confirmed(s) => s.verification_flow_id.clone(),
|
||||||
|
InnerSas::MacReceived(s) => s.verification_flow_id.clone(),
|
||||||
|
InnerSas::WaitingForDone(s) => s.verification_flow_id.clone(),
|
||||||
|
InnerSas::WaitingForDoneUnconfirmed(s) => s.verification_flow_id.clone(),
|
||||||
|
InnerSas::Done(s) => s.verification_flow_id.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emoji(&self) -> Option<Vec<(&'static str, &'static str)>> {
|
||||||
|
match self {
|
||||||
|
InnerSas::KeyRecieved(s) => Some(s.get_emoji()),
|
||||||
|
InnerSas::MacReceived(s) => Some(s.get_emoji()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decimals(&self) -> Option<(u16, u16, u16)> {
|
||||||
|
match self {
|
||||||
|
InnerSas::KeyRecieved(s) => Some(s.get_decimal()),
|
||||||
|
InnerSas::MacReceived(s) => Some(s.get_decimal()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verified_devices(&self) -> Option<Arc<[ReadOnlyDevice]>> {
|
||||||
|
if let InnerSas::Done(s) = self {
|
||||||
|
Some(s.verified_devices())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verified_identities(&self) -> Option<Arc<[UserIdentities]>> {
|
||||||
|
if let InnerSas::Done(s) = self {
|
||||||
|
Some(s.verified_identities())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,47 +12,53 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
mod event_enums;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
mod inner_sas;
|
||||||
mod sas_state;
|
mod sas_state;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use event_enums::AcceptContent;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tracing::{error, info, trace, warn};
|
use tracing::{error, info, trace, warn};
|
||||||
|
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::{
|
||||||
api::r0::keys::upload_signatures::Request as SignatureUploadRequest,
|
api::r0::keys::upload_signatures::Request as SignatureUploadRequest,
|
||||||
events::{
|
events::{
|
||||||
key::verification::{
|
key::verification::cancel::CancelCode, AnyMessageEvent, AnyMessageEventContent,
|
||||||
accept::AcceptToDeviceEventContent, cancel::CancelCode, mac::MacToDeviceEventContent,
|
AnyToDeviceEvent, AnyToDeviceEventContent,
|
||||||
start::StartToDeviceEventContent,
|
|
||||||
},
|
},
|
||||||
AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent,
|
identifiers::{DeviceId, EventId, RoomId, UserId},
|
||||||
},
|
uuid::Uuid,
|
||||||
identifiers::{DeviceId, UserId},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::SignatureError,
|
error::SignatureError,
|
||||||
identities::{LocalTrust, ReadOnlyDevice, UserIdentities},
|
identities::{LocalTrust, ReadOnlyDevice, UserIdentities},
|
||||||
olm::PrivateCrossSigningIdentity,
|
olm::PrivateCrossSigningIdentity,
|
||||||
|
requests::{OutgoingVerificationRequest, RoomMessageRequest},
|
||||||
store::{Changes, CryptoStore, CryptoStoreError, DeviceChanges},
|
store::{Changes, CryptoStore, CryptoStoreError, DeviceChanges},
|
||||||
ReadOnlyAccount, ToDeviceRequest,
|
ReadOnlyAccount, ToDeviceRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use helpers::content_to_request;
|
pub use helpers::content_to_request;
|
||||||
use sas_state::{
|
use inner_sas::InnerSas;
|
||||||
Accepted, Canceled, Confirmed, Created, Done, KeyReceived, MacReceived, SasState, Started,
|
pub use sas_state::FlowId;
|
||||||
};
|
|
||||||
|
pub use event_enums::{OutgoingContent, StartContent};
|
||||||
|
|
||||||
|
use self::event_enums::CancelContent;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
/// A result of a verification flow.
|
/// A result of a verification flow.
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub enum VerificationResult {
|
pub enum VerificationResult {
|
||||||
/// The verification succeeded, nothing needs to be done.
|
/// The verification succeeded, nothing needs to be done.
|
||||||
Ok,
|
Ok,
|
||||||
/// The verification was canceled.
|
/// The verification was canceled.
|
||||||
Cancel(ToDeviceRequest),
|
Cancel(OutgoingVerificationRequest),
|
||||||
/// The verification is done and has signatures that need to be uploaded.
|
/// The verification is done and has signatures that need to be uploaded.
|
||||||
SignatureUpload(SignatureUploadRequest),
|
SignatureUpload(SignatureUploadRequest),
|
||||||
}
|
}
|
||||||
|
@ -66,7 +72,7 @@ pub struct Sas {
|
||||||
private_identity: PrivateCrossSigningIdentity,
|
private_identity: PrivateCrossSigningIdentity,
|
||||||
other_device: ReadOnlyDevice,
|
other_device: ReadOnlyDevice,
|
||||||
other_identity: Option<UserIdentities>,
|
other_identity: Option<UserIdentities>,
|
||||||
flow_id: Arc<str>,
|
flow_id: Arc<FlowId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sas {
|
impl Sas {
|
||||||
|
@ -96,7 +102,7 @@ impl Sas {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the unique ID that identifies this SAS verification flow.
|
/// Get the unique ID that identifies this SAS verification flow.
|
||||||
pub fn flow_id(&self) -> &str {
|
pub fn flow_id(&self) -> &FlowId {
|
||||||
&self.flow_id
|
&self.flow_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +112,27 @@ impl Sas {
|
||||||
self.inner.lock().unwrap().set_creation_time(time)
|
self.inner.lock().unwrap().set_creation_time(time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn start_helper(
|
||||||
|
inner_sas: InnerSas,
|
||||||
|
account: ReadOnlyAccount,
|
||||||
|
private_identity: PrivateCrossSigningIdentity,
|
||||||
|
other_device: ReadOnlyDevice,
|
||||||
|
store: Arc<Box<dyn CryptoStore>>,
|
||||||
|
other_identity: Option<UserIdentities>,
|
||||||
|
) -> Sas {
|
||||||
|
let flow_id = inner_sas.verification_flow_id();
|
||||||
|
|
||||||
|
Sas {
|
||||||
|
inner: Arc::new(Mutex::new(inner_sas)),
|
||||||
|
account,
|
||||||
|
private_identity,
|
||||||
|
store,
|
||||||
|
other_device,
|
||||||
|
flow_id,
|
||||||
|
other_identity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Start a new SAS auth flow with the given device.
|
/// Start a new SAS auth flow with the given device.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
@ -122,25 +149,65 @@ impl Sas {
|
||||||
other_device: ReadOnlyDevice,
|
other_device: ReadOnlyDevice,
|
||||||
store: Arc<Box<dyn CryptoStore>>,
|
store: Arc<Box<dyn CryptoStore>>,
|
||||||
other_identity: Option<UserIdentities>,
|
other_identity: Option<UserIdentities>,
|
||||||
) -> (Sas, StartToDeviceEventContent) {
|
) -> (Sas, StartContent) {
|
||||||
let (inner, content) = InnerSas::start(
|
let (inner, content) = InnerSas::start(
|
||||||
account.clone(),
|
account.clone(),
|
||||||
other_device.clone(),
|
other_device.clone(),
|
||||||
other_identity.clone(),
|
other_identity.clone(),
|
||||||
);
|
);
|
||||||
let flow_id = inner.verification_flow_id();
|
|
||||||
|
|
||||||
let sas = Sas {
|
(
|
||||||
inner: Arc::new(Mutex::new(inner)),
|
Self::start_helper(
|
||||||
|
inner,
|
||||||
account,
|
account,
|
||||||
private_identity,
|
private_identity,
|
||||||
store,
|
|
||||||
other_device,
|
other_device,
|
||||||
flow_id,
|
store,
|
||||||
other_identity,
|
other_identity,
|
||||||
};
|
),
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
(sas, content)
|
/// Start a new SAS auth flow with the given device inside the given room.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `account` - Our own account.
|
||||||
|
///
|
||||||
|
/// * `other_device` - The other device which we are going to verify.
|
||||||
|
///
|
||||||
|
/// Returns the new `Sas` object and a `StartEventContent` that needs to be
|
||||||
|
/// sent out through the server to the other device.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn start_in_room(
|
||||||
|
flow_id: EventId,
|
||||||
|
room_id: RoomId,
|
||||||
|
account: ReadOnlyAccount,
|
||||||
|
private_identity: PrivateCrossSigningIdentity,
|
||||||
|
other_device: ReadOnlyDevice,
|
||||||
|
store: Arc<Box<dyn CryptoStore>>,
|
||||||
|
other_identity: Option<UserIdentities>,
|
||||||
|
) -> (Sas, StartContent) {
|
||||||
|
let (inner, content) = InnerSas::start_in_room(
|
||||||
|
flow_id,
|
||||||
|
room_id,
|
||||||
|
account.clone(),
|
||||||
|
other_device.clone(),
|
||||||
|
other_identity.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
(
|
||||||
|
Self::start_helper(
|
||||||
|
inner,
|
||||||
|
account,
|
||||||
|
private_identity,
|
||||||
|
other_device,
|
||||||
|
store,
|
||||||
|
other_identity,
|
||||||
|
),
|
||||||
|
content,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new Sas object from a m.key.verification.start request.
|
/// Create a new Sas object from a m.key.verification.start request.
|
||||||
|
@ -158,15 +225,16 @@ impl Sas {
|
||||||
private_identity: PrivateCrossSigningIdentity,
|
private_identity: PrivateCrossSigningIdentity,
|
||||||
other_device: ReadOnlyDevice,
|
other_device: ReadOnlyDevice,
|
||||||
store: Arc<Box<dyn CryptoStore>>,
|
store: Arc<Box<dyn CryptoStore>>,
|
||||||
event: &ToDeviceEvent<StartToDeviceEventContent>,
|
content: impl Into<StartContent>,
|
||||||
other_identity: Option<UserIdentities>,
|
other_identity: Option<UserIdentities>,
|
||||||
) -> Result<Sas, AnyToDeviceEventContent> {
|
) -> Result<Sas, OutgoingContent> {
|
||||||
let inner = InnerSas::from_start_event(
|
let inner = InnerSas::from_start_event(
|
||||||
account.clone(),
|
account.clone(),
|
||||||
other_device.clone(),
|
other_device.clone(),
|
||||||
event,
|
content,
|
||||||
other_identity.clone(),
|
other_identity.clone(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let flow_id = inner.verification_flow_id();
|
let flow_id = inner.verification_flow_id();
|
||||||
|
|
||||||
Ok(Sas {
|
Ok(Sas {
|
||||||
|
@ -184,10 +252,18 @@ impl Sas {
|
||||||
///
|
///
|
||||||
/// This does nothing if the verification was already accepted, otherwise it
|
/// This does nothing if the verification was already accepted, otherwise it
|
||||||
/// returns an `AcceptEventContent` that needs to be sent out.
|
/// returns an `AcceptEventContent` that needs to be sent out.
|
||||||
pub fn accept(&self) -> Option<ToDeviceRequest> {
|
pub fn accept(&self) -> Option<OutgoingVerificationRequest> {
|
||||||
self.inner.lock().unwrap().accept().map(|c| {
|
self.inner.lock().unwrap().accept().map(|c| match c {
|
||||||
|
AcceptContent::ToDevice(c) => {
|
||||||
let content = AnyToDeviceEventContent::KeyVerificationAccept(c);
|
let content = AnyToDeviceEventContent::KeyVerificationAccept(c);
|
||||||
self.content_to_request(content)
|
self.content_to_request(content).into()
|
||||||
|
}
|
||||||
|
AcceptContent::Room(room_id, content) => RoomMessageRequest {
|
||||||
|
room_id,
|
||||||
|
txn_id: Uuid::new_v4(),
|
||||||
|
content: AnyMessageEventContent::KeyVerificationAccept(content),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +276,13 @@ impl Sas {
|
||||||
/// the server.
|
/// the server.
|
||||||
pub async fn confirm(
|
pub async fn confirm(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<(Option<ToDeviceRequest>, Option<SignatureUploadRequest>), CryptoStoreError> {
|
) -> Result<
|
||||||
|
(
|
||||||
|
Option<OutgoingVerificationRequest>,
|
||||||
|
Option<SignatureUploadRequest>,
|
||||||
|
),
|
||||||
|
CryptoStoreError,
|
||||||
|
> {
|
||||||
let (content, done) = {
|
let (content, done) = {
|
||||||
let mut guard = self.inner.lock().unwrap();
|
let mut guard = self.inner.lock().unwrap();
|
||||||
let sas: InnerSas = (*guard).clone();
|
let sas: InnerSas = (*guard).clone();
|
||||||
|
@ -210,8 +292,17 @@ impl Sas {
|
||||||
(content, guard.is_done())
|
(content, guard.is_done())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mac_request = content
|
let mac_request = content.map(|c| match c {
|
||||||
.map(|c| self.content_to_request(AnyToDeviceEventContent::KeyVerificationMac(c)));
|
event_enums::MacContent::ToDevice(c) => self
|
||||||
|
.content_to_request(AnyToDeviceEventContent::KeyVerificationMac(c))
|
||||||
|
.into(),
|
||||||
|
event_enums::MacContent::Room(r, c) => RoomMessageRequest {
|
||||||
|
room_id: r,
|
||||||
|
txn_id: Uuid::new_v4(),
|
||||||
|
content: AnyMessageEventContent::KeyVerificationMac(c),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
});
|
||||||
|
|
||||||
if done {
|
if done {
|
||||||
match self.mark_as_done().await? {
|
match self.mark_as_done().await? {
|
||||||
|
@ -452,16 +543,26 @@ impl Sas {
|
||||||
///
|
///
|
||||||
/// Returns None if the `Sas` object is already in a canceled state,
|
/// Returns None if the `Sas` object is already in a canceled state,
|
||||||
/// otherwise it returns a request that needs to be sent out.
|
/// otherwise it returns a request that needs to be sent out.
|
||||||
pub fn cancel(&self) -> Option<ToDeviceRequest> {
|
pub fn cancel(&self) -> Option<OutgoingVerificationRequest> {
|
||||||
let mut guard = self.inner.lock().unwrap();
|
let mut guard = self.inner.lock().unwrap();
|
||||||
let sas: InnerSas = (*guard).clone();
|
let sas: InnerSas = (*guard).clone();
|
||||||
let (sas, content) = sas.cancel(CancelCode::User);
|
let (sas, content) = sas.cancel(CancelCode::User);
|
||||||
*guard = sas;
|
*guard = sas;
|
||||||
|
|
||||||
content.map(|c| self.content_to_request(c))
|
content.map(|c| match c {
|
||||||
|
CancelContent::Room(room_id, content) => RoomMessageRequest {
|
||||||
|
room_id,
|
||||||
|
txn_id: Uuid::new_v4(),
|
||||||
|
content: AnyMessageEventContent::KeyVerificationCancel(content),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
CancelContent::ToDevice(c) => self
|
||||||
|
.content_to_request(AnyToDeviceEventContent::KeyVerificationCancel(c))
|
||||||
|
.into(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn cancel_if_timed_out(&self) -> Option<ToDeviceRequest> {
|
pub(crate) fn cancel_if_timed_out(&self) -> Option<OutgoingVerificationRequest> {
|
||||||
if self.is_canceled() || self.is_done() {
|
if self.is_canceled() || self.is_done() {
|
||||||
None
|
None
|
||||||
} else if self.timed_out() {
|
} else if self.timed_out() {
|
||||||
|
@ -469,7 +570,17 @@ impl Sas {
|
||||||
let sas: InnerSas = (*guard).clone();
|
let sas: InnerSas = (*guard).clone();
|
||||||
let (sas, content) = sas.cancel(CancelCode::Timeout);
|
let (sas, content) = sas.cancel(CancelCode::Timeout);
|
||||||
*guard = sas;
|
*guard = sas;
|
||||||
content.map(|c| self.content_to_request(c))
|
content.map(|c| match c {
|
||||||
|
CancelContent::Room(room_id, content) => RoomMessageRequest {
|
||||||
|
room_id,
|
||||||
|
txn_id: Uuid::new_v4(),
|
||||||
|
content: AnyMessageEventContent::KeyVerificationCancel(content),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
CancelContent::ToDevice(c) => self
|
||||||
|
.content_to_request(AnyToDeviceEventContent::KeyVerificationCancel(c))
|
||||||
|
.into(),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -512,10 +623,16 @@ impl Sas {
|
||||||
self.inner.lock().unwrap().decimals()
|
self.inner.lock().unwrap().decimals()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn receive_event(
|
pub(crate) fn receive_room_event(&self, event: &AnyMessageEvent) -> Option<OutgoingContent> {
|
||||||
&self,
|
let mut guard = self.inner.lock().unwrap();
|
||||||
event: &mut AnyToDeviceEvent,
|
let sas: InnerSas = (*guard).clone();
|
||||||
) -> Option<AnyToDeviceEventContent> {
|
let (sas, content) = sas.receive_room_event(event);
|
||||||
|
*guard = sas;
|
||||||
|
|
||||||
|
content
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn receive_event(&self, event: &AnyToDeviceEvent) -> Option<OutgoingContent> {
|
||||||
let mut guard = self.inner.lock().unwrap();
|
let mut guard = self.inner.lock().unwrap();
|
||||||
let sas: InnerSas = (*guard).clone();
|
let sas: InnerSas = (*guard).clone();
|
||||||
let (sas, content) = sas.receive_event(event);
|
let (sas, content) = sas.receive_event(event);
|
||||||
|
@ -537,246 +654,11 @@ impl Sas {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
enum InnerSas {
|
|
||||||
Created(SasState<Created>),
|
|
||||||
Started(SasState<Started>),
|
|
||||||
Accepted(SasState<Accepted>),
|
|
||||||
KeyRecieved(SasState<KeyReceived>),
|
|
||||||
Confirmed(SasState<Confirmed>),
|
|
||||||
MacReceived(SasState<MacReceived>),
|
|
||||||
Done(SasState<Done>),
|
|
||||||
Canceled(SasState<Canceled>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InnerSas {
|
|
||||||
fn start(
|
|
||||||
account: ReadOnlyAccount,
|
|
||||||
other_device: ReadOnlyDevice,
|
|
||||||
other_identity: Option<UserIdentities>,
|
|
||||||
) -> (InnerSas, StartToDeviceEventContent) {
|
|
||||||
let sas = SasState::<Created>::new(account, other_device, other_identity);
|
|
||||||
let content = sas.as_content();
|
|
||||||
(InnerSas::Created(sas), content)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_start_event(
|
|
||||||
account: ReadOnlyAccount,
|
|
||||||
other_device: ReadOnlyDevice,
|
|
||||||
event: &ToDeviceEvent<StartToDeviceEventContent>,
|
|
||||||
other_identity: Option<UserIdentities>,
|
|
||||||
) -> Result<InnerSas, AnyToDeviceEventContent> {
|
|
||||||
match SasState::<Started>::from_start_event(account, other_device, event, other_identity) {
|
|
||||||
Ok(s) => Ok(InnerSas::Started(s)),
|
|
||||||
Err(s) => Err(s.as_content()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn accept(&self) -> Option<AcceptToDeviceEventContent> {
|
|
||||||
if let InnerSas::Started(s) = self {
|
|
||||||
Some(s.as_content())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn set_creation_time(&mut self, time: Instant) {
|
|
||||||
match self {
|
|
||||||
InnerSas::Created(s) => s.set_creation_time(time),
|
|
||||||
InnerSas::Started(s) => s.set_creation_time(time),
|
|
||||||
InnerSas::Canceled(s) => s.set_creation_time(time),
|
|
||||||
InnerSas::Accepted(s) => s.set_creation_time(time),
|
|
||||||
InnerSas::KeyRecieved(s) => s.set_creation_time(time),
|
|
||||||
InnerSas::Confirmed(s) => s.set_creation_time(time),
|
|
||||||
InnerSas::MacReceived(s) => s.set_creation_time(time),
|
|
||||||
InnerSas::Done(s) => s.set_creation_time(time),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cancel(self, code: CancelCode) -> (InnerSas, Option<AnyToDeviceEventContent>) {
|
|
||||||
let sas = match self {
|
|
||||||
InnerSas::Created(s) => s.cancel(code),
|
|
||||||
InnerSas::Started(s) => s.cancel(code),
|
|
||||||
InnerSas::Accepted(s) => s.cancel(code),
|
|
||||||
InnerSas::KeyRecieved(s) => s.cancel(code),
|
|
||||||
InnerSas::MacReceived(s) => s.cancel(code),
|
|
||||||
_ => return (self, None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let content = sas.as_content();
|
|
||||||
|
|
||||||
(InnerSas::Canceled(sas), Some(content))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn confirm(self) -> (InnerSas, Option<MacToDeviceEventContent>) {
|
|
||||||
match self {
|
|
||||||
InnerSas::KeyRecieved(s) => {
|
|
||||||
let sas = s.confirm();
|
|
||||||
let content = sas.as_content();
|
|
||||||
(InnerSas::Confirmed(sas), Some(content))
|
|
||||||
}
|
|
||||||
InnerSas::MacReceived(s) => {
|
|
||||||
let sas = s.confirm();
|
|
||||||
let content = sas.as_content();
|
|
||||||
(InnerSas::Done(sas), Some(content))
|
|
||||||
}
|
|
||||||
_ => (self, None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn receive_event(
|
|
||||||
self,
|
|
||||||
event: &mut AnyToDeviceEvent,
|
|
||||||
) -> (InnerSas, Option<AnyToDeviceEventContent>) {
|
|
||||||
match event {
|
|
||||||
AnyToDeviceEvent::KeyVerificationAccept(e) => {
|
|
||||||
if let InnerSas::Created(s) = self {
|
|
||||||
match s.into_accepted(e) {
|
|
||||||
Ok(s) => {
|
|
||||||
let content = s.as_content();
|
|
||||||
(
|
|
||||||
InnerSas::Accepted(s),
|
|
||||||
Some(AnyToDeviceEventContent::KeyVerificationKey(content)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Err(s) => {
|
|
||||||
let content = s.as_content();
|
|
||||||
(InnerSas::Canceled(s), Some(content))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(self, None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AnyToDeviceEvent::KeyVerificationKey(e) => match self {
|
|
||||||
InnerSas::Accepted(s) => match s.into_key_received(e) {
|
|
||||||
Ok(s) => (InnerSas::KeyRecieved(s), None),
|
|
||||||
Err(s) => {
|
|
||||||
let content = s.as_content();
|
|
||||||
(InnerSas::Canceled(s), Some(content))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
InnerSas::Started(s) => match s.into_key_received(e) {
|
|
||||||
Ok(s) => {
|
|
||||||
let content = s.as_content();
|
|
||||||
(
|
|
||||||
InnerSas::KeyRecieved(s),
|
|
||||||
Some(AnyToDeviceEventContent::KeyVerificationKey(content)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Err(s) => {
|
|
||||||
let content = s.as_content();
|
|
||||||
(InnerSas::Canceled(s), Some(content))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => (self, None),
|
|
||||||
},
|
|
||||||
AnyToDeviceEvent::KeyVerificationMac(e) => match self {
|
|
||||||
InnerSas::KeyRecieved(s) => match s.into_mac_received(e) {
|
|
||||||
Ok(s) => (InnerSas::MacReceived(s), None),
|
|
||||||
Err(s) => {
|
|
||||||
let content = s.as_content();
|
|
||||||
(InnerSas::Canceled(s), Some(content))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
InnerSas::Confirmed(s) => match s.into_done(e) {
|
|
||||||
Ok(s) => (InnerSas::Done(s), None),
|
|
||||||
Err(s) => {
|
|
||||||
let content = s.as_content();
|
|
||||||
(InnerSas::Canceled(s), Some(content))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => (self, None),
|
|
||||||
},
|
|
||||||
_ => (self, None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_be_presented(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
InnerSas::KeyRecieved(_) => true,
|
|
||||||
InnerSas::MacReceived(_) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_done(&self) -> bool {
|
|
||||||
matches!(self, InnerSas::Done(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_canceled(&self) -> bool {
|
|
||||||
matches!(self, InnerSas::Canceled(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timed_out(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
InnerSas::Created(s) => s.timed_out(),
|
|
||||||
InnerSas::Started(s) => s.timed_out(),
|
|
||||||
InnerSas::Canceled(s) => s.timed_out(),
|
|
||||||
InnerSas::Accepted(s) => s.timed_out(),
|
|
||||||
InnerSas::KeyRecieved(s) => s.timed_out(),
|
|
||||||
InnerSas::Confirmed(s) => s.timed_out(),
|
|
||||||
InnerSas::MacReceived(s) => s.timed_out(),
|
|
||||||
InnerSas::Done(s) => s.timed_out(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verification_flow_id(&self) -> Arc<str> {
|
|
||||||
match self {
|
|
||||||
InnerSas::Created(s) => s.verification_flow_id.clone(),
|
|
||||||
InnerSas::Started(s) => s.verification_flow_id.clone(),
|
|
||||||
InnerSas::Canceled(s) => s.verification_flow_id.clone(),
|
|
||||||
InnerSas::Accepted(s) => s.verification_flow_id.clone(),
|
|
||||||
InnerSas::KeyRecieved(s) => s.verification_flow_id.clone(),
|
|
||||||
InnerSas::Confirmed(s) => s.verification_flow_id.clone(),
|
|
||||||
InnerSas::MacReceived(s) => s.verification_flow_id.clone(),
|
|
||||||
InnerSas::Done(s) => s.verification_flow_id.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emoji(&self) -> Option<Vec<(&'static str, &'static str)>> {
|
|
||||||
match self {
|
|
||||||
InnerSas::KeyRecieved(s) => Some(s.get_emoji()),
|
|
||||||
InnerSas::MacReceived(s) => Some(s.get_emoji()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decimals(&self) -> Option<(u16, u16, u16)> {
|
|
||||||
match self {
|
|
||||||
InnerSas::KeyRecieved(s) => Some(s.get_decimal()),
|
|
||||||
InnerSas::MacReceived(s) => Some(s.get_decimal()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verified_devices(&self) -> Option<Arc<[ReadOnlyDevice]>> {
|
|
||||||
if let InnerSas::Done(s) = self {
|
|
||||||
Some(s.verified_devices())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verified_identities(&self) -> Option<Arc<[UserIdentities]>> {
|
|
||||||
if let InnerSas::Done(s) = self {
|
|
||||||
Some(s.verified_identities())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::{convert::TryFrom, sync::Arc};
|
use std::{convert::TryFrom, sync::Arc};
|
||||||
|
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::identifiers::{DeviceId, UserId};
|
||||||
events::{EventContent, ToDeviceEvent},
|
|
||||||
identifiers::{DeviceId, UserId},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
olm::PrivateCrossSigningIdentity,
|
olm::PrivateCrossSigningIdentity,
|
||||||
|
@ -785,7 +667,7 @@ mod test {
|
||||||
ReadOnlyAccount, ReadOnlyDevice,
|
ReadOnlyAccount, ReadOnlyDevice,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{Accepted, Created, Sas, SasState, Started};
|
use super::Sas;
|
||||||
|
|
||||||
fn alice_id() -> UserId {
|
fn alice_id() -> UserId {
|
||||||
UserId::try_from("@alice:example.org").unwrap()
|
UserId::try_from("@alice:example.org").unwrap()
|
||||||
|
@ -803,97 +685,6 @@ mod test {
|
||||||
"BOBDEVCIE".into()
|
"BOBDEVCIE".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_to_device_event<C: EventContent>(sender: &UserId, content: C) -> ToDeviceEvent<C> {
|
|
||||||
ToDeviceEvent {
|
|
||||||
sender: sender.clone(),
|
|
||||||
content,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_sas_pair() -> (SasState<Created>, SasState<Started>) {
|
|
||||||
let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id());
|
|
||||||
let alice_device = ReadOnlyDevice::from_account(&alice).await;
|
|
||||||
|
|
||||||
let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id());
|
|
||||||
let bob_device = ReadOnlyDevice::from_account(&bob).await;
|
|
||||||
|
|
||||||
let alice_sas = SasState::<Created>::new(alice.clone(), bob_device, None);
|
|
||||||
|
|
||||||
let start_content = alice_sas.as_content();
|
|
||||||
let event = wrap_to_device_event(alice_sas.user_id(), start_content);
|
|
||||||
|
|
||||||
let bob_sas =
|
|
||||||
SasState::<Started>::from_start_event(bob.clone(), alice_device, &event, None);
|
|
||||||
|
|
||||||
(alice_sas, bob_sas.unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn create_sas() {
|
|
||||||
let (_, _) = get_sas_pair().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn sas_accept() {
|
|
||||||
let (alice, bob) = get_sas_pair().await;
|
|
||||||
|
|
||||||
let event = wrap_to_device_event(bob.user_id(), bob.as_content());
|
|
||||||
|
|
||||||
alice.into_accepted(&event).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn sas_key_share() {
|
|
||||||
let (alice, bob) = get_sas_pair().await;
|
|
||||||
|
|
||||||
let event = wrap_to_device_event(bob.user_id(), bob.as_content());
|
|
||||||
|
|
||||||
let alice: SasState<Accepted> = alice.into_accepted(&event).unwrap();
|
|
||||||
let mut event = wrap_to_device_event(alice.user_id(), alice.as_content());
|
|
||||||
|
|
||||||
let bob = bob.into_key_received(&mut event).unwrap();
|
|
||||||
|
|
||||||
let mut event = wrap_to_device_event(bob.user_id(), bob.as_content());
|
|
||||||
|
|
||||||
let alice = alice.into_key_received(&mut event).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(alice.get_decimal(), bob.get_decimal());
|
|
||||||
assert_eq!(alice.get_emoji(), bob.get_emoji());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn sas_full() {
|
|
||||||
let (alice, bob) = get_sas_pair().await;
|
|
||||||
|
|
||||||
let event = wrap_to_device_event(bob.user_id(), bob.as_content());
|
|
||||||
|
|
||||||
let alice: SasState<Accepted> = alice.into_accepted(&event).unwrap();
|
|
||||||
let mut event = wrap_to_device_event(alice.user_id(), alice.as_content());
|
|
||||||
|
|
||||||
let bob = bob.into_key_received(&mut event).unwrap();
|
|
||||||
|
|
||||||
let mut event = wrap_to_device_event(bob.user_id(), bob.as_content());
|
|
||||||
|
|
||||||
let alice = alice.into_key_received(&mut event).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(alice.get_decimal(), bob.get_decimal());
|
|
||||||
assert_eq!(alice.get_emoji(), bob.get_emoji());
|
|
||||||
|
|
||||||
let bob = bob.confirm();
|
|
||||||
|
|
||||||
let event = wrap_to_device_event(bob.user_id(), bob.as_content());
|
|
||||||
|
|
||||||
let alice = alice.into_mac_received(&event).unwrap();
|
|
||||||
assert!(!alice.get_emoji().is_empty());
|
|
||||||
let alice = alice.confirm();
|
|
||||||
|
|
||||||
let event = wrap_to_device_event(alice.user_id(), alice.as_content());
|
|
||||||
let bob = bob.into_done(&event).unwrap();
|
|
||||||
|
|
||||||
assert!(bob.verified_devices().contains(&bob.other_device()));
|
|
||||||
assert!(alice.verified_devices().contains(&alice.other_device()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn sas_wrapper_full() {
|
async fn sas_wrapper_full() {
|
||||||
let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id());
|
let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id());
|
||||||
|
@ -916,50 +707,48 @@ mod test {
|
||||||
alice_store,
|
alice_store,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let event = wrap_to_device_event(alice.user_id(), content);
|
|
||||||
|
|
||||||
let bob = Sas::from_start_event(
|
let bob = Sas::from_start_event(
|
||||||
bob,
|
bob,
|
||||||
PrivateCrossSigningIdentity::empty(bob_id()),
|
PrivateCrossSigningIdentity::empty(bob_id()),
|
||||||
alice_device,
|
alice_device,
|
||||||
bob_store,
|
bob_store,
|
||||||
&event,
|
content,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut event = wrap_any_to_device_content(
|
let event = wrap_any_to_device_content(
|
||||||
bob.user_id(),
|
bob.user_id(),
|
||||||
get_content_from_request(&bob.accept().unwrap()),
|
get_content_from_request(&bob.accept().unwrap()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let content = alice.receive_event(&mut event);
|
let content = alice.receive_event(&event);
|
||||||
|
|
||||||
assert!(!alice.can_be_presented());
|
assert!(!alice.can_be_presented());
|
||||||
assert!(!bob.can_be_presented());
|
assert!(!bob.can_be_presented());
|
||||||
|
|
||||||
let mut event = wrap_any_to_device_content(alice.user_id(), content.unwrap());
|
let event = wrap_any_to_device_content(alice.user_id(), content.unwrap());
|
||||||
let mut event =
|
let event = wrap_any_to_device_content(bob.user_id(), bob.receive_event(&event).unwrap());
|
||||||
wrap_any_to_device_content(bob.user_id(), bob.receive_event(&mut event).unwrap());
|
|
||||||
|
|
||||||
assert!(bob.can_be_presented());
|
assert!(bob.can_be_presented());
|
||||||
|
|
||||||
alice.receive_event(&mut event);
|
alice.receive_event(&event);
|
||||||
assert!(alice.can_be_presented());
|
assert!(alice.can_be_presented());
|
||||||
|
|
||||||
assert_eq!(alice.emoji().unwrap(), bob.emoji().unwrap());
|
assert_eq!(alice.emoji().unwrap(), bob.emoji().unwrap());
|
||||||
assert_eq!(alice.decimals().unwrap(), bob.decimals().unwrap());
|
assert_eq!(alice.decimals().unwrap(), bob.decimals().unwrap());
|
||||||
|
|
||||||
let mut event = wrap_any_to_device_content(
|
let event = wrap_any_to_device_content(
|
||||||
alice.user_id(),
|
alice.user_id(),
|
||||||
get_content_from_request(&alice.confirm().await.unwrap().0.unwrap()),
|
get_content_from_request(&alice.confirm().await.unwrap().0.unwrap()),
|
||||||
);
|
);
|
||||||
bob.receive_event(&mut event);
|
bob.receive_event(&event);
|
||||||
|
|
||||||
let mut event = wrap_any_to_device_content(
|
let event = wrap_any_to_device_content(
|
||||||
bob.user_id(),
|
bob.user_id(),
|
||||||
get_content_from_request(&bob.confirm().await.unwrap().0.unwrap()),
|
get_content_from_request(&bob.confirm().await.unwrap().0.unwrap()),
|
||||||
);
|
);
|
||||||
alice.receive_event(&mut event);
|
alice.receive_event(&event);
|
||||||
|
|
||||||
assert!(alice
|
assert!(alice
|
||||||
.verified_devices()
|
.verified_devices()
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -355,6 +355,7 @@ pub enum SyncResponseFile {
|
||||||
DefaultWithSummary,
|
DefaultWithSummary,
|
||||||
Invite,
|
Invite,
|
||||||
Leave,
|
Leave,
|
||||||
|
Voip,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get specific API responses for testing
|
/// Get specific API responses for testing
|
||||||
|
@ -365,6 +366,7 @@ pub fn sync_response(kind: SyncResponseFile) -> SyncResponse {
|
||||||
SyncResponseFile::DefaultWithSummary => &test_json::DEFAULT_SYNC_SUMMARY,
|
SyncResponseFile::DefaultWithSummary => &test_json::DEFAULT_SYNC_SUMMARY,
|
||||||
SyncResponseFile::Invite => &test_json::INVITE_SYNC,
|
SyncResponseFile::Invite => &test_json::INVITE_SYNC,
|
||||||
SyncResponseFile::Leave => &test_json::LEAVE_SYNC,
|
SyncResponseFile::Leave => &test_json::LEAVE_SYNC,
|
||||||
|
SyncResponseFile::Voip => &test_json::VOIP_SYNC,
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = Response::builder()
|
let response = Response::builder()
|
||||||
|
|
|
@ -16,7 +16,9 @@ pub use events::{
|
||||||
REACTION, REDACTED, REDACTED_INVALID, REDACTED_STATE, REDACTION, REGISTRATION_RESPONSE_ERR,
|
REACTION, REDACTED, REDACTED_INVALID, REDACTED_STATE, REDACTION, REGISTRATION_RESPONSE_ERR,
|
||||||
ROOM_ID, ROOM_MESSAGES, TYPING,
|
ROOM_ID, ROOM_MESSAGES, TYPING,
|
||||||
};
|
};
|
||||||
pub use sync::{DEFAULT_SYNC_SUMMARY, INVITE_SYNC, LEAVE_SYNC, LEAVE_SYNC_EVENT, MORE_SYNC, SYNC};
|
pub use sync::{
|
||||||
|
DEFAULT_SYNC_SUMMARY, INVITE_SYNC, LEAVE_SYNC, LEAVE_SYNC_EVENT, MORE_SYNC, SYNC, VOIP_SYNC,
|
||||||
|
};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref DEVICES: JsonValue = json!({
|
pub static ref DEVICES: JsonValue = json!({
|
||||||
|
|
|
@ -1103,3 +1103,123 @@ lazy_static! {
|
||||||
"next_batch": "s1380317562_757269739_1655566_503953763_334052043_1209862_55290918_65705002_101146"
|
"next_batch": "s1380317562_757269739_1655566_503953763_334052043_1209862_55290918_65705002_101146"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref VOIP_SYNC: JsonValue = json!({
|
||||||
|
"device_one_time_keys_count": {},
|
||||||
|
"next_batch": "s526_47314_0_7_1_1_1_11444_1",
|
||||||
|
"device_lists": {
|
||||||
|
"changed": [
|
||||||
|
"@example:example.org"
|
||||||
|
],
|
||||||
|
"left": []
|
||||||
|
},
|
||||||
|
"rooms": {
|
||||||
|
"invite": {},
|
||||||
|
"join": {
|
||||||
|
"!SVkFJHzfwvuaIEawgC:localhost": {
|
||||||
|
"summary": {},
|
||||||
|
"account_data": {
|
||||||
|
"events": []
|
||||||
|
},
|
||||||
|
"ephemeral": {
|
||||||
|
"events": [ ]
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"events": []
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"call_id": "12345",
|
||||||
|
"lifetime": 60000,
|
||||||
|
"offer": {
|
||||||
|
"sdp": "v=0\r\no=- 6584580628695956864 2 IN IP4 127.0.0.1[...]",
|
||||||
|
"type": "offer"
|
||||||
|
},
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"event_id": "$143273582443PhrSn:example.org",
|
||||||
|
"origin_server_ts": 143273582,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.call.invite",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"answer": {
|
||||||
|
"sdp": "v=0\r\no=- 6584580628695956864 2 IN IP4 127.0.0.1[...]",
|
||||||
|
"type": "answer"
|
||||||
|
},
|
||||||
|
"call_id": "12345",
|
||||||
|
"lifetime": 60000,
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"event_id": "$143273582443PhrSn:example.org",
|
||||||
|
"origin_server_ts": 143273582,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.call.answer",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"call_id": "12345",
|
||||||
|
"candidates": [
|
||||||
|
{
|
||||||
|
"candidate": "candidate:863018703 1 udp 2122260223 10.9.64.156 43670 typ host generation 0",
|
||||||
|
"sdpMLineIndex": 0,
|
||||||
|
"sdpMid": "audio"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"event_id": "$143273582443PhrSn:example.org",
|
||||||
|
"origin_server_ts": 143273582,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.call.candidates",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"call_id": "12345",
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"event_id": "$143273582443PhrSn:example.org",
|
||||||
|
"origin_server_ts": 143273582,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.call.hangup",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"limited": true,
|
||||||
|
"prev_batch": "t392-516_47314_0_7_1_1_1_11444_1"
|
||||||
|
},
|
||||||
|
"unread_notifications": {
|
||||||
|
"highlight_count": 0,
|
||||||
|
"notification_count": 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"leave": {}
|
||||||
|
},
|
||||||
|
"to_device": {
|
||||||
|
"events": []
|
||||||
|
},
|
||||||
|
"presence": {
|
||||||
|
"events": []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue