1use std::{pin::Pin, str::FromStr};
2
3use bitwarden_encoding::{B64, FromStrVisitor};
4use ciborium::{Value, value::Integer};
5use coset::{
6 CborSerializable, RegisteredLabelWithPrivate,
7 iana::{EnumI64, KeyOperation, KeyParameter, KeyType, SymmetricKeyParameter},
8};
9use hybrid_array::Array;
10use rand::RngExt;
11#[cfg(test)]
12use rand::SeedableRng;
13#[cfg(test)]
14use rand_chacha::ChaChaRng;
15use serde::{Deserialize, Serialize};
16#[cfg(test)]
17use sha2::Digest;
18use subtle::{Choice, ConstantTimeEq};
19use typenum::U32;
20#[cfg(feature = "wasm")]
21use wasm_bindgen::convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi};
22use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
23
24use super::{key_encryptable::CryptoKey, key_id::KeyId};
25use crate::{
26 BitwardenLegacyKeyBytes, ContentFormat, CoseKeyBytes, CoseKeyThumbprint, CryptoError, cose,
27 cose::{
28 CoseKeyThumbprintExt, symmetric::CoseContentEncryptionAlgorithm,
29 thumbprint_from_required_params,
30 },
31 error::EncodingError,
32};
33
34#[cfg(feature = "wasm")]
35#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
36const TS_CUSTOM_TYPES: &'static str = r#"
37export type SymmetricKey = Tagged<string, "SymmetricKey">;
38"#;
39
40#[cfg(feature = "wasm")]
41impl wasm_bindgen::describe::WasmDescribe for SymmetricCryptoKey {
42 fn describe() {
43 <String as wasm_bindgen::describe::WasmDescribe>::describe();
44 }
45}
46
47#[cfg(feature = "wasm")]
48impl FromWasmAbi for SymmetricCryptoKey {
49 type Abi = <String as FromWasmAbi>::Abi;
50
51 unsafe fn from_abi(abi: Self::Abi) -> Self {
52 use wasm_bindgen::UnwrapThrowExt;
53 let string = unsafe { String::from_abi(abi) };
54 let b64 = B64::try_from(string).unwrap_throw();
55 SymmetricCryptoKey::try_from(b64).unwrap_throw()
56 }
57}
58
59#[cfg(feature = "wasm")]
60impl OptionFromWasmAbi for SymmetricCryptoKey {
61 fn is_none(abi: &Self::Abi) -> bool {
62 <String as OptionFromWasmAbi>::is_none(abi)
63 }
64}
65
66#[cfg(feature = "wasm")]
67impl IntoWasmAbi for SymmetricCryptoKey {
68 type Abi = <String as IntoWasmAbi>::Abi;
69
70 fn into_abi(self) -> Self::Abi {
71 let string: String = self.to_base64().to_string();
72 string.into_abi()
73 }
74}
75
76#[cfg(feature = "wasm")]
77impl TryFrom<wasm_bindgen::JsValue> for SymmetricCryptoKey {
78 type Error = CryptoError;
79
80 fn try_from(value: wasm_bindgen::JsValue) -> Result<Self, Self::Error> {
81 let string = value.as_string().ok_or(CryptoError::InvalidKey)?;
82 Self::try_from(string)
83 }
84}
85
86#[derive(Debug, PartialEq)]
88pub enum SymmetricKeyAlgorithm {
89 Aes256CbcHmac,
91 XChaCha20Poly1305,
93 Aes256Gcm,
100 XAes256Gcm,
103}
104
105#[derive(ZeroizeOnDrop, Clone)]
109pub struct Aes256CbcKey {
110 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
112}
113
114impl ConstantTimeEq for Aes256CbcKey {
115 fn ct_eq(&self, other: &Self) -> Choice {
116 self.enc_key.ct_eq(&other.enc_key)
117 }
118}
119
120impl PartialEq for Aes256CbcKey {
121 fn eq(&self, other: &Self) -> bool {
122 self.ct_eq(other).into()
123 }
124}
125
126#[derive(ZeroizeOnDrop, Clone)]
129pub struct Aes256CbcHmacKey {
130 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
132 pub(crate) mac_key: Pin<Box<Array<u8, U32>>>,
134}
135
136impl ConstantTimeEq for Aes256CbcHmacKey {
137 fn ct_eq(&self, other: &Self) -> Choice {
138 self.enc_key.ct_eq(&other.enc_key) & self.mac_key.ct_eq(&other.mac_key)
139 }
140}
141
142impl Aes256CbcHmacKey {
143 pub(crate) fn to_composite_key(&self) -> Zeroizing<[u8; 64]> {
146 let mut key = Zeroizing::new([0u8; 64]);
147 key[..32].copy_from_slice(&self.enc_key);
148 key[32..].copy_from_slice(&self.mac_key);
149 key
150 }
151}
152
153impl PartialEq for Aes256CbcHmacKey {
154 fn eq(&self, other: &Self) -> bool {
155 self.ct_eq(other).into()
156 }
157}
158
159#[derive(Zeroize, Clone)]
164pub struct XChaCha20Poly1305Key {
165 pub(crate) key_id: KeyId,
166 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
167 #[zeroize(skip)]
172 pub(crate) supported_operations: Vec<KeyOperation>,
173}
174
175impl XChaCha20Poly1305Key {
176 pub fn make() -> Self {
178 let mut rng = bitwarden_random::rng();
179 let mut enc_key = Box::pin(Array::<u8, U32>::default());
180 rng.fill(enc_key.as_mut_slice());
181 let key_id = KeyId::make();
182
183 Self {
184 enc_key,
185 key_id,
186 supported_operations: vec![
187 KeyOperation::Decrypt,
188 KeyOperation::Encrypt,
189 KeyOperation::WrapKey,
190 KeyOperation::UnwrapKey,
191 ],
192 }
193 }
194}
195
196impl ConstantTimeEq for XChaCha20Poly1305Key {
197 fn ct_eq(&self, other: &Self) -> Choice {
198 self.enc_key.ct_eq(&other.enc_key) & self.key_id.ct_eq(&other.key_id)
199 }
200}
201
202impl PartialEq for XChaCha20Poly1305Key {
203 fn eq(&self, other: &Self) -> bool {
204 self.ct_eq(other).into()
205 }
206}
207
208#[derive(Zeroize, Clone)]
210pub struct Aes256GcmKey {
211 pub(crate) key_id: KeyId,
212 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
213 #[zeroize(skip)]
216 pub(crate) supported_operations: Vec<KeyOperation>,
217}
218
219impl Aes256GcmKey {
220 pub fn make() -> Self {
222 let mut rng = bitwarden_random::rng();
223 let mut enc_key = Box::pin(Array::<u8, U32>::default());
224 rng.fill(enc_key.as_mut_slice());
225 let key_id = KeyId::make();
226
227 Self {
228 enc_key,
229 key_id,
230 supported_operations: vec![
231 KeyOperation::Decrypt,
232 KeyOperation::Encrypt,
233 KeyOperation::WrapKey,
234 KeyOperation::UnwrapKey,
235 ],
236 }
237 }
238
239 pub(crate) fn disable_key_operation(&mut self, op: KeyOperation) -> &mut Self {
240 self.supported_operations.retain(|k| *k != op);
241 self
242 }
243}
244
245impl ConstantTimeEq for Aes256GcmKey {
246 fn ct_eq(&self, other: &Self) -> Choice {
247 self.enc_key.ct_eq(&other.enc_key) & self.key_id.ct_eq(&other.key_id)
248 }
249}
250
251impl PartialEq for Aes256GcmKey {
252 fn eq(&self, other: &Self) -> bool {
253 self.ct_eq(other).into()
254 }
255}
256
257#[derive(Zeroize, Clone)]
259pub struct XAes256GcmKey {
260 pub(crate) key_id: KeyId,
261 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
262 #[zeroize(skip)]
263 pub(crate) supported_operations: Vec<KeyOperation>,
264}
265
266impl XAes256GcmKey {
267 pub fn make() -> Self {
269 let mut rng = bitwarden_random::rng();
270 let mut enc_key = Box::pin(Array::<u8, U32>::default());
271 rng.fill(enc_key.as_mut_slice());
272
273 Self {
274 key_id: KeyId::make(),
275 enc_key,
276 supported_operations: vec![
277 KeyOperation::Decrypt,
278 KeyOperation::Encrypt,
279 KeyOperation::WrapKey,
280 KeyOperation::UnwrapKey,
281 ],
282 }
283 }
284}
285
286impl ConstantTimeEq for XAes256GcmKey {
287 fn ct_eq(&self, other: &Self) -> Choice {
288 self.enc_key.ct_eq(&other.enc_key) & self.key_id.ct_eq(&other.key_id)
289 }
290}
291
292impl PartialEq for XAes256GcmKey {
293 fn eq(&self, other: &Self) -> bool {
294 self.ct_eq(other).into()
295 }
296}
297
298pub(crate) enum CoseKeyView<'a> {
300 Aes256Gcm(&'a Aes256GcmKey),
301 XChaCha20Poly1305(&'a XChaCha20Poly1305Key),
302 XAes256Gcm(&'a XAes256GcmKey),
303}
304
305impl CoseKeyView<'_> {
306 pub(crate) fn key_id(&self) -> &KeyId {
307 match self {
308 CoseKeyView::Aes256Gcm(k) => &k.key_id,
309 CoseKeyView::XChaCha20Poly1305(k) => &k.key_id,
310 CoseKeyView::XAes256Gcm(k) => &k.key_id,
311 }
312 }
313
314 pub(crate) fn key_bytes(&self) -> &[u8] {
315 match self {
316 CoseKeyView::Aes256Gcm(k) => k.enc_key.as_slice(),
317 CoseKeyView::XChaCha20Poly1305(k) => k.enc_key.as_slice(),
318 CoseKeyView::XAes256Gcm(k) => k.enc_key.as_slice(),
319 }
320 }
321
322 pub(crate) fn algorithm(&self) -> CoseContentEncryptionAlgorithm {
323 match self {
324 CoseKeyView::Aes256Gcm(_) => CoseContentEncryptionAlgorithm::Aes256Gcm,
325 CoseKeyView::XChaCha20Poly1305(_) => CoseContentEncryptionAlgorithm::XChaCha20Poly1305,
326 CoseKeyView::XAes256Gcm(_) => CoseContentEncryptionAlgorithm::XAes256Gcm,
327 }
328 }
329}
330
331#[derive(ZeroizeOnDrop, Clone)]
333pub enum SymmetricCryptoKey {
334 #[allow(missing_docs)]
335 Aes256CbcKey(Aes256CbcKey),
336 #[allow(missing_docs)]
337 Aes256CbcHmacKey(Aes256CbcHmacKey),
338 XChaCha20Poly1305Key(XChaCha20Poly1305Key),
341 Aes256GcmKey(Aes256GcmKey),
344 XAes256GcmKey(XAes256GcmKey),
347}
348
349impl SymmetricCryptoKey {
350 const AES256_CBC_KEY_LEN: usize = 32;
352 const AES256_CBC_HMAC_KEY_LEN: usize = 64;
354
355 pub(crate) fn make_aes256_cbc_hmac_key_internal(mut rng: impl rand::CryptoRng) -> Self {
361 let mut enc_key = Box::pin(Array::<u8, U32>::default());
362 let mut mac_key = Box::pin(Array::<u8, U32>::default());
363
364 rng.fill(enc_key.as_mut_slice());
365 rng.fill(mac_key.as_mut_slice());
366
367 Self::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key })
368 }
369
370 pub fn make(algorithm: SymmetricKeyAlgorithm) -> Self {
372 match algorithm {
373 SymmetricKeyAlgorithm::Aes256CbcHmac => Self::make_aes256_cbc_hmac_key(),
374 SymmetricKeyAlgorithm::XChaCha20Poly1305 => Self::make_xchacha20_poly1305_key(),
375 SymmetricKeyAlgorithm::Aes256Gcm => Self::Aes256GcmKey(Aes256GcmKey::make()),
376 SymmetricKeyAlgorithm::XAes256Gcm => Self::XAes256GcmKey(XAes256GcmKey::make()),
377 }
378 }
379
380 pub(crate) fn make_aes256_cbc_hmac_key() -> Self {
382 let rng = bitwarden_random::rng();
383 Self::make_aes256_cbc_hmac_key_internal(rng)
384 }
385
386 pub(crate) fn make_xchacha20_poly1305_key() -> Self {
388 let mut rng = bitwarden_random::rng();
389 let mut enc_key = Box::pin(Array::<u8, U32>::default());
390 rng.fill(enc_key.as_mut_slice());
391 Self::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
392 enc_key,
393 key_id: KeyId::make(),
394 supported_operations: vec![
395 KeyOperation::Decrypt,
396 KeyOperation::Encrypt,
397 KeyOperation::WrapKey,
398 KeyOperation::UnwrapKey,
399 ],
400 })
401 }
402
403 pub fn to_encoded(&self) -> BitwardenLegacyKeyBytes {
412 let encoded_key = self.to_encoded_raw();
413 match encoded_key {
414 EncodedSymmetricKey::BitwardenLegacyKey(_) => {
415 let encoded_key: Vec<u8> = encoded_key.into();
416 BitwardenLegacyKeyBytes::from(encoded_key)
417 }
418 EncodedSymmetricKey::CoseKey(_) => {
419 let mut encoded_key: Vec<u8> = encoded_key.into();
420 pad_key(&mut encoded_key, (Self::AES256_CBC_HMAC_KEY_LEN + 1) as u8); BitwardenLegacyKeyBytes::from(encoded_key)
422 }
423 }
424 }
425
426 #[cfg(test)]
429 pub fn generate_seeded_for_unit_tests(seed: &str) -> Self {
430 let mut seeded_rng = ChaChaRng::from_seed(sha2::Sha256::digest(seed.as_bytes()).into());
432 let mut enc_key = Box::pin(Array::<u8, U32>::default());
433 let mut mac_key = Box::pin(Array::<u8, U32>::default());
434
435 seeded_rng.fill(enc_key.as_mut_slice());
436 seeded_rng.fill(mac_key.as_mut_slice());
437
438 SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key })
439 }
440
441 pub(crate) fn to_encoded_raw(&self) -> EncodedSymmetricKey {
454 match self {
455 Self::Aes256CbcKey(key) => {
456 EncodedSymmetricKey::BitwardenLegacyKey(key.enc_key.to_vec().into())
457 }
458 Self::Aes256CbcHmacKey(key) => {
459 let mut buf = Vec::with_capacity(64);
460 buf.extend_from_slice(&key.enc_key);
461 buf.extend_from_slice(&key.mac_key);
462 EncodedSymmetricKey::BitwardenLegacyKey(buf.into())
463 }
464 Self::XChaCha20Poly1305Key(key) => {
465 let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec());
466 let mut cose_key = builder.key_id((&key.key_id).into());
467 for op in &key.supported_operations {
468 cose_key = cose_key.add_key_op(*op);
469 }
470 let mut cose_key = cose_key.build();
471 cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse(
472 cose::XCHACHA20_POLY1305,
473 ));
474 EncodedSymmetricKey::CoseKey(
475 cose_key
476 .to_vec()
477 .expect("cose key serialization should not fail")
478 .into(),
479 )
480 }
481 Self::XAes256GcmKey(key) => {
482 let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec());
483 let mut cose_key = builder.key_id((&key.key_id).into());
484 for op in &key.supported_operations {
485 cose_key = cose_key.add_key_op(*op);
486 }
487 let mut cose_key = cose_key.build();
488 cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse(cose::XAES_256_GCM));
489 EncodedSymmetricKey::CoseKey(
490 cose_key
491 .to_vec()
492 .expect("cose key serialization should not fail")
493 .into(),
494 )
495 }
496 Self::Aes256GcmKey(key) => {
497 let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec());
498 let mut cose_key = builder.key_id((&key.key_id).into());
499 for op in &key.supported_operations {
500 cose_key = cose_key.add_key_op(*op);
501 }
502 let mut cose_key = cose_key.build();
503 cose_key.alg = Some(RegisteredLabelWithPrivate::Assigned(
504 coset::iana::Algorithm::A256GCM,
505 ));
506 EncodedSymmetricKey::CoseKey(
507 cose_key
508 .to_vec()
509 .expect("cose key serialization should not fail")
510 .into(),
511 )
512 }
513 }
514 }
515
516 pub(crate) fn try_from_cose(serialized_key: &[u8]) -> Result<Self, CryptoError> {
517 let cose_key =
518 coset::CoseKey::from_slice(serialized_key).map_err(|_| CryptoError::InvalidKey)?;
519 let key = SymmetricCryptoKey::try_from(&cose_key)?;
520 Ok(key)
521 }
522
523 #[allow(missing_docs)]
524 pub fn to_base64(&self) -> B64 {
525 B64::from(self.to_encoded().as_ref())
526 }
527
528 pub fn key_id(&self) -> Option<KeyId> {
530 match self {
531 Self::Aes256CbcKey(_) => None,
532 Self::Aes256CbcHmacKey(_) => None,
533 Self::XChaCha20Poly1305Key(key) => Some(key.key_id.clone()),
534 Self::Aes256GcmKey(key) => Some(key.key_id.clone()),
535 Self::XAes256GcmKey(key) => Some(key.key_id.clone()),
536 }
537 }
538
539 pub(crate) fn as_cose_key_view(&self) -> Option<CoseKeyView<'_>> {
542 match self {
543 Self::Aes256GcmKey(k) => Some(CoseKeyView::Aes256Gcm(k)),
544 Self::XChaCha20Poly1305Key(k) => Some(CoseKeyView::XChaCha20Poly1305(k)),
545 Self::XAes256GcmKey(k) => Some(CoseKeyView::XAes256Gcm(k)),
546 Self::Aes256CbcKey(_) | Self::Aes256CbcHmacKey(_) => None,
547 }
548 }
549}
550
551impl CoseKeyThumbprintExt for SymmetricCryptoKey {
552 fn thumbprint(&self) -> Result<CoseKeyThumbprint, CryptoError> {
557 let view = self
558 .as_cose_key_view()
559 .ok_or(EncodingError::UnsupportedValue(
560 "legacy AES-CBC keys are not COSE keys and have no thumbprint",
561 ))?;
562 let params = vec![
563 (
564 KeyParameter::Kty.to_i64(),
565 Value::Integer(Integer::from(KeyType::Symmetric.to_i64())),
566 ),
567 (
568 SymmetricKeyParameter::K.to_i64(),
569 Value::Bytes(view.key_bytes().to_vec()),
570 ),
571 ];
572 Ok(thumbprint_from_required_params(params))
573 }
574}
575
576impl ConstantTimeEq for SymmetricCryptoKey {
577 fn ct_eq(&self, other: &SymmetricCryptoKey) -> Choice {
581 use SymmetricCryptoKey::*;
582 match (self, other) {
583 (Aes256CbcKey(a), Aes256CbcKey(b)) => a.ct_eq(b),
584 (Aes256CbcKey(_), _) => Choice::from(0),
585
586 (Aes256CbcHmacKey(a), Aes256CbcHmacKey(b)) => a.ct_eq(b),
587 (Aes256CbcHmacKey(_), _) => Choice::from(0),
588
589 (XChaCha20Poly1305Key(a), XChaCha20Poly1305Key(b)) => a.ct_eq(b),
590 (XChaCha20Poly1305Key(_), _) => Choice::from(0),
591
592 (Aes256GcmKey(a), Aes256GcmKey(b)) => a.ct_eq(b),
593 (Aes256GcmKey(_), _) => Choice::from(0),
594
595 (XAes256GcmKey(a), XAes256GcmKey(b)) => a.ct_eq(b),
596 (XAes256GcmKey(_), _) => Choice::from(0),
597 }
598 }
599}
600
601impl PartialEq for SymmetricCryptoKey {
602 fn eq(&self, other: &Self) -> bool {
603 self.ct_eq(other).into()
604 }
605}
606
607impl TryFrom<String> for SymmetricCryptoKey {
608 type Error = CryptoError;
609
610 fn try_from(value: String) -> Result<Self, Self::Error> {
611 let bytes = B64::try_from(value).map_err(|_| CryptoError::InvalidKey)?;
612 Self::try_from(bytes)
613 }
614}
615
616impl TryFrom<B64> for SymmetricCryptoKey {
617 type Error = CryptoError;
618
619 fn try_from(value: B64) -> Result<Self, Self::Error> {
620 Self::try_from(&BitwardenLegacyKeyBytes::from(&value))
621 }
622}
623
624impl TryFrom<&BitwardenLegacyKeyBytes> for SymmetricCryptoKey {
625 type Error = CryptoError;
626
627 fn try_from(value: &BitwardenLegacyKeyBytes) -> Result<Self, Self::Error> {
628 let slice = value.as_ref();
629
630 if slice.len() == Self::AES256_CBC_HMAC_KEY_LEN || slice.len() == Self::AES256_CBC_KEY_LEN {
636 Self::try_from(EncodedSymmetricKey::BitwardenLegacyKey(value.clone()))
637 } else if slice.len() > Self::AES256_CBC_HMAC_KEY_LEN {
638 let unpadded_value = unpad_key(slice)?;
639 Ok(Self::try_from_cose(unpadded_value)?)
640 } else {
641 Err(CryptoError::InvalidKeyLen)
642 }
643 }
644}
645
646impl TryFrom<EncodedSymmetricKey> for SymmetricCryptoKey {
647 type Error = CryptoError;
648
649 fn try_from(value: EncodedSymmetricKey) -> Result<Self, Self::Error> {
650 match value {
651 EncodedSymmetricKey::BitwardenLegacyKey(key)
652 if key.as_ref().len() == Self::AES256_CBC_KEY_LEN =>
653 {
654 let mut enc_key = Box::pin(Array::<u8, U32>::default());
655 enc_key.copy_from_slice(&key.as_ref()[..Self::AES256_CBC_KEY_LEN]);
656 Ok(Self::Aes256CbcKey(Aes256CbcKey { enc_key }))
657 }
658 EncodedSymmetricKey::BitwardenLegacyKey(key)
659 if key.as_ref().len() == Self::AES256_CBC_HMAC_KEY_LEN =>
660 {
661 let mut enc_key = Box::pin(Array::<u8, U32>::default());
662 enc_key.copy_from_slice(&key.as_ref()[..32]);
663
664 let mut mac_key = Box::pin(Array::<u8, U32>::default());
665 mac_key.copy_from_slice(&key.as_ref()[32..]);
666
667 Ok(Self::Aes256CbcHmacKey(Aes256CbcHmacKey {
668 enc_key,
669 mac_key,
670 }))
671 }
672 EncodedSymmetricKey::CoseKey(key) => Self::try_from_cose(key.as_ref()),
673 _ => Err(CryptoError::InvalidKey),
674 }
675 }
676}
677
678impl CryptoKey for SymmetricCryptoKey {}
679
680impl std::fmt::Debug for SymmetricCryptoKey {
682 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
683 match self {
684 SymmetricCryptoKey::Aes256CbcKey(key) => key.fmt(f),
685 SymmetricCryptoKey::Aes256CbcHmacKey(key) => key.fmt(f),
686 SymmetricCryptoKey::XChaCha20Poly1305Key(key) => key.fmt(f),
687 SymmetricCryptoKey::Aes256GcmKey(key) => key.fmt(f),
688 SymmetricCryptoKey::XAes256GcmKey(key) => key.fmt(f),
689 }
690 }
691}
692
693impl std::fmt::Debug for Aes256CbcKey {
694 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
695 let mut debug_struct = f.debug_struct("SymmetricKey::Aes256Cbc");
696 #[cfg(feature = "dangerous-crypto-debug")]
697 debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
698 debug_struct.finish()
699 }
700}
701
702impl std::fmt::Debug for Aes256CbcHmacKey {
703 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
704 let mut debug_struct = f.debug_struct("SymmetricKey::Aes256CbcHmac");
705 #[cfg(feature = "dangerous-crypto-debug")]
706 debug_struct
707 .field("enc_key", &hex::encode(self.enc_key.as_slice()))
708 .field("mac_key", &hex::encode(self.mac_key.as_slice()));
709 debug_struct.finish()
710 }
711}
712
713impl std::fmt::Debug for XChaCha20Poly1305Key {
714 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
715 let mut debug_struct = f.debug_struct("SymmetricKey::XChaCha20Poly1305");
716 debug_struct.field("key_id", &self.key_id);
717 debug_struct.field(
718 "supported_operations",
719 &self
720 .supported_operations
721 .iter()
722 .map(|key_operation: &KeyOperation| cose::debug_key_operation(*key_operation))
723 .collect::<Vec<_>>(),
724 );
725 #[cfg(feature = "dangerous-crypto-debug")]
726 debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
727 debug_struct.finish()
728 }
729}
730
731impl std::fmt::Debug for XAes256GcmKey {
732 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
733 let mut debug_struct = f.debug_struct("SymmetricKey::XAes256Gcm");
734 debug_struct.field("key_id", &self.key_id);
735 debug_struct.field(
736 "supported_operations",
737 &self
738 .supported_operations
739 .iter()
740 .map(|key_operation: &KeyOperation| cose::debug_key_operation(*key_operation))
741 .collect::<Vec<_>>(),
742 );
743 #[cfg(feature = "dangerous-crypto-debug")]
744 debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
745 debug_struct.finish()
746 }
747}
748
749impl std::fmt::Debug for Aes256GcmKey {
750 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
751 let mut debug_struct = f.debug_struct("SymmetricKey::Aes256Gcm");
752 debug_struct.field("key_id", &self.key_id);
753 debug_struct.field(
754 "supported_operations",
755 &self
756 .supported_operations
757 .iter()
758 .map(|key_operation: &KeyOperation| cose::debug_key_operation(*key_operation))
759 .collect::<Vec<_>>(),
760 );
761 #[cfg(feature = "dangerous-crypto-debug")]
762 debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
763 debug_struct.finish()
764 }
765}
766
767fn pad_key(key_bytes: &mut Vec<u8>, min_length: u8) {
777 crate::keys::utils::pad_bytes(key_bytes, min_length as usize)
778 .expect("Padding cannot fail since the min_length is < 255")
779}
780
781fn unpad_key(key_bytes: &[u8]) -> Result<&[u8], CryptoError> {
791 crate::keys::utils::unpad_bytes(key_bytes).map_err(|_| CryptoError::InvalidKey)
792}
793
794pub enum EncodedSymmetricKey {
796 BitwardenLegacyKey(BitwardenLegacyKeyBytes),
798 CoseKey(CoseKeyBytes),
800}
801impl From<EncodedSymmetricKey> for Vec<u8> {
802 fn from(val: EncodedSymmetricKey) -> Self {
803 match val {
804 EncodedSymmetricKey::BitwardenLegacyKey(key) => key.to_vec(),
805 EncodedSymmetricKey::CoseKey(key) => key.to_vec(),
806 }
807 }
808}
809impl EncodedSymmetricKey {
810 #[allow(private_interfaces)]
812 pub fn content_format(&self) -> ContentFormat {
813 match self {
814 EncodedSymmetricKey::BitwardenLegacyKey(_) => ContentFormat::BitwardenLegacyKey,
815 EncodedSymmetricKey::CoseKey(_) => ContentFormat::CoseKey,
816 }
817 }
818}
819
820impl<'de> Deserialize<'de> for SymmetricCryptoKey {
825 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
826 where
827 D: serde::Deserializer<'de>,
828 {
829 deserializer.deserialize_str(FromStrVisitor::new())
830 }
831}
832
833impl FromStr for SymmetricCryptoKey {
834 type Err = CryptoError;
835
836 fn from_str(s: &str) -> Result<Self, Self::Err> {
837 let bytes = B64::try_from(s.to_string()).map_err(|_| CryptoError::InvalidKey)?;
838 Self::try_from(bytes).map_err(|_| CryptoError::InvalidKey)
839 }
840}
841
842impl Serialize for SymmetricCryptoKey {
843 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
844 where
845 S: serde::Serializer,
846 {
847 serializer.serialize_str(&self.to_base64().to_string())
848 }
849}
850
851#[cfg(test)]
853pub fn derive_symmetric_key(name: &str) -> Aes256CbcHmacKey {
854 use zeroize::Zeroizing;
855
856 use crate::{derive_shareable_key, generate_random_bytes};
857
858 let secret: Zeroizing<[u8; 16]> = generate_random_bytes();
859 derive_shareable_key(secret, name, None)
860}
861
862#[cfg(test)]
863mod tests {
864 use bitwarden_encoding::B64;
865 use coset::{CborSerializable, iana::KeyOperation};
866 use hybrid_array::Array;
867 use typenum::U32;
868
869 use super::{EncodedSymmetricKey, SymmetricCryptoKey, derive_symmetric_key};
870 use crate::{
871 Aes256CbcHmacKey, Aes256CbcKey, BitwardenLegacyKeyBytes, CoseKeyThumbprintExt,
872 SymmetricKeyAlgorithm, XAes256GcmKey, XChaCha20Poly1305Key,
873 keys::{
874 KeyId,
875 symmetric_crypto_key::{pad_key, unpad_key},
876 },
877 };
878
879 #[test]
880 #[ignore = "Manual test to verify debug format"]
881 fn test_key_debug() {
882 let aes_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
883 println!("{:?}", aes_key);
884 let xchacha_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
885 println!("{:?}", xchacha_key);
886 }
887
888 #[test]
889 fn test_serialize_deserialize_symmetric_crypto_key() {
890 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
891 let serialized = serde_json::to_string(&key).unwrap();
892 let deserialized: SymmetricCryptoKey = serde_json::from_str(&serialized).unwrap();
893 assert_eq!(key, deserialized);
894 }
895
896 #[test]
897 fn test_symmetric_crypto_key() {
898 let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
899 let key2 = SymmetricCryptoKey::try_from(key.to_base64()).unwrap();
900
901 assert_eq!(key, key2);
902
903 let key = "UY4B5N4DA4UisCNClgZtRr6VLy9ZF5BXXC7cDZRqourKi4ghEMgISbCsubvgCkHf5DZctQjVot11/vVvN9NNHQ==".to_string();
904 let key2 = SymmetricCryptoKey::try_from(key.clone()).unwrap();
905 assert_eq!(key, key2.to_base64().to_string());
906 }
907
908 #[test]
909 fn test_encode_decode_old_symmetric_crypto_key() {
910 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
911 let encoded = key.to_encoded();
912 let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
913 assert_eq!(key, decoded);
914 }
915
916 #[test]
917 fn test_decode_new_symmetric_crypto_key() {
918 let key: B64 = ("pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB").parse()
919 .unwrap();
920 let key = BitwardenLegacyKeyBytes::from(&key);
921 let key = SymmetricCryptoKey::try_from(&key).unwrap();
922 match key {
923 SymmetricCryptoKey::XChaCha20Poly1305Key(_) => (),
924 _ => panic!("Invalid key type"),
925 }
926 }
927
928 #[test]
929 fn test_encode_xchacha20_poly1305_key() {
930 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
931 let encoded = key.to_encoded();
932 let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
933 assert_eq!(key, decoded);
934 }
935
936 #[test]
937 fn test_pad_unpad_key_63() {
938 let original_key = vec![1u8; 63];
939 let mut key_bytes = original_key.clone();
940 let mut encoded_bytes = vec![1u8; 65];
941 encoded_bytes[63] = 2;
942 encoded_bytes[64] = 2;
943 pad_key(&mut key_bytes, 65);
944 assert_eq!(encoded_bytes, key_bytes);
945 let unpadded_key = unpad_key(&key_bytes).unwrap();
946 assert_eq!(original_key, unpadded_key);
947 }
948
949 #[test]
950 fn test_pad_unpad_key_64() {
951 let original_key = vec![1u8; 64];
952 let mut key_bytes = original_key.clone();
953 let mut encoded_bytes = vec![1u8; 65];
954 encoded_bytes[64] = 1;
955 pad_key(&mut key_bytes, 65);
956 assert_eq!(encoded_bytes, key_bytes);
957 let unpadded_key = unpad_key(&key_bytes).unwrap();
958 assert_eq!(original_key, unpadded_key);
959 }
960
961 #[test]
962 fn test_pad_unpad_key_65() {
963 let original_key = vec![1u8; 65];
964 let mut key_bytes = original_key.clone();
965 let mut encoded_bytes = vec![1u8; 66];
966 encoded_bytes[65] = 1;
967 pad_key(&mut key_bytes, 65);
968 assert_eq!(encoded_bytes, key_bytes);
969 let unpadded_key = unpad_key(&key_bytes).unwrap();
970 assert_eq!(original_key, unpadded_key);
971 }
972
973 #[test]
974 fn test_eq_aes_cbc_hmac() {
975 let key1 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
976 let key2 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
977 assert_ne!(key1, key2);
978 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
979 assert_eq!(key1, key3);
980 }
981
982 #[test]
983 fn test_eq_aes_cbc() {
984 let key1 =
985 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![1u8; 32])).unwrap();
986 let key2 =
987 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![2u8; 32])).unwrap();
988 assert_ne!(key1, key2);
989 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
990 assert_eq!(key1, key3);
991 }
992
993 #[test]
994 fn test_eq_xchacha20_poly1305() {
995 let key1 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
996 let key2 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
997 assert_ne!(key1, key2);
998 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
999 assert_eq!(key1, key3);
1000 }
1001
1002 #[test]
1003 fn test_neq_different_key_types() {
1004 let key1 = SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey {
1005 enc_key: Box::pin(Array::<u8, U32>::default()),
1006 });
1007 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
1008 enc_key: Box::pin(Array::<u8, U32>::default()),
1009 key_id: KeyId::from([0; 16]),
1010 supported_operations: vec![
1011 KeyOperation::Decrypt,
1012 KeyOperation::Encrypt,
1013 KeyOperation::WrapKey,
1014 KeyOperation::UnwrapKey,
1015 ],
1016 });
1017 assert_ne!(key1, key2);
1018 }
1019
1020 #[test]
1021 fn test_eq_variant_aes256_cbc() {
1022 let key1 = Aes256CbcKey {
1023 enc_key: Box::pin(Array::from([1u8; 32])),
1024 };
1025 let key2 = Aes256CbcKey {
1026 enc_key: Box::pin(Array::from([1u8; 32])),
1027 };
1028 let key3 = Aes256CbcKey {
1029 enc_key: Box::pin(Array::from([2u8; 32])),
1030 };
1031 assert_eq!(key1, key2);
1032 assert_ne!(key1, key3);
1033 }
1034
1035 #[test]
1036 fn test_eq_variant_aes256_cbc_hmac() {
1037 let key1 = Aes256CbcHmacKey {
1038 enc_key: Box::pin(Array::from([1u8; 32])),
1039 mac_key: Box::pin(Array::from([2u8; 32])),
1040 };
1041 let key2 = Aes256CbcHmacKey {
1042 enc_key: Box::pin(Array::from([1u8; 32])),
1043 mac_key: Box::pin(Array::from([2u8; 32])),
1044 };
1045 let key3 = Aes256CbcHmacKey {
1046 enc_key: Box::pin(Array::from([3u8; 32])),
1047 mac_key: Box::pin(Array::from([4u8; 32])),
1048 };
1049 assert_eq!(key1, key2);
1050 assert_ne!(key1, key3);
1051 }
1052
1053 #[test]
1054 fn test_eq_variant_xchacha20_poly1305() {
1055 let key1 = XChaCha20Poly1305Key {
1056 enc_key: Box::pin(Array::from([1u8; 32])),
1057 key_id: KeyId::from([0; 16]),
1058 supported_operations: vec![
1059 KeyOperation::Decrypt,
1060 KeyOperation::Encrypt,
1061 KeyOperation::WrapKey,
1062 KeyOperation::UnwrapKey,
1063 ],
1064 };
1065 let key2 = XChaCha20Poly1305Key {
1066 enc_key: Box::pin(Array::from([1u8; 32])),
1067 key_id: KeyId::from([0; 16]),
1068 supported_operations: vec![
1069 KeyOperation::Decrypt,
1070 KeyOperation::Encrypt,
1071 KeyOperation::WrapKey,
1072 KeyOperation::UnwrapKey,
1073 ],
1074 };
1075 let key3 = XChaCha20Poly1305Key {
1076 enc_key: Box::pin(Array::from([2u8; 32])),
1077 key_id: KeyId::from([1; 16]),
1078 supported_operations: vec![
1079 KeyOperation::Decrypt,
1080 KeyOperation::Encrypt,
1081 KeyOperation::WrapKey,
1082 KeyOperation::UnwrapKey,
1083 ],
1084 };
1085 assert_eq!(key1, key2);
1086 assert_ne!(key1, key3);
1087 }
1088
1089 fn fixed_xaes_key_inner() -> XAes256GcmKey {
1090 XAes256GcmKey {
1091 enc_key: Box::pin(Array::from([
1092 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
1093 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
1094 0x1c, 0x1d, 0x1e, 0x1f,
1095 ])),
1096 key_id: KeyId::from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
1097 supported_operations: vec![KeyOperation::Encrypt, KeyOperation::Decrypt],
1098 }
1099 }
1100
1101 fn fixed_xaes_key() -> SymmetricCryptoKey {
1102 SymmetricCryptoKey::XAes256GcmKey(fixed_xaes_key_inner())
1103 }
1104
1105 #[test]
1106 fn test_make_xaes256_gcm_key() {
1107 assert!(matches!(
1108 SymmetricCryptoKey::make(SymmetricKeyAlgorithm::XAes256Gcm),
1109 SymmetricCryptoKey::XAes256GcmKey(_)
1110 ));
1111 }
1112
1113 #[test]
1114 fn test_xaes256_gcm_encoding_roundtrips() {
1115 const PADDED_KEY: &str = "pQEEAlAAAQIDBAUGBwgJCgsMDQ4PAzoAARF5BIIDBCBYIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fAQ==";
1116
1117 let key = fixed_xaes_key();
1118 assert_eq!(key.to_base64().to_string(), PADDED_KEY);
1119 let padded = SymmetricCryptoKey::try_from(PADDED_KEY.to_owned()).unwrap();
1120 assert_eq!(padded, key);
1121 let SymmetricCryptoKey::XAes256GcmKey(ref padded) = padded else {
1122 panic!("expected XAES-256-GCM key");
1123 };
1124 assert_eq!(
1125 padded.supported_operations,
1126 [KeyOperation::Encrypt, KeyOperation::Decrypt]
1127 );
1128
1129 let EncodedSymmetricKey::CoseKey(raw) = key.to_encoded_raw() else {
1130 panic!("expected COSE key encoding");
1131 };
1132 assert_eq!(
1133 SymmetricCryptoKey::try_from_cose(raw.as_ref()).unwrap(),
1134 key
1135 );
1136
1137 let cose_key = coset::CoseKey::from_slice(raw.as_ref()).unwrap();
1138 assert_eq!(
1139 cose_key.alg,
1140 Some(coset::Algorithm::PrivateUse(crate::cose::XAES_256_GCM))
1141 );
1142 assert_eq!(cose_key.key_id, (0u8..16).collect::<Vec<_>>());
1143 assert_eq!(cose_key.key_ops.len(), 2);
1144 assert!(
1145 cose_key
1146 .key_ops
1147 .contains(&coset::RegisteredLabel::Assigned(KeyOperation::Encrypt))
1148 );
1149 assert!(
1150 cose_key
1151 .key_ops
1152 .contains(&coset::RegisteredLabel::Assigned(KeyOperation::Decrypt))
1153 );
1154 }
1155
1156 #[test]
1157 fn test_xaes256_gcm_equality() {
1158 let key = fixed_xaes_key();
1159 let same = fixed_xaes_key();
1160 assert_eq!(key, same);
1161
1162 let mut different_bytes = fixed_xaes_key_inner();
1163 different_bytes.enc_key[0] ^= 1;
1164 assert_ne!(key, SymmetricCryptoKey::XAes256GcmKey(different_bytes));
1165
1166 let mut different_id = fixed_xaes_key_inner();
1167 different_id.key_id = KeyId::from([1; 16]);
1168 assert_ne!(key, SymmetricCryptoKey::XAes256GcmKey(different_id));
1169
1170 for other in [
1171 SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey {
1172 enc_key: Box::pin(Array::default()),
1173 }),
1174 SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey {
1175 enc_key: Box::pin(Array::default()),
1176 mac_key: Box::pin(Array::default()),
1177 }),
1178 SymmetricCryptoKey::Aes256GcmKey(crate::Aes256GcmKey::make()),
1179 SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key::make()),
1180 ] {
1181 assert_ne!(key, other);
1182 }
1183 }
1184
1185 #[test]
1186 fn test_neq_different_key_id() {
1187 let key1 = XChaCha20Poly1305Key {
1188 enc_key: Box::pin(Array::<u8, U32>::default()),
1189 key_id: KeyId::from([0; 16]),
1190 supported_operations: vec![
1191 KeyOperation::Decrypt,
1192 KeyOperation::Encrypt,
1193 KeyOperation::WrapKey,
1194 KeyOperation::UnwrapKey,
1195 ],
1196 };
1197 let key2 = XChaCha20Poly1305Key {
1198 enc_key: Box::pin(Array::<u8, U32>::default()),
1199 key_id: KeyId::from([1; 16]),
1200 supported_operations: vec![
1201 KeyOperation::Decrypt,
1202 KeyOperation::Encrypt,
1203 KeyOperation::WrapKey,
1204 KeyOperation::UnwrapKey,
1205 ],
1206 };
1207 assert_ne!(key1, key2);
1208
1209 let key1 = SymmetricCryptoKey::XChaCha20Poly1305Key(key1);
1210 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(key2);
1211 assert_ne!(key1, key2);
1212 }
1213
1214 const AES256_GCM_KEY: &str =
1215 "pQEEAlACAgICAgICAgICAgICAgICAwMEhAMEBQYgWCABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=";
1216 const AES256_GCM_KEY_THUMBPRINT: &str =
1217 "3810c7275ee292caca13d938a057a94c75210087d960d3eb6868c0ffe99b5643";
1218
1219 const XCHACHA20_POLY1305_KEY: &str = "pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB";
1220 const XCHACHA20_POLY1305_KEY_THUMBPRINT: &str =
1221 "64aec2d09ef5ba8b310ef9a70346b03422443e295b6f045e38169ae97e579d85";
1222
1223 #[test]
1224 fn test_decode_new_aes256_gcm_key() {
1225 let key: B64 = AES256_GCM_KEY.parse().unwrap();
1226 let key = SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(&key)).unwrap();
1227 match key {
1228 SymmetricCryptoKey::Aes256GcmKey(_) => (),
1229 _ => panic!("Invalid key type"),
1230 }
1231 }
1232
1233 #[test]
1234 fn test_thumbprint_aes256_gcm_vector() {
1235 let key: B64 = AES256_GCM_KEY.parse().unwrap();
1237 let key = SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(&key)).unwrap();
1238 assert_eq!(
1239 key.thumbprint().unwrap().to_hex(),
1240 AES256_GCM_KEY_THUMBPRINT
1241 );
1242 }
1243
1244 #[test]
1245 fn test_thumbprint_xchacha20_poly1305_vector() {
1246 let key: B64 = XCHACHA20_POLY1305_KEY.parse().unwrap();
1248 let key = SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(&key)).unwrap();
1249 assert_eq!(
1250 key.thumbprint().unwrap().to_hex(),
1251 XCHACHA20_POLY1305_KEY_THUMBPRINT
1252 );
1253 }
1254
1255 #[test]
1256 fn test_thumbprint_is_deterministic() {
1257 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
1258 assert_eq!(key.thumbprint().unwrap(), key.thumbprint().unwrap());
1259 }
1260
1261 #[test]
1262 fn test_thumbprint_errors_for_legacy_aes_cbc() {
1263 assert!(
1264 SymmetricCryptoKey::make_aes256_cbc_hmac_key()
1265 .thumbprint()
1266 .is_err()
1267 );
1268 }
1269}