Skip to main content

bitwarden_crypto/store/
context.rs

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