diff --git a/matrix_sdk/examples/emoji_verification.rs b/matrix_sdk/examples/emoji_verification.rs index 1f0e679a..2dfc1a1e 100644 --- a/matrix_sdk/examples/emoji_verification.rs +++ b/matrix_sdk/examples/emoji_verification.rs @@ -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; diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index f63301f3..2a17a423 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -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 { + 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 { + pub(crate) async fn send_to_device( + &self, + request: &ToDeviceRequest, + ) -> Result { 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 { + 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?) diff --git a/matrix_sdk/src/device.rs b/matrix_sdk/src/device.rs index 776b9f0e..39274896 100644 --- a/matrix_sdk/src/device.rs +++ b/matrix_sdk/src/device.rs @@ -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 { 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 { 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 + '_ { - 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(), }) } } diff --git a/matrix_sdk/src/lib.rs b/matrix_sdk/src/lib.rs index dc310d11..8018aa6f 100644 --- a/matrix_sdk/src/lib.rs +++ b/matrix_sdk/src/lib.rs @@ -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")] diff --git a/matrix_sdk/src/sas.rs b/matrix_sdk/src/sas.rs index 3db1df47..ccbc4224 100644 --- a/matrix_sdk/src/sas.rs +++ b/matrix_sdk/src/sas.rs @@ -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(()) } diff --git a/matrix_sdk/src/verification_request.rs b/matrix_sdk/src/verification_request.rs new file mode 100644 index 00000000..88c5ddd0 --- /dev/null +++ b/matrix_sdk/src/verification_request.rs @@ -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(()) + } +} diff --git a/matrix_sdk_base/src/event_emitter/mod.rs b/matrix_sdk_base/src/event_emitter/mod.rs index d2481c44..d9a0563a 100644 --- a/matrix_sdk_base/src/event_emitter/mod.rs +++ b/matrix_sdk_base/src/event_emitter/mod.rs @@ -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, ) { } + /// Fires when `Client` receives a `RoomEvent::CallInvite` event + async fn on_room_call_invite(&self, _: RoomState, _: &SyncMessageEvent) {} + /// Fires when `Client` receives a `RoomEvent::CallAnswer` event + async fn on_room_call_answer(&self, _: RoomState, _: &SyncMessageEvent) {} + /// Fires when `Client` receives a `RoomEvent::CallCandidates` event + async fn on_room_call_candidates( + &self, + _: RoomState, + _: &SyncMessageEvent, + ) { + } + /// Fires when `Client` receives a `RoomEvent::CallHangup` event + async fn on_room_call_hangup(&self, _: RoomState, _: &SyncMessageEvent) {} /// 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, + ) { + self.0.lock().await.push("call invite".to_string()) + } + async fn on_room_call_answer( + &self, + _: RoomState, + _: &SyncMessageEvent, + ) { + self.0.lock().await.push("call answer".to_string()) + } + async fn on_room_call_candidates( + &self, + _: RoomState, + _: &SyncMessageEvent, + ) { + self.0.lock().await.push("call candidates".to_string()) + } + async fn on_room_call_hangup( + &self, + _: RoomState, + _: &SyncMessageEvent, + ) { + 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", + ], + ) + } } diff --git a/matrix_sdk_common/Cargo.toml b/matrix_sdk_common/Cargo.toml index d3c6dcb7..2067d1f4 100644 --- a/matrix_sdk_common/Cargo.toml +++ b/matrix_sdk_common/Cargo.toml @@ -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] diff --git a/matrix_sdk_crypto/src/identities/device.rs b/matrix_sdk_crypto/src/identities/device.rs index 69e766c1..f732cc19 100644 --- a/matrix_sdk_crypto/src/identities/device.rs +++ b/matrix_sdk_crypto/src/identities/device.rs @@ -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. diff --git a/matrix_sdk_crypto/src/lib.rs b/matrix_sdk_crypto/src/lib.rs index f37cf97e..e4262e3c 100644 --- a/matrix_sdk_crypto/src/lib.rs +++ b/matrix_sdk_crypto/src/lib.rs @@ -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}; diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 2cdab741..2a731a5b 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -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 { + self.verification_machine.get_request(flow_id) + } + async fn update_one_time_key_count(&self, key_count: &BTreeMap) { 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()); } diff --git a/matrix_sdk_crypto/src/olm/account.rs b/matrix_sdk_crypto/src/olm/account.rs index 9b6551bd..c619ffeb 100644 --- a/matrix_sdk_crypto/src/olm/account.rs +++ b/matrix_sdk_crypto/src/olm/account.rs @@ -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(); diff --git a/matrix_sdk_crypto/src/olm/group_sessions/outbound.rs b/matrix_sdk_crypto/src/olm/group_sessions/outbound.rs index adecc4e1..700563df 100644 --- a/matrix_sdk_crypto/src/olm/group_sessions/outbound.rs +++ b/matrix_sdk_crypto/src/olm/group_sessions/outbound.rs @@ -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 = 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 { - 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(), - ) + 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(); + + encrypted_content.relates_to = relates_to; + + EncryptedEventContent::MegolmV1AesSha2(encrypted_content) } /// Check if the session has expired and if it should be rotated. diff --git a/matrix_sdk_crypto/src/olm/signing/mod.rs b/matrix_sdk_crypto/src/olm/signing/mod.rs index 0e73f73e..1208edf1 100644 --- a/matrix_sdk_crypto/src/olm/signing/mod.rs +++ b/matrix_sdk_crypto/src/olm/signing/mod.rs @@ -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; diff --git a/matrix_sdk_crypto/src/olm/signing/pk_signing.rs b/matrix_sdk_crypto/src/olm/signing/pk_signing.rs index 661c7a61..b78d11e1 100644 --- a/matrix_sdk_crypto/src/olm/signing/pk_signing.rs +++ b/matrix_sdk_crypto/src/olm/signing/pk_signing.rs @@ -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 { 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 { diff --git a/matrix_sdk_crypto/src/olm/utility.rs b/matrix_sdk_crypto/src/olm/utility.rs index 5994a453..e398a139 100644 --- a/matrix_sdk_crypto/src/olm/utility.rs +++ b/matrix_sdk_crypto/src/olm/utility.rs @@ -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"); + } +} diff --git a/matrix_sdk_crypto/src/requests.rs b/matrix_sdk_crypto/src/requests.rs index fb619018..a14b46bb 100644 --- a/matrix_sdk_crypto/src/requests.rs +++ b/matrix_sdk_crypto/src/requests.rs @@ -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 for OutgoingRequests { } } +impl From for OutgoingRequests { + fn from(request: RoomMessageRequest) -> Self { + OutgoingRequests::RoomMessage(request) + } +} + impl From for OutgoingRequests { fn from(request: SignatureUploadRequest) -> Self { OutgoingRequests::SignatureUpload(request) } } +impl From for OutgoingRequest { + fn from(r: OutgoingVerificationRequest) -> Self { + Self { + request_id: r.request_id(), + request: Arc::new(r.into()), + } + } +} + +impl From 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 for OutgoingVerificationRequest { + fn from(r: ToDeviceRequest) -> Self { + OutgoingVerificationRequest::ToDevice(r) + } +} + +impl From for OutgoingVerificationRequest { + fn from(r: RoomMessageRequest) -> Self { + OutgoingVerificationRequest::InRoom(r) + } +} + +impl From for OutgoingRequests { + fn from(request: OutgoingVerificationRequest) -> Self { + match request { + OutgoingVerificationRequest::ToDevice(r) => OutgoingRequests::ToDeviceRequest(r), + OutgoingVerificationRequest::InRoom(r) => OutgoingRequests::RoomMessage(r), + } + } +} diff --git a/matrix_sdk_crypto/src/verification/machine.rs b/matrix_sdk_crypto/src/verification/machine.rs index c81344c3..2b7e03cb 100644 --- a/matrix_sdk_crypto/src/verification/machine.rs +++ b/matrix_sdk_crypto/src/verification/machine.rs @@ -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>, pub(crate) store: Arc>, verifications: Arc>, + room_verifications: Arc>, + requests: Arc>, outgoing_to_device_messages: Arc>, + outgoing_room_messages: Arc>, } 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,50 +85,111 @@ 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()); + self.verifications + .insert(sas.flow_id().as_str().to_owned(), sas.clone()); + + request.into() + } + }; Ok((sas, request)) } - pub fn get_sas(&self, transaction_id: &str) -> Option { + pub fn get_request(&self, flow_id: &EventId) -> Option { #[allow(clippy::map_clone)] - self.verifications.get(transaction_id).map(|s| s.clone()) + self.requests.get(flow_id).map(|s| s.clone()) + } + + pub fn get_sas(&self, transaction_id: &str) -> Option { + 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); - let request_id = request.txn_id; + match content { + OutgoingContent::ToDevice(c) => { + let request = content_to_request(recipient, recipient_device, c); + let request_id = request.txn_id; - let request = OutgoingRequest { - request_id, - request: Arc::new(request.into()), - }; + let request = OutgoingRequest { + request_id, + request: Arc::new(request.into()), + }; - self.outgoing_to_device_messages.insert(request_id, request); + self.outgoing_to_device_messages.insert(request_id, request); + } + + 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); + } + } } - fn receive_event_helper(&self, sas: &Sas, event: &mut AnyToDeviceEvent) { + #[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 { + self.outgoing_room_messages + .iter() + .map(|r| (*r).clone()) + .collect() + } + pub fn outgoing_to_device_requests(&self) -> Vec { #[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()); diff --git a/matrix_sdk_crypto/src/verification/mod.rs b/matrix_sdk_crypto/src/verification/mod.rs index d93dc8ec..eeed8de5 100644 --- a/matrix_sdk_crypto/src/verification/mod.rs +++ b/matrix_sdk_crypto/src/verification/mod.rs @@ -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() } } diff --git a/matrix_sdk_crypto/src/verification/requests.rs b/matrix_sdk_crypto/src/verification/requests.rs new file mode 100644 index 00000000..6a96d9e6 --- /dev/null +++ b/matrix_sdk_crypto/src/verification/requests.rs @@ -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>, + account: ReadOnlyAccount, + other_user_id: Arc, + private_cross_signing_identity: PrivateCrossSigningIdentity, + store: Arc>, + room_id: Arc, +} + +impl VerificationRequest { + /// TODO + pub fn new( + account: ReadOnlyAccount, + private_cross_signing_identity: PrivateCrossSigningIdentity, + store: Arc>, + room_id: Arc, + 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 { + 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>, + 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 { + 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, + device: ReadOnlyDevice, + user_identity: Option, + ) -> Result { + 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), + Sent(RequestState), + Requested(RequestState), + Ready(RequestState), + Passive(RequestState), +} + +impl InnerRequest { + fn accept(&mut self) -> Option { + 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, + store: Arc>, + account: ReadOnlyAccount, + private_identity: PrivateCrossSigningIdentity, + other_device: ReadOnlyDevice, + other_identity: Option, + ) -> Result, 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 { + /// 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 { + 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 { + 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, + + /// 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 { + fn into_ready(self, _sender: &UserId, content: &ReadyEventContent) -> RequestState { + // 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, + + /// 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 { + fn from_request_event( + own_user_id: &UserId, + own_device_id: &DeviceId, + sender: &UserId, + event_id: &EventId, + content: &KeyVerificationRequestEventContent, + ) -> RequestState { + // 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, 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, + + /// 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 { + fn into_started_sas( + self, + event: &MessageEvent, + store: Arc>, + account: ReadOnlyAccount, + private_identity: PrivateCrossSigningIdentity, + other_device: ReadOnlyDevice, + other_identity: Option, + ) -> Result { + 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>, + _account: ReadOnlyAccount, + _private_identity: PrivateCrossSigningIdentity, + _other_device: ReadOnlyDevice, + _other_identity: Option, + ) -> (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 = Box::new(MemoryStore::new()); + let alice_identity = PrivateCrossSigningIdentity::empty(alice_id()); + + let bob = ReadOnlyAccount::new(&bob_id(), &bob_device_id()); + let bob_store: Box = 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()); + } +} diff --git a/matrix_sdk_crypto/src/verification/sas/event_enums.rs b/matrix_sdk_crypto/src/verification/sas/event_enums.rs new file mode 100644 index 00000000..ce122eac --- /dev/null +++ b/matrix_sdk_crypto/src/verification/sas/event_enums.rs @@ -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 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 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 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 { + 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 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 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 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 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 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 for OutgoingContent { + fn from(content: DoneContent) -> Self { + match content { + DoneContent::Room(r, c) => (r, AnyMessageEventContent::KeyVerificationDone(c)).into(), + } + } +} + +impl From 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) + } +} diff --git a/matrix_sdk_crypto/src/verification/sas/helpers.rs b/matrix_sdk_crypto/src/verification/sas/helpers.rs index 37974d14..76d29729 100644 --- a/matrix_sdk_crypto/src/verification/sas/helpers.rs +++ b/matrix_sdk_crypto/src/verification/sas/helpers.rs @@ -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) -> 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, + sender: &UserId, + content: &MacContent, ) -> Result<(Vec, Vec), 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::>(); + let mut keys = content.mac().keys().cloned().collect::>(); 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 = 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,10 +329,24 @@ 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(), - keys, - mac, + 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(), } } @@ -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); } diff --git a/matrix_sdk_crypto/src/verification/sas/inner_sas.rs b/matrix_sdk_crypto/src/verification/sas/inner_sas.rs new file mode 100644 index 00000000..3f8e08ad --- /dev/null +++ b/matrix_sdk_crypto/src/verification/sas/inner_sas.rs @@ -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), + Started(SasState), + Accepted(SasState), + KeyRecieved(SasState), + Confirmed(SasState), + MacReceived(SasState), + WaitingForDone(SasState), + WaitingForDoneUnconfirmed(SasState), + Done(SasState), + Canceled(SasState), +} + +impl InnerSas { + pub fn start( + account: ReadOnlyAccount, + other_device: ReadOnlyDevice, + other_identity: Option, + ) -> (InnerSas, StartContent) { + let sas = SasState::::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, + ) -> (InnerSas, StartContent) { + let sas = SasState::::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, + other_identity: Option, + ) -> Result { + match SasState::::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 { + 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) { + 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) { + 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) { + 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) { + 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 { + 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> { + 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> { + if let InnerSas::Done(s) = self { + Some(s.verified_devices()) + } else { + None + } + } + + pub fn verified_identities(&self) -> Option> { + if let InnerSas::Done(s) = self { + Some(s.verified_identities()) + } else { + None + } + } +} diff --git a/matrix_sdk_crypto/src/verification/sas/mod.rs b/matrix_sdk_crypto/src/verification/sas/mod.rs index d6f1322f..22445fa0 100644 --- a/matrix_sdk_crypto/src/verification/sas/mod.rs +++ b/matrix_sdk_crypto/src/verification/sas/mod.rs @@ -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, - }, - AnyToDeviceEvent, AnyToDeviceEventContent, ToDeviceEvent, + key::verification::cancel::CancelCode, AnyMessageEvent, AnyMessageEventContent, + AnyToDeviceEvent, AnyToDeviceEventContent, }, - 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, - flow_id: Arc, + flow_id: Arc, } 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>, + other_identity: Option, + ) -> 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>, other_identity: Option, - ) -> (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)), - account, - private_identity, - store, - other_device, + ( + Self::start_helper( + inner, + account, + private_identity, + other_device, + store, + other_identity, + ), + 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>, + other_identity: Option, + ) -> (Sas, StartContent) { + let (inner, content) = InnerSas::start_in_room( flow_id, - other_identity, - }; + room_id, + account.clone(), + other_device.clone(), + other_identity.clone(), + ); - (sas, content) + ( + 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>, - event: &ToDeviceEvent, + content: impl Into, other_identity: Option, - ) -> Result { + ) -> Result { 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 { - self.inner.lock().unwrap().accept().map(|c| { - let content = AnyToDeviceEventContent::KeyVerificationAccept(c); - self.content_to_request(content) + pub fn accept(&self) -> Option { + self.inner.lock().unwrap().accept().map(|c| match c { + AcceptContent::ToDevice(c) => { + let content = AnyToDeviceEventContent::KeyVerificationAccept(c); + 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, Option), CryptoStoreError> { + ) -> Result< + ( + Option, + Option, + ), + 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 { + pub fn cancel(&self) -> Option { 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 { + pub(crate) fn cancel_if_timed_out(&self) -> Option { 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 { + pub(crate) fn receive_room_event(&self, event: &AnyMessageEvent) -> Option { + 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 { 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), - Started(SasState), - Accepted(SasState), - KeyRecieved(SasState), - Confirmed(SasState), - MacReceived(SasState), - Done(SasState), - Canceled(SasState), -} - -impl InnerSas { - fn start( - account: ReadOnlyAccount, - other_device: ReadOnlyDevice, - other_identity: Option, - ) -> (InnerSas, StartToDeviceEventContent) { - let sas = SasState::::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, - other_identity: Option, - ) -> Result { - match SasState::::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 { - 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) { - 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) { - 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) { - 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 { - 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> { - 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> { - if let InnerSas::Done(s) = self { - Some(s.verified_devices()) - } else { - None - } - } - - fn verified_identities(&self) -> Option> { - 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(sender: &UserId, content: C) -> ToDeviceEvent { - ToDeviceEvent { - sender: sender.clone(), - content, - } - } - - async fn get_sas_pair() -> (SasState, SasState) { - 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::::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::::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 = 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 = 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() diff --git a/matrix_sdk_crypto/src/verification/sas/sas_state.rs b/matrix_sdk_crypto/src/verification/sas/sas_state.rs index ab136cae..ae65d7e5 100644 --- a/matrix_sdk_crypto/src/verification/sas/sas_state.rs +++ b/matrix_sdk_crypto/src/verification/sas/sas_state.rs @@ -14,7 +14,7 @@ use std::{ convert::TryFrom, - mem, + matches, sync::{Arc, Mutex}, time::{Duration, Instant}, }; @@ -22,28 +22,33 @@ use std::{ use olm_rs::sas::OlmSas; use matrix_sdk_common::{ - events::{ - key::verification::{ - accept::{ - AcceptMethod, AcceptToDeviceEventContent, MSasV1Content as AcceptV1Content, - MSasV1ContentInit as AcceptV1ContentInit, - }, - cancel::{CancelCode, CancelToDeviceEventContent}, - key::KeyToDeviceEventContent, - mac::MacToDeviceEventContent, - start::{MSasV1Content, MSasV1ContentInit, StartMethod, StartToDeviceEventContent}, - HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode, - ShortAuthenticationString, VerificationMethod, + events::key::verification::{ + accept::{ + AcceptEventContent, AcceptMethod, AcceptToDeviceEventContent, + MSasV1Content as AcceptV1Content, MSasV1ContentInit as AcceptV1ContentInit, }, - AnyToDeviceEventContent, ToDeviceEvent, + cancel::{CancelCode, CancelEventContent, CancelToDeviceEventContent}, + done::DoneEventContent, + key::{KeyEventContent, KeyToDeviceEventContent}, + start::{ + MSasV1Content, MSasV1ContentInit, StartEventContent, StartMethod, + StartToDeviceEventContent, + }, + HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode, Relation, + ShortAuthenticationString, VerificationMethod, }, - identifiers::{DeviceId, UserId}, + identifiers::{DeviceId, EventId, RoomId, UserId}, uuid::Uuid, }; use tracing::error; -use super::helpers::{ - calculate_commitment, get_decimal, get_emoji, get_mac_content, receive_mac_event, SasIds, +use super::{ + event_enums::{ + AcceptContent, CancelContent, DoneContent, KeyContent, MacContent, StartContent, + }, + helpers::{ + calculate_commitment, get_decimal, get_emoji, get_mac_content, receive_mac_event, SasIds, + }, }; use crate::{ @@ -66,6 +71,29 @@ const MAX_AGE: Duration = Duration::from_secs(60 * 5); // The max time a SAS object will wait for a new event to arrive. const MAX_EVENT_TIMEOUT: Duration = Duration::from_secs(60); +#[derive(Clone, Debug)] +pub enum FlowId { + ToDevice(String), + InRoom(RoomId, EventId), +} + +impl FlowId { + pub fn room_id(&self) -> Option<&RoomId> { + if let FlowId::InRoom(r, _) = &self { + Some(r) + } else { + None + } + } + + pub fn as_str(&self) -> &str { + match self { + FlowId::InRoom(_, r) => r.as_str(), + FlowId::ToDevice(t) => t.as_str(), + } + } +} + /// Struct containing the protocols that were agreed to be used for the SAS /// flow. #[derive(Clone, Debug)] @@ -144,7 +172,7 @@ pub struct SasState { /// /// This will be the transaction id for to-device events and the relates_to /// field for in-room events. - pub verification_flow_id: Arc, + pub verification_flow_id: Arc, /// The SAS state we're in. state: Arc, @@ -179,7 +207,7 @@ pub struct Started { #[derive(Clone, Debug)] pub struct Accepted { accepted_protocols: Arc, - start_content: Arc, + start_content: Arc, commitment: String, } @@ -213,6 +241,15 @@ pub struct MacReceived { verified_master_keys: Arc<[UserIdentities]>, } +/// The SAS state we're going to be in after we receive a MAC event in a DM. DMs +/// require a final message `m.key.verification.done` message to conclude the +/// verificaton. This state waits for such a message. +#[derive(Clone, Debug)] +pub struct WaitingForDone { + verified_devices: Arc<[ReadOnlyDevice]>, + verified_master_keys: Arc<[UserIdentities]>, +} + /// The SAS state indicating that the verification finished successfully. /// /// We can now mark the device in our verified devices lits as verified and sign @@ -262,6 +299,11 @@ impl SasState { self.creation_time.elapsed() > MAX_AGE || self.last_event_time.elapsed() > MAX_EVENT_TIMEOUT } + /// Is this verification happening inside a DM. + pub fn is_dm_verification(&self) -> bool { + matches!(&*self.verification_flow_id, FlowId::InRoom(_, _)) + } + #[cfg(test)] #[allow(dead_code)] pub fn set_creation_time(&mut self, time: Instant) { @@ -269,7 +311,7 @@ impl SasState { } fn check_event(&self, sender: &UserId, flow_id: &str) -> Result<(), CancelCode> { - if *flow_id != *self.verification_flow_id { + if *flow_id != *self.verification_flow_id.as_str() { Err(CancelCode::UnknownTransaction) } else if sender != self.ids.other_device.user_id() { Err(CancelCode::UserMismatch) @@ -289,13 +331,46 @@ impl SasState { /// * `account` - Our own account. /// /// * `other_device` - The other device which we are going to verify. + /// + /// * `other_identity` - The identity of the other user if one exists. pub fn new( account: ReadOnlyAccount, other_device: ReadOnlyDevice, other_identity: Option, ) -> SasState { - let verification_flow_id = Uuid::new_v4().to_string(); + let flow_id = FlowId::ToDevice(Uuid::new_v4().to_string()); + Self::new_helper(flow_id, account, other_device, other_identity) + } + /// Create a new SAS in-room verification flow. + /// + /// # Arguments + /// + /// * `event_id` - The event id of the `m.key.verification.request` event + /// that started the verification flow. + /// + /// * `account` - Our own account. + /// + /// * `other_device` - The other device which we are going to verify. + /// + /// * `other_identity` - The identity of the other user if one exists. + pub fn new_in_room( + room_id: RoomId, + event_id: EventId, + account: ReadOnlyAccount, + other_device: ReadOnlyDevice, + other_identity: Option, + ) -> SasState { + let flow_id = FlowId::InRoom(room_id, event_id); + Self::new_helper(flow_id, account, other_device, other_identity) + } + + fn new_helper( + flow_id: FlowId, + account: ReadOnlyAccount, + other_device: ReadOnlyDevice, + other_identity: Option, + ) -> SasState { SasState { inner: Arc::new(Mutex::new(OlmSas::new())), ids: SasIds { @@ -303,7 +378,7 @@ impl SasState { other_device, other_identity, }, - verification_flow_id: verification_flow_id.into(), + verification_flow_id: flow_id.into(), creation_time: Arc::new(Instant::now()), last_event_time: Arc::new(Instant::now()), @@ -319,16 +394,28 @@ impl SasState { } } - /// Get the content for the start event. - /// - /// The content needs to be sent to the other device. - pub fn as_content(&self) -> StartToDeviceEventContent { - StartToDeviceEventContent { - transaction_id: self.verification_flow_id.to_string(), - from_device: self.device_id().into(), - method: StartMethod::MSasV1( - MSasV1Content::new(self.state.protocol_definitions.clone()) - .expect("Invalid initial protocol definitions."), + pub fn as_content(&self) -> StartContent { + match self.verification_flow_id.as_ref() { + FlowId::ToDevice(s) => StartContent::ToDevice(StartToDeviceEventContent { + transaction_id: s.to_string(), + from_device: self.device_id().into(), + method: StartMethod::MSasV1( + MSasV1Content::new(self.state.protocol_definitions.clone()) + .expect("Invalid initial protocol definitions."), + ), + }), + FlowId::InRoom(r, e) => StartContent::Room( + r.clone(), + StartEventContent { + from_device: self.device_id().into(), + method: StartMethod::MSasV1( + MSasV1Content::new(self.state.protocol_definitions.clone()) + .expect("Invalid initial protocol definitions."), + ), + relation: Relation { + event_id: e.clone(), + }, + }, ), } } @@ -342,12 +429,14 @@ impl SasState { /// the other side. pub fn into_accepted( self, - event: &ToDeviceEvent, + sender: &UserId, + content: impl Into, ) -> Result, SasState> { - self.check_event(&event.sender, &event.content.transaction_id) + let content = content.into(); + self.check_event(&sender, content.flow_id().as_str()) .map_err(|c| self.clone().cancel(c))?; - if let AcceptMethod::MSasV1(content) = &event.content.method { + if let AcceptMethod::MSasV1(content) = content.method() { let accepted_protocols = AcceptedProtocols::try_from(content.clone()).map_err(|c| self.clone().cancel(c))?; @@ -372,7 +461,7 @@ impl SasState { } impl SasState { - /// Create a new SAS verification flow from a m.key.verification.start + /// Create a new SAS verification flow from an in-room m.key.verification.start /// event. /// /// This will put us in the `started` state. @@ -388,18 +477,27 @@ impl SasState { pub fn from_start_event( account: ReadOnlyAccount, other_device: ReadOnlyDevice, - event: &ToDeviceEvent, other_identity: Option, + content: impl Into, ) -> Result, SasState> { - if let StartMethod::MSasV1(content) = &event.content.method { + Self::from_start_helper(account, other_device, other_identity, &content.into()) + } + + fn from_start_helper( + account: ReadOnlyAccount, + other_device: ReadOnlyDevice, + other_identity: Option, + content: &StartContent, + ) -> Result, SasState> { + if let StartMethod::MSasV1(method_content) = content.method() { let sas = OlmSas::new(); let pubkey = sas.public_key(); - let commitment = calculate_commitment(&pubkey, &event.content); + let commitment = calculate_commitment(&pubkey, content.clone()); error!( "Calculated commitment for pubkey {} and content {:?} {}", - pubkey, event.content, commitment + pubkey, content, commitment ); let sas = SasState { @@ -414,25 +512,25 @@ impl SasState { creation_time: Arc::new(Instant::now()), last_event_time: Arc::new(Instant::now()), - verification_flow_id: event.content.transaction_id.as_str().into(), + verification_flow_id: content.flow_id().into(), state: Arc::new(Started { - protocol_definitions: content.clone(), + protocol_definitions: method_content.clone(), commitment, }), }; - if !content + if !method_content .key_agreement_protocols .contains(&KeyAgreementProtocol::Curve25519HkdfSha256) - || !content + || !method_content .message_authentication_codes .contains(&MessageAuthenticationCode::HkdfHmacSha256) - || !content.hashes.contains(&HashAlgorithm::Sha256) - || (!content + || !method_content.hashes.contains(&HashAlgorithm::Sha256) + || (!method_content .short_authentication_string .contains(&ShortAuthenticationString::Decimal) - && !content + && !method_content .short_authentication_string .contains(&ShortAuthenticationString::Emoji)) { @@ -453,7 +551,7 @@ impl SasState { other_identity, }, - verification_flow_id: event.content.transaction_id.as_str().into(), + verification_flow_id: content.flow_id().into(), state: Arc::new(Canceled::new(CancelCode::UnknownMethod)), }) } @@ -466,25 +564,40 @@ impl SasState { /// This should be sent out automatically if the SAS verification flow has /// been started because of a /// m.key.verification.request -> m.key.verification.ready flow. - pub fn as_content(&self) -> AcceptToDeviceEventContent { + pub fn as_content(&self) -> AcceptContent { let accepted_protocols = AcceptedProtocols::default(); - AcceptToDeviceEventContent { - transaction_id: self.verification_flow_id.to_string(), - method: AcceptMethod::MSasV1( - AcceptV1ContentInit { - commitment: self.state.commitment.clone(), - hash: accepted_protocols.hash, - key_agreement_protocol: accepted_protocols.key_agreement_protocol, - message_authentication_code: accepted_protocols.message_auth_code, - short_authentication_string: self - .state - .protocol_definitions - .short_authentication_string - .clone(), - } + let method = AcceptMethod::MSasV1( + AcceptV1ContentInit { + commitment: self.state.commitment.clone(), + hash: accepted_protocols.hash, + key_agreement_protocol: accepted_protocols.key_agreement_protocol, + message_authentication_code: accepted_protocols.message_auth_code, + short_authentication_string: self + .state + .protocol_definitions + .short_authentication_string + .clone(), + } + .into(), + ); + + match self.verification_flow_id.as_ref() { + FlowId::ToDevice(s) => AcceptToDeviceEventContent { + transaction_id: s.to_string(), + method, + } + .into(), + FlowId::InRoom(r, e) => ( + r.clone(), + AcceptEventContent { + method, + relation: Relation { + event_id: e.clone(), + }, + }, + ) .into(), - ), } } @@ -498,14 +611,17 @@ impl SasState { /// anymore. pub fn into_key_received( self, - event: &mut ToDeviceEvent, + sender: &UserId, + content: impl Into, ) -> Result, SasState> { - self.check_event(&event.sender, &event.content.transaction_id) + let content = content.into(); + + self.check_event(&sender, &content.flow_id().as_str()) .map_err(|c| self.clone().cancel(c))?; let accepted_protocols = AcceptedProtocols::default(); - let their_pubkey = mem::take(&mut event.content.key); + let their_pubkey = content.public_key().to_owned(); self.inner .lock() @@ -539,17 +655,23 @@ impl SasState { /// anymore. pub fn into_key_received( self, - event: &mut ToDeviceEvent, + sender: &UserId, + content: impl Into, ) -> Result, SasState> { - self.check_event(&event.sender, &event.content.transaction_id) + let content = content.into(); + + self.check_event(&sender, content.flow_id().as_str()) .map_err(|c| self.clone().cancel(c))?; - let commitment = calculate_commitment(&event.content.key, &self.state.start_content); + let commitment = calculate_commitment( + content.public_key(), + self.state.start_content.as_ref().clone(), + ); if self.state.commitment != commitment { Err(self.cancel(CancelCode::InvalidMessage)) } else { - let their_pubkey = mem::take(&mut event.content.key); + let their_pubkey = content.public_key().to_owned(); self.inner .lock() @@ -575,10 +697,23 @@ impl SasState { /// Get the content for the key event. /// /// The content needs to be automatically sent to the other side. - pub fn as_content(&self) -> KeyToDeviceEventContent { - KeyToDeviceEventContent { - transaction_id: self.verification_flow_id.to_string(), - key: self.inner.lock().unwrap().public_key(), + pub fn as_content(&self) -> KeyContent { + match &*self.verification_flow_id { + FlowId::ToDevice(s) => KeyToDeviceEventContent { + transaction_id: s.to_string(), + key: self.inner.lock().unwrap().public_key(), + } + .into(), + FlowId::InRoom(r, e) => ( + r.clone(), + KeyEventContent { + key: self.inner.lock().unwrap().public_key(), + relation: Relation { + event_id: e.clone(), + }, + }, + ) + .into(), } } } @@ -588,10 +723,23 @@ impl SasState { /// /// The content needs to be automatically sent to the other side if and only /// if we_started is false. - pub fn as_content(&self) -> KeyToDeviceEventContent { - KeyToDeviceEventContent { - transaction_id: self.verification_flow_id.to_string(), - key: self.inner.lock().unwrap().public_key(), + pub fn as_content(&self) -> KeyContent { + match &*self.verification_flow_id { + FlowId::ToDevice(s) => KeyToDeviceEventContent { + transaction_id: s.to_string(), + key: self.inner.lock().unwrap().public_key(), + } + .into(), + FlowId::InRoom(r, e) => ( + r.clone(), + KeyEventContent { + key: self.inner.lock().unwrap().public_key(), + relation: Relation { + event_id: e.clone(), + }, + }, + ) + .into(), } } @@ -604,7 +752,7 @@ impl SasState { &self.inner.lock().unwrap(), &self.ids, &self.state.their_pubkey, - &self.verification_flow_id, + self.verification_flow_id.as_str(), self.state.we_started, ) } @@ -618,7 +766,7 @@ impl SasState { &self.inner.lock().unwrap(), &self.ids, &self.state.their_pubkey, - &self.verification_flow_id, + self.verification_flow_id.as_str(), self.state.we_started, ) } @@ -632,16 +780,20 @@ impl SasState { /// the other side. pub fn into_mac_received( self, - event: &ToDeviceEvent, + sender: &UserId, + content: impl Into, ) -> Result, SasState> { - self.check_event(&event.sender, &event.content.transaction_id) + let content = content.into(); + + self.check_event(&sender, content.flow_id().as_str()) .map_err(|c| self.clone().cancel(c))?; let (devices, master_keys) = receive_mac_event( &self.inner.lock().unwrap(), &self.ids, - &self.verification_flow_id, - event, + self.verification_flow_id.as_str(), + sender, + &content, ) .map_err(|c| self.clone().cancel(c))?; @@ -688,16 +840,20 @@ impl SasState { /// the other side. pub fn into_done( self, - event: &ToDeviceEvent, + sender: &UserId, + content: impl Into, ) -> Result, SasState> { - self.check_event(&event.sender, &event.content.transaction_id) + let content = content.into(); + + self.check_event(&sender, &content.flow_id().as_str()) .map_err(|c| self.clone().cancel(c))?; let (devices, master_keys) = receive_mac_event( &self.inner.lock().unwrap(), &self.ids, - &self.verification_flow_id, - event, + &self.verification_flow_id.as_str(), + sender, + &content, ) .map_err(|c| self.clone().cancel(c))?; @@ -715,10 +871,52 @@ impl SasState { }) } + /// Receive a m.key.verification.mac event, changing the state into + /// a `WaitingForDone` one. This method should be used instead of + /// `into_done()` if the verification started with a + /// `m.key.verification.request`. + /// + /// # Arguments + /// + /// * `event` - The m.key.verification.mac event that was sent to us by + /// the other side. + pub fn into_waiting_for_done( + self, + sender: &UserId, + content: impl Into, + ) -> Result, SasState> { + let content = content.into(); + + self.check_event(&sender, &content.flow_id().as_str()) + .map_err(|c| self.clone().cancel(c))?; + + let (devices, master_keys) = receive_mac_event( + &self.inner.lock().unwrap(), + &self.ids, + &self.verification_flow_id.as_str(), + sender, + &content, + ) + .map_err(|c| self.clone().cancel(c))?; + + Ok(SasState { + inner: self.inner, + creation_time: self.creation_time, + last_event_time: self.last_event_time, + verification_flow_id: self.verification_flow_id, + ids: self.ids, + + state: Arc::new(WaitingForDone { + verified_devices: devices.into(), + verified_master_keys: master_keys.into(), + }), + }) + } + /// Get the content for the mac event. /// /// The content needs to be automatically sent to the other side. - pub fn as_content(&self) -> MacToDeviceEventContent { + pub fn as_content(&self) -> MacContent { get_mac_content( &self.inner.lock().unwrap(), &self.ids, @@ -746,6 +944,26 @@ impl SasState { } } + /// Confirm that the short auth string matches but wait for the other side + /// to confirm that it's done. + /// + /// This needs to be done by the user, this will put us in the `WaitForDone` + /// state where we wait for the other side to confirm that the MAC event was + /// successfully received. + pub fn confirm_and_wait_for_done(self) -> SasState { + SasState { + inner: self.inner, + verification_flow_id: self.verification_flow_id, + creation_time: self.creation_time, + last_event_time: self.last_event_time, + ids: self.ids, + state: Arc::new(WaitingForDone { + verified_devices: self.state.verified_devices.clone(), + verified_master_keys: self.state.verified_master_keys.clone(), + }), + } + } + /// Get the emoji version of the short authentication string. /// /// Returns a vector of tuples where the first element is the emoji and the @@ -755,7 +973,7 @@ impl SasState { &self.inner.lock().unwrap(), &self.ids, &self.state.their_pubkey, - &self.verification_flow_id, + &self.verification_flow_id.as_str(), self.state.we_started, ) } @@ -769,18 +987,80 @@ impl SasState { &self.inner.lock().unwrap(), &self.ids, &self.state.their_pubkey, - &self.verification_flow_id, + &self.verification_flow_id.as_str(), self.state.we_started, ) } } +impl SasState { + /// Get the content for the mac event. + /// + /// The content needs to be automatically sent to the other side if it + /// wasn't already sent. + pub fn as_content(&self) -> MacContent { + get_mac_content( + &self.inner.lock().unwrap(), + &self.ids, + &self.verification_flow_id, + ) + } + + pub fn done_content(&self) -> DoneContent { + match self.verification_flow_id.as_ref() { + FlowId::ToDevice(_) => { + unreachable!("The done content isn't supported yet for to-device verifications") + } + FlowId::InRoom(r, e) => ( + r.clone(), + DoneEventContent { + relation: Relation { + event_id: e.clone(), + }, + }, + ) + .into(), + } + } + + /// Receive a m.key.verification.mac event, changing the state into + /// a `Done` one + /// + /// # Arguments + /// + /// * `event` - The m.key.verification.mac event that was sent to us by + /// the other side. + pub fn into_done( + self, + sender: &UserId, + content: impl Into, + ) -> Result, SasState> { + let content = content.into(); + + self.check_event(&sender, &content.flow_id().as_str()) + .map_err(|c| self.clone().cancel(c))?; + + Ok(SasState { + inner: self.inner, + creation_time: self.creation_time, + last_event_time: self.last_event_time, + verification_flow_id: self.verification_flow_id, + ids: self.ids, + + state: Arc::new(Done { + verified_devices: self.state.verified_devices.clone(), + verified_master_keys: self.state.verified_master_keys.clone(), + }), + }) + } +} + impl SasState { /// Get the content for the mac event. /// /// The content needs to be automatically sent to the other side if it /// wasn't already sent. - pub fn as_content(&self) -> MacToDeviceEventContent { + pub fn as_content(&self) -> MacContent { get_mac_content( &self.inner.lock().unwrap(), &self.ids, @@ -788,6 +1068,23 @@ impl SasState { ) } + pub fn done_content(&self) -> DoneContent { + match self.verification_flow_id.as_ref() { + FlowId::ToDevice(_) => { + unreachable!("The done content isn't supported yet for to-device verifications") + } + FlowId::InRoom(r, e) => ( + r.clone(), + DoneEventContent { + relation: Relation { + event_id: e.clone(), + }, + }, + ) + .into(), + } + } + /// Get the list of verified devices. pub fn verified_devices(&self) -> Arc<[ReadOnlyDevice]> { self.state.verified_devices.clone() @@ -828,12 +1125,27 @@ impl Canceled { } impl SasState { - pub fn as_content(&self) -> AnyToDeviceEventContent { - AnyToDeviceEventContent::KeyVerificationCancel(CancelToDeviceEventContent { - transaction_id: self.verification_flow_id.to_string(), - reason: self.state.reason.to_string(), - code: self.state.cancel_code.clone(), - }) + pub fn as_content(&self) -> CancelContent { + match self.verification_flow_id.as_ref() { + FlowId::ToDevice(s) => CancelToDeviceEventContent { + transaction_id: s.clone(), + reason: self.state.reason.to_string(), + code: self.state.cancel_code.clone(), + } + .into(), + + FlowId::InRoom(r, e) => ( + r.clone(), + CancelEventContent { + reason: self.state.reason.to_string(), + code: self.state.cancel_code.clone(), + relation: Relation { + event_id: e.clone(), + }, + }, + ) + .into(), + } } } @@ -841,14 +1153,14 @@ impl SasState { mod test { use std::convert::TryFrom; - use crate::{ReadOnlyAccount, ReadOnlyDevice}; + use crate::{ + verification::sas::{event_enums::AcceptContent, StartContent}, + ReadOnlyAccount, ReadOnlyDevice, + }; use matrix_sdk_common::{ - events::{ - key::verification::{ - accept::{AcceptMethod, CustomContent}, - start::{CustomContent as CustomStartContent, StartMethod}, - }, - EventContent, ToDeviceEvent, + events::key::verification::{ + accept::{AcceptMethod, CustomContent}, + start::{CustomContent as CustomStartContent, StartMethod}, }, identifiers::{DeviceId, UserId}, }; @@ -871,13 +1183,6 @@ mod test { "BOBDEVCIE".into() } - fn wrap_to_device_event(sender: &UserId, content: C) -> ToDeviceEvent { - ToDeviceEvent { - sender: sender.clone(), - content, - } - } - async fn get_sas_pair() -> (SasState, SasState) { let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id()); let alice_device = ReadOnlyDevice::from_account(&alice).await; @@ -888,10 +1193,9 @@ mod test { let alice_sas = SasState::::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::::from_start_event(bob.clone(), alice_device, &event, None); + SasState::::from_start_event(bob.clone(), alice_device, None, start_content); (alice_sas, bob_sas.unwrap()) } @@ -905,25 +1209,25 @@ mod test { async fn sas_accept() { let (alice, bob) = get_sas_pair().await; - let event = wrap_to_device_event(bob.user_id(), bob.as_content()); + let event = bob.as_content(); - alice.into_accepted(&event).unwrap(); + alice.into_accepted(bob.user_id(), 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 content = bob.as_content(); - let alice: SasState = alice.into_accepted(&event).unwrap(); - let mut event = wrap_to_device_event(alice.user_id(), alice.as_content()); + let alice: SasState = alice.into_accepted(bob.user_id(), content).unwrap(); + let content = alice.as_content(); - let bob = bob.into_key_received(&mut event).unwrap(); + let bob = bob.into_key_received(alice.user_id(), content).unwrap(); - let mut event = wrap_to_device_event(bob.user_id(), bob.as_content()); + let content = bob.as_content(); - let alice = alice.into_key_received(&mut event).unwrap(); + let alice = alice.into_key_received(bob.user_id(), content).unwrap(); assert_eq!(alice.get_decimal(), bob.get_decimal()); assert_eq!(alice.get_emoji(), bob.get_emoji()); @@ -933,16 +1237,16 @@ mod 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 content = bob.as_content(); - let alice: SasState = alice.into_accepted(&event).unwrap(); - let mut event = wrap_to_device_event(alice.user_id(), alice.as_content()); + let alice: SasState = alice.into_accepted(bob.user_id(), content).unwrap(); + let content = alice.as_content(); - let bob = bob.into_key_received(&mut event).unwrap(); + let bob = bob.into_key_received(alice.user_id(), content).unwrap(); - let mut event = wrap_to_device_event(bob.user_id(), bob.as_content()); + let content = bob.as_content(); - let alice = alice.into_key_received(&mut event).unwrap(); + let alice = alice.into_key_received(bob.user_id(), content).unwrap(); assert_eq!(alice.get_decimal(), bob.get_decimal()); assert_eq!(alice.get_emoji(), bob.get_emoji()); @@ -951,15 +1255,15 @@ mod test { let bob = bob.confirm(); - let event = wrap_to_device_event(bob.user_id(), bob.as_content()); + let content = bob.as_content(); - let alice = alice.into_mac_received(&event).unwrap(); + let alice = alice.into_mac_received(bob.user_id(), content).unwrap(); assert!(!alice.get_emoji().is_empty()); assert_eq!(alice.get_decimal(), bob_decimals); let alice = alice.confirm(); - let event = wrap_to_device_event(alice.user_id(), alice.as_content()); - let bob = bob.into_done(&event).unwrap(); + let content = alice.as_content(); + let bob = bob.into_done(alice.user_id(), content).unwrap(); assert!(bob.verified_devices().contains(&bob.other_device())); assert!(alice.verified_devices().contains(&alice.other_device())); @@ -969,23 +1273,28 @@ mod test { async fn sas_invalid_commitment() { let (alice, bob) = get_sas_pair().await; - let mut event = wrap_to_device_event(bob.user_id(), bob.as_content()); + let mut content = bob.as_content(); - match &mut event.content.method { + let mut method = match &mut content { + AcceptContent::ToDevice(c) => &mut c.method, + AcceptContent::Room(_, c) => &mut c.method, + }; + + match &mut method { AcceptMethod::MSasV1(ref mut c) => { c.commitment = "".to_string(); } _ => panic!("Unknown accept event content"), } - let alice: SasState = alice.into_accepted(&event).unwrap(); + let alice: SasState = alice.into_accepted(bob.user_id(), content).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 content = alice.as_content(); + let bob = bob.into_key_received(alice.user_id(), content).unwrap(); + let content = bob.as_content(); alice - .into_key_received(&mut event) + .into_key_received(bob.user_id(), content) .expect_err("Didn't cancel on invalid commitment"); } @@ -993,10 +1302,10 @@ mod test { async fn sas_invalid_sender() { let (alice, bob) = get_sas_pair().await; - let mut event = wrap_to_device_event(bob.user_id(), bob.as_content()); - event.sender = UserId::try_from("@malory:example.org").unwrap(); + let content = bob.as_content(); + let sender = UserId::try_from("@malory:example.org").unwrap(); alice - .into_accepted(&event) + .into_accepted(&sender, content) .expect_err("Didn't cancel on a invalid sender"); } @@ -1004,9 +1313,14 @@ mod test { async fn sas_unknown_sas_method() { let (alice, bob) = get_sas_pair().await; - let mut event = wrap_to_device_event(bob.user_id(), bob.as_content()); + let mut content = bob.as_content(); - match &mut event.content.method { + let mut method = match &mut content { + AcceptContent::ToDevice(c) => &mut c.method, + AcceptContent::Room(_, c) => &mut c.method, + }; + + match &mut method { AcceptMethod::MSasV1(ref mut c) => { c.short_authentication_string = vec![]; } @@ -1014,7 +1328,7 @@ mod test { } alice - .into_accepted(&event) + .into_accepted(bob.user_id(), content) .expect_err("Didn't cancel on an invalid SAS method"); } @@ -1022,15 +1336,20 @@ mod test { async fn sas_unknown_method() { let (alice, bob) = get_sas_pair().await; - let mut event = wrap_to_device_event(bob.user_id(), bob.as_content()); + let mut content = bob.as_content(); - event.content.method = AcceptMethod::Custom(CustomContent { + let method = match &mut content { + AcceptContent::ToDevice(c) => &mut c.method, + AcceptContent::Room(_, c) => &mut c.method, + }; + + *method = AcceptMethod::Custom(CustomContent { method: "m.sas.custom".to_string(), fields: vec![].into_iter().collect(), }); alice - .into_accepted(&event) + .into_accepted(bob.user_id(), content) .expect_err("Didn't cancel on an unknown SAS method"); } @@ -1046,26 +1365,39 @@ mod test { let mut start_content = alice_sas.as_content(); - match start_content.method { + let method = match &mut start_content { + StartContent::ToDevice(c) => &mut c.method, + StartContent::Room(_, c) => &mut c.method, + }; + + match method { StartMethod::MSasV1(ref mut c) => { c.message_authentication_codes = vec![]; } _ => panic!("Unknown SAS start method"), } - let event = wrap_to_device_event(alice_sas.user_id(), start_content); - SasState::::from_start_event(bob.clone(), alice_device.clone(), &event, None) - .expect_err("Didn't cancel on invalid MAC method"); + SasState::::from_start_event( + bob.clone(), + alice_device.clone(), + None, + start_content, + ) + .expect_err("Didn't cancel on invalid MAC method"); let mut start_content = alice_sas.as_content(); - start_content.method = StartMethod::Custom(CustomStartContent { + let method = match &mut start_content { + StartContent::ToDevice(c) => &mut c.method, + StartContent::Room(_, c) => &mut c.method, + }; + + *method = StartMethod::Custom(CustomStartContent { method: "m.sas.custom".to_string(), fields: vec![].into_iter().collect(), }); - let event = wrap_to_device_event(alice_sas.user_id(), start_content); - SasState::::from_start_event(bob.clone(), alice_device, &event, None) + SasState::::from_start_event(bob.clone(), alice_device, None, start_content) .expect_err("Didn't cancel on unknown sas method"); } } diff --git a/matrix_sdk_test/src/lib.rs b/matrix_sdk_test/src/lib.rs index c1659408..02bbd4db 100644 --- a/matrix_sdk_test/src/lib.rs +++ b/matrix_sdk_test/src/lib.rs @@ -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() diff --git a/matrix_sdk_test/src/test_json/mod.rs b/matrix_sdk_test/src/test_json/mod.rs index d3e72df5..b03d55b7 100644 --- a/matrix_sdk_test/src/test_json/mod.rs +++ b/matrix_sdk_test/src/test_json/mod.rs @@ -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!({ diff --git a/matrix_sdk_test/src/test_json/sync.rs b/matrix_sdk_test/src/test_json/sync.rs index 99e54907..88094d84 100644 --- a/matrix_sdk_test/src/test_json/sync.rs +++ b/matrix_sdk_test/src/test_json/sync.rs @@ -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": [] + } + }); +}