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