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, U64};
20#[cfg(feature = "wasm")]
21use wasm_bindgen::convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi};
22use zeroize::{Zeroize, ZeroizeOnDrop};
23
24use super::{
25 key_encryptable::CryptoKey,
26 key_id::{KEY_ID_SIZE, KeyId},
27};
28use crate::{
29 BitwardenLegacyKeyBytes, ContentFormat, CoseKeyBytes, CoseKeyThumbprint, CryptoError, cose,
30 cose::{
31 CoseKeyThumbprintExt, symmetric::CoseContentEncryptionAlgorithm,
32 thumbprint_from_required_params,
33 },
34 error::EncodingError,
35};
36
37#[cfg(feature = "wasm")]
38#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
39const TS_CUSTOM_TYPES: &'static str = r#"
40export type SymmetricKey = Tagged<string, "SymmetricKey">;
41"#;
42
43#[cfg(feature = "wasm")]
44impl wasm_bindgen::describe::WasmDescribe for SymmetricCryptoKey {
45 fn describe() {
46 <String as wasm_bindgen::describe::WasmDescribe>::describe();
47 }
48}
49
50#[cfg(feature = "wasm")]
51impl FromWasmAbi for SymmetricCryptoKey {
52 type Abi = <String as FromWasmAbi>::Abi;
53
54 unsafe fn from_abi(abi: Self::Abi) -> Self {
55 use wasm_bindgen::UnwrapThrowExt;
56 let string = unsafe { String::from_abi(abi) };
57 let b64 = B64::try_from(string).unwrap_throw();
58 SymmetricCryptoKey::try_from(b64).unwrap_throw()
59 }
60}
61
62#[cfg(feature = "wasm")]
63impl OptionFromWasmAbi for SymmetricCryptoKey {
64 fn is_none(abi: &Self::Abi) -> bool {
65 <String as OptionFromWasmAbi>::is_none(abi)
66 }
67}
68
69#[cfg(feature = "wasm")]
70impl IntoWasmAbi for SymmetricCryptoKey {
71 type Abi = <String as IntoWasmAbi>::Abi;
72
73 fn into_abi(self) -> Self::Abi {
74 let string: String = self.to_base64().to_string();
75 string.into_abi()
76 }
77}
78
79#[cfg(feature = "wasm")]
80impl TryFrom<wasm_bindgen::JsValue> for SymmetricCryptoKey {
81 type Error = CryptoError;
82
83 fn try_from(value: wasm_bindgen::JsValue) -> Result<Self, Self::Error> {
84 let string = value.as_string().ok_or(CryptoError::InvalidKey)?;
85 Self::try_from(string)
86 }
87}
88
89#[derive(Debug, PartialEq)]
91pub enum SymmetricKeyAlgorithm {
92 Aes256CbcHmac,
94 XChaCha20Poly1305,
96 Aes256Gcm,
103 XAes256Gcm,
106}
107
108#[derive(ZeroizeOnDrop, Clone)]
112pub struct Aes256CbcKey {
113 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
115}
116
117impl ConstantTimeEq for Aes256CbcKey {
118 fn ct_eq(&self, other: &Self) -> Choice {
119 self.enc_key.ct_eq(&other.enc_key)
120 }
121}
122
123impl PartialEq for Aes256CbcKey {
124 fn eq(&self, other: &Self) -> bool {
125 self.ct_eq(other).into()
126 }
127}
128
129pub(crate) const AES256_CBC_HMAC_ENC_KEY_SIZE: usize = 32;
131pub(crate) const AES256_CBC_HMAC_MAC_KEY_SIZE: usize = 32;
133pub(crate) const AES256_CBC_HMAC_KEY_SIZE: usize =
135 AES256_CBC_HMAC_ENC_KEY_SIZE + AES256_CBC_HMAC_MAC_KEY_SIZE;
136
137#[derive(ZeroizeOnDrop, Clone)]
140pub struct Aes256CbcHmacKey {
141 pub(crate) key: Pin<Box<Array<u8, U64>>>,
152}
153
154impl ConstantTimeEq for Aes256CbcHmacKey {
155 fn ct_eq(&self, other: &Self) -> Choice {
156 self.key.ct_eq(&other.key)
157 }
158}
159
160impl Aes256CbcHmacKey {
161 pub(crate) fn new(
163 enc_key: &[u8; AES256_CBC_HMAC_ENC_KEY_SIZE],
164 mac_key: &[u8; AES256_CBC_HMAC_MAC_KEY_SIZE],
165 ) -> Self {
166 let mut key = Box::pin(Array::<u8, U64>::default());
167 let (enc, mac) = key.split_at_mut(AES256_CBC_HMAC_ENC_KEY_SIZE);
168 enc.copy_from_slice(enc_key);
169 mac.copy_from_slice(mac_key);
170 Self { key }
171 }
172
173 pub(crate) fn as_composite_key(&self) -> &[u8; AES256_CBC_HMAC_KEY_SIZE] {
178 &self.key.0
179 }
180
181 pub(crate) fn as_slice(&self) -> &[u8] {
183 self.key.as_slice()
184 }
185
186 pub(crate) fn enc_key(&self) -> &[u8; AES256_CBC_HMAC_ENC_KEY_SIZE] {
188 let (enc_key, _) = self.key.split_at(AES256_CBC_HMAC_ENC_KEY_SIZE);
189 enc_key
190 .try_into()
191 .expect("first half of a 64-byte key is always 32 bytes")
192 }
193
194 pub(crate) fn mac_key(&self) -> &[u8; AES256_CBC_HMAC_MAC_KEY_SIZE] {
196 let (_, mac_key) = self.key.split_at(AES256_CBC_HMAC_ENC_KEY_SIZE);
197 mac_key
198 .try_into()
199 .expect("second half of a 64-byte key is always 32 bytes")
200 }
201
202 pub(crate) fn key_id(&self) -> KeyId {
210 let thumbprint = symmetric_key_thumbprint(self.as_slice());
211 let mut key_id = [0u8; KEY_ID_SIZE];
212 key_id.copy_from_slice(&thumbprint.as_bytes()[..KEY_ID_SIZE]);
213 KeyId::from(key_id)
214 }
215}
216
217fn symmetric_key_thumbprint(key_bytes: &[u8]) -> CoseKeyThumbprint {
221 thumbprint_from_required_params(vec![
222 (
223 KeyParameter::Kty.to_i64(),
224 Value::Integer(Integer::from(KeyType::Symmetric.to_i64())),
225 ),
226 (
227 SymmetricKeyParameter::K.to_i64(),
228 Value::Bytes(key_bytes.to_vec()),
229 ),
230 ])
231}
232
233impl PartialEq for Aes256CbcHmacKey {
234 fn eq(&self, other: &Self) -> bool {
235 self.ct_eq(other).into()
236 }
237}
238
239#[derive(Zeroize, Clone)]
244pub struct XChaCha20Poly1305Key {
245 pub(crate) key_id: KeyId,
246 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
247 #[zeroize(skip)]
252 pub(crate) supported_operations: Vec<KeyOperation>,
253}
254
255impl XChaCha20Poly1305Key {
256 pub fn make() -> Self {
258 let mut rng = bitwarden_random::rng();
259 let mut enc_key = Box::pin(Array::<u8, U32>::default());
260 rng.fill(enc_key.as_mut_slice());
261 let key_id = KeyId::make();
262
263 Self {
264 enc_key,
265 key_id,
266 supported_operations: vec![
267 KeyOperation::Decrypt,
268 KeyOperation::Encrypt,
269 KeyOperation::WrapKey,
270 KeyOperation::UnwrapKey,
271 ],
272 }
273 }
274}
275
276impl ConstantTimeEq for XChaCha20Poly1305Key {
277 fn ct_eq(&self, other: &Self) -> Choice {
278 self.enc_key.ct_eq(&other.enc_key) & self.key_id.ct_eq(&other.key_id)
279 }
280}
281
282impl PartialEq for XChaCha20Poly1305Key {
283 fn eq(&self, other: &Self) -> bool {
284 self.ct_eq(other).into()
285 }
286}
287
288#[derive(Zeroize, Clone)]
290pub struct Aes256GcmKey {
291 pub(crate) key_id: KeyId,
292 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
293 #[zeroize(skip)]
296 pub(crate) supported_operations: Vec<KeyOperation>,
297}
298
299impl Aes256GcmKey {
300 pub fn make() -> Self {
302 let mut rng = bitwarden_random::rng();
303 let mut enc_key = Box::pin(Array::<u8, U32>::default());
304 rng.fill(enc_key.as_mut_slice());
305 let key_id = KeyId::make();
306
307 Self {
308 enc_key,
309 key_id,
310 supported_operations: vec![
311 KeyOperation::Decrypt,
312 KeyOperation::Encrypt,
313 KeyOperation::WrapKey,
314 KeyOperation::UnwrapKey,
315 ],
316 }
317 }
318
319 pub(crate) fn disable_key_operation(&mut self, op: KeyOperation) -> &mut Self {
320 self.supported_operations.retain(|k| *k != op);
321 self
322 }
323}
324
325impl ConstantTimeEq for Aes256GcmKey {
326 fn ct_eq(&self, other: &Self) -> Choice {
327 self.enc_key.ct_eq(&other.enc_key) & self.key_id.ct_eq(&other.key_id)
328 }
329}
330
331impl PartialEq for Aes256GcmKey {
332 fn eq(&self, other: &Self) -> bool {
333 self.ct_eq(other).into()
334 }
335}
336
337#[derive(Zeroize, Clone)]
339pub struct XAes256GcmKey {
340 pub(crate) key_id: KeyId,
341 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
342 #[zeroize(skip)]
343 pub(crate) supported_operations: Vec<KeyOperation>,
344}
345
346impl XAes256GcmKey {
347 pub fn make() -> Self {
349 let mut rng = bitwarden_random::rng();
350 let mut enc_key = Box::pin(Array::<u8, U32>::default());
351 rng.fill(enc_key.as_mut_slice());
352
353 Self {
354 key_id: KeyId::make(),
355 enc_key,
356 supported_operations: vec![
357 KeyOperation::Decrypt,
358 KeyOperation::Encrypt,
359 KeyOperation::WrapKey,
360 KeyOperation::UnwrapKey,
361 ],
362 }
363 }
364}
365
366impl ConstantTimeEq for XAes256GcmKey {
367 fn ct_eq(&self, other: &Self) -> Choice {
368 self.enc_key.ct_eq(&other.enc_key) & self.key_id.ct_eq(&other.key_id)
369 }
370}
371
372impl PartialEq for XAes256GcmKey {
373 fn eq(&self, other: &Self) -> bool {
374 self.ct_eq(other).into()
375 }
376}
377
378pub(crate) enum CoseKeyView<'a> {
380 Aes256Gcm(&'a Aes256GcmKey),
381 XChaCha20Poly1305(&'a XChaCha20Poly1305Key),
382 XAes256Gcm(&'a XAes256GcmKey),
383 Aes256CbcHmac(&'a Aes256CbcHmacKey),
384}
385
386impl CoseKeyView<'_> {
387 pub(crate) fn key_id(&self) -> KeyId {
393 match self {
394 CoseKeyView::Aes256Gcm(k) => k.key_id.clone(),
395 CoseKeyView::XChaCha20Poly1305(k) => k.key_id.clone(),
396 CoseKeyView::XAes256Gcm(k) => k.key_id.clone(),
397 CoseKeyView::Aes256CbcHmac(k) => k.key_id(),
398 }
399 }
400
401 pub(crate) fn key_bytes(&self) -> &[u8] {
404 match self {
405 CoseKeyView::Aes256Gcm(k) => k.enc_key.as_slice(),
406 CoseKeyView::XChaCha20Poly1305(k) => k.enc_key.as_slice(),
407 CoseKeyView::XAes256Gcm(k) => k.enc_key.as_slice(),
408 CoseKeyView::Aes256CbcHmac(k) => k.as_slice(),
409 }
410 }
411
412 pub(crate) fn algorithm(&self) -> CoseContentEncryptionAlgorithm {
413 match self {
414 CoseKeyView::Aes256Gcm(_) => CoseContentEncryptionAlgorithm::Aes256Gcm,
415 CoseKeyView::XChaCha20Poly1305(_) => CoseContentEncryptionAlgorithm::XChaCha20Poly1305,
416 CoseKeyView::XAes256Gcm(_) => CoseContentEncryptionAlgorithm::XAes256Gcm,
417 CoseKeyView::Aes256CbcHmac(_) => CoseContentEncryptionAlgorithm::Aes256CbcHmacSha256,
418 }
419 }
420}
421
422#[derive(ZeroizeOnDrop, Clone)]
424pub enum SymmetricCryptoKey {
425 #[allow(missing_docs)]
426 Aes256CbcKey(Aes256CbcKey),
427 #[allow(missing_docs)]
428 Aes256CbcHmacKey(Aes256CbcHmacKey),
429 XChaCha20Poly1305Key(XChaCha20Poly1305Key),
432 Aes256GcmKey(Aes256GcmKey),
435 XAes256GcmKey(XAes256GcmKey),
438}
439
440impl SymmetricCryptoKey {
441 const AES256_CBC_KEY_LEN: usize = 32;
443 const AES256_CBC_HMAC_KEY_LEN: usize = 64;
445
446 pub(crate) fn make_aes256_cbc_hmac_key_internal(mut rng: impl rand::CryptoRng) -> Self {
452 let mut key = Box::pin(Array::<u8, U64>::default());
453 let (enc_key, mac_key) = key.split_at_mut(AES256_CBC_HMAC_ENC_KEY_SIZE);
456 rng.fill(enc_key);
457 rng.fill(mac_key);
458
459 Self::Aes256CbcHmacKey(Aes256CbcHmacKey { key })
460 }
461
462 pub fn make(algorithm: SymmetricKeyAlgorithm) -> Self {
464 match algorithm {
465 SymmetricKeyAlgorithm::Aes256CbcHmac => Self::make_aes256_cbc_hmac_key(),
466 SymmetricKeyAlgorithm::XChaCha20Poly1305 => Self::make_xchacha20_poly1305_key(),
467 SymmetricKeyAlgorithm::Aes256Gcm => Self::Aes256GcmKey(Aes256GcmKey::make()),
468 SymmetricKeyAlgorithm::XAes256Gcm => Self::XAes256GcmKey(XAes256GcmKey::make()),
469 }
470 }
471
472 pub(crate) fn make_aes256_cbc_hmac_key() -> Self {
474 let rng = bitwarden_random::rng();
475 Self::make_aes256_cbc_hmac_key_internal(rng)
476 }
477
478 pub(crate) fn make_xchacha20_poly1305_key() -> Self {
480 let mut rng = bitwarden_random::rng();
481 let mut enc_key = Box::pin(Array::<u8, U32>::default());
482 rng.fill(enc_key.as_mut_slice());
483 Self::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
484 enc_key,
485 key_id: KeyId::make(),
486 supported_operations: vec![
487 KeyOperation::Decrypt,
488 KeyOperation::Encrypt,
489 KeyOperation::WrapKey,
490 KeyOperation::UnwrapKey,
491 ],
492 })
493 }
494
495 pub fn to_encoded(&self) -> BitwardenLegacyKeyBytes {
504 let encoded_key = self.to_encoded_raw();
505 match encoded_key {
506 EncodedSymmetricKey::BitwardenLegacyKey(_) => {
507 let encoded_key: Vec<u8> = encoded_key.into();
508 BitwardenLegacyKeyBytes::from(encoded_key)
509 }
510 EncodedSymmetricKey::CoseKey(_) => {
511 let mut encoded_key: Vec<u8> = encoded_key.into();
512 pad_key(&mut encoded_key, (Self::AES256_CBC_HMAC_KEY_LEN + 1) as u8); BitwardenLegacyKeyBytes::from(encoded_key)
514 }
515 }
516 }
517
518 #[cfg(test)]
521 pub fn generate_seeded_for_unit_tests(seed: &str) -> Self {
522 let mut seeded_rng = ChaChaRng::from_seed(sha2::Sha256::digest(seed.as_bytes()).into());
524 let mut key = Box::pin(Array::<u8, U64>::default());
525 let (enc_key, mac_key) = key.split_at_mut(AES256_CBC_HMAC_ENC_KEY_SIZE);
527 seeded_rng.fill(enc_key);
528 seeded_rng.fill(mac_key);
529
530 SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { key })
531 }
532
533 pub(crate) fn to_encoded_raw(&self) -> EncodedSymmetricKey {
546 match self {
547 Self::Aes256CbcKey(key) => {
548 EncodedSymmetricKey::BitwardenLegacyKey(key.enc_key.to_vec().into())
549 }
550 Self::Aes256CbcHmacKey(key) => {
551 EncodedSymmetricKey::BitwardenLegacyKey(key.as_slice().to_vec().into())
552 }
553 Self::XChaCha20Poly1305Key(key) => {
554 let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec());
555 let mut cose_key = builder.key_id((&key.key_id).into());
556 for op in &key.supported_operations {
557 cose_key = cose_key.add_key_op(*op);
558 }
559 let mut cose_key = cose_key.build();
560 cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse(
561 cose::XCHACHA20_POLY1305,
562 ));
563 EncodedSymmetricKey::CoseKey(
564 cose_key
565 .to_vec()
566 .expect("cose key serialization should not fail")
567 .into(),
568 )
569 }
570 Self::XAes256GcmKey(key) => {
571 let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec());
572 let mut cose_key = builder.key_id((&key.key_id).into());
573 for op in &key.supported_operations {
574 cose_key = cose_key.add_key_op(*op);
575 }
576 let mut cose_key = cose_key.build();
577 cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse(cose::XAES_256_GCM));
578 EncodedSymmetricKey::CoseKey(
579 cose_key
580 .to_vec()
581 .expect("cose key serialization should not fail")
582 .into(),
583 )
584 }
585 Self::Aes256GcmKey(key) => {
586 let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec());
587 let mut cose_key = builder.key_id((&key.key_id).into());
588 for op in &key.supported_operations {
589 cose_key = cose_key.add_key_op(*op);
590 }
591 let mut cose_key = cose_key.build();
592 cose_key.alg = Some(RegisteredLabelWithPrivate::Assigned(
593 coset::iana::Algorithm::A256GCM,
594 ));
595 EncodedSymmetricKey::CoseKey(
596 cose_key
597 .to_vec()
598 .expect("cose key serialization should not fail")
599 .into(),
600 )
601 }
602 }
603 }
604
605 pub(crate) fn try_from_cose(serialized_key: &[u8]) -> Result<Self, CryptoError> {
606 let cose_key =
607 coset::CoseKey::from_slice(serialized_key).map_err(|_| CryptoError::InvalidKey)?;
608 let key = SymmetricCryptoKey::try_from(&cose_key)?;
609 Ok(key)
610 }
611
612 #[allow(missing_docs)]
613 pub fn to_base64(&self) -> B64 {
614 B64::from(self.to_encoded().as_ref())
615 }
616
617 pub fn key_id(&self) -> Option<KeyId> {
624 match self {
625 Self::Aes256CbcKey(_) => None,
626 Self::Aes256CbcHmacKey(key) => Some(key.key_id()),
627 Self::XChaCha20Poly1305Key(key) => Some(key.key_id.clone()),
628 Self::Aes256GcmKey(key) => Some(key.key_id.clone()),
629 Self::XAes256GcmKey(key) => Some(key.key_id.clone()),
630 }
631 }
632
633 pub(crate) fn as_cose_key_view(&self) -> Option<CoseKeyView<'_>> {
637 match self {
638 Self::Aes256GcmKey(k) => Some(CoseKeyView::Aes256Gcm(k)),
639 Self::XChaCha20Poly1305Key(k) => Some(CoseKeyView::XChaCha20Poly1305(k)),
640 Self::XAes256GcmKey(k) => Some(CoseKeyView::XAes256Gcm(k)),
641 Self::Aes256CbcHmacKey(k) => Some(CoseKeyView::Aes256CbcHmac(k)),
642 Self::Aes256CbcKey(_) => None,
643 }
644 }
645}
646
647impl CoseKeyThumbprintExt for SymmetricCryptoKey {
648 fn thumbprint(&self) -> Result<CoseKeyThumbprint, CryptoError> {
653 let view = self
654 .as_cose_key_view()
655 .ok_or(EncodingError::UnsupportedValue(
656 "unauthenticated AES-CBC keys are not COSE keys and have no thumbprint",
657 ))?;
658 Ok(symmetric_key_thumbprint(view.key_bytes()))
659 }
660}
661
662impl ConstantTimeEq for SymmetricCryptoKey {
663 fn ct_eq(&self, other: &SymmetricCryptoKey) -> Choice {
667 use SymmetricCryptoKey::*;
668 match (self, other) {
669 (Aes256CbcKey(a), Aes256CbcKey(b)) => a.ct_eq(b),
670 (Aes256CbcKey(_), _) => Choice::from(0),
671
672 (Aes256CbcHmacKey(a), Aes256CbcHmacKey(b)) => a.ct_eq(b),
673 (Aes256CbcHmacKey(_), _) => Choice::from(0),
674
675 (XChaCha20Poly1305Key(a), XChaCha20Poly1305Key(b)) => a.ct_eq(b),
676 (XChaCha20Poly1305Key(_), _) => Choice::from(0),
677
678 (Aes256GcmKey(a), Aes256GcmKey(b)) => a.ct_eq(b),
679 (Aes256GcmKey(_), _) => Choice::from(0),
680
681 (XAes256GcmKey(a), XAes256GcmKey(b)) => a.ct_eq(b),
682 (XAes256GcmKey(_), _) => Choice::from(0),
683 }
684 }
685}
686
687impl PartialEq for SymmetricCryptoKey {
688 fn eq(&self, other: &Self) -> bool {
689 self.ct_eq(other).into()
690 }
691}
692
693impl TryFrom<String> for SymmetricCryptoKey {
694 type Error = CryptoError;
695
696 fn try_from(value: String) -> Result<Self, Self::Error> {
697 let bytes = B64::try_from(value).map_err(|_| CryptoError::InvalidKey)?;
698 Self::try_from(bytes)
699 }
700}
701
702impl TryFrom<B64> for SymmetricCryptoKey {
703 type Error = CryptoError;
704
705 fn try_from(value: B64) -> Result<Self, Self::Error> {
706 Self::try_from(&BitwardenLegacyKeyBytes::from(&value))
707 }
708}
709
710impl TryFrom<&BitwardenLegacyKeyBytes> for SymmetricCryptoKey {
711 type Error = CryptoError;
712
713 fn try_from(value: &BitwardenLegacyKeyBytes) -> Result<Self, Self::Error> {
714 let slice = value.as_ref();
715
716 if slice.len() == Self::AES256_CBC_HMAC_KEY_LEN || slice.len() == Self::AES256_CBC_KEY_LEN {
722 Self::try_from(EncodedSymmetricKey::BitwardenLegacyKey(value.clone()))
723 } else if slice.len() > Self::AES256_CBC_HMAC_KEY_LEN {
724 let unpadded_value = unpad_key(slice)?;
725 Ok(Self::try_from_cose(unpadded_value)?)
726 } else {
727 Err(CryptoError::InvalidKeyLen)
728 }
729 }
730}
731
732impl TryFrom<EncodedSymmetricKey> for SymmetricCryptoKey {
733 type Error = CryptoError;
734
735 fn try_from(value: EncodedSymmetricKey) -> Result<Self, Self::Error> {
736 match value {
737 EncodedSymmetricKey::BitwardenLegacyKey(key)
738 if key.as_ref().len() == Self::AES256_CBC_KEY_LEN =>
739 {
740 let mut enc_key = Box::pin(Array::<u8, U32>::default());
741 enc_key.copy_from_slice(&key.as_ref()[..Self::AES256_CBC_KEY_LEN]);
742 Ok(Self::Aes256CbcKey(Aes256CbcKey { enc_key }))
743 }
744 EncodedSymmetricKey::BitwardenLegacyKey(key)
745 if key.as_ref().len() == Self::AES256_CBC_HMAC_KEY_LEN =>
746 {
747 let mut composite_key = Box::pin(Array::<u8, U64>::default());
749 composite_key.copy_from_slice(key.as_ref());
750
751 Ok(Self::Aes256CbcHmacKey(Aes256CbcHmacKey {
752 key: composite_key,
753 }))
754 }
755 EncodedSymmetricKey::CoseKey(key) => Self::try_from_cose(key.as_ref()),
756 _ => Err(CryptoError::InvalidKey),
757 }
758 }
759}
760
761impl CryptoKey for SymmetricCryptoKey {}
762
763impl std::fmt::Debug for SymmetricCryptoKey {
765 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
766 match self {
767 SymmetricCryptoKey::Aes256CbcKey(key) => key.fmt(f),
768 SymmetricCryptoKey::Aes256CbcHmacKey(key) => key.fmt(f),
769 SymmetricCryptoKey::XChaCha20Poly1305Key(key) => key.fmt(f),
770 SymmetricCryptoKey::Aes256GcmKey(key) => key.fmt(f),
771 SymmetricCryptoKey::XAes256GcmKey(key) => key.fmt(f),
772 }
773 }
774}
775
776impl std::fmt::Debug for Aes256CbcKey {
777 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
778 let mut debug_struct = f.debug_struct("SymmetricKey::Aes256Cbc");
779 #[cfg(feature = "dangerous-crypto-debug")]
780 debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
781 debug_struct.finish()
782 }
783}
784
785impl std::fmt::Debug for Aes256CbcHmacKey {
786 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
787 let mut debug_struct = f.debug_struct("SymmetricKey::Aes256CbcHmac");
788 #[cfg(feature = "dangerous-crypto-debug")]
789 debug_struct
790 .field("enc_key", &hex::encode(self.enc_key()))
791 .field("mac_key", &hex::encode(self.mac_key()));
792 debug_struct.finish()
793 }
794}
795
796impl std::fmt::Debug for XChaCha20Poly1305Key {
797 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
798 let mut debug_struct = f.debug_struct("SymmetricKey::XChaCha20Poly1305");
799 debug_struct.field("key_id", &self.key_id);
800 debug_struct.field(
801 "supported_operations",
802 &self
803 .supported_operations
804 .iter()
805 .map(|key_operation: &KeyOperation| cose::debug_key_operation(*key_operation))
806 .collect::<Vec<_>>(),
807 );
808 #[cfg(feature = "dangerous-crypto-debug")]
809 debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
810 debug_struct.finish()
811 }
812}
813
814impl std::fmt::Debug for XAes256GcmKey {
815 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
816 let mut debug_struct = f.debug_struct("SymmetricKey::XAes256Gcm");
817 debug_struct.field("key_id", &self.key_id);
818 debug_struct.field(
819 "supported_operations",
820 &self
821 .supported_operations
822 .iter()
823 .map(|key_operation: &KeyOperation| cose::debug_key_operation(*key_operation))
824 .collect::<Vec<_>>(),
825 );
826 #[cfg(feature = "dangerous-crypto-debug")]
827 debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
828 debug_struct.finish()
829 }
830}
831
832impl std::fmt::Debug for Aes256GcmKey {
833 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
834 let mut debug_struct = f.debug_struct("SymmetricKey::Aes256Gcm");
835 debug_struct.field("key_id", &self.key_id);
836 debug_struct.field(
837 "supported_operations",
838 &self
839 .supported_operations
840 .iter()
841 .map(|key_operation: &KeyOperation| cose::debug_key_operation(*key_operation))
842 .collect::<Vec<_>>(),
843 );
844 #[cfg(feature = "dangerous-crypto-debug")]
845 debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
846 debug_struct.finish()
847 }
848}
849
850fn pad_key(key_bytes: &mut Vec<u8>, min_length: u8) {
860 crate::keys::utils::pad_bytes(key_bytes, min_length as usize)
861 .expect("Padding cannot fail since the min_length is < 255")
862}
863
864fn unpad_key(key_bytes: &[u8]) -> Result<&[u8], CryptoError> {
874 crate::keys::utils::unpad_bytes(key_bytes).map_err(|_| CryptoError::InvalidKey)
875}
876
877pub enum EncodedSymmetricKey {
879 BitwardenLegacyKey(BitwardenLegacyKeyBytes),
881 CoseKey(CoseKeyBytes),
883}
884impl From<EncodedSymmetricKey> for Vec<u8> {
885 fn from(val: EncodedSymmetricKey) -> Self {
886 match val {
887 EncodedSymmetricKey::BitwardenLegacyKey(key) => key.to_vec(),
888 EncodedSymmetricKey::CoseKey(key) => key.to_vec(),
889 }
890 }
891}
892impl EncodedSymmetricKey {
893 #[allow(private_interfaces)]
895 pub fn content_format(&self) -> ContentFormat {
896 match self {
897 EncodedSymmetricKey::BitwardenLegacyKey(_) => ContentFormat::BitwardenLegacyKey,
898 EncodedSymmetricKey::CoseKey(_) => ContentFormat::CoseKey,
899 }
900 }
901}
902
903impl<'de> Deserialize<'de> for SymmetricCryptoKey {
908 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
909 where
910 D: serde::Deserializer<'de>,
911 {
912 deserializer.deserialize_str(FromStrVisitor::new())
913 }
914}
915
916impl FromStr for SymmetricCryptoKey {
917 type Err = CryptoError;
918
919 fn from_str(s: &str) -> Result<Self, Self::Err> {
920 let bytes = B64::try_from(s.to_string()).map_err(|_| CryptoError::InvalidKey)?;
921 Self::try_from(bytes).map_err(|_| CryptoError::InvalidKey)
922 }
923}
924
925impl Serialize for SymmetricCryptoKey {
926 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
927 where
928 S: serde::Serializer,
929 {
930 serializer.serialize_str(&self.to_base64().to_string())
931 }
932}
933
934#[cfg(test)]
936pub fn derive_symmetric_key(name: &str) -> Aes256CbcHmacKey {
937 use zeroize::Zeroizing;
938
939 use crate::{derive_shareable_key, generate_random_bytes};
940
941 let secret: Zeroizing<[u8; 16]> = generate_random_bytes();
942 derive_shareable_key(secret, name, None)
943}
944
945#[cfg(test)]
946mod tests {
947 use bitwarden_encoding::B64;
948 use coset::{CborSerializable, iana::KeyOperation};
949 use hybrid_array::Array;
950 use typenum::U32;
951
952 use super::{
953 AES256_CBC_HMAC_ENC_KEY_SIZE, EncodedSymmetricKey, KEY_ID_SIZE, SymmetricCryptoKey,
954 derive_symmetric_key,
955 };
956 use crate::{
957 Aes256CbcHmacKey, Aes256CbcKey, BitwardenLegacyKeyBytes, CoseKeyThumbprintExt,
958 SymmetricKeyAlgorithm, XAes256GcmKey, XChaCha20Poly1305Key,
959 keys::{
960 KeyId,
961 symmetric_crypto_key::{pad_key, unpad_key},
962 },
963 };
964
965 #[test]
966 #[ignore = "Manual test to verify debug format"]
967 fn test_key_debug() {
968 let aes_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
969 println!("{:?}", aes_key);
970 let xchacha_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
971 println!("{:?}", xchacha_key);
972 }
973
974 #[test]
975 fn test_serialize_deserialize_symmetric_crypto_key() {
976 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
977 let serialized = serde_json::to_string(&key).unwrap();
978 let deserialized: SymmetricCryptoKey = serde_json::from_str(&serialized).unwrap();
979 assert_eq!(key, deserialized);
980 }
981
982 #[test]
983 fn test_symmetric_crypto_key() {
984 let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
985 let key2 = SymmetricCryptoKey::try_from(key.to_base64()).unwrap();
986
987 assert_eq!(key, key2);
988
989 let key = "UY4B5N4DA4UisCNClgZtRr6VLy9ZF5BXXC7cDZRqourKi4ghEMgISbCsubvgCkHf5DZctQjVot11/vVvN9NNHQ==".to_string();
990 let key2 = SymmetricCryptoKey::try_from(key.clone()).unwrap();
991 assert_eq!(key, key2.to_base64().to_string());
992 }
993
994 #[test]
995 fn test_encode_decode_old_symmetric_crypto_key() {
996 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
997 let encoded = key.to_encoded();
998 let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
999 assert_eq!(key, decoded);
1000 }
1001
1002 #[test]
1003 fn test_decode_new_symmetric_crypto_key() {
1004 let key: B64 = ("pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB").parse()
1005 .unwrap();
1006 let key = BitwardenLegacyKeyBytes::from(&key);
1007 let key = SymmetricCryptoKey::try_from(&key).unwrap();
1008 match key {
1009 SymmetricCryptoKey::XChaCha20Poly1305Key(_) => (),
1010 _ => panic!("Invalid key type"),
1011 }
1012 }
1013
1014 #[test]
1015 fn test_encode_xchacha20_poly1305_key() {
1016 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
1017 let encoded = key.to_encoded();
1018 let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
1019 assert_eq!(key, decoded);
1020 }
1021
1022 #[test]
1023 fn test_pad_unpad_key_63() {
1024 let original_key = vec![1u8; 63];
1025 let mut key_bytes = original_key.clone();
1026 let mut encoded_bytes = vec![1u8; 65];
1027 encoded_bytes[63] = 2;
1028 encoded_bytes[64] = 2;
1029 pad_key(&mut key_bytes, 65);
1030 assert_eq!(encoded_bytes, key_bytes);
1031 let unpadded_key = unpad_key(&key_bytes).unwrap();
1032 assert_eq!(original_key, unpadded_key);
1033 }
1034
1035 #[test]
1036 fn test_pad_unpad_key_64() {
1037 let original_key = vec![1u8; 64];
1038 let mut key_bytes = original_key.clone();
1039 let mut encoded_bytes = vec![1u8; 65];
1040 encoded_bytes[64] = 1;
1041 pad_key(&mut key_bytes, 65);
1042 assert_eq!(encoded_bytes, key_bytes);
1043 let unpadded_key = unpad_key(&key_bytes).unwrap();
1044 assert_eq!(original_key, unpadded_key);
1045 }
1046
1047 #[test]
1048 fn test_pad_unpad_key_65() {
1049 let original_key = vec![1u8; 65];
1050 let mut key_bytes = original_key.clone();
1051 let mut encoded_bytes = vec![1u8; 66];
1052 encoded_bytes[65] = 1;
1053 pad_key(&mut key_bytes, 65);
1054 assert_eq!(encoded_bytes, key_bytes);
1055 let unpadded_key = unpad_key(&key_bytes).unwrap();
1056 assert_eq!(original_key, unpadded_key);
1057 }
1058
1059 #[test]
1060 fn test_eq_aes_cbc_hmac() {
1061 let key1 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
1062 let key2 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
1063 assert_ne!(key1, key2);
1064 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
1065 assert_eq!(key1, key3);
1066 }
1067
1068 #[test]
1069 fn test_eq_aes_cbc() {
1070 let key1 =
1071 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![1u8; 32])).unwrap();
1072 let key2 =
1073 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![2u8; 32])).unwrap();
1074 assert_ne!(key1, key2);
1075 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
1076 assert_eq!(key1, key3);
1077 }
1078
1079 #[test]
1080 fn test_eq_xchacha20_poly1305() {
1081 let key1 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
1082 let key2 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
1083 assert_ne!(key1, key2);
1084 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
1085 assert_eq!(key1, key3);
1086 }
1087
1088 #[test]
1089 fn test_neq_different_key_types() {
1090 let key1 = SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey {
1091 enc_key: Box::pin(Array::<u8, U32>::default()),
1092 });
1093 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
1094 enc_key: Box::pin(Array::<u8, U32>::default()),
1095 key_id: KeyId::from([0; 16]),
1096 supported_operations: vec![
1097 KeyOperation::Decrypt,
1098 KeyOperation::Encrypt,
1099 KeyOperation::WrapKey,
1100 KeyOperation::UnwrapKey,
1101 ],
1102 });
1103 assert_ne!(key1, key2);
1104 }
1105
1106 #[test]
1107 fn test_eq_variant_aes256_cbc() {
1108 let key1 = Aes256CbcKey {
1109 enc_key: Box::pin(Array::from([1u8; 32])),
1110 };
1111 let key2 = Aes256CbcKey {
1112 enc_key: Box::pin(Array::from([1u8; 32])),
1113 };
1114 let key3 = Aes256CbcKey {
1115 enc_key: Box::pin(Array::from([2u8; 32])),
1116 };
1117 assert_eq!(key1, key2);
1118 assert_ne!(key1, key3);
1119 }
1120
1121 #[test]
1122 fn test_eq_variant_aes256_cbc_hmac() {
1123 let key1 = Aes256CbcHmacKey::new(&[1u8; 32], &[2u8; 32]);
1124 let key2 = Aes256CbcHmacKey::new(&[1u8; 32], &[2u8; 32]);
1125 let key3 = Aes256CbcHmacKey::new(&[3u8; 32], &[4u8; 32]);
1126 assert_eq!(key1, key2);
1127 assert_ne!(key1, key3);
1128 }
1129
1130 #[test]
1131 fn test_eq_variant_xchacha20_poly1305() {
1132 let key1 = XChaCha20Poly1305Key {
1133 enc_key: Box::pin(Array::from([1u8; 32])),
1134 key_id: KeyId::from([0; 16]),
1135 supported_operations: vec![
1136 KeyOperation::Decrypt,
1137 KeyOperation::Encrypt,
1138 KeyOperation::WrapKey,
1139 KeyOperation::UnwrapKey,
1140 ],
1141 };
1142 let key2 = XChaCha20Poly1305Key {
1143 enc_key: Box::pin(Array::from([1u8; 32])),
1144 key_id: KeyId::from([0; 16]),
1145 supported_operations: vec![
1146 KeyOperation::Decrypt,
1147 KeyOperation::Encrypt,
1148 KeyOperation::WrapKey,
1149 KeyOperation::UnwrapKey,
1150 ],
1151 };
1152 let key3 = XChaCha20Poly1305Key {
1153 enc_key: Box::pin(Array::from([2u8; 32])),
1154 key_id: KeyId::from([1; 16]),
1155 supported_operations: vec![
1156 KeyOperation::Decrypt,
1157 KeyOperation::Encrypt,
1158 KeyOperation::WrapKey,
1159 KeyOperation::UnwrapKey,
1160 ],
1161 };
1162 assert_eq!(key1, key2);
1163 assert_ne!(key1, key3);
1164 }
1165
1166 fn fixed_xaes_key_inner() -> XAes256GcmKey {
1167 XAes256GcmKey {
1168 enc_key: Box::pin(Array::from([
1169 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
1170 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
1171 0x1c, 0x1d, 0x1e, 0x1f,
1172 ])),
1173 key_id: KeyId::from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
1174 supported_operations: vec![KeyOperation::Encrypt, KeyOperation::Decrypt],
1175 }
1176 }
1177
1178 fn fixed_xaes_key() -> SymmetricCryptoKey {
1179 SymmetricCryptoKey::XAes256GcmKey(fixed_xaes_key_inner())
1180 }
1181
1182 #[test]
1183 fn test_make_xaes256_gcm_key() {
1184 assert!(matches!(
1185 SymmetricCryptoKey::make(SymmetricKeyAlgorithm::XAes256Gcm),
1186 SymmetricCryptoKey::XAes256GcmKey(_)
1187 ));
1188 }
1189
1190 #[test]
1191 fn test_xaes256_gcm_encoding_roundtrips() {
1192 const PADDED_KEY: &str = "pQEEAlAAAQIDBAUGBwgJCgsMDQ4PAzoAARF5BIIDBCBYIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fAQ==";
1193
1194 let key = fixed_xaes_key();
1195 assert_eq!(key.to_base64().to_string(), PADDED_KEY);
1196 let padded = SymmetricCryptoKey::try_from(PADDED_KEY.to_owned()).unwrap();
1197 assert_eq!(padded, key);
1198 let SymmetricCryptoKey::XAes256GcmKey(ref padded) = padded else {
1199 panic!("expected XAES-256-GCM key");
1200 };
1201 assert_eq!(
1202 padded.supported_operations,
1203 [KeyOperation::Encrypt, KeyOperation::Decrypt]
1204 );
1205
1206 let EncodedSymmetricKey::CoseKey(raw) = key.to_encoded_raw() else {
1207 panic!("expected COSE key encoding");
1208 };
1209 assert_eq!(
1210 SymmetricCryptoKey::try_from_cose(raw.as_ref()).unwrap(),
1211 key
1212 );
1213
1214 let cose_key = coset::CoseKey::from_slice(raw.as_ref()).unwrap();
1215 assert_eq!(
1216 cose_key.alg,
1217 Some(coset::Algorithm::PrivateUse(crate::cose::XAES_256_GCM))
1218 );
1219 assert_eq!(cose_key.key_id, (0u8..16).collect::<Vec<_>>());
1220 assert_eq!(cose_key.key_ops.len(), 2);
1221 assert!(
1222 cose_key
1223 .key_ops
1224 .contains(&coset::RegisteredLabel::Assigned(KeyOperation::Encrypt))
1225 );
1226 assert!(
1227 cose_key
1228 .key_ops
1229 .contains(&coset::RegisteredLabel::Assigned(KeyOperation::Decrypt))
1230 );
1231 }
1232
1233 #[test]
1234 fn test_xaes256_gcm_equality() {
1235 let key = fixed_xaes_key();
1236 let same = fixed_xaes_key();
1237 assert_eq!(key, same);
1238
1239 let mut different_bytes = fixed_xaes_key_inner();
1240 different_bytes.enc_key[0] ^= 1;
1241 assert_ne!(key, SymmetricCryptoKey::XAes256GcmKey(different_bytes));
1242
1243 let mut different_id = fixed_xaes_key_inner();
1244 different_id.key_id = KeyId::from([1; 16]);
1245 assert_ne!(key, SymmetricCryptoKey::XAes256GcmKey(different_id));
1246
1247 for other in [
1248 SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey {
1249 enc_key: Box::pin(Array::default()),
1250 }),
1251 SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey::new(&[0u8; 32], &[0u8; 32])),
1252 SymmetricCryptoKey::Aes256GcmKey(crate::Aes256GcmKey::make()),
1253 SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key::make()),
1254 ] {
1255 assert_ne!(key, other);
1256 }
1257 }
1258
1259 #[test]
1260 fn test_neq_different_key_id() {
1261 let key1 = XChaCha20Poly1305Key {
1262 enc_key: Box::pin(Array::<u8, U32>::default()),
1263 key_id: KeyId::from([0; 16]),
1264 supported_operations: vec![
1265 KeyOperation::Decrypt,
1266 KeyOperation::Encrypt,
1267 KeyOperation::WrapKey,
1268 KeyOperation::UnwrapKey,
1269 ],
1270 };
1271 let key2 = XChaCha20Poly1305Key {
1272 enc_key: Box::pin(Array::<u8, U32>::default()),
1273 key_id: KeyId::from([1; 16]),
1274 supported_operations: vec![
1275 KeyOperation::Decrypt,
1276 KeyOperation::Encrypt,
1277 KeyOperation::WrapKey,
1278 KeyOperation::UnwrapKey,
1279 ],
1280 };
1281 assert_ne!(key1, key2);
1282
1283 let key1 = SymmetricCryptoKey::XChaCha20Poly1305Key(key1);
1284 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(key2);
1285 assert_ne!(key1, key2);
1286 }
1287
1288 const AES256_GCM_KEY: &str =
1289 "pQEEAlACAgICAgICAgICAgICAgICAwMEhAMEBQYgWCABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=";
1290 const AES256_GCM_KEY_THUMBPRINT: &str =
1291 "3810c7275ee292caca13d938a057a94c75210087d960d3eb6868c0ffe99b5643";
1292
1293 const XCHACHA20_POLY1305_KEY: &str = "pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB";
1294 const XCHACHA20_POLY1305_KEY_THUMBPRINT: &str =
1295 "64aec2d09ef5ba8b310ef9a70346b03422443e295b6f045e38169ae97e579d85";
1296
1297 #[test]
1298 fn test_decode_new_aes256_gcm_key() {
1299 let key: B64 = AES256_GCM_KEY.parse().unwrap();
1300 let key = SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(&key)).unwrap();
1301 match key {
1302 SymmetricCryptoKey::Aes256GcmKey(_) => (),
1303 _ => panic!("Invalid key type"),
1304 }
1305 }
1306
1307 #[test]
1308 fn test_thumbprint_aes256_gcm_vector() {
1309 let key: B64 = AES256_GCM_KEY.parse().unwrap();
1311 let key = SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(&key)).unwrap();
1312 assert_eq!(
1313 key.thumbprint().unwrap().to_hex(),
1314 AES256_GCM_KEY_THUMBPRINT
1315 );
1316 }
1317
1318 #[test]
1319 fn test_thumbprint_xchacha20_poly1305_vector() {
1320 let key: B64 = XCHACHA20_POLY1305_KEY.parse().unwrap();
1322 let key = SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(&key)).unwrap();
1323 assert_eq!(
1324 key.thumbprint().unwrap().to_hex(),
1325 XCHACHA20_POLY1305_KEY_THUMBPRINT
1326 );
1327 }
1328
1329 #[test]
1330 fn test_thumbprint_is_deterministic() {
1331 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
1332 assert_eq!(key.thumbprint().unwrap(), key.thumbprint().unwrap());
1333 }
1334
1335 #[test]
1338 fn test_thumbprint_errors_for_unauthenticated_aes_cbc() {
1339 let key = SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey {
1340 enc_key: Box::pin(Array::from([1u8; 32])),
1341 });
1342 assert!(key.thumbprint().is_err());
1343 }
1344
1345 const AES256_CBC_HMAC_KEY_THUMBPRINT: &str =
1346 "ac4ecffb2e087a59180d1dd19950b8e9e634997f47bbfd7a2bb829edf7a9f900";
1347 const AES256_CBC_HMAC_KEY_ID: &str = "ac4ecffb2e087a59180d1dd19950b8e9";
1348
1349 fn fixed_aes_cbc_hmac_key_inner() -> Aes256CbcHmacKey {
1351 Aes256CbcHmacKey::new(
1352 &std::array::from_fn(|i| i as u8),
1353 &std::array::from_fn(|i| (i + 32) as u8),
1354 )
1355 }
1356
1357 fn fixed_aes_cbc_hmac_key() -> SymmetricCryptoKey {
1358 SymmetricCryptoKey::Aes256CbcHmacKey(fixed_aes_cbc_hmac_key_inner())
1359 }
1360
1361 #[test]
1362 #[ignore = "Generates test vectors; run manually"]
1363 fn generate_aes256_cbc_hmac_thumbprint_vectors() {
1364 let key = fixed_aes_cbc_hmac_key();
1365 println!(
1366 "const AES256_CBC_HMAC_KEY_THUMBPRINT: &str = \"{}\";",
1367 key.thumbprint().unwrap().to_hex()
1368 );
1369 println!(
1370 "const AES256_CBC_HMAC_KEY_ID: &str = \"{}\";",
1371 hex::encode(key.key_id().unwrap().as_slice())
1372 );
1373 }
1374
1375 #[test]
1379 fn test_thumbprint_aes256_cbc_hmac_vector() {
1380 assert_eq!(
1381 fixed_aes_cbc_hmac_key().thumbprint().unwrap().to_hex(),
1382 AES256_CBC_HMAC_KEY_THUMBPRINT
1383 );
1384 }
1385
1386 #[test]
1387 fn test_key_id_aes256_cbc_hmac_vector() {
1388 let key_id = fixed_aes_cbc_hmac_key().key_id().unwrap();
1389 assert_eq!(hex::encode(key_id.as_slice()), AES256_CBC_HMAC_KEY_ID);
1390 }
1391
1392 #[test]
1395 fn test_key_id_is_thumbprint_prefix() {
1396 let key = fixed_aes_cbc_hmac_key();
1397 let thumbprint = key.thumbprint().unwrap();
1398 assert_eq!(
1399 key.key_id().unwrap().as_slice(),
1400 &thumbprint.as_bytes()[..KEY_ID_SIZE]
1401 );
1402 }
1403
1404 #[test]
1405 fn test_key_id_aes256_cbc_hmac_is_deterministic() {
1406 assert_eq!(
1407 fixed_aes_cbc_hmac_key().key_id(),
1408 fixed_aes_cbc_hmac_key().key_id()
1409 );
1410 }
1411
1412 #[test]
1415 fn test_key_id_aes256_cbc_hmac_covers_both_halves() {
1416 let key = fixed_aes_cbc_hmac_key();
1417
1418 let mut different_enc = fixed_aes_cbc_hmac_key_inner();
1420 different_enc.key[0] ^= 1;
1421 assert_ne!(
1422 key.key_id(),
1423 SymmetricCryptoKey::Aes256CbcHmacKey(different_enc).key_id()
1424 );
1425
1426 let mut different_mac = fixed_aes_cbc_hmac_key_inner();
1427 different_mac.key[AES256_CBC_HMAC_ENC_KEY_SIZE] ^= 1;
1428 assert_ne!(
1429 key.key_id(),
1430 SymmetricCryptoKey::Aes256CbcHmacKey(different_mac).key_id()
1431 );
1432 }
1433
1434 #[test]
1437 fn test_aes256_cbc_hmac_still_encodes_as_legacy_64_bytes() {
1438 let key = fixed_aes_cbc_hmac_key();
1439
1440 let EncodedSymmetricKey::BitwardenLegacyKey(raw) = key.to_encoded_raw() else {
1441 panic!("expected legacy key encoding");
1442 };
1443 assert_eq!(raw.as_ref().len(), 64);
1444 assert_eq!(raw.as_ref(), (0u8..64).collect::<Vec<_>>());
1445
1446 assert_eq!(key.to_encoded().as_ref().len(), 64);
1447 assert_eq!(SymmetricCryptoKey::try_from(key.to_base64()).unwrap(), key);
1448 }
1449}