bitwarden_crypto/keys/
symmetric_crypto_key.rs

1use std::pin::Pin;
2
3use bitwarden_encoding::B64;
4use coset::{CborSerializable, RegisteredLabelWithPrivate, iana::KeyOperation};
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::{KEY_ID_SIZE, KeyId},
20};
21use crate::{BitwardenLegacyKeyBytes, ContentFormat, CoseKeyBytes, CryptoError, cose};
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    /// Controls which key operations are allowed with this key. Note: Only checking decrypt is
75    /// implemented right now, and implementing is tracked here <https://bitwarden.atlassian.net/browse/PM-27513>.
76    /// Further, disabling decrypt will also disable unwrap. The only use-case so far is
77    /// `DataEnvelope`.
78    #[zeroize(skip)]
79    pub(crate) supported_operations: Vec<KeyOperation>,
80}
81
82impl XChaCha20Poly1305Key {
83    /// Creates a new XChaCha20Poly1305Key with a securely sampled cryptographic key and key id.
84    pub fn make() -> Self {
85        let mut rng = rand::thread_rng();
86        let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
87        rng.fill(enc_key.as_mut_slice());
88        let mut key_id = [0u8; KEY_ID_SIZE];
89        rng.fill(&mut key_id);
90
91        Self {
92            enc_key,
93            key_id,
94            supported_operations: vec![
95                KeyOperation::Decrypt,
96                KeyOperation::Encrypt,
97                KeyOperation::WrapKey,
98                KeyOperation::UnwrapKey,
99            ],
100        }
101    }
102
103    pub(crate) fn disable_key_operation(&mut self, op: KeyOperation) -> &mut Self {
104        self.supported_operations.retain(|k| *k != op);
105        self
106    }
107}
108
109impl ConstantTimeEq for XChaCha20Poly1305Key {
110    fn ct_eq(&self, other: &Self) -> Choice {
111        self.enc_key.ct_eq(&other.enc_key) & self.key_id.ct_eq(&other.key_id)
112    }
113}
114
115impl PartialEq for XChaCha20Poly1305Key {
116    fn eq(&self, other: &Self) -> bool {
117        self.ct_eq(other).into()
118    }
119}
120
121/// A symmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::EncString)
122#[derive(ZeroizeOnDrop, Clone)]
123pub enum SymmetricCryptoKey {
124    #[allow(missing_docs)]
125    Aes256CbcKey(Aes256CbcKey),
126    #[allow(missing_docs)]
127    Aes256CbcHmacKey(Aes256CbcHmacKey),
128    /// Data encrypted by XChaCha20Poly1305Key keys has type
129    /// [`Cose_Encrypt0_B64`](crate::EncString::Cose_Encrypt0_B64)
130    XChaCha20Poly1305Key(XChaCha20Poly1305Key),
131}
132
133impl SymmetricCryptoKey {
134    // enc type 0 old static format
135    const AES256_CBC_KEY_LEN: usize = 32;
136    // enc type 2 old static format
137    const AES256_CBC_HMAC_KEY_LEN: usize = 64;
138
139    /// Generate a new random AES256_CBC [SymmetricCryptoKey]
140    ///
141    /// WARNING: This function should only be used with a proper cryptographic RNG. If you do not
142    /// have a good reason for using this function, use
143    /// [SymmetricCryptoKey::make_aes256_cbc_hmac_key] instead.
144    pub(crate) fn make_aes256_cbc_hmac_key_internal(
145        mut rng: impl rand::RngCore + rand::CryptoRng,
146    ) -> Self {
147        let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
148        let mut mac_key = Box::pin(GenericArray::<u8, U32>::default());
149
150        rng.fill(enc_key.as_mut_slice());
151        rng.fill(mac_key.as_mut_slice());
152
153        Self::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key })
154    }
155
156    /// Generate a new random AES256_CBC_HMAC [SymmetricCryptoKey]
157    pub fn make_aes256_cbc_hmac_key() -> Self {
158        let rng = rand::thread_rng();
159        Self::make_aes256_cbc_hmac_key_internal(rng)
160    }
161
162    /// Generate a new random XChaCha20Poly1305 [SymmetricCryptoKey]
163    pub fn make_xchacha20_poly1305_key() -> Self {
164        let mut rng = rand::thread_rng();
165        let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
166        rng.fill(enc_key.as_mut_slice());
167        Self::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
168            enc_key,
169            key_id: KeyId::make().into(),
170            supported_operations: vec![
171                KeyOperation::Decrypt,
172                KeyOperation::Encrypt,
173                KeyOperation::WrapKey,
174                KeyOperation::UnwrapKey,
175            ],
176        })
177    }
178
179    /// Encodes the key to a byte array representation, that is separated by size.
180    /// [SymmetricCryptoKey::Aes256CbcHmacKey] and [SymmetricCryptoKey::Aes256CbcKey] are
181    /// encoded as 64 and 32 bytes respectively. [SymmetricCryptoKey::XChaCha20Poly1305Key]
182    /// is encoded as at least 65 bytes, using padding.
183    ///
184    /// This can be used for storage and transmission in the old byte array format.
185    /// When the wrapping key is a COSE key, and the wrapped key is a COSE key, then this should
186    /// not use the byte representation but instead use the COSE key representation.
187    pub fn to_encoded(&self) -> BitwardenLegacyKeyBytes {
188        let encoded_key = self.to_encoded_raw();
189        match encoded_key {
190            EncodedSymmetricKey::BitwardenLegacyKey(_) => {
191                let encoded_key: Vec<u8> = encoded_key.into();
192                BitwardenLegacyKeyBytes::from(encoded_key)
193            }
194            EncodedSymmetricKey::CoseKey(_) => {
195                let mut encoded_key: Vec<u8> = encoded_key.into();
196                pad_key(&mut encoded_key, (Self::AES256_CBC_HMAC_KEY_LEN + 1) as u8); // This is less than 255
197                BitwardenLegacyKeyBytes::from(encoded_key)
198            }
199        }
200    }
201
202    /// Generate a new random [SymmetricCryptoKey] for unit tests. Note: DO NOT USE THIS
203    /// IN PRODUCTION CODE.
204    #[cfg(test)]
205    pub fn generate_seeded_for_unit_tests(seed: &str) -> Self {
206        // Keep this separate from the other generate function to not break test vectors.
207        let mut seeded_rng = ChaChaRng::from_seed(sha2::Sha256::digest(seed.as_bytes()).into());
208        let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
209        let mut mac_key = Box::pin(GenericArray::<u8, U32>::default());
210
211        seeded_rng.fill(enc_key.as_mut_slice());
212        seeded_rng.fill(mac_key.as_mut_slice());
213
214        SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key })
215    }
216
217    /// Creates the byte representation of the key, without any padding. This should not
218    /// be used directly for creating serialized key representations, instead,
219    /// [SymmetricCryptoKey::to_encoded] should be used.
220    ///
221    /// [SymmetricCryptoKey::Aes256CbcHmacKey] and [SymmetricCryptoKey::Aes256CbcKey] are
222    /// encoded as 64 and 32 byte arrays respectively, representing the key bytes directly.
223    /// [SymmetricCryptoKey::XChaCha20Poly1305Key] is encoded as a COSE key, serialized to a byte
224    /// array. The COSE key can be either directly encrypted using COSE, where the content
225    /// format hints an the key type, or can be represented as a byte array, if padded to be
226    /// larger than the byte array representation of the other key types using the
227    /// aforementioned [SymmetricCryptoKey::to_encoded] function.
228    pub(crate) fn to_encoded_raw(&self) -> EncodedSymmetricKey {
229        match self {
230            Self::Aes256CbcKey(key) => {
231                EncodedSymmetricKey::BitwardenLegacyKey(key.enc_key.to_vec().into())
232            }
233            Self::Aes256CbcHmacKey(key) => {
234                let mut buf = Vec::with_capacity(64);
235                buf.extend_from_slice(&key.enc_key);
236                buf.extend_from_slice(&key.mac_key);
237                EncodedSymmetricKey::BitwardenLegacyKey(buf.into())
238            }
239            Self::XChaCha20Poly1305Key(key) => {
240                let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec());
241                let mut cose_key = builder.key_id(key.key_id.to_vec());
242                for op in &key.supported_operations {
243                    cose_key = cose_key.add_key_op(*op);
244                }
245                let mut cose_key = cose_key.build();
246                cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse(
247                    cose::XCHACHA20_POLY1305,
248                ));
249                EncodedSymmetricKey::CoseKey(
250                    cose_key
251                        .to_vec()
252                        .expect("cose key serialization should not fail")
253                        .into(),
254                )
255            }
256        }
257    }
258
259    pub(crate) fn try_from_cose(serialized_key: &[u8]) -> Result<Self, CryptoError> {
260        let cose_key =
261            coset::CoseKey::from_slice(serialized_key).map_err(|_| CryptoError::InvalidKey)?;
262        let key = SymmetricCryptoKey::try_from(&cose_key)?;
263        Ok(key)
264    }
265
266    #[allow(missing_docs)]
267    pub fn to_base64(&self) -> B64 {
268        B64::from(self.to_encoded().as_ref())
269    }
270}
271
272impl ConstantTimeEq for SymmetricCryptoKey {
273    /// Note: This is constant time with respect to comparing two keys of the same type, but not
274    /// constant type with respect to the fact that different keys are compared. If two types of
275    /// different keys are compared, then this does have different timing.
276    fn ct_eq(&self, other: &SymmetricCryptoKey) -> Choice {
277        use SymmetricCryptoKey::*;
278        match (self, other) {
279            (Aes256CbcKey(a), Aes256CbcKey(b)) => a.ct_eq(b),
280            (Aes256CbcKey(_), _) => Choice::from(0),
281
282            (Aes256CbcHmacKey(a), Aes256CbcHmacKey(b)) => a.ct_eq(b),
283            (Aes256CbcHmacKey(_), _) => Choice::from(0),
284
285            (XChaCha20Poly1305Key(a), XChaCha20Poly1305Key(b)) => a.ct_eq(b),
286            (XChaCha20Poly1305Key(_), _) => Choice::from(0),
287        }
288    }
289}
290
291impl PartialEq for SymmetricCryptoKey {
292    fn eq(&self, other: &Self) -> bool {
293        self.ct_eq(other).into()
294    }
295}
296
297impl TryFrom<String> for SymmetricCryptoKey {
298    type Error = CryptoError;
299
300    fn try_from(value: String) -> Result<Self, Self::Error> {
301        let bytes = B64::try_from(value).map_err(|_| CryptoError::InvalidKey)?;
302        Self::try_from(bytes)
303    }
304}
305
306impl TryFrom<B64> for SymmetricCryptoKey {
307    type Error = CryptoError;
308
309    fn try_from(value: B64) -> Result<Self, Self::Error> {
310        Self::try_from(&BitwardenLegacyKeyBytes::from(&value))
311    }
312}
313
314impl TryFrom<&BitwardenLegacyKeyBytes> for SymmetricCryptoKey {
315    type Error = CryptoError;
316
317    fn try_from(value: &BitwardenLegacyKeyBytes) -> Result<Self, Self::Error> {
318        let slice = value.as_ref();
319
320        // Raw byte serialized keys are either 32, 64, or more bytes long. If they are 32/64, they
321        // are the raw serializations of the AES256-CBC, and AES256-CBC-HMAC keys. If they
322        // are longer, they are COSE keys. The COSE keys are padded to the minimum length of
323        // 65 bytes, when serialized to raw byte arrays.
324
325        if slice.len() == Self::AES256_CBC_HMAC_KEY_LEN || slice.len() == Self::AES256_CBC_KEY_LEN {
326            Self::try_from(EncodedSymmetricKey::BitwardenLegacyKey(value.clone()))
327        } else if slice.len() > Self::AES256_CBC_HMAC_KEY_LEN {
328            let unpadded_value = unpad_key(slice)?;
329            Ok(Self::try_from_cose(unpadded_value)?)
330        } else {
331            Err(CryptoError::InvalidKeyLen)
332        }
333    }
334}
335
336impl TryFrom<EncodedSymmetricKey> for SymmetricCryptoKey {
337    type Error = CryptoError;
338
339    fn try_from(value: EncodedSymmetricKey) -> Result<Self, Self::Error> {
340        match value {
341            EncodedSymmetricKey::BitwardenLegacyKey(key)
342                if key.as_ref().len() == Self::AES256_CBC_KEY_LEN =>
343            {
344                let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
345                enc_key.copy_from_slice(&key.as_ref()[..Self::AES256_CBC_KEY_LEN]);
346                Ok(Self::Aes256CbcKey(Aes256CbcKey { enc_key }))
347            }
348            EncodedSymmetricKey::BitwardenLegacyKey(key)
349                if key.as_ref().len() == Self::AES256_CBC_HMAC_KEY_LEN =>
350            {
351                let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
352                enc_key.copy_from_slice(&key.as_ref()[..32]);
353
354                let mut mac_key = Box::pin(GenericArray::<u8, U32>::default());
355                mac_key.copy_from_slice(&key.as_ref()[32..]);
356
357                Ok(Self::Aes256CbcHmacKey(Aes256CbcHmacKey {
358                    enc_key,
359                    mac_key,
360                }))
361            }
362            EncodedSymmetricKey::CoseKey(key) => Self::try_from_cose(key.as_ref()),
363            _ => Err(CryptoError::InvalidKey),
364        }
365    }
366}
367
368impl CryptoKey for SymmetricCryptoKey {}
369
370// We manually implement these to make sure we don't print any sensitive data
371impl std::fmt::Debug for SymmetricCryptoKey {
372    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373        f.debug_struct("SymmetricCryptoKey")
374            .field(
375                "inner_type",
376                match self {
377                    SymmetricCryptoKey::Aes256CbcKey(key) => key,
378                    SymmetricCryptoKey::Aes256CbcHmacKey(key) => key,
379                    SymmetricCryptoKey::XChaCha20Poly1305Key(key) => key,
380                },
381            )
382            .finish()
383    }
384}
385
386impl std::fmt::Debug for Aes256CbcKey {
387    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
388        f.debug_struct("Aes256CbcKey").finish()
389    }
390}
391
392impl std::fmt::Debug for Aes256CbcHmacKey {
393    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394        f.debug_struct("Aes256CbcHmacKey").finish()
395    }
396}
397
398impl std::fmt::Debug for XChaCha20Poly1305Key {
399    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
400        f.debug_struct("XChaCha20Poly1305Key")
401            .field("key_id", &self.key_id)
402            .finish()
403    }
404}
405
406/// Pad a key to a minimum length using PKCS7-like padding.
407/// The last N bytes of the padded bytes all have the value N.
408/// For example, padded to size 4, the value 0,0 becomes 0,0,2,2.
409///
410/// Keys that have the type [SymmetricCryptoKey::XChaCha20Poly1305Key] must be distinguishable
411/// from [SymmetricCryptoKey::Aes256CbcHmacKey] keys, when both are encoded as byte arrays
412/// with no additional content format included in the encoding message. For this reason, the
413/// padding is used to make sure that the byte representation uniquely separates the keys by
414/// size of the byte array. The previous key types [SymmetricCryptoKey::Aes256CbcHmacKey] and
415/// [SymmetricCryptoKey::Aes256CbcKey] are 64 and 32 bytes long respectively.
416fn pad_key(key_bytes: &mut Vec<u8>, min_length: u8) {
417    crate::keys::utils::pad_bytes(key_bytes, min_length as usize)
418        .expect("Padding cannot fail since the min_length is < 255")
419}
420
421/// Unpad a key that is padded using the PKCS7-like padding defined by [pad_key].
422/// The last N bytes of the padded bytes all have the value N.
423/// For example, padded to size 4, the value 0,0 becomes 0,0,2,2.
424///
425/// Keys that have the type [SymmetricCryptoKey::XChaCha20Poly1305Key] must be distinguishable
426/// from [SymmetricCryptoKey::Aes256CbcHmacKey] keys, when both are encoded as byte arrays
427/// with no additional content format included in the encoding message. For this reason, the
428/// padding is used to make sure that the byte representation uniquely separates the keys by
429/// size of the byte array the previous key types [SymmetricCryptoKey::Aes256CbcHmacKey] and
430/// [SymmetricCryptoKey::Aes256CbcKey] are 64 and 32 bytes long respectively.
431fn unpad_key(key_bytes: &[u8]) -> Result<&[u8], CryptoError> {
432    crate::keys::utils::unpad_bytes(key_bytes).map_err(|_| CryptoError::InvalidKey)
433}
434
435/// Encoded representation of [SymmetricCryptoKey]
436pub enum EncodedSymmetricKey {
437    /// An Aes256-CBC-HMAC key, or a Aes256-CBC key
438    BitwardenLegacyKey(BitwardenLegacyKeyBytes),
439    /// A symmetric key encoded as a COSE key
440    CoseKey(CoseKeyBytes),
441}
442impl From<EncodedSymmetricKey> for Vec<u8> {
443    fn from(val: EncodedSymmetricKey) -> Self {
444        match val {
445            EncodedSymmetricKey::BitwardenLegacyKey(key) => key.to_vec(),
446            EncodedSymmetricKey::CoseKey(key) => key.to_vec(),
447        }
448    }
449}
450impl EncodedSymmetricKey {
451    /// Returns the content format of the encoded symmetric key.
452    #[allow(private_interfaces)]
453    pub fn content_format(&self) -> ContentFormat {
454        match self {
455            EncodedSymmetricKey::BitwardenLegacyKey(_) => ContentFormat::BitwardenLegacyKey,
456            EncodedSymmetricKey::CoseKey(_) => ContentFormat::CoseKey,
457        }
458    }
459}
460
461/// Test only helper for deriving a symmetric key.
462#[cfg(test)]
463pub fn derive_symmetric_key(name: &str) -> Aes256CbcHmacKey {
464    use zeroize::Zeroizing;
465
466    use crate::{derive_shareable_key, generate_random_bytes};
467
468    let secret: Zeroizing<[u8; 16]> = generate_random_bytes();
469    derive_shareable_key(secret, name, None)
470}
471
472#[cfg(test)]
473mod tests {
474    use bitwarden_encoding::B64;
475    use coset::iana::KeyOperation;
476    use generic_array::GenericArray;
477    use typenum::U32;
478
479    use super::{SymmetricCryptoKey, derive_symmetric_key};
480    use crate::{
481        Aes256CbcHmacKey, Aes256CbcKey, BitwardenLegacyKeyBytes, XChaCha20Poly1305Key,
482        keys::symmetric_crypto_key::{pad_key, unpad_key},
483    };
484
485    #[test]
486    fn test_symmetric_crypto_key() {
487        let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
488        let key2 = SymmetricCryptoKey::try_from(key.to_base64()).unwrap();
489
490        assert_eq!(key, key2);
491
492        let key = "UY4B5N4DA4UisCNClgZtRr6VLy9ZF5BXXC7cDZRqourKi4ghEMgISbCsubvgCkHf5DZctQjVot11/vVvN9NNHQ==".to_string();
493        let key2 = SymmetricCryptoKey::try_from(key.clone()).unwrap();
494        assert_eq!(key, key2.to_base64().to_string());
495    }
496
497    #[test]
498    fn test_encode_decode_old_symmetric_crypto_key() {
499        let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
500        let encoded = key.to_encoded();
501        let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
502        assert_eq!(key, decoded);
503    }
504
505    #[test]
506    fn test_decode_new_symmetric_crypto_key() {
507        let key: B64 = ("pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB").parse()
508        .unwrap();
509        let key = BitwardenLegacyKeyBytes::from(&key);
510        let key = SymmetricCryptoKey::try_from(&key).unwrap();
511        match key {
512            SymmetricCryptoKey::XChaCha20Poly1305Key(_) => (),
513            _ => panic!("Invalid key type"),
514        }
515    }
516
517    #[test]
518    fn test_encode_xchacha20_poly1305_key() {
519        let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
520        let encoded = key.to_encoded();
521        let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
522        assert_eq!(key, decoded);
523    }
524
525    #[test]
526    fn test_pad_unpad_key_63() {
527        let original_key = vec![1u8; 63];
528        let mut key_bytes = original_key.clone();
529        let mut encoded_bytes = vec![1u8; 65];
530        encoded_bytes[63] = 2;
531        encoded_bytes[64] = 2;
532        pad_key(&mut key_bytes, 65);
533        assert_eq!(encoded_bytes, key_bytes);
534        let unpadded_key = unpad_key(&key_bytes).unwrap();
535        assert_eq!(original_key, unpadded_key);
536    }
537
538    #[test]
539    fn test_pad_unpad_key_64() {
540        let original_key = vec![1u8; 64];
541        let mut key_bytes = original_key.clone();
542        let mut encoded_bytes = vec![1u8; 65];
543        encoded_bytes[64] = 1;
544        pad_key(&mut key_bytes, 65);
545        assert_eq!(encoded_bytes, key_bytes);
546        let unpadded_key = unpad_key(&key_bytes).unwrap();
547        assert_eq!(original_key, unpadded_key);
548    }
549
550    #[test]
551    fn test_pad_unpad_key_65() {
552        let original_key = vec![1u8; 65];
553        let mut key_bytes = original_key.clone();
554        let mut encoded_bytes = vec![1u8; 66];
555        encoded_bytes[65] = 1;
556        pad_key(&mut key_bytes, 65);
557        assert_eq!(encoded_bytes, key_bytes);
558        let unpadded_key = unpad_key(&key_bytes).unwrap();
559        assert_eq!(original_key, unpadded_key);
560    }
561
562    #[test]
563    fn test_eq_aes_cbc_hmac() {
564        let key1 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
565        let key2 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
566        assert_ne!(key1, key2);
567        let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
568        assert_eq!(key1, key3);
569    }
570
571    #[test]
572    fn test_eq_aes_cbc() {
573        let key1 =
574            SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![1u8; 32])).unwrap();
575        let key2 =
576            SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![2u8; 32])).unwrap();
577        assert_ne!(key1, key2);
578        let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
579        assert_eq!(key1, key3);
580    }
581
582    #[test]
583    fn test_eq_xchacha20_poly1305() {
584        let key1 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
585        let key2 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
586        assert_ne!(key1, key2);
587        let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
588        assert_eq!(key1, key3);
589    }
590
591    #[test]
592    fn test_neq_different_key_types() {
593        let key1 = SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey {
594            enc_key: Box::pin(GenericArray::<u8, U32>::default()),
595        });
596        let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
597            enc_key: Box::pin(GenericArray::<u8, U32>::default()),
598            key_id: [0; 16],
599            supported_operations: vec![
600                KeyOperation::Decrypt,
601                KeyOperation::Encrypt,
602                KeyOperation::WrapKey,
603                KeyOperation::UnwrapKey,
604            ],
605        });
606        assert_ne!(key1, key2);
607    }
608
609    #[test]
610    fn test_eq_variant_aes256_cbc() {
611        let key1 = Aes256CbcKey {
612            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
613                vec![1u8; 32].as_slice(),
614            )),
615        };
616        let key2 = Aes256CbcKey {
617            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
618                vec![1u8; 32].as_slice(),
619            )),
620        };
621        let key3 = Aes256CbcKey {
622            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
623                vec![2u8; 32].as_slice(),
624            )),
625        };
626        assert_eq!(key1, key2);
627        assert_ne!(key1, key3);
628    }
629
630    #[test]
631    fn test_eq_variant_aes256_cbc_hmac() {
632        let key1 = Aes256CbcHmacKey {
633            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
634                vec![1u8; 32].as_slice(),
635            )),
636            mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
637                vec![2u8; 32].as_slice(),
638            )),
639        };
640        let key2 = Aes256CbcHmacKey {
641            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
642                vec![1u8; 32].as_slice(),
643            )),
644            mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
645                vec![2u8; 32].as_slice(),
646            )),
647        };
648        let key3 = Aes256CbcHmacKey {
649            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
650                vec![3u8; 32].as_slice(),
651            )),
652            mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
653                vec![4u8; 32].as_slice(),
654            )),
655        };
656        assert_eq!(key1, key2);
657        assert_ne!(key1, key3);
658    }
659
660    #[test]
661    fn test_eq_variant_xchacha20_poly1305() {
662        let key1 = XChaCha20Poly1305Key {
663            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
664                vec![1u8; 32].as_slice(),
665            )),
666            key_id: [0; 16],
667            supported_operations: vec![
668                KeyOperation::Decrypt,
669                KeyOperation::Encrypt,
670                KeyOperation::WrapKey,
671                KeyOperation::UnwrapKey,
672            ],
673        };
674        let key2 = XChaCha20Poly1305Key {
675            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
676                vec![1u8; 32].as_slice(),
677            )),
678            key_id: [0; 16],
679            supported_operations: vec![
680                KeyOperation::Decrypt,
681                KeyOperation::Encrypt,
682                KeyOperation::WrapKey,
683                KeyOperation::UnwrapKey,
684            ],
685        };
686        let key3 = XChaCha20Poly1305Key {
687            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
688                vec![2u8; 32].as_slice(),
689            )),
690            key_id: [1; 16],
691            supported_operations: vec![
692                KeyOperation::Decrypt,
693                KeyOperation::Encrypt,
694                KeyOperation::WrapKey,
695                KeyOperation::UnwrapKey,
696            ],
697        };
698        assert_eq!(key1, key2);
699        assert_ne!(key1, key3);
700    }
701
702    #[test]
703    fn test_neq_different_key_id() {
704        let key1 = XChaCha20Poly1305Key {
705            enc_key: Box::pin(GenericArray::<u8, U32>::default()),
706            key_id: [0; 16],
707            supported_operations: vec![
708                KeyOperation::Decrypt,
709                KeyOperation::Encrypt,
710                KeyOperation::WrapKey,
711                KeyOperation::UnwrapKey,
712            ],
713        };
714        let key2 = XChaCha20Poly1305Key {
715            enc_key: Box::pin(GenericArray::<u8, U32>::default()),
716            key_id: [1; 16],
717            supported_operations: vec![
718                KeyOperation::Decrypt,
719                KeyOperation::Encrypt,
720                KeyOperation::WrapKey,
721                KeyOperation::UnwrapKey,
722            ],
723        };
724        assert_ne!(key1, key2);
725
726        let key1 = SymmetricCryptoKey::XChaCha20Poly1305Key(key1);
727        let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(key2);
728        assert_ne!(key1, key2);
729    }
730}