From cf4a5d263fc96c32647fdcac6133720b52d19d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Mon, 2 Oct 2023 12:44:18 +0200 Subject: [PATCH] events: Add support for custom SecretStorageEncryptionAlgorithm --- crates/ruma-events/CHANGELOG.md | 1 + crates/ruma-events/src/secret_storage/key.rs | 104 ++++++++++++++++-- .../key/secret_encryption_algorithm_serde.rs | 64 +++++++++++ 3 files changed, 158 insertions(+), 11 deletions(-) create mode 100644 crates/ruma-events/src/secret_storage/key/secret_encryption_algorithm_serde.rs diff --git a/crates/ruma-events/CHANGELOG.md b/crates/ruma-events/CHANGELOG.md index b22d945f..74e04a86 100644 --- a/crates/ruma-events/CHANGELOG.md +++ b/crates/ruma-events/CHANGELOG.md @@ -69,6 +69,7 @@ Improvements: - `RedactedRoomPowerLevelsEventContent`, - `RedactedRoomMemberEventContent` - Add `RoomMessageEventContent::make_reply_to_raw` to build replies to any event +- Add support for custom `SecretStorageEncryptionAlgorithm` # 0.26.1 diff --git a/crates/ruma-events/src/secret_storage/key.rs b/crates/ruma-events/src/secret_storage/key.rs index 0100e83e..d8b76bc7 100644 --- a/crates/ruma-events/src/secret_storage/key.rs +++ b/crates/ruma-events/src/secret_storage/key.rs @@ -2,9 +2,17 @@ //! //! [`m.secret_storage.key.*`]: https://spec.matrix.org/latest/client-server-api/#key-storage +use std::borrow::Cow; + use js_int::{uint, UInt}; -use ruma_common::{serde::Base64, KeyDerivationAlgorithm}; +use ruma_common::{ + serde::{Base64, JsonObject}, + KeyDerivationAlgorithm, +}; use serde::{Deserialize, Serialize}; +use serde_json::Value as JsonValue; + +mod secret_encryption_algorithm_serde; use crate::macros::EventContent; @@ -77,16 +85,49 @@ impl SecretStorageKeyEventContent { } /// An algorithm and its properties, used to encrypt a secret. -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(tag = "algorithm")] +#[derive(Debug, Clone)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub enum SecretStorageEncryptionAlgorithm { - #[serde(rename = "m.secret_storage.v1.aes-hmac-sha2")] /// Encrypted using the `m.secret_storage.v1.aes-hmac-sha2` algorithm. /// /// Secrets using this method are encrypted using AES-CTR-256 and authenticated using /// HMAC-SHA-256. V1AesHmacSha2(SecretStorageV1AesHmacSha2Properties), + + /// Encrypted using a custom algorithm. + #[doc(hidden)] + _Custom(CustomSecretEncryptionAlgorithm), +} + +impl SecretStorageEncryptionAlgorithm { + /// The `algorithm` string. + pub fn algorithm(&self) -> &str { + match self { + Self::V1AesHmacSha2(_) => "m.secret_storage.v1.aes-hmac-sha2", + Self::_Custom(c) => &c.algorithm, + } + } + + /// The algorithm-specific properties. + /// + /// The returned JSON object won't contain the `algorithm` field, use [`Self::algorithm()`] to + /// access it. + /// + /// Prefer to use the public variants of `SecretStorageEncryptionAlgorithm` where possible; this + /// method is meant to be used for custom algorithms only. + pub fn properties(&self) -> Cow<'_, JsonObject> { + fn serialize(obj: &T) -> JsonObject { + match serde_json::to_value(obj).expect("secret properties serialization to succeed") { + JsonValue::Object(obj) => obj, + _ => panic!("all secret properties must serialize to objects"), + } + } + + match self { + Self::V1AesHmacSha2(p) => Cow::Owned(serialize(p)), + Self::_Custom(c) => Cow::Borrowed(&c.properties), + } + } } /// The key properties for the `m.secret_storage.v1.aes-hmac-sha2` algorithm. @@ -108,6 +149,18 @@ impl SecretStorageV1AesHmacSha2Properties { } } +/// The payload for a custom secret encryption algorithm. +#[doc(hidden)] +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct CustomSecretEncryptionAlgorithm { + /// The encryption algorithm to be used for the key. + algorithm: String, + + /// Algorithm-specific properties. + #[serde(flatten)] + properties: JsonObject, +} + #[cfg(test)] mod tests { use assert_matches2::assert_matches; @@ -125,7 +178,7 @@ mod tests { use crate::{EventContentFromType, GlobalAccountDataEvent}; #[test] - fn test_key_description_serialization() { + fn key_description_serialization() { let mut content = SecretStorageKeyEventContent::new( "my_key".into(), SecretStorageEncryptionAlgorithm::V1AesHmacSha2(SecretStorageV1AesHmacSha2Properties { @@ -146,7 +199,7 @@ mod tests { } #[test] - fn test_key_description_deserialization() { + fn key_description_deserialization() { let json = to_raw_json_value(&json!({ "name": "my_key", "algorithm": "m.secret_storage.v1.aes-hmac-sha2", @@ -172,7 +225,7 @@ mod tests { } #[test] - fn test_key_description_deserialization_without_name() { + fn key_description_deserialization_without_name() { let json = to_raw_json_value(&json!({ "algorithm": "m.secret_storage.v1.aes-hmac-sha2", "iv": "YWJjZGVmZ2hpamtsbW5vcA", @@ -197,7 +250,7 @@ mod tests { } #[test] - fn test_key_description_with_passphrase_serialization() { + fn key_description_with_passphrase_serialization() { let mut content = SecretStorageKeyEventContent { passphrase: Some(PassPhrase::new("rocksalt".into(), uint!(8))), ..SecretStorageKeyEventContent::new( @@ -228,7 +281,7 @@ mod tests { } #[test] - fn test_key_description_with_passphrase_deserialization() { + fn key_description_with_passphrase_deserialization() { let json = to_raw_json_value(&json!({ "name": "my_key", "algorithm": "m.secret_storage.v1.aes-hmac-sha2", @@ -265,7 +318,7 @@ mod tests { } #[test] - fn test_event_serialization() { + fn event_serialization() { let mut content = SecretStorageKeyEventContent::new( "my_key_id".into(), SecretStorageEncryptionAlgorithm::V1AesHmacSha2(SecretStorageV1AesHmacSha2Properties { @@ -286,7 +339,7 @@ mod tests { } #[test] - fn test_event_deserialization() { + fn event_deserialization() { let json = json!({ "type": "m.secret_storage.key.my_key_id", "content": { @@ -313,4 +366,33 @@ mod tests { assert_eq!(iv.encode(), "YWJjZGVmZ2hpamtsbW5vcA"); assert_eq!(mac.encode(), "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U"); } + + #[test] + fn custom_algorithm_key_description_deserialization() { + let json = to_raw_json_value(&json!({ + "name": "my_key", + "algorithm": "io.ruma.custom_alg", + "io.ruma.custom_prop1": "YWJjZGVmZ2hpamtsbW5vcA", + "io.ruma.custom_prop2": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U" + })) + .unwrap(); + + let content = + SecretStorageKeyEventContent::from_parts("m.secret_storage.key.test", &json).unwrap(); + assert_eq!(content.name.unwrap(), "my_key"); + assert_matches!(content.passphrase, None); + + let algorithm = content.algorithm; + assert_eq!(algorithm.algorithm(), "io.ruma.custom_alg"); + let properties = algorithm.properties(); + assert_eq!(properties.len(), 2); + assert_eq!( + properties.get("io.ruma.custom_prop1").unwrap().as_str(), + Some("YWJjZGVmZ2hpamtsbW5vcA") + ); + assert_eq!( + properties.get("io.ruma.custom_prop2").unwrap().as_str(), + Some("aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U") + ); + } } diff --git a/crates/ruma-events/src/secret_storage/key/secret_encryption_algorithm_serde.rs b/crates/ruma-events/src/secret_storage/key/secret_encryption_algorithm_serde.rs new file mode 100644 index 00000000..a0c448da --- /dev/null +++ b/crates/ruma-events/src/secret_storage/key/secret_encryption_algorithm_serde.rs @@ -0,0 +1,64 @@ +use serde::{de, Deserialize, Serialize}; + +use super::{ + CustomSecretEncryptionAlgorithm, SecretStorageEncryptionAlgorithm, + SecretStorageV1AesHmacSha2Properties, +}; + +#[derive(Deserialize)] +#[serde(untagged)] +enum SecretStorageEncryptionAlgorithmDeHelper { + Known(KnownSecretStorageEncryptionAlgorithmDeHelper), + Unknown(CustomSecretEncryptionAlgorithm), +} + +#[derive(Deserialize)] +#[serde(tag = "algorithm")] +enum KnownSecretStorageEncryptionAlgorithmDeHelper { + #[serde(rename = "m.secret_storage.v1.aes-hmac-sha2")] + V1AesHmacSha2(SecretStorageV1AesHmacSha2Properties), +} + +impl<'de> Deserialize<'de> for SecretStorageEncryptionAlgorithm { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let helper = SecretStorageEncryptionAlgorithmDeHelper::deserialize(deserializer)?; + + Ok(match helper { + SecretStorageEncryptionAlgorithmDeHelper::Known(k) => match k { + KnownSecretStorageEncryptionAlgorithmDeHelper::V1AesHmacSha2(p) => { + Self::V1AesHmacSha2(p) + } + }, + SecretStorageEncryptionAlgorithmDeHelper::Unknown(c) => Self::_Custom(c), + }) + } +} + +#[derive(Debug, Serialize)] +struct SecretStorageEncryptionAlgorithmSerHelper<'a, T: Serialize> { + algorithm: &'a str, + #[serde(flatten)] + properties: T, +} + +impl Serialize for SecretStorageEncryptionAlgorithm { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let algorithm = self.algorithm(); + match self { + Self::V1AesHmacSha2(properties) => { + SecretStorageEncryptionAlgorithmSerHelper { algorithm, properties } + .serialize(serializer) + } + Self::_Custom(properties) => { + SecretStorageEncryptionAlgorithmSerHelper { algorithm, properties } + .serialize(serializer) + } + } + } +}