1use std::str::FromStr;
4
5use bitwarden_encoding::{B64, FromStrVisitor};
6use coset::{CborSerializable, CoseEncrypt0Builder, HeaderBuilder};
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9#[cfg(feature = "wasm")]
10use wasm_bindgen::convert::FromWasmAbi;
11
12use crate::{
13 ContentFormat, EncodedSymmetricKey, KeySlotIds, KeyStoreContext, SymmetricCryptoKey,
14 XAes256GcmKey,
15 cose::{
16 ContentNamespace, SafeObjectNamespace,
17 symmetric::{
18 CoseAlgorithmPolicy, CoseContentEncryptionAlgorithm, decrypt_cose0, encrypt_cose0,
19 },
20 },
21 keys::KeyId,
22 safe::{
23 DecodeSealedKeyError, decode_sealed_symmetric_key, extract_key_id,
24 helpers::{debug_fmt, set_safe_namespaces, validate_safe_namespaces},
25 set_contained_key_id,
26 },
27};
28
29#[derive(Debug, Error)]
31pub enum SymmetricKeyEnvelopeError {
32 #[error("Wrong key")]
34 WrongKey,
35 #[error("Parsing error {0}")]
37 Parsing(String),
38 #[error("Key missing error")]
40 KeyMissing,
41 #[error("Could not write to key store")]
43 KeyStore,
44 #[error("Wrong key type")]
46 WrongKeyType,
47 #[error("Invalid namespace")]
49 InvalidNamespace,
50}
51
52pub struct SymmetricKeyEnvelope {
54 cose_encrypt0: coset::CoseEncrypt0,
55}
56
57impl SymmetricKeyEnvelope {
58 pub fn seal<Ids: KeySlotIds>(
60 key_to_seal: Ids::Symmetric,
61 sealing_key: Ids::Symmetric,
62 namespace: SymmetricKeyEnvelopeNamespace,
63 ctx: &KeyStoreContext<Ids>,
64 ) -> Result<Self, SymmetricKeyEnvelopeError> {
65 let key_to_seal = ctx
67 .get_symmetric_key(key_to_seal)
68 .map_err(|_| SymmetricKeyEnvelopeError::KeyMissing)?;
69 let wrapping_key = ctx
70 .get_symmetric_key(sealing_key)
71 .map_err(|_| SymmetricKeyEnvelopeError::KeyMissing)?;
72
73 let wrapping_key: &XAes256GcmKey = match wrapping_key {
74 SymmetricCryptoKey::XAes256GcmKey(key) => key,
75 _ => return Err(SymmetricKeyEnvelopeError::WrongKeyType),
76 };
77
78 let (content_format, key_bytes) = match key_to_seal.to_encoded_raw() {
79 EncodedSymmetricKey::BitwardenLegacyKey(key_bytes) => {
80 (ContentFormat::BitwardenLegacyKey, key_bytes.to_vec())
81 }
82 EncodedSymmetricKey::CoseKey(key_bytes) => (ContentFormat::CoseKey, key_bytes.to_vec()),
83 };
84
85 let mut protected_header = HeaderBuilder::from(content_format).build();
86
87 set_contained_key_id(&mut protected_header, key_to_seal.key_id());
88
89 set_safe_namespaces(
90 &mut protected_header,
91 SafeObjectNamespace::SymmetricKeyEnvelope,
92 namespace,
93 );
94 protected_header.key_id = wrapping_key.key_id.as_slice().into();
95
96 let cose_encrypt0 = encrypt_cose0(
97 CoseContentEncryptionAlgorithm::XAes256Gcm,
98 CoseEncrypt0Builder::new(),
99 protected_header,
100 &key_bytes,
101 wrapping_key.enc_key.as_slice(),
102 )
103 .map_err(|_| SymmetricKeyEnvelopeError::WrongKeyType)?;
104
105 Ok(SymmetricKeyEnvelope { cose_encrypt0 })
106 }
107
108 pub fn unseal<Ids: KeySlotIds>(
110 &self,
111 wrapping_key: Ids::Symmetric,
112 namespace: SymmetricKeyEnvelopeNamespace,
113 ctx: &mut KeyStoreContext<Ids>,
114 ) -> Result<Ids::Symmetric, SymmetricKeyEnvelopeError> {
115 let wrapping_key_ref = ctx
116 .get_symmetric_key(wrapping_key)
117 .map_err(|_| SymmetricKeyEnvelopeError::KeyMissing)?;
118
119 let wrapping_key_inner = match wrapping_key_ref {
120 SymmetricCryptoKey::XAes256GcmKey(key) => key,
121 _ => return Err(SymmetricKeyEnvelopeError::WrongKeyType),
122 };
123
124 validate_safe_namespaces(
125 &self.cose_encrypt0.protected.header,
126 SafeObjectNamespace::SymmetricKeyEnvelope,
127 namespace,
128 )
129 .map_err(|_| SymmetricKeyEnvelopeError::InvalidNamespace)?;
130
131 let key_bytes = decrypt_cose0(
134 &self.cose_encrypt0,
135 CoseAlgorithmPolicy::Exactly(CoseContentEncryptionAlgorithm::XAes256Gcm),
136 wrapping_key_inner.enc_key.as_slice(),
137 )
138 .map_err(|_| SymmetricKeyEnvelopeError::WrongKey)?;
139
140 let key = decode_sealed_symmetric_key(&self.cose_encrypt0.protected.header, key_bytes)
141 .map_err(|e| match e {
142 DecodeSealedKeyError::InvalidContentFormat => {
143 SymmetricKeyEnvelopeError::Parsing("Invalid content format".to_string())
144 }
145 DecodeSealedKeyError::UnsupportedContentFormat
146 | DecodeSealedKeyError::InvalidKey => SymmetricKeyEnvelopeError::WrongKeyType,
147 })?;
148
149 Ok(ctx.add_local_symmetric_key(key))
150 }
151
152 pub fn contained_key_id(&self) -> Result<Option<KeyId>, SymmetricKeyEnvelopeError> {
154 extract_key_id(&self.cose_encrypt0.protected.header)
155 .map_err(|_| SymmetricKeyEnvelopeError::Parsing("Invalid contained key id".to_string()))
156 }
157}
158
159impl From<&SymmetricKeyEnvelope> for Vec<u8> {
160 fn from(val: &SymmetricKeyEnvelope) -> Self {
161 val.cose_encrypt0
162 .clone()
163 .to_vec()
164 .expect("Serialization to cose should not fail")
165 }
166}
167
168impl TryFrom<&Vec<u8>> for SymmetricKeyEnvelope {
169 type Error = coset::CoseError;
170
171 fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
172 let cose_encrypt0 = coset::CoseEncrypt0::from_slice(value)?;
173 Ok(SymmetricKeyEnvelope { cose_encrypt0 })
174 }
175}
176
177impl std::fmt::Debug for SymmetricKeyEnvelope {
178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179 let mut s = f.debug_struct("SymmetricKeyEnvelope");
180
181 if !self.cose_encrypt0.protected.header.key_id.is_empty() {
182 s.field(
183 "sealing_key_id",
184 &self.cose_encrypt0.protected.header.key_id,
185 );
186 }
187
188 debug_fmt::<SymmetricKeyEnvelopeNamespace>(&mut s, &self.cose_encrypt0.protected.header);
189
190 if let Ok(Some(key_id)) = self.contained_key_id() {
191 s.field("contained_key_id", &key_id);
192 }
193
194 s.finish()
195 }
196}
197
198impl FromStr for SymmetricKeyEnvelope {
199 type Err = SymmetricKeyEnvelopeError;
200
201 fn from_str(s: &str) -> Result<Self, Self::Err> {
202 let data = B64::try_from(s).map_err(|_| {
203 SymmetricKeyEnvelopeError::Parsing(
204 "Invalid WrappedSymmetricKey Base64 encoding".to_string(),
205 )
206 })?;
207 Self::try_from(&data.into_bytes()).map_err(|_| {
208 SymmetricKeyEnvelopeError::Parsing("Failed to parse SymmetricKeyEnvelope".to_string())
209 })
210 }
211}
212
213impl From<SymmetricKeyEnvelope> for String {
214 fn from(val: SymmetricKeyEnvelope) -> Self {
215 let serialized: Vec<u8> = (&val).into();
216 B64::from(serialized).to_string()
217 }
218}
219
220impl<'de> Deserialize<'de> for SymmetricKeyEnvelope {
221 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
222 where
223 D: serde::Deserializer<'de>,
224 {
225 deserializer.deserialize_str(FromStrVisitor::new())
226 }
227}
228
229impl Serialize for SymmetricKeyEnvelope {
230 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
231 where
232 S: serde::Serializer,
233 {
234 let serialized: Vec<u8> = self.into();
235 serializer.serialize_str(&B64::from(serialized).to_string())
236 }
237}
238
239#[cfg(feature = "wasm")]
240#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
241const TS_CUSTOM_TYPES: &'static str = r#"
242export type SymmetricKeyEnvelope = Tagged<string, "SymmetricKeyEnvelope">;
243"#;
244
245#[cfg(feature = "wasm")]
246impl wasm_bindgen::describe::WasmDescribe for SymmetricKeyEnvelope {
247 fn describe() {
248 <String as wasm_bindgen::describe::WasmDescribe>::describe();
249 }
250}
251
252#[cfg(feature = "wasm")]
253impl FromWasmAbi for SymmetricKeyEnvelope {
254 type Abi = <String as FromWasmAbi>::Abi;
255
256 unsafe fn from_abi(abi: Self::Abi) -> Self {
257 use wasm_bindgen::UnwrapThrowExt;
258 let string = unsafe { String::from_abi(abi) };
259 SymmetricKeyEnvelope::from_str(&string).unwrap_throw()
260 }
261}
262
263#[allow(clippy::enum_variant_names)]
265#[derive(Debug, Copy, Clone, PartialEq)]
266pub enum SymmetricKeyEnvelopeNamespace {
267 SessionKey = 1,
269 #[cfg(test)]
270 ExampleNamespace = -3,
272 #[cfg(test)]
273 ExampleNamespace2 = -4,
275}
276
277impl SymmetricKeyEnvelopeNamespace {
278 pub fn as_i64(&self) -> i64 {
280 *self as i64
281 }
282}
283
284impl TryFrom<i128> for SymmetricKeyEnvelopeNamespace {
285 type Error = SymmetricKeyEnvelopeError;
286
287 fn try_from(value: i128) -> Result<Self, Self::Error> {
288 match value {
289 1 => Ok(SymmetricKeyEnvelopeNamespace::SessionKey),
290 #[cfg(test)]
291 -3 => Ok(SymmetricKeyEnvelopeNamespace::ExampleNamespace),
292 #[cfg(test)]
293 -4 => Ok(SymmetricKeyEnvelopeNamespace::ExampleNamespace2),
294 _ => Err(SymmetricKeyEnvelopeError::InvalidNamespace),
295 }
296 }
297}
298
299impl TryFrom<i64> for SymmetricKeyEnvelopeNamespace {
300 type Error = SymmetricKeyEnvelopeError;
301
302 fn try_from(value: i64) -> Result<Self, Self::Error> {
303 Self::try_from(i128::from(value))
304 }
305}
306
307impl From<SymmetricKeyEnvelopeNamespace> for i128 {
308 fn from(value: SymmetricKeyEnvelopeNamespace) -> Self {
309 value.as_i64().into()
310 }
311}
312
313impl ContentNamespace for SymmetricKeyEnvelopeNamespace {}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use crate::{KeyStore, SymmetricKeyAlgorithm, traits::tests::TestIds};
319
320 const TEST_VECTOR_SEALING_KEY: &str = "pQEEAlAJiRm3TVKQVUpm9gqA4tm6AzoAARF5BIQDBAUGIFggQyO5bN7Uto3hpXUyqluuArn+zppBmhdnahDRJ6p4s84B";
321 const TEST_VECTOR_KEY_TO_SEAL: &str = "pQEEAlDpQoswNPD5xaz7sYLHZXXXAzoAARFvBIQDBAUGIFgg2YO7eUZhb9WSxxsGvdURTunDOBV0W4FRk9E4TV7c00QB";
322 const TEST_VECTOR_ENVELOPE: &str = "g1g+pgE6AAEReQMYZQRQCYkZt01SkFVKZvYKgOLZujoAARVcUOlCizA08PnFrPuxgsdlddc6AAE4gQM6AAE4gCKhBVgYLLX2iYq+Ko+2Hp2fByDfGd5AC0Lw4+zxWFS9n8fBbBKHfiarETj6Q3uRwZYpDS5p29MBEJW7WxOaf5Az51+Qtr8ugnoocUJhb8l6vmkDxFy1blU6iHuoBvpd+3MSFOeCdiuMLbKrf469fmUlDn0=";
323
324 #[test]
325 #[ignore = "Manual test to verify debug format"]
326 fn test_debug() {
327 let key_store = KeyStore::<TestIds>::default();
328 let mut ctx = key_store.context_mut();
329 let key1 = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
330 let key2 = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
331
332 let envelope = SymmetricKeyEnvelope::seal(
333 key1,
334 key2,
335 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
336 &ctx,
337 );
338 println!("{:?}", envelope);
339 }
340
341 #[test]
342 fn test_seal_unseal_symmetric() {
343 let key_store = KeyStore::<TestIds>::default();
344 let mut ctx = key_store.context_mut();
345
346 let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
347 let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
348
349 let envelope = SymmetricKeyEnvelope::seal(
350 key_to_seal,
351 wrapping_key,
352 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
353 &ctx,
354 )
355 .unwrap();
356
357 assert_eq!(
358 envelope.cose_encrypt0.protected.header.alg,
359 Some(coset::Algorithm::PrivateUse(crate::cose::XAES_256_GCM))
360 );
361
362 let unsealed_key = envelope
363 .unseal(
364 wrapping_key,
365 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
366 &mut ctx,
367 )
368 .unwrap();
369
370 let unsealed_key_ref = ctx
371 .get_symmetric_key(unsealed_key)
372 .expect("Key should exist in the key store");
373
374 let original_key_ref = ctx
375 .get_symmetric_key(key_to_seal)
376 .expect("Key should exist in the key store");
377
378 assert_eq!(unsealed_key_ref, original_key_ref);
379 }
380
381 #[test]
382 fn test_contained_key_id_symmetric() {
383 let key_store = KeyStore::<TestIds>::default();
384 let mut ctx = key_store.context_mut();
385
386 let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
387 let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
388
389 let envelope = SymmetricKeyEnvelope::seal(
390 key_to_seal,
391 wrapping_key,
392 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
393 &ctx,
394 )
395 .unwrap();
396
397 let key_to_seal_ref = ctx
398 .get_symmetric_key(key_to_seal)
399 .expect("Key should exist in the key store");
400
401 let contained_key_id = envelope.contained_key_id().unwrap();
402
403 assert_eq!(key_to_seal_ref.key_id(), contained_key_id);
404 }
405
406 #[test]
407 fn test_string_serialization() {
408 let key_store = KeyStore::<TestIds>::default();
409 let mut ctx = key_store.context_mut();
410
411 let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
412 let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
413
414 let envelope = SymmetricKeyEnvelope::seal(
415 key_to_seal,
416 wrapping_key,
417 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
418 &ctx,
419 )
420 .unwrap();
421
422 let serialized: String = envelope.into();
423 let deserialized = SymmetricKeyEnvelope::from_str(&serialized).unwrap();
424
425 let unsealed_key = deserialized
426 .unseal(
427 wrapping_key,
428 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
429 &mut ctx,
430 )
431 .unwrap();
432
433 let unsealed_key_ref = ctx
434 .get_symmetric_key(unsealed_key)
435 .expect("Key should exist in the key store");
436
437 let original_key_ref = ctx
438 .get_symmetric_key(key_to_seal)
439 .expect("Key should exist in the key store");
440
441 assert_eq!(unsealed_key_ref, original_key_ref);
442 }
443
444 #[test]
445 fn test_wrong_key() {
446 let key_store = KeyStore::<TestIds>::default();
447 let mut ctx = key_store.context_mut();
448
449 let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
450 let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
451 let wrong_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
452
453 let envelope = SymmetricKeyEnvelope::seal(
454 key_to_seal,
455 wrapping_key,
456 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
457 &ctx,
458 )
459 .unwrap();
460
461 assert!(matches!(
462 envelope.unseal(
463 wrong_key,
464 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
465 &mut ctx
466 ),
467 Err(SymmetricKeyEnvelopeError::WrongKey)
468 ));
469 }
470
471 #[test]
472 fn test_rejects_non_xaes_wrapping_keys() {
473 let key_store = KeyStore::<TestIds>::default();
474 let mut ctx = key_store.context_mut();
475
476 let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
477 let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
478 let envelope = SymmetricKeyEnvelope::seal(
479 key_to_seal,
480 wrapping_key,
481 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
482 &ctx,
483 )
484 .unwrap();
485
486 let unsupported_wrapping_keys = [
487 ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac),
488 ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256Gcm),
489 ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305),
490 ];
491
492 for unsupported_wrapping_key in unsupported_wrapping_keys {
493 assert!(matches!(
494 SymmetricKeyEnvelope::seal(
495 key_to_seal,
496 unsupported_wrapping_key,
497 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
498 &ctx,
499 ),
500 Err(SymmetricKeyEnvelopeError::WrongKeyType)
501 ));
502 assert!(matches!(
503 envelope.unseal(
504 unsupported_wrapping_key,
505 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
506 &mut ctx,
507 ),
508 Err(SymmetricKeyEnvelopeError::WrongKeyType)
509 ));
510 }
511 }
512
513 #[test]
514 fn test_wrong_namespace() {
515 let key_store = KeyStore::<TestIds>::default();
516 let mut ctx = key_store.context_mut();
517
518 let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
519 let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
520
521 let envelope = SymmetricKeyEnvelope::seal(
522 key_to_seal,
523 wrapping_key,
524 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
525 &ctx,
526 )
527 .unwrap();
528
529 assert!(matches!(
530 envelope.unseal(
531 wrapping_key,
532 SymmetricKeyEnvelopeNamespace::ExampleNamespace2,
533 &mut ctx
534 ),
535 Err(SymmetricKeyEnvelopeError::InvalidNamespace)
536 ));
537 }
538
539 #[test]
540 #[ignore]
541 fn generate_test_vectors() {
542 let key_store = KeyStore::<TestIds>::default();
543 let mut ctx = key_store.context_mut();
544
545 let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
546 let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
547
548 let envelope = SymmetricKeyEnvelope::seal(
549 key_to_seal,
550 wrapping_key,
551 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
552 &ctx,
553 )
554 .unwrap();
555
556 println!(
557 "const TEST_VECTOR_SEALING_KEY: &str = \"{}\";",
558 bitwarden_encoding::B64::from(
559 ctx.get_symmetric_key(wrapping_key)
560 .unwrap()
561 .to_encoded()
562 .to_vec()
563 .as_slice()
564 )
565 );
566 println!(
567 "const TEST_VECTOR_KEY_TO_SEAL: &str = \"{}\";",
568 bitwarden_encoding::B64::from(
569 ctx.get_symmetric_key(key_to_seal)
570 .unwrap()
571 .to_encoded()
572 .to_vec()
573 .as_slice()
574 )
575 );
576 let serialized: String = envelope.into();
577 println!("const TEST_VECTOR_ENVELOPE: &str = \"{}\";", serialized);
578 }
579
580 #[test]
581 fn decrypt_test_vectors() {
582 let key_store = KeyStore::<TestIds>::default();
583 let mut ctx = key_store.context_mut();
584
585 let sealing_key = SymmetricCryptoKey::try_from(TEST_VECTOR_SEALING_KEY.to_string())
586 .expect("Failed to parse sealing key from test vector");
587 let sealed_key_test_vector =
588 SymmetricCryptoKey::try_from(TEST_VECTOR_KEY_TO_SEAL.to_string())
589 .expect("Failed to parse key to seal from test vector");
590
591 let sealing_key_id = ctx.add_local_symmetric_key(sealing_key);
592
593 let envelope = SymmetricKeyEnvelope::from_str(TEST_VECTOR_ENVELOPE).unwrap();
594
595 let unsealed_key_id = envelope
596 .unseal(
597 sealing_key_id,
598 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
599 &mut ctx,
600 )
601 .unwrap();
602 let unsealed_key = ctx.get_symmetric_key(unsealed_key_id).unwrap();
603 assert_eq!(unsealed_key.to_owned(), sealed_key_test_vector.to_owned());
604 }
605}