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