Skip to main content

bitwarden_core/key_management/
wasm_unlock_state.rs

1//! The WASM sdk currently does not hold persistent SDK instances and instead re-createds SDK
2//! instances frequently. The unlock-state is lost, since the user-key is only held in the SDK. This
3//! file implements setting the user-key to WASM client-managed ephemeral state, so that
4//! SDK-re-creations have access to the user-key.
5//!
6//! This is not required on UNIFFI since there SDK instances live as long as the client is unlocked.
7//! Eventually, the WASM sdk will also hold SDK instances like described above.
8
9use bitwarden_crypto::SymmetricCryptoKey;
10use tracing::info;
11
12use crate::{
13    Client,
14    key_management::{self, SymmetricKeyId},
15};
16
17// The repository pattern requires us to specify a key. Here we use an empty string as the only
18// key for this repository map.
19const USER_KEY_REPOSITORY_KEY: &str = "";
20
21/// Error indicating inability to set the user key into state
22pub(crate) struct UnableToSetError;
23/// Sets the decrypted user key into the client-managed state, so that it survives re-creation of
24/// the SDK
25pub(crate) async fn copy_user_key_to_client_managed_state(
26    client: &Client,
27) -> Result<(), UnableToSetError> {
28    // Read the user-key from key-store. There should be no other reason to do this in other parts
29    // of the SDK. Do not use this as an example.
30    let user_key = {
31        let key_store = client.internal.get_key_store();
32        let ctx = key_store.context();
33        #[expect(deprecated)]
34        ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
35            .map_err(|_| UnableToSetError)?
36            .clone()
37    };
38
39    if let Ok(user_key_repository) = client
40        .platform()
41        .state()
42        .get::<key_management::UserKeyState>()
43    {
44        // We do not want to set the user-key if it is already set as that may trigger an observable
45        // loop in the client side which subscribes to the state
46        if let Ok(Some(existing_key)) = user_key_repository
47            .get(USER_KEY_REPOSITORY_KEY.to_string())
48            .await
49        {
50            if SymmetricCryptoKey::try_from(existing_key.decrypted_user_key)
51                .map_err(|_| UnableToSetError)?
52                == user_key
53            {
54                info!("User-key in client managed state is already up to date, skipping set");
55                return Ok(());
56            } else {
57                info!("User-key in client managed state is outdated, updating it");
58            }
59        } else {
60            info!("No user-key in client managed state, setting it");
61        }
62    } else {
63        // UserKeyState repository does not exist, older clients should
64        // just return gracefully.
65        info!("No UserKeyState repository exists in client managed state, exiting gracefully");
66        return Ok(());
67    }
68
69    info!("Setting the user-key to client managed-state from SDK");
70    // Set the user-key into the state repository.
71    client
72        .platform()
73        .state()
74        .get::<key_management::UserKeyState>()
75        .map_err(|_| UnableToSetError)?
76        .set(
77            USER_KEY_REPOSITORY_KEY.to_string(),
78            key_management::UserKeyState {
79                decrypted_user_key: user_key.to_base64(),
80            },
81        )
82        .await
83        .map_err(|_| UnableToSetError)
84}
85
86pub(crate) struct UnableToGetError;
87pub(crate) async fn get_user_key_from_client_managed_state(
88    client: &Client,
89) -> Result<SymmetricCryptoKey, UnableToGetError> {
90    info!("Getting the user-key from client managed-state in SDK");
91    // Get the user-key from the state repository.
92    let user_key_state = client
93        .platform()
94        .state()
95        .get::<key_management::UserKeyState>()
96        .map_err(|_| UnableToGetError)?
97        .get(USER_KEY_REPOSITORY_KEY.to_string())
98        .await
99        .map_err(|_| UnableToGetError)?
100        .ok_or(UnableToGetError)?;
101    SymmetricCryptoKey::try_from(user_key_state.decrypted_user_key).map_err(|_| UnableToGetError)
102}