1use std::str::FromStr;
4
5use bitwarden_encoding::{B64, FromStrVisitor};
6use ciborium::Value;
7use coset::{CborSerializable, CoseEncrypt0Builder, HeaderBuilder};
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10#[cfg(feature = "wasm")]
11use wasm_bindgen::convert::FromWasmAbi;
12
13use crate::{
14 BitwardenLegacyKeyBytes, ContentFormat, CoseKeyBytes, EncodedSymmetricKey, KeyIds,
15 KeyStoreContext, SymmetricCryptoKey, XChaCha20Poly1305Key,
16 cose::{CONTAINED_KEY_ID, ContentNamespace, SafeObjectNamespace, XCHACHA20_POLY1305},
17 keys::KeyId,
18 safe::helpers::{
19 debug_fmt, extract_contained_key_id, set_safe_namespaces, validate_safe_namespaces,
20 },
21};
22
23#[derive(Debug, Error)]
25pub enum SymmetricKeyEnvelopeError {
26 #[error("Wrong key")]
28 WrongKey,
29 #[error("Parsing error {0}")]
31 Parsing(String),
32 #[error("Key missing error")]
34 KeyMissing,
35 #[error("Could not write to key store")]
37 KeyStore,
38 #[error("Wrong key type")]
40 WrongKeyType,
41 #[error("Invalid namespace")]
43 InvalidNamespace,
44}
45
46pub struct SymmetricKeyEnvelope {
48 cose_encrypt0: coset::CoseEncrypt0,
49}
50
51impl SymmetricKeyEnvelope {
52 pub fn seal<Ids: KeyIds>(
56 key_to_seal: Ids::Symmetric,
57 sealing_key: Ids::Symmetric,
58 namespace: SymmetricKeyEnvelopeNamespace,
59 ctx: &KeyStoreContext<Ids>,
60 ) -> Result<Self, SymmetricKeyEnvelopeError> {
61 let key_to_seal = ctx
63 .get_symmetric_key(key_to_seal)
64 .map_err(|_| SymmetricKeyEnvelopeError::KeyMissing)?;
65 let wrapping_key = ctx
66 .get_symmetric_key(sealing_key)
67 .map_err(|_| SymmetricKeyEnvelopeError::KeyMissing)?;
68
69 let wrapping_key: &XChaCha20Poly1305Key = match wrapping_key {
71 SymmetricCryptoKey::XChaCha20Poly1305Key(key) => key,
72 _ => {
73 return Err(SymmetricKeyEnvelopeError::Parsing(
74 "Wrapping key must be XChaCha20Poly1305".to_string(),
75 ));
76 }
77 };
78
79 let (content_format, key_bytes) = match key_to_seal.to_encoded_raw() {
80 EncodedSymmetricKey::BitwardenLegacyKey(key_bytes) => {
81 (ContentFormat::BitwardenLegacyKey, key_bytes.to_vec())
82 }
83 EncodedSymmetricKey::CoseKey(key_bytes) => (ContentFormat::CoseKey, key_bytes.to_vec()),
84 };
85
86 let mut header_builder = HeaderBuilder::from(content_format);
87
88 if let Some(key_id) = key_to_seal.key_id() {
90 header_builder =
91 header_builder.value(CONTAINED_KEY_ID, Value::from(Vec::from(&key_id)));
92 }
93
94 let mut protected_header = header_builder.build();
95 set_safe_namespaces(
96 &mut protected_header,
97 SafeObjectNamespace::SymmetricKeyEnvelope,
98 namespace,
99 );
100 protected_header.alg = Some(coset::Algorithm::PrivateUse(XCHACHA20_POLY1305));
101 protected_header.key_id = wrapping_key.key_id.as_slice().into();
102
103 let cose_encrypt0 = crate::cose::encrypt_cose(
104 CoseEncrypt0Builder::new().protected(protected_header),
105 &key_bytes,
106 wrapping_key,
107 );
108
109 Ok(SymmetricKeyEnvelope { cose_encrypt0 })
110 }
111
112 pub fn unseal<Ids: KeyIds>(
114 &self,
115 wrapping_key: Ids::Symmetric,
116 namespace: SymmetricKeyEnvelopeNamespace,
117 ctx: &mut KeyStoreContext<Ids>,
118 ) -> Result<Ids::Symmetric, SymmetricKeyEnvelopeError> {
119 let wrapping_key_ref = ctx
120 .get_symmetric_key(wrapping_key)
121 .map_err(|_| SymmetricKeyEnvelopeError::KeyMissing)?;
122
123 let wrapping_key_inner = match wrapping_key_ref {
124 SymmetricCryptoKey::XChaCha20Poly1305Key(key) => key,
125 _ => {
126 return Err(SymmetricKeyEnvelopeError::Parsing(
127 "Wrapping key must be XChaCha20Poly1305".to_string(),
128 ));
129 }
130 };
131
132 validate_safe_namespaces(
133 &self.cose_encrypt0.protected.header,
134 SafeObjectNamespace::SymmetricKeyEnvelope,
135 namespace,
136 )
137 .map_err(|_| SymmetricKeyEnvelopeError::InvalidNamespace)?;
138
139 let content_format = ContentFormat::try_from(&self.cose_encrypt0.protected.header)
141 .map_err(|_| {
142 SymmetricKeyEnvelopeError::Parsing("Invalid content format".to_string())
143 })?;
144
145 let key_bytes = crate::cose::decrypt_cose(&self.cose_encrypt0, wrapping_key_inner)
147 .map_err(|_| SymmetricKeyEnvelopeError::WrongKey)?;
148
149 let encoded_key = match content_format {
151 ContentFormat::BitwardenLegacyKey => {
152 EncodedSymmetricKey::BitwardenLegacyKey(BitwardenLegacyKeyBytes::from(key_bytes))
153 }
154 ContentFormat::CoseKey => EncodedSymmetricKey::CoseKey(CoseKeyBytes::from(key_bytes)),
155 _ => {
156 return Err(SymmetricKeyEnvelopeError::WrongKeyType);
157 }
158 };
159
160 let key = SymmetricCryptoKey::try_from(encoded_key)
161 .map_err(|_| SymmetricKeyEnvelopeError::WrongKeyType)?;
162
163 Ok(ctx.add_local_symmetric_key(key))
164 }
165
166 pub fn contained_key_id(&self) -> Result<Option<KeyId>, SymmetricKeyEnvelopeError> {
168 extract_contained_key_id(&self.cose_encrypt0.protected.header)
169 .map_err(|_| SymmetricKeyEnvelopeError::Parsing("Invalid contained key id".to_string()))
170 }
171}
172
173impl From<&SymmetricKeyEnvelope> for Vec<u8> {
174 fn from(val: &SymmetricKeyEnvelope) -> Self {
175 val.cose_encrypt0
176 .clone()
177 .to_vec()
178 .expect("Serialization to cose should not fail")
179 }
180}
181
182impl TryFrom<&Vec<u8>> for SymmetricKeyEnvelope {
183 type Error = coset::CoseError;
184
185 fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
186 let cose_encrypt0 = coset::CoseEncrypt0::from_slice(value)?;
187 Ok(SymmetricKeyEnvelope { cose_encrypt0 })
188 }
189}
190
191impl std::fmt::Debug for SymmetricKeyEnvelope {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 let mut s = f.debug_struct("SymmetricKeyEnvelope");
194
195 if !self.cose_encrypt0.protected.header.key_id.is_empty() {
196 s.field(
197 "sealing_key_id",
198 &self.cose_encrypt0.protected.header.key_id,
199 );
200 }
201
202 debug_fmt::<SymmetricKeyEnvelopeNamespace>(&mut s, &self.cose_encrypt0.protected.header);
203
204 if let Ok(Some(key_id)) = self.contained_key_id() {
205 s.field("contained_key_id", &key_id);
206 }
207
208 s.finish()
209 }
210}
211
212impl FromStr for SymmetricKeyEnvelope {
213 type Err = SymmetricKeyEnvelopeError;
214
215 fn from_str(s: &str) -> Result<Self, Self::Err> {
216 let data = B64::try_from(s).map_err(|_| {
217 SymmetricKeyEnvelopeError::Parsing(
218 "Invalid WrappedSymmetricKey Base64 encoding".to_string(),
219 )
220 })?;
221 Self::try_from(&data.into_bytes()).map_err(|_| {
222 SymmetricKeyEnvelopeError::Parsing("Failed to parse SymmetricKeyEnvelope".to_string())
223 })
224 }
225}
226
227impl From<SymmetricKeyEnvelope> for String {
228 fn from(val: SymmetricKeyEnvelope) -> Self {
229 let serialized: Vec<u8> = (&val).into();
230 B64::from(serialized).to_string()
231 }
232}
233
234impl<'de> Deserialize<'de> for SymmetricKeyEnvelope {
235 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
236 where
237 D: serde::Deserializer<'de>,
238 {
239 deserializer.deserialize_str(FromStrVisitor::new())
240 }
241}
242
243impl Serialize for SymmetricKeyEnvelope {
244 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
245 where
246 S: serde::Serializer,
247 {
248 let serialized: Vec<u8> = self.into();
249 serializer.serialize_str(&B64::from(serialized).to_string())
250 }
251}
252
253#[cfg(feature = "wasm")]
254#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
255const TS_CUSTOM_TYPES: &'static str = r#"
256export type SymmetricKeyEnvelope = Tagged<string, "SymmetricKeyEnvelope">;
257"#;
258
259#[cfg(feature = "wasm")]
260impl wasm_bindgen::describe::WasmDescribe for SymmetricKeyEnvelope {
261 fn describe() {
262 <String as wasm_bindgen::describe::WasmDescribe>::describe();
263 }
264}
265
266#[cfg(feature = "wasm")]
267impl FromWasmAbi for SymmetricKeyEnvelope {
268 type Abi = <String as FromWasmAbi>::Abi;
269
270 unsafe fn from_abi(abi: Self::Abi) -> Self {
271 use wasm_bindgen::UnwrapThrowExt;
272 let string = unsafe { String::from_abi(abi) };
273 SymmetricKeyEnvelope::from_str(&string).unwrap_throw()
274 }
275}
276
277#[allow(clippy::enum_variant_names)]
279#[derive(Debug, Copy, Clone, PartialEq)]
280pub enum SymmetricKeyEnvelopeNamespace {
281 SessionKey = 1,
283 #[cfg(test)]
284 ExampleNamespace = -3,
286 #[cfg(test)]
287 ExampleNamespace2 = -4,
289}
290
291impl SymmetricKeyEnvelopeNamespace {
292 pub fn as_i64(&self) -> i64 {
294 *self as i64
295 }
296}
297
298impl TryFrom<i128> for SymmetricKeyEnvelopeNamespace {
299 type Error = SymmetricKeyEnvelopeError;
300
301 fn try_from(value: i128) -> Result<Self, Self::Error> {
302 match value {
303 1 => Ok(SymmetricKeyEnvelopeNamespace::SessionKey),
304 #[cfg(test)]
305 -3 => Ok(SymmetricKeyEnvelopeNamespace::ExampleNamespace),
306 #[cfg(test)]
307 -4 => Ok(SymmetricKeyEnvelopeNamespace::ExampleNamespace2),
308 _ => Err(SymmetricKeyEnvelopeError::InvalidNamespace),
309 }
310 }
311}
312
313impl TryFrom<i64> for SymmetricKeyEnvelopeNamespace {
314 type Error = SymmetricKeyEnvelopeError;
315
316 fn try_from(value: i64) -> Result<Self, Self::Error> {
317 Self::try_from(i128::from(value))
318 }
319}
320
321impl From<SymmetricKeyEnvelopeNamespace> for i128 {
322 fn from(value: SymmetricKeyEnvelopeNamespace) -> Self {
323 value.as_i64().into()
324 }
325}
326
327impl ContentNamespace for SymmetricKeyEnvelopeNamespace {}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use crate::{KeyStore, SymmetricKeyAlgorithm, traits::tests::TestIds};
333
334 const TEST_VECTOR_SEALING_KEY: &str = "pQEEAlBLD8tcKNRLZaXNSr8OcwkgAzoAARFvBIQDBAUGIFgggG++dwvSRVPaPrIis1+XXXCizFYcakDZxSJP2HlJj0YB";
335 const TEST_VECTOR_KEY_TO_SEAL: &str = "pQEEAlCEjXxxMulOVJtq1CSNv1aqAzoAARFvBIQDBAUGIFggwdF1yfFVwesj1CMQlVMhm+tvjwA1pxvTnQVUmfBMlJMB";
336 const TEST_VECTOR_ENVELOPE: &str = "g1gspQE6AAERbwMYZToAATiBAzoAATiAIjoAARVcUISNfHEy6U5Um2rUJI2/VqqhBVgYnGokg0NPDLDd18K13zYsAM1SN+NkcfSJWFSEgtR3nhrJzUHRq35myF0tZh18iQx+0vJQ2BHj4lWwHLV3awLcyxdD8UBNQYgKu6nDHs1KDiFN+D48iI60aelHmLgJpNVsCBovnTxZVLbo67AIgw4=";
337
338 #[test]
339 #[ignore = "Manual test to verify debug format"]
340 fn test_debug() {
341 let key_store = KeyStore::<TestIds>::default();
342 let mut ctx = key_store.context_mut();
343 let key1 = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
344 let key2 = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
345
346 let envelope = SymmetricKeyEnvelope::seal(
347 key1,
348 key2,
349 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
350 &ctx,
351 );
352 println!("{:?}", envelope);
353 }
354
355 #[test]
356 fn test_seal_unseal_symmetric() {
357 let key_store = KeyStore::<TestIds>::default();
358 let mut ctx = key_store.context_mut();
359
360 let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
361 let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
362
363 let envelope = SymmetricKeyEnvelope::seal(
364 key_to_seal,
365 wrapping_key,
366 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
367 &ctx,
368 )
369 .unwrap();
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::XChaCha20Poly1305);
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::XChaCha20Poly1305);
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::XChaCha20Poly1305);
460 let wrong_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
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_wrong_namespace() {
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::XChaCha20Poly1305);
487
488 let envelope = SymmetricKeyEnvelope::seal(
489 key_to_seal,
490 wrapping_key,
491 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
492 &ctx,
493 )
494 .unwrap();
495
496 assert!(matches!(
497 envelope.unseal(
498 wrapping_key,
499 SymmetricKeyEnvelopeNamespace::ExampleNamespace2,
500 &mut ctx
501 ),
502 Err(SymmetricKeyEnvelopeError::InvalidNamespace)
503 ));
504 }
505
506 #[test]
507 #[ignore]
508 fn generate_test_vectors() {
509 let key_store = KeyStore::<TestIds>::default();
510 let mut ctx = key_store.context_mut();
511
512 let key_to_seal = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
513 let wrapping_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
514
515 let envelope = SymmetricKeyEnvelope::seal(
516 key_to_seal,
517 wrapping_key,
518 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
519 &ctx,
520 )
521 .unwrap();
522
523 println!(
524 "const TEST_VECTOR_SEALING_KEY: &str = \"{}\";",
525 bitwarden_encoding::B64::from(
526 ctx.get_symmetric_key(wrapping_key)
527 .unwrap()
528 .to_encoded()
529 .to_vec()
530 .as_slice()
531 )
532 );
533 println!(
534 "const TEST_VECTOR_KEY_TO_SEAL: &str = \"{}\";",
535 bitwarden_encoding::B64::from(
536 ctx.get_symmetric_key(key_to_seal)
537 .unwrap()
538 .to_encoded()
539 .to_vec()
540 .as_slice()
541 )
542 );
543 let serialized: String = envelope.into();
544 println!("const TEST_VECTOR_ENVELOPE: &str = \"{}\";", serialized);
545 }
546
547 #[test]
548 fn decrypt_test_vectors() {
549 let key_store = KeyStore::<TestIds>::default();
550 let mut ctx = key_store.context_mut();
551
552 let sealing_key = SymmetricCryptoKey::try_from(TEST_VECTOR_SEALING_KEY.to_string())
553 .expect("Failed to parse sealing key from test vector");
554 let sealed_key_test_vector =
555 SymmetricCryptoKey::try_from(TEST_VECTOR_KEY_TO_SEAL.to_string())
556 .expect("Failed to parse key to seal from test vector");
557
558 let sealing_key_id = ctx.add_local_symmetric_key(sealing_key);
559
560 let envelope = SymmetricKeyEnvelope::from_str(TEST_VECTOR_ENVELOPE).unwrap();
561
562 let unsealed_key_id = envelope
563 .unseal(
564 sealing_key_id,
565 SymmetricKeyEnvelopeNamespace::ExampleNamespace,
566 &mut ctx,
567 )
568 .unwrap();
569 let unsealed_key = ctx.get_symmetric_key(unsealed_key_id).unwrap();
570 assert_eq!(unsealed_key.to_owned(), sealed_key_test_vector.to_owned());
571 }
572}