events: Add support for custom SecretStorageEncryptionAlgorithm

This commit is contained in:
Kévin Commaille 2023-10-02 12:44:18 +02:00 committed by Kévin Commaille
parent 9696e9083f
commit cf4a5d263f
3 changed files with 158 additions and 11 deletions

View file

@ -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

View file

@ -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<T: 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")
);
}
}

View file

@ -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<D>(deserializer: D) -> Result<Self, D::Error>
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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)
}
}
}
}