Skip to main content

bitwarden_crypto/store/
context.rs

1use std::{
2    cell::Cell,
3    sync::{RwLockReadGuard, RwLockWriteGuard},
4};
5
6use coset::iana::KeyOperation;
7use serde::Serialize;
8use zeroize::Zeroizing;
9
10use super::{CipherSuite, KeyStoreInner};
11use crate::{
12    BitwardenLegacyKeyBytes, ContentFormat, CoseEncrypt0Bytes, CoseKeyBytes, CoseSerializable,
13    CryptoError, EncString, KeyDecryptable, KeyEncryptable, KeyId, KeySlotId, KeySlotIds, LocalId,
14    Pkcs8PrivateKeyBytes, PrivateKey, PublicKey, PublicKeyEncryptionAlgorithm, Result,
15    RotatedUserKeys, Signature, SignatureAlgorithm, SignedObject, SignedPublicKey,
16    SignedPublicKeyMessage, SigningKey, SymmetricCryptoKey, SymmetricKeyAlgorithm, VerifyingKey,
17    derive_shareable_key, error::UnsupportedOperationError,
18    hazmat::symmetric_encryption::Aes256CbcHmacSha256, signing, store::backend::StoreBackend,
19};
20
21/// The context of a crypto operation using [super::KeyStore]
22///
23/// This will usually be accessed from an implementation of [crate::Decryptable] or
24/// [crate::CompositeEncryptable], [crate::PrimitiveEncryptable],
25/// but can also be obtained
26/// through [super::KeyStore::context]
27///
28/// This context contains access to the user keys stored in the [super::KeyStore] (sometimes
29/// referred to as `global keys`) and it also contains it's own individual secure backend for key
30/// storage. Keys stored in this individual backend are usually referred to as `local keys`, they
31/// will be cleared when this context goes out of scope and is dropped and they do not affect either
32/// the global [super::KeyStore] or other instances of contexts.
33///
34/// This context-local storage is recommended for ephemeral and temporary keys that are decrypted
35/// during the course of a decrypt/encrypt operation, but won't be used after the operation itself
36/// is complete.
37///
38/// ```rust
39/// # use bitwarden_crypto::*;
40/// # key_slot_ids! {
41/// #     #[symmetric]
42/// #     pub enum SymmKeySlotIds {
43/// #         User,
44/// #         #[local]
45/// #         Local(LocalId),
46/// #     }
47/// #     #[private]
48/// #     pub enum PrivateKeySlotIds {
49/// #         UserPrivate,
50/// #         #[local]
51/// #         Local(LocalId),
52/// #     }
53/// #     #[signing]
54/// #     pub enum SigningKeySlotIds {
55/// #         UserSigning,
56/// #         #[local]
57/// #         Local(LocalId),
58/// #     }
59/// #     pub Ids => SymmKeySlotIds, PrivateKeySlotIds, SigningKeySlotIds;
60/// # }
61/// struct Data {
62///     key: EncString,
63///     name: String,
64/// }
65/// # impl IdentifyKey<SymmKeySlotIds> for Data {
66/// #    fn key_identifier(&self) -> SymmKeySlotIds {
67/// #        SymmKeySlotIds::User
68/// #    }
69/// # }
70///
71///
72/// impl CompositeEncryptable<Ids, SymmKeySlotIds, EncString> for Data {
73///     fn encrypt_composite(&self, ctx: &mut KeyStoreContext<Ids>, key: SymmKeySlotIds) -> Result<EncString, CryptoError> {
74///         let local_key_id = ctx.unwrap_symmetric_key(key, &self.key)?;
75///         self.name.encrypt(ctx, local_key_id)
76///     }
77/// }
78/// ```
79#[must_use]
80pub struct KeyStoreContext<'a, Ids: KeySlotIds> {
81    pub(super) global_keys: GlobalKeys<'a, Ids>,
82
83    pub(super) local_symmetric_keys: Box<dyn StoreBackend<Ids::Symmetric>>,
84    pub(super) local_private_keys: Box<dyn StoreBackend<Ids::Private>>,
85    pub(super) local_signing_keys: Box<dyn StoreBackend<Ids::Signing>>,
86
87    pub(super) security_state_version: u64,
88
89    pub(super) cipher_suite: CipherSuite,
90
91    // Make sure the context is !Send & !Sync
92    pub(super) _phantom: std::marker::PhantomData<(Cell<()>, RwLockReadGuard<'static, ()>)>,
93}
94
95/// A KeyStoreContext is usually limited to a read only access to the global keys,
96/// which allows us to have multiple read only contexts at the same time and do multitheaded
97/// encryption/decryption. We also have the option to create a read/write context, which allows us
98/// to modify the global keys, but only allows one context at a time. This is controlled by a
99/// [std::sync::RwLock] on the global keys, and this struct stores both types of guards.
100pub(crate) enum GlobalKeys<'a, Ids: KeySlotIds> {
101    ReadOnly(RwLockReadGuard<'a, KeyStoreInner<Ids>>),
102    ReadWrite(RwLockWriteGuard<'a, KeyStoreInner<Ids>>),
103}
104
105impl<Ids: KeySlotIds> GlobalKeys<'_, Ids> {
106    /// Get a shared reference to the underlying `KeyStoreInner`.
107    ///
108    /// This returns a shared reference regardless of whether the global keys were locked
109    /// for read-only or read-write access. Callers who need mutable access should use
110    /// `get_mut` which will return an error when the context is read-only.
111    pub fn get(&self) -> &KeyStoreInner<Ids> {
112        match self {
113            GlobalKeys::ReadOnly(keys) => keys,
114            GlobalKeys::ReadWrite(keys) => keys,
115        }
116    }
117
118    /// Get a mutable reference to the underlying `KeyStoreInner`.
119    ///
120    /// This will succeed only when the context was created with write access. If the
121    /// context is read-only an error (`CryptoError::ReadOnlyKeyStore`) is returned.
122    ///
123    /// # Errors
124    /// Returns [`CryptoError::ReadOnlyKeyStore`] when attempting to get mutable access from
125    /// a read-only context.
126    pub fn get_mut(&mut self) -> Result<&mut KeyStoreInner<Ids>> {
127        match self {
128            GlobalKeys::ReadOnly(_) => Err(CryptoError::ReadOnlyKeyStore),
129            GlobalKeys::ReadWrite(keys) => Ok(keys),
130        }
131    }
132}
133
134impl<Ids: KeySlotIds> KeyStoreContext<'_, Ids> {
135    /// Clears all the local keys stored in this context
136    /// This will not affect the global keys even if this context has write access.
137    /// To clear the global keys, you need to use [super::KeyStore::clear] instead.
138    pub fn clear_local(&mut self) {
139        self.local_symmetric_keys.clear();
140        self.local_private_keys.clear();
141        self.local_signing_keys.clear();
142    }
143
144    /// Returns the version of the security state of the key context. This describes the user's
145    /// encryption version and can be used to disable certain old / dangerous format features
146    /// safely.
147    pub fn get_security_state_version(&self) -> u64 {
148        self.security_state_version
149    }
150
151    /// Returns the [CipherSuite] this context operates under, which determines the algorithms
152    /// operations are allowed to use in the current environment.
153    pub fn cipher_suite(&self) -> CipherSuite {
154        self.cipher_suite
155    }
156
157    /// Remove all symmetric keys from the context for which the predicate returns false
158    /// This will also remove the keys from the global store if this context has write access
159    pub fn retain_symmetric_keys(&mut self, f: fn(Ids::Symmetric) -> bool) {
160        if let Ok(keys) = self.global_keys.get_mut() {
161            keys.symmetric_keys.retain(f);
162        }
163        self.local_symmetric_keys.retain(f);
164    }
165
166    /// Remove all private keys from the context for which the predicate returns false
167    /// This will also remove the keys from the global store if this context has write access
168    pub fn retain_private_keys(&mut self, f: fn(Ids::Private) -> bool) {
169        if let Ok(keys) = self.global_keys.get_mut() {
170            keys.private_keys.retain(f);
171        }
172        self.local_private_keys.retain(f);
173    }
174
175    /// Drop a symmetric key from the context by its identifier.
176    /// This will also remove the key from the global store if this context has write access and the
177    /// key is not local.
178    pub fn drop_symmetric_key(&mut self, key_id: Ids::Symmetric) -> Result<()> {
179        if key_id.is_local() {
180            self.local_symmetric_keys.remove(key_id);
181        } else {
182            self.global_keys.get_mut()?.symmetric_keys.remove(key_id);
183        }
184        Ok(())
185    }
186
187    /// Drop a private key from the context by its identifier.
188    /// This will also remove the key from the global store if this context has write access and the
189    /// key is not local.
190    pub fn drop_private_key(&mut self, key_id: Ids::Private) -> Result<()> {
191        if key_id.is_local() {
192            self.local_private_keys.remove(key_id);
193        } else {
194            self.global_keys.get_mut()?.private_keys.remove(key_id);
195        }
196        Ok(())
197    }
198
199    /// Drop a signing key from the context by its identifier.
200    /// This will also remove the key from the global store if this context has write access and the
201    /// key is not local.
202    pub fn drop_signing_key(&mut self, key_id: Ids::Signing) -> Result<()> {
203        if key_id.is_local() {
204            self.local_signing_keys.remove(key_id);
205        } else {
206            self.global_keys.get_mut()?.signing_keys.remove(key_id);
207        }
208        Ok(())
209    }
210
211    // TODO: All these encrypt x key with x key look like they need to be made generic,
212    // but I haven't found the best way to do that yet.
213
214    /// Decrypt a symmetric key into the context by using an already existing symmetric key
215    ///
216    /// # Arguments
217    ///
218    /// * `wrapping_key` - The key id used to decrypt the `wrapped_key`. It must already exist in
219    ///   the context
220    /// * `new_key_id` - The key id where the decrypted key will be stored. If it already exists, it
221    ///   will be overwritten
222    /// * `wrapped_key` - The key to decrypt
223    #[bitwarden_logging::instrument(err, fields(wrapping_key = ?wrapping_key))]
224    pub fn unwrap_symmetric_key(
225        &mut self,
226        wrapping_key: Ids::Symmetric,
227        wrapped_key: &EncString,
228    ) -> Result<Ids::Symmetric> {
229        let wrapping_key = self.get_symmetric_key(wrapping_key)?;
230
231        let key = match (wrapped_key, wrapping_key) {
232            (EncString::Aes256Cbc_B64 { .. }, SymmetricCryptoKey::Aes256CbcKey(_)) => {
233                return Err(CryptoError::OperationNotSupported(
234                    UnsupportedOperationError::DecryptionNotImplementedForKey,
235                ));
236            }
237            (
238                EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data },
239                SymmetricCryptoKey::Aes256CbcHmacKey(key),
240            ) => SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(
241                Aes256CbcHmacSha256::decrypt(iv, data, mac, key.as_composite_key())
242                    .map_err(|_| CryptoError::Decrypt)?,
243            ))?,
244            (
245                EncString::Cose_Encrypt0_B64 { data },
246                SymmetricCryptoKey::XChaCha20Poly1305Key(key),
247            ) => {
248                let (content_bytes, content_format) =
249                    crate::cose::symmetric::decrypt_xchacha20_poly1305(
250                        &CoseEncrypt0Bytes::from(data.clone()),
251                        key,
252                    )?;
253                match content_format {
254                    ContentFormat::BitwardenLegacyKey => {
255                        SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(content_bytes))?
256                    }
257                    ContentFormat::CoseKey => SymmetricCryptoKey::try_from_cose(&content_bytes)?,
258                    _ => return Err(CryptoError::InvalidKey),
259                }
260            }
261            (EncString::Cose_Encrypt0_B64 { data }, SymmetricCryptoKey::XAes256GcmKey(key)) => {
262                let (content_bytes, content_format) = crate::cose::symmetric::decrypt_xaes256_gcm(
263                    &CoseEncrypt0Bytes::from(data.clone()),
264                    key,
265                )?;
266                match content_format {
267                    ContentFormat::BitwardenLegacyKey => {
268                        SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(content_bytes))?
269                    }
270                    ContentFormat::CoseKey => SymmetricCryptoKey::try_from_cose(&content_bytes)?,
271                    _ => return Err(CryptoError::InvalidKey),
272                }
273            }
274            (EncString::Unparseable { .. }, _) => return Err(CryptoError::UnparseableEncString),
275            _ => {
276                tracing::warn!(
277                    "Unsupported unwrap operation for the given key and data {:?}, {:?}",
278                    wrapping_key,
279                    wrapped_key
280                );
281                return Err(CryptoError::InvalidKey);
282            }
283        };
284
285        let new_key_id = Ids::Symmetric::new_local(LocalId::new());
286
287        #[allow(deprecated)]
288        self.set_symmetric_key(new_key_id, key)?;
289
290        // Returning the new key identifier for convenience
291        Ok(new_key_id)
292    }
293
294    /// Move a symmetric key from a local identifier to a global identifier within the context
295    ///
296    /// The key value is copied to `to` and the original identifier `from` is removed.
297    ///
298    /// # Errors
299    /// Returns an error if the source key does not exist or if setting the destination key
300    /// fails (for example due to read-only global store).
301    pub fn persist_symmetric_key(
302        &mut self,
303        from: Ids::Symmetric,
304        to: Ids::Symmetric,
305    ) -> Result<()> {
306        if !from.is_local() || to.is_local() {
307            return Err(CryptoError::InvalidKeyStoreOperation);
308        }
309        let key = self.get_symmetric_key(from)?.to_owned();
310        self.drop_symmetric_key(from)?;
311        #[allow(deprecated)]
312        self.set_symmetric_key(to, key)?;
313        Ok(())
314    }
315
316    /// Move a private key from a local identifier to a global identifier within the context
317    ///
318    /// The key value is copied to `to` and the original identifier `from` is removed.
319    ///
320    /// # Errors
321    /// Returns an error if the source key does not exist or if setting the destination key
322    /// fails (for example due to read-only global store).
323    pub fn persist_private_key(&mut self, from: Ids::Private, to: Ids::Private) -> Result<()> {
324        if !from.is_local() || to.is_local() {
325            return Err(CryptoError::InvalidKeyStoreOperation);
326        }
327        let key = self.get_private_key(from)?.to_owned();
328        self.drop_private_key(from)?;
329        #[allow(deprecated)]
330        self.set_private_key(to, key)?;
331        Ok(())
332    }
333
334    /// Move a signing key from a local identifier to a global identifier within the context
335    ///
336    /// The key value at `from` will be copied to `to` and the original `from` will be removed.
337    ///
338    /// # Errors
339    /// Returns an error if the source key does not exist or updating the destination fails.
340    pub fn persist_signing_key(&mut self, from: Ids::Signing, to: Ids::Signing) -> Result<()> {
341        if !from.is_local() || to.is_local() {
342            return Err(CryptoError::InvalidKeyStoreOperation);
343        }
344        let key = self.get_signing_key(from)?.to_owned();
345        self.drop_signing_key(from)?;
346        #[allow(deprecated)]
347        self.set_signing_key(to, key)?;
348        Ok(())
349    }
350
351    /// Wrap (encrypt) a signing key with a symmetric key.
352    ///
353    /// The signing key identified by `key_to_wrap` will be serialized to COSE and encrypted
354    /// with the symmetric `wrapping_key`, returning an `EncString` suitable for storage or
355    /// transport.
356    ///
357    /// # Errors
358    /// Returns an error if either key id does not exist or the encryption fails.
359    pub fn wrap_signing_key(
360        &self,
361        wrapping_key: Ids::Symmetric,
362        key_to_wrap: Ids::Signing,
363    ) -> Result<EncString> {
364        let wrapping_key = self.get_symmetric_key(wrapping_key)?;
365        let signing_key = self.get_signing_key(key_to_wrap)?.to_owned();
366        signing_key.to_cose().encrypt_with_key(wrapping_key)
367    }
368
369    /// Wrap (encrypt) a private key with a symmetric key.
370    ///
371    /// The private key identified by `key_to_wrap` will be serialized to DER (PKCS#8) and
372    /// encrypted with `wrapping_key`, returning an `EncString` suitable for storage.
373    ///
374    /// # Errors
375    /// Returns an error if the keys are missing or serialization/encryption fails.
376    pub fn wrap_private_key(
377        &self,
378        wrapping_key: Ids::Symmetric,
379        key_to_wrap: Ids::Private,
380    ) -> Result<EncString> {
381        let wrapping_key = self.get_symmetric_key(wrapping_key)?;
382        let private_key = self.get_private_key(key_to_wrap)?.to_owned();
383        private_key.to_der()?.encrypt_with_key(wrapping_key)
384    }
385
386    /// Decrypt and import a previously wrapped private key into the context.
387    ///
388    /// The `wrapped_key` will be decrypted using `wrapping_key` and parsed as a PKCS#8
389    /// private key; the resulting key will be inserted as a local private key and the
390    /// new local identifier returned.
391    ///
392    /// # Errors
393    /// Returns an error if decryption or parsing fails.
394    #[bitwarden_logging::instrument(err, fields(wrapping_key = ?wrapping_key))]
395    pub fn unwrap_private_key(
396        &mut self,
397        wrapping_key: Ids::Symmetric,
398        wrapped_key: &EncString,
399    ) -> Result<Ids::Private> {
400        let wrapping_key = self.get_symmetric_key(wrapping_key)?;
401        let private_key_bytes: Vec<u8> = wrapped_key.decrypt_with_key(wrapping_key)?;
402        let private_key = PrivateKey::from_der(&Pkcs8PrivateKeyBytes::from(private_key_bytes))?;
403        Ok(self.add_local_private_key(private_key))
404    }
405
406    /// Decrypt and import a previously wrapped signing key into the context.
407    ///
408    /// The wrapped COSE key will be decrypted with `wrapping_key` and parsed into a
409    /// `SigningKey` which is inserted as a local signing key. The new local identifier
410    /// is returned.
411    ///
412    /// # Errors
413    /// Returns an error if decryption or parsing fails.
414    pub fn unwrap_signing_key(
415        &mut self,
416        wrapping_key: Ids::Symmetric,
417        wrapped_key: &EncString,
418    ) -> Result<Ids::Signing> {
419        let wrapping_key = self.get_symmetric_key(wrapping_key)?;
420        let signing_key_bytes: Vec<u8> = wrapped_key.decrypt_with_key(wrapping_key)?;
421        let signing_key = SigningKey::from_cose(&CoseKeyBytes::from(signing_key_bytes))?;
422        Ok(self.add_local_signing_key(signing_key))
423    }
424
425    /// Return the verifying (public) key corresponding to a signing key identifier.
426    ///
427    /// This converts the stored `SigningKey` into a `VerifyingKey` suitable for
428    /// signature verification operations.
429    ///
430    /// # Errors
431    /// Returns an error if the signing key id does not exist.
432    pub fn get_verifying_key(&self, signing_key_id: Ids::Signing) -> Result<VerifyingKey> {
433        let signing_key = self.get_signing_key(signing_key_id)?;
434        Ok(signing_key.to_verifying_key())
435    }
436
437    /// Return the public key corresponding to an private key identifier.
438    ///
439    /// This converts the stored private key into its public key representation.
440    ///
441    /// # Errors
442    /// Returns an error if the private key id does not exist.
443    pub fn get_public_key(&self, private_key_id: Ids::Private) -> Result<PublicKey> {
444        let private_key = self.get_private_key(private_key_id)?;
445        Ok(private_key.to_public_key())
446    }
447
448    /// Encrypt and return a symmetric key from the context by using an already existing symmetric
449    /// key
450    ///
451    /// # Arguments
452    ///
453    /// * `wrapping_key` - The key id used to wrap (encrypt) the `key_to_wrap`. It must already
454    ///   exist in the context
455    /// * `key_to_wrap` - The key id to wrap. It must already exist in the context
456    pub fn wrap_symmetric_key(
457        &self,
458        wrapping_key: Ids::Symmetric,
459        key_to_wrap: Ids::Symmetric,
460    ) -> Result<EncString> {
461        use SymmetricCryptoKey::*;
462
463        let wrapping_key_instance = self.get_symmetric_key(wrapping_key)?;
464        let key_to_wrap_instance = self.get_symmetric_key(key_to_wrap)?;
465        // `Aes256CbcHmacKey` can wrap keys by encrypting their byte serialization obtained using
466        // `SymmetricCryptoKey::to_encoded()`. General-purpose COSE wrapping keys serialize the
467        // wrapped key without padding and authenticate whether it is a legacy key or a COSE key
468        // through the content format.
469        match (wrapping_key_instance, key_to_wrap_instance) {
470            (
471                Aes256CbcHmacKey(_),
472                Aes256CbcHmacKey(_)
473                | Aes256CbcKey(_)
474                | XChaCha20Poly1305Key(_)
475                | Aes256GcmKey(_)
476                | XAes256GcmKey(_),
477            ) => self.encrypt_data_with_symmetric_key(
478                wrapping_key,
479                key_to_wrap_instance
480                    .to_encoded()
481                    .as_ref()
482                    .to_vec()
483                    .as_slice(),
484                ContentFormat::BitwardenLegacyKey,
485            ),
486            (XChaCha20Poly1305Key(_), _) | (XAes256GcmKey(_), _) => {
487                let encoded = key_to_wrap_instance.to_encoded_raw();
488                let content_format = encoded.content_format();
489                self.encrypt_data_with_symmetric_key(
490                    wrapping_key,
491                    Into::<Vec<u8>>::into(encoded).as_slice(),
492                    content_format,
493                )
494            }
495            _ => Err(CryptoError::OperationNotSupported(
496                UnsupportedOperationError::EncryptionNotImplementedForKey,
497            )),
498        }
499    }
500
501    /// Returns `true` if the context has a symmetric key with the given identifier
502    pub fn has_symmetric_key(&self, key_id: Ids::Symmetric) -> bool {
503        self.get_symmetric_key(key_id).is_ok()
504    }
505
506    /// Returns `true` if the context has a private key with the given identifier
507    pub fn has_private_key(&self, key_id: Ids::Private) -> bool {
508        self.get_private_key(key_id).is_ok()
509    }
510
511    /// Returns `true` if the context has a signing key with the given identifier
512    pub fn has_signing_key(&self, key_id: Ids::Signing) -> bool {
513        self.get_signing_key(key_id).is_ok()
514    }
515
516    /// Generate a new random symmetric key and store it in the context
517    pub fn generate_symmetric_key(&mut self) -> Ids::Symmetric {
518        self.add_local_symmetric_key(SymmetricCryptoKey::make_aes256_cbc_hmac_key())
519    }
520
521    /// Generate a new symmetric encryption key using the specified algorithm and store it in the
522    /// context as a local key
523    pub fn make_symmetric_key(&mut self, algorithm: SymmetricKeyAlgorithm) -> Ids::Symmetric {
524        self.add_local_symmetric_key(SymmetricCryptoKey::make(algorithm))
525    }
526
527    /// Makes a new private encryption key using the current default algorithm, and stores it in
528    /// the context as a local key
529    pub fn make_private_key(&mut self, algorithm: PublicKeyEncryptionAlgorithm) -> Ids::Private {
530        self.add_local_private_key(PrivateKey::make(algorithm))
531    }
532
533    /// Makes a new signing key using the current default algorithm, and stores it in the context as
534    /// a local key
535    pub fn make_signing_key(&mut self, algorithm: SignatureAlgorithm) -> Ids::Signing {
536        self.add_local_signing_key(SigningKey::make(algorithm))
537    }
538
539    /// Derive a shareable key using hkdf from secret and name and store it in the context.
540    ///
541    /// A specialized variant of this function was called `CryptoService.makeSendKey` in the
542    /// Bitwarden `clients` repository.
543    pub fn derive_shareable_key(
544        &mut self,
545        secret: Zeroizing<[u8; 16]>,
546        name: &str,
547        info: Option<&str>,
548    ) -> Result<Ids::Symmetric> {
549        let key_id = Ids::Symmetric::new_local(LocalId::new());
550        #[allow(deprecated)]
551        self.set_symmetric_key(
552            key_id,
553            SymmetricCryptoKey::Aes256CbcHmacKey(derive_shareable_key(secret, name, info)),
554        )?;
555        Ok(key_id)
556    }
557
558    /// Return a reference to a symmetric key stored in the context.
559    ///
560    /// Deprecated: intended only for internal use and tests. This exposes the underlying
561    /// `SymmetricCryptoKey` reference directly and should not be used by external code. Use
562    /// the higher-level APIs (for example encryption/decryption helpers) or `get_symmetric_key`
563    /// internally when possible.
564    ///
565    /// # Errors
566    /// Returns [`CryptoError::MissingKeyId`] if the key id does not exist in the context.
567    #[deprecated(note = "This function should ideally never be used outside this crate")]
568    pub fn dangerous_get_symmetric_key(
569        &self,
570        key_id: Ids::Symmetric,
571    ) -> Result<&SymmetricCryptoKey> {
572        self.get_symmetric_key(key_id)
573    }
574
575    /// Return the key id if the symmetric key exists in the context
576    pub fn get_symmetric_key_id(&self, key_slot_id: Ids::Symmetric) -> Option<KeyId> {
577        let Ok(key) = self.get_symmetric_key(key_slot_id) else {
578            return None;
579        };
580        key.key_id()
581    }
582
583    /// Return a reference to a signing key stored in the context.
584    ///
585    /// Deprecated: intended only for internal use and tests. This exposes the underlying
586    /// `SigningKey` reference directly and should not be used by external code. Use the
587    /// higher-level APIs (for example signing helpers) or `get_signing_key` internally when
588    /// possible
589    ///
590    /// # Errors
591    /// Returns [`CryptoError::MissingKeyId`] if the key id does not exist in
592    /// the context.
593    #[deprecated(note = "This function should ideally never be used outside this crate")]
594    pub fn dangerous_get_signing_key(&self, key_id: Ids::Signing) -> Result<&SigningKey> {
595        self.get_signing_key(key_id)
596    }
597
598    /// Return a reference to an asymmetric (private) key stored in the context.
599    ///
600    /// Deprecated: intended only for internal use and tests. This exposes the underlying
601    /// `PrivateKey` reference directly and should not be used by external code. Prefer
602    /// using the public key via `get_public_key` or other higher-level APIs instead.
603    ///
604    /// # Errors
605    /// Returns [`CryptoError::MissingKeyId`] if the key id does not exist in the context.
606    #[deprecated(note = "This function should ideally never be used outside this crate")]
607    pub fn dangerous_get_private_key(&self, key_id: Ids::Private) -> Result<&PrivateKey> {
608        self.get_private_key(key_id)
609    }
610
611    /// Makes a signed public key from a private key and signing key stored in context.
612    /// Signing a public key asserts ownership, and makes the claim to other users that if they want
613    /// to share with you, they can use this public key.
614    pub fn make_signed_public_key(
615        &self,
616        private_key_id: Ids::Private,
617        signing_key_id: Ids::Signing,
618    ) -> Result<SignedPublicKey> {
619        let public_key = self.get_private_key(private_key_id)?.to_public_key();
620        let signing_key = self.get_signing_key(signing_key_id)?;
621        let signed_public_key =
622            SignedPublicKeyMessage::from_public_key(&public_key)?.sign(signing_key)?;
623        Ok(signed_public_key)
624    }
625
626    pub(crate) fn get_symmetric_key(&self, key_id: Ids::Symmetric) -> Result<&SymmetricCryptoKey> {
627        if key_id.is_local() {
628            self.local_symmetric_keys.get(key_id)
629        } else {
630            self.global_keys.get().symmetric_keys.get(key_id)
631        }
632        .ok_or_else(|| crate::CryptoError::MissingKeyId(format!("{key_id:?}")))
633    }
634
635    pub(super) fn get_private_key(&self, key_id: Ids::Private) -> Result<&PrivateKey> {
636        if key_id.is_local() {
637            self.local_private_keys.get(key_id)
638        } else {
639            self.global_keys.get().private_keys.get(key_id)
640        }
641        .ok_or_else(|| crate::CryptoError::MissingKeyId(format!("{key_id:?}")))
642    }
643
644    pub(super) fn get_signing_key(&self, key_id: Ids::Signing) -> Result<&SigningKey> {
645        if key_id.is_local() {
646            self.local_signing_keys.get(key_id)
647        } else {
648            self.global_keys.get().signing_keys.get(key_id)
649        }
650        .ok_or_else(|| crate::CryptoError::MissingKeyId(format!("{key_id:?}")))
651    }
652
653    /// Set a symmetric key in the context.
654    ///
655    /// # Errors
656    /// Returns [`CryptoError::ReadOnlyKeyStore`] if the context does not have write access when
657    /// attempting to modify the global store.
658    #[deprecated(note = "This function should ideally never be used outside this crate")]
659    pub fn set_symmetric_key(
660        &mut self,
661        key_id: Ids::Symmetric,
662        key: SymmetricCryptoKey,
663    ) -> Result<()> {
664        self.set_symmetric_key_internal(key_id, key)
665    }
666
667    pub(crate) fn set_symmetric_key_internal(
668        &mut self,
669        key_id: Ids::Symmetric,
670        key: SymmetricCryptoKey,
671    ) -> Result<()> {
672        if key_id.is_local() {
673            self.local_symmetric_keys.upsert(key_id, key);
674        } else {
675            self.global_keys
676                .get_mut()?
677                .symmetric_keys
678                .upsert(key_id, key);
679        }
680        Ok(())
681    }
682
683    /// Add a new symmetric key to the local context, returning a new unique identifier for it.
684    pub fn add_local_symmetric_key(&mut self, key: SymmetricCryptoKey) -> Ids::Symmetric {
685        let key_id = Ids::Symmetric::new_local(LocalId::new());
686        self.local_symmetric_keys.upsert(key_id, key);
687        key_id
688    }
689
690    /// Get the type of a symmetric key stored in the context.
691    pub fn get_symmetric_key_algorithm(
692        &self,
693        key_id: Ids::Symmetric,
694    ) -> Result<SymmetricKeyAlgorithm> {
695        let key = self.get_symmetric_key(key_id)?;
696        match key {
697            // Note this is dropped soon
698            SymmetricCryptoKey::Aes256CbcKey(_) => Err(CryptoError::OperationNotSupported(
699                UnsupportedOperationError::EncryptionNotImplementedForKey,
700            )),
701            SymmetricCryptoKey::Aes256CbcHmacKey(_) => Ok(SymmetricKeyAlgorithm::Aes256CbcHmac),
702            SymmetricCryptoKey::XChaCha20Poly1305Key(_) => {
703                Ok(SymmetricKeyAlgorithm::XChaCha20Poly1305)
704            }
705            SymmetricCryptoKey::Aes256GcmKey(_) => Ok(SymmetricKeyAlgorithm::Aes256Gcm),
706            SymmetricCryptoKey::XAes256GcmKey(_) => Ok(SymmetricKeyAlgorithm::XAes256Gcm),
707        }
708    }
709
710    /// Returns `true` if the given symmetric key uses V1 (Aes256CbcHmac) encryption.
711    #[bitwarden_logging::instrument(err, fields(key_id = ?key_id))]
712    pub fn is_v1_symmetric_key(&self, key_id: Ids::Symmetric) -> Result<bool> {
713        let algorithm = self.get_symmetric_key_algorithm(key_id)?;
714        Ok(algorithm == SymmetricKeyAlgorithm::Aes256CbcHmac)
715    }
716
717    /// Set a private key in the context.
718    ///
719    /// # Errors
720    /// Returns [`CryptoError::ReadOnlyKeyStore`] if attempting to write to the global store when
721    /// the context is read-only.
722    #[deprecated(note = "This function should ideally never be used outside this crate")]
723    pub fn set_private_key(&mut self, key_id: Ids::Private, key: PrivateKey) -> Result<()> {
724        if key_id.is_local() {
725            self.local_private_keys.upsert(key_id, key);
726        } else {
727            self.global_keys.get_mut()?.private_keys.upsert(key_id, key);
728        }
729        Ok(())
730    }
731
732    /// Add a new private key to the local context, returning a new unique identifier for it.
733    pub fn add_local_private_key(&mut self, key: PrivateKey) -> Ids::Private {
734        let key_id = Ids::Private::new_local(LocalId::new());
735        self.local_private_keys.upsert(key_id, key);
736        key_id
737    }
738
739    /// Sets a signing key in the context
740    ///
741    /// # Errors
742    /// Returns [`CryptoError::ReadOnlyKeyStore`] if attempting to write to the global store when
743    /// the context is read-only.
744    #[deprecated(note = "This function should ideally never be used outside this crate")]
745    pub fn set_signing_key(&mut self, key_id: Ids::Signing, key: SigningKey) -> Result<()> {
746        if key_id.is_local() {
747            self.local_signing_keys.upsert(key_id, key);
748        } else {
749            self.global_keys.get_mut()?.signing_keys.upsert(key_id, key);
750        }
751        Ok(())
752    }
753
754    /// Add a new signing key to the local context, returning a new unique identifier for it.
755    pub fn add_local_signing_key(&mut self, key: SigningKey) -> Ids::Signing {
756        let key_id = Ids::Signing::new_local(LocalId::new());
757        self.local_signing_keys.upsert(key_id, key);
758        key_id
759    }
760
761    #[bitwarden_logging::instrument(err, fields(key = ?key))]
762    pub(crate) fn decrypt_data_with_symmetric_key(
763        &self,
764        key: Ids::Symmetric,
765        data: &EncString,
766    ) -> Result<Vec<u8>> {
767        let key = self.get_symmetric_key(key)?;
768
769        match (data, key) {
770            (EncString::Aes256Cbc_B64 { .. }, SymmetricCryptoKey::Aes256CbcKey(_)) => {
771                Err(CryptoError::OperationNotSupported(
772                    UnsupportedOperationError::DecryptionNotImplementedForKey,
773                ))
774            }
775            (
776                EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data },
777                SymmetricCryptoKey::Aes256CbcHmacKey(key),
778            ) => Aes256CbcHmacSha256::decrypt(iv, data, mac, key.as_composite_key())
779                .map_err(|_| CryptoError::Decrypt),
780            (
781                EncString::Cose_Encrypt0_B64 { data },
782                SymmetricCryptoKey::XChaCha20Poly1305Key(key),
783            ) => {
784                let (data, _) = crate::cose::symmetric::decrypt_xchacha20_poly1305(
785                    &CoseEncrypt0Bytes::from(data.clone()),
786                    key,
787                )?;
788                Ok(data)
789            }
790            (EncString::Cose_Encrypt0_B64 { data }, SymmetricCryptoKey::XAes256GcmKey(key)) => {
791                let (data, _) = crate::cose::symmetric::decrypt_xaes256_gcm(
792                    &CoseEncrypt0Bytes::from(data.clone()),
793                    key,
794                )?;
795                Ok(data)
796            }
797            (EncString::Unparseable { .. }, _) => Err(CryptoError::UnparseableEncString),
798            _ => {
799                tracing::warn!("Unsupported decryption operation for the given key and data");
800                Err(CryptoError::InvalidKey)
801            }
802        }
803    }
804
805    pub(crate) fn encrypt_data_with_symmetric_key(
806        &self,
807        key: Ids::Symmetric,
808        data: &[u8],
809        content_format: ContentFormat,
810    ) -> Result<EncString> {
811        let key = self.get_symmetric_key(key)?;
812        match key {
813            SymmetricCryptoKey::Aes256CbcKey(_) => Err(CryptoError::OperationNotSupported(
814                UnsupportedOperationError::EncryptionNotImplementedForKey,
815            )),
816            SymmetricCryptoKey::Aes256CbcHmacKey(key) => EncString::encrypt_aes256_hmac(data, key),
817            SymmetricCryptoKey::XChaCha20Poly1305Key(key) => {
818                if !key.supported_operations.contains(&KeyOperation::Encrypt) {
819                    return Err(CryptoError::KeyOperationNotSupported(KeyOperation::Encrypt));
820                }
821                EncString::encrypt_xchacha20_poly1305(data, key, content_format)
822            }
823            SymmetricCryptoKey::Aes256GcmKey(_) => Err(CryptoError::OperationNotSupported(
824                UnsupportedOperationError::EncryptionNotImplementedForKey,
825            )),
826            SymmetricCryptoKey::XAes256GcmKey(key) => {
827                if !key.supported_operations.contains(&KeyOperation::Encrypt) {
828                    return Err(CryptoError::KeyOperationNotSupported(KeyOperation::Encrypt));
829                }
830                EncString::encrypt_xaes256_gcm(data, key, content_format)
831            }
832        }
833    }
834
835    /// Signs the given data using the specified signing key, for the given
836    /// [crate::SigningNamespace] and returns the signature and the serialized message. See
837    /// [crate::SigningKey::sign]
838    pub fn sign<Message: Serialize>(
839        &self,
840        key: Ids::Signing,
841        message: &Message,
842        namespace: &crate::SigningNamespace,
843    ) -> Result<SignedObject> {
844        self.get_signing_key(key)?.sign(message, namespace)
845    }
846
847    /// Signs the given data using the specified signing key, for the given
848    /// [crate::SigningNamespace] and returns the signature and the serialized message. See
849    /// [crate::SigningKey::sign_detached]
850    #[allow(unused)]
851    pub(crate) fn sign_detached<Message: Serialize>(
852        &self,
853        key: Ids::Signing,
854        message: &Message,
855        namespace: &crate::SigningNamespace,
856    ) -> Result<(Signature, signing::SerializedMessage)> {
857        self.get_signing_key(key)?.sign_detached(message, namespace)
858    }
859
860    /// Re-encrypts the user's keys with the provided symmetric key for a v2 user.
861    pub fn dangerous_get_v2_rotated_account_keys(
862        &self,
863        current_user_private_key_id: Ids::Private,
864        current_user_signing_key_id: Ids::Signing,
865    ) -> Result<RotatedUserKeys> {
866        #[expect(deprecated)]
867        crate::dangerous_get_v2_rotated_account_keys(
868            current_user_private_key_id,
869            current_user_signing_key_id,
870            self,
871        )
872    }
873
874    /// A test helper to assert that the symmetric keys corresponding to the given identifiers are
875    /// equal.
876    #[cfg(any(test, feature = "test-utils"))]
877    pub fn assert_symmetric_keys_equal(&self, key_id_1: Ids::Symmetric, key_id_2: Ids::Symmetric) {
878        let key_1 = self
879            .get_symmetric_key(key_id_1)
880            .expect("Key 1 should exist in context");
881        let key_2 = self
882            .get_symmetric_key(key_id_2)
883            .expect("Key 2 should exist in context");
884        if key_1 != key_2 {
885            panic!(
886                "Symmetric keys with ids {:?} and {:?} are not equal",
887                key_id_1, key_id_2,
888            );
889        }
890    }
891
892    /// A test helper to assert that the symmetric keys corresponding to the given identifiers are
893    /// not equal.
894    #[cfg(any(test, feature = "test-utils"))]
895    pub fn assert_symmetric_keys_not_equal(
896        &self,
897        key_id_1: Ids::Symmetric,
898        key_id_2: Ids::Symmetric,
899    ) {
900        let key_1 = self
901            .get_symmetric_key(key_id_1)
902            .expect("Key 1 should exist in context");
903        let key_2 = self
904            .get_symmetric_key(key_id_2)
905            .expect("Key 2 should exist in context");
906        if key_1 == key_2 {
907            panic!(
908                "Symmetric keys with ids {:?} and {:?} are equal",
909                key_id_1, key_id_2,
910            );
911        }
912    }
913}
914
915#[cfg(test)]
916#[allow(deprecated)]
917mod tests {
918    use serde::{Deserialize, Serialize};
919
920    use crate::{
921        CompositeEncryptable, CoseKeyBytes, CoseSerializable, CryptoError, Decryptable, EncString,
922        KeyDecryptable, Pkcs8PrivateKeyBytes, PrivateKey, PublicKey, PublicKeyEncryptionAlgorithm,
923        SignatureAlgorithm, SigningKey, SigningNamespace, SymmetricCryptoKey,
924        SymmetricKeyAlgorithm,
925        store::{
926            KeyStore,
927            tests::{Data, DataView},
928        },
929        traits::tests::{TestIds, TestSigningKey, TestSymmKey},
930    };
931
932    #[test]
933    fn test_set_signing_key() {
934        let store: KeyStore<TestIds> = KeyStore::default();
935
936        // Generate and insert a key
937        let key_a0_id = TestSigningKey::A(0);
938        let key_a0 = SigningKey::make(SignatureAlgorithm::Ed25519);
939        store
940            .context_mut()
941            .set_signing_key(key_a0_id, key_a0)
942            .unwrap();
943    }
944
945    #[test]
946    fn test_set_keys_for_encryption() {
947        let store: KeyStore<TestIds> = KeyStore::default();
948
949        // Generate and insert a key
950        let key_a0_id = TestSymmKey::A(0);
951        let mut ctx = store.context_mut();
952        let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
953        ctx.persist_symmetric_key(local_key_id, TestSymmKey::A(0))
954            .unwrap();
955
956        assert!(ctx.has_symmetric_key(key_a0_id));
957
958        // Encrypt some data with the key
959        let data = DataView("Hello, World!".to_string(), key_a0_id);
960        let _encrypted: Data = data.encrypt_composite(&mut ctx, key_a0_id).unwrap();
961    }
962
963    #[test]
964    fn test_key_encryption() {
965        let store: KeyStore<TestIds> = KeyStore::default();
966
967        let mut ctx = store.context();
968
969        // Generate and insert a key
970        let key_1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
971
972        assert!(ctx.has_symmetric_key(key_1_id));
973
974        // Generate and insert a new key
975        let key_2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
976
977        assert!(ctx.has_symmetric_key(key_2_id));
978
979        // Encrypt the new key with the old key
980        let key_2_enc = ctx.wrap_symmetric_key(key_1_id, key_2_id).unwrap();
981
982        // Decrypt the new key with the old key in a different identifier
983        let new_key_id = ctx.unwrap_symmetric_key(key_1_id, &key_2_enc).unwrap();
984
985        // Now `key_2_id` and `new_key_id` contain the same key, so we should be able to encrypt
986        // with one and decrypt with the other
987
988        let data = DataView("Hello, World!".to_string(), key_2_id);
989        let encrypted = data.encrypt_composite(&mut ctx, key_2_id).unwrap();
990
991        let decrypted1 = encrypted.decrypt(&mut ctx, key_2_id).unwrap();
992        let decrypted2 = encrypted.decrypt(&mut ctx, new_key_id).unwrap();
993
994        // Assert that the decrypted data is the same
995        assert_eq!(decrypted1.0, decrypted2.0);
996    }
997
998    #[test]
999    fn test_wrap_unwrap() {
1000        let store: KeyStore<TestIds> = KeyStore::default();
1001        let mut ctx = store.context_mut();
1002
1003        let cbc = TestSymmKey::A(1);
1004        let xchacha = TestSymmKey::A(2);
1005        let aes_gcm = TestSymmKey::A(3);
1006        let xaes = TestSymmKey::A(4);
1007        for (id, key) in [
1008            (
1009                cbc,
1010                SymmetricCryptoKey::make(SymmetricKeyAlgorithm::Aes256CbcHmac),
1011            ),
1012            (
1013                xchacha,
1014                SymmetricCryptoKey::make(SymmetricKeyAlgorithm::XChaCha20Poly1305),
1015            ),
1016            (
1017                aes_gcm,
1018                SymmetricCryptoKey::make(SymmetricKeyAlgorithm::Aes256Gcm),
1019            ),
1020            (
1021                xaes,
1022                SymmetricCryptoKey::make(SymmetricKeyAlgorithm::XAes256Gcm),
1023            ),
1024        ] {
1025            ctx.set_symmetric_key(id, key).unwrap();
1026        }
1027
1028        for (wrapping_key, wrapped_key) in [
1029            (cbc, cbc),
1030            (cbc, xchacha),
1031            (xchacha, cbc),
1032            (xchacha, xchacha),
1033            (xaes, cbc),
1034            (xaes, xchacha),
1035            (xaes, aes_gcm),
1036            (xaes, xaes),
1037            (cbc, xaes),
1038            (xchacha, xaes),
1039        ] {
1040            let encrypted = ctx.wrap_symmetric_key(wrapping_key, wrapped_key).unwrap();
1041            let unwrapped = ctx.unwrap_symmetric_key(wrapping_key, &encrypted).unwrap();
1042            ctx.assert_symmetric_keys_equal(unwrapped, wrapped_key);
1043        }
1044    }
1045
1046    #[test]
1047    fn test_signing() {
1048        let store: KeyStore<TestIds> = KeyStore::default();
1049
1050        // Generate and insert a key
1051        let key_a0_id = TestSigningKey::A(0);
1052        let key_a0 = SigningKey::make(SignatureAlgorithm::Ed25519);
1053        let verifying_key = key_a0.to_verifying_key();
1054        store
1055            .context_mut()
1056            .set_signing_key(key_a0_id, key_a0)
1057            .unwrap();
1058
1059        assert!(store.context().has_signing_key(key_a0_id));
1060
1061        // Sign some data with the key
1062        #[derive(Serialize, Deserialize)]
1063        struct TestData {
1064            data: String,
1065        }
1066        let signed_object = store
1067            .context()
1068            .sign(
1069                key_a0_id,
1070                &TestData {
1071                    data: "Hello".to_string(),
1072                },
1073                &SigningNamespace::ExampleNamespace,
1074            )
1075            .unwrap();
1076        let payload: Result<TestData, CryptoError> =
1077            signed_object.verify_and_unwrap(&verifying_key, &SigningNamespace::ExampleNamespace);
1078        assert!(payload.is_ok());
1079
1080        let (signature, serialized_message) = store
1081            .context()
1082            .sign_detached(
1083                key_a0_id,
1084                &TestData {
1085                    data: "Hello".to_string(),
1086                },
1087                &SigningNamespace::ExampleNamespace,
1088            )
1089            .unwrap();
1090        assert!(signature.verify(
1091            serialized_message.as_bytes(),
1092            &verifying_key,
1093            &SigningNamespace::ExampleNamespace
1094        ))
1095    }
1096
1097    #[test]
1098    fn test_account_key_rotation() {
1099        let store: KeyStore<TestIds> = KeyStore::default();
1100        let mut ctx = store.context_mut();
1101
1102        // Make the keys
1103        let current_user_signing_key_id = ctx.make_signing_key(SignatureAlgorithm::Ed25519);
1104        let current_user_private_key_id =
1105            ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
1106
1107        // Get the rotated account keys
1108        let rotated_keys = ctx
1109            .dangerous_get_v2_rotated_account_keys(
1110                current_user_private_key_id,
1111                current_user_signing_key_id,
1112            )
1113            .unwrap();
1114
1115        // Public/Private key
1116        assert_eq!(
1117            PublicKey::from_der(&rotated_keys.public_key)
1118                .unwrap()
1119                .to_der()
1120                .unwrap(),
1121            ctx.get_private_key(current_user_private_key_id)
1122                .unwrap()
1123                .to_public_key()
1124                .to_der()
1125                .unwrap()
1126        );
1127        let decrypted_private_key: Vec<u8> = rotated_keys
1128            .private_key
1129            .decrypt_with_key(&rotated_keys.user_key)
1130            .unwrap();
1131        let private_key =
1132            PrivateKey::from_der(&Pkcs8PrivateKeyBytes::from(decrypted_private_key)).unwrap();
1133        assert_eq!(
1134            private_key.to_der().unwrap(),
1135            ctx.get_private_key(current_user_private_key_id)
1136                .unwrap()
1137                .to_der()
1138                .unwrap()
1139        );
1140
1141        // Signing Key
1142        let decrypted_signing_key: Vec<u8> = rotated_keys
1143            .signing_key
1144            .decrypt_with_key(&rotated_keys.user_key)
1145            .unwrap();
1146        let signing_key =
1147            SigningKey::from_cose(&CoseKeyBytes::from(decrypted_signing_key)).unwrap();
1148        assert_eq!(
1149            signing_key.to_cose(),
1150            ctx.get_signing_key(current_user_signing_key_id)
1151                .unwrap()
1152                .to_cose(),
1153        );
1154
1155        // Signed Public Key
1156        let signed_public_key = rotated_keys.signed_public_key;
1157        let unwrapped_key = signed_public_key
1158            .verify_and_unwrap(
1159                &ctx.get_signing_key(current_user_signing_key_id)
1160                    .unwrap()
1161                    .to_verifying_key(),
1162            )
1163            .unwrap();
1164        assert_eq!(
1165            unwrapped_key.to_der().unwrap(),
1166            ctx.get_private_key(current_user_private_key_id)
1167                .unwrap()
1168                .to_public_key()
1169                .to_der()
1170                .unwrap()
1171        );
1172    }
1173
1174    #[test]
1175    fn test_encrypt_fails_when_operation_not_allowed() {
1176        use coset::iana::KeyOperation;
1177        let store = KeyStore::<TestIds>::default();
1178        let mut ctx = store.context_mut();
1179        let key_id = TestSymmKey::A(0);
1180        // Key with only Decrypt allowed
1181        let key = SymmetricCryptoKey::XChaCha20Poly1305Key(crate::XChaCha20Poly1305Key {
1182            key_id: [0u8; 16].into(),
1183            enc_key: Box::pin([0u8; 32].into()),
1184            supported_operations: vec![KeyOperation::Decrypt],
1185        });
1186        ctx.set_symmetric_key(key_id, key).unwrap();
1187        let data = DataView("should fail".to_string(), key_id);
1188        let result = data.encrypt_composite(&mut ctx, key_id);
1189        assert!(
1190            matches!(
1191                result,
1192                Err(CryptoError::KeyOperationNotSupported(KeyOperation::Encrypt))
1193            ),
1194            "Expected encrypt to fail with KeyOperationNotSupported",
1195        );
1196    }
1197
1198    #[test]
1199    fn test_xaes_data_roundtrip_and_encrypt_operation() {
1200        use coset::iana::KeyOperation;
1201
1202        let store = KeyStore::<TestIds>::default();
1203        let mut ctx = store.context_mut();
1204        let key_id = TestSymmKey::A(0);
1205        ctx.set_symmetric_key(
1206            key_id,
1207            SymmetricCryptoKey::make(SymmetricKeyAlgorithm::XAes256Gcm),
1208        )
1209        .unwrap();
1210
1211        let plaintext = b"data encrypted directly by the key store";
1212        let encrypted = ctx
1213            .encrypt_data_with_symmetric_key(key_id, plaintext, crate::ContentFormat::OctetStream)
1214            .unwrap();
1215        assert_eq!(
1216            ctx.decrypt_data_with_symmetric_key(key_id, &encrypted)
1217                .unwrap(),
1218            plaintext
1219        );
1220
1221        let no_encrypt = TestSymmKey::A(1);
1222        ctx.set_symmetric_key(
1223            no_encrypt,
1224            SymmetricCryptoKey::XAes256GcmKey(crate::XAes256GcmKey {
1225                key_id: [1; 16].into(),
1226                enc_key: Box::pin([1; 32].into()),
1227                supported_operations: vec![KeyOperation::Decrypt],
1228            }),
1229        )
1230        .unwrap();
1231        assert!(matches!(
1232            ctx.encrypt_data_with_symmetric_key(
1233                no_encrypt,
1234                plaintext,
1235                crate::ContentFormat::OctetStream,
1236            ),
1237            Err(CryptoError::KeyOperationNotSupported(KeyOperation::Encrypt))
1238        ));
1239    }
1240
1241    #[test]
1242    fn test_xaes_key_store_rejects_unsupported_inputs() {
1243        let store = KeyStore::<TestIds>::default();
1244        let mut ctx = store.context_mut();
1245        let xaes = TestSymmKey::A(0);
1246        ctx.set_symmetric_key(
1247            xaes,
1248            SymmetricCryptoKey::make(SymmetricKeyAlgorithm::XAes256Gcm),
1249        )
1250        .unwrap();
1251
1252        let non_key = ctx
1253            .encrypt_data_with_symmetric_key(xaes, b"not a key", crate::ContentFormat::OctetStream)
1254            .unwrap();
1255        assert!(matches!(
1256            ctx.unwrap_symmetric_key(xaes, &non_key),
1257            Err(CryptoError::InvalidKey)
1258        ));
1259
1260        let legacy_data =
1261            EncString::encrypt_aes256_hmac(b"data", &crate::derive_symmetric_key("test key"))
1262                .unwrap();
1263        assert!(matches!(
1264            ctx.decrypt_data_with_symmetric_key(xaes, &legacy_data),
1265            Err(CryptoError::InvalidKey)
1266        ));
1267    }
1268
1269    #[test]
1270    fn test_move_key() {
1271        let store: KeyStore<TestIds> = KeyStore::default();
1272        let mut ctx = store.context_mut();
1273
1274        // Generate and insert a key
1275        let key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
1276
1277        assert!(ctx.has_symmetric_key(key));
1278
1279        // Move the key to a new identifier
1280        let new_key_id = TestSymmKey::A(1);
1281        ctx.persist_symmetric_key(key, new_key_id).unwrap();
1282
1283        // Ensure the old key id is gone and the new `one has the key
1284        assert!(!ctx.has_symmetric_key(key));
1285        assert!(ctx.has_symmetric_key(new_key_id));
1286    }
1287
1288    #[test]
1289    fn test_encrypt_decrypt_data_fails_when_key_is_type_0() {
1290        let store = KeyStore::<TestIds>::default();
1291        let mut ctx = store.context_mut();
1292
1293        let key_id = TestSymmKey::A(0);
1294        let key = SymmetricCryptoKey::Aes256CbcKey(crate::Aes256CbcKey {
1295            enc_key: Box::pin([0u8; 32].into()),
1296        });
1297        ctx.set_symmetric_key_internal(key_id, key).unwrap();
1298
1299        let data_to_encrypt: Vec<u8> = vec![1, 2, 3, 4, 5];
1300        let result = ctx.encrypt_data_with_symmetric_key(
1301            key_id,
1302            &data_to_encrypt,
1303            crate::ContentFormat::OctetStream,
1304        );
1305        assert!(
1306            matches!(
1307                result,
1308                Err(CryptoError::OperationNotSupported(
1309                    crate::error::UnsupportedOperationError::EncryptionNotImplementedForKey
1310                ))
1311            ),
1312            "Expected encrypt to fail when using deprecated type 0 keys",
1313        );
1314
1315        let data_to_decrypt = EncString::Aes256Cbc_B64 {
1316            iv: [0; 16],
1317            data: data_to_encrypt,
1318        }; // dummy value; shouldn't matter
1319        let result = ctx.decrypt_data_with_symmetric_key(key_id, &data_to_decrypt);
1320        assert!(
1321            matches!(
1322                result,
1323                Err(CryptoError::OperationNotSupported(
1324                    crate::error::UnsupportedOperationError::DecryptionNotImplementedForKey
1325                ))
1326            ),
1327            "Expected decrypt to fail when using deprecated type 0 keys",
1328        );
1329    }
1330
1331    /// An unparseable `EncString` must fail at decryption time, with a precise error.
1332    #[test]
1333    fn test_decrypt_and_unwrap_fail_for_unparseable_enc_string() {
1334        let store = KeyStore::<TestIds>::default();
1335        let mut ctx = store.context_mut();
1336
1337        let key_id = TestSymmKey::A(0);
1338        ctx.set_symmetric_key_internal(key_id, SymmetricCryptoKey::make_aes256_cbc_hmac_key())
1339            .unwrap();
1340
1341        let unparseable: EncString = "2.AAECAw==|Y3Q=|AAECAw==".parse().unwrap();
1342
1343        assert!(matches!(
1344            ctx.decrypt_data_with_symmetric_key(key_id, &unparseable),
1345            Err(CryptoError::UnparseableEncString)
1346        ));
1347        assert!(matches!(
1348            ctx.unwrap_symmetric_key(key_id, &unparseable),
1349            Err(CryptoError::UnparseableEncString)
1350        ));
1351    }
1352
1353    #[test]
1354    fn test_wrap_unwrap_key_fails_when_key_is_type_0() {
1355        let store = KeyStore::<TestIds>::default();
1356        let mut ctx = store.context_mut();
1357
1358        let wrapping_key_id = TestSymmKey::A(0);
1359        let wrapping_key = SymmetricCryptoKey::Aes256CbcKey(crate::Aes256CbcKey {
1360            enc_key: Box::pin([0u8; 32].into()),
1361        });
1362        ctx.set_symmetric_key_internal(wrapping_key_id, wrapping_key)
1363            .unwrap();
1364
1365        let key_to_wrap_id = TestSymmKey::A(1);
1366        let key_to_wrap = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
1367        ctx.set_symmetric_key_internal(key_to_wrap_id, key_to_wrap)
1368            .unwrap();
1369
1370        let result = ctx.wrap_symmetric_key(wrapping_key_id, key_to_wrap_id);
1371        assert!(
1372            matches!(
1373                result,
1374                Err(CryptoError::OperationNotSupported(
1375                    crate::error::UnsupportedOperationError::EncryptionNotImplementedForKey
1376                ))
1377            ),
1378            "Expected encrypt to fail when using deprecated type 0 keys",
1379        );
1380
1381        let wrapped_key = &EncString::Aes256Cbc_B64 {
1382            iv: [0; 16],
1383            data: vec![0],
1384        }; // dummy value; shouldn't matter
1385        let result = ctx.unwrap_symmetric_key(wrapping_key_id, wrapped_key);
1386        assert!(
1387            matches!(
1388                result,
1389                Err(CryptoError::OperationNotSupported(
1390                    crate::error::UnsupportedOperationError::DecryptionNotImplementedForKey
1391                ))
1392            ),
1393            "Expected decrypt to fail when using deprecated type 0 keys",
1394        );
1395    }
1396}