Skip to main content

bitwarden_core/key_management/
local_user_data_key.rs

1use bitwarden_crypto::{EncString, KeyStoreContext};
2use key_management::LocalUserDataKeyState;
3use thiserror::Error;
4use tracing::instrument;
5
6use crate::{
7    key_management,
8    key_management::{KeyIds, SymmetricKeyId},
9};
10
11/// An indirect symmetric key for encrypting local user data (e.g. password generator history).
12/// Enables offline decryption of local data after a key rotation: only the wrapped key is
13/// re-encrypted; the local user data key itself stays intact.
14#[derive(Debug, Clone)]
15pub(crate) struct WrappedLocalUserDataKey(pub(crate) EncString);
16
17impl WrappedLocalUserDataKey {
18    /// Create a user key, wrapped by the user key.
19    #[instrument(skip(ctx), err)]
20    pub(crate) fn from_context_user_key(
21        ctx: &mut KeyStoreContext<KeyIds>,
22    ) -> Result<Self, LocalUserDataKeyError> {
23        let wrapped_local_user_data_key = ctx
24            .wrap_symmetric_key(SymmetricKeyId::User, SymmetricKeyId::User)
25            .map_err(|_| LocalUserDataKeyError::EncryptionFailed)?;
26        Ok(WrappedLocalUserDataKey(wrapped_local_user_data_key))
27    }
28
29    /// Unwrap the local user data key and set it in the context under the
30    /// [`SymmetricKeyId::LocalUserData`] key id.
31    #[instrument(skip(self, ctx), err)]
32    pub(crate) fn unwrap_to_context(
33        &self,
34        ctx: &mut KeyStoreContext<KeyIds>,
35    ) -> Result<(), LocalUserDataKeyError> {
36        let local_id = ctx
37            .unwrap_symmetric_key(SymmetricKeyId::User, &self.0)
38            .map_err(|_| LocalUserDataKeyError::DecryptionFailed)?;
39        ctx.persist_symmetric_key(local_id, SymmetricKeyId::LocalUserData)
40            .map_err(|_| LocalUserDataKeyError::DecryptionFailed)?;
41        Ok(())
42    }
43}
44
45/// Errors that can occur when working with [`WrappedLocalUserDataKey`].
46#[derive(Debug, Error)]
47pub enum LocalUserDataKeyError {
48    /// Decryption of a wrapped key failed
49    #[error("Decryption failed")]
50    DecryptionFailed,
51    /// Failed to encrypt a key
52    #[error("Encryption failed")]
53    EncryptionFailed,
54}
55
56impl From<WrappedLocalUserDataKey> for LocalUserDataKeyState {
57    fn from(wrapped_key: WrappedLocalUserDataKey) -> Self {
58        Self {
59            wrapped_key: wrapped_key.0,
60        }
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use bitwarden_crypto::{Decryptable, KeyStore, PrimitiveEncryptable};
67
68    use super::*;
69    use crate::key_management::{KeyIds, SymmetricKeyId};
70
71    fn make_key_store_with_user_key() -> KeyStore<KeyIds> {
72        let key_store = KeyStore::<KeyIds>::default();
73        let mut ctx = key_store.context_mut();
74        let user_key = ctx.generate_symmetric_key();
75        ctx.persist_symmetric_key(user_key, SymmetricKeyId::User)
76            .expect("persisting user key should succeed");
77        drop(ctx);
78        key_store
79    }
80
81    #[test]
82    fn test_from_context_user_key_wraps_user_key() {
83        let key_store = make_key_store_with_user_key();
84        let mut ctx = key_store.context_mut();
85
86        let plaintext = "test data";
87        let ciphertext = plaintext
88            .encrypt(&mut ctx, SymmetricKeyId::User)
89            .expect("encryption with user key should succeed");
90
91        let wrapped = WrappedLocalUserDataKey::from_context_user_key(&mut ctx)
92            .expect("wrapping should succeed");
93        wrapped
94            .unwrap_to_context(&mut ctx)
95            .expect("unwrapping should succeed");
96
97        // Verify LocalUserData key is the same as User key: data encrypted with User
98        // must be decryptable with LocalUserData.
99        let decrypted: String = ciphertext
100            .decrypt(&mut ctx, SymmetricKeyId::LocalUserData)
101            .expect("decryption with local user data key should succeed");
102        assert_eq!(decrypted, plaintext);
103    }
104
105    #[test]
106    fn test_unwrap_to_context_fails_with_wrong_key() {
107        let key_store_a = make_key_store_with_user_key();
108        let wrapped = {
109            let mut ctx = key_store_a.context_mut();
110            WrappedLocalUserDataKey::from_context_user_key(&mut ctx)
111                .expect("wrapping should succeed")
112        };
113
114        let key_store_b = make_key_store_with_user_key();
115        let mut ctx_b = key_store_b.context_mut();
116        assert!(
117            wrapped.unwrap_to_context(&mut ctx_b).is_err(),
118            "unwrapping with a different key should fail"
119        );
120    }
121}