From 4afafa8b370db3b36e37dd9d6b3f3edf17938086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 3 Dec 2020 15:36:22 +0100 Subject: [PATCH] ruma-events: Add the majority of in-room verification events This patch adds equivalent in-room versions of the m.key.verfication.* events we have for to-device events. The MSC that adds this is found over here: https://github.com/matrix-org/matrix-doc/pull/2241 --- ruma-events/src/enums.rs | 10 ++ ruma-events/src/key/verification.rs | 44 ++++++++ ruma-events/src/key/verification/accept.rs | 108 ++++++++++++++++++- ruma-events/src/key/verification/cancel.rs | 32 +++++- ruma-events/src/key/verification/key.rs | 27 ++++- ruma-events/src/key/verification/mac.rs | 33 +++++- ruma-events/src/key/verification/start.rs | 115 ++++++++++++++++++++- 7 files changed, 363 insertions(+), 6 deletions(-) diff --git a/ruma-events/src/enums.rs b/ruma-events/src/enums.rs index 00dd1285..db329b29 100644 --- a/ruma-events/src/enums.rs +++ b/ruma-events/src/enums.rs @@ -37,6 +37,16 @@ event_enum! { "m.call.hangup", "m.call.candidates", #[cfg(feature = "unstable-pre-spec")] + "m.key.verification.start", + #[cfg(feature = "unstable-pre-spec")] + "m.key.verification.cancel", + #[cfg(feature = "unstable-pre-spec")] + "m.key.verification.accept", + #[cfg(feature = "unstable-pre-spec")] + "m.key.verification.key", + #[cfg(feature = "unstable-pre-spec")] + "m.key.verification.mac", + #[cfg(feature = "unstable-pre-spec")] "m.reaction", "m.room.encrypted", "m.room.message", diff --git a/ruma-events/src/key/verification.rs b/ruma-events/src/key/verification.rs index fbe7412e..f647e6c3 100644 --- a/ruma-events/src/key/verification.rs +++ b/ruma-events/src/key/verification.rs @@ -1,9 +1,22 @@ //! Modules for events in the *m.key.verification* namespace. //! //! This module also contains types shared by events in its child namespaces. +//! +//! The MSC for the in-room variants of the `m.key.verification.*` events can be found +//! [here](https://github.com/matrix-org/matrix-doc/pull/2241). +#[cfg(feature = "unstable-pre-spec")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "unstable-pre-spec")] +use std::convert::TryFrom; + +#[cfg(feature = "unstable-pre-spec")] +use ruma_identifiers::EventId; use ruma_serde::StringEnum; +#[cfg(feature = "unstable-pre-spec")] +use crate::room::relationships::{Reference, RelatesToJsonRepr, RelationJsonRepr}; + pub mod accept; pub mod cancel; pub mod key; @@ -64,6 +77,37 @@ pub enum ShortAuthenticationString { _Custom(String), } +/// The relation that contains info which event the reaction is applying to. +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(try_from = "RelatesToJsonRepr", into = "RelatesToJsonRepr")] +#[cfg(feature = "unstable-pre-spec")] +pub struct Relation { + /// The event that is being referenced. + pub event_id: EventId, +} + +#[cfg(feature = "unstable-pre-spec")] +impl From for RelatesToJsonRepr { + fn from(relation: Relation) -> Self { + RelatesToJsonRepr::Relation(RelationJsonRepr::Reference(Reference { + event_id: relation.event_id, + })) + } +} + +#[cfg(feature = "unstable-pre-spec")] +impl TryFrom for Relation { + type Error = &'static str; + + fn try_from(value: RelatesToJsonRepr) -> Result { + if let RelatesToJsonRepr::Relation(RelationJsonRepr::Reference(r)) = value { + Ok(Relation { event_id: r.event_id }) + } else { + Err("Expected a relation with a rel_type of `reference`") + } + } +} + /// A Short Authentication String (SAS) verification method. #[derive(Clone, Debug, PartialEq, Eq, StringEnum)] pub enum VerificationMethod { diff --git a/ruma-events/src/key/verification/accept.rs b/ruma-events/src/key/verification/accept.rs index a182ba0d..de299f81 100644 --- a/ruma-events/src/key/verification/accept.rs +++ b/ruma-events/src/key/verification/accept.rs @@ -3,6 +3,8 @@ use std::collections::BTreeMap; use ruma_events_macros::BasicEventContent; +#[cfg(feature = "unstable-pre-spec")] +use ruma_events_macros::MessageEventContent; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; @@ -10,7 +12,17 @@ use super::{ HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode, ShortAuthenticationString, }; -/// The payload for `AcceptEvent`. +#[cfg(feature = "unstable-pre-spec")] +use super::Relation; + +#[cfg(feature = "unstable-pre-spec")] +use crate::MessageEvent; + +/// Accepts a previously sent *m.key.verification.start* message. +#[cfg(feature = "unstable-pre-spec")] +pub type AcceptEvent = MessageEvent; + +/// The payload for a to-device `AcceptEvent`. #[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)] #[ruma_event(type = "m.key.verification.accept")] pub struct AcceptToDeviceEventContent { @@ -25,6 +37,20 @@ pub struct AcceptToDeviceEventContent { pub method: AcceptMethod, } +/// The payload for a in-room `AcceptEvent`. +#[derive(Clone, Debug, Deserialize, Serialize, MessageEventContent)] +#[ruma_event(type = "m.key.verification.accept")] +#[cfg(feature = "unstable-pre-spec")] +pub struct AcceptEventContent { + /// The method specific content. + #[serde(flatten)] + pub method: AcceptMethod, + + /// Information about the related event. + #[serde(rename = "m.relates_to")] + pub relation: Relation, +} + /// An enum representing the different method specific /// *m.key.verification.accept* content. #[derive(Clone, Debug, Deserialize, Serialize)] @@ -124,12 +150,16 @@ mod tests { use std::collections::BTreeMap; use matches::assert_matches; + #[cfg(feature = "unstable-pre-spec")] + use ruma_identifiers::event_id; use ruma_identifiers::user_id; use ruma_serde::Raw; use serde_json::{ from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue, }; + #[cfg(feature = "unstable-pre-spec")] + use super::{AcceptEventContent, Relation}; use super::{ AcceptMethod, AcceptToDeviceEventContent, CustomContent, HashAlgorithm, KeyAgreementProtocol, MSasV1Content, MessageAuthenticationCode, ShortAuthenticationString, @@ -198,6 +228,38 @@ mod tests { assert_eq!(to_json_value(&key_verification_accept).unwrap(), json_data); } + #[test] + #[cfg(feature = "unstable-pre-spec")] + fn in_room_serialization() { + let event_id = event_id!("$1598361704261elfgc:localhost"); + + let key_verification_accept_content = AcceptEventContent { + relation: Relation { event_id: event_id.clone() }, + method: AcceptMethod::MSasV1(MSasV1Content { + hash: HashAlgorithm::Sha256, + key_agreement_protocol: KeyAgreementProtocol::Curve25519, + message_authentication_code: MessageAuthenticationCode::HkdfHmacSha256, + short_authentication_string: vec![ShortAuthenticationString::Decimal], + commitment: "test_commitment".into(), + }), + }; + + let json_data = json!({ + "method": "m.sas.v1", + "commitment": "test_commitment", + "key_agreement_protocol": "curve25519", + "hash": "sha256", + "message_authentication_code": "hkdf-hmac-sha256", + "short_authentication_string": ["decimal"], + "m.relates_to": { + "rel_type": "m.reference", + "event_id": event_id, + } + }); + + assert_eq!(to_json_value(&key_verification_accept_content).unwrap(), json_data); + } + #[test] fn deserialization() { let json = json!({ @@ -308,4 +370,48 @@ mod tests { && fields.get("test").unwrap() == &JsonValue::from("field") ); } + + #[test] + #[cfg(feature = "unstable-pre-spec")] + fn in_room_deserialization() { + let id = event_id!("$1598361704261elfgc:localhost"); + + let json = json!({ + "commitment": "test_commitment", + "method": "m.sas.v1", + "hash": "sha256", + "key_agreement_protocol": "curve25519", + "message_authentication_code": "hkdf-hmac-sha256", + "short_authentication_string": ["decimal"], + "m.relates_to": { + "rel_type": "m.reference", + "event_id": id, + } + }); + + // Deserialize the content struct separately to verify `TryFromRaw` is implemented for it. + assert_matches!( + from_json_value::>(json) + .unwrap() + .deserialize() + .unwrap(), + AcceptEventContent { + relation: Relation { + event_id + }, + method: AcceptMethod::MSasV1(MSasV1Content { + commitment, + hash, + key_agreement_protocol, + message_authentication_code, + short_authentication_string, + }) + } if commitment == "test_commitment" + && event_id == id + && hash == HashAlgorithm::Sha256 + && key_agreement_protocol == KeyAgreementProtocol::Curve25519 + && message_authentication_code == MessageAuthenticationCode::HkdfHmacSha256 + && short_authentication_string == vec![ShortAuthenticationString::Decimal] + ); + } } diff --git a/ruma-events/src/key/verification/cancel.rs b/ruma-events/src/key/verification/cancel.rs index d9ce80a9..6682bb78 100644 --- a/ruma-events/src/key/verification/cancel.rs +++ b/ruma-events/src/key/verification/cancel.rs @@ -1,10 +1,22 @@ //! Types for the *m.key.verification.cancel* event. use ruma_events_macros::BasicEventContent; +#[cfg(feature = "unstable-pre-spec")] +use ruma_events_macros::MessageEventContent; use ruma_serde::StringEnum; use serde::{Deserialize, Serialize}; -/// The payload for `CancelEvent`. +#[cfg(feature = "unstable-pre-spec")] +use crate::MessageEvent; + +#[cfg(feature = "unstable-pre-spec")] +use super::Relation; + +/// Cancels a key verification process/request. +#[cfg(feature = "unstable-pre-spec")] +pub type CancelEvent = MessageEvent; + +/// The payload for a to-device `CancelEvent`. #[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)] #[ruma_event(type = "m.key.verification.cancel")] pub struct CancelToDeviceEventContent { @@ -20,6 +32,24 @@ pub struct CancelToDeviceEventContent { pub code: CancelCode, } +/// The payload for an in-room `CancelEvent`. +#[derive(Clone, Debug, Deserialize, Serialize, MessageEventContent)] +#[ruma_event(type = "m.key.verification.cancel")] +#[cfg(feature = "unstable-pre-spec")] +pub struct CancelEventContent { + /// A human readable description of the `code`. + /// + /// The client should only rely on this string if it does not understand the `code`. + pub reason: String, + + /// The error code for why the process/request was cancelled by the user. + pub code: CancelCode, + + /// Information about the related event. + #[serde(rename = "m.relates_to")] + pub relation: Relation, +} + /// An error code for why the process/request was cancelled by the user. /// /// Custom error codes should use the Java package naming convention. diff --git a/ruma-events/src/key/verification/key.rs b/ruma-events/src/key/verification/key.rs index 67034d8f..b0bc3bd9 100644 --- a/ruma-events/src/key/verification/key.rs +++ b/ruma-events/src/key/verification/key.rs @@ -1,9 +1,21 @@ //! Types for the *m.key.verification.key* event. use ruma_events_macros::BasicEventContent; +#[cfg(feature = "unstable-pre-spec")] +use ruma_events_macros::MessageEventContent; use serde::{Deserialize, Serialize}; -/// The payload for `KeyEvent`. +#[cfg(feature = "unstable-pre-spec")] +use super::Relation; + +#[cfg(feature = "unstable-pre-spec")] +use crate::MessageEvent; + +/// Sends the ephemeral public key for a device to the partner device. +#[cfg(feature = "unstable-pre-spec")] +pub type KeyEvent = MessageEvent; + +/// The payload for a to-device `KeyEvent`. #[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)] #[ruma_event(type = "m.key.verification.key")] pub struct KeyToDeviceEventContent { @@ -15,3 +27,16 @@ pub struct KeyToDeviceEventContent { /// The device's ephemeral public key, encoded as unpadded Base64. pub key: String, } + +/// The payload for in-room `KeyEvent`. +#[derive(Clone, Debug, Deserialize, Serialize, MessageEventContent)] +#[ruma_event(type = "m.key.verification.key")] +#[cfg(feature = "unstable-pre-spec")] +pub struct KeyEventContent { + /// The device's ephemeral public key, encoded as unpadded Base64. + pub key: String, + + /// Information about the related event. + #[serde(rename = "m.relates_to")] + pub relation: Relation, +} diff --git a/ruma-events/src/key/verification/mac.rs b/ruma-events/src/key/verification/mac.rs index b66e5667..b6c852ac 100644 --- a/ruma-events/src/key/verification/mac.rs +++ b/ruma-events/src/key/verification/mac.rs @@ -3,9 +3,21 @@ use std::collections::BTreeMap; use ruma_events_macros::BasicEventContent; +#[cfg(feature = "unstable-pre-spec")] +use ruma_events_macros::MessageEventContent; use serde::{Deserialize, Serialize}; -/// The payload for `MacEvent`. +#[cfg(feature = "unstable-pre-spec")] +use super::Relation; + +#[cfg(feature = "unstable-pre-spec")] +use crate::MessageEvent; + +/// Sends the MAC of a device's key to the partner device. +#[cfg(feature = "unstable-pre-spec")] +pub type MacEvent = MessageEvent; + +/// The payload for a to-device `MacEvent`. #[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)] #[ruma_event(type = "m.key.verification.mac")] pub struct MacToDeviceEventContent { @@ -23,3 +35,22 @@ pub struct MacToDeviceEventContent { /// encoded as unpadded Base64. pub keys: String, } + +/// The payload for an in-room `MacEvent`. +#[derive(Clone, Debug, Deserialize, Serialize, MessageEventContent)] +#[ruma_event(type = "m.key.verification.mac")] +#[cfg(feature = "unstable-pre-spec")] +pub struct MacEventContent { + /// A map of the key ID to the MAC of the key, using the algorithm in the verification process. + /// + /// The MAC is encoded as unpadded Base64. + pub mac: BTreeMap, + + /// The MAC of the comma-separated, sorted, list of key IDs given in the `mac` property, + /// encoded as unpadded Base64. + pub keys: String, + + /// Information about the related event. + #[serde(rename = "m.relates_to")] + pub relation: Relation, +} diff --git a/ruma-events/src/key/verification/start.rs b/ruma-events/src/key/verification/start.rs index 57529231..769e7692 100644 --- a/ruma-events/src/key/verification/start.rs +++ b/ruma-events/src/key/verification/start.rs @@ -3,16 +3,27 @@ use std::{collections::BTreeMap, convert::TryFrom}; use ruma_events_macros::BasicEventContent; +#[cfg(feature = "unstable-pre-spec")] +use ruma_events_macros::MessageEventContent; use ruma_identifiers::DeviceIdBox; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; +#[cfg(feature = "unstable-pre-spec")] +use super::Relation; use super::{ HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode, ShortAuthenticationString, }; -use crate::InvalidInput; -/// The payload of an *m.key.verification.start* event. +use crate::InvalidInput; +#[cfg(feature = "unstable-pre-spec")] +use crate::MessageEvent; + +/// Begins an SAS key verification process. +#[cfg(feature = "unstable-pre-spec")] +pub type StartEvent = MessageEvent; + +/// The payload of a to-device *m.key.verification.start* event. #[derive(Clone, Debug, Deserialize, Serialize, BasicEventContent)] #[ruma_event(type = "m.key.verification.start")] pub struct StartToDeviceEventContent { @@ -31,6 +42,23 @@ pub struct StartToDeviceEventContent { pub method: StartMethod, } +/// The payload of an in-room *m.key.verification.start* event. +#[derive(Clone, Debug, Deserialize, Serialize, MessageEventContent)] +#[ruma_event(type = "m.key.verification.start")] +#[cfg(feature = "unstable-pre-spec")] +pub struct StartEventContent { + /// The device ID which is initiating the process. + pub from_device: DeviceIdBox, + + /// Method specific content. + #[serde(flatten)] + pub method: StartMethod, + + /// Information about the related event. + #[serde(rename = "m.relates_to")] + pub relation: Relation, +} + /// An enum representing the different method specific /// *m.key.verification.start* content. #[derive(Clone, Debug, Deserialize, Serialize)] @@ -174,6 +202,8 @@ mod tests { use std::collections::BTreeMap; use matches::assert_matches; + #[cfg(feature = "unstable-pre-spec")] + use ruma_identifiers::event_id; use ruma_identifiers::user_id; use ruma_serde::Raw; use serde_json::{ @@ -185,6 +215,8 @@ mod tests { MessageAuthenticationCode, ShortAuthenticationString, StartMethod, StartToDeviceEventContent, }; + #[cfg(feature = "unstable-pre-spec")] + use super::{Relation, StartEventContent}; use crate::ToDeviceEvent; #[test] @@ -310,6 +342,41 @@ mod tests { assert_eq!(to_json_value(&key_verification_start).unwrap(), json_data); } + #[test] + #[cfg(feature = "unstable-pre-spec")] + fn in_room_serialization() { + let event_id = event_id!("$1598361704261elfgc:localhost"); + + let key_verification_start_content = StartEventContent { + from_device: "123".into(), + relation: Relation { event_id: event_id.clone() }, + method: StartMethod::MSasV1( + MSasV1Content::new(MSasV1ContentInit { + hashes: vec![HashAlgorithm::Sha256], + key_agreement_protocols: vec![KeyAgreementProtocol::Curve25519], + message_authentication_codes: vec![MessageAuthenticationCode::HkdfHmacSha256], + short_authentication_string: vec![ShortAuthenticationString::Decimal], + }) + .unwrap(), + ), + }; + + let json_data = json!({ + "from_device": "123", + "method": "m.sas.v1", + "key_agreement_protocols": ["curve25519"], + "hashes": ["sha256"], + "message_authentication_codes": ["hkdf-hmac-sha256"], + "short_authentication_string": ["decimal"], + "m.relates_to": { + "rel_type": "m.reference", + "event_id": event_id, + } + }); + + assert_eq!(to_json_value(&key_verification_start_content).unwrap(), json_data); + } + #[test] fn deserialization() { let json = json!({ @@ -423,6 +490,50 @@ mod tests { ); } + #[test] + #[cfg(feature = "unstable-pre-spec")] + fn in_room_deserialization() { + let id = event_id!("$1598361704261elfgc:localhost"); + + let json = json!({ + "from_device": "123", + "method": "m.sas.v1", + "hashes": ["sha256"], + "key_agreement_protocols": ["curve25519"], + "message_authentication_codes": ["hkdf-hmac-sha256"], + "short_authentication_string": ["decimal"], + "m.relates_to": { + "rel_type": "m.reference", + "event_id": id, + } + }); + + // Deserialize the content struct separately to verify `TryFromRaw` is implemented for it. + assert_matches!( + from_json_value::>(json) + .unwrap() + .deserialize() + .unwrap(), + StartEventContent { + from_device, + relation: Relation { + event_id, + }, + method: StartMethod::MSasV1(MSasV1Content { + hashes, + key_agreement_protocols, + message_authentication_codes, + short_authentication_string, + }) + } if from_device == "123" + && event_id == id + && hashes == vec![HashAlgorithm::Sha256] + && key_agreement_protocols == vec![KeyAgreementProtocol::Curve25519] + && message_authentication_codes == vec![MessageAuthenticationCode::HkdfHmacSha256] + && short_authentication_string == vec![ShortAuthenticationString::Decimal] + ); + } + #[test] fn deserialization_failure() { // Ensure that invalid JSON creates a `serde_json::Error` and not `InvalidEvent`