Skip to main content

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