use js_int::{uint, UInt};
use serde::{Deserialize, Serialize};
use crate::{events::macros::EventContent, identifiers::KeyDerivationAlgorithm, serde::Base64};
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct PassPhrase {
pub algorithm: KeyDerivationAlgorithm,
pub salt: String,
pub iterations: UInt,
#[serde(default = "default_bits", skip_serializing_if = "is_default_bits")]
pub bits: UInt,
}
impl PassPhrase {
pub fn new(salt: String, iterations: UInt) -> Self {
Self { algorithm: KeyDerivationAlgorithm::Pbkfd2, salt, iterations, bits: default_bits() }
}
}
fn default_bits() -> UInt {
uint!(256)
}
fn is_default_bits(val: &UInt) -> bool {
*val == default_bits()
}
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
#[ruma_event(type = "m.secret_storage.key.*", kind = GlobalAccountData)]
pub struct SecretStorageKeyEventContent {
#[ruma_event(type_fragment)]
#[serde(skip)]
pub key_id: String,
pub name: String,
#[serde(flatten)]
pub algorithm: SecretEncryptionAlgorithm,
#[serde(skip_serializing_if = "Option::is_none")]
pub passphrase: Option<PassPhrase>,
}
impl SecretStorageKeyEventContent {
pub fn new(key_id: String, name: String, algorithm: SecretEncryptionAlgorithm) -> Self {
Self { key_id, name, algorithm, passphrase: None }
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "algorithm")]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub enum SecretEncryptionAlgorithm {
#[serde(rename = "m.secret_storage.v1.aes-hmac-sha2")]
SecretStorageV1AesHmacSha2 {
iv: Base64,
mac: Base64,
},
}
#[cfg(test)]
mod tests {
use js_int::uint;
use matches::assert_matches;
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
use super::{PassPhrase, SecretEncryptionAlgorithm, SecretStorageKeyEventContent};
use crate::{events::GlobalAccountDataEvent, serde::Base64, KeyDerivationAlgorithm};
#[test]
fn test_key_description_serialization() {
let content = SecretStorageKeyEventContent::new(
"my_key".into(),
"my_key".into(),
SecretEncryptionAlgorithm::SecretStorageV1AesHmacSha2 {
iv: Base64::parse("YWJjZGVmZ2hpamtsbW5vcA").unwrap(),
mac: Base64::parse("aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U").unwrap(),
},
);
let json = json!({
"name": "my_key",
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
"iv": "YWJjZGVmZ2hpamtsbW5vcA",
"mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U"
});
assert_eq!(to_json_value(&content).unwrap(), json);
}
#[test]
fn test_key_description_deserialization() {
let json = json!({
"name": "my_key",
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
"iv": "YWJjZGVmZ2hpamtsbW5vcA",
"mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U"
});
assert_matches!(
from_json_value(json).unwrap(),
SecretStorageKeyEventContent {
key_id: _,
name,
algorithm: SecretEncryptionAlgorithm::SecretStorageV1AesHmacSha2 {
iv,
mac,
},
passphrase: None,
}
if name == *"my_key"
&& iv == Base64::parse("YWJjZGVmZ2hpamtsbW5vcA").unwrap()
&& mac == Base64::parse("aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U").unwrap()
)
}
#[test]
fn test_key_description_with_passphrase_serialization() {
let content = SecretStorageKeyEventContent {
passphrase: Some(PassPhrase::new("rocksalt".into(), uint!(8))),
..SecretStorageKeyEventContent::new(
"my_key".into(),
"my_key".into(),
SecretEncryptionAlgorithm::SecretStorageV1AesHmacSha2 {
iv: Base64::parse("YWJjZGVmZ2hpamtsbW5vcA").unwrap(),
mac: Base64::parse("aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U").unwrap(),
},
)
};
let json = json!({
"name": "my_key",
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
"iv": "YWJjZGVmZ2hpamtsbW5vcA",
"mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U",
"passphrase": {
"algorithm": "m.pbkdf2",
"salt": "rocksalt",
"iterations": 8
}
});
assert_eq!(to_json_value(&content).unwrap(), json);
}
#[test]
fn test_key_description_with_passphrase_deserialization() {
let json = json!({
"name": "my_key",
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
"iv": "YWJjZGVmZ2hpamtsbW5vcA",
"mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U",
"passphrase": {
"algorithm": "m.pbkdf2",
"salt": "rocksalt",
"iterations": 8,
"bits": 256
}
});
assert_matches!(
from_json_value(json).unwrap(),
SecretStorageKeyEventContent {
key_id: _key,
name,
algorithm: SecretEncryptionAlgorithm::SecretStorageV1AesHmacSha2 {
iv,
mac,
},
passphrase: Some(PassPhrase {
algorithm: KeyDerivationAlgorithm::Pbkfd2,
salt,
iterations,
bits
})
}
if name == *"my_key"
&& iv == Base64::parse("YWJjZGVmZ2hpamtsbW5vcA").unwrap()
&& mac == Base64::parse("aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U").unwrap()
&& salt == *"rocksalt"
&& iterations == uint!(8)
&& bits == uint!(256)
)
}
#[test]
fn test_event_serialization() {
let event = GlobalAccountDataEvent {
content: SecretStorageKeyEventContent::new(
"my_key_id".into(),
"my_key".into(),
SecretEncryptionAlgorithm::SecretStorageV1AesHmacSha2 {
iv: Base64::parse("YWJjZGVmZ2hpamtsbW5vcA").unwrap(),
mac: Base64::parse("aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U").unwrap(),
},
),
};
let json = json!({
"type": "m.secret_storage.key.my_key_id",
"content": {
"name": "my_key",
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
"iv": "YWJjZGVmZ2hpamtsbW5vcA",
"mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U"
}
});
assert_eq!(to_json_value(&event).unwrap(), json);
}
#[test]
fn test_event_deserialization() {
let json = json!({
"type": "m.secret_storage.key.my_key_id",
"content": {
"name": "my_key",
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
"iv": "YWJjZGVmZ2hpamtsbW5vcA",
"mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U"
}
});
assert_matches!(
from_json_value(json).unwrap(),
GlobalAccountDataEvent {
content: SecretStorageKeyEventContent {
key_id,
name,
algorithm: SecretEncryptionAlgorithm::SecretStorageV1AesHmacSha2 {
iv,
mac,
},
passphrase: None,
}
}
if key_id == *"my_key_id"
&& name == *"my_key"
&& iv == Base64::parse("YWJjZGVmZ2hpamtsbW5vcA").unwrap()
&& mac == Base64::parse("aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U").unwrap()
)
}
}