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