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 matrix_sdk::{
|
||||
self, events::AnyToDeviceEvent, identifiers::UserId, Client, ClientConfig, LoopCtrl, Sas,
|
||||
SyncSettings,
|
||||
self,
|
||||
events::{
|
||||
room::message::MessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent, AnyToDeviceEvent,
|
||||
},
|
||||
identifiers::UserId,
|
||||
Client, ClientConfig, LoopCtrl, Sas, SyncSettings,
|
||||
};
|
||||
|
||||
async fn wait_for_confirmation(client: Client, sas: Sas) {
|
||||
|
@ -68,10 +79,13 @@ async fn login(
|
|||
.await?;
|
||||
|
||||
let client_ref = &client;
|
||||
let initial_sync = Arc::new(AtomicBool::from(true));
|
||||
let initial_ref = &initial_sync;
|
||||
|
||||
client
|
||||
.sync_with_callback(SyncSettings::new(), |response| async move {
|
||||
let client = &client_ref;
|
||||
let initial = &initial_ref;
|
||||
|
||||
for event in &response.to_device.events {
|
||||
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
|
||||
})
|
||||
.await;
|
||||
|
|
|
@ -48,7 +48,7 @@ use matrix_sdk_base::{
|
|||
#[cfg(feature = "encryption")]
|
||||
use matrix_sdk_base::crypto::{
|
||||
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.
|
||||
|
@ -122,6 +122,7 @@ use matrix_sdk_common::{
|
|||
|
||||
use crate::{
|
||||
http_client::{client_with_config, HttpClient, HttpSend},
|
||||
verification_request::VerificationRequest,
|
||||
Error, OutgoingRequest, Result,
|
||||
};
|
||||
|
||||
|
@ -1168,6 +1169,17 @@ impl Client {
|
|||
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.
|
||||
///
|
||||
/// Returns the parsed response from the server.
|
||||
|
@ -1475,7 +1487,10 @@ impl Client {
|
|||
}
|
||||
|
||||
#[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 request = RumaToDeviceRequest::new(
|
||||
request.event_type.clone(),
|
||||
|
@ -1757,6 +1772,14 @@ impl Client {
|
|||
.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
|
||||
.map(|sas| 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 {
|
||||
inner: d,
|
||||
http_client: self.http_client.clone(),
|
||||
client: self.clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -2077,7 +2113,7 @@ impl Client {
|
|||
|
||||
Ok(UserDevices {
|
||||
inner: devices,
|
||||
http_client: self.http_client.clone(),
|
||||
client: self.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -2157,8 +2193,8 @@ impl Client {
|
|||
|
||||
let encrypt = move || -> Result<()> {
|
||||
let export: String = encrypt_key_export(&keys, &passphrase, 500_000)?;
|
||||
let mut file = std::fs::File::create(path).unwrap();
|
||||
file.write_all(&export.into_bytes()).unwrap();
|
||||
let mut file = std::fs::File::create(path)?;
|
||||
file.write_all(&export.into_bytes())?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
|
@ -2222,6 +2258,7 @@ impl Client {
|
|||
};
|
||||
|
||||
let task = tokio::task::spawn_blocking(decrypt);
|
||||
// TODO remove this unwrap.
|
||||
let import = task.await.expect("Task join error").unwrap();
|
||||
|
||||
Ok(olm.import_keys(import).await?)
|
||||
|
|
|
@ -18,18 +18,15 @@ use matrix_sdk_base::crypto::{
|
|||
store::CryptoStoreError, Device as BaseDevice, LocalTrust, ReadOnlyDevice,
|
||||
UserDevices as BaseUserDevices,
|
||||
};
|
||||
use matrix_sdk_common::{
|
||||
api::r0::to_device::send_event_to_device::Request as ToDeviceRequest,
|
||||
identifiers::{DeviceId, DeviceIdBox},
|
||||
};
|
||||
use matrix_sdk_common::identifiers::{DeviceId, DeviceIdBox};
|
||||
|
||||
use crate::{error::Result, http_client::HttpClient, Sas};
|
||||
use crate::{error::Result, Client, Sas};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// A device represents a E2EE capable client of an user.
|
||||
pub struct Device {
|
||||
pub(crate) inner: BaseDevice,
|
||||
pub(crate) http_client: HttpClient,
|
||||
pub(crate) client: Client,
|
||||
}
|
||||
|
||||
impl Deref for Device {
|
||||
|
@ -66,14 +63,11 @@ impl Device {
|
|||
/// ```
|
||||
pub async fn start_verification(&self) -> Result<Sas> {
|
||||
let (sas, request) = self.inner.start_verification().await?;
|
||||
let txn_id_string = request.txn_id_string();
|
||||
let request = ToDeviceRequest::new(request.event_type, &txn_id_string, request.messages);
|
||||
|
||||
self.http_client.send(request).await?;
|
||||
self.client.send_to_device(&request).await?;
|
||||
|
||||
Ok(Sas {
|
||||
inner: sas,
|
||||
http_client: self.http_client.clone(),
|
||||
client: self.client.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -102,7 +96,7 @@ impl Device {
|
|||
#[derive(Debug)]
|
||||
pub struct UserDevices {
|
||||
pub(crate) inner: BaseUserDevices,
|
||||
pub(crate) http_client: HttpClient,
|
||||
pub(crate) client: Client,
|
||||
}
|
||||
|
||||
impl UserDevices {
|
||||
|
@ -110,7 +104,7 @@ impl UserDevices {
|
|||
pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
|
||||
self.inner.get(device_id).map(|d| Device {
|
||||
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.
|
||||
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 {
|
||||
inner: d,
|
||||
http_client: client.clone(),
|
||||
client: client.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,6 +82,8 @@ mod http_client;
|
|||
mod device;
|
||||
#[cfg(feature = "encryption")]
|
||||
mod sas;
|
||||
#[cfg(feature = "encryption")]
|
||||
mod verification_request;
|
||||
|
||||
pub use client::{Client, ClientConfig, LoopCtrl, SyncSettings};
|
||||
#[cfg(feature = "encryption")]
|
||||
|
|
|
@ -12,43 +12,49 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use matrix_sdk_base::crypto::{ReadOnlyDevice, Sas as BaseSas};
|
||||
use matrix_sdk_common::api::r0::to_device::send_event_to_device::Request as ToDeviceRequest;
|
||||
use matrix_sdk_base::crypto::{OutgoingVerificationRequest, ReadOnlyDevice, Sas as BaseSas};
|
||||
|
||||
use crate::{error::Result, http_client::HttpClient};
|
||||
use crate::{error::Result, Client};
|
||||
|
||||
/// An object controling the interactive verification flow.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Sas {
|
||||
pub(crate) inner: BaseSas,
|
||||
pub(crate) http_client: HttpClient,
|
||||
pub(crate) client: Client,
|
||||
}
|
||||
|
||||
impl Sas {
|
||||
/// Accept the interactive verification flow.
|
||||
pub async fn accept(&self) -> Result<()> {
|
||||
if let Some(req) = self.inner.accept() {
|
||||
let txn_id_string = req.txn_id_string();
|
||||
let request = ToDeviceRequest::new(req.event_type, &txn_id_string, req.messages);
|
||||
|
||||
self.http_client.send(request).await?;
|
||||
match req {
|
||||
OutgoingVerificationRequest::ToDevice(r) => {
|
||||
self.client.send_to_device(&r).await?;
|
||||
}
|
||||
OutgoingVerificationRequest::InRoom(_) => todo!(),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Confirm that the short auth strings match on both sides.
|
||||
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 {
|
||||
let txn_id_string = req.txn_id_string();
|
||||
let request = ToDeviceRequest::new(req.event_type, &txn_id_string, req.messages);
|
||||
match request {
|
||||
Some(OutgoingVerificationRequest::InRoom(r)) => {
|
||||
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 {
|
||||
self.http_client.send(s).await?;
|
||||
self.client.send(s).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -56,12 +62,17 @@ impl Sas {
|
|||
|
||||
/// Cancel the interactive verification flow.
|
||||
pub async fn cancel(&self) -> Result<()> {
|
||||
if let Some(req) = self.inner.cancel() {
|
||||
let txn_id_string = req.txn_id_string();
|
||||
let request = ToDeviceRequest::new(req.event_type, &txn_id_string, req.messages);
|
||||
|
||||
self.http_client.send(request).await?;
|
||||
if let Some(request) = self.inner.cancel() {
|
||||
match request {
|
||||
OutgoingVerificationRequest::ToDevice(r) => {
|
||||
self.client.send_to_device(&r).await?;
|
||||
}
|
||||
OutgoingVerificationRequest::InRoom(r) => {
|
||||
self.client.room_send_helper(&r).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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::{
|
||||
events::{
|
||||
call::{
|
||||
answer::AnswerEventContent, candidates::CandidatesEventContent,
|
||||
hangup::HangupEventContent, invite::InviteEventContent,
|
||||
},
|
||||
custom::CustomEventContent,
|
||||
fully_read::FullyReadEventContent,
|
||||
ignored_user_list::IgnoredUserListEventContent,
|
||||
|
@ -143,6 +147,12 @@ impl Emitter {
|
|||
AnySyncMessageEvent::Custom(e) => {
|
||||
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) => {}
|
||||
|
@ -320,6 +330,19 @@ pub trait EventEmitter: Send + Sync {
|
|||
_: &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.
|
||||
async fn on_room_redaction(&self, _: RoomState, _: &SyncRedactionEvent) {}
|
||||
/// Fires when `Client` receives a `RoomEvent::RoomPowerLevels` event.
|
||||
|
@ -507,6 +530,34 @@ mod test {
|
|||
) {
|
||||
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) {
|
||||
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]
|
||||
version = "0.0.1"
|
||||
git = "https://github.com/ruma/ruma"
|
||||
rev = "e8882fe8142d7b55ed4c8ccc6150946945f9e237"
|
||||
rev = "1a4e9aa20abff4786dbb91a19fb72c1dfa4410a7"
|
||||
features = ["client-api", "unstable-pre-spec"]
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
|
|
|
@ -42,6 +42,7 @@ use tracing::warn;
|
|||
use crate::{
|
||||
olm::{InboundGroupSession, PrivateCrossSigningIdentity, Session},
|
||||
store::{Changes, DeviceChanges},
|
||||
OutgoingVerificationRequest,
|
||||
};
|
||||
#[cfg(test)]
|
||||
use crate::{OlmMachine, ReadOnlyAccount};
|
||||
|
@ -91,9 +92,16 @@ impl Device {
|
|||
///
|
||||
/// Returns a `Sas` object and to-device request that needs to be sent out.
|
||||
pub async fn start_verification(&self) -> StoreResult<(Sas, ToDeviceRequest)> {
|
||||
self.verification_machine
|
||||
let (sas, request) = self
|
||||
.verification_machine
|
||||
.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.
|
||||
|
|
|
@ -51,6 +51,7 @@ pub use machine::OlmMachine;
|
|||
pub use olm::EncryptionSettings;
|
||||
pub(crate) use olm::ReadOnlyAccount;
|
||||
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,
|
||||
},
|
||||
identifiers::{
|
||||
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UserId,
|
||||
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, EventId, RoomId,
|
||||
UserId,
|
||||
},
|
||||
js_int::UInt,
|
||||
locks::Mutex,
|
||||
|
@ -44,8 +45,6 @@ use matrix_sdk_common::{
|
|||
Raw,
|
||||
};
|
||||
|
||||
#[cfg(feature = "sqlite_cryptostore")]
|
||||
use crate::store::sqlite::SqliteStore;
|
||||
use crate::{
|
||||
error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult},
|
||||
identities::{Device, IdentityManager, UserDevices},
|
||||
|
@ -64,6 +63,8 @@ use crate::{
|
|||
verification::{Sas, VerificationMachine},
|
||||
ToDeviceRequest,
|
||||
};
|
||||
#[cfg(feature = "sqlite_cryptostore")]
|
||||
use crate::{store::sqlite::SqliteStore, verification::VerificationRequest};
|
||||
|
||||
/// State machine implementation of the Olm/Megolm encryption protocol used for
|
||||
/// Matrix end to end encryption.
|
||||
|
@ -321,6 +322,7 @@ impl OlmMachine {
|
|||
}
|
||||
|
||||
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
|
||||
|
@ -359,6 +361,9 @@ impl OlmMachine {
|
|||
IncomingResponse::SignatureUpload(_) => {
|
||||
self.verification_machine.mark_request_as_sent(request_id);
|
||||
}
|
||||
IncomingResponse::RoomMessage(_) => {
|
||||
self.verification_machine.mark_request_as_sent(request_id);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
|
@ -738,8 +743,8 @@ impl OlmMachine {
|
|||
}
|
||||
}
|
||||
|
||||
async fn handle_verification_event(&self, mut event: &mut AnyToDeviceEvent) {
|
||||
if let Err(e) = self.verification_machine.receive_event(&mut event).await {
|
||||
async fn handle_verification_event(&self, event: &AnyToDeviceEvent) {
|
||||
if let Err(e) = self.verification_machine.receive_event(&event).await {
|
||||
error!("Error handling a verification event: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
@ -767,6 +772,11 @@ impl OlmMachine {
|
|||
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>) {
|
||||
self.account.update_uploaded_key_count(key_count).await;
|
||||
}
|
||||
|
@ -869,7 +879,7 @@ impl OlmMachine {
|
|||
| AnyToDeviceEvent::KeyVerificationMac(..)
|
||||
| AnyToDeviceEvent::KeyVerificationRequest(..)
|
||||
| AnyToDeviceEvent::KeyVerificationStart(..) => {
|
||||
self.handle_verification_event(&mut event).await;
|
||||
self.handle_verification_event(&event).await;
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
|
@ -924,6 +934,12 @@ impl OlmMachine {
|
|||
// TODO set the encryption info on the event (is it verified, was it
|
||||
// decrypted, sender key...)
|
||||
|
||||
if let Ok(e) = decrypted_event.deserialize() {
|
||||
self.verification_machine
|
||||
.receive_room_event(room_id, &e)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(decrypted_event)
|
||||
}
|
||||
|
||||
|
@ -1810,34 +1826,34 @@ pub(crate) mod test {
|
|||
|
||||
let (alice_sas, request) = bob_device.start_verification().await.unwrap();
|
||||
|
||||
let mut event = request_to_event(alice.user_id(), &request);
|
||||
bob.handle_verification_event(&mut event).await;
|
||||
let event = request_to_event(alice.user_id(), &request.into());
|
||||
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!(bob_sas.emoji().is_none());
|
||||
|
||||
let mut event = bob_sas
|
||||
let event = bob_sas
|
||||
.accept()
|
||||
.map(|r| request_to_event(bob.user_id(), &r))
|
||||
.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()
|
||||
.first()
|
||||
.map(|r| outgoing_request_to_event(alice.user_id(), r))
|
||||
.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()
|
||||
.first()
|
||||
.map(|r| outgoing_request_to_event(bob.user_id(), r))
|
||||
.unwrap();
|
||||
alice.handle_verification_event(&mut event).await;
|
||||
alice.handle_verification_event(&event).await;
|
||||
|
||||
assert!(alice_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.decimals(), bob_sas.decimals());
|
||||
|
||||
let mut event = bob_sas
|
||||
let event = bob_sas
|
||||
.confirm()
|
||||
.await
|
||||
.unwrap()
|
||||
.0
|
||||
.map(|r| request_to_event(bob.user_id(), &r))
|
||||
.unwrap();
|
||||
alice.handle_verification_event(&mut event).await;
|
||||
alice.handle_verification_event(&event).await;
|
||||
|
||||
assert!(!alice_sas.is_done());
|
||||
assert!(!bob_sas.is_done());
|
||||
|
||||
let mut event = alice_sas
|
||||
let event = alice_sas
|
||||
.confirm()
|
||||
.await
|
||||
.unwrap()
|
||||
|
@ -1875,7 +1891,7 @@ pub(crate) mod test {
|
|||
.unwrap();
|
||||
|
||||
assert!(!alice_device.is_trusted());
|
||||
bob.handle_verification_event(&mut event).await;
|
||||
bob.handle_verification_event(&event).await;
|
||||
assert!(bob_sas.is_done());
|
||||
assert!(alice_device.is_trusted());
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ use matrix_sdk_common::{
|
|||
instant::Instant,
|
||||
js_int::UInt,
|
||||
locks::Mutex,
|
||||
Raw,
|
||||
CanonicalJsonValue, Raw,
|
||||
};
|
||||
use olm_rs::{
|
||||
account::{IdentityKeys, OlmAccount, OneTimeKeys},
|
||||
|
@ -743,7 +743,7 @@ impl ReadOnlyAccount {
|
|||
.or_insert_with(BTreeMap::new)
|
||||
.insert(
|
||||
DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, &self.device_id),
|
||||
self.sign_json(&json_device_keys).await,
|
||||
self.sign_json(json_device_keys).await,
|
||||
);
|
||||
|
||||
device_keys
|
||||
|
@ -770,8 +770,10 @@ impl ReadOnlyAccount {
|
|||
/// # Panic
|
||||
///
|
||||
/// Panics if the json value can't be serialized.
|
||||
pub async fn sign_json(&self, json: &Value) -> String {
|
||||
self.sign(&json.to_string()).await
|
||||
pub async fn sign_json(&self, json: Value) -> String {
|
||||
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(
|
||||
|
@ -785,7 +787,7 @@ impl ReadOnlyAccount {
|
|||
"key": key,
|
||||
});
|
||||
|
||||
let signature = self.sign_json(&key_json).await;
|
||||
let signature = self.sign_json(key_json).await;
|
||||
|
||||
let mut signature_map = BTreeMap::new();
|
||||
|
||||
|
|
|
@ -13,7 +13,14 @@
|
|||
// limitations under the License.
|
||||
|
||||
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::{
|
||||
cmp::min,
|
||||
fmt,
|
||||
|
@ -240,19 +247,31 @@ impl OutboundGroupSession {
|
|||
"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 ciphertext = self.encrypt_helper(plaintext).await;
|
||||
|
||||
EncryptedEventContent::MegolmV1AesSha2(
|
||||
matrix_sdk_common::events::room::encrypted::MegolmV1AesSha2ContentInit {
|
||||
let mut encrypted_content: MegolmV1AesSha2Content = MegolmV1AesSha2ContentInit {
|
||||
ciphertext,
|
||||
sender_key: self.account_identity_keys.curve25519().to_owned(),
|
||||
session_id: self.session_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.
|
||||
|
|
|
@ -214,7 +214,7 @@ impl PrivateCrossSigningIdentity {
|
|||
master.cross_signing_key(account.user_id().to_owned(), KeyUsage::Master);
|
||||
let signature = account
|
||||
.sign_json(
|
||||
&serde_json::to_value(&public_key)
|
||||
serde_json::to_value(&public_key)
|
||||
.expect("Can't convert own public master key to json"),
|
||||
)
|
||||
.await;
|
||||
|
|
|
@ -23,7 +23,7 @@ use matrix_sdk_common::{
|
|||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
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 zeroize::Zeroizing;
|
||||
|
||||
|
@ -36,6 +36,7 @@ use matrix_sdk_common::{
|
|||
api::r0::keys::{CrossSigningKey, KeyUsage},
|
||||
identifiers::UserId,
|
||||
locks::Mutex,
|
||||
CanonicalJsonValue,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
@ -404,8 +405,9 @@ impl Signing {
|
|||
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.remove("signatures");
|
||||
let canonical_json = serde_json::to_string(json_object)?;
|
||||
Ok(self.sign(&canonical_json).await)
|
||||
let canonical_json: CanonicalJsonValue =
|
||||
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 {
|
||||
|
|
|
@ -14,8 +14,12 @@
|
|||
|
||||
use olm_rs::utility::OlmUtility;
|
||||
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;
|
||||
|
||||
|
@ -63,11 +67,12 @@ impl Utility {
|
|||
let unsigned = json_object.remove("unsigned");
|
||||
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 {
|
||||
json_object.insert("unsigned".to_string(), u);
|
||||
}
|
||||
let canonical_json: String = canonical_json.to_string();
|
||||
|
||||
let signatures = signatures.ok_or(SignatureError::NoSignatureFound)?;
|
||||
let signature_object = signatures
|
||||
|
@ -81,18 +86,66 @@ impl Utility {
|
|||
.ok_or(SignatureError::NoSignatureFound)?;
|
||||
let signature = signature.as_str().ok_or(SignatureError::NoSignatureFound)?;
|
||||
|
||||
let ret = if self
|
||||
let ret = match self
|
||||
.inner
|
||||
.ed25519_verify(signing_key, &canonical_json, signature)
|
||||
.is_ok()
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SignatureError::VerificationError)
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => 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);
|
||||
|
||||
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
|
||||
// limitations under the License.
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::{collections::BTreeMap, sync::Arc, time::Duration};
|
||||
|
||||
use matrix_sdk_common::{
|
||||
|
@ -26,10 +28,11 @@ use matrix_sdk_common::{
|
|||
upload_signing_keys::Response as SigningKeysUploadResponse,
|
||||
CrossSigningKey,
|
||||
},
|
||||
message::send_message_event::Response as RoomMessageResponse,
|
||||
to_device::{send_event_to_device::Response as ToDeviceResponse, DeviceIdOrAllDevices},
|
||||
},
|
||||
events::EventType,
|
||||
identifiers::{DeviceIdBox, UserId},
|
||||
events::{AnyMessageEventContent, EventType},
|
||||
identifiers::{DeviceIdBox, RoomId, UserId},
|
||||
uuid::Uuid,
|
||||
};
|
||||
|
||||
|
@ -120,6 +123,7 @@ pub enum OutgoingRequests {
|
|||
/// Signature upload request, this request is used after a successful device
|
||||
/// or user verification is done.
|
||||
SignatureUpload(SignatureUploadRequest),
|
||||
RoomMessage(RoomMessageRequest),
|
||||
}
|
||||
|
||||
#[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 {
|
||||
fn from(request: SignatureUploadRequest) -> Self {
|
||||
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.
|
||||
#[derive(Debug)]
|
||||
pub enum IncomingResponse<'a> {
|
||||
|
@ -176,6 +204,7 @@ pub enum IncomingResponse<'a> {
|
|||
/// The cross signing keys upload response, marking our private cross
|
||||
/// signing identity as shared.
|
||||
SignatureUpload(&'a SignatureUploadResponse),
|
||||
RoomMessage(&'a RoomMessageResponse),
|
||||
}
|
||||
|
||||
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> {
|
||||
fn from(response: &'a KeysClaimResponse) -> Self {
|
||||
IncomingResponse::KeysClaim(response)
|
||||
|
@ -230,3 +265,55 @@ impl OutgoingRequest {
|
|||
&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
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{convert::TryFrom, sync::Arc};
|
||||
|
||||
use dashmap::DashMap;
|
||||
|
||||
use matrix_sdk_common::locks::Mutex;
|
||||
use tracing::{trace, warn};
|
||||
use tracing::{info, trace, warn};
|
||||
|
||||
use matrix_sdk_common::{
|
||||
events::{AnyToDeviceEvent, AnyToDeviceEventContent},
|
||||
identifiers::{DeviceId, UserId},
|
||||
events::{
|
||||
room::message::MessageEventContent, AnyMessageEvent, AnySyncMessageEvent, AnySyncRoomEvent,
|
||||
AnyToDeviceEvent,
|
||||
},
|
||||
identifiers::{DeviceId, EventId, RoomId, UserId},
|
||||
locks::Mutex,
|
||||
uuid::Uuid,
|
||||
};
|
||||
|
||||
use super::sas::{content_to_request, Sas, VerificationResult};
|
||||
use super::{
|
||||
requests::VerificationRequest,
|
||||
sas::{content_to_request, OutgoingContent, Sas, VerificationResult},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
olm::PrivateCrossSigningIdentity,
|
||||
requests::{OutgoingRequest, ToDeviceRequest},
|
||||
requests::OutgoingRequest,
|
||||
store::{CryptoStore, CryptoStoreError},
|
||||
ReadOnlyAccount, ReadOnlyDevice,
|
||||
OutgoingVerificationRequest, ReadOnlyAccount, ReadOnlyDevice, RoomMessageRequest,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -39,7 +46,10 @@ pub struct VerificationMachine {
|
|||
private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
|
||||
pub(crate) store: Arc<Box<dyn CryptoStore>>,
|
||||
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_room_messages: Arc<DashMap<Uuid, OutgoingRequest>>,
|
||||
}
|
||||
|
||||
impl VerificationMachine {
|
||||
|
@ -52,15 +62,18 @@ impl VerificationMachine {
|
|||
account,
|
||||
private_identity: identity,
|
||||
store,
|
||||
verifications: Arc::new(DashMap::new()),
|
||||
outgoing_to_device_messages: Arc::new(DashMap::new()),
|
||||
verifications: DashMap::new().into(),
|
||||
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(
|
||||
&self,
|
||||
device: ReadOnlyDevice,
|
||||
) -> Result<(Sas, ToDeviceRequest), CryptoStoreError> {
|
||||
) -> Result<(Sas, OutgoingVerificationRequest), CryptoStoreError> {
|
||||
let identity = self.store.get_user_identity(device.user_id()).await?;
|
||||
let private_identity = self.private_identity.lock().await.clone();
|
||||
|
||||
|
@ -72,30 +85,56 @@ impl VerificationMachine {
|
|||
identity,
|
||||
);
|
||||
|
||||
let request = content_to_request(
|
||||
device.user_id(),
|
||||
device.device_id(),
|
||||
AnyToDeviceEventContent::KeyVerificationStart(content),
|
||||
);
|
||||
let request = match content.into() {
|
||||
OutgoingContent::Room(r, c) => RoomMessageRequest {
|
||||
room_id: r,
|
||||
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
|
||||
.insert(sas.flow_id().to_owned(), sas.clone());
|
||||
.insert(sas.flow_id().as_str().to_owned(), sas.clone());
|
||||
|
||||
request.into()
|
||||
}
|
||||
};
|
||||
|
||||
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> {
|
||||
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)]
|
||||
self.verifications.get(transaction_id).map(|s| s.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn queue_up_content(
|
||||
&self,
|
||||
recipient: &UserId,
|
||||
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 = OutgoingRequest {
|
||||
|
@ -106,16 +145,51 @@ impl VerificationMachine {
|
|||
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) {
|
||||
self.queue_up_content(sas.other_user_id(), sas.other_device_id(), c);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark_request_as_sent(&self, uuid: &Uuid) {
|
||||
self.outgoing_room_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> {
|
||||
#[allow(clippy::map_clone)]
|
||||
self.outgoing_to_device_messages
|
||||
|
@ -131,9 +205,9 @@ impl VerificationMachine {
|
|||
for sas in self.verifications.iter() {
|
||||
if let Some(r) = sas.cancel_if_timed_out() {
|
||||
self.outgoing_to_device_messages.insert(
|
||||
r.txn_id,
|
||||
r.request_id(),
|
||||
OutgoingRequest {
|
||||
request_id: r.txn_id,
|
||||
request_id: r.request_id(),
|
||||
request: Arc::new(r.into()),
|
||||
},
|
||||
);
|
||||
|
@ -141,10 +215,159 @@ impl VerificationMachine {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn receive_event(
|
||||
pub async fn receive_room_event(
|
||||
&self,
|
||||
event: &mut AnyToDeviceEvent,
|
||||
room_id: &RoomId,
|
||||
event: &AnySyncRoomEvent,
|
||||
) -> 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);
|
||||
|
||||
match event {
|
||||
|
@ -166,7 +389,7 @@ impl VerificationMachine {
|
|||
private_identity,
|
||||
d,
|
||||
self.store.clone(),
|
||||
e,
|
||||
e.content.clone(),
|
||||
self.store.get_user_identity(&e.sender).await?,
|
||||
) {
|
||||
Ok(s) => {
|
||||
|
@ -210,9 +433,9 @@ impl VerificationMachine {
|
|||
VerificationResult::Ok => (),
|
||||
VerificationResult::Cancel(r) => {
|
||||
self.outgoing_to_device_messages.insert(
|
||||
r.txn_id,
|
||||
r.request_id(),
|
||||
OutgoingRequest {
|
||||
request_id: r.txn_id,
|
||||
request_id: r.request_id(),
|
||||
request: Arc::new(r.into()),
|
||||
},
|
||||
);
|
||||
|
@ -248,7 +471,6 @@ mod test {
|
|||
};
|
||||
|
||||
use matrix_sdk_common::{
|
||||
events::AnyToDeviceEventContent,
|
||||
identifiers::{DeviceId, UserId},
|
||||
locks::Mutex,
|
||||
};
|
||||
|
@ -300,10 +522,11 @@ mod test {
|
|||
bob_store,
|
||||
None,
|
||||
);
|
||||
|
||||
machine
|
||||
.receive_event(&mut wrap_any_to_device_content(
|
||||
.receive_event(&wrap_any_to_device_content(
|
||||
bob_sas.user_id(),
|
||||
AnyToDeviceEventContent::KeyVerificationStart(start_content),
|
||||
start_content.into(),
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -323,20 +546,20 @@ mod test {
|
|||
async fn full_flow() {
|
||||
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()
|
||||
.map(|c| wrap_any_to_device_content(alice.user_id(), get_content_from_request(&c)))
|
||||
.unwrap();
|
||||
|
||||
let mut event = bob
|
||||
.receive_event(&mut event)
|
||||
let event = bob
|
||||
.receive_event(&event)
|
||||
.map(|c| wrap_any_to_device_content(bob.user_id(), c))
|
||||
.unwrap();
|
||||
|
||||
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());
|
||||
|
||||
let request = alice_machine
|
||||
|
@ -348,33 +571,34 @@ mod test {
|
|||
let txn_id = *request.request_id();
|
||||
|
||||
let r = if let OutgoingRequests::ToDeviceRequest(r) = request.request() {
|
||||
r
|
||||
r.clone()
|
||||
} else {
|
||||
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);
|
||||
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!(bob.emoji().is_some());
|
||||
|
||||
assert_eq!(alice.emoji(), bob.emoji());
|
||||
|
||||
let mut event = wrap_any_to_device_content(
|
||||
let event = wrap_any_to_device_content(
|
||||
alice.user_id(),
|
||||
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(),
|
||||
get_content_from_request(&bob.confirm().await.unwrap().0.unwrap()),
|
||||
);
|
||||
alice.receive_event(&mut event);
|
||||
alice.receive_event(&event);
|
||||
|
||||
assert!(alice.is_done());
|
||||
assert!(bob.is_done());
|
||||
|
@ -384,7 +608,7 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn timing_out() {
|
||||
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_machine.outgoing_to_device_messages.is_empty());
|
||||
|
|
|
@ -13,14 +13,19 @@
|
|||
// limitations under the License.
|
||||
|
||||
mod machine;
|
||||
mod requests;
|
||||
mod sas;
|
||||
|
||||
pub use machine::VerificationMachine;
|
||||
pub use requests::VerificationRequest;
|
||||
pub use sas::{Sas, VerificationResult};
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test {
|
||||
use crate::requests::{OutgoingRequest, OutgoingRequests, ToDeviceRequest};
|
||||
use crate::{
|
||||
requests::{OutgoingRequest, OutgoingRequests},
|
||||
OutgoingVerificationRequest,
|
||||
};
|
||||
use serde_json::Value;
|
||||
|
||||
use matrix_sdk_common::{
|
||||
|
@ -28,7 +33,12 @@ pub(crate) mod test {
|
|||
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);
|
||||
wrap_any_to_device_content(sender, content)
|
||||
}
|
||||
|
@ -38,15 +48,21 @@ pub(crate) mod test {
|
|||
request: &OutgoingRequest,
|
||||
) -> AnyToDeviceEvent {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn wrap_any_to_device_content(
|
||||
sender: &UserId,
|
||||
content: AnyToDeviceEventContent,
|
||||
content: OutgoingContent,
|
||||
) -> AnyToDeviceEvent {
|
||||
let content = if let OutgoingContent::ToDevice(c) = content {
|
||||
c
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
match content {
|
||||
AnyToDeviceEventContent::KeyVerificationKey(c) => {
|
||||
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(
|
||||
request
|
||||
.messages
|
||||
|
@ -109,5 +133,6 @@ pub(crate) mod test {
|
|||
),
|
||||
_ => 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,
|
||||
events::{
|
||||
key::verification::{
|
||||
cancel::CancelCode, mac::MacToDeviceEventContent, start::StartToDeviceEventContent,
|
||||
cancel::CancelCode,
|
||||
mac::{MacEventContent, MacToDeviceEventContent},
|
||||
Relation,
|
||||
},
|
||||
AnyToDeviceEventContent, EventType, ToDeviceEvent,
|
||||
AnyToDeviceEventContent, EventType,
|
||||
},
|
||||
identifiers::{DeviceId, DeviceKeyAlgorithm, DeviceKeyId, UserId},
|
||||
uuid::Uuid,
|
||||
CanonicalJsonValue,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
@ -38,6 +39,11 @@ use crate::{
|
|||
ReadOnlyAccount, ToDeviceRequest,
|
||||
};
|
||||
|
||||
use super::{
|
||||
event_enums::{MacContent, StartContent},
|
||||
sas_state::FlowId,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SasIds {
|
||||
pub account: ReadOnlyAccount,
|
||||
|
@ -55,15 +61,12 @@ pub struct SasIds {
|
|||
///
|
||||
/// * `content` - The `m.key.verification.start` event content that started the
|
||||
/// interactive verification process.
|
||||
pub fn calculate_commitment(public_key: &str, content: &StartToDeviceEventContent) -> String {
|
||||
let json_content: CanonicalJsonValue = serde_json::to_value(content)
|
||||
.expect("Can't serialize content")
|
||||
.try_into()
|
||||
.expect("Can't canonicalize content");
|
||||
pub fn calculate_commitment(public_key: &str, content: impl Into<StartContent>) -> String {
|
||||
let content = content.into().canonical_json();
|
||||
|
||||
encode(
|
||||
Sha256::new()
|
||||
.chain(&format!("{}{}", public_key, json_content))
|
||||
.chain(&format!("{}{}", public_key, content))
|
||||
.finalize(),
|
||||
)
|
||||
}
|
||||
|
@ -187,7 +190,8 @@ pub fn receive_mac_event(
|
|||
sas: &OlmSas,
|
||||
ids: &SasIds,
|
||||
flow_id: &str,
|
||||
event: &ToDeviceEvent<MacToDeviceEventContent>,
|
||||
sender: &UserId,
|
||||
content: &MacContent,
|
||||
) -> Result<(Vec<ReadOnlyDevice>, Vec<UserIdentities>), CancelCode> {
|
||||
let mut verified_devices = Vec::new();
|
||||
let mut verified_identities = Vec::new();
|
||||
|
@ -196,25 +200,25 @@ pub fn receive_mac_event(
|
|||
|
||||
trace!(
|
||||
"Received a key.verification.mac event from {} {}",
|
||||
event.sender,
|
||||
sender,
|
||||
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();
|
||||
let keys = sas
|
||||
.calculate_mac(&keys.join(","), &format!("{}KEY_IDS", &info))
|
||||
.expect("Can't calculate SAS MAC");
|
||||
|
||||
if keys != event.content.keys {
|
||||
if keys != content.keys() {
|
||||
return Err(CancelCode::KeyMismatch);
|
||||
}
|
||||
|
||||
for (key_id, key_mac) in &event.content.mac {
|
||||
for (key_id, key_mac) in content.mac() {
|
||||
trace!(
|
||||
"Checking MAC for the key id {} from {} {}",
|
||||
key_id,
|
||||
event.sender,
|
||||
sender,
|
||||
ids.other_device.device_id()
|
||||
);
|
||||
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))
|
||||
.expect("Can't calculate SAS MAC")
|
||||
{
|
||||
trace!(
|
||||
"Successfully verified the device key {} from {}",
|
||||
key_id,
|
||||
sender
|
||||
);
|
||||
|
||||
verified_devices.push(ids.other_device.clone());
|
||||
} else {
|
||||
return Err(CancelCode::KeyMismatch);
|
||||
|
@ -244,7 +254,7 @@ pub fn receive_mac_event(
|
|||
trace!(
|
||||
"Successfully verified the master key {} from {}",
|
||||
key_id,
|
||||
event.sender
|
||||
sender
|
||||
);
|
||||
verified_identities.push(identity.clone())
|
||||
} else {
|
||||
|
@ -256,7 +266,7 @@ pub fn receive_mac_event(
|
|||
"Key ID {} in MAC event from {} {} doesn't belong to any device \
|
||||
or user identity",
|
||||
key_id,
|
||||
event.sender,
|
||||
sender,
|
||||
ids.other_device.device_id()
|
||||
);
|
||||
}
|
||||
|
@ -298,12 +308,12 @@ fn extra_mac_info_send(ids: &SasIds, flow_id: &str) -> String {
|
|||
/// # Panics
|
||||
///
|
||||
/// 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 key_id = DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, ids.account.device_id());
|
||||
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(
|
||||
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))
|
||||
.expect("Can't calculate SAS MAC");
|
||||
|
||||
MacToDeviceEventContent {
|
||||
transaction_id: flow_id.to_owned(),
|
||||
match flow_id {
|
||||
FlowId::ToDevice(s) => MacToDeviceEventContent {
|
||||
transaction_id: s.to_string(),
|
||||
keys,
|
||||
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
|
||||
|
@ -546,7 +570,7 @@ mod test {
|
|||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
// limitations under the License.
|
||||
|
||||
mod event_enums;
|
||||
mod helpers;
|
||||
mod inner_sas;
|
||||
mod sas_state;
|
||||
|
||||
#[cfg(test)]
|
||||
use std::time::Instant;
|
||||
|
||||
use event_enums::AcceptContent;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tracing::{error, info, trace, warn};
|
||||
|
||||
use matrix_sdk_common::{
|
||||
api::r0::keys::upload_signatures::Request as SignatureUploadRequest,
|
||||
events::{
|
||||
key::verification::{
|
||||
accept::AcceptToDeviceEventContent, cancel::CancelCode, mac::MacToDeviceEventContent,
|
||||
start::StartToDeviceEventContent,
|
||||
key::verification::cancel::CancelCode, AnyMessageEvent, AnyMessageEventContent,
|
||||
AnyToDeviceEvent, AnyToDeviceEventContent,
|
||||
},
|
||||
AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent,
|
||||
},
|
||||
identifiers::{DeviceId, UserId},
|
||||
identifiers::{DeviceId, EventId, RoomId, UserId},
|
||||
uuid::Uuid,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::SignatureError,
|
||||
identities::{LocalTrust, ReadOnlyDevice, UserIdentities},
|
||||
olm::PrivateCrossSigningIdentity,
|
||||
requests::{OutgoingVerificationRequest, RoomMessageRequest},
|
||||
store::{Changes, CryptoStore, CryptoStoreError, DeviceChanges},
|
||||
ReadOnlyAccount, ToDeviceRequest,
|
||||
};
|
||||
|
||||
pub use helpers::content_to_request;
|
||||
use sas_state::{
|
||||
Accepted, Canceled, Confirmed, Created, Done, KeyReceived, MacReceived, SasState, Started,
|
||||
};
|
||||
use inner_sas::InnerSas;
|
||||
pub use sas_state::FlowId;
|
||||
|
||||
pub use event_enums::{OutgoingContent, StartContent};
|
||||
|
||||
use self::event_enums::CancelContent;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// A result of a verification flow.
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum VerificationResult {
|
||||
/// The verification succeeded, nothing needs to be done.
|
||||
Ok,
|
||||
/// The verification was canceled.
|
||||
Cancel(ToDeviceRequest),
|
||||
Cancel(OutgoingVerificationRequest),
|
||||
/// The verification is done and has signatures that need to be uploaded.
|
||||
SignatureUpload(SignatureUploadRequest),
|
||||
}
|
||||
|
@ -66,7 +72,7 @@ pub struct Sas {
|
|||
private_identity: PrivateCrossSigningIdentity,
|
||||
other_device: ReadOnlyDevice,
|
||||
other_identity: Option<UserIdentities>,
|
||||
flow_id: Arc<str>,
|
||||
flow_id: Arc<FlowId>,
|
||||
}
|
||||
|
||||
impl Sas {
|
||||
|
@ -96,7 +102,7 @@ impl Sas {
|
|||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
|
@ -106,6 +112,27 @@ impl Sas {
|
|||
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.
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -122,25 +149,65 @@ impl Sas {
|
|||
other_device: ReadOnlyDevice,
|
||||
store: Arc<Box<dyn CryptoStore>>,
|
||||
other_identity: Option<UserIdentities>,
|
||||
) -> (Sas, StartToDeviceEventContent) {
|
||||
) -> (Sas, StartContent) {
|
||||
let (inner, content) = InnerSas::start(
|
||||
account.clone(),
|
||||
other_device.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,
|
||||
private_identity,
|
||||
store,
|
||||
other_device,
|
||||
flow_id,
|
||||
store,
|
||||
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.
|
||||
|
@ -158,15 +225,16 @@ impl Sas {
|
|||
private_identity: PrivateCrossSigningIdentity,
|
||||
other_device: ReadOnlyDevice,
|
||||
store: Arc<Box<dyn CryptoStore>>,
|
||||
event: &ToDeviceEvent<StartToDeviceEventContent>,
|
||||
content: impl Into<StartContent>,
|
||||
other_identity: Option<UserIdentities>,
|
||||
) -> Result<Sas, AnyToDeviceEventContent> {
|
||||
) -> Result<Sas, OutgoingContent> {
|
||||
let inner = InnerSas::from_start_event(
|
||||
account.clone(),
|
||||
other_device.clone(),
|
||||
event,
|
||||
content,
|
||||
other_identity.clone(),
|
||||
)?;
|
||||
|
||||
let flow_id = inner.verification_flow_id();
|
||||
|
||||
Ok(Sas {
|
||||
|
@ -184,10 +252,18 @@ impl Sas {
|
|||
///
|
||||
/// This does nothing if the verification was already accepted, otherwise it
|
||||
/// returns an `AcceptEventContent` that needs to be sent out.
|
||||
pub fn accept(&self) -> Option<ToDeviceRequest> {
|
||||
self.inner.lock().unwrap().accept().map(|c| {
|
||||
pub fn accept(&self) -> Option<OutgoingVerificationRequest> {
|
||||
self.inner.lock().unwrap().accept().map(|c| match c {
|
||||
AcceptContent::ToDevice(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.
|
||||
pub async fn confirm(
|
||||
&self,
|
||||
) -> Result<(Option<ToDeviceRequest>, Option<SignatureUploadRequest>), CryptoStoreError> {
|
||||
) -> Result<
|
||||
(
|
||||
Option<OutgoingVerificationRequest>,
|
||||
Option<SignatureUploadRequest>,
|
||||
),
|
||||
CryptoStoreError,
|
||||
> {
|
||||
let (content, done) = {
|
||||
let mut guard = self.inner.lock().unwrap();
|
||||
let sas: InnerSas = (*guard).clone();
|
||||
|
@ -210,8 +292,17 @@ impl Sas {
|
|||
(content, guard.is_done())
|
||||
};
|
||||
|
||||
let mac_request = content
|
||||
.map(|c| self.content_to_request(AnyToDeviceEventContent::KeyVerificationMac(c)));
|
||||
let mac_request = content.map(|c| match 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 {
|
||||
match self.mark_as_done().await? {
|
||||
|
@ -452,16 +543,26 @@ impl Sas {
|
|||
///
|
||||
/// Returns None if the `Sas` object is already in a canceled state,
|
||||
/// 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 sas: InnerSas = (*guard).clone();
|
||||
let (sas, content) = sas.cancel(CancelCode::User);
|
||||
*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() {
|
||||
None
|
||||
} else if self.timed_out() {
|
||||
|
@ -469,7 +570,17 @@ impl Sas {
|
|||
let sas: InnerSas = (*guard).clone();
|
||||
let (sas, content) = sas.cancel(CancelCode::Timeout);
|
||||
*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 {
|
||||
None
|
||||
}
|
||||
|
@ -512,10 +623,16 @@ impl Sas {
|
|||
self.inner.lock().unwrap().decimals()
|
||||
}
|
||||
|
||||
pub(crate) fn receive_event(
|
||||
&self,
|
||||
event: &mut AnyToDeviceEvent,
|
||||
) -> Option<AnyToDeviceEventContent> {
|
||||
pub(crate) fn receive_room_event(&self, event: &AnyMessageEvent) -> Option<OutgoingContent> {
|
||||
let mut guard = self.inner.lock().unwrap();
|
||||
let sas: InnerSas = (*guard).clone();
|
||||
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 sas: InnerSas = (*guard).clone();
|
||||
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)]
|
||||
mod test {
|
||||
use std::{convert::TryFrom, sync::Arc};
|
||||
|
||||
use matrix_sdk_common::{
|
||||
events::{EventContent, ToDeviceEvent},
|
||||
identifiers::{DeviceId, UserId},
|
||||
};
|
||||
use matrix_sdk_common::identifiers::{DeviceId, UserId};
|
||||
|
||||
use crate::{
|
||||
olm::PrivateCrossSigningIdentity,
|
||||
|
@ -785,7 +667,7 @@ mod test {
|
|||
ReadOnlyAccount, ReadOnlyDevice,
|
||||
};
|
||||
|
||||
use super::{Accepted, Created, Sas, SasState, Started};
|
||||
use super::Sas;
|
||||
|
||||
fn alice_id() -> UserId {
|
||||
UserId::try_from("@alice:example.org").unwrap()
|
||||
|
@ -803,97 +685,6 @@ mod test {
|
|||
"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]
|
||||
async fn sas_wrapper_full() {
|
||||
let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id());
|
||||
|
@ -916,50 +707,48 @@ mod test {
|
|||
alice_store,
|
||||
None,
|
||||
);
|
||||
let event = wrap_to_device_event(alice.user_id(), content);
|
||||
|
||||
let bob = Sas::from_start_event(
|
||||
bob,
|
||||
PrivateCrossSigningIdentity::empty(bob_id()),
|
||||
alice_device,
|
||||
bob_store,
|
||||
&event,
|
||||
content,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let mut event = wrap_any_to_device_content(
|
||||
let event = wrap_any_to_device_content(
|
||||
bob.user_id(),
|
||||
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!(!bob.can_be_presented());
|
||||
|
||||
let mut event = wrap_any_to_device_content(alice.user_id(), content.unwrap());
|
||||
let mut event =
|
||||
wrap_any_to_device_content(bob.user_id(), bob.receive_event(&mut event).unwrap());
|
||||
let event = wrap_any_to_device_content(alice.user_id(), content.unwrap());
|
||||
let event = wrap_any_to_device_content(bob.user_id(), bob.receive_event(&event).unwrap());
|
||||
|
||||
assert!(bob.can_be_presented());
|
||||
|
||||
alice.receive_event(&mut event);
|
||||
alice.receive_event(&event);
|
||||
assert!(alice.can_be_presented());
|
||||
|
||||
assert_eq!(alice.emoji().unwrap(), bob.emoji().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(),
|
||||
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(),
|
||||
get_content_from_request(&bob.confirm().await.unwrap().0.unwrap()),
|
||||
);
|
||||
alice.receive_event(&mut event);
|
||||
alice.receive_event(&event);
|
||||
|
||||
assert!(alice
|
||||
.verified_devices()
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -355,6 +355,7 @@ pub enum SyncResponseFile {
|
|||
DefaultWithSummary,
|
||||
Invite,
|
||||
Leave,
|
||||
Voip,
|
||||
}
|
||||
|
||||
/// 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::Invite => &test_json::INVITE_SYNC,
|
||||
SyncResponseFile::Leave => &test_json::LEAVE_SYNC,
|
||||
SyncResponseFile::Voip => &test_json::VOIP_SYNC,
|
||||
};
|
||||
|
||||
let response = Response::builder()
|
||||
|
|
|
@ -16,7 +16,9 @@ pub use events::{
|
|||
REACTION, REDACTED, REDACTED_INVALID, REDACTED_STATE, REDACTION, REGISTRATION_RESPONSE_ERR,
|
||||
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! {
|
||||
pub static ref DEVICES: JsonValue = json!({
|
||||
|
|
|
@ -1103,3 +1103,123 @@ lazy_static! {
|
|||
"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