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