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