From d4327d4cfcc0aa2dfb3d0c59cc55bc03634b5977 Mon Sep 17 00:00:00 2001 From: Alex Black Date: Tue, 15 Dec 2020 21:15:42 +1100 Subject: [PATCH 1/3] EventEmitter: add VoIP event support (m.call.* event types) Signed-off-by: Alex Black --- matrix_sdk_base/src/client.rs | 12 +++ matrix_sdk_base/src/event_emitter/mod.rs | 57 +++++++++++ matrix_sdk_test/src/lib.rs | 2 + matrix_sdk_test/src/test_json/mod.rs | 4 +- matrix_sdk_test/src/test_json/sync.rs | 120 +++++++++++++++++++++++ 5 files changed, 194 insertions(+), 1 deletion(-) diff --git a/matrix_sdk_base/src/client.rs b/matrix_sdk_base/src/client.rs index 55142e98..3839419e 100644 --- a/matrix_sdk_base/src/client.rs +++ b/matrix_sdk_base/src/client.rs @@ -1498,6 +1498,18 @@ impl BaseClient { .on_custom_event(room, &CustomEvent::Message(e)) .await } + AnySyncMessageEvent::CallInvite(e) => { + event_emitter.on_room_call_invite(room, e).await + } + AnySyncMessageEvent::CallAnswer(e) => { + event_emitter.on_room_call_answer(room, e).await + } + AnySyncMessageEvent::CallCandidates(e) => { + event_emitter.on_room_call_candidates(room, e).await + } + AnySyncMessageEvent::CallHangup(e) => { + event_emitter.on_room_call_hangup(room, e).await + } _ => {} }, AnySyncRoomEvent::RedactedState(_event) => {} diff --git a/matrix_sdk_base/src/event_emitter/mod.rs b/matrix_sdk_base/src/event_emitter/mod.rs index 7a334f4d..8f5937a8 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, @@ -135,6 +139,19 @@ pub trait EventEmitter: Send + Sync { _: &SyncMessageEvent, ) { } + /// Fires when `Client` receives a `RoomEvent::CallInvite` event + async fn on_room_call_invite(&self, _: SyncRoom, _: &SyncMessageEvent) {} + /// Fires when `Client` receives a `RoomEvent::CallAnswer` event + async fn on_room_call_answer(&self, _: SyncRoom, _: &SyncMessageEvent) {} + /// Fires when `Client` receives a `RoomEvent::CallCandidates` event + async fn on_room_call_candidates( + &self, + _: SyncRoom, + _: &SyncMessageEvent, + ) { + } + /// Fires when `Client` receives a `RoomEvent::CallHangup` event + async fn on_room_call_hangup(&self, _: SyncRoom, _: &SyncMessageEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomRedaction` event. async fn on_room_redaction(&self, _: SyncRoom, _: &SyncRedactionEvent) {} /// Fires when `Client` receives a `RoomEvent::RoomPowerLevels` event. @@ -317,6 +334,22 @@ mod test { ) { self.0.lock().await.push("feedback".to_string()) } + async fn on_room_call_invite(&self, _: SyncRoom, _: &SyncMessageEvent) { + self.0.lock().await.push("call invite".to_string()) + } + async fn on_room_call_answer(&self, _: SyncRoom, _: &SyncMessageEvent) { + self.0.lock().await.push("call answer".to_string()) + } + async fn on_room_call_candidates( + &self, + _: SyncRoom, + _: &SyncMessageEvent, + ) { + self.0.lock().await.push("call candidates".to_string()) + } + async fn on_room_call_hangup(&self, _: SyncRoom, _: &SyncMessageEvent) { + self.0.lock().await.push("call hangup".to_string()) + } async fn on_room_redaction(&self, _: SyncRoom, _: &SyncRedactionEvent) { self.0.lock().await.push("redaction".to_string()) } @@ -601,4 +634,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 mut response = sync_response(SyncResponseFile::Voip); + client.receive_sync_response(&mut 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_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 9bd262c1..868f94e0 100644 --- a/matrix_sdk_test/src/test_json/sync.rs +++ b/matrix_sdk_test/src/test_json/sync.rs @@ -1101,3 +1101,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": [] + } + }); +} From d39e3141fcc5f3d8c46d7dcf3b83a842dc49c53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 22 Dec 2020 14:12:57 +0100 Subject: [PATCH 2/3] crypto: Use CanonicalJsonValue for all the signature calculations. --- matrix_sdk_crypto/src/olm/account.rs | 12 +++++++----- matrix_sdk_crypto/src/olm/signing/mod.rs | 2 +- matrix_sdk_crypto/src/olm/signing/pk_signing.rs | 8 +++++--- 3 files changed, 13 insertions(+), 9 deletions(-) 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/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 { From 9245b2a89a9cd61038379a51423abe8f7fc71a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 22 Dec 2020 15:45:42 +0100 Subject: [PATCH 3/3] crypto: Properly canonicalize the json when verifying signatures as well. --- matrix_sdk_crypto/src/olm/utility.rs | 73 ++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 10 deletions(-) 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"); + } +}