Skip to main content

bitwarden_crypto/keys/
symmetric_crypto_key.rs

1use std::{pin::Pin, str::FromStr};
2
3use bitwarden_encoding::{B64, FromStrVisitor};
4use ciborium::{Value, value::Integer};
5use coset::{
6    CborSerializable, RegisteredLabelWithPrivate,
7    iana::{EnumI64, KeyOperation, KeyParameter, KeyType, SymmetricKeyParameter},
8};
9use hybrid_array::Array;
10use rand::RngExt;
11#[cfg(test)]
12use rand::SeedableRng;
13#[cfg(test)]
14use rand_chacha::ChaChaRng;
15use serde::{Deserialize, Serialize};
16#[cfg(test)]
17use sha2::Digest;
18use subtle::{Choice, ConstantTimeEq};
19use typenum::U32;
20#[cfg(feature = "wasm")]
21use wasm_bindgen::convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi};
22use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
23
24use super::{key_encryptable::CryptoKey, key_id::KeyId};
25use crate::{
26    BitwardenLegacyKeyBytes, ContentFormat, CoseKeyBytes, CoseKeyThumbprint, CryptoError, cose,
27    cose::{
28        CoseKeyThumbprintExt, symmetric::CoseContentEncryptionAlgorithm,
29        thumbprint_from_required_params,
30    },
31    error::EncodingError,
32};
33
34#[cfg(feature = "wasm")]
35#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
36const TS_CUSTOM_TYPES: &'static str = r#"
37export type SymmetricKey = Tagged<string, "SymmetricKey">;
38"#;
39
40#[cfg(feature = "wasm")]
41impl wasm_bindgen::describe::WasmDescribe for SymmetricCryptoKey {
42    fn describe() {
43        <String as wasm_bindgen::describe::WasmDescribe>::describe();
44    }
45}
46
47#[cfg(feature = "wasm")]
48impl FromWasmAbi for SymmetricCryptoKey {
49    type Abi = <String as FromWasmAbi>::Abi;
50
51    unsafe fn from_abi(abi: Self::Abi) -> Self {
52        use wasm_bindgen::UnwrapThrowExt;
53        let string = unsafe { String::from_abi(abi) };
54        let b64 = B64::try_from(string).unwrap_throw();
55        SymmetricCryptoKey::try_from(b64).unwrap_throw()
56    }
57}
58
59#[cfg(feature = "wasm")]
60impl OptionFromWasmAbi for SymmetricCryptoKey {
61    fn is_none(abi: &Self::Abi) -> bool {
62        <String as OptionFromWasmAbi>::is_none(abi)
63    }
64}
65
66#[cfg(feature = "wasm")]
67impl IntoWasmAbi for SymmetricCryptoKey {
68    type Abi = <String as IntoWasmAbi>::Abi;
69
70    fn into_abi(self) -> Self::Abi {
71        let string: String = self.to_base64().to_string();
72        string.into_abi()
73    }
74}
75
76#[cfg(feature = "wasm")]
77impl TryFrom<wasm_bindgen::JsValue> for SymmetricCryptoKey {
78    type Error = CryptoError;
79
80    fn try_from(value: wasm_bindgen::JsValue) -> Result<Self, Self::Error> {
81        let string = value.as_string().ok_or(CryptoError::InvalidKey)?;
82        Self::try_from(string)
83    }
84}
85
86/// The symmetric key algorithm to use when generating a new symmetric key.
87#[derive(Debug, PartialEq)]
88pub enum SymmetricKeyAlgorithm {
89    /// Used for V1 user keys and data encryption
90    Aes256CbcHmac,
91    /// Used by old PIN PasswordProtectedKeyEnvelope
92    XChaCha20Poly1305,
93    /// FIPS-approved AEAD.
94    /// Used as content encryption key in:
95    /// [`DataEnvelope`](crate::safe::DataEnvelope)s.
96    /// [`PasswordProtectedKeyEnvelope`](crate::safe::PasswordProtectedKeyEnvelope)
97    ///
98    /// May not be used for multi-device scoped keys such as the user-key or organization-key
99    Aes256Gcm,
100    /// Extended-nonce AES-256-GCM. Used for V2 user keys and as a general-purpose
101    /// encryption/wrapping key.
102    XAes256Gcm,
103}
104
105/// [Aes256CbcKey] is a symmetric encryption key, consisting of one 256-bit key,
106/// used to decrypt legacy type 0 enc strings. The data is not authenticated
107/// so this should be used with caution, and removed where possible.
108#[derive(ZeroizeOnDrop, Clone)]
109pub struct Aes256CbcKey {
110    /// Uses a pinned heap data structure, as noted in [Pinned heap data][crate#pinned-heap-data]
111    pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
112}
113
114impl ConstantTimeEq for Aes256CbcKey {
115    fn ct_eq(&self, other: &Self) -> Choice {
116        self.enc_key.ct_eq(&other.enc_key)
117    }
118}
119
120impl PartialEq for Aes256CbcKey {
121    fn eq(&self, other: &Self) -> bool {
122        self.ct_eq(other).into()
123    }
124}
125
126/// [Aes256CbcHmacKey] is a symmetric encryption key consisting
127/// of two 256-bit keys, one for encryption and one for MAC
128#[derive(ZeroizeOnDrop, Clone)]
129pub struct Aes256CbcHmacKey {
130    /// Uses a pinned heap data structure, as noted in [Pinned heap data][crate#pinned-heap-data]
131    pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
132    /// Uses a pinned heap data structure, as noted in [Pinned heap data][crate#pinned-heap-data]
133    pub(crate) mac_key: Pin<Box<Array<u8, U32>>>,
134}
135
136impl ConstantTimeEq for Aes256CbcHmacKey {
137    fn ct_eq(&self, other: &Self) -> Choice {
138        self.enc_key.ct_eq(&other.enc_key) & self.mac_key.ct_eq(&other.mac_key)
139    }
140}
141
142impl Aes256CbcHmacKey {
143    /// Returns the 64-byte composite key (`enc_key || mac_key`) in the layout expected by the
144    /// [`Aes256CbcHmacSha256`](crate::hazmat::symmetric_encryption::Aes256CbcHmacSha256) cipher.
145    pub(crate) fn to_composite_key(&self) -> Zeroizing<[u8; 64]> {
146        let mut key = Zeroizing::new([0u8; 64]);
147        key[..32].copy_from_slice(&self.enc_key);
148        key[32..].copy_from_slice(&self.mac_key);
149        key
150    }
151}
152
153impl PartialEq for Aes256CbcHmacKey {
154    fn eq(&self, other: &Self) -> bool {
155        self.ct_eq(other).into()
156    }
157}
158
159/// [XChaCha20Poly1305Key] is a symmetric encryption key consisting
160/// of one 256-bit key, and contains a key id. In contrast to the
161/// [Aes256CbcKey] and [Aes256CbcHmacKey], this key type is used to create
162/// CoseEncrypt0 messages.
163#[derive(Zeroize, Clone)]
164pub struct XChaCha20Poly1305Key {
165    pub(crate) key_id: KeyId,
166    pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
167    /// Controls which key operations are allowed with this key. Note: Only checking decrypt is
168    /// implemented right now, and implementing is tracked here <https://bitwarden.atlassian.net/browse/PM-27513>.
169    /// Further, disabling decrypt will also disable unwrap. The only use-case so far is
170    /// `DataEnvelope`.
171    #[zeroize(skip)]
172    pub(crate) supported_operations: Vec<KeyOperation>,
173}
174
175impl XChaCha20Poly1305Key {
176    /// Creates a new XChaCha20Poly1305Key with a securely sampled cryptographic key and key id.
177    pub fn make() -> Self {
178        let mut rng = bitwarden_random::rng();
179        let mut enc_key = Box::pin(Array::<u8, U32>::default());
180        rng.fill(enc_key.as_mut_slice());
181        let key_id = KeyId::make();
182
183        Self {
184            enc_key,
185            key_id,
186            supported_operations: vec![
187                KeyOperation::Decrypt,
188                KeyOperation::Encrypt,
189                KeyOperation::WrapKey,
190                KeyOperation::UnwrapKey,
191            ],
192        }
193    }
194}
195
196impl ConstantTimeEq for XChaCha20Poly1305Key {
197    fn ct_eq(&self, other: &Self) -> Choice {
198        self.enc_key.ct_eq(&other.enc_key) & self.key_id.ct_eq(&other.key_id)
199    }
200}
201
202impl PartialEq for XChaCha20Poly1305Key {
203    fn eq(&self, other: &Self) -> bool {
204        self.ct_eq(other).into()
205    }
206}
207
208/// [Aes256GcmKey] is a symmetric AEAD key consisting of one 256-bit key
209#[derive(Zeroize, Clone)]
210pub struct Aes256GcmKey {
211    pub(crate) key_id: KeyId,
212    pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
213    /// Controls which key operations are allowed with this key. See
214    /// [`XChaCha20Poly1305Key::supported_operations`].
215    #[zeroize(skip)]
216    pub(crate) supported_operations: Vec<KeyOperation>,
217}
218
219impl Aes256GcmKey {
220    /// Creates a new Aes256GcmKey with a securely sampled cryptographic key and key id.
221    pub fn make() -> Self {
222        let mut rng = bitwarden_random::rng();
223        let mut enc_key = Box::pin(Array::<u8, U32>::default());
224        rng.fill(enc_key.as_mut_slice());
225        let key_id = KeyId::make();
226
227        Self {
228            enc_key,
229            key_id,
230            supported_operations: vec![
231                KeyOperation::Decrypt,
232                KeyOperation::Encrypt,
233                KeyOperation::WrapKey,
234                KeyOperation::UnwrapKey,
235            ],
236        }
237    }
238
239    pub(crate) fn disable_key_operation(&mut self, op: KeyOperation) -> &mut Self {
240        self.supported_operations.retain(|k| *k != op);
241        self
242    }
243}
244
245impl ConstantTimeEq for Aes256GcmKey {
246    fn ct_eq(&self, other: &Self) -> Choice {
247        self.enc_key.ct_eq(&other.enc_key) & self.key_id.ct_eq(&other.key_id)
248    }
249}
250
251impl PartialEq for Aes256GcmKey {
252    fn eq(&self, other: &Self) -> bool {
253        self.ct_eq(other).into()
254    }
255}
256
257/// An XAES-256-GCM key consisting of one 256-bit key and a key ID.
258#[derive(Zeroize, Clone)]
259pub struct XAes256GcmKey {
260    pub(crate) key_id: KeyId,
261    pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
262    #[zeroize(skip)]
263    pub(crate) supported_operations: Vec<KeyOperation>,
264}
265
266impl XAes256GcmKey {
267    /// Creates a new XAES-256-GCM key with securely sampled key bytes and key ID.
268    pub fn make() -> Self {
269        let mut rng = bitwarden_random::rng();
270        let mut enc_key = Box::pin(Array::<u8, U32>::default());
271        rng.fill(enc_key.as_mut_slice());
272
273        Self {
274            key_id: KeyId::make(),
275            enc_key,
276            supported_operations: vec![
277                KeyOperation::Decrypt,
278                KeyOperation::Encrypt,
279                KeyOperation::WrapKey,
280                KeyOperation::UnwrapKey,
281            ],
282        }
283    }
284}
285
286impl ConstantTimeEq for XAes256GcmKey {
287    fn ct_eq(&self, other: &Self) -> Choice {
288        self.enc_key.ct_eq(&other.enc_key) & self.key_id.ct_eq(&other.key_id)
289    }
290}
291
292impl PartialEq for XAes256GcmKey {
293    fn eq(&self, other: &Self) -> bool {
294        self.ct_eq(other).into()
295    }
296}
297
298/// A borrowed view over a symmetric key that is encoded as a COSE key.
299pub(crate) enum CoseKeyView<'a> {
300    Aes256Gcm(&'a Aes256GcmKey),
301    XChaCha20Poly1305(&'a XChaCha20Poly1305Key),
302    XAes256Gcm(&'a XAes256GcmKey),
303}
304
305impl CoseKeyView<'_> {
306    pub(crate) fn key_id(&self) -> &KeyId {
307        match self {
308            CoseKeyView::Aes256Gcm(k) => &k.key_id,
309            CoseKeyView::XChaCha20Poly1305(k) => &k.key_id,
310            CoseKeyView::XAes256Gcm(k) => &k.key_id,
311        }
312    }
313
314    pub(crate) fn key_bytes(&self) -> &[u8] {
315        match self {
316            CoseKeyView::Aes256Gcm(k) => k.enc_key.as_slice(),
317            CoseKeyView::XChaCha20Poly1305(k) => k.enc_key.as_slice(),
318            CoseKeyView::XAes256Gcm(k) => k.enc_key.as_slice(),
319        }
320    }
321
322    pub(crate) fn algorithm(&self) -> CoseContentEncryptionAlgorithm {
323        match self {
324            CoseKeyView::Aes256Gcm(_) => CoseContentEncryptionAlgorithm::Aes256Gcm,
325            CoseKeyView::XChaCha20Poly1305(_) => CoseContentEncryptionAlgorithm::XChaCha20Poly1305,
326            CoseKeyView::XAes256Gcm(_) => CoseContentEncryptionAlgorithm::XAes256Gcm,
327        }
328    }
329}
330
331/// A symmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::EncString)
332#[derive(ZeroizeOnDrop, Clone)]
333pub enum SymmetricCryptoKey {
334    #[allow(missing_docs)]
335    Aes256CbcKey(Aes256CbcKey),
336    #[allow(missing_docs)]
337    Aes256CbcHmacKey(Aes256CbcHmacKey),
338    /// Data encrypted by XChaCha20Poly1305Key keys has type
339    /// [`Cose_Encrypt0_B64`](crate::EncString::Cose_Encrypt0_B64)
340    XChaCha20Poly1305Key(XChaCha20Poly1305Key),
341    /// FIPS-approved AES-256-GCM key, used as the content-encryption key for FIPS
342    /// [`DataEnvelope`](crate::safe::DataEnvelope)s. Encoded as a COSE key.
343    Aes256GcmKey(Aes256GcmKey),
344    /// Extended-nonce AES-256-GCM key. Encoded as a COSE key and used with
345    /// type-7 EncStrings.
346    XAes256GcmKey(XAes256GcmKey),
347}
348
349impl SymmetricCryptoKey {
350    // enc type 0 old static format
351    const AES256_CBC_KEY_LEN: usize = 32;
352    // enc type 2 old static format
353    const AES256_CBC_HMAC_KEY_LEN: usize = 64;
354
355    /// Generate a new random AES256_CBC [SymmetricCryptoKey]
356    ///
357    /// WARNING: This function should only be used with a proper cryptographic RNG. If you do not
358    /// have a good reason for using this function, use
359    /// [SymmetricCryptoKey::make_aes256_cbc_hmac_key] instead.
360    pub(crate) fn make_aes256_cbc_hmac_key_internal(mut rng: impl rand::CryptoRng) -> Self {
361        let mut enc_key = Box::pin(Array::<u8, U32>::default());
362        let mut mac_key = Box::pin(Array::<u8, U32>::default());
363
364        rng.fill(enc_key.as_mut_slice());
365        rng.fill(mac_key.as_mut_slice());
366
367        Self::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key })
368    }
369
370    /// Make a new [SymmetricCryptoKey] for the specified algorithm
371    pub fn make(algorithm: SymmetricKeyAlgorithm) -> Self {
372        match algorithm {
373            SymmetricKeyAlgorithm::Aes256CbcHmac => Self::make_aes256_cbc_hmac_key(),
374            SymmetricKeyAlgorithm::XChaCha20Poly1305 => Self::make_xchacha20_poly1305_key(),
375            SymmetricKeyAlgorithm::Aes256Gcm => Self::Aes256GcmKey(Aes256GcmKey::make()),
376            SymmetricKeyAlgorithm::XAes256Gcm => Self::XAes256GcmKey(XAes256GcmKey::make()),
377        }
378    }
379
380    /// Generate a new random AES256_CBC_HMAC [SymmetricCryptoKey]
381    pub(crate) fn make_aes256_cbc_hmac_key() -> Self {
382        let rng = bitwarden_random::rng();
383        Self::make_aes256_cbc_hmac_key_internal(rng)
384    }
385
386    /// Generate a new random XChaCha20Poly1305 [SymmetricCryptoKey]
387    pub(crate) fn make_xchacha20_poly1305_key() -> Self {
388        let mut rng = bitwarden_random::rng();
389        let mut enc_key = Box::pin(Array::<u8, U32>::default());
390        rng.fill(enc_key.as_mut_slice());
391        Self::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
392            enc_key,
393            key_id: KeyId::make(),
394            supported_operations: vec![
395                KeyOperation::Decrypt,
396                KeyOperation::Encrypt,
397                KeyOperation::WrapKey,
398                KeyOperation::UnwrapKey,
399            ],
400        })
401    }
402
403    /// Encodes the key to a byte array representation, that is separated by size.
404    /// [SymmetricCryptoKey::Aes256CbcHmacKey] and [SymmetricCryptoKey::Aes256CbcKey] are
405    /// encoded as 64 and 32 bytes respectively. COSE-serialized variants are encoded as at least
406    /// 65 bytes using padding.
407    ///
408    /// This can be used for storage and transmission in the old byte array format.
409    /// When the wrapping key is a COSE key, and the wrapped key is a COSE key, then this should
410    /// not use the byte representation but instead use the COSE key representation.
411    pub fn to_encoded(&self) -> BitwardenLegacyKeyBytes {
412        let encoded_key = self.to_encoded_raw();
413        match encoded_key {
414            EncodedSymmetricKey::BitwardenLegacyKey(_) => {
415                let encoded_key: Vec<u8> = encoded_key.into();
416                BitwardenLegacyKeyBytes::from(encoded_key)
417            }
418            EncodedSymmetricKey::CoseKey(_) => {
419                let mut encoded_key: Vec<u8> = encoded_key.into();
420                pad_key(&mut encoded_key, (Self::AES256_CBC_HMAC_KEY_LEN + 1) as u8); // This is less than 255
421                BitwardenLegacyKeyBytes::from(encoded_key)
422            }
423        }
424    }
425
426    /// Generate a new random [SymmetricCryptoKey] for unit tests. Note: DO NOT USE THIS
427    /// IN PRODUCTION CODE.
428    #[cfg(test)]
429    pub fn generate_seeded_for_unit_tests(seed: &str) -> Self {
430        // Keep this separate from the other generate function to not break test vectors.
431        let mut seeded_rng = ChaChaRng::from_seed(sha2::Sha256::digest(seed.as_bytes()).into());
432        let mut enc_key = Box::pin(Array::<u8, U32>::default());
433        let mut mac_key = Box::pin(Array::<u8, U32>::default());
434
435        seeded_rng.fill(enc_key.as_mut_slice());
436        seeded_rng.fill(mac_key.as_mut_slice());
437
438        SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key })
439    }
440
441    /// Creates the byte representation of the key, without any padding. This should not
442    /// be used directly for creating serialized key representations, instead,
443    /// [SymmetricCryptoKey::to_encoded] should be used.
444    ///
445    /// [SymmetricCryptoKey::Aes256CbcHmacKey] and
446    /// [SymmetricCryptoKey::Aes256CbcKey] are encoded as 64 and 32 byte arrays
447    /// respectively, representing the key bytes directly. XChaCha20-Poly1305,
448    /// AES-256-GCM, and XAES-256-GCM keys are encoded as serialized COSE keys.
449    /// A COSE key can be directly encrypted with a COSE key content format or
450    /// represented as a byte array. When represented as a byte array, the array
451    /// is padded to be larger than the byte array representation of the other
452    /// aforementioned key types.
453    pub(crate) fn to_encoded_raw(&self) -> EncodedSymmetricKey {
454        match self {
455            Self::Aes256CbcKey(key) => {
456                EncodedSymmetricKey::BitwardenLegacyKey(key.enc_key.to_vec().into())
457            }
458            Self::Aes256CbcHmacKey(key) => {
459                let mut buf = Vec::with_capacity(64);
460                buf.extend_from_slice(&key.enc_key);
461                buf.extend_from_slice(&key.mac_key);
462                EncodedSymmetricKey::BitwardenLegacyKey(buf.into())
463            }
464            Self::XChaCha20Poly1305Key(key) => {
465                let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec());
466                let mut cose_key = builder.key_id((&key.key_id).into());
467                for op in &key.supported_operations {
468                    cose_key = cose_key.add_key_op(*op);
469                }
470                let mut cose_key = cose_key.build();
471                cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse(
472                    cose::XCHACHA20_POLY1305,
473                ));
474                EncodedSymmetricKey::CoseKey(
475                    cose_key
476                        .to_vec()
477                        .expect("cose key serialization should not fail")
478                        .into(),
479                )
480            }
481            Self::XAes256GcmKey(key) => {
482                let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec());
483                let mut cose_key = builder.key_id((&key.key_id).into());
484                for op in &key.supported_operations {
485                    cose_key = cose_key.add_key_op(*op);
486                }
487                let mut cose_key = cose_key.build();
488                cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse(cose::XAES_256_GCM));
489                EncodedSymmetricKey::CoseKey(
490                    cose_key
491                        .to_vec()
492                        .expect("cose key serialization should not fail")
493                        .into(),
494                )
495            }
496            Self::Aes256GcmKey(key) => {
497                let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec());
498                let mut cose_key = builder.key_id((&key.key_id).into());
499                for op in &key.supported_operations {
500                    cose_key = cose_key.add_key_op(*op);
501                }
502                let mut cose_key = cose_key.build();
503                cose_key.alg = Some(RegisteredLabelWithPrivate::Assigned(
504                    coset::iana::Algorithm::A256GCM,
505                ));
506                EncodedSymmetricKey::CoseKey(
507                    cose_key
508                        .to_vec()
509                        .expect("cose key serialization should not fail")
510                        .into(),
511                )
512            }
513        }
514    }
515
516    pub(crate) fn try_from_cose(serialized_key: &[u8]) -> Result<Self, CryptoError> {
517        let cose_key =
518            coset::CoseKey::from_slice(serialized_key).map_err(|_| CryptoError::InvalidKey)?;
519        let key = SymmetricCryptoKey::try_from(&cose_key)?;
520        Ok(key)
521    }
522
523    #[allow(missing_docs)]
524    pub fn to_base64(&self) -> B64 {
525        B64::from(self.to_encoded().as_ref())
526    }
527
528    /// Returns the key ID of the key, if it has one. COSE-serialized key variants have a key ID.
529    pub fn key_id(&self) -> Option<KeyId> {
530        match self {
531            Self::Aes256CbcKey(_) => None,
532            Self::Aes256CbcHmacKey(_) => None,
533            Self::XChaCha20Poly1305Key(key) => Some(key.key_id.clone()),
534            Self::Aes256GcmKey(key) => Some(key.key_id.clone()),
535            Self::XAes256GcmKey(key) => Some(key.key_id.clone()),
536        }
537    }
538
539    /// Returns a [`CoseKeyView`] for COSE-encoded symmetric key variants.
540    /// Legacy AES-CBC variants return `None`.
541    pub(crate) fn as_cose_key_view(&self) -> Option<CoseKeyView<'_>> {
542        match self {
543            Self::Aes256GcmKey(k) => Some(CoseKeyView::Aes256Gcm(k)),
544            Self::XChaCha20Poly1305Key(k) => Some(CoseKeyView::XChaCha20Poly1305(k)),
545            Self::XAes256GcmKey(k) => Some(CoseKeyView::XAes256Gcm(k)),
546            Self::Aes256CbcKey(_) | Self::Aes256CbcHmacKey(_) => None,
547        }
548    }
549}
550
551impl CoseKeyThumbprintExt for SymmetricCryptoKey {
552    /// Computes the RFC 9679 thumbprint of this symmetric key.
553    ///
554    /// Returns an error for legacy AES-CBC keys, which are not currently representable as COSE
555    /// keys.
556    fn thumbprint(&self) -> Result<CoseKeyThumbprint, CryptoError> {
557        let view = self
558            .as_cose_key_view()
559            .ok_or(EncodingError::UnsupportedValue(
560                "legacy AES-CBC keys are not COSE keys and have no thumbprint",
561            ))?;
562        let params = vec![
563            (
564                KeyParameter::Kty.to_i64(),
565                Value::Integer(Integer::from(KeyType::Symmetric.to_i64())),
566            ),
567            (
568                SymmetricKeyParameter::K.to_i64(),
569                Value::Bytes(view.key_bytes().to_vec()),
570            ),
571        ];
572        Ok(thumbprint_from_required_params(params))
573    }
574}
575
576impl ConstantTimeEq for SymmetricCryptoKey {
577    /// Note: This is constant time with respect to comparing two keys of the same type, but not
578    /// constant type with respect to the fact that different keys are compared. If two types of
579    /// different keys are compared, then this does have different timing.
580    fn ct_eq(&self, other: &SymmetricCryptoKey) -> Choice {
581        use SymmetricCryptoKey::*;
582        match (self, other) {
583            (Aes256CbcKey(a), Aes256CbcKey(b)) => a.ct_eq(b),
584            (Aes256CbcKey(_), _) => Choice::from(0),
585
586            (Aes256CbcHmacKey(a), Aes256CbcHmacKey(b)) => a.ct_eq(b),
587            (Aes256CbcHmacKey(_), _) => Choice::from(0),
588
589            (XChaCha20Poly1305Key(a), XChaCha20Poly1305Key(b)) => a.ct_eq(b),
590            (XChaCha20Poly1305Key(_), _) => Choice::from(0),
591
592            (Aes256GcmKey(a), Aes256GcmKey(b)) => a.ct_eq(b),
593            (Aes256GcmKey(_), _) => Choice::from(0),
594
595            (XAes256GcmKey(a), XAes256GcmKey(b)) => a.ct_eq(b),
596            (XAes256GcmKey(_), _) => Choice::from(0),
597        }
598    }
599}
600
601impl PartialEq for SymmetricCryptoKey {
602    fn eq(&self, other: &Self) -> bool {
603        self.ct_eq(other).into()
604    }
605}
606
607impl TryFrom<String> for SymmetricCryptoKey {
608    type Error = CryptoError;
609
610    fn try_from(value: String) -> Result<Self, Self::Error> {
611        let bytes = B64::try_from(value).map_err(|_| CryptoError::InvalidKey)?;
612        Self::try_from(bytes)
613    }
614}
615
616impl TryFrom<B64> for SymmetricCryptoKey {
617    type Error = CryptoError;
618
619    fn try_from(value: B64) -> Result<Self, Self::Error> {
620        Self::try_from(&BitwardenLegacyKeyBytes::from(&value))
621    }
622}
623
624impl TryFrom<&BitwardenLegacyKeyBytes> for SymmetricCryptoKey {
625    type Error = CryptoError;
626
627    fn try_from(value: &BitwardenLegacyKeyBytes) -> Result<Self, Self::Error> {
628        let slice = value.as_ref();
629
630        // Raw byte serialized keys are either 32, 64, or more bytes long. If they are 32/64, they
631        // are the raw serializations of the AES256-CBC, and AES256-CBC-HMAC keys. If they
632        // are longer, they are COSE keys. The COSE keys are padded to the minimum length of
633        // 65 bytes, when serialized to raw byte arrays.
634
635        if slice.len() == Self::AES256_CBC_HMAC_KEY_LEN || slice.len() == Self::AES256_CBC_KEY_LEN {
636            Self::try_from(EncodedSymmetricKey::BitwardenLegacyKey(value.clone()))
637        } else if slice.len() > Self::AES256_CBC_HMAC_KEY_LEN {
638            let unpadded_value = unpad_key(slice)?;
639            Ok(Self::try_from_cose(unpadded_value)?)
640        } else {
641            Err(CryptoError::InvalidKeyLen)
642        }
643    }
644}
645
646impl TryFrom<EncodedSymmetricKey> for SymmetricCryptoKey {
647    type Error = CryptoError;
648
649    fn try_from(value: EncodedSymmetricKey) -> Result<Self, Self::Error> {
650        match value {
651            EncodedSymmetricKey::BitwardenLegacyKey(key)
652                if key.as_ref().len() == Self::AES256_CBC_KEY_LEN =>
653            {
654                let mut enc_key = Box::pin(Array::<u8, U32>::default());
655                enc_key.copy_from_slice(&key.as_ref()[..Self::AES256_CBC_KEY_LEN]);
656                Ok(Self::Aes256CbcKey(Aes256CbcKey { enc_key }))
657            }
658            EncodedSymmetricKey::BitwardenLegacyKey(key)
659                if key.as_ref().len() == Self::AES256_CBC_HMAC_KEY_LEN =>
660            {
661                let mut enc_key = Box::pin(Array::<u8, U32>::default());
662                enc_key.copy_from_slice(&key.as_ref()[..32]);
663
664                let mut mac_key = Box::pin(Array::<u8, U32>::default());
665                mac_key.copy_from_slice(&key.as_ref()[32..]);
666
667                Ok(Self::Aes256CbcHmacKey(Aes256CbcHmacKey {
668                    enc_key,
669                    mac_key,
670                }))
671            }
672            EncodedSymmetricKey::CoseKey(key) => Self::try_from_cose(key.as_ref()),
673            _ => Err(CryptoError::InvalidKey),
674        }
675    }
676}
677
678impl CryptoKey for SymmetricCryptoKey {}
679
680// We manually implement these to make sure we don't print any sensitive data
681impl std::fmt::Debug for SymmetricCryptoKey {
682    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
683        match self {
684            SymmetricCryptoKey::Aes256CbcKey(key) => key.fmt(f),
685            SymmetricCryptoKey::Aes256CbcHmacKey(key) => key.fmt(f),
686            SymmetricCryptoKey::XChaCha20Poly1305Key(key) => key.fmt(f),
687            SymmetricCryptoKey::Aes256GcmKey(key) => key.fmt(f),
688            SymmetricCryptoKey::XAes256GcmKey(key) => key.fmt(f),
689        }
690    }
691}
692
693impl std::fmt::Debug for Aes256CbcKey {
694    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
695        let mut debug_struct = f.debug_struct("SymmetricKey::Aes256Cbc");
696        #[cfg(feature = "dangerous-crypto-debug")]
697        debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
698        debug_struct.finish()
699    }
700}
701
702impl std::fmt::Debug for Aes256CbcHmacKey {
703    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
704        let mut debug_struct = f.debug_struct("SymmetricKey::Aes256CbcHmac");
705        #[cfg(feature = "dangerous-crypto-debug")]
706        debug_struct
707            .field("enc_key", &hex::encode(self.enc_key.as_slice()))
708            .field("mac_key", &hex::encode(self.mac_key.as_slice()));
709        debug_struct.finish()
710    }
711}
712
713impl std::fmt::Debug for XChaCha20Poly1305Key {
714    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
715        let mut debug_struct = f.debug_struct("SymmetricKey::XChaCha20Poly1305");
716        debug_struct.field("key_id", &self.key_id);
717        debug_struct.field(
718            "supported_operations",
719            &self
720                .supported_operations
721                .iter()
722                .map(|key_operation: &KeyOperation| cose::debug_key_operation(*key_operation))
723                .collect::<Vec<_>>(),
724        );
725        #[cfg(feature = "dangerous-crypto-debug")]
726        debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
727        debug_struct.finish()
728    }
729}
730
731impl std::fmt::Debug for XAes256GcmKey {
732    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
733        let mut debug_struct = f.debug_struct("SymmetricKey::XAes256Gcm");
734        debug_struct.field("key_id", &self.key_id);
735        debug_struct.field(
736            "supported_operations",
737            &self
738                .supported_operations
739                .iter()
740                .map(|key_operation: &KeyOperation| cose::debug_key_operation(*key_operation))
741                .collect::<Vec<_>>(),
742        );
743        #[cfg(feature = "dangerous-crypto-debug")]
744        debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
745        debug_struct.finish()
746    }
747}
748
749impl std::fmt::Debug for Aes256GcmKey {
750    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
751        let mut debug_struct = f.debug_struct("SymmetricKey::Aes256Gcm");
752        debug_struct.field("key_id", &self.key_id);
753        debug_struct.field(
754            "supported_operations",
755            &self
756                .supported_operations
757                .iter()
758                .map(|key_operation: &KeyOperation| cose::debug_key_operation(*key_operation))
759                .collect::<Vec<_>>(),
760        );
761        #[cfg(feature = "dangerous-crypto-debug")]
762        debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
763        debug_struct.finish()
764    }
765}
766
767/// Pad a key to a minimum length using PKCS7-like padding.
768/// The last N bytes of the padded bytes all have the value N.
769/// For example, padded to size 4, the value 0,0 becomes 0,0,2,2.
770///
771/// COSE-serialized keys must be distinguishable from
772/// [SymmetricCryptoKey::Aes256CbcHmacKey] keys when encoded as byte arrays with no additional
773/// content format. Padding ensures that the byte representation uniquely separates keys by size.
774/// The previous key types [SymmetricCryptoKey::Aes256CbcHmacKey] and
775/// [SymmetricCryptoKey::Aes256CbcKey] are 64 and 32 bytes long respectively.
776fn pad_key(key_bytes: &mut Vec<u8>, min_length: u8) {
777    crate::keys::utils::pad_bytes(key_bytes, min_length as usize)
778        .expect("Padding cannot fail since the min_length is < 255")
779}
780
781/// Unpad a key that is padded using the PKCS7-like padding defined by [pad_key].
782/// The last N bytes of the padded bytes all have the value N.
783/// For example, padded to size 4, the value 0,0 becomes 0,0,2,2.
784///
785/// COSE-serialized keys must be distinguishable from
786/// [SymmetricCryptoKey::Aes256CbcHmacKey] keys when encoded as byte arrays with no additional
787/// content format. Padding ensures that the byte representation uniquely separates keys by size.
788/// The previous key types [SymmetricCryptoKey::Aes256CbcHmacKey] and
789/// [SymmetricCryptoKey::Aes256CbcKey] are 64 and 32 bytes long respectively.
790fn unpad_key(key_bytes: &[u8]) -> Result<&[u8], CryptoError> {
791    crate::keys::utils::unpad_bytes(key_bytes).map_err(|_| CryptoError::InvalidKey)
792}
793
794/// Encoded representation of [SymmetricCryptoKey]
795pub enum EncodedSymmetricKey {
796    /// An Aes256-CBC-HMAC key, or a Aes256-CBC key
797    BitwardenLegacyKey(BitwardenLegacyKeyBytes),
798    /// A symmetric key encoded as a COSE key
799    CoseKey(CoseKeyBytes),
800}
801impl From<EncodedSymmetricKey> for Vec<u8> {
802    fn from(val: EncodedSymmetricKey) -> Self {
803        match val {
804            EncodedSymmetricKey::BitwardenLegacyKey(key) => key.to_vec(),
805            EncodedSymmetricKey::CoseKey(key) => key.to_vec(),
806        }
807    }
808}
809impl EncodedSymmetricKey {
810    /// Returns the content format of the encoded symmetric key.
811    #[allow(private_interfaces)]
812    pub fn content_format(&self) -> ContentFormat {
813        match self {
814            EncodedSymmetricKey::BitwardenLegacyKey(_) => ContentFormat::BitwardenLegacyKey,
815            EncodedSymmetricKey::CoseKey(_) => ContentFormat::CoseKey,
816        }
817    }
818}
819
820// Note: Deserialize and Serialize are only implemented until external usages of
821// symmetric crypto keys are removed. We do not want to support these, but while
822// these have to be supported, we want to have type-safety over having raw byte
823// arrays.
824impl<'de> Deserialize<'de> for SymmetricCryptoKey {
825    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
826    where
827        D: serde::Deserializer<'de>,
828    {
829        deserializer.deserialize_str(FromStrVisitor::new())
830    }
831}
832
833impl FromStr for SymmetricCryptoKey {
834    type Err = CryptoError;
835
836    fn from_str(s: &str) -> Result<Self, Self::Err> {
837        let bytes = B64::try_from(s.to_string()).map_err(|_| CryptoError::InvalidKey)?;
838        Self::try_from(bytes).map_err(|_| CryptoError::InvalidKey)
839    }
840}
841
842impl Serialize for SymmetricCryptoKey {
843    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
844    where
845        S: serde::Serializer,
846    {
847        serializer.serialize_str(&self.to_base64().to_string())
848    }
849}
850
851/// Test only helper for deriving a symmetric key.
852#[cfg(test)]
853pub fn derive_symmetric_key(name: &str) -> Aes256CbcHmacKey {
854    use zeroize::Zeroizing;
855
856    use crate::{derive_shareable_key, generate_random_bytes};
857
858    let secret: Zeroizing<[u8; 16]> = generate_random_bytes();
859    derive_shareable_key(secret, name, None)
860}
861
862#[cfg(test)]
863mod tests {
864    use bitwarden_encoding::B64;
865    use coset::{CborSerializable, iana::KeyOperation};
866    use hybrid_array::Array;
867    use typenum::U32;
868
869    use super::{EncodedSymmetricKey, SymmetricCryptoKey, derive_symmetric_key};
870    use crate::{
871        Aes256CbcHmacKey, Aes256CbcKey, BitwardenLegacyKeyBytes, CoseKeyThumbprintExt,
872        SymmetricKeyAlgorithm, XAes256GcmKey, XChaCha20Poly1305Key,
873        keys::{
874            KeyId,
875            symmetric_crypto_key::{pad_key, unpad_key},
876        },
877    };
878
879    #[test]
880    #[ignore = "Manual test to verify debug format"]
881    fn test_key_debug() {
882        let aes_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
883        println!("{:?}", aes_key);
884        let xchacha_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
885        println!("{:?}", xchacha_key);
886    }
887
888    #[test]
889    fn test_serialize_deserialize_symmetric_crypto_key() {
890        let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
891        let serialized = serde_json::to_string(&key).unwrap();
892        let deserialized: SymmetricCryptoKey = serde_json::from_str(&serialized).unwrap();
893        assert_eq!(key, deserialized);
894    }
895
896    #[test]
897    fn test_symmetric_crypto_key() {
898        let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
899        let key2 = SymmetricCryptoKey::try_from(key.to_base64()).unwrap();
900
901        assert_eq!(key, key2);
902
903        let key = "UY4B5N4DA4UisCNClgZtRr6VLy9ZF5BXXC7cDZRqourKi4ghEMgISbCsubvgCkHf5DZctQjVot11/vVvN9NNHQ==".to_string();
904        let key2 = SymmetricCryptoKey::try_from(key.clone()).unwrap();
905        assert_eq!(key, key2.to_base64().to_string());
906    }
907
908    #[test]
909    fn test_encode_decode_old_symmetric_crypto_key() {
910        let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
911        let encoded = key.to_encoded();
912        let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
913        assert_eq!(key, decoded);
914    }
915
916    #[test]
917    fn test_decode_new_symmetric_crypto_key() {
918        let key: B64 = ("pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB").parse()
919        .unwrap();
920        let key = BitwardenLegacyKeyBytes::from(&key);
921        let key = SymmetricCryptoKey::try_from(&key).unwrap();
922        match key {
923            SymmetricCryptoKey::XChaCha20Poly1305Key(_) => (),
924            _ => panic!("Invalid key type"),
925        }
926    }
927
928    #[test]
929    fn test_encode_xchacha20_poly1305_key() {
930        let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
931        let encoded = key.to_encoded();
932        let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
933        assert_eq!(key, decoded);
934    }
935
936    #[test]
937    fn test_pad_unpad_key_63() {
938        let original_key = vec![1u8; 63];
939        let mut key_bytes = original_key.clone();
940        let mut encoded_bytes = vec![1u8; 65];
941        encoded_bytes[63] = 2;
942        encoded_bytes[64] = 2;
943        pad_key(&mut key_bytes, 65);
944        assert_eq!(encoded_bytes, key_bytes);
945        let unpadded_key = unpad_key(&key_bytes).unwrap();
946        assert_eq!(original_key, unpadded_key);
947    }
948
949    #[test]
950    fn test_pad_unpad_key_64() {
951        let original_key = vec![1u8; 64];
952        let mut key_bytes = original_key.clone();
953        let mut encoded_bytes = vec![1u8; 65];
954        encoded_bytes[64] = 1;
955        pad_key(&mut key_bytes, 65);
956        assert_eq!(encoded_bytes, key_bytes);
957        let unpadded_key = unpad_key(&key_bytes).unwrap();
958        assert_eq!(original_key, unpadded_key);
959    }
960
961    #[test]
962    fn test_pad_unpad_key_65() {
963        let original_key = vec![1u8; 65];
964        let mut key_bytes = original_key.clone();
965        let mut encoded_bytes = vec![1u8; 66];
966        encoded_bytes[65] = 1;
967        pad_key(&mut key_bytes, 65);
968        assert_eq!(encoded_bytes, key_bytes);
969        let unpadded_key = unpad_key(&key_bytes).unwrap();
970        assert_eq!(original_key, unpadded_key);
971    }
972
973    #[test]
974    fn test_eq_aes_cbc_hmac() {
975        let key1 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
976        let key2 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
977        assert_ne!(key1, key2);
978        let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
979        assert_eq!(key1, key3);
980    }
981
982    #[test]
983    fn test_eq_aes_cbc() {
984        let key1 =
985            SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![1u8; 32])).unwrap();
986        let key2 =
987            SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![2u8; 32])).unwrap();
988        assert_ne!(key1, key2);
989        let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
990        assert_eq!(key1, key3);
991    }
992
993    #[test]
994    fn test_eq_xchacha20_poly1305() {
995        let key1 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
996        let key2 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
997        assert_ne!(key1, key2);
998        let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
999        assert_eq!(key1, key3);
1000    }
1001
1002    #[test]
1003    fn test_neq_different_key_types() {
1004        let key1 = SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey {
1005            enc_key: Box::pin(Array::<u8, U32>::default()),
1006        });
1007        let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
1008            enc_key: Box::pin(Array::<u8, U32>::default()),
1009            key_id: KeyId::from([0; 16]),
1010            supported_operations: vec![
1011                KeyOperation::Decrypt,
1012                KeyOperation::Encrypt,
1013                KeyOperation::WrapKey,
1014                KeyOperation::UnwrapKey,
1015            ],
1016        });
1017        assert_ne!(key1, key2);
1018    }
1019
1020    #[test]
1021    fn test_eq_variant_aes256_cbc() {
1022        let key1 = Aes256CbcKey {
1023            enc_key: Box::pin(Array::from([1u8; 32])),
1024        };
1025        let key2 = Aes256CbcKey {
1026            enc_key: Box::pin(Array::from([1u8; 32])),
1027        };
1028        let key3 = Aes256CbcKey {
1029            enc_key: Box::pin(Array::from([2u8; 32])),
1030        };
1031        assert_eq!(key1, key2);
1032        assert_ne!(key1, key3);
1033    }
1034
1035    #[test]
1036    fn test_eq_variant_aes256_cbc_hmac() {
1037        let key1 = Aes256CbcHmacKey {
1038            enc_key: Box::pin(Array::from([1u8; 32])),
1039            mac_key: Box::pin(Array::from([2u8; 32])),
1040        };
1041        let key2 = Aes256CbcHmacKey {
1042            enc_key: Box::pin(Array::from([1u8; 32])),
1043            mac_key: Box::pin(Array::from([2u8; 32])),
1044        };
1045        let key3 = Aes256CbcHmacKey {
1046            enc_key: Box::pin(Array::from([3u8; 32])),
1047            mac_key: Box::pin(Array::from([4u8; 32])),
1048        };
1049        assert_eq!(key1, key2);
1050        assert_ne!(key1, key3);
1051    }
1052
1053    #[test]
1054    fn test_eq_variant_xchacha20_poly1305() {
1055        let key1 = XChaCha20Poly1305Key {
1056            enc_key: Box::pin(Array::from([1u8; 32])),
1057            key_id: KeyId::from([0; 16]),
1058            supported_operations: vec![
1059                KeyOperation::Decrypt,
1060                KeyOperation::Encrypt,
1061                KeyOperation::WrapKey,
1062                KeyOperation::UnwrapKey,
1063            ],
1064        };
1065        let key2 = XChaCha20Poly1305Key {
1066            enc_key: Box::pin(Array::from([1u8; 32])),
1067            key_id: KeyId::from([0; 16]),
1068            supported_operations: vec![
1069                KeyOperation::Decrypt,
1070                KeyOperation::Encrypt,
1071                KeyOperation::WrapKey,
1072                KeyOperation::UnwrapKey,
1073            ],
1074        };
1075        let key3 = XChaCha20Poly1305Key {
1076            enc_key: Box::pin(Array::from([2u8; 32])),
1077            key_id: KeyId::from([1; 16]),
1078            supported_operations: vec![
1079                KeyOperation::Decrypt,
1080                KeyOperation::Encrypt,
1081                KeyOperation::WrapKey,
1082                KeyOperation::UnwrapKey,
1083            ],
1084        };
1085        assert_eq!(key1, key2);
1086        assert_ne!(key1, key3);
1087    }
1088
1089    fn fixed_xaes_key_inner() -> XAes256GcmKey {
1090        XAes256GcmKey {
1091            enc_key: Box::pin(Array::from([
1092                0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
1093                0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
1094                0x1c, 0x1d, 0x1e, 0x1f,
1095            ])),
1096            key_id: KeyId::from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
1097            supported_operations: vec![KeyOperation::Encrypt, KeyOperation::Decrypt],
1098        }
1099    }
1100
1101    fn fixed_xaes_key() -> SymmetricCryptoKey {
1102        SymmetricCryptoKey::XAes256GcmKey(fixed_xaes_key_inner())
1103    }
1104
1105    #[test]
1106    fn test_make_xaes256_gcm_key() {
1107        assert!(matches!(
1108            SymmetricCryptoKey::make(SymmetricKeyAlgorithm::XAes256Gcm),
1109            SymmetricCryptoKey::XAes256GcmKey(_)
1110        ));
1111    }
1112
1113    #[test]
1114    fn test_xaes256_gcm_encoding_roundtrips() {
1115        const PADDED_KEY: &str = "pQEEAlAAAQIDBAUGBwgJCgsMDQ4PAzoAARF5BIIDBCBYIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fAQ==";
1116
1117        let key = fixed_xaes_key();
1118        assert_eq!(key.to_base64().to_string(), PADDED_KEY);
1119        let padded = SymmetricCryptoKey::try_from(PADDED_KEY.to_owned()).unwrap();
1120        assert_eq!(padded, key);
1121        let SymmetricCryptoKey::XAes256GcmKey(ref padded) = padded else {
1122            panic!("expected XAES-256-GCM key");
1123        };
1124        assert_eq!(
1125            padded.supported_operations,
1126            [KeyOperation::Encrypt, KeyOperation::Decrypt]
1127        );
1128
1129        let EncodedSymmetricKey::CoseKey(raw) = key.to_encoded_raw() else {
1130            panic!("expected COSE key encoding");
1131        };
1132        assert_eq!(
1133            SymmetricCryptoKey::try_from_cose(raw.as_ref()).unwrap(),
1134            key
1135        );
1136
1137        let cose_key = coset::CoseKey::from_slice(raw.as_ref()).unwrap();
1138        assert_eq!(
1139            cose_key.alg,
1140            Some(coset::Algorithm::PrivateUse(crate::cose::XAES_256_GCM))
1141        );
1142        assert_eq!(cose_key.key_id, (0u8..16).collect::<Vec<_>>());
1143        assert_eq!(cose_key.key_ops.len(), 2);
1144        assert!(
1145            cose_key
1146                .key_ops
1147                .contains(&coset::RegisteredLabel::Assigned(KeyOperation::Encrypt))
1148        );
1149        assert!(
1150            cose_key
1151                .key_ops
1152                .contains(&coset::RegisteredLabel::Assigned(KeyOperation::Decrypt))
1153        );
1154    }
1155
1156    #[test]
1157    fn test_xaes256_gcm_equality() {
1158        let key = fixed_xaes_key();
1159        let same = fixed_xaes_key();
1160        assert_eq!(key, same);
1161
1162        let mut different_bytes = fixed_xaes_key_inner();
1163        different_bytes.enc_key[0] ^= 1;
1164        assert_ne!(key, SymmetricCryptoKey::XAes256GcmKey(different_bytes));
1165
1166        let mut different_id = fixed_xaes_key_inner();
1167        different_id.key_id = KeyId::from([1; 16]);
1168        assert_ne!(key, SymmetricCryptoKey::XAes256GcmKey(different_id));
1169
1170        for other in [
1171            SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey {
1172                enc_key: Box::pin(Array::default()),
1173            }),
1174            SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey {
1175                enc_key: Box::pin(Array::default()),
1176                mac_key: Box::pin(Array::default()),
1177            }),
1178            SymmetricCryptoKey::Aes256GcmKey(crate::Aes256GcmKey::make()),
1179            SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key::make()),
1180        ] {
1181            assert_ne!(key, other);
1182        }
1183    }
1184
1185    #[test]
1186    fn test_neq_different_key_id() {
1187        let key1 = XChaCha20Poly1305Key {
1188            enc_key: Box::pin(Array::<u8, U32>::default()),
1189            key_id: KeyId::from([0; 16]),
1190            supported_operations: vec![
1191                KeyOperation::Decrypt,
1192                KeyOperation::Encrypt,
1193                KeyOperation::WrapKey,
1194                KeyOperation::UnwrapKey,
1195            ],
1196        };
1197        let key2 = XChaCha20Poly1305Key {
1198            enc_key: Box::pin(Array::<u8, U32>::default()),
1199            key_id: KeyId::from([1; 16]),
1200            supported_operations: vec![
1201                KeyOperation::Decrypt,
1202                KeyOperation::Encrypt,
1203                KeyOperation::WrapKey,
1204                KeyOperation::UnwrapKey,
1205            ],
1206        };
1207        assert_ne!(key1, key2);
1208
1209        let key1 = SymmetricCryptoKey::XChaCha20Poly1305Key(key1);
1210        let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(key2);
1211        assert_ne!(key1, key2);
1212    }
1213
1214    const AES256_GCM_KEY: &str =
1215        "pQEEAlACAgICAgICAgICAgICAgICAwMEhAMEBQYgWCABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=";
1216    const AES256_GCM_KEY_THUMBPRINT: &str =
1217        "3810c7275ee292caca13d938a057a94c75210087d960d3eb6868c0ffe99b5643";
1218
1219    const XCHACHA20_POLY1305_KEY: &str = "pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB";
1220    const XCHACHA20_POLY1305_KEY_THUMBPRINT: &str =
1221        "64aec2d09ef5ba8b310ef9a70346b03422443e295b6f045e38169ae97e579d85";
1222
1223    #[test]
1224    fn test_decode_new_aes256_gcm_key() {
1225        let key: B64 = AES256_GCM_KEY.parse().unwrap();
1226        let key = SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(&key)).unwrap();
1227        match key {
1228            SymmetricCryptoKey::Aes256GcmKey(_) => (),
1229            _ => panic!("Invalid key type"),
1230        }
1231    }
1232
1233    #[test]
1234    fn test_thumbprint_aes256_gcm_vector() {
1235        // A fixed AES-256-GCM COSE key.
1236        let key: B64 = AES256_GCM_KEY.parse().unwrap();
1237        let key = SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(&key)).unwrap();
1238        assert_eq!(
1239            key.thumbprint().unwrap().to_hex(),
1240            AES256_GCM_KEY_THUMBPRINT
1241        );
1242    }
1243
1244    #[test]
1245    fn test_thumbprint_xchacha20_poly1305_vector() {
1246        // A fixed XChaCha20Poly1305 COSE key.
1247        let key: B64 = XCHACHA20_POLY1305_KEY.parse().unwrap();
1248        let key = SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(&key)).unwrap();
1249        assert_eq!(
1250            key.thumbprint().unwrap().to_hex(),
1251            XCHACHA20_POLY1305_KEY_THUMBPRINT
1252        );
1253    }
1254
1255    #[test]
1256    fn test_thumbprint_is_deterministic() {
1257        let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
1258        assert_eq!(key.thumbprint().unwrap(), key.thumbprint().unwrap());
1259    }
1260
1261    #[test]
1262    fn test_thumbprint_errors_for_legacy_aes_cbc() {
1263        assert!(
1264            SymmetricCryptoKey::make_aes256_cbc_hmac_key()
1265                .thumbprint()
1266                .is_err()
1267        );
1268    }
1269}