1use std::pin::Pin;
2
3use bitwarden_encoding::B64;
4use coset::{CborSerializable, RegisteredLabelWithPrivate, iana::KeyOperation};
5use generic_array::GenericArray;
6use rand::Rng;
7#[cfg(test)]
8use rand::SeedableRng;
9#[cfg(test)]
10use rand_chacha::ChaChaRng;
11#[cfg(test)]
12use sha2::Digest;
13use subtle::{Choice, ConstantTimeEq};
14use typenum::U32;
15use zeroize::{Zeroize, ZeroizeOnDrop};
16
17use super::{
18 key_encryptable::CryptoKey,
19 key_id::{KEY_ID_SIZE, KeyId},
20};
21use crate::{BitwardenLegacyKeyBytes, ContentFormat, CoseKeyBytes, CryptoError, cose};
22
23#[derive(ZeroizeOnDrop, Clone)]
27pub struct Aes256CbcKey {
28 pub(crate) enc_key: Pin<Box<GenericArray<u8, U32>>>,
30}
31
32impl ConstantTimeEq for Aes256CbcKey {
33 fn ct_eq(&self, other: &Self) -> Choice {
34 self.enc_key.ct_eq(&other.enc_key)
35 }
36}
37
38impl PartialEq for Aes256CbcKey {
39 fn eq(&self, other: &Self) -> bool {
40 self.ct_eq(other).into()
41 }
42}
43
44#[derive(ZeroizeOnDrop, Clone)]
47pub struct Aes256CbcHmacKey {
48 pub(crate) enc_key: Pin<Box<GenericArray<u8, U32>>>,
50 pub(crate) mac_key: Pin<Box<GenericArray<u8, U32>>>,
52}
53
54impl ConstantTimeEq for Aes256CbcHmacKey {
55 fn ct_eq(&self, other: &Self) -> Choice {
56 self.enc_key.ct_eq(&other.enc_key) & self.mac_key.ct_eq(&other.mac_key)
57 }
58}
59
60impl PartialEq for Aes256CbcHmacKey {
61 fn eq(&self, other: &Self) -> bool {
62 self.ct_eq(other).into()
63 }
64}
65
66#[derive(Zeroize, Clone)]
71pub struct XChaCha20Poly1305Key {
72 pub(crate) key_id: [u8; KEY_ID_SIZE],
73 pub(crate) enc_key: Pin<Box<GenericArray<u8, U32>>>,
74}
75
76impl ConstantTimeEq for XChaCha20Poly1305Key {
77 fn ct_eq(&self, other: &Self) -> Choice {
78 self.enc_key.ct_eq(&other.enc_key) & self.key_id.ct_eq(&other.key_id)
79 }
80}
81
82impl PartialEq for XChaCha20Poly1305Key {
83 fn eq(&self, other: &Self) -> bool {
84 self.ct_eq(other).into()
85 }
86}
87
88#[derive(ZeroizeOnDrop, Clone)]
90pub enum SymmetricCryptoKey {
91 #[allow(missing_docs)]
92 Aes256CbcKey(Aes256CbcKey),
93 #[allow(missing_docs)]
94 Aes256CbcHmacKey(Aes256CbcHmacKey),
95 XChaCha20Poly1305Key(XChaCha20Poly1305Key),
98}
99
100impl SymmetricCryptoKey {
101 const AES256_CBC_KEY_LEN: usize = 32;
103 const AES256_CBC_HMAC_KEY_LEN: usize = 64;
105
106 pub(crate) fn make_aes256_cbc_hmac_key_internal(
112 mut rng: impl rand::RngCore + rand::CryptoRng,
113 ) -> Self {
114 let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
115 let mut mac_key = Box::pin(GenericArray::<u8, U32>::default());
116
117 rng.fill(enc_key.as_mut_slice());
118 rng.fill(mac_key.as_mut_slice());
119
120 Self::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key })
121 }
122
123 pub fn make_aes256_cbc_hmac_key() -> Self {
125 let rng = rand::thread_rng();
126 Self::make_aes256_cbc_hmac_key_internal(rng)
127 }
128
129 pub fn make_xchacha20_poly1305_key() -> Self {
131 let mut rng = rand::thread_rng();
132 let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
133 rng.fill(enc_key.as_mut_slice());
134 Self::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
135 enc_key,
136 key_id: KeyId::make().into(),
137 })
138 }
139
140 pub fn to_encoded(&self) -> BitwardenLegacyKeyBytes {
149 let encoded_key = self.to_encoded_raw();
150 match encoded_key {
151 EncodedSymmetricKey::BitwardenLegacyKey(_) => {
152 let encoded_key: Vec<u8> = encoded_key.into();
153 BitwardenLegacyKeyBytes::from(encoded_key)
154 }
155 EncodedSymmetricKey::CoseKey(_) => {
156 let mut encoded_key: Vec<u8> = encoded_key.into();
157 pad_key(&mut encoded_key, (Self::AES256_CBC_HMAC_KEY_LEN + 1) as u8); BitwardenLegacyKeyBytes::from(encoded_key)
159 }
160 }
161 }
162
163 #[cfg(test)]
166 pub fn generate_seeded_for_unit_tests(seed: &str) -> Self {
167 let mut seeded_rng = ChaChaRng::from_seed(sha2::Sha256::digest(seed.as_bytes()).into());
169 let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
170 let mut mac_key = Box::pin(GenericArray::<u8, U32>::default());
171
172 seeded_rng.fill(enc_key.as_mut_slice());
173 seeded_rng.fill(mac_key.as_mut_slice());
174
175 SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key })
176 }
177
178 pub(crate) fn to_encoded_raw(&self) -> EncodedSymmetricKey {
190 match self {
191 Self::Aes256CbcKey(key) => {
192 EncodedSymmetricKey::BitwardenLegacyKey(key.enc_key.to_vec().into())
193 }
194 Self::Aes256CbcHmacKey(key) => {
195 let mut buf = Vec::with_capacity(64);
196 buf.extend_from_slice(&key.enc_key);
197 buf.extend_from_slice(&key.mac_key);
198 EncodedSymmetricKey::BitwardenLegacyKey(buf.into())
199 }
200 Self::XChaCha20Poly1305Key(key) => {
201 let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec());
202 let mut cose_key = builder
203 .key_id(key.key_id.to_vec())
204 .add_key_op(KeyOperation::Decrypt)
205 .add_key_op(KeyOperation::Encrypt)
206 .add_key_op(KeyOperation::WrapKey)
207 .add_key_op(KeyOperation::UnwrapKey)
208 .build();
209 cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse(
210 cose::XCHACHA20_POLY1305,
211 ));
212 EncodedSymmetricKey::CoseKey(
213 cose_key
214 .to_vec()
215 .expect("cose key serialization should not fail")
216 .into(),
217 )
218 }
219 }
220 }
221
222 pub(crate) fn try_from_cose(serialized_key: &[u8]) -> Result<Self, CryptoError> {
223 let cose_key =
224 coset::CoseKey::from_slice(serialized_key).map_err(|_| CryptoError::InvalidKey)?;
225 let key = SymmetricCryptoKey::try_from(&cose_key)?;
226 Ok(key)
227 }
228
229 #[allow(missing_docs)]
230 pub fn to_base64(&self) -> B64 {
231 B64::from(self.to_encoded().as_ref())
232 }
233}
234
235impl ConstantTimeEq for SymmetricCryptoKey {
236 fn ct_eq(&self, other: &SymmetricCryptoKey) -> Choice {
240 use SymmetricCryptoKey::*;
241 match (self, other) {
242 (Aes256CbcKey(a), Aes256CbcKey(b)) => a.ct_eq(b),
243 (Aes256CbcKey(_), _) => Choice::from(0),
244
245 (Aes256CbcHmacKey(a), Aes256CbcHmacKey(b)) => a.ct_eq(b),
246 (Aes256CbcHmacKey(_), _) => Choice::from(0),
247
248 (XChaCha20Poly1305Key(a), XChaCha20Poly1305Key(b)) => a.ct_eq(b),
249 (XChaCha20Poly1305Key(_), _) => Choice::from(0),
250 }
251 }
252}
253
254impl PartialEq for SymmetricCryptoKey {
255 fn eq(&self, other: &Self) -> bool {
256 self.ct_eq(other).into()
257 }
258}
259
260impl TryFrom<String> for SymmetricCryptoKey {
261 type Error = CryptoError;
262
263 fn try_from(value: String) -> Result<Self, Self::Error> {
264 let bytes = B64::try_from(value).map_err(|_| CryptoError::InvalidKey)?;
265 Self::try_from(bytes)
266 }
267}
268
269impl TryFrom<B64> for SymmetricCryptoKey {
270 type Error = CryptoError;
271
272 fn try_from(value: B64) -> Result<Self, Self::Error> {
273 Self::try_from(&BitwardenLegacyKeyBytes::from(&value))
274 }
275}
276
277impl TryFrom<&BitwardenLegacyKeyBytes> for SymmetricCryptoKey {
278 type Error = CryptoError;
279
280 fn try_from(value: &BitwardenLegacyKeyBytes) -> Result<Self, Self::Error> {
281 let slice = value.as_ref();
282
283 if slice.len() == Self::AES256_CBC_HMAC_KEY_LEN || slice.len() == Self::AES256_CBC_KEY_LEN {
289 Self::try_from(EncodedSymmetricKey::BitwardenLegacyKey(value.clone()))
290 } else if slice.len() > Self::AES256_CBC_HMAC_KEY_LEN {
291 let unpadded_value = unpad_key(slice)?;
292 Ok(Self::try_from_cose(unpadded_value)?)
293 } else {
294 Err(CryptoError::InvalidKeyLen)
295 }
296 }
297}
298
299impl TryFrom<EncodedSymmetricKey> for SymmetricCryptoKey {
300 type Error = CryptoError;
301
302 fn try_from(value: EncodedSymmetricKey) -> Result<Self, Self::Error> {
303 match value {
304 EncodedSymmetricKey::BitwardenLegacyKey(key)
305 if key.as_ref().len() == Self::AES256_CBC_KEY_LEN =>
306 {
307 let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
308 enc_key.copy_from_slice(&key.as_ref()[..Self::AES256_CBC_KEY_LEN]);
309 Ok(Self::Aes256CbcKey(Aes256CbcKey { enc_key }))
310 }
311 EncodedSymmetricKey::BitwardenLegacyKey(key)
312 if key.as_ref().len() == Self::AES256_CBC_HMAC_KEY_LEN =>
313 {
314 let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
315 enc_key.copy_from_slice(&key.as_ref()[..32]);
316
317 let mut mac_key = Box::pin(GenericArray::<u8, U32>::default());
318 mac_key.copy_from_slice(&key.as_ref()[32..]);
319
320 Ok(Self::Aes256CbcHmacKey(Aes256CbcHmacKey {
321 enc_key,
322 mac_key,
323 }))
324 }
325 EncodedSymmetricKey::CoseKey(key) => Self::try_from_cose(key.as_ref()),
326 _ => Err(CryptoError::InvalidKey),
327 }
328 }
329}
330
331impl CryptoKey for SymmetricCryptoKey {}
332
333impl std::fmt::Debug for SymmetricCryptoKey {
335 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
336 f.debug_struct("SymmetricCryptoKey")
337 .field(
338 "inner_type",
339 match self {
340 SymmetricCryptoKey::Aes256CbcKey(key) => key,
341 SymmetricCryptoKey::Aes256CbcHmacKey(key) => key,
342 SymmetricCryptoKey::XChaCha20Poly1305Key(key) => key,
343 },
344 )
345 .finish()
346 }
347}
348
349impl std::fmt::Debug for Aes256CbcKey {
350 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
351 f.debug_struct("Aes256CbcKey").finish()
352 }
353}
354
355impl std::fmt::Debug for Aes256CbcHmacKey {
356 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
357 f.debug_struct("Aes256CbcHmacKey").finish()
358 }
359}
360
361impl std::fmt::Debug for XChaCha20Poly1305Key {
362 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363 f.debug_struct("XChaCha20Poly1305Key")
364 .field("key_id", &self.key_id)
365 .finish()
366 }
367}
368
369fn pad_key(key_bytes: &mut Vec<u8>, min_length: u8) {
380 crate::keys::utils::pad_bytes(key_bytes, min_length as usize)
381 .expect("Padding cannot fail since the min_length is < 255")
382}
383
384fn unpad_key(key_bytes: &[u8]) -> Result<&[u8], CryptoError> {
395 crate::keys::utils::unpad_bytes(key_bytes).map_err(|_| CryptoError::InvalidKey)
396}
397
398pub enum EncodedSymmetricKey {
400 BitwardenLegacyKey(BitwardenLegacyKeyBytes),
402 CoseKey(CoseKeyBytes),
404}
405impl From<EncodedSymmetricKey> for Vec<u8> {
406 fn from(val: EncodedSymmetricKey) -> Self {
407 match val {
408 EncodedSymmetricKey::BitwardenLegacyKey(key) => key.to_vec(),
409 EncodedSymmetricKey::CoseKey(key) => key.to_vec(),
410 }
411 }
412}
413impl EncodedSymmetricKey {
414 #[allow(private_interfaces)]
416 pub fn content_format(&self) -> ContentFormat {
417 match self {
418 EncodedSymmetricKey::BitwardenLegacyKey(_) => ContentFormat::BitwardenLegacyKey,
419 EncodedSymmetricKey::CoseKey(_) => ContentFormat::CoseKey,
420 }
421 }
422}
423
424#[cfg(test)]
426pub fn derive_symmetric_key(name: &str) -> Aes256CbcHmacKey {
427 use zeroize::Zeroizing;
428
429 use crate::{derive_shareable_key, generate_random_bytes};
430
431 let secret: Zeroizing<[u8; 16]> = generate_random_bytes();
432 derive_shareable_key(secret, name, None)
433}
434
435#[cfg(test)]
436mod tests {
437 use bitwarden_encoding::B64;
438 use generic_array::GenericArray;
439 use typenum::U32;
440
441 use super::{SymmetricCryptoKey, derive_symmetric_key};
442 use crate::{
443 Aes256CbcHmacKey, Aes256CbcKey, BitwardenLegacyKeyBytes, XChaCha20Poly1305Key,
444 keys::symmetric_crypto_key::{pad_key, unpad_key},
445 };
446
447 #[test]
448 fn test_symmetric_crypto_key() {
449 let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
450 let key2 = SymmetricCryptoKey::try_from(key.to_base64()).unwrap();
451
452 assert_eq!(key, key2);
453
454 let key = "UY4B5N4DA4UisCNClgZtRr6VLy9ZF5BXXC7cDZRqourKi4ghEMgISbCsubvgCkHf5DZctQjVot11/vVvN9NNHQ==".to_string();
455 let key2 = SymmetricCryptoKey::try_from(key.clone()).unwrap();
456 assert_eq!(key, key2.to_base64().to_string());
457 }
458
459 #[test]
460 fn test_encode_decode_old_symmetric_crypto_key() {
461 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
462 let encoded = key.to_encoded();
463 let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
464 assert_eq!(key, decoded);
465 }
466
467 #[test]
468 fn test_decode_new_symmetric_crypto_key() {
469 let key: B64 = ("pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB").parse()
470 .unwrap();
471 let key = BitwardenLegacyKeyBytes::from(&key);
472 let key = SymmetricCryptoKey::try_from(&key).unwrap();
473 match key {
474 SymmetricCryptoKey::XChaCha20Poly1305Key(_) => (),
475 _ => panic!("Invalid key type"),
476 }
477 }
478
479 #[test]
480 fn test_encode_xchacha20_poly1305_key() {
481 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
482 let encoded = key.to_encoded();
483 let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
484 assert_eq!(key, decoded);
485 }
486
487 #[test]
488 fn test_pad_unpad_key_63() {
489 let original_key = vec![1u8; 63];
490 let mut key_bytes = original_key.clone();
491 let mut encoded_bytes = vec![1u8; 65];
492 encoded_bytes[63] = 2;
493 encoded_bytes[64] = 2;
494 pad_key(&mut key_bytes, 65);
495 assert_eq!(encoded_bytes, key_bytes);
496 let unpadded_key = unpad_key(&key_bytes).unwrap();
497 assert_eq!(original_key, unpadded_key);
498 }
499
500 #[test]
501 fn test_pad_unpad_key_64() {
502 let original_key = vec![1u8; 64];
503 let mut key_bytes = original_key.clone();
504 let mut encoded_bytes = vec![1u8; 65];
505 encoded_bytes[64] = 1;
506 pad_key(&mut key_bytes, 65);
507 assert_eq!(encoded_bytes, key_bytes);
508 let unpadded_key = unpad_key(&key_bytes).unwrap();
509 assert_eq!(original_key, unpadded_key);
510 }
511
512 #[test]
513 fn test_pad_unpad_key_65() {
514 let original_key = vec![1u8; 65];
515 let mut key_bytes = original_key.clone();
516 let mut encoded_bytes = vec![1u8; 66];
517 encoded_bytes[65] = 1;
518 pad_key(&mut key_bytes, 65);
519 assert_eq!(encoded_bytes, key_bytes);
520 let unpadded_key = unpad_key(&key_bytes).unwrap();
521 assert_eq!(original_key, unpadded_key);
522 }
523
524 #[test]
525 fn test_eq_aes_cbc_hmac() {
526 let key1 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
527 let key2 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
528 assert_ne!(key1, key2);
529 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
530 assert_eq!(key1, key3);
531 }
532
533 #[test]
534 fn test_eq_aes_cbc() {
535 let key1 =
536 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![1u8; 32])).unwrap();
537 let key2 =
538 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![2u8; 32])).unwrap();
539 assert_ne!(key1, key2);
540 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
541 assert_eq!(key1, key3);
542 }
543
544 #[test]
545 fn test_eq_xchacha20_poly1305() {
546 let key1 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
547 let key2 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
548 assert_ne!(key1, key2);
549 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
550 assert_eq!(key1, key3);
551 }
552
553 #[test]
554 fn test_neq_different_key_types() {
555 let key1 = SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey {
556 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
557 });
558 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
559 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
560 key_id: [0; 16],
561 });
562 assert_ne!(key1, key2);
563 }
564
565 #[test]
566 fn test_eq_variant_aes256_cbc() {
567 let key1 = Aes256CbcKey {
568 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
569 vec![1u8; 32].as_slice(),
570 )),
571 };
572 let key2 = Aes256CbcKey {
573 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
574 vec![1u8; 32].as_slice(),
575 )),
576 };
577 let key3 = Aes256CbcKey {
578 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
579 vec![2u8; 32].as_slice(),
580 )),
581 };
582 assert_eq!(key1, key2);
583 assert_ne!(key1, key3);
584 }
585
586 #[test]
587 fn test_eq_variant_aes256_cbc_hmac() {
588 let key1 = Aes256CbcHmacKey {
589 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
590 vec![1u8; 32].as_slice(),
591 )),
592 mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
593 vec![2u8; 32].as_slice(),
594 )),
595 };
596 let key2 = Aes256CbcHmacKey {
597 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
598 vec![1u8; 32].as_slice(),
599 )),
600 mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
601 vec![2u8; 32].as_slice(),
602 )),
603 };
604 let key3 = Aes256CbcHmacKey {
605 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
606 vec![3u8; 32].as_slice(),
607 )),
608 mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
609 vec![4u8; 32].as_slice(),
610 )),
611 };
612 assert_eq!(key1, key2);
613 assert_ne!(key1, key3);
614 }
615
616 #[test]
617 fn test_eq_variant_xchacha20_poly1305() {
618 let key1 = XChaCha20Poly1305Key {
619 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
620 vec![1u8; 32].as_slice(),
621 )),
622 key_id: [0; 16],
623 };
624 let key2 = XChaCha20Poly1305Key {
625 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
626 vec![1u8; 32].as_slice(),
627 )),
628 key_id: [0; 16],
629 };
630 let key3 = XChaCha20Poly1305Key {
631 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
632 vec![2u8; 32].as_slice(),
633 )),
634 key_id: [1; 16],
635 };
636 assert_eq!(key1, key2);
637 assert_ne!(key1, key3);
638 }
639
640 #[test]
641 fn test_neq_different_key_id() {
642 let key1 = XChaCha20Poly1305Key {
643 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
644 key_id: [0; 16],
645 };
646 let key2 = XChaCha20Poly1305Key {
647 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
648 key_id: [1; 16],
649 };
650 assert_ne!(key1, key2);
651
652 let key1 = SymmetricCryptoKey::XChaCha20Poly1305Key(key1);
653 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(key2);
654 assert_ne!(key1, key2);
655 }
656}