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