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    /// Returns `true` if the given symmetric key uses V1 (Aes256CbcHmac) encryption.
683    #[instrument(skip(self), err)]
684    pub fn is_v1_symmetric_key(&self, key_id: Ids::Symmetric) -> Result<bool> {
685        let algorithm = self.get_symmetric_key_algorithm(key_id)?;
686        Ok(algorithm == SymmetricKeyAlgorithm::Aes256CbcHmac)
687    }
688
689    /// Set a private key in the context.
690    ///
691    /// # Errors
692    /// Returns [`CryptoError::ReadOnlyKeyStore`] if attempting to write to the global store when
693    /// the context is read-only.
694    #[deprecated(note = "This function should ideally never be used outside this crate")]
695    pub fn set_private_key(&mut self, key_id: Ids::Private, key: PrivateKey) -> Result<()> {
696        if key_id.is_local() {
697            self.local_private_keys.upsert(key_id, key);
698        } else {
699            self.global_keys.get_mut()?.private_keys.upsert(key_id, key);
700        }
701        Ok(())
702    }
703
704    /// Add a new private key to the local context, returning a new unique identifier for it.
705    pub fn add_local_private_key(&mut self, key: PrivateKey) -> Ids::Private {
706        let key_id = Ids::Private::new_local(LocalId::new());
707        self.local_private_keys.upsert(key_id, key);
708        key_id
709    }
710
711    /// Sets a signing key in the context
712    ///
713    /// # Errors
714    /// Returns [`CryptoError::ReadOnlyKeyStore`] if attempting to write to the global store when
715    /// the context is read-only.
716    #[deprecated(note = "This function should ideally never be used outside this crate")]
717    pub fn set_signing_key(&mut self, key_id: Ids::Signing, key: SigningKey) -> Result<()> {
718        if key_id.is_local() {
719            self.local_signing_keys.upsert(key_id, key);
720        } else {
721            self.global_keys.get_mut()?.signing_keys.upsert(key_id, key);
722        }
723        Ok(())
724    }
725
726    /// Add a new signing key to the local context, returning a new unique identifier for it.
727    pub fn add_local_signing_key(&mut self, key: SigningKey) -> Ids::Signing {
728        let key_id = Ids::Signing::new_local(LocalId::new());
729        self.local_signing_keys.upsert(key_id, key);
730        key_id
731    }
732
733    #[instrument(skip(self, data), err)]
734    pub(crate) fn decrypt_data_with_symmetric_key(
735        &self,
736        key: Ids::Symmetric,
737        data: &EncString,
738    ) -> Result<Vec<u8>> {
739        let key = self.get_symmetric_key(key)?;
740
741        match (data, key) {
742            (EncString::Aes256Cbc_B64 { .. }, SymmetricCryptoKey::Aes256CbcKey(_)) => {
743                Err(CryptoError::OperationNotSupported(
744                    UnsupportedOperationError::DecryptionNotImplementedForKey,
745                ))
746            }
747            (
748                EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data },
749                SymmetricCryptoKey::Aes256CbcHmacKey(key),
750            ) => crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), &key.mac_key, &key.enc_key)
751                .map_err(|_| CryptoError::Decrypt),
752            (
753                EncString::Cose_Encrypt0_B64 { data },
754                SymmetricCryptoKey::XChaCha20Poly1305Key(key),
755            ) => {
756                let (data, _) = crate::cose::decrypt_xchacha20_poly1305(
757                    &CoseEncrypt0Bytes::from(data.clone()),
758                    key,
759                )?;
760                Ok(data)
761            }
762            _ => {
763                tracing::warn!("Unsupported decryption operation for the given key and data");
764                Err(CryptoError::InvalidKey)
765            }
766        }
767    }
768
769    pub(crate) fn encrypt_data_with_symmetric_key(
770        &self,
771        key: Ids::Symmetric,
772        data: &[u8],
773        content_format: ContentFormat,
774    ) -> Result<EncString> {
775        let key = self.get_symmetric_key(key)?;
776        match key {
777            SymmetricCryptoKey::Aes256CbcKey(_) => Err(CryptoError::OperationNotSupported(
778                UnsupportedOperationError::EncryptionNotImplementedForKey,
779            )),
780            SymmetricCryptoKey::Aes256CbcHmacKey(key) => EncString::encrypt_aes256_hmac(data, key),
781            SymmetricCryptoKey::XChaCha20Poly1305Key(key) => {
782                if !key.supported_operations.contains(&KeyOperation::Encrypt) {
783                    return Err(CryptoError::KeyOperationNotSupported(KeyOperation::Encrypt));
784                }
785                EncString::encrypt_xchacha20_poly1305(data, key, content_format)
786            }
787        }
788    }
789
790    /// Signs the given data using the specified signing key, for the given
791    /// [crate::SigningNamespace] and returns the signature and the serialized message. See
792    /// [crate::SigningKey::sign]
793    pub fn sign<Message: Serialize>(
794        &self,
795        key: Ids::Signing,
796        message: &Message,
797        namespace: &crate::SigningNamespace,
798    ) -> Result<SignedObject> {
799        self.get_signing_key(key)?.sign(message, namespace)
800    }
801
802    /// Signs the given data using the specified signing key, for the given
803    /// [crate::SigningNamespace] and returns the signature and the serialized message. See
804    /// [crate::SigningKey::sign_detached]
805    #[allow(unused)]
806    pub(crate) fn sign_detached<Message: Serialize>(
807        &self,
808        key: Ids::Signing,
809        message: &Message,
810        namespace: &crate::SigningNamespace,
811    ) -> Result<(Signature, signing::SerializedMessage)> {
812        self.get_signing_key(key)?.sign_detached(message, namespace)
813    }
814
815    /// Re-encrypts the user's keys with the provided symmetric key for a v2 user.
816    pub fn dangerous_get_v2_rotated_account_keys(
817        &self,
818        current_user_private_key_id: Ids::Private,
819        current_user_signing_key_id: Ids::Signing,
820    ) -> Result<RotatedUserKeys> {
821        #[expect(deprecated)]
822        crate::dangerous_get_v2_rotated_account_keys(
823            current_user_private_key_id,
824            current_user_signing_key_id,
825            self,
826        )
827    }
828
829    /// A test helper to assert that the symmetric keys corresponding to the given identifiers are
830    /// equal.
831    #[cfg(any(test, feature = "test-utils"))]
832    pub fn assert_symmetric_keys_equal(&self, key_id_1: Ids::Symmetric, key_id_2: Ids::Symmetric) {
833        let key_1 = self
834            .get_symmetric_key(key_id_1)
835            .expect("Key 1 should exist in context");
836        let key_2 = self
837            .get_symmetric_key(key_id_2)
838            .expect("Key 2 should exist in context");
839        if key_1 != key_2 {
840            panic!(
841                "Symmetric keys with ids {:?} and {:?} are not equal",
842                key_id_1, key_id_2,
843            );
844        }
845    }
846}
847
848#[cfg(test)]
849#[allow(deprecated)]
850mod tests {
851    use serde::{Deserialize, Serialize};
852
853    use crate::{
854        CompositeEncryptable, CoseKeyBytes, CoseSerializable, CryptoError, Decryptable, EncString,
855        KeyDecryptable, Pkcs8PrivateKeyBytes, PrivateKey, PublicKey, PublicKeyEncryptionAlgorithm,
856        SignatureAlgorithm, SigningKey, SigningNamespace, SymmetricCryptoKey,
857        SymmetricKeyAlgorithm,
858        store::{
859            KeyStore,
860            tests::{Data, DataView},
861        },
862        traits::tests::{TestIds, TestSigningKey, TestSymmKey},
863    };
864
865    #[test]
866    fn test_set_signing_key() {
867        let store: KeyStore<TestIds> = KeyStore::default();
868
869        // Generate and insert a key
870        let key_a0_id = TestSigningKey::A(0);
871        let key_a0 = SigningKey::make(SignatureAlgorithm::Ed25519);
872        store
873            .context_mut()
874            .set_signing_key(key_a0_id, key_a0)
875            .unwrap();
876    }
877
878    #[test]
879    fn test_set_keys_for_encryption() {
880        let store: KeyStore<TestIds> = KeyStore::default();
881
882        // Generate and insert a key
883        let key_a0_id = TestSymmKey::A(0);
884        let mut ctx = store.context_mut();
885        let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
886        ctx.persist_symmetric_key(local_key_id, TestSymmKey::A(0))
887            .unwrap();
888
889        assert!(ctx.has_symmetric_key(key_a0_id));
890
891        // Encrypt some data with the key
892        let data = DataView("Hello, World!".to_string(), key_a0_id);
893        let _encrypted: Data = data.encrypt_composite(&mut ctx, key_a0_id).unwrap();
894    }
895
896    #[test]
897    fn test_key_encryption() {
898        let store: KeyStore<TestIds> = KeyStore::default();
899
900        let mut ctx = store.context();
901
902        // Generate and insert a key
903        let key_1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
904
905        assert!(ctx.has_symmetric_key(key_1_id));
906
907        // Generate and insert a new key
908        let key_2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
909
910        assert!(ctx.has_symmetric_key(key_2_id));
911
912        // Encrypt the new key with the old key
913        let key_2_enc = ctx.wrap_symmetric_key(key_1_id, key_2_id).unwrap();
914
915        // Decrypt the new key with the old key in a different identifier
916        let new_key_id = ctx.unwrap_symmetric_key(key_1_id, &key_2_enc).unwrap();
917
918        // Now `key_2_id` and `new_key_id` contain the same key, so we should be able to encrypt
919        // with one and decrypt with the other
920
921        let data = DataView("Hello, World!".to_string(), key_2_id);
922        let encrypted = data.encrypt_composite(&mut ctx, key_2_id).unwrap();
923
924        let decrypted1 = encrypted.decrypt(&mut ctx, key_2_id).unwrap();
925        let decrypted2 = encrypted.decrypt(&mut ctx, new_key_id).unwrap();
926
927        // Assert that the decrypted data is the same
928        assert_eq!(decrypted1.0, decrypted2.0);
929    }
930
931    #[test]
932    fn test_wrap_unwrap() {
933        let store: KeyStore<TestIds> = KeyStore::default();
934        let mut ctx = store.context_mut();
935
936        // Aes256 CBC HMAC keys
937        let key_aes_1_id = TestSymmKey::A(1);
938        let local_key_1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
939        ctx.persist_symmetric_key(local_key_1_id, key_aes_1_id)
940            .unwrap();
941        let key_aes_2_id = TestSymmKey::A(2);
942        let local_key_2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
943        ctx.persist_symmetric_key(local_key_2_id, key_aes_2_id)
944            .unwrap();
945
946        // XChaCha20 Poly1305 keys
947        let key_xchacha_3_id = TestSymmKey::A(3);
948        let key_xchacha_3 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
949        ctx.set_symmetric_key(key_xchacha_3_id, key_xchacha_3.clone())
950            .unwrap();
951        let key_xchacha_4_id = TestSymmKey::A(4);
952        let key_xchacha_4 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
953        ctx.set_symmetric_key(key_xchacha_4_id, key_xchacha_4.clone())
954            .unwrap();
955
956        // Wrap and unwrap the keys
957        let wrapped_key_1_2 = ctx.wrap_symmetric_key(key_aes_1_id, key_aes_2_id).unwrap();
958        let wrapped_key_1_3 = ctx
959            .wrap_symmetric_key(key_aes_1_id, key_xchacha_3_id)
960            .unwrap();
961        let wrapped_key_3_1 = ctx
962            .wrap_symmetric_key(key_xchacha_3_id, key_aes_1_id)
963            .unwrap();
964        let wrapped_key_3_4 = ctx
965            .wrap_symmetric_key(key_xchacha_3_id, key_xchacha_4_id)
966            .unwrap();
967
968        // Unwrap the keys
969        let _unwrapped_key_2 = ctx
970            .unwrap_symmetric_key(key_aes_1_id, &wrapped_key_1_2)
971            .unwrap();
972        let _unwrapped_key_3 = ctx
973            .unwrap_symmetric_key(key_aes_1_id, &wrapped_key_1_3)
974            .unwrap();
975        let _unwrapped_key_1 = ctx
976            .unwrap_symmetric_key(key_xchacha_3_id, &wrapped_key_3_1)
977            .unwrap();
978        let _unwrapped_key_4 = ctx
979            .unwrap_symmetric_key(key_xchacha_3_id, &wrapped_key_3_4)
980            .unwrap();
981    }
982
983    #[test]
984    fn test_signing() {
985        let store: KeyStore<TestIds> = KeyStore::default();
986
987        // Generate and insert a key
988        let key_a0_id = TestSigningKey::A(0);
989        let key_a0 = SigningKey::make(SignatureAlgorithm::Ed25519);
990        let verifying_key = key_a0.to_verifying_key();
991        store
992            .context_mut()
993            .set_signing_key(key_a0_id, key_a0)
994            .unwrap();
995
996        assert!(store.context().has_signing_key(key_a0_id));
997
998        // Sign some data with the key
999        #[derive(Serialize, Deserialize)]
1000        struct TestData {
1001            data: String,
1002        }
1003        let signed_object = store
1004            .context()
1005            .sign(
1006                key_a0_id,
1007                &TestData {
1008                    data: "Hello".to_string(),
1009                },
1010                &SigningNamespace::ExampleNamespace,
1011            )
1012            .unwrap();
1013        let payload: Result<TestData, CryptoError> =
1014            signed_object.verify_and_unwrap(&verifying_key, &SigningNamespace::ExampleNamespace);
1015        assert!(payload.is_ok());
1016
1017        let (signature, serialized_message) = store
1018            .context()
1019            .sign_detached(
1020                key_a0_id,
1021                &TestData {
1022                    data: "Hello".to_string(),
1023                },
1024                &SigningNamespace::ExampleNamespace,
1025            )
1026            .unwrap();
1027        assert!(signature.verify(
1028            serialized_message.as_bytes(),
1029            &verifying_key,
1030            &SigningNamespace::ExampleNamespace
1031        ))
1032    }
1033
1034    #[test]
1035    fn test_account_key_rotation() {
1036        let store: KeyStore<TestIds> = KeyStore::default();
1037        let mut ctx = store.context_mut();
1038
1039        // Make the keys
1040        let current_user_signing_key_id = ctx.make_signing_key(SignatureAlgorithm::Ed25519);
1041        let current_user_private_key_id =
1042            ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
1043
1044        // Get the rotated account keys
1045        let rotated_keys = ctx
1046            .dangerous_get_v2_rotated_account_keys(
1047                current_user_private_key_id,
1048                current_user_signing_key_id,
1049            )
1050            .unwrap();
1051
1052        // Public/Private key
1053        assert_eq!(
1054            PublicKey::from_der(&rotated_keys.public_key)
1055                .unwrap()
1056                .to_der()
1057                .unwrap(),
1058            ctx.get_private_key(current_user_private_key_id)
1059                .unwrap()
1060                .to_public_key()
1061                .to_der()
1062                .unwrap()
1063        );
1064        let decrypted_private_key: Vec<u8> = rotated_keys
1065            .private_key
1066            .decrypt_with_key(&rotated_keys.user_key)
1067            .unwrap();
1068        let private_key =
1069            PrivateKey::from_der(&Pkcs8PrivateKeyBytes::from(decrypted_private_key)).unwrap();
1070        assert_eq!(
1071            private_key.to_der().unwrap(),
1072            ctx.get_private_key(current_user_private_key_id)
1073                .unwrap()
1074                .to_der()
1075                .unwrap()
1076        );
1077
1078        // Signing Key
1079        let decrypted_signing_key: Vec<u8> = rotated_keys
1080            .signing_key
1081            .decrypt_with_key(&rotated_keys.user_key)
1082            .unwrap();
1083        let signing_key =
1084            SigningKey::from_cose(&CoseKeyBytes::from(decrypted_signing_key)).unwrap();
1085        assert_eq!(
1086            signing_key.to_cose(),
1087            ctx.get_signing_key(current_user_signing_key_id)
1088                .unwrap()
1089                .to_cose(),
1090        );
1091
1092        // Signed Public Key
1093        let signed_public_key = rotated_keys.signed_public_key;
1094        let unwrapped_key = signed_public_key
1095            .verify_and_unwrap(
1096                &ctx.get_signing_key(current_user_signing_key_id)
1097                    .unwrap()
1098                    .to_verifying_key(),
1099            )
1100            .unwrap();
1101        assert_eq!(
1102            unwrapped_key.to_der().unwrap(),
1103            ctx.get_private_key(current_user_private_key_id)
1104                .unwrap()
1105                .to_public_key()
1106                .to_der()
1107                .unwrap()
1108        );
1109    }
1110
1111    #[test]
1112    fn test_encrypt_fails_when_operation_not_allowed() {
1113        use coset::iana::KeyOperation;
1114        let store = KeyStore::<TestIds>::default();
1115        let mut ctx = store.context_mut();
1116        let key_id = TestSymmKey::A(0);
1117        // Key with only Decrypt allowed
1118        let key = SymmetricCryptoKey::XChaCha20Poly1305Key(crate::XChaCha20Poly1305Key {
1119            key_id: [0u8; 16].into(),
1120            enc_key: Box::pin([0u8; 32].into()),
1121            supported_operations: vec![KeyOperation::Decrypt],
1122        });
1123        ctx.set_symmetric_key(key_id, key).unwrap();
1124        let data = DataView("should fail".to_string(), key_id);
1125        let result = data.encrypt_composite(&mut ctx, key_id);
1126        assert!(
1127            matches!(
1128                result,
1129                Err(CryptoError::KeyOperationNotSupported(KeyOperation::Encrypt))
1130            ),
1131            "Expected encrypt to fail with KeyOperationNotSupported",
1132        );
1133    }
1134
1135    #[test]
1136    fn test_move_key() {
1137        let store: KeyStore<TestIds> = KeyStore::default();
1138        let mut ctx = store.context_mut();
1139
1140        // Generate and insert a key
1141        let key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
1142
1143        assert!(ctx.has_symmetric_key(key));
1144
1145        // Move the key to a new identifier
1146        let new_key_id = TestSymmKey::A(1);
1147        ctx.persist_symmetric_key(key, new_key_id).unwrap();
1148
1149        // Ensure the old key id is gone and the new `one has the key
1150        assert!(!ctx.has_symmetric_key(key));
1151        assert!(ctx.has_symmetric_key(new_key_id));
1152    }
1153
1154    #[test]
1155    fn test_encrypt_decrypt_data_fails_when_key_is_type_0() {
1156        let store = KeyStore::<TestIds>::default();
1157        let mut ctx = store.context_mut();
1158
1159        let key_id = TestSymmKey::A(0);
1160        let key = SymmetricCryptoKey::Aes256CbcKey(crate::Aes256CbcKey {
1161            enc_key: Box::pin([0u8; 32].into()),
1162        });
1163        ctx.set_symmetric_key_internal(key_id, key).unwrap();
1164
1165        let data_to_encrypt: Vec<u8> = vec![1, 2, 3, 4, 5];
1166        let result = ctx.encrypt_data_with_symmetric_key(
1167            key_id,
1168            &data_to_encrypt,
1169            crate::ContentFormat::OctetStream,
1170        );
1171        assert!(
1172            matches!(
1173                result,
1174                Err(CryptoError::OperationNotSupported(
1175                    crate::error::UnsupportedOperationError::EncryptionNotImplementedForKey
1176                ))
1177            ),
1178            "Expected encrypt to fail when using deprecated type 0 keys",
1179        );
1180
1181        let data_to_decrypt = EncString::Aes256Cbc_B64 {
1182            iv: [0; 16],
1183            data: data_to_encrypt,
1184        }; // dummy value; shouldn't matter
1185        let result = ctx.decrypt_data_with_symmetric_key(key_id, &data_to_decrypt);
1186        assert!(
1187            matches!(
1188                result,
1189                Err(CryptoError::OperationNotSupported(
1190                    crate::error::UnsupportedOperationError::DecryptionNotImplementedForKey
1191                ))
1192            ),
1193            "Expected decrypt to fail when using deprecated type 0 keys",
1194        );
1195    }
1196
1197    #[test]
1198    fn test_wrap_unwrap_key_fails_when_key_is_type_0() {
1199        let store = KeyStore::<TestIds>::default();
1200        let mut ctx = store.context_mut();
1201
1202        let wrapping_key_id = TestSymmKey::A(0);
1203        let wrapping_key = SymmetricCryptoKey::Aes256CbcKey(crate::Aes256CbcKey {
1204            enc_key: Box::pin([0u8; 32].into()),
1205        });
1206        ctx.set_symmetric_key_internal(wrapping_key_id, wrapping_key)
1207            .unwrap();
1208
1209        let key_to_wrap_id = TestSymmKey::A(1);
1210        let key_to_wrap = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
1211        ctx.set_symmetric_key_internal(key_to_wrap_id, key_to_wrap)
1212            .unwrap();
1213
1214        let result = ctx.wrap_symmetric_key(wrapping_key_id, key_to_wrap_id);
1215        assert!(
1216            matches!(
1217                result,
1218                Err(CryptoError::OperationNotSupported(
1219                    crate::error::UnsupportedOperationError::EncryptionNotImplementedForKey
1220                ))
1221            ),
1222            "Expected encrypt to fail when using deprecated type 0 keys",
1223        );
1224
1225        let wrapped_key = &EncString::Aes256Cbc_B64 {
1226            iv: [0; 16],
1227            data: vec![0],
1228        }; // dummy value; shouldn't matter
1229        let result = ctx.unwrap_symmetric_key(wrapping_key_id, wrapped_key);
1230        assert!(
1231            matches!(
1232                result,
1233                Err(CryptoError::OperationNotSupported(
1234                    crate::error::UnsupportedOperationError::DecryptionNotImplementedForKey
1235                ))
1236            ),
1237            "Expected decrypt to fail when using deprecated type 0 keys",
1238        );
1239    }
1240}