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)]
412 pub fn content_format(&self) -> ContentFormat {
413 match self {
414 EncodedSymmetricKey::BitwardenLegacyKey(_) => ContentFormat::BitwardenLegacyKey,
415 EncodedSymmetricKey::CoseKey(_) => ContentFormat::CoseKey,
416 }
417 }
418}
419
420#[cfg(test)]
422pub fn derive_symmetric_key(name: &str) -> Aes256CbcHmacKey {
423 use zeroize::Zeroizing;
424
425 use crate::{derive_shareable_key, generate_random_bytes};
426
427 let secret: Zeroizing<[u8; 16]> = generate_random_bytes();
428 derive_shareable_key(secret, name, None)
429}
430
431#[cfg(test)]
432mod tests {
433 use base64::{engine::general_purpose::STANDARD, Engine};
434 use generic_array::GenericArray;
435 use typenum::U32;
436
437 use super::{derive_symmetric_key, SymmetricCryptoKey};
438 use crate::{
439 keys::symmetric_crypto_key::{pad_key, unpad_key},
440 Aes256CbcHmacKey, Aes256CbcKey, BitwardenLegacyKeyBytes, XChaCha20Poly1305Key,
441 };
442
443 #[test]
444 fn test_symmetric_crypto_key() {
445 let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
446 let key2 = SymmetricCryptoKey::try_from(key.to_base64()).unwrap();
447
448 assert_eq!(key, key2);
449
450 let key = "UY4B5N4DA4UisCNClgZtRr6VLy9ZF5BXXC7cDZRqourKi4ghEMgISbCsubvgCkHf5DZctQjVot11/vVvN9NNHQ==".to_string();
451 let key2 = SymmetricCryptoKey::try_from(key.clone()).unwrap();
452 assert_eq!(key, key2.to_base64());
453 }
454
455 #[test]
456 fn test_encode_decode_old_symmetric_crypto_key() {
457 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
458 let encoded = key.to_encoded();
459 let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
460 assert_eq!(key, decoded);
461 }
462
463 #[test]
464 fn test_decode_new_symmetric_crypto_key() {
465 let key = STANDARD.decode("pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB").unwrap();
466 let key = BitwardenLegacyKeyBytes::from(key);
467 let key = SymmetricCryptoKey::try_from(&key).unwrap();
468 match key {
469 SymmetricCryptoKey::XChaCha20Poly1305Key(_) => (),
470 _ => panic!("Invalid key type"),
471 }
472 }
473
474 #[test]
475 fn test_encode_xchacha20_poly1305_key() {
476 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
477 let encoded = key.to_encoded();
478 let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
479 assert_eq!(key, decoded);
480 }
481
482 #[test]
483 fn test_pad_unpad_key_63() {
484 let original_key = vec![1u8; 63];
485 let mut key_bytes = original_key.clone();
486 let mut encoded_bytes = vec![1u8; 65];
487 encoded_bytes[63] = 2;
488 encoded_bytes[64] = 2;
489 pad_key(&mut key_bytes, 65);
490 assert_eq!(encoded_bytes, key_bytes);
491 let unpadded_key = unpad_key(&key_bytes).unwrap();
492 assert_eq!(original_key, unpadded_key);
493 }
494
495 #[test]
496 fn test_pad_unpad_key_64() {
497 let original_key = vec![1u8; 64];
498 let mut key_bytes = original_key.clone();
499 let mut encoded_bytes = vec![1u8; 65];
500 encoded_bytes[64] = 1;
501 pad_key(&mut key_bytes, 65);
502 assert_eq!(encoded_bytes, key_bytes);
503 let unpadded_key = unpad_key(&key_bytes).unwrap();
504 assert_eq!(original_key, unpadded_key);
505 }
506
507 #[test]
508 fn test_pad_unpad_key_65() {
509 let original_key = vec![1u8; 65];
510 let mut key_bytes = original_key.clone();
511 let mut encoded_bytes = vec![1u8; 66];
512 encoded_bytes[65] = 1;
513 pad_key(&mut key_bytes, 65);
514 assert_eq!(encoded_bytes, key_bytes);
515 let unpadded_key = unpad_key(&key_bytes).unwrap();
516 assert_eq!(original_key, unpadded_key);
517 }
518
519 #[test]
520 fn test_eq_aes_cbc_hmac() {
521 let key1 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
522 let key2 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
523 assert_ne!(key1, key2);
524 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
525 assert_eq!(key1, key3);
526 }
527
528 #[test]
529 fn test_eq_aes_cbc() {
530 let key1 =
531 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![1u8; 32])).unwrap();
532 let key2 =
533 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![2u8; 32])).unwrap();
534 assert_ne!(key1, key2);
535 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
536 assert_eq!(key1, key3);
537 }
538
539 #[test]
540 fn test_eq_xchacha20_poly1305() {
541 let key1 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
542 let key2 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
543 assert_ne!(key1, key2);
544 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
545 assert_eq!(key1, key3);
546 }
547
548 #[test]
549 fn test_neq_different_key_types() {
550 let key1 = SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey {
551 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
552 });
553 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
554 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
555 key_id: [0; 16],
556 });
557 assert_ne!(key1, key2);
558 }
559
560 #[test]
561 fn test_eq_variant_aes256_cbc() {
562 let key1 = Aes256CbcKey {
563 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
564 vec![1u8; 32].as_slice(),
565 )),
566 };
567 let key2 = Aes256CbcKey {
568 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
569 vec![1u8; 32].as_slice(),
570 )),
571 };
572 let key3 = Aes256CbcKey {
573 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
574 vec![2u8; 32].as_slice(),
575 )),
576 };
577 assert_eq!(key1, key2);
578 assert_ne!(key1, key3);
579 }
580
581 #[test]
582 fn test_eq_variant_aes256_cbc_hmac() {
583 let key1 = Aes256CbcHmacKey {
584 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
585 vec![1u8; 32].as_slice(),
586 )),
587 mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
588 vec![2u8; 32].as_slice(),
589 )),
590 };
591 let key2 = 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 key3 = Aes256CbcHmacKey {
600 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
601 vec![3u8; 32].as_slice(),
602 )),
603 mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
604 vec![4u8; 32].as_slice(),
605 )),
606 };
607 assert_eq!(key1, key2);
608 assert_ne!(key1, key3);
609 }
610
611 #[test]
612 fn test_eq_variant_xchacha20_poly1305() {
613 let key1 = XChaCha20Poly1305Key {
614 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
615 vec![1u8; 32].as_slice(),
616 )),
617 key_id: [0; 16],
618 };
619 let key2 = XChaCha20Poly1305Key {
620 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
621 vec![1u8; 32].as_slice(),
622 )),
623 key_id: [0; 16],
624 };
625 let key3 = XChaCha20Poly1305Key {
626 enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
627 vec![2u8; 32].as_slice(),
628 )),
629 key_id: [1; 16],
630 };
631 assert_eq!(key1, key2);
632 assert_ne!(key1, key3);
633 }
634
635 #[test]
636 fn test_neq_different_key_id() {
637 let key1 = XChaCha20Poly1305Key {
638 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
639 key_id: [0; 16],
640 };
641 let key2 = XChaCha20Poly1305Key {
642 enc_key: Box::pin(GenericArray::<u8, U32>::default()),
643 key_id: [1; 16],
644 };
645 assert_ne!(key1, key2);
646
647 let key1 = SymmetricCryptoKey::XChaCha20Poly1305Key(key1);
648 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(key2);
649 assert_ne!(key1, key2);
650 }
651}