bitwarden_crypto/keys/
symmetric_crypto_key.rs

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/// [Aes256CbcKey] is a symmetric encryption key, consisting of one 256-bit key,
24/// used to decrypt legacy type 0 enc strings. The data is not authenticated
25/// so this should be used with caution, and removed where possible.
26#[derive(ZeroizeOnDrop, Clone)]
27pub struct Aes256CbcKey {
28    /// Uses a pinned heap data structure, as noted in [Pinned heap data][crate#pinned-heap-data]
29    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/// [Aes256CbcHmacKey] is a symmetric encryption key consisting
45/// of two 256-bit keys, one for encryption and one for MAC
46#[derive(ZeroizeOnDrop, Clone)]
47pub struct Aes256CbcHmacKey {
48    /// Uses a pinned heap data structure, as noted in [Pinned heap data][crate#pinned-heap-data]
49    pub(crate) enc_key: Pin<Box<GenericArray<u8, U32>>>,
50    /// Uses a pinned heap data structure, as noted in [Pinned heap data][crate#pinned-heap-data]
51    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/// [XChaCha20Poly1305Key] is a symmetric encryption key consisting
67/// of one 256-bit key, and contains a key id. In contrast to the
68/// [Aes256CbcKey] and [Aes256CbcHmacKey], this key type is used to create
69/// CoseEncrypt0 messages.
70#[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/// A symmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::EncString)
89#[derive(ZeroizeOnDrop, Clone)]
90pub enum SymmetricCryptoKey {
91    #[allow(missing_docs)]
92    Aes256CbcKey(Aes256CbcKey),
93    #[allow(missing_docs)]
94    Aes256CbcHmacKey(Aes256CbcHmacKey),
95    /// Data encrypted by XChaCha20Poly1305Key keys has type
96    /// [`Cose_Encrypt0_B64`](crate::EncString::Cose_Encrypt0_B64)
97    XChaCha20Poly1305Key(XChaCha20Poly1305Key),
98}
99
100impl SymmetricCryptoKey {
101    // enc type 0 old static format
102    const AES256_CBC_KEY_LEN: usize = 32;
103    // enc type 2 old static format
104    const AES256_CBC_HMAC_KEY_LEN: usize = 64;
105
106    /// Generate a new random AES256_CBC [SymmetricCryptoKey]
107    ///
108    /// WARNING: This function should only be used with a proper cryptographic RNG. If you do not
109    /// have a good reason for using this function, use
110    /// [SymmetricCryptoKey::make_aes256_cbc_hmac_key] instead.
111    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    /// Generate a new random AES256_CBC_HMAC [SymmetricCryptoKey]
124    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    /// Generate a new random XChaCha20Poly1305 [SymmetricCryptoKey]
130    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    /// Encodes the key to a byte array representation, that is separated by size.
141    /// [SymmetricCryptoKey::Aes256CbcHmacKey] and [SymmetricCryptoKey::Aes256CbcKey] are
142    /// encoded as 64 and 32 bytes respectively. [SymmetricCryptoKey::XChaCha20Poly1305Key]
143    /// is encoded as at least 65 bytes, using padding.
144    ///
145    /// This can be used for storage and transmission in the old byte array format.
146    /// When the wrapping key is a COSE key, and the wrapped key is a COSE key, then this should
147    /// not use the byte representation but instead use the COSE key representation.
148    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); // This is less than 255
158                BitwardenLegacyKeyBytes::from(encoded_key)
159            }
160        }
161    }
162
163    /// Generate a new random [SymmetricCryptoKey] for unit tests. Note: DO NOT USE THIS
164    /// IN PRODUCTION CODE.
165    #[cfg(test)]
166    pub fn generate_seeded_for_unit_tests(seed: &str) -> Self {
167        // Keep this separate from the other generate function to not break test vectors.
168        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    /// Creates the byte representation of the key, without any padding. This should not
179    /// be used directly for creating serialized key representations, instead,
180    /// [SymmetricCryptoKey::to_encoded] should be used.
181    ///
182    /// [SymmetricCryptoKey::Aes256CbcHmacKey] and [SymmetricCryptoKey::Aes256CbcKey] are
183    /// encoded as 64 and 32 byte arrays respectively, representing the key bytes directly.
184    /// [SymmetricCryptoKey::XChaCha20Poly1305Key] is encoded as a COSE key, serialized to a byte
185    /// array. The COSE key can be either directly encrypted using COSE, where the content
186    /// format hints an the key type, or can be represented as a byte array, if padded to be
187    /// larger than the byte array representation of the other key types using the
188    /// aforementioned [SymmetricCryptoKey::to_encoded] function.
189    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    /// Note: This is constant time with respect to comparing two keys of the same type, but not
237    /// constant type with respect to the fact that different keys are compared. If two types of
238    /// different keys are compared, then this does have different timing.
239    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        // Raw byte serialized keys are either 32, 64, or more bytes long. If they are 32/64, they
284        // are the raw serializations of the AES256-CBC, and AES256-CBC-HMAC keys. If they
285        // are longer, they are COSE keys. The COSE keys are padded to the minimum length of
286        // 65 bytes, when serialized to raw byte arrays.
287        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
336// We manually implement these to make sure we don't print any sensitive data
337impl 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
372/// Pad a key to a minimum length using PKCS7-like padding.
373/// The last N bytes of the padded bytes all have the value N.
374/// For example, padded to size 4, the value 0,0 becomes 0,0,2,2.
375///
376/// Keys that have the type [SymmetricCryptoKey::XChaCha20Poly1305Key] must be distinguishable
377/// from [SymmetricCryptoKey::Aes256CbcHmacKey] keys, when both are encoded as byte arrays
378/// with no additional content format included in the encoding message. For this reason, the
379/// padding is used to make sure that the byte representation uniquely separates the keys by
380/// size of the byte array. The previous key types [SymmetricCryptoKey::Aes256CbcHmacKey] and
381/// [SymmetricCryptoKey::Aes256CbcKey] are 64 and 32 bytes long respectively.
382fn 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
387/// Unpad a key that is padded using the PKCS7-like padding defined by [pad_key].
388/// The last N bytes of the padded bytes all have the value N.
389/// For example, padded to size 4, the value 0,0 becomes 0,0,2,2.
390///
391/// Keys that have the type [SymmetricCryptoKey::XChaCha20Poly1305Key] must be distinguishable
392/// from [SymmetricCryptoKey::Aes256CbcHmacKey] keys, when both are encoded as byte arrays
393/// with no additional content format included in the encoding message. For this reason, the
394/// padding is used to make sure that the byte representation uniquely separates the keys by
395/// size of the byte array the previous key types [SymmetricCryptoKey::Aes256CbcHmacKey] and
396/// [SymmetricCryptoKey::Aes256CbcKey] are 64 and 32 bytes long respectively.
397fn unpad_key(key_bytes: &[u8]) -> Result<&[u8], CryptoError> {
398    crate::keys::utils::unpad_bytes(key_bytes).map_err(|_| CryptoError::InvalidKey)
399}
400
401/// Encoded representation of [SymmetricCryptoKey]
402pub enum EncodedSymmetricKey {
403    /// An Aes256-CBC-HMAC key, or a Aes256-CBC key
404    BitwardenLegacyKey(BitwardenLegacyKeyBytes),
405    /// A symmetric key encoded as a COSE key
406    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    /// Returns the content format of the encoded symmetric key.
418    #[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/// Test only helper for deriving a symmetric key.
428#[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}