1use std::pin::Pin;
2
3use bitwarden_encoding::B64;
4use coset::{iana::KeyOperation, CborSerializable, RegisteredLabelWithPrivate};
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::{KeyId, KEY_ID_SIZE},
20};
21use crate::{cose, BitwardenLegacyKeyBytes, ContentFormat, CoseKeyBytes, CryptoError};
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 let result = if slice.len() == Self::AES256_CBC_HMAC_KEY_LEN
288 || slice.len() == Self::AES256_CBC_KEY_LEN
289 {
290 Self::try_from(EncodedSymmetricKey::BitwardenLegacyKey(value.clone()))
291 } else if slice.len() > Self::AES256_CBC_HMAC_KEY_LEN {
292 let unpadded_value = unpad_key(slice)?;
293 Ok(Self::try_from_cose(unpadded_value)?)
294 } else {
295 Err(CryptoError::InvalidKeyLen)
296 };
297
298 result
299 }
300}
301
302impl TryFrom<EncodedSymmetricKey> for SymmetricCryptoKey {
303 type Error = CryptoError;
304
305 fn try_from(value: EncodedSymmetricKey) -> Result<Self, Self::Error> {
306 match value {
307 EncodedSymmetricKey::BitwardenLegacyKey(key)
308 if key.as_ref().len() == Self::AES256_CBC_KEY_LEN =>
309 {
310 let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
311 enc_key.copy_from_slice(&key.as_ref()[..Self::AES256_CBC_KEY_LEN]);
312 Ok(Self::Aes256CbcKey(Aes256CbcKey { enc_key }))
313 }
314 EncodedSymmetricKey::BitwardenLegacyKey(key)
315 if key.as_ref().len() == Self::AES256_CBC_HMAC_KEY_LEN =>
316 {
317 let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
318 enc_key.copy_from_slice(&key.as_ref()[..32]);
319
320 let mut mac_key = Box::pin(GenericArray::<u8, U32>::default());
321 mac_key.copy_from_slice(&key.as_ref()[32..]);
322
323 Ok(Self::Aes256CbcHmacKey(Aes256CbcHmacKey {
324 enc_key,
325 mac_key,
326 }))
327 }
328 EncodedSymmetricKey::CoseKey(key) => Self::try_from_cose(key.as_ref()),
329 _ => Err(CryptoError::InvalidKey),
330 }
331 }
332}
333
334impl CryptoKey for SymmetricCryptoKey {}
335
336impl std::fmt::Debug for SymmetricCryptoKey {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339 f.debug_struct("SymmetricCryptoKey")
340 .field(
341 "inner_type",
342 match self {
343 SymmetricCryptoKey::Aes256CbcKey(key) => key,
344 SymmetricCryptoKey::Aes256CbcHmacKey(key) => key,
345 SymmetricCryptoKey::XChaCha20Poly1305Key(key) => key,
346 },
347 )
348 .finish()
349 }
350}
351
352impl std::fmt::Debug for Aes256CbcKey {
353 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
354 f.debug_struct("Aes256CbcKey").finish()
355 }
356}
357
358impl std::fmt::Debug for Aes256CbcHmacKey {
359 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
360 f.debug_struct("Aes256CbcHmacKey").finish()
361 }
362}
363
364impl std::fmt::Debug for XChaCha20Poly1305Key {
365 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366 f.debug_struct("XChaCha20Poly1305Key")
367 .field("key_id", &self.key_id)
368 .finish()
369 }
370}
371
372fn pad_key(key_bytes: &mut Vec<u8>, min_length: u8) {
383 crate::keys::utils::pad_bytes(key_bytes, min_length as usize)
384 .expect("Padding cannot fail since the min_length is < 255")
385}
386
387fn unpad_key(key_bytes: &[u8]) -> Result<&[u8], CryptoError> {
398 crate::keys::utils::unpad_bytes(key_bytes).map_err(|_| CryptoError::InvalidKey)
399}
400
401pub enum EncodedSymmetricKey {
403 BitwardenLegacyKey(BitwardenLegacyKeyBytes),
405 CoseKey(CoseKeyBytes),
407}
408impl From<EncodedSymmetricKey> for Vec<u8> {
409 fn from(val: EncodedSymmetricKey) -> Self {
410 match val {
411 EncodedSymmetricKey::BitwardenLegacyKey(key) => key.to_vec(),
412 EncodedSymmetricKey::CoseKey(key) => key.to_vec(),
413 }
414 }
415}
416impl EncodedSymmetricKey {
417 #[allow(private_interfaces)]
419 pub fn content_format(&self) -> ContentFormat {
420 match self {
421 EncodedSymmetricKey::BitwardenLegacyKey(_) => ContentFormat::BitwardenLegacyKey,
422 EncodedSymmetricKey::CoseKey(_) => ContentFormat::CoseKey,
423 }
424 }
425}
426
427#[cfg(test)]
429pub fn derive_symmetric_key(name: &str) -> Aes256CbcHmacKey {
430 use zeroize::Zeroizing;
431
432 use crate::{derive_shareable_key, generate_random_bytes};
433
434 let secret: Zeroizing<[u8; 16]> = generate_random_bytes();
435 derive_shareable_key(secret, name, None)
436}
437
438#[cfg(test)]
439mod tests {
440 use bitwarden_encoding::B64;
441 use generic_array::GenericArray;
442 use typenum::U32;
443
444 use super::{derive_symmetric_key, SymmetricCryptoKey};
445 use crate::{
446 keys::symmetric_crypto_key::{pad_key, unpad_key},
447 Aes256CbcHmacKey, Aes256CbcKey, BitwardenLegacyKeyBytes, XChaCha20Poly1305Key,
448 };
449
450 #[test]
451 fn test_symmetric_crypto_key() {
452 let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
453 let key2 = SymmetricCryptoKey::try_from(key.to_base64()).unwrap();
454
455 assert_eq!(key, key2);
456
457 let key = "UY4B5N4DA4UisCNClgZtRr6VLy9ZF5BXXC7cDZRqourKi4ghEMgISbCsubvgCkHf5DZctQjVot11/vVvN9NNHQ==".to_string();
458 let key2 = SymmetricCryptoKey::try_from(key.clone()).unwrap();
459 assert_eq!(key, key2.to_base64().to_string());
460 }
461
462 #[test]
463 fn test_encode_decode_old_symmetric_crypto_key() {
464 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
465 let encoded = key.to_encoded();
466 let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
467 assert_eq!(key, decoded);
468 }
469
470 #[test]
471 fn test_decode_new_symmetric_crypto_key() {
472 let key: B64 = ("pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB").parse()
473 .unwrap();
474 let key = BitwardenLegacyKeyBytes::from(&key);
475 let key = SymmetricCryptoKey::try_from(&key).unwrap();
476 match key {
477 SymmetricCryptoKey::XChaCha20Poly1305Key(_) => (),
478 _ => panic!("Invalid key type"),
479 }
480 }
481
482 #[test]
483 fn test_encode_xchacha20_poly1305_key() {
484 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
485 let encoded = key.to_encoded();
486 let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
487 assert_eq!(key, decoded);
488 }
489
490 #[test]
491 fn test_pad_unpad_key_63() {
492 let original_key = vec![1u8; 63];
493 let mut key_bytes = original_key.clone();
494 let mut encoded_bytes = vec![1u8; 65];
495 encoded_bytes[63] = 2;
496 encoded_bytes[64] = 2;
497 pad_key(&mut key_bytes, 65);
498 assert_eq!(encoded_bytes, key_bytes);
499 let unpadded_key = unpad_key(&key_bytes).unwrap();
500 assert_eq!(original_key, unpadded_key);
501 }
502
503 #[test]
504 fn test_pad_unpad_key_64() {
505 let original_key = vec![1u8; 64];
506 let mut key_bytes = original_key.clone();
507 let mut encoded_bytes = vec![1u8; 65];
508 encoded_bytes[64] = 1;
509 pad_key(&mut key_bytes, 65);
510 assert_eq!(encoded_bytes, key_bytes);
511 let unpadded_key = unpad_key(&key_bytes).unwrap();
512 assert_eq!(original_key, unpadded_key);
513 }
514
515 #[test]
516 fn test_pad_unpad_key_65() {
517 let original_key = vec![1u8; 65];
518 let mut key_bytes = original_key.clone();
519 let mut encoded_bytes = vec![1u8; 66];
520 encoded_bytes[65] = 1;
521 pad_key(&mut key_bytes, 65);
522 assert_eq!(encoded_bytes, key_bytes);
523 let unpadded_key = unpad_key(&key_bytes).unwrap();
524 assert_eq!(original_key, unpadded_key);
525 }
526
527 #[test]
528 fn test_eq_aes_cbc_hmac() {
529 let key1 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
530 let key2 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
531 assert_ne!(key1, key2);
532 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
533 assert_eq!(key1, key3);
534 }
535
536 #[test]
537 fn test_eq_aes_cbc() {
538 let key1 =
539 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![1u8; 32])).unwrap();
540 let key2 =
541 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![2u8; 32])).unwrap();
542 assert_ne!(key1, key2);
543 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
544 assert_eq!(key1, key3);
545 }
546
547 #[test]
548 fn test_eq_xchacha20_poly1305() {
549 let key1 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
550 let key2 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
551 assert_ne!(key1, key2);
552 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
553 assert_eq!(key1, key3);
554 }
555
556 #[test]
557 fn test_neq_different_key_types() {
558 let key1 = SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey {
559 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
560 });
561 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
562 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
563 key_id: [0; 16],
564 });
565 assert_ne!(key1, key2);
566 }
567
568 #[test]
569 fn test_eq_variant_aes256_cbc() {
570 let key1 = Aes256CbcKey {
571 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
572 vec![1u8; 32].as_slice(),
573 )),
574 };
575 let key2 = Aes256CbcKey {
576 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
577 vec![1u8; 32].as_slice(),
578 )),
579 };
580 let key3 = Aes256CbcKey {
581 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
582 vec![2u8; 32].as_slice(),
583 )),
584 };
585 assert_eq!(key1, key2);
586 assert_ne!(key1, key3);
587 }
588
589 #[test]
590 fn test_eq_variant_aes256_cbc_hmac() {
591 let key1 = Aes256CbcHmacKey {
592 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
593 vec![1u8; 32].as_slice(),
594 )),
595 mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
596 vec![2u8; 32].as_slice(),
597 )),
598 };
599 let key2 = Aes256CbcHmacKey {
600 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
601 vec![1u8; 32].as_slice(),
602 )),
603 mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
604 vec![2u8; 32].as_slice(),
605 )),
606 };
607 let key3 = Aes256CbcHmacKey {
608 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
609 vec![3u8; 32].as_slice(),
610 )),
611 mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
612 vec![4u8; 32].as_slice(),
613 )),
614 };
615 assert_eq!(key1, key2);
616 assert_ne!(key1, key3);
617 }
618
619 #[test]
620 fn test_eq_variant_xchacha20_poly1305() {
621 let key1 = XChaCha20Poly1305Key {
622 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
623 vec![1u8; 32].as_slice(),
624 )),
625 key_id: [0; 16],
626 };
627 let key2 = XChaCha20Poly1305Key {
628 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
629 vec![1u8; 32].as_slice(),
630 )),
631 key_id: [0; 16],
632 };
633 let key3 = XChaCha20Poly1305Key {
634 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
635 vec![2u8; 32].as_slice(),
636 )),
637 key_id: [1; 16],
638 };
639 assert_eq!(key1, key2);
640 assert_ne!(key1, key3);
641 }
642
643 #[test]
644 fn test_neq_different_key_id() {
645 let key1 = XChaCha20Poly1305Key {
646 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
647 key_id: [0; 16],
648 };
649 let key2 = XChaCha20Poly1305Key {
650 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
651 key_id: [1; 16],
652 };
653 assert_ne!(key1, key2);
654
655 let key1 = SymmetricCryptoKey::XChaCha20Poly1305Key(key1);
656 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(key2);
657 assert_ne!(key1, key2);
658 }
659}