Skip to main content

ruma_events/secret_storage/
secret.rs

1//! Types for events used for secrets to be stored in the user's account_data.
2
3use std::collections::BTreeMap;
4
5use ruma_common::serde::{Base64, JsonCastable, Raw};
6use serde::{Deserialize, Serialize};
7
8/// A secret and its encrypted contents.
9#[derive(Clone, Debug, Serialize, Deserialize)]
10#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
11pub struct SecretEventContent {
12    /// Map from key ID to the encrypted data.
13    ///
14    /// The exact format for the encrypted data is dependent on the key algorithm.
15    pub encrypted: BTreeMap<String, Raw<SecretEncryptedData>>,
16}
17
18impl SecretEventContent {
19    /// Create a new `SecretEventContent` with the given encrypted content.
20    pub fn new(encrypted: BTreeMap<String, Raw<SecretEncryptedData>>) -> Self {
21        Self { encrypted }
22    }
23}
24
25/// Encrypted data for a secret storage encryption algorithm.
26///
27/// This type cannot be constructed, it is only used for its semantic value and is meant to be used
28/// with the `Raw::cast()` and `Raw::deserialize_as()` APIs.
29///
30/// It can be cast to or from the following types:
31///
32/// * [`AesHmacSha2EncryptedData`]
33///
34/// Convenience methods are also available for casting encrypted data from or to known compatible
35/// types.
36#[non_exhaustive]
37pub struct SecretEncryptedData;
38
39impl SecretEncryptedData {
40    /// Construct a `Raw<SecretEncryptedData>` by casting the given serialized encrypted data.
41    pub fn new<T: JsonCastable<Self>>(encrypted_data: Raw<T>) -> Raw<Self> {
42        encrypted_data.cast()
43    }
44
45    /// Serialize the given encrypted data as a `Raw<SecretEncryptedData>`.
46    pub fn serialize<T: Serialize + JsonCastable<Self>>(
47        encrypted_data: &T,
48    ) -> Result<Raw<Self>, serde_json::Error> {
49        Raw::new(encrypted_data).map(Raw::cast)
50    }
51
52    /// Deserialize the given data encrypted with the `m.secret_storage.v1.aes-hmac-sha2` algorithm.
53    pub fn deserialize_as_aes_hmac_sha2(
54        encrypted_data: &Raw<Self>,
55    ) -> Result<AesHmacSha2EncryptedData, serde_json::Error> {
56        encrypted_data.deserialize_as()
57    }
58}
59
60/// Data encrypted using the `m.secret_storage.v1.aes-hmac-sha2` algorithm.
61#[derive(Clone, Debug, Serialize, Deserialize)]
62#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
63pub struct AesHmacSha2EncryptedData {
64    /// The 16-byte initialization vector, encoded as base64.
65    pub iv: Base64,
66
67    /// The AES-CTR-encrypted data, encoded as base64.
68    pub ciphertext: Base64,
69
70    /// The MAC, encoded as base64.
71    pub mac: Base64,
72}
73
74impl AesHmacSha2EncryptedData {
75    /// Construct a new `` with the given initialization vector, ciphertext and MAC.
76    pub fn new(iv: Base64, ciphertext: Base64, mac: Base64) -> Self {
77        Self { iv, ciphertext, mac }
78    }
79}
80
81impl JsonCastable<SecretEncryptedData> for AesHmacSha2EncryptedData {}
82
83impl JsonCastable<AesHmacSha2EncryptedData> for SecretEncryptedData {}
84
85#[cfg(test)]
86mod tests {
87    use std::collections::BTreeMap;
88
89    use assert_matches2::assert_matches;
90    use ruma_common::{canonical_json::assert_to_canonical_json_eq, serde::Base64};
91    use serde_json::{from_value as from_json_value, json};
92
93    use super::{AesHmacSha2EncryptedData, SecretEncryptedData, SecretEventContent};
94
95    #[test]
96    fn test_secret_serialization() {
97        let key_one_data = AesHmacSha2EncryptedData {
98            iv: Base64::parse("YWJjZGVmZ2hpamtsbW5vcA").unwrap(),
99            ciphertext: Base64::parse("dGhpc2lzZGVmaW5pdGVseWNpcGhlcnRleHQ").unwrap(),
100            mac: Base64::parse("aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U").unwrap(),
101        };
102
103        let mut encrypted = BTreeMap::new();
104        encrypted
105            .insert("key_one".to_owned(), SecretEncryptedData::serialize(&key_one_data).unwrap());
106
107        let content = SecretEventContent::new(encrypted);
108
109        assert_to_canonical_json_eq!(
110            content,
111            json!({
112                "encrypted": {
113                    "key_one" : {
114                        "iv": "YWJjZGVmZ2hpamtsbW5vcA",
115                        "ciphertext": "dGhpc2lzZGVmaW5pdGVseWNpcGhlcnRleHQ",
116                        "mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U",
117                    },
118                },
119            }),
120        );
121    }
122
123    #[test]
124    fn test_secret_deserialization() {
125        let json = json!({
126            "encrypted": {
127                "key_one" : {
128                    "iv": "YWJjZGVmZ2hpamtsbW5vcA",
129                    "ciphertext": "dGhpc2lzZGVmaW5pdGVseWNpcGhlcnRleHQ",
130                    "mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U"
131                }
132            }
133        });
134
135        let deserialized: SecretEventContent = from_json_value(json).unwrap();
136        let secret_data = deserialized.encrypted.get("key_one").unwrap();
137
138        assert_matches!(
139            SecretEncryptedData::deserialize_as_aes_hmac_sha2(secret_data),
140            Ok(AesHmacSha2EncryptedData { iv, ciphertext, mac })
141        );
142        assert_eq!(iv.encode(), "YWJjZGVmZ2hpamtsbW5vcA");
143        assert_eq!(ciphertext.encode(), "dGhpc2lzZGVmaW5pdGVseWNpcGhlcnRleHQ");
144        assert_eq!(mac.encode(), "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U");
145    }
146}