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.to_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            _ => {
275                tracing::warn!(
276                    "Unsupported unwrap operation for the given key and data {:?}, {:?}",
277                    wrapping_key,
278                    wrapped_key
279                );
280                return Err(CryptoError::InvalidKey);
281            }
282        };
283
284        let new_key_id = Ids::Symmetric::new_local(LocalId::new());
285
286        #[allow(deprecated)]
287        self.set_symmetric_key(new_key_id, key)?;
288
289        // Returning the new key identifier for convenience
290        Ok(new_key_id)
291    }
292
293    /// Move a symmetric key from a local identifier to a global identifier within the context
294    ///
295    /// The key value is copied to `to` and the original identifier `from` is removed.
296    ///
297    /// # Errors
298    /// Returns an error if the source key does not exist or if setting the destination key
299    /// fails (for example due to read-only global store).
300    pub fn persist_symmetric_key(
301        &mut self,
302        from: Ids::Symmetric,
303        to: Ids::Symmetric,
304    ) -> Result<()> {
305        if !from.is_local() || to.is_local() {
306            return Err(CryptoError::InvalidKeyStoreOperation);
307        }
308        let key = self.get_symmetric_key(from)?.to_owned();
309        self.drop_symmetric_key(from)?;
310        #[allow(deprecated)]
311        self.set_symmetric_key(to, key)?;
312        Ok(())
313    }
314
315    /// Move a private key from a local identifier to a global identifier within the context
316    ///
317    /// The key value is copied to `to` and the original identifier `from` is removed.
318    ///
319    /// # Errors
320    /// Returns an error if the source key does not exist or if setting the destination key
321    /// fails (for example due to read-only global store).
322    pub fn persist_private_key(&mut self, from: Ids::Private, to: Ids::Private) -> Result<()> {
323        if !from.is_local() || to.is_local() {
324            return Err(CryptoError::InvalidKeyStoreOperation);
325        }
326        let key = self.get_private_key(from)?.to_owned();
327        self.drop_private_key(from)?;
328        #[allow(deprecated)]
329        self.set_private_key(to, key)?;
330        Ok(())
331    }
332
333    /// Move a signing key from a local identifier to a global identifier within the context
334    ///
335    /// The key value at `from` will be copied to `to` and the original `from` will be removed.
336    ///
337    /// # Errors
338    /// Returns an error if the source key does not exist or updating the destination fails.
339    pub fn persist_signing_key(&mut self, from: Ids::Signing, to: Ids::Signing) -> Result<()> {
340        if !from.is_local() || to.is_local() {
341            return Err(CryptoError::InvalidKeyStoreOperation);
342        }
343        let key = self.get_signing_key(from)?.to_owned();
344        self.drop_signing_key(from)?;
345        #[allow(deprecated)]
346        self.set_signing_key(to, key)?;
347        Ok(())
348    }
349
350    /// Wrap (encrypt) a signing key with a symmetric key.
351    ///
352    /// The signing key identified by `key_to_wrap` will be serialized to COSE and encrypted
353    /// with the symmetric `wrapping_key`, returning an `EncString` suitable for storage or
354    /// transport.
355    ///
356    /// # Errors
357    /// Returns an error if either key id does not exist or the encryption fails.
358    pub fn wrap_signing_key(
359        &self,
360        wrapping_key: Ids::Symmetric,
361        key_to_wrap: Ids::Signing,
362    ) -> Result<EncString> {
363        let wrapping_key = self.get_symmetric_key(wrapping_key)?;
364        let signing_key = self.get_signing_key(key_to_wrap)?.to_owned();
365        signing_key.to_cose().encrypt_with_key(wrapping_key)
366    }
367
368    /// Wrap (encrypt) a private key with a symmetric key.
369    ///
370    /// The private key identified by `key_to_wrap` will be serialized to DER (PKCS#8) and
371    /// encrypted with `wrapping_key`, returning an `EncString` suitable for storage.
372    ///
373    /// # Errors
374    /// Returns an error if the keys are missing or serialization/encryption fails.
375    pub fn wrap_private_key(
376        &self,
377        wrapping_key: Ids::Symmetric,
378        key_to_wrap: Ids::Private,
379    ) -> Result<EncString> {
380        let wrapping_key = self.get_symmetric_key(wrapping_key)?;
381        let private_key = self.get_private_key(key_to_wrap)?.to_owned();
382        private_key.to_der()?.encrypt_with_key(wrapping_key)
383    }
384
385    /// Decrypt and import a previously wrapped private key into the context.
386    ///
387    /// The `wrapped_key` will be decrypted using `wrapping_key` and parsed as a PKCS#8
388    /// private key; the resulting key will be inserted as a local private key and the
389    /// new local identifier returned.
390    ///
391    /// # Errors
392    /// Returns an error if decryption or parsing fails.
393    #[bitwarden_logging::instrument(err, fields(wrapping_key = ?wrapping_key))]
394    pub fn unwrap_private_key(
395        &mut self,
396        wrapping_key: Ids::Symmetric,
397        wrapped_key: &EncString,
398    ) -> Result<Ids::Private> {
399        let wrapping_key = self.get_symmetric_key(wrapping_key)?;
400        let private_key_bytes: Vec<u8> = wrapped_key.decrypt_with_key(wrapping_key)?;
401        let private_key = PrivateKey::from_der(&Pkcs8PrivateKeyBytes::from(private_key_bytes))?;
402        Ok(self.add_local_private_key(private_key))
403    }
404
405    /// Decrypt and import a previously wrapped signing key into the context.
406    ///
407    /// The wrapped COSE key will be decrypted with `wrapping_key` and parsed into a
408    /// `SigningKey` which is inserted as a local signing key. The new local identifier
409    /// is returned.
410    ///
411    /// # Errors
412    /// Returns an error if decryption or parsing fails.
413    pub fn unwrap_signing_key(
414        &mut self,
415        wrapping_key: Ids::Symmetric,
416        wrapped_key: &EncString,
417    ) -> Result<Ids::Signing> {
418        let wrapping_key = self.get_symmetric_key(wrapping_key)?;
419        let signing_key_bytes: Vec<u8> = wrapped_key.decrypt_with_key(wrapping_key)?;
420        let signing_key = SigningKey::from_cose(&CoseKeyBytes::from(signing_key_bytes))?;
421        Ok(self.add_local_signing_key(signing_key))
422    }
423
424    /// Return the verifying (public) key corresponding to a signing key identifier.
425    ///
426    /// This converts the stored `SigningKey` into a `VerifyingKey` suitable for
427    /// signature verification operations.
428    ///
429    /// # Errors
430    /// Returns an error if the signing key id does not exist.
431    pub fn get_verifying_key(&self, signing_key_id: Ids::Signing) -> Result<VerifyingKey> {
432        let signing_key = self.get_signing_key(signing_key_id)?;
433        Ok(signing_key.to_verifying_key())
434    }
435
436    /// Return the public key corresponding to an private key identifier.
437    ///
438    /// This converts the stored private key into its public key representation.
439    ///
440    /// # Errors
441    /// Returns an error if the private key id does not exist.
442    pub fn get_public_key(&self, private_key_id: Ids::Private) -> Result<PublicKey> {
443        let private_key = self.get_private_key(private_key_id)?;
444        Ok(private_key.to_public_key())
445    }
446
447    /// Encrypt and return a symmetric key from the context by using an already existing symmetric
448    /// key
449    ///
450    /// # Arguments
451    ///
452    /// * `wrapping_key` - The key id used to wrap (encrypt) the `key_to_wrap`. It must already
453    ///   exist in the context
454    /// * `key_to_wrap` - The key id to wrap. It must already exist in the context
455    pub fn wrap_symmetric_key(
456        &self,
457        wrapping_key: Ids::Symmetric,
458        key_to_wrap: Ids::Symmetric,
459    ) -> Result<EncString> {
460        use SymmetricCryptoKey::*;
461
462        let wrapping_key_instance = self.get_symmetric_key(wrapping_key)?;
463        let key_to_wrap_instance = self.get_symmetric_key(key_to_wrap)?;
464        // `Aes256CbcHmacKey` can wrap keys by encrypting their byte serialization obtained using
465        // `SymmetricCryptoKey::to_encoded()`. General-purpose COSE wrapping keys serialize the
466        // wrapped key without padding and authenticate whether it is a legacy key or a COSE key
467        // through the content format.
468        match (wrapping_key_instance, key_to_wrap_instance) {
469            (
470                Aes256CbcHmacKey(_),
471                Aes256CbcHmacKey(_)
472                | Aes256CbcKey(_)
473                | XChaCha20Poly1305Key(_)
474                | Aes256GcmKey(_)
475                | XAes256GcmKey(_),
476            ) => self.encrypt_data_with_symmetric_key(
477                wrapping_key,
478                key_to_wrap_instance
479                    .to_encoded()
480                    .as_ref()
481                    .to_vec()
482                    .as_slice(),
483                ContentFormat::BitwardenLegacyKey,
484            ),
485            (XChaCha20Poly1305Key(_), _) | (XAes256GcmKey(_), _) => {
486                let encoded = key_to_wrap_instance.to_encoded_raw();
487                let content_format = encoded.content_format();
488                self.encrypt_data_with_symmetric_key(
489                    wrapping_key,
490                    Into::<Vec<u8>>::into(encoded).as_slice(),
491                    content_format,
492                )
493            }
494            _ => Err(CryptoError::OperationNotSupported(
495                UnsupportedOperationError::EncryptionNotImplementedForKey,
496            )),
497        }
498    }
499
500    /// Returns `true` if the context has a symmetric key with the given identifier
501    pub fn has_symmetric_key(&self, key_id: Ids::Symmetric) -> bool {
502        self.get_symmetric_key(key_id).is_ok()
503    }
504
505    /// Returns `true` if the context has a private key with the given identifier
506    pub fn has_private_key(&self, key_id: Ids::Private) -> bool {
507        self.get_private_key(key_id).is_ok()
508    }
509
510    /// Returns `true` if the context has a signing key with the given identifier
511    pub fn has_signing_key(&self, key_id: Ids::Signing) -> bool {
512        self.get_signing_key(key_id).is_ok()
513    }
514
515    /// Generate a new random symmetric key and store it in the context
516    pub fn generate_symmetric_key(&mut self) -> Ids::Symmetric {
517        self.add_local_symmetric_key(SymmetricCryptoKey::make_aes256_cbc_hmac_key())
518    }
519
520    /// Generate a new symmetric encryption key using the specified algorithm and store it in the
521    /// context as a local key
522    pub fn make_symmetric_key(&mut self, algorithm: SymmetricKeyAlgorithm) -> Ids::Symmetric {
523        self.add_local_symmetric_key(SymmetricCryptoKey::make(algorithm))
524    }
525
526    /// Makes a new private encryption key using the current default algorithm, and stores it in
527    /// the context as a local key
528    pub fn make_private_key(&mut self, algorithm: PublicKeyEncryptionAlgorithm) -> Ids::Private {
529        self.add_local_private_key(PrivateKey::make(algorithm))
530    }
531
532    /// Makes a new signing key using the current default algorithm, and stores it in the context as
533    /// a local key
534    pub fn make_signing_key(&mut self, algorithm: SignatureAlgorithm) -> Ids::Signing {
535        self.add_local_signing_key(SigningKey::make(algorithm))
536    }
537
538    /// Derive a shareable key using hkdf from secret and name and store it in the context.
539    ///
540    /// A specialized variant of this function was called `CryptoService.makeSendKey` in the
541    /// Bitwarden `clients` repository.
542    pub fn derive_shareable_key(
543        &mut self,
544        secret: Zeroizing<[u8; 16]>,
545        name: &str,
546        info: Option<&str>,
547    ) -> Result<Ids::Symmetric> {
548        let key_id = Ids::Symmetric::new_local(LocalId::new());
549        #[allow(deprecated)]
550        self.set_symmetric_key(
551            key_id,
552            SymmetricCryptoKey::Aes256CbcHmacKey(derive_shareable_key(secret, name, info)),
553        )?;
554        Ok(key_id)
555    }
556
557    /// Return a reference to a symmetric key stored in the context.
558    ///
559    /// Deprecated: intended only for internal use and tests. This exposes the underlying
560    /// `SymmetricCryptoKey` reference directly and should not be used by external code. Use
561    /// the higher-level APIs (for example encryption/decryption helpers) or `get_symmetric_key`
562    /// internally when possible.
563    ///
564    /// # Errors
565    /// Returns [`CryptoError::MissingKeyId`] if the key id does not exist in the context.
566    #[deprecated(note = "This function should ideally never be used outside this crate")]
567    pub fn dangerous_get_symmetric_key(
568        &self,
569        key_id: Ids::Symmetric,
570    ) -> Result<&SymmetricCryptoKey> {
571        self.get_symmetric_key(key_id)
572    }
573
574    /// Return the key id if the symmetric key exists in the context
575    pub fn get_symmetric_key_id(&self, key_slot_id: Ids::Symmetric) -> Option<KeyId> {
576        let Ok(key) = self.get_symmetric_key(key_slot_id) else {
577            return None;
578        };
579        key.key_id()
580    }
581
582    /// Return a reference to a signing key stored in the context.
583    ///
584    /// Deprecated: intended only for internal use and tests. This exposes the underlying
585    /// `SigningKey` reference directly and should not be used by external code. Use the
586    /// higher-level APIs (for example signing helpers) or `get_signing_key` internally when
587    /// possible
588    ///
589    /// # Errors
590    /// Returns [`CryptoError::MissingKeyId`] if the key id does not exist in
591    /// the context.
592    #[deprecated(note = "This function should ideally never be used outside this crate")]
593    pub fn dangerous_get_signing_key(&self, key_id: Ids::Signing) -> Result<&SigningKey> {
594        self.get_signing_key(key_id)
595    }
596
597    /// Return a reference to an asymmetric (private) key stored in the context.
598    ///
599    /// Deprecated: intended only for internal use and tests. This exposes the underlying
600    /// `PrivateKey` reference directly and should not be used by external code. Prefer
601    /// using the public key via `get_public_key` or other higher-level APIs instead.
602    ///
603    /// # Errors
604    /// Returns [`CryptoError::MissingKeyId`] if the key id does not exist in the context.
605    #[deprecated(note = "This function should ideally never be used outside this crate")]
606    pub fn dangerous_get_private_key(&self, key_id: Ids::Private) -> Result<&PrivateKey> {
607        self.get_private_key(key_id)
608    }
609
610    /// Makes a signed public key from a private key and signing key stored in context.
611    /// Signing a public key asserts ownership, and makes the claim to other users that if they want
612    /// to share with you, they can use this public key.
613    pub fn make_signed_public_key(
614        &self,
615        private_key_id: Ids::Private,
616        signing_key_id: Ids::Signing,
617    ) -> Result<SignedPublicKey> {
618        let public_key = self.get_private_key(private_key_id)?.to_public_key();
619        let signing_key = self.get_signing_key(signing_key_id)?;
620        let signed_public_key =
621            SignedPublicKeyMessage::from_public_key(&public_key)?.sign(signing_key)?;
622        Ok(signed_public_key)
623    }
624
625    pub(crate) fn get_symmetric_key(&self, key_id: Ids::Symmetric) -> Result<&SymmetricCryptoKey> {
626        if key_id.is_local() {
627            self.local_symmetric_keys.get(key_id)
628        } else {
629            self.global_keys.get().symmetric_keys.get(key_id)
630        }
631        .ok_or_else(|| crate::CryptoError::MissingKeyId(format!("{key_id:?}")))
632    }
633
634    pub(super) fn get_private_key(&self, key_id: Ids::Private) -> Result<&PrivateKey> {
635        if key_id.is_local() {
636            self.local_private_keys.get(key_id)
637        } else {
638            self.global_keys.get().private_keys.get(key_id)
639        }
640        .ok_or_else(|| crate::CryptoError::MissingKeyId(format!("{key_id:?}")))
641    }
642
643    pub(super) fn get_signing_key(&self, key_id: Ids::Signing) -> Result<&SigningKey> {
644        if key_id.is_local() {
645            self.local_signing_keys.get(key_id)
646        } else {
647            self.global_keys.get().signing_keys.get(key_id)
648        }
649        .ok_or_else(|| crate::CryptoError::MissingKeyId(format!("{key_id:?}")))
650    }
651
652    /// Set a symmetric key in the context.
653    ///
654    /// # Errors
655    /// Returns [`CryptoError::ReadOnlyKeyStore`] if the context does not have write access when
656    /// attempting to modify the global store.
657    #[deprecated(note = "This function should ideally never be used outside this crate")]
658    pub fn set_symmetric_key(
659        &mut self,
660        key_id: Ids::Symmetric,
661        key: SymmetricCryptoKey,
662    ) -> Result<()> {
663        self.set_symmetric_key_internal(key_id, key)
664    }
665
666    pub(crate) fn set_symmetric_key_internal(
667        &mut self,
668        key_id: Ids::Symmetric,
669        key: SymmetricCryptoKey,
670    ) -> Result<()> {
671        if key_id.is_local() {
672            self.local_symmetric_keys.upsert(key_id, key);
673        } else {
674            self.global_keys
675                .get_mut()?
676                .symmetric_keys
677                .upsert(key_id, key);
678        }
679        Ok(())
680    }
681
682    /// Add a new symmetric key to the local context, returning a new unique identifier for it.
683    pub fn add_local_symmetric_key(&mut self, key: SymmetricCryptoKey) -> Ids::Symmetric {
684        let key_id = Ids::Symmetric::new_local(LocalId::new());
685        self.local_symmetric_keys.upsert(key_id, key);
686        key_id
687    }
688
689    /// Get the type of a symmetric key stored in the context.
690    pub fn get_symmetric_key_algorithm(
691        &self,
692        key_id: Ids::Symmetric,
693    ) -> Result<SymmetricKeyAlgorithm> {
694        let key = self.get_symmetric_key(key_id)?;
695        match key {
696            // Note this is dropped soon
697            SymmetricCryptoKey::Aes256CbcKey(_) => Err(CryptoError::OperationNotSupported(
698                UnsupportedOperationError::EncryptionNotImplementedForKey,
699            )),
700            SymmetricCryptoKey::Aes256CbcHmacKey(_) => Ok(SymmetricKeyAlgorithm::Aes256CbcHmac),
701            SymmetricCryptoKey::XChaCha20Poly1305Key(_) => {
702                Ok(SymmetricKeyAlgorithm::XChaCha20Poly1305)
703            }
704            SymmetricCryptoKey::Aes256GcmKey(_) => Ok(SymmetricKeyAlgorithm::Aes256Gcm),
705            SymmetricCryptoKey::XAes256GcmKey(_) => Ok(SymmetricKeyAlgorithm::XAes256Gcm),
706        }
707    }
708
709    /// Returns `true` if the given symmetric key uses V1 (Aes256CbcHmac) encryption.
710    #[bitwarden_logging::instrument(err, fields(key_id = ?key_id))]
711    pub fn is_v1_symmetric_key(&self, key_id: Ids::Symmetric) -> Result<bool> {
712        let algorithm = self.get_symmetric_key_algorithm(key_id)?;
713        Ok(algorithm == SymmetricKeyAlgorithm::Aes256CbcHmac)
714    }
715
716    /// Set a private key in the context.
717    ///
718    /// # Errors
719    /// Returns [`CryptoError::ReadOnlyKeyStore`] if attempting to write to the global store when
720    /// the context is read-only.
721    #[deprecated(note = "This function should ideally never be used outside this crate")]
722    pub fn set_private_key(&mut self, key_id: Ids::Private, key: PrivateKey) -> Result<()> {
723        if key_id.is_local() {
724            self.local_private_keys.upsert(key_id, key);
725        } else {
726            self.global_keys.get_mut()?.private_keys.upsert(key_id, key);
727        }
728        Ok(())
729    }
730
731    /// Add a new private key to the local context, returning a new unique identifier for it.
732    pub fn add_local_private_key(&mut self, key: PrivateKey) -> Ids::Private {
733        let key_id = Ids::Private::new_local(LocalId::new());
734        self.local_private_keys.upsert(key_id, key);
735        key_id
736    }
737
738    /// Sets a signing key in the context
739    ///
740    /// # Errors
741    /// Returns [`CryptoError::ReadOnlyKeyStore`] if attempting to write to the global store when
742    /// the context is read-only.
743    #[deprecated(note = "This function should ideally never be used outside this crate")]
744    pub fn set_signing_key(&mut self, key_id: Ids::Signing, key: SigningKey) -> Result<()> {
745        if key_id.is_local() {
746            self.local_signing_keys.upsert(key_id, key);
747        } else {
748            self.global_keys.get_mut()?.signing_keys.upsert(key_id, key);
749        }
750        Ok(())
751    }
752
753    /// Add a new signing key to the local context, returning a new unique identifier for it.
754    pub fn add_local_signing_key(&mut self, key: SigningKey) -> Ids::Signing {
755        let key_id = Ids::Signing::new_local(LocalId::new());
756        self.local_signing_keys.upsert(key_id, key);
757        key_id
758    }
759
760    #[bitwarden_logging::instrument(err, fields(key = ?key))]
761    pub(crate) fn decrypt_data_with_symmetric_key(
762        &self,
763        key: Ids::Symmetric,
764        data: &EncString,
765    ) -> Result<Vec<u8>> {
766        let key = self.get_symmetric_key(key)?;
767
768        match (data, key) {
769            (EncString::Aes256Cbc_B64 { .. }, SymmetricCryptoKey::Aes256CbcKey(_)) => {
770                Err(CryptoError::OperationNotSupported(
771                    UnsupportedOperationError::DecryptionNotImplementedForKey,
772                ))
773            }
774            (
775                EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data },
776                SymmetricCryptoKey::Aes256CbcHmacKey(key),
777            ) => Aes256CbcHmacSha256::decrypt(iv, data, mac, &key.to_composite_key())
778                .map_err(|_| CryptoError::Decrypt),
779            (
780                EncString::Cose_Encrypt0_B64 { data },
781                SymmetricCryptoKey::XChaCha20Poly1305Key(key),
782            ) => {
783                let (data, _) = crate::cose::symmetric::decrypt_xchacha20_poly1305(
784                    &CoseEncrypt0Bytes::from(data.clone()),
785                    key,
786                )?;
787                Ok(data)
788            }
789            (EncString::Cose_Encrypt0_B64 { data }, SymmetricCryptoKey::XAes256GcmKey(key)) => {
790                let (data, _) = crate::cose::symmetric::decrypt_xaes256_gcm(
791                    &CoseEncrypt0Bytes::from(data.clone()),
792                    key,
793                )?;
794                Ok(data)
795            }
796            _ => {
797                tracing::warn!("Unsupported decryption operation for the given key and data");
798                Err(CryptoError::InvalidKey)
799            }
800        }
801    }
802
803    pub(crate) fn encrypt_data_with_symmetric_key(
804        &self,
805        key: Ids::Symmetric,
806        data: &[u8],
807        content_format: ContentFormat,
808    ) -> Result<EncString> {
809        let key = self.get_symmetric_key(key)?;
810        match key {
811            SymmetricCryptoKey::Aes256CbcKey(_) => Err(CryptoError::OperationNotSupported(
812                UnsupportedOperationError::EncryptionNotImplementedForKey,
813            )),
814            SymmetricCryptoKey::Aes256CbcHmacKey(key) => EncString::encrypt_aes256_hmac(data, key),
815            SymmetricCryptoKey::XChaCha20Poly1305Key(key) => {
816                if !key.supported_operations.contains(&KeyOperation::Encrypt) {
817                    return Err(CryptoError::KeyOperationNotSupported(KeyOperation::Encrypt));
818                }
819                EncString::encrypt_xchacha20_poly1305(data, key, content_format)
820            }
821            SymmetricCryptoKey::Aes256GcmKey(_) => Err(CryptoError::OperationNotSupported(
822                UnsupportedOperationError::EncryptionNotImplementedForKey,
823            )),
824            SymmetricCryptoKey::XAes256GcmKey(key) => {
825                if !key.supported_operations.contains(&KeyOperation::Encrypt) {
826                    return Err(CryptoError::KeyOperationNotSupported(KeyOperation::Encrypt));
827                }
828                EncString::encrypt_xaes256_gcm(data, key, content_format)
829            }
830        }
831    }
832
833    /// Signs the given data using the specified signing key, for the given
834    /// [crate::SigningNamespace] and returns the signature and the serialized message. See
835    /// [crate::SigningKey::sign]
836    pub fn sign<Message: Serialize>(
837        &self,
838        key: Ids::Signing,
839        message: &Message,
840        namespace: &crate::SigningNamespace,
841    ) -> Result<SignedObject> {
842        self.get_signing_key(key)?.sign(message, namespace)
843    }
844
845    /// Signs the given data using the specified signing key, for the given
846    /// [crate::SigningNamespace] and returns the signature and the serialized message. See
847    /// [crate::SigningKey::sign_detached]
848    #[allow(unused)]
849    pub(crate) fn sign_detached<Message: Serialize>(
850        &self,
851        key: Ids::Signing,
852        message: &Message,
853        namespace: &crate::SigningNamespace,
854    ) -> Result<(Signature, signing::SerializedMessage)> {
855        self.get_signing_key(key)?.sign_detached(message, namespace)
856    }
857
858    /// Re-encrypts the user's keys with the provided symmetric key for a v2 user.
859    pub fn dangerous_get_v2_rotated_account_keys(
860        &self,
861        current_user_private_key_id: Ids::Private,
862        current_user_signing_key_id: Ids::Signing,
863    ) -> Result<RotatedUserKeys> {
864        #[expect(deprecated)]
865        crate::dangerous_get_v2_rotated_account_keys(
866            current_user_private_key_id,
867            current_user_signing_key_id,
868            self,
869        )
870    }
871
872    /// A test helper to assert that the symmetric keys corresponding to the given identifiers are
873    /// equal.
874    #[cfg(any(test, feature = "test-utils"))]
875    pub fn assert_symmetric_keys_equal(&self, key_id_1: Ids::Symmetric, key_id_2: Ids::Symmetric) {
876        let key_1 = self
877            .get_symmetric_key(key_id_1)
878            .expect("Key 1 should exist in context");
879        let key_2 = self
880            .get_symmetric_key(key_id_2)
881            .expect("Key 2 should exist in context");
882        if key_1 != key_2 {
883            panic!(
884                "Symmetric keys with ids {:?} and {:?} are not equal",
885                key_id_1, key_id_2,
886            );
887        }
888    }
889
890    /// A test helper to assert that the symmetric keys corresponding to the given identifiers are
891    /// not equal.
892    #[cfg(any(test, feature = "test-utils"))]
893    pub fn assert_symmetric_keys_not_equal(
894        &self,
895        key_id_1: Ids::Symmetric,
896        key_id_2: Ids::Symmetric,
897    ) {
898        let key_1 = self
899            .get_symmetric_key(key_id_1)
900            .expect("Key 1 should exist in context");
901        let key_2 = self
902            .get_symmetric_key(key_id_2)
903            .expect("Key 2 should exist in context");
904        if key_1 == key_2 {
905            panic!(
906                "Symmetric keys with ids {:?} and {:?} are equal",
907                key_id_1, key_id_2,
908            );
909        }
910    }
911}
912
913#[cfg(test)]
914#[allow(deprecated)]
915mod tests {
916    use serde::{Deserialize, Serialize};
917
918    use crate::{
919        CompositeEncryptable, CoseKeyBytes, CoseSerializable, CryptoError, Decryptable, EncString,
920        KeyDecryptable, Pkcs8PrivateKeyBytes, PrivateKey, PublicKey, PublicKeyEncryptionAlgorithm,
921        SignatureAlgorithm, SigningKey, SigningNamespace, SymmetricCryptoKey,
922        SymmetricKeyAlgorithm,
923        store::{
924            KeyStore,
925            tests::{Data, DataView},
926        },
927        traits::tests::{TestIds, TestSigningKey, TestSymmKey},
928    };
929
930    #[test]
931    fn test_set_signing_key() {
932        let store: KeyStore<TestIds> = KeyStore::default();
933
934        // Generate and insert a key
935        let key_a0_id = TestSigningKey::A(0);
936        let key_a0 = SigningKey::make(SignatureAlgorithm::Ed25519);
937        store
938            .context_mut()
939            .set_signing_key(key_a0_id, key_a0)
940            .unwrap();
941    }
942
943    #[test]
944    fn test_set_keys_for_encryption() {
945        let store: KeyStore<TestIds> = KeyStore::default();
946
947        // Generate and insert a key
948        let key_a0_id = TestSymmKey::A(0);
949        let mut ctx = store.context_mut();
950        let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
951        ctx.persist_symmetric_key(local_key_id, TestSymmKey::A(0))
952            .unwrap();
953
954        assert!(ctx.has_symmetric_key(key_a0_id));
955
956        // Encrypt some data with the key
957        let data = DataView("Hello, World!".to_string(), key_a0_id);
958        let _encrypted: Data = data.encrypt_composite(&mut ctx, key_a0_id).unwrap();
959    }
960
961    #[test]
962    fn test_key_encryption() {
963        let store: KeyStore<TestIds> = KeyStore::default();
964
965        let mut ctx = store.context();
966
967        // Generate and insert a key
968        let key_1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
969
970        assert!(ctx.has_symmetric_key(key_1_id));
971
972        // Generate and insert a new key
973        let key_2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
974
975        assert!(ctx.has_symmetric_key(key_2_id));
976
977        // Encrypt the new key with the old key
978        let key_2_enc = ctx.wrap_symmetric_key(key_1_id, key_2_id).unwrap();
979
980        // Decrypt the new key with the old key in a different identifier
981        let new_key_id = ctx.unwrap_symmetric_key(key_1_id, &key_2_enc).unwrap();
982
983        // Now `key_2_id` and `new_key_id` contain the same key, so we should be able to encrypt
984        // with one and decrypt with the other
985
986        let data = DataView("Hello, World!".to_string(), key_2_id);
987        let encrypted = data.encrypt_composite(&mut ctx, key_2_id).unwrap();
988
989        let decrypted1 = encrypted.decrypt(&mut ctx, key_2_id).unwrap();
990        let decrypted2 = encrypted.decrypt(&mut ctx, new_key_id).unwrap();
991
992        // Assert that the decrypted data is the same
993        assert_eq!(decrypted1.0, decrypted2.0);
994    }
995
996    #[test]
997    fn test_wrap_unwrap() {
998        let store: KeyStore<TestIds> = KeyStore::default();
999        let mut ctx = store.context_mut();
1000
1001        let cbc = TestSymmKey::A(1);
1002        let xchacha = TestSymmKey::A(2);
1003        let aes_gcm = TestSymmKey::A(3);
1004        let xaes = TestSymmKey::A(4);
1005        for (id, key) in [
1006            (
1007                cbc,
1008                SymmetricCryptoKey::make(SymmetricKeyAlgorithm::Aes256CbcHmac),
1009            ),
1010            (
1011                xchacha,
1012                SymmetricCryptoKey::make(SymmetricKeyAlgorithm::XChaCha20Poly1305),
1013            ),
1014            (
1015                aes_gcm,
1016                SymmetricCryptoKey::make(SymmetricKeyAlgorithm::Aes256Gcm),
1017            ),
1018            (
1019                xaes,
1020                SymmetricCryptoKey::make(SymmetricKeyAlgorithm::XAes256Gcm),
1021            ),
1022        ] {
1023            ctx.set_symmetric_key(id, key).unwrap();
1024        }
1025
1026        for (wrapping_key, wrapped_key) in [
1027            (cbc, cbc),
1028            (cbc, xchacha),
1029            (xchacha, cbc),
1030            (xchacha, xchacha),
1031            (xaes, cbc),
1032            (xaes, xchacha),
1033            (xaes, aes_gcm),
1034            (xaes, xaes),
1035            (cbc, xaes),
1036            (xchacha, xaes),
1037        ] {
1038            let encrypted = ctx.wrap_symmetric_key(wrapping_key, wrapped_key).unwrap();
1039            let unwrapped = ctx.unwrap_symmetric_key(wrapping_key, &encrypted).unwrap();
1040            ctx.assert_symmetric_keys_equal(unwrapped, wrapped_key);
1041        }
1042    }
1043
1044    #[test]
1045    fn test_signing() {
1046        let store: KeyStore<TestIds> = KeyStore::default();
1047
1048        // Generate and insert a key
1049        let key_a0_id = TestSigningKey::A(0);
1050        let key_a0 = SigningKey::make(SignatureAlgorithm::Ed25519);
1051        let verifying_key = key_a0.to_verifying_key();
1052        store
1053            .context_mut()
1054            .set_signing_key(key_a0_id, key_a0)
1055            .unwrap();
1056
1057        assert!(store.context().has_signing_key(key_a0_id));
1058
1059        // Sign some data with the key
1060        #[derive(Serialize, Deserialize)]
1061        struct TestData {
1062            data: String,
1063        }
1064        let signed_object = store
1065            .context()
1066            .sign(
1067                key_a0_id,
1068                &TestData {
1069                    data: "Hello".to_string(),
1070                },
1071                &SigningNamespace::ExampleNamespace,
1072            )
1073            .unwrap();
1074        let payload: Result<TestData, CryptoError> =
1075            signed_object.verify_and_unwrap(&verifying_key, &SigningNamespace::ExampleNamespace);
1076        assert!(payload.is_ok());
1077
1078        let (signature, serialized_message) = store
1079            .context()
1080            .sign_detached(
1081                key_a0_id,
1082                &TestData {
1083                    data: "Hello".to_string(),
1084                },
1085                &SigningNamespace::ExampleNamespace,
1086            )
1087            .unwrap();
1088        assert!(signature.verify(
1089            serialized_message.as_bytes(),
1090            &verifying_key,
1091            &SigningNamespace::ExampleNamespace
1092        ))
1093    }
1094
1095    #[test]
1096    fn test_account_key_rotation() {
1097        let store: KeyStore<TestIds> = KeyStore::default();
1098        let mut ctx = store.context_mut();
1099
1100        // Make the keys
1101        let current_user_signing_key_id = ctx.make_signing_key(SignatureAlgorithm::Ed25519);
1102        let current_user_private_key_id =
1103            ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
1104
1105        // Get the rotated account keys
1106        let rotated_keys = ctx
1107            .dangerous_get_v2_rotated_account_keys(
1108                current_user_private_key_id,
1109                current_user_signing_key_id,
1110            )
1111            .unwrap();
1112
1113        // Public/Private key
1114        assert_eq!(
1115            PublicKey::from_der(&rotated_keys.public_key)
1116                .unwrap()
1117                .to_der()
1118                .unwrap(),
1119            ctx.get_private_key(current_user_private_key_id)
1120                .unwrap()
1121                .to_public_key()
1122                .to_der()
1123                .unwrap()
1124        );
1125        let decrypted_private_key: Vec<u8> = rotated_keys
1126            .private_key
1127            .decrypt_with_key(&rotated_keys.user_key)
1128            .unwrap();
1129        let private_key =
1130            PrivateKey::from_der(&Pkcs8PrivateKeyBytes::from(decrypted_private_key)).unwrap();
1131        assert_eq!(
1132            private_key.to_der().unwrap(),
1133            ctx.get_private_key(current_user_private_key_id)
1134                .unwrap()
1135                .to_der()
1136                .unwrap()
1137        );
1138
1139        // Signing Key
1140        let decrypted_signing_key: Vec<u8> = rotated_keys
1141            .signing_key
1142            .decrypt_with_key(&rotated_keys.user_key)
1143            .unwrap();
1144        let signing_key =
1145            SigningKey::from_cose(&CoseKeyBytes::from(decrypted_signing_key)).unwrap();
1146        assert_eq!(
1147            signing_key.to_cose(),
1148            ctx.get_signing_key(current_user_signing_key_id)
1149                .unwrap()
1150                .to_cose(),
1151        );
1152
1153        // Signed Public Key
1154        let signed_public_key = rotated_keys.signed_public_key;
1155        let unwrapped_key = signed_public_key
1156            .verify_and_unwrap(
1157                &ctx.get_signing_key(current_user_signing_key_id)
1158                    .unwrap()
1159                    .to_verifying_key(),
1160            )
1161            .unwrap();
1162        assert_eq!(
1163            unwrapped_key.to_der().unwrap(),
1164            ctx.get_private_key(current_user_private_key_id)
1165                .unwrap()
1166                .to_public_key()
1167                .to_der()
1168                .unwrap()
1169        );
1170    }
1171
1172    #[test]
1173    fn test_encrypt_fails_when_operation_not_allowed() {
1174        use coset::iana::KeyOperation;
1175        let store = KeyStore::<TestIds>::default();
1176        let mut ctx = store.context_mut();
1177        let key_id = TestSymmKey::A(0);
1178        // Key with only Decrypt allowed
1179        let key = SymmetricCryptoKey::XChaCha20Poly1305Key(crate::XChaCha20Poly1305Key {
1180            key_id: [0u8; 16].into(),
1181            enc_key: Box::pin([0u8; 32].into()),
1182            supported_operations: vec![KeyOperation::Decrypt],
1183        });
1184        ctx.set_symmetric_key(key_id, key).unwrap();
1185        let data = DataView("should fail".to_string(), key_id);
1186        let result = data.encrypt_composite(&mut ctx, key_id);
1187        assert!(
1188            matches!(
1189                result,
1190                Err(CryptoError::KeyOperationNotSupported(KeyOperation::Encrypt))
1191            ),
1192            "Expected encrypt to fail with KeyOperationNotSupported",
1193        );
1194    }
1195
1196    #[test]
1197    fn test_xaes_data_roundtrip_and_encrypt_operation() {
1198        use coset::iana::KeyOperation;
1199
1200        let store = KeyStore::<TestIds>::default();
1201        let mut ctx = store.context_mut();
1202        let key_id = TestSymmKey::A(0);
1203        ctx.set_symmetric_key(
1204            key_id,
1205            SymmetricCryptoKey::make(SymmetricKeyAlgorithm::XAes256Gcm),
1206        )
1207        .unwrap();
1208
1209        let plaintext = b"data encrypted directly by the key store";
1210        let encrypted = ctx
1211            .encrypt_data_with_symmetric_key(key_id, plaintext, crate::ContentFormat::OctetStream)
1212            .unwrap();
1213        assert_eq!(
1214            ctx.decrypt_data_with_symmetric_key(key_id, &encrypted)
1215                .unwrap(),
1216            plaintext
1217        );
1218
1219        let no_encrypt = TestSymmKey::A(1);
1220        ctx.set_symmetric_key(
1221            no_encrypt,
1222            SymmetricCryptoKey::XAes256GcmKey(crate::XAes256GcmKey {
1223                key_id: [1; 16].into(),
1224                enc_key: Box::pin([1; 32].into()),
1225                supported_operations: vec![KeyOperation::Decrypt],
1226            }),
1227        )
1228        .unwrap();
1229        assert!(matches!(
1230            ctx.encrypt_data_with_symmetric_key(
1231                no_encrypt,
1232                plaintext,
1233                crate::ContentFormat::OctetStream,
1234            ),
1235            Err(CryptoError::KeyOperationNotSupported(KeyOperation::Encrypt))
1236        ));
1237    }
1238
1239    #[test]
1240    fn test_xaes_key_store_rejects_unsupported_inputs() {
1241        let store = KeyStore::<TestIds>::default();
1242        let mut ctx = store.context_mut();
1243        let xaes = TestSymmKey::A(0);
1244        ctx.set_symmetric_key(
1245            xaes,
1246            SymmetricCryptoKey::make(SymmetricKeyAlgorithm::XAes256Gcm),
1247        )
1248        .unwrap();
1249
1250        let non_key = ctx
1251            .encrypt_data_with_symmetric_key(xaes, b"not a key", crate::ContentFormat::OctetStream)
1252            .unwrap();
1253        assert!(matches!(
1254            ctx.unwrap_symmetric_key(xaes, &non_key),
1255            Err(CryptoError::InvalidKey)
1256        ));
1257
1258        let legacy_data =
1259            EncString::encrypt_aes256_hmac(b"data", &crate::derive_symmetric_key("test key"))
1260                .unwrap();
1261        assert!(matches!(
1262            ctx.decrypt_data_with_symmetric_key(xaes, &legacy_data),
1263            Err(CryptoError::InvalidKey)
1264        ));
1265    }
1266
1267    #[test]
1268    fn test_move_key() {
1269        let store: KeyStore<TestIds> = KeyStore::default();
1270        let mut ctx = store.context_mut();
1271
1272        // Generate and insert a key
1273        let key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
1274
1275        assert!(ctx.has_symmetric_key(key));
1276
1277        // Move the key to a new identifier
1278        let new_key_id = TestSymmKey::A(1);
1279        ctx.persist_symmetric_key(key, new_key_id).unwrap();
1280
1281        // Ensure the old key id is gone and the new `one has the key
1282        assert!(!ctx.has_symmetric_key(key));
1283        assert!(ctx.has_symmetric_key(new_key_id));
1284    }
1285
1286    #[test]
1287    fn test_encrypt_decrypt_data_fails_when_key_is_type_0() {
1288        let store = KeyStore::<TestIds>::default();
1289        let mut ctx = store.context_mut();
1290
1291        let key_id = TestSymmKey::A(0);
1292        let key = SymmetricCryptoKey::Aes256CbcKey(crate::Aes256CbcKey {
1293            enc_key: Box::pin([0u8; 32].into()),
1294        });
1295        ctx.set_symmetric_key_internal(key_id, key).unwrap();
1296
1297        let data_to_encrypt: Vec<u8> = vec![1, 2, 3, 4, 5];
1298        let result = ctx.encrypt_data_with_symmetric_key(
1299            key_id,
1300            &data_to_encrypt,
1301            crate::ContentFormat::OctetStream,
1302        );
1303        assert!(
1304            matches!(
1305                result,
1306                Err(CryptoError::OperationNotSupported(
1307                    crate::error::UnsupportedOperationError::EncryptionNotImplementedForKey
1308                ))
1309            ),
1310            "Expected encrypt to fail when using deprecated type 0 keys",
1311        );
1312
1313        let data_to_decrypt = EncString::Aes256Cbc_B64 {
1314            iv: [0; 16],
1315            data: data_to_encrypt,
1316        }; // dummy value; shouldn't matter
1317        let result = ctx.decrypt_data_with_symmetric_key(key_id, &data_to_decrypt);
1318        assert!(
1319            matches!(
1320                result,
1321                Err(CryptoError::OperationNotSupported(
1322                    crate::error::UnsupportedOperationError::DecryptionNotImplementedForKey
1323                ))
1324            ),
1325            "Expected decrypt to fail when using deprecated type 0 keys",
1326        );
1327    }
1328
1329    #[test]
1330    fn test_wrap_unwrap_key_fails_when_key_is_type_0() {
1331        let store = KeyStore::<TestIds>::default();
1332        let mut ctx = store.context_mut();
1333
1334        let wrapping_key_id = TestSymmKey::A(0);
1335        let wrapping_key = SymmetricCryptoKey::Aes256CbcKey(crate::Aes256CbcKey {
1336            enc_key: Box::pin([0u8; 32].into()),
1337        });
1338        ctx.set_symmetric_key_internal(wrapping_key_id, wrapping_key)
1339            .unwrap();
1340
1341        let key_to_wrap_id = TestSymmKey::A(1);
1342        let key_to_wrap = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
1343        ctx.set_symmetric_key_internal(key_to_wrap_id, key_to_wrap)
1344            .unwrap();
1345
1346        let result = ctx.wrap_symmetric_key(wrapping_key_id, key_to_wrap_id);
1347        assert!(
1348            matches!(
1349                result,
1350                Err(CryptoError::OperationNotSupported(
1351                    crate::error::UnsupportedOperationError::EncryptionNotImplementedForKey
1352                ))
1353            ),
1354            "Expected encrypt to fail when using deprecated type 0 keys",
1355        );
1356
1357        let wrapped_key = &EncString::Aes256Cbc_B64 {
1358            iv: [0; 16],
1359            data: vec![0],
1360        }; // dummy value; shouldn't matter
1361        let result = ctx.unwrap_symmetric_key(wrapping_key_id, wrapped_key);
1362        assert!(
1363            matches!(
1364                result,
1365                Err(CryptoError::OperationNotSupported(
1366                    crate::error::UnsupportedOperationError::DecryptionNotImplementedForKey
1367                ))
1368            ),
1369            "Expected decrypt to fail when using deprecated type 0 keys",
1370        );
1371    }
1372}