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    /// Returns the key ID of the key, if it has one. Only
289    /// [SymmetricCryptoKey::XChaCha20Poly1305Key] has a key ID.
290    pub(crate) fn key_id(&self) -> Option<KeyId> {
291        match self {
292            Self::Aes256CbcKey(_) => None,
293            Self::Aes256CbcHmacKey(_) => None,
294            Self::XChaCha20Poly1305Key(key) => Some(KeyId::from(key.key_id)),
295        }
296    }
297}
298
299impl ConstantTimeEq for SymmetricCryptoKey {
300    /// Note: This is constant time with respect to comparing two keys of the same type, but not
301    /// constant type with respect to the fact that different keys are compared. If two types of
302    /// different keys are compared, then this does have different timing.
303    fn ct_eq(&self, other: &SymmetricCryptoKey) -> Choice {
304        use SymmetricCryptoKey::*;
305        match (self, other) {
306            (Aes256CbcKey(a), Aes256CbcKey(b)) => a.ct_eq(b),
307            (Aes256CbcKey(_), _) => Choice::from(0),
308
309            (Aes256CbcHmacKey(a), Aes256CbcHmacKey(b)) => a.ct_eq(b),
310            (Aes256CbcHmacKey(_), _) => Choice::from(0),
311
312            (XChaCha20Poly1305Key(a), XChaCha20Poly1305Key(b)) => a.ct_eq(b),
313            (XChaCha20Poly1305Key(_), _) => Choice::from(0),
314        }
315    }
316}
317
318impl PartialEq for SymmetricCryptoKey {
319    fn eq(&self, other: &Self) -> bool {
320        self.ct_eq(other).into()
321    }
322}
323
324impl TryFrom<String> for SymmetricCryptoKey {
325    type Error = CryptoError;
326
327    fn try_from(value: String) -> Result<Self, Self::Error> {
328        let bytes = B64::try_from(value).map_err(|_| CryptoError::InvalidKey)?;
329        Self::try_from(bytes)
330    }
331}
332
333impl TryFrom<B64> for SymmetricCryptoKey {
334    type Error = CryptoError;
335
336    fn try_from(value: B64) -> Result<Self, Self::Error> {
337        Self::try_from(&BitwardenLegacyKeyBytes::from(&value))
338    }
339}
340
341impl TryFrom<&BitwardenLegacyKeyBytes> for SymmetricCryptoKey {
342    type Error = CryptoError;
343
344    fn try_from(value: &BitwardenLegacyKeyBytes) -> Result<Self, Self::Error> {
345        let slice = value.as_ref();
346
347        // Raw byte serialized keys are either 32, 64, or more bytes long. If they are 32/64, they
348        // are the raw serializations of the AES256-CBC, and AES256-CBC-HMAC keys. If they
349        // are longer, they are COSE keys. The COSE keys are padded to the minimum length of
350        // 65 bytes, when serialized to raw byte arrays.
351
352        if slice.len() == Self::AES256_CBC_HMAC_KEY_LEN || slice.len() == Self::AES256_CBC_KEY_LEN {
353            Self::try_from(EncodedSymmetricKey::BitwardenLegacyKey(value.clone()))
354        } else if slice.len() > Self::AES256_CBC_HMAC_KEY_LEN {
355            let unpadded_value = unpad_key(slice)?;
356            Ok(Self::try_from_cose(unpadded_value)?)
357        } else {
358            Err(CryptoError::InvalidKeyLen)
359        }
360    }
361}
362
363impl TryFrom<EncodedSymmetricKey> for SymmetricCryptoKey {
364    type Error = CryptoError;
365
366    fn try_from(value: EncodedSymmetricKey) -> Result<Self, Self::Error> {
367        match value {
368            EncodedSymmetricKey::BitwardenLegacyKey(key)
369                if key.as_ref().len() == Self::AES256_CBC_KEY_LEN =>
370            {
371                let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
372                enc_key.copy_from_slice(&key.as_ref()[..Self::AES256_CBC_KEY_LEN]);
373                Ok(Self::Aes256CbcKey(Aes256CbcKey { enc_key }))
374            }
375            EncodedSymmetricKey::BitwardenLegacyKey(key)
376                if key.as_ref().len() == Self::AES256_CBC_HMAC_KEY_LEN =>
377            {
378                let mut enc_key = Box::pin(GenericArray::<u8, U32>::default());
379                enc_key.copy_from_slice(&key.as_ref()[..32]);
380
381                let mut mac_key = Box::pin(GenericArray::<u8, U32>::default());
382                mac_key.copy_from_slice(&key.as_ref()[32..]);
383
384                Ok(Self::Aes256CbcHmacKey(Aes256CbcHmacKey {
385                    enc_key,
386                    mac_key,
387                }))
388            }
389            EncodedSymmetricKey::CoseKey(key) => Self::try_from_cose(key.as_ref()),
390            _ => Err(CryptoError::InvalidKey),
391        }
392    }
393}
394
395impl CryptoKey for SymmetricCryptoKey {}
396
397// We manually implement these to make sure we don't print any sensitive data
398impl std::fmt::Debug for SymmetricCryptoKey {
399    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
400        f.debug_struct("SymmetricCryptoKey")
401            .field(
402                "inner_type",
403                match self {
404                    SymmetricCryptoKey::Aes256CbcKey(key) => key,
405                    SymmetricCryptoKey::Aes256CbcHmacKey(key) => key,
406                    SymmetricCryptoKey::XChaCha20Poly1305Key(key) => key,
407                },
408            )
409            .finish()
410    }
411}
412
413impl std::fmt::Debug for Aes256CbcKey {
414    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415        let mut debug_struct = f.debug_struct("Aes256CbcKey");
416        #[cfg(feature = "dangerous-crypto-debug")]
417        debug_struct.field("enc_key", &self.enc_key.as_slice());
418        debug_struct.finish()
419    }
420}
421
422impl std::fmt::Debug for Aes256CbcHmacKey {
423    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
424        let mut debug_struct = f.debug_struct("Aes256CbcHmacKey");
425        #[cfg(feature = "dangerous-crypto-debug")]
426        debug_struct
427            .field("enc_key", &self.enc_key.as_slice())
428            .field("mac_key", &self.mac_key.as_slice());
429        debug_struct.finish()
430    }
431}
432
433impl std::fmt::Debug for XChaCha20Poly1305Key {
434    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
435        let mut debug_struct = f.debug_struct("XChaCha20Poly1305Key");
436        debug_struct.field("key_id", &self.key_id);
437        #[cfg(feature = "dangerous-crypto-debug")]
438        debug_struct.field("enc_key", &self.enc_key.as_slice());
439        debug_struct.finish()
440    }
441}
442
443/// Pad a key to a minimum length using PKCS7-like padding.
444/// The last N bytes of the padded bytes all have the value N.
445/// For example, padded to size 4, the value 0,0 becomes 0,0,2,2.
446///
447/// Keys that have the type [SymmetricCryptoKey::XChaCha20Poly1305Key] must be distinguishable
448/// from [SymmetricCryptoKey::Aes256CbcHmacKey] keys, when both are encoded as byte arrays
449/// with no additional content format included in the encoding message. For this reason, the
450/// padding is used to make sure that the byte representation uniquely separates the keys by
451/// size of the byte array. The previous key types [SymmetricCryptoKey::Aes256CbcHmacKey] and
452/// [SymmetricCryptoKey::Aes256CbcKey] are 64 and 32 bytes long respectively.
453fn pad_key(key_bytes: &mut Vec<u8>, min_length: u8) {
454    crate::keys::utils::pad_bytes(key_bytes, min_length as usize)
455        .expect("Padding cannot fail since the min_length is < 255")
456}
457
458/// Unpad a key that is padded using the PKCS7-like padding defined by [pad_key].
459/// The last N bytes of the padded bytes all have the value N.
460/// For example, padded to size 4, the value 0,0 becomes 0,0,2,2.
461///
462/// Keys that have the type [SymmetricCryptoKey::XChaCha20Poly1305Key] must be distinguishable
463/// from [SymmetricCryptoKey::Aes256CbcHmacKey] keys, when both are encoded as byte arrays
464/// with no additional content format included in the encoding message. For this reason, the
465/// padding is used to make sure that the byte representation uniquely separates the keys by
466/// size of the byte array the previous key types [SymmetricCryptoKey::Aes256CbcHmacKey] and
467/// [SymmetricCryptoKey::Aes256CbcKey] are 64 and 32 bytes long respectively.
468fn unpad_key(key_bytes: &[u8]) -> Result<&[u8], CryptoError> {
469    crate::keys::utils::unpad_bytes(key_bytes).map_err(|_| CryptoError::InvalidKey)
470}
471
472/// Encoded representation of [SymmetricCryptoKey]
473pub enum EncodedSymmetricKey {
474    /// An Aes256-CBC-HMAC key, or a Aes256-CBC key
475    BitwardenLegacyKey(BitwardenLegacyKeyBytes),
476    /// A symmetric key encoded as a COSE key
477    CoseKey(CoseKeyBytes),
478}
479impl From<EncodedSymmetricKey> for Vec<u8> {
480    fn from(val: EncodedSymmetricKey) -> Self {
481        match val {
482            EncodedSymmetricKey::BitwardenLegacyKey(key) => key.to_vec(),
483            EncodedSymmetricKey::CoseKey(key) => key.to_vec(),
484        }
485    }
486}
487impl EncodedSymmetricKey {
488    /// Returns the content format of the encoded symmetric key.
489    #[allow(private_interfaces)]
490    pub fn content_format(&self) -> ContentFormat {
491        match self {
492            EncodedSymmetricKey::BitwardenLegacyKey(_) => ContentFormat::BitwardenLegacyKey,
493            EncodedSymmetricKey::CoseKey(_) => ContentFormat::CoseKey,
494        }
495    }
496}
497
498/// Test only helper for deriving a symmetric key.
499#[cfg(test)]
500pub fn derive_symmetric_key(name: &str) -> Aes256CbcHmacKey {
501    use zeroize::Zeroizing;
502
503    use crate::{derive_shareable_key, generate_random_bytes};
504
505    let secret: Zeroizing<[u8; 16]> = generate_random_bytes();
506    derive_shareable_key(secret, name, None)
507}
508
509#[cfg(test)]
510mod tests {
511    use bitwarden_encoding::B64;
512    use coset::iana::KeyOperation;
513    use generic_array::GenericArray;
514    use typenum::U32;
515
516    use super::{SymmetricCryptoKey, derive_symmetric_key};
517    use crate::{
518        Aes256CbcHmacKey, Aes256CbcKey, BitwardenLegacyKeyBytes, XChaCha20Poly1305Key,
519        keys::symmetric_crypto_key::{pad_key, unpad_key},
520    };
521
522    #[test]
523    fn test_symmetric_crypto_key() {
524        let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
525        let key2 = SymmetricCryptoKey::try_from(key.to_base64()).unwrap();
526
527        assert_eq!(key, key2);
528
529        let key = "UY4B5N4DA4UisCNClgZtRr6VLy9ZF5BXXC7cDZRqourKi4ghEMgISbCsubvgCkHf5DZctQjVot11/vVvN9NNHQ==".to_string();
530        let key2 = SymmetricCryptoKey::try_from(key.clone()).unwrap();
531        assert_eq!(key, key2.to_base64().to_string());
532    }
533
534    #[test]
535    fn test_encode_decode_old_symmetric_crypto_key() {
536        let key = SymmetricCryptoKey::make_aes256_cbc_hmac_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_decode_new_symmetric_crypto_key() {
544        let key: B64 = ("pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB").parse()
545        .unwrap();
546        let key = BitwardenLegacyKeyBytes::from(&key);
547        let key = SymmetricCryptoKey::try_from(&key).unwrap();
548        match key {
549            SymmetricCryptoKey::XChaCha20Poly1305Key(_) => (),
550            _ => panic!("Invalid key type"),
551        }
552    }
553
554    #[test]
555    fn test_encode_xchacha20_poly1305_key() {
556        let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
557        let encoded = key.to_encoded();
558        let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
559        assert_eq!(key, decoded);
560    }
561
562    #[test]
563    fn test_pad_unpad_key_63() {
564        let original_key = vec![1u8; 63];
565        let mut key_bytes = original_key.clone();
566        let mut encoded_bytes = vec![1u8; 65];
567        encoded_bytes[63] = 2;
568        encoded_bytes[64] = 2;
569        pad_key(&mut key_bytes, 65);
570        assert_eq!(encoded_bytes, key_bytes);
571        let unpadded_key = unpad_key(&key_bytes).unwrap();
572        assert_eq!(original_key, unpadded_key);
573    }
574
575    #[test]
576    fn test_pad_unpad_key_64() {
577        let original_key = vec![1u8; 64];
578        let mut key_bytes = original_key.clone();
579        let mut encoded_bytes = vec![1u8; 65];
580        encoded_bytes[64] = 1;
581        pad_key(&mut key_bytes, 65);
582        assert_eq!(encoded_bytes, key_bytes);
583        let unpadded_key = unpad_key(&key_bytes).unwrap();
584        assert_eq!(original_key, unpadded_key);
585    }
586
587    #[test]
588    fn test_pad_unpad_key_65() {
589        let original_key = vec![1u8; 65];
590        let mut key_bytes = original_key.clone();
591        let mut encoded_bytes = vec![1u8; 66];
592        encoded_bytes[65] = 1;
593        pad_key(&mut key_bytes, 65);
594        assert_eq!(encoded_bytes, key_bytes);
595        let unpadded_key = unpad_key(&key_bytes).unwrap();
596        assert_eq!(original_key, unpadded_key);
597    }
598
599    #[test]
600    fn test_eq_aes_cbc_hmac() {
601        let key1 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
602        let key2 = SymmetricCryptoKey::make_aes256_cbc_hmac_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_eq_aes_cbc() {
610        let key1 =
611            SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![1u8; 32])).unwrap();
612        let key2 =
613            SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![2u8; 32])).unwrap();
614        assert_ne!(key1, key2);
615        let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
616        assert_eq!(key1, key3);
617    }
618
619    #[test]
620    fn test_eq_xchacha20_poly1305() {
621        let key1 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
622        let key2 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
623        assert_ne!(key1, key2);
624        let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
625        assert_eq!(key1, key3);
626    }
627
628    #[test]
629    fn test_neq_different_key_types() {
630        let key1 = SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey {
631            enc_key: Box::pin(GenericArray::<u8, U32>::default()),
632        });
633        let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
634            enc_key: Box::pin(GenericArray::<u8, U32>::default()),
635            key_id: [0; 16],
636            supported_operations: vec![
637                KeyOperation::Decrypt,
638                KeyOperation::Encrypt,
639                KeyOperation::WrapKey,
640                KeyOperation::UnwrapKey,
641            ],
642        });
643        assert_ne!(key1, key2);
644    }
645
646    #[test]
647    fn test_eq_variant_aes256_cbc() {
648        let key1 = Aes256CbcKey {
649            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
650                vec![1u8; 32].as_slice(),
651            )),
652        };
653        let key2 = Aes256CbcKey {
654            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
655                vec![1u8; 32].as_slice(),
656            )),
657        };
658        let key3 = Aes256CbcKey {
659            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
660                vec![2u8; 32].as_slice(),
661            )),
662        };
663        assert_eq!(key1, key2);
664        assert_ne!(key1, key3);
665    }
666
667    #[test]
668    fn test_eq_variant_aes256_cbc_hmac() {
669        let key1 = Aes256CbcHmacKey {
670            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
671                vec![1u8; 32].as_slice(),
672            )),
673            mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
674                vec![2u8; 32].as_slice(),
675            )),
676        };
677        let key2 = Aes256CbcHmacKey {
678            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
679                vec![1u8; 32].as_slice(),
680            )),
681            mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
682                vec![2u8; 32].as_slice(),
683            )),
684        };
685        let key3 = Aes256CbcHmacKey {
686            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
687                vec![3u8; 32].as_slice(),
688            )),
689            mac_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
690                vec![4u8; 32].as_slice(),
691            )),
692        };
693        assert_eq!(key1, key2);
694        assert_ne!(key1, key3);
695    }
696
697    #[test]
698    fn test_eq_variant_xchacha20_poly1305() {
699        let key1 = XChaCha20Poly1305Key {
700            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
701                vec![1u8; 32].as_slice(),
702            )),
703            key_id: [0; 16],
704            supported_operations: vec![
705                KeyOperation::Decrypt,
706                KeyOperation::Encrypt,
707                KeyOperation::WrapKey,
708                KeyOperation::UnwrapKey,
709            ],
710        };
711        let key2 = XChaCha20Poly1305Key {
712            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
713                vec![1u8; 32].as_slice(),
714            )),
715            key_id: [0; 16],
716            supported_operations: vec![
717                KeyOperation::Decrypt,
718                KeyOperation::Encrypt,
719                KeyOperation::WrapKey,
720                KeyOperation::UnwrapKey,
721            ],
722        };
723        let key3 = XChaCha20Poly1305Key {
724            enc_key: Box::pin(GenericArray::<u8, U32>::clone_from_slice(
725                vec![2u8; 32].as_slice(),
726            )),
727            key_id: [1; 16],
728            supported_operations: vec![
729                KeyOperation::Decrypt,
730                KeyOperation::Encrypt,
731                KeyOperation::WrapKey,
732                KeyOperation::UnwrapKey,
733            ],
734        };
735        assert_eq!(key1, key2);
736        assert_ne!(key1, key3);
737    }
738
739    #[test]
740    fn test_neq_different_key_id() {
741        let key1 = XChaCha20Poly1305Key {
742            enc_key: Box::pin(GenericArray::<u8, U32>::default()),
743            key_id: [0; 16],
744            supported_operations: vec![
745                KeyOperation::Decrypt,
746                KeyOperation::Encrypt,
747                KeyOperation::WrapKey,
748                KeyOperation::UnwrapKey,
749            ],
750        };
751        let key2 = XChaCha20Poly1305Key {
752            enc_key: Box::pin(GenericArray::<u8, U32>::default()),
753            key_id: [1; 16],
754            supported_operations: vec![
755                KeyOperation::Decrypt,
756                KeyOperation::Encrypt,
757                KeyOperation::WrapKey,
758                KeyOperation::UnwrapKey,
759            ],
760        };
761        assert_ne!(key1, key2);
762
763        let key1 = SymmetricCryptoKey::XChaCha20Poly1305Key(key1);
764        let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(key2);
765        assert_ne!(key1, key2);
766    }
767}