Skip to main content

bitwarden_crypto/keys/
symmetric_crypto_key.rs

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