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