1use std::pin::Pin;
2
3use bitwarden_encoding::B64;
4use coset::{CborSerializable, RegisteredLabelWithPrivate, iana::KeyOperation};
5use hybrid_array::Array;
6use rand::RngExt;
7#[cfg(test)]
8use rand::SeedableRng;
9#[cfg(test)]
10use rand_chacha::ChaChaRng;
11use serde::{Deserialize, Serialize};
12#[cfg(test)]
13use sha2::Digest;
14use subtle::{Choice, ConstantTimeEq};
15use typenum::U32;
16#[cfg(feature = "wasm")]
17use wasm_bindgen::convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi};
18use zeroize::{Zeroize, ZeroizeOnDrop};
19
20use super::{key_encryptable::CryptoKey, key_id::KeyId};
21use crate::{BitwardenLegacyKeyBytes, ContentFormat, CoseKeyBytes, CryptoError, cose};
22
23#[cfg(feature = "wasm")]
24#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
25const TS_CUSTOM_TYPES: &'static str = r#"
26export type SymmetricKey = Tagged<string, "SymmetricKey">;
27"#;
28
29#[cfg(feature = "wasm")]
30impl wasm_bindgen::describe::WasmDescribe for SymmetricCryptoKey {
31 fn describe() {
32 <String as wasm_bindgen::describe::WasmDescribe>::describe();
33 }
34}
35
36#[cfg(feature = "wasm")]
37impl FromWasmAbi for SymmetricCryptoKey {
38 type Abi = <String as FromWasmAbi>::Abi;
39
40 unsafe fn from_abi(abi: Self::Abi) -> Self {
41 use wasm_bindgen::UnwrapThrowExt;
42 let string = unsafe { String::from_abi(abi) };
43 let b64 = B64::try_from(string).unwrap_throw();
44 SymmetricCryptoKey::try_from(b64).unwrap_throw()
45 }
46}
47
48#[cfg(feature = "wasm")]
49impl OptionFromWasmAbi for SymmetricCryptoKey {
50 fn is_none(abi: &Self::Abi) -> bool {
51 <String as OptionFromWasmAbi>::is_none(abi)
52 }
53}
54
55#[cfg(feature = "wasm")]
56impl IntoWasmAbi for SymmetricCryptoKey {
57 type Abi = <String as IntoWasmAbi>::Abi;
58
59 fn into_abi(self) -> Self::Abi {
60 let string: String = self.to_base64().to_string();
61 string.into_abi()
62 }
63}
64
65#[derive(Debug, PartialEq)]
67pub enum SymmetricKeyAlgorithm {
68 Aes256CbcHmac,
70 XChaCha20Poly1305,
72}
73
74#[derive(ZeroizeOnDrop, Clone)]
78pub struct Aes256CbcKey {
79 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
81}
82
83impl ConstantTimeEq for Aes256CbcKey {
84 fn ct_eq(&self, other: &Self) -> Choice {
85 self.enc_key.ct_eq(&other.enc_key)
86 }
87}
88
89impl PartialEq for Aes256CbcKey {
90 fn eq(&self, other: &Self) -> bool {
91 self.ct_eq(other).into()
92 }
93}
94
95#[derive(ZeroizeOnDrop, Clone)]
98pub struct Aes256CbcHmacKey {
99 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
101 pub(crate) mac_key: Pin<Box<Array<u8, U32>>>,
103}
104
105impl ConstantTimeEq for Aes256CbcHmacKey {
106 fn ct_eq(&self, other: &Self) -> Choice {
107 self.enc_key.ct_eq(&other.enc_key) & self.mac_key.ct_eq(&other.mac_key)
108 }
109}
110
111impl PartialEq for Aes256CbcHmacKey {
112 fn eq(&self, other: &Self) -> bool {
113 self.ct_eq(other).into()
114 }
115}
116
117#[derive(Zeroize, Clone)]
122pub struct XChaCha20Poly1305Key {
123 pub(crate) key_id: KeyId,
124 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
125 #[zeroize(skip)]
130 pub(crate) supported_operations: Vec<KeyOperation>,
131}
132
133impl XChaCha20Poly1305Key {
134 pub fn make() -> Self {
136 let mut rng = rand::rng();
137 let mut enc_key = Box::pin(Array::<u8, U32>::default());
138 rng.fill(enc_key.as_mut_slice());
139 let key_id = KeyId::make();
140
141 Self {
142 enc_key,
143 key_id,
144 supported_operations: vec![
145 KeyOperation::Decrypt,
146 KeyOperation::Encrypt,
147 KeyOperation::WrapKey,
148 KeyOperation::UnwrapKey,
149 ],
150 }
151 }
152
153 pub(crate) fn disable_key_operation(&mut self, op: KeyOperation) -> &mut Self {
154 self.supported_operations.retain(|k| *k != op);
155 self
156 }
157}
158
159impl ConstantTimeEq for XChaCha20Poly1305Key {
160 fn ct_eq(&self, other: &Self) -> Choice {
161 self.enc_key.ct_eq(&other.enc_key) & self.key_id.ct_eq(&other.key_id)
162 }
163}
164
165impl PartialEq for XChaCha20Poly1305Key {
166 fn eq(&self, other: &Self) -> bool {
167 self.ct_eq(other).into()
168 }
169}
170
171#[derive(ZeroizeOnDrop, Clone)]
173pub enum SymmetricCryptoKey {
174 #[allow(missing_docs)]
175 Aes256CbcKey(Aes256CbcKey),
176 #[allow(missing_docs)]
177 Aes256CbcHmacKey(Aes256CbcHmacKey),
178 XChaCha20Poly1305Key(XChaCha20Poly1305Key),
181}
182
183impl SymmetricCryptoKey {
184 const AES256_CBC_KEY_LEN: usize = 32;
186 const AES256_CBC_HMAC_KEY_LEN: usize = 64;
188
189 pub(crate) fn make_aes256_cbc_hmac_key_internal(mut rng: impl rand::CryptoRng) -> Self {
195 let mut enc_key = Box::pin(Array::<u8, U32>::default());
196 let mut mac_key = Box::pin(Array::<u8, U32>::default());
197
198 rng.fill(enc_key.as_mut_slice());
199 rng.fill(mac_key.as_mut_slice());
200
201 Self::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key })
202 }
203
204 pub fn make(algorithm: SymmetricKeyAlgorithm) -> Self {
206 match algorithm {
207 SymmetricKeyAlgorithm::Aes256CbcHmac => Self::make_aes256_cbc_hmac_key(),
208 SymmetricKeyAlgorithm::XChaCha20Poly1305 => Self::make_xchacha20_poly1305_key(),
209 }
210 }
211
212 pub fn make_aes256_cbc_hmac_key() -> Self {
214 let rng = rand::rng();
215 Self::make_aes256_cbc_hmac_key_internal(rng)
216 }
217
218 pub fn make_xchacha20_poly1305_key() -> Self {
220 let mut rng = rand::rng();
221 let mut enc_key = Box::pin(Array::<u8, U32>::default());
222 rng.fill(enc_key.as_mut_slice());
223 Self::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
224 enc_key,
225 key_id: KeyId::make(),
226 supported_operations: vec![
227 KeyOperation::Decrypt,
228 KeyOperation::Encrypt,
229 KeyOperation::WrapKey,
230 KeyOperation::UnwrapKey,
231 ],
232 })
233 }
234
235 pub fn to_encoded(&self) -> BitwardenLegacyKeyBytes {
244 let encoded_key = self.to_encoded_raw();
245 match encoded_key {
246 EncodedSymmetricKey::BitwardenLegacyKey(_) => {
247 let encoded_key: Vec<u8> = encoded_key.into();
248 BitwardenLegacyKeyBytes::from(encoded_key)
249 }
250 EncodedSymmetricKey::CoseKey(_) => {
251 let mut encoded_key: Vec<u8> = encoded_key.into();
252 pad_key(&mut encoded_key, (Self::AES256_CBC_HMAC_KEY_LEN + 1) as u8); BitwardenLegacyKeyBytes::from(encoded_key)
254 }
255 }
256 }
257
258 #[cfg(test)]
261 pub fn generate_seeded_for_unit_tests(seed: &str) -> Self {
262 let mut seeded_rng = ChaChaRng::from_seed(sha2::Sha256::digest(seed.as_bytes()).into());
264 let mut enc_key = Box::pin(Array::<u8, U32>::default());
265 let mut mac_key = Box::pin(Array::<u8, U32>::default());
266
267 seeded_rng.fill(enc_key.as_mut_slice());
268 seeded_rng.fill(mac_key.as_mut_slice());
269
270 SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key })
271 }
272
273 pub(crate) fn to_encoded_raw(&self) -> EncodedSymmetricKey {
285 match self {
286 Self::Aes256CbcKey(key) => {
287 EncodedSymmetricKey::BitwardenLegacyKey(key.enc_key.to_vec().into())
288 }
289 Self::Aes256CbcHmacKey(key) => {
290 let mut buf = Vec::with_capacity(64);
291 buf.extend_from_slice(&key.enc_key);
292 buf.extend_from_slice(&key.mac_key);
293 EncodedSymmetricKey::BitwardenLegacyKey(buf.into())
294 }
295 Self::XChaCha20Poly1305Key(key) => {
296 let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec());
297 let mut cose_key = builder.key_id((&key.key_id).into());
298 for op in &key.supported_operations {
299 cose_key = cose_key.add_key_op(*op);
300 }
301 let mut cose_key = cose_key.build();
302 cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse(
303 cose::XCHACHA20_POLY1305,
304 ));
305 EncodedSymmetricKey::CoseKey(
306 cose_key
307 .to_vec()
308 .expect("cose key serialization should not fail")
309 .into(),
310 )
311 }
312 }
313 }
314
315 pub(crate) fn try_from_cose(serialized_key: &[u8]) -> Result<Self, CryptoError> {
316 let cose_key =
317 coset::CoseKey::from_slice(serialized_key).map_err(|_| CryptoError::InvalidKey)?;
318 let key = SymmetricCryptoKey::try_from(&cose_key)?;
319 Ok(key)
320 }
321
322 #[allow(missing_docs)]
323 pub fn to_base64(&self) -> B64 {
324 B64::from(self.to_encoded().as_ref())
325 }
326
327 pub fn key_id(&self) -> Option<KeyId> {
330 match self {
331 Self::Aes256CbcKey(_) => None,
332 Self::Aes256CbcHmacKey(_) => None,
333 Self::XChaCha20Poly1305Key(key) => Some(key.key_id.clone()),
334 }
335 }
336}
337
338impl ConstantTimeEq for SymmetricCryptoKey {
339 fn ct_eq(&self, other: &SymmetricCryptoKey) -> Choice {
343 use SymmetricCryptoKey::*;
344 match (self, other) {
345 (Aes256CbcKey(a), Aes256CbcKey(b)) => a.ct_eq(b),
346 (Aes256CbcKey(_), _) => Choice::from(0),
347
348 (Aes256CbcHmacKey(a), Aes256CbcHmacKey(b)) => a.ct_eq(b),
349 (Aes256CbcHmacKey(_), _) => Choice::from(0),
350
351 (XChaCha20Poly1305Key(a), XChaCha20Poly1305Key(b)) => a.ct_eq(b),
352 (XChaCha20Poly1305Key(_), _) => Choice::from(0),
353 }
354 }
355}
356
357impl PartialEq for SymmetricCryptoKey {
358 fn eq(&self, other: &Self) -> bool {
359 self.ct_eq(other).into()
360 }
361}
362
363impl TryFrom<String> for SymmetricCryptoKey {
364 type Error = CryptoError;
365
366 fn try_from(value: String) -> Result<Self, Self::Error> {
367 let bytes = B64::try_from(value).map_err(|_| CryptoError::InvalidKey)?;
368 Self::try_from(bytes)
369 }
370}
371
372impl TryFrom<B64> for SymmetricCryptoKey {
373 type Error = CryptoError;
374
375 fn try_from(value: B64) -> Result<Self, Self::Error> {
376 Self::try_from(&BitwardenLegacyKeyBytes::from(&value))
377 }
378}
379
380impl TryFrom<&BitwardenLegacyKeyBytes> for SymmetricCryptoKey {
381 type Error = CryptoError;
382
383 fn try_from(value: &BitwardenLegacyKeyBytes) -> Result<Self, Self::Error> {
384 let slice = value.as_ref();
385
386 if slice.len() == Self::AES256_CBC_HMAC_KEY_LEN || slice.len() == Self::AES256_CBC_KEY_LEN {
392 Self::try_from(EncodedSymmetricKey::BitwardenLegacyKey(value.clone()))
393 } else if slice.len() > Self::AES256_CBC_HMAC_KEY_LEN {
394 let unpadded_value = unpad_key(slice)?;
395 Ok(Self::try_from_cose(unpadded_value)?)
396 } else {
397 Err(CryptoError::InvalidKeyLen)
398 }
399 }
400}
401
402impl TryFrom<EncodedSymmetricKey> for SymmetricCryptoKey {
403 type Error = CryptoError;
404
405 fn try_from(value: EncodedSymmetricKey) -> Result<Self, Self::Error> {
406 match value {
407 EncodedSymmetricKey::BitwardenLegacyKey(key)
408 if key.as_ref().len() == Self::AES256_CBC_KEY_LEN =>
409 {
410 let mut enc_key = Box::pin(Array::<u8, U32>::default());
411 enc_key.copy_from_slice(&key.as_ref()[..Self::AES256_CBC_KEY_LEN]);
412 Ok(Self::Aes256CbcKey(Aes256CbcKey { enc_key }))
413 }
414 EncodedSymmetricKey::BitwardenLegacyKey(key)
415 if key.as_ref().len() == Self::AES256_CBC_HMAC_KEY_LEN =>
416 {
417 let mut enc_key = Box::pin(Array::<u8, U32>::default());
418 enc_key.copy_from_slice(&key.as_ref()[..32]);
419
420 let mut mac_key = Box::pin(Array::<u8, U32>::default());
421 mac_key.copy_from_slice(&key.as_ref()[32..]);
422
423 Ok(Self::Aes256CbcHmacKey(Aes256CbcHmacKey {
424 enc_key,
425 mac_key,
426 }))
427 }
428 EncodedSymmetricKey::CoseKey(key) => Self::try_from_cose(key.as_ref()),
429 _ => Err(CryptoError::InvalidKey),
430 }
431 }
432}
433
434impl CryptoKey for SymmetricCryptoKey {}
435
436impl std::fmt::Debug for SymmetricCryptoKey {
438 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439 match self {
440 SymmetricCryptoKey::Aes256CbcKey(key) => key.fmt(f),
441 SymmetricCryptoKey::Aes256CbcHmacKey(key) => key.fmt(f),
442 SymmetricCryptoKey::XChaCha20Poly1305Key(key) => key.fmt(f),
443 }
444 }
445}
446
447impl std::fmt::Debug for Aes256CbcKey {
448 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
449 let mut debug_struct = f.debug_struct("SymmetricKey::Aes256Cbc");
450 #[cfg(feature = "dangerous-crypto-debug")]
451 debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
452 debug_struct.finish()
453 }
454}
455
456impl std::fmt::Debug for Aes256CbcHmacKey {
457 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
458 let mut debug_struct = f.debug_struct("SymmetricKey::Aes256CbcHmac");
459 #[cfg(feature = "dangerous-crypto-debug")]
460 debug_struct
461 .field("enc_key", &hex::encode(self.enc_key.as_slice()))
462 .field("mac_key", &hex::encode(self.mac_key.as_slice()));
463 debug_struct.finish()
464 }
465}
466
467impl std::fmt::Debug for XChaCha20Poly1305Key {
468 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
469 let mut debug_struct = f.debug_struct("SymmetricKey::XChaCha20Poly1305");
470 debug_struct.field("key_id", &self.key_id);
471 debug_struct.field(
472 "supported_operations",
473 &self
474 .supported_operations
475 .iter()
476 .map(|key_operation: &KeyOperation| cose::debug_key_operation(*key_operation))
477 .collect::<Vec<_>>(),
478 );
479 #[cfg(feature = "dangerous-crypto-debug")]
480 debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
481 debug_struct.finish()
482 }
483}
484
485fn pad_key(key_bytes: &mut Vec<u8>, min_length: u8) {
496 crate::keys::utils::pad_bytes(key_bytes, min_length as usize)
497 .expect("Padding cannot fail since the min_length is < 255")
498}
499
500fn unpad_key(key_bytes: &[u8]) -> Result<&[u8], CryptoError> {
511 crate::keys::utils::unpad_bytes(key_bytes).map_err(|_| CryptoError::InvalidKey)
512}
513
514pub enum EncodedSymmetricKey {
516 BitwardenLegacyKey(BitwardenLegacyKeyBytes),
518 CoseKey(CoseKeyBytes),
520}
521impl From<EncodedSymmetricKey> for Vec<u8> {
522 fn from(val: EncodedSymmetricKey) -> Self {
523 match val {
524 EncodedSymmetricKey::BitwardenLegacyKey(key) => key.to_vec(),
525 EncodedSymmetricKey::CoseKey(key) => key.to_vec(),
526 }
527 }
528}
529impl EncodedSymmetricKey {
530 #[allow(private_interfaces)]
532 pub fn content_format(&self) -> ContentFormat {
533 match self {
534 EncodedSymmetricKey::BitwardenLegacyKey(_) => ContentFormat::BitwardenLegacyKey,
535 EncodedSymmetricKey::CoseKey(_) => ContentFormat::CoseKey,
536 }
537 }
538}
539
540impl<'de> Deserialize<'de> for SymmetricCryptoKey {
545 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
546 where
547 D: serde::Deserializer<'de>,
548 {
549 let encoded_key = BitwardenLegacyKeyBytes::deserialize(deserializer)?;
550 SymmetricCryptoKey::try_from(&encoded_key).map_err(serde::de::Error::custom)
551 }
552}
553
554impl Serialize for SymmetricCryptoKey {
555 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
556 where
557 S: serde::Serializer,
558 {
559 let encoded_key = self.to_encoded();
560 encoded_key.serialize(serializer)
561 }
562}
563
564#[cfg(test)]
566pub fn derive_symmetric_key(name: &str) -> Aes256CbcHmacKey {
567 use zeroize::Zeroizing;
568
569 use crate::{derive_shareable_key, generate_random_bytes};
570
571 let secret: Zeroizing<[u8; 16]> = generate_random_bytes();
572 derive_shareable_key(secret, name, None)
573}
574
575#[cfg(test)]
576mod tests {
577 use bitwarden_encoding::B64;
578 use coset::iana::KeyOperation;
579 use hybrid_array::Array;
580 use typenum::U32;
581
582 use super::{SymmetricCryptoKey, derive_symmetric_key};
583 use crate::{
584 Aes256CbcHmacKey, Aes256CbcKey, BitwardenLegacyKeyBytes, XChaCha20Poly1305Key,
585 keys::{
586 KeyId,
587 symmetric_crypto_key::{pad_key, unpad_key},
588 },
589 };
590
591 #[test]
592 #[ignore = "Manual test to verify debug format"]
593 fn test_key_debug() {
594 let aes_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
595 println!("{:?}", aes_key);
596 let xchacha_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
597 println!("{:?}", xchacha_key);
598 }
599
600 #[test]
601 fn test_serialize_deserialize_symmetric_crypto_key() {
602 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
603 let serialized = serde_json::to_string(&key).unwrap();
604 let deserialized: SymmetricCryptoKey = serde_json::from_str(&serialized).unwrap();
605 assert_eq!(key, deserialized);
606 }
607
608 #[test]
609 fn test_symmetric_crypto_key() {
610 let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
611 let key2 = SymmetricCryptoKey::try_from(key.to_base64()).unwrap();
612
613 assert_eq!(key, key2);
614
615 let key = "UY4B5N4DA4UisCNClgZtRr6VLy9ZF5BXXC7cDZRqourKi4ghEMgISbCsubvgCkHf5DZctQjVot11/vVvN9NNHQ==".to_string();
616 let key2 = SymmetricCryptoKey::try_from(key.clone()).unwrap();
617 assert_eq!(key, key2.to_base64().to_string());
618 }
619
620 #[test]
621 fn test_encode_decode_old_symmetric_crypto_key() {
622 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
623 let encoded = key.to_encoded();
624 let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
625 assert_eq!(key, decoded);
626 }
627
628 #[test]
629 fn test_decode_new_symmetric_crypto_key() {
630 let key: B64 = ("pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB").parse()
631 .unwrap();
632 let key = BitwardenLegacyKeyBytes::from(&key);
633 let key = SymmetricCryptoKey::try_from(&key).unwrap();
634 match key {
635 SymmetricCryptoKey::XChaCha20Poly1305Key(_) => (),
636 _ => panic!("Invalid key type"),
637 }
638 }
639
640 #[test]
641 fn test_encode_xchacha20_poly1305_key() {
642 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
643 let encoded = key.to_encoded();
644 let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
645 assert_eq!(key, decoded);
646 }
647
648 #[test]
649 fn test_pad_unpad_key_63() {
650 let original_key = vec![1u8; 63];
651 let mut key_bytes = original_key.clone();
652 let mut encoded_bytes = vec![1u8; 65];
653 encoded_bytes[63] = 2;
654 encoded_bytes[64] = 2;
655 pad_key(&mut key_bytes, 65);
656 assert_eq!(encoded_bytes, key_bytes);
657 let unpadded_key = unpad_key(&key_bytes).unwrap();
658 assert_eq!(original_key, unpadded_key);
659 }
660
661 #[test]
662 fn test_pad_unpad_key_64() {
663 let original_key = vec![1u8; 64];
664 let mut key_bytes = original_key.clone();
665 let mut encoded_bytes = vec![1u8; 65];
666 encoded_bytes[64] = 1;
667 pad_key(&mut key_bytes, 65);
668 assert_eq!(encoded_bytes, key_bytes);
669 let unpadded_key = unpad_key(&key_bytes).unwrap();
670 assert_eq!(original_key, unpadded_key);
671 }
672
673 #[test]
674 fn test_pad_unpad_key_65() {
675 let original_key = vec![1u8; 65];
676 let mut key_bytes = original_key.clone();
677 let mut encoded_bytes = vec![1u8; 66];
678 encoded_bytes[65] = 1;
679 pad_key(&mut key_bytes, 65);
680 assert_eq!(encoded_bytes, key_bytes);
681 let unpadded_key = unpad_key(&key_bytes).unwrap();
682 assert_eq!(original_key, unpadded_key);
683 }
684
685 #[test]
686 fn test_eq_aes_cbc_hmac() {
687 let key1 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
688 let key2 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
689 assert_ne!(key1, key2);
690 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
691 assert_eq!(key1, key3);
692 }
693
694 #[test]
695 fn test_eq_aes_cbc() {
696 let key1 =
697 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![1u8; 32])).unwrap();
698 let key2 =
699 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![2u8; 32])).unwrap();
700 assert_ne!(key1, key2);
701 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
702 assert_eq!(key1, key3);
703 }
704
705 #[test]
706 fn test_eq_xchacha20_poly1305() {
707 let key1 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
708 let key2 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
709 assert_ne!(key1, key2);
710 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
711 assert_eq!(key1, key3);
712 }
713
714 #[test]
715 fn test_neq_different_key_types() {
716 let key1 = SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey {
717 enc_key: Box::pin(Array::<u8, U32>::default()),
718 });
719 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
720 enc_key: Box::pin(Array::<u8, U32>::default()),
721 key_id: KeyId::from([0; 16]),
722 supported_operations: vec![
723 KeyOperation::Decrypt,
724 KeyOperation::Encrypt,
725 KeyOperation::WrapKey,
726 KeyOperation::UnwrapKey,
727 ],
728 });
729 assert_ne!(key1, key2);
730 }
731
732 #[test]
733 fn test_eq_variant_aes256_cbc() {
734 let key1 = Aes256CbcKey {
735 enc_key: Box::pin(Array::from([1u8; 32])),
736 };
737 let key2 = Aes256CbcKey {
738 enc_key: Box::pin(Array::from([1u8; 32])),
739 };
740 let key3 = Aes256CbcKey {
741 enc_key: Box::pin(Array::from([2u8; 32])),
742 };
743 assert_eq!(key1, key2);
744 assert_ne!(key1, key3);
745 }
746
747 #[test]
748 fn test_eq_variant_aes256_cbc_hmac() {
749 let key1 = Aes256CbcHmacKey {
750 enc_key: Box::pin(Array::from([1u8; 32])),
751 mac_key: Box::pin(Array::from([2u8; 32])),
752 };
753 let key2 = Aes256CbcHmacKey {
754 enc_key: Box::pin(Array::from([1u8; 32])),
755 mac_key: Box::pin(Array::from([2u8; 32])),
756 };
757 let key3 = Aes256CbcHmacKey {
758 enc_key: Box::pin(Array::from([3u8; 32])),
759 mac_key: Box::pin(Array::from([4u8; 32])),
760 };
761 assert_eq!(key1, key2);
762 assert_ne!(key1, key3);
763 }
764
765 #[test]
766 fn test_eq_variant_xchacha20_poly1305() {
767 let key1 = XChaCha20Poly1305Key {
768 enc_key: Box::pin(Array::from([1u8; 32])),
769 key_id: KeyId::from([0; 16]),
770 supported_operations: vec![
771 KeyOperation::Decrypt,
772 KeyOperation::Encrypt,
773 KeyOperation::WrapKey,
774 KeyOperation::UnwrapKey,
775 ],
776 };
777 let key2 = XChaCha20Poly1305Key {
778 enc_key: Box::pin(Array::from([1u8; 32])),
779 key_id: KeyId::from([0; 16]),
780 supported_operations: vec![
781 KeyOperation::Decrypt,
782 KeyOperation::Encrypt,
783 KeyOperation::WrapKey,
784 KeyOperation::UnwrapKey,
785 ],
786 };
787 let key3 = XChaCha20Poly1305Key {
788 enc_key: Box::pin(Array::from([2u8; 32])),
789 key_id: KeyId::from([1; 16]),
790 supported_operations: vec![
791 KeyOperation::Decrypt,
792 KeyOperation::Encrypt,
793 KeyOperation::WrapKey,
794 KeyOperation::UnwrapKey,
795 ],
796 };
797 assert_eq!(key1, key2);
798 assert_ne!(key1, key3);
799 }
800
801 #[test]
802 fn test_neq_different_key_id() {
803 let key1 = XChaCha20Poly1305Key {
804 enc_key: Box::pin(Array::<u8, U32>::default()),
805 key_id: KeyId::from([0; 16]),
806 supported_operations: vec![
807 KeyOperation::Decrypt,
808 KeyOperation::Encrypt,
809 KeyOperation::WrapKey,
810 KeyOperation::UnwrapKey,
811 ],
812 };
813 let key2 = XChaCha20Poly1305Key {
814 enc_key: Box::pin(Array::<u8, U32>::default()),
815 key_id: KeyId::from([1; 16]),
816 supported_operations: vec![
817 KeyOperation::Decrypt,
818 KeyOperation::Encrypt,
819 KeyOperation::WrapKey,
820 KeyOperation::UnwrapKey,
821 ],
822 };
823 assert_ne!(key1, key2);
824
825 let key1 = SymmetricCryptoKey::XChaCha20Poly1305Key(key1);
826 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(key2);
827 assert_ne!(key1, key2);
828 }
829}