1use std::borrow::Cow;
6
7use js_int::{UInt, uint};
8use ruma_common::{
9 KeyDerivationAlgorithm,
10 serde::{Base64, JsonObject},
11};
12use serde::{Deserialize, Serialize};
13use serde_json::Value as JsonValue;
14
15mod secret_encryption_algorithm_serde;
16
17use crate::macros::EventContent;
18
19#[derive(Clone, Debug, Deserialize, Serialize)]
21#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
22pub struct PassPhrase {
23 pub algorithm: KeyDerivationAlgorithm,
27
28 pub salt: String,
30
31 pub iterations: UInt,
33
34 #[serde(default = "default_bits", skip_serializing_if = "is_default_bits")]
38 pub bits: UInt,
39}
40
41impl PassPhrase {
42 pub fn new(salt: String, iterations: UInt) -> Self {
44 Self { algorithm: KeyDerivationAlgorithm::Pbkfd2, salt, iterations, bits: default_bits() }
45 }
46}
47
48fn default_bits() -> UInt {
49 uint!(256)
50}
51
52fn is_default_bits(val: &UInt) -> bool {
53 *val == default_bits()
54}
55
56#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
62#[derive(Clone, Debug, Serialize, EventContent)]
63#[ruma_event(type = "m.secret_storage.key.*", kind = GlobalAccountData)]
64pub struct SecretStorageKeyEventContent {
65 #[ruma_event(type_fragment)]
67 #[serde(skip)]
68 pub key_id: String,
69
70 pub name: Option<String>,
72
73 #[serde(flatten)]
77 pub algorithm: SecretStorageEncryptionAlgorithm,
78
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub passphrase: Option<PassPhrase>,
82}
83
84impl SecretStorageKeyEventContent {
85 pub fn new(key_id: String, algorithm: SecretStorageEncryptionAlgorithm) -> Self {
87 Self { key_id, name: None, algorithm, passphrase: None }
88 }
89}
90
91#[derive(Debug, Clone)]
93#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
94pub enum SecretStorageEncryptionAlgorithm {
95 V1AesHmacSha2(SecretStorageV1AesHmacSha2Properties),
100
101 #[doc(hidden)]
103 _Custom(CustomSecretEncryptionAlgorithm),
104}
105
106impl SecretStorageEncryptionAlgorithm {
107 pub fn algorithm(&self) -> &str {
109 match self {
110 Self::V1AesHmacSha2(_) => "m.secret_storage.v1.aes-hmac-sha2",
111 Self::_Custom(c) => &c.algorithm,
112 }
113 }
114
115 pub fn properties(&self) -> Cow<'_, JsonObject> {
123 fn serialize<T: Serialize>(obj: &T) -> JsonObject {
124 match serde_json::to_value(obj).expect("secret properties serialization to succeed") {
125 JsonValue::Object(obj) => obj,
126 _ => panic!("all secret properties must serialize to objects"),
127 }
128 }
129
130 match self {
131 Self::V1AesHmacSha2(p) => Cow::Owned(serialize(p)),
132 Self::_Custom(c) => Cow::Borrowed(&c.properties),
133 }
134 }
135}
136
137#[derive(Debug, Clone, Deserialize, Serialize)]
142#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
143pub struct SecretStorageV1AesHmacSha2Properties {
144 pub iv: Option<Base64>,
146
147 pub mac: Option<Base64>,
149}
150
151impl SecretStorageV1AesHmacSha2Properties {
152 pub fn new(iv: Option<Base64>, mac: Option<Base64>) -> Self {
155 Self { iv, mac }
156 }
157}
158
159#[doc(hidden)]
161#[derive(Clone, Debug, Deserialize, Serialize)]
162pub struct CustomSecretEncryptionAlgorithm {
163 algorithm: String,
165
166 #[serde(flatten)]
168 properties: JsonObject,
169}
170
171#[cfg(test)]
172mod tests {
173 use assert_matches2::assert_matches;
174 use js_int::uint;
175 use ruma_common::{
176 KeyDerivationAlgorithm, canonical_json::assert_to_canonical_json_eq, serde::Base64,
177 };
178 use serde_json::{
179 from_value as from_json_value, json, value::to_raw_value as to_raw_json_value,
180 };
181
182 use super::{
183 PassPhrase, SecretStorageEncryptionAlgorithm, SecretStorageKeyEventContent,
184 SecretStorageV1AesHmacSha2Properties,
185 };
186 use crate::{AnyGlobalAccountDataEvent, EventContentFromType, GlobalAccountDataEvent};
187
188 #[test]
189 fn key_description_serialization() {
190 let mut content = SecretStorageKeyEventContent::new(
191 "my_key".into(),
192 SecretStorageEncryptionAlgorithm::V1AesHmacSha2(SecretStorageV1AesHmacSha2Properties {
193 iv: Some(Base64::parse("YWJjZGVmZ2hpamtsbW5vcA").unwrap()),
194 mac: Some(Base64::parse("aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U").unwrap()),
195 }),
196 );
197 content.name = Some("my_key".to_owned());
198
199 assert_to_canonical_json_eq!(
200 content,
201 json!({
202 "name": "my_key",
203 "algorithm": "m.secret_storage.v1.aes-hmac-sha2",
204 "iv": "YWJjZGVmZ2hpamtsbW5vcA",
205 "mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U",
206 }),
207 );
208 }
209
210 #[test]
211 fn key_description_deserialization() {
212 let json = to_raw_json_value(&json!({
213 "name": "my_key",
214 "algorithm": "m.secret_storage.v1.aes-hmac-sha2",
215 "iv": "YWJjZGVmZ2hpamtsbW5vcA",
216 "mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U"
217 }))
218 .unwrap();
219
220 let content =
221 SecretStorageKeyEventContent::from_parts("m.secret_storage.key.test", &json).unwrap();
222 assert_eq!(content.name.unwrap(), "my_key");
223 assert_matches!(content.passphrase, None);
224
225 assert_matches!(
226 content.algorithm,
227 SecretStorageEncryptionAlgorithm::V1AesHmacSha2(SecretStorageV1AesHmacSha2Properties {
228 iv: Some(iv),
229 mac: Some(mac)
230 })
231 );
232
233 assert_eq!(iv.encode(), "YWJjZGVmZ2hpamtsbW5vcA");
234 assert_eq!(mac.encode(), "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U");
235 }
236
237 #[test]
238 fn key_description_deserialization_without_name() {
239 let json = to_raw_json_value(&json!({
240 "algorithm": "m.secret_storage.v1.aes-hmac-sha2",
241 "iv": "YWJjZGVmZ2hpamtsbW5vcA",
242 "mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U"
243 }))
244 .unwrap();
245
246 let content =
247 SecretStorageKeyEventContent::from_parts("m.secret_storage.key.test", &json).unwrap();
248 assert!(content.name.is_none());
249 assert_matches!(content.passphrase, None);
250
251 assert_matches!(
252 content.algorithm,
253 SecretStorageEncryptionAlgorithm::V1AesHmacSha2(SecretStorageV1AesHmacSha2Properties {
254 iv: Some(iv),
255 mac: Some(mac)
256 })
257 );
258 assert_eq!(iv.encode(), "YWJjZGVmZ2hpamtsbW5vcA");
259 assert_eq!(mac.encode(), "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U");
260 }
261
262 #[test]
263 fn key_description_with_passphrase_serialization() {
264 let mut content = SecretStorageKeyEventContent {
265 passphrase: Some(PassPhrase::new("rocksalt".into(), uint!(8))),
266 ..SecretStorageKeyEventContent::new(
267 "my_key".into(),
268 SecretStorageEncryptionAlgorithm::V1AesHmacSha2(
269 SecretStorageV1AesHmacSha2Properties {
270 iv: Some(Base64::parse("YWJjZGVmZ2hpamtsbW5vcA").unwrap()),
271 mac: Some(Base64::parse("aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U").unwrap()),
272 },
273 ),
274 )
275 };
276 content.name = Some("my_key".to_owned());
277
278 assert_to_canonical_json_eq!(
279 content,
280 json!({
281 "name": "my_key",
282 "algorithm": "m.secret_storage.v1.aes-hmac-sha2",
283 "iv": "YWJjZGVmZ2hpamtsbW5vcA",
284 "mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U",
285 "passphrase": {
286 "algorithm": "m.pbkdf2",
287 "salt": "rocksalt",
288 "iterations": 8,
289 },
290 }),
291 );
292 }
293
294 #[test]
295 fn key_description_with_passphrase_deserialization() {
296 let json = to_raw_json_value(&json!({
297 "name": "my_key",
298 "algorithm": "m.secret_storage.v1.aes-hmac-sha2",
299 "iv": "YWJjZGVmZ2hpamtsbW5vcA",
300 "mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U",
301 "passphrase": {
302 "algorithm": "m.pbkdf2",
303 "salt": "rocksalt",
304 "iterations": 8,
305 "bits": 256
306 }
307 }))
308 .unwrap();
309
310 let content =
311 SecretStorageKeyEventContent::from_parts("m.secret_storage.key.test", &json).unwrap();
312 assert_eq!(content.name.unwrap(), "my_key");
313
314 let passphrase = content.passphrase.unwrap();
315 assert_eq!(passphrase.algorithm, KeyDerivationAlgorithm::Pbkfd2);
316 assert_eq!(passphrase.salt, "rocksalt");
317 assert_eq!(passphrase.iterations, uint!(8));
318 assert_eq!(passphrase.bits, uint!(256));
319
320 assert_matches!(
321 content.algorithm,
322 SecretStorageEncryptionAlgorithm::V1AesHmacSha2(SecretStorageV1AesHmacSha2Properties {
323 iv: Some(iv),
324 mac: Some(mac)
325 })
326 );
327 assert_eq!(iv.encode(), "YWJjZGVmZ2hpamtsbW5vcA");
328 assert_eq!(mac.encode(), "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U");
329 }
330
331 #[test]
332 fn event_content_serialization() {
333 let mut content = SecretStorageKeyEventContent::new(
334 "my_key_id".into(),
335 SecretStorageEncryptionAlgorithm::V1AesHmacSha2(SecretStorageV1AesHmacSha2Properties {
336 iv: Some(Base64::parse("YWJjZGVmZ2hpamtsbW5vcA").unwrap()),
337 mac: Some(Base64::parse("aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U").unwrap()),
338 }),
339 );
340 content.name = Some("my_key".to_owned());
341
342 assert_to_canonical_json_eq!(
343 content,
344 json!({
345 "name": "my_key",
346 "algorithm": "m.secret_storage.v1.aes-hmac-sha2",
347 "iv": "YWJjZGVmZ2hpamtsbW5vcA",
348 "mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U",
349 }),
350 );
351 }
352
353 #[test]
354 fn event_serialization() {
355 let mut content = SecretStorageKeyEventContent::new(
356 "my_key_id".into(),
357 SecretStorageEncryptionAlgorithm::V1AesHmacSha2(SecretStorageV1AesHmacSha2Properties {
358 iv: Some(Base64::parse("YWJjZGVmZ2hpamtsbW5vcA").unwrap()),
359 mac: Some(Base64::parse("aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U").unwrap()),
360 }),
361 );
362 content.name = Some("my_key".to_owned());
363 let event = GlobalAccountDataEvent { content };
364
365 assert_to_canonical_json_eq!(
366 event,
367 json!({
368 "type": "m.secret_storage.key.my_key_id",
369 "content": {
370 "name": "my_key",
371 "algorithm": "m.secret_storage.v1.aes-hmac-sha2",
372 "iv": "YWJjZGVmZ2hpamtsbW5vcA",
373 "mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U",
374 },
375 }),
376 );
377 }
378
379 #[test]
380 fn event_deserialization() {
381 let json = json!({
382 "type": "m.secret_storage.key.my_key_id",
383 "content": {
384 "name": "my_key",
385 "algorithm": "m.secret_storage.v1.aes-hmac-sha2",
386 "iv": "YWJjZGVmZ2hpamtsbW5vcA",
387 "mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U"
388 }
389 });
390
391 let any_ev = from_json_value::<AnyGlobalAccountDataEvent>(json).unwrap();
392 assert_matches!(any_ev, AnyGlobalAccountDataEvent::SecretStorageKey(ev));
393 assert_eq!(ev.content.key_id, "my_key_id");
394 assert_eq!(ev.content.name.unwrap(), "my_key");
395 assert_matches!(ev.content.passphrase, None);
396
397 assert_matches!(
398 ev.content.algorithm,
399 SecretStorageEncryptionAlgorithm::V1AesHmacSha2(SecretStorageV1AesHmacSha2Properties {
400 iv: Some(iv),
401 mac: Some(mac)
402 })
403 );
404 assert_eq!(iv.encode(), "YWJjZGVmZ2hpamtsbW5vcA");
405 assert_eq!(mac.encode(), "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U");
406 }
407
408 #[test]
409 fn custom_algorithm_key_description_deserialization() {
410 let json = to_raw_json_value(&json!({
411 "name": "my_key",
412 "algorithm": "io.ruma.custom_alg",
413 "io.ruma.custom_prop1": "YWJjZGVmZ2hpamtsbW5vcA",
414 "io.ruma.custom_prop2": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U"
415 }))
416 .unwrap();
417
418 let content =
419 SecretStorageKeyEventContent::from_parts("m.secret_storage.key.test", &json).unwrap();
420 assert_eq!(content.name.unwrap(), "my_key");
421 assert_matches!(content.passphrase, None);
422
423 let algorithm = content.algorithm;
424 assert_eq!(algorithm.algorithm(), "io.ruma.custom_alg");
425 let properties = algorithm.properties();
426 assert_eq!(properties.len(), 2);
427 assert_eq!(
428 properties.get("io.ruma.custom_prop1").unwrap().as_str(),
429 Some("YWJjZGVmZ2hpamtsbW5vcA")
430 );
431 assert_eq!(
432 properties.get("io.ruma.custom_prop2").unwrap().as_str(),
433 Some("aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U")
434 );
435 }
436}