Skip to main content

bitwarden_core/key_management/
state_bridge.rs

1//! The state bridge is a temporary layer that allows quickly transitioning
2//! non-repository shaped state to be accessible from within the SDK.
3//!
4//! This is not a public API that should be used by other teams. It will be
5//! replaced by a `bitwarden-state` implementation as soon as that gains support
6//! for non-repository state.
7
8use std::sync::{Arc, Mutex};
9
10use bitwarden_crypto::{
11    EncString, Kdf, KeyId, SymmetricCryptoKey, safe::PasswordProtectedKeyEnvelope,
12};
13#[cfg(feature = "wasm")]
14use wasm_bindgen::prelude::*;
15
16use crate::{
17    Client,
18    key_management::{
19        MasterPasswordUnlockData, V2UpgradeToken, WebAuthnPrfUnlockData,
20        account_cryptographic_state::WrappedAccountCryptographicState,
21    },
22};
23
24/// Thread-safe wrapper around the registered [`StateBridgeImpl`] instance.
25pub struct StateBridge {
26    implementation: Mutex<Option<Arc<dyn StateBridgeImpl + Send + Sync>>>,
27}
28
29impl StateBridge {
30    /// Creates an empty bridge with no registered implementation.
31    pub fn new() -> Self {
32        Self {
33            implementation: Mutex::new(None),
34        }
35    }
36
37    /// Returns true if an implementation has been registered.
38    pub fn is_registered(&self) -> bool {
39        self.implementation
40            .lock()
41            .expect("Mutex is not poisoned")
42            .is_some()
43    }
44
45    /// Registers the host-supplied implementation. Replaces any prior registration.
46    pub fn register(&self, implementation: Box<dyn StateBridgeImpl + Send + Sync>) {
47        *self.implementation.lock().expect("Mutex is not poisoned") = Some(implementation.into());
48    }
49}
50
51impl Default for StateBridge {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57/// Client for interacting with the key-management state bridge. This is used to read and write
58/// state held by the clients
59#[derive(Clone)]
60#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
61#[cfg_attr(feature = "wasm", wasm_bindgen)]
62pub struct StateBridgeClient {
63    pub(crate) client: crate::Client,
64}
65
66impl Client {
67    /// A temporary client to bridge KM state into the SDK.
68    pub fn km_state_bridge(&self) -> StateBridgeClient {
69        StateBridgeClient {
70            client: self.clone(),
71        }
72    }
73}
74
75impl StateBridgeClient {
76    /// Returns true if a state bridge implementation has been registered.
77    pub fn is_bridge_registered(&self) -> bool {
78        self.client.internal.state_bridge.is_registered()
79    }
80
81    /// Registers a bridge implementation used to read and write temporary key-management state.
82    pub fn register_bridge(&self, bridge_impl: Box<dyn StateBridgeImpl + Send + Sync>) {
83        self.client.internal.state_bridge.register(bridge_impl);
84    }
85}
86
87#[cfg(target_arch = "wasm32")]
88#[wasm_bindgen]
89extern "C" {
90    /// Raw JavaScript-side state bridge implementation. The corresponding TypeScript
91    /// interface (`WasmStateBridge`) and the per-method extern bindings are generated
92    /// by the `state_bridge!` macro below.
93    #[wasm_bindgen(typescript_type = "WasmStateBridge")]
94    pub type RawWasmStateBridge;
95}
96
97#[cfg(target_arch = "wasm32")]
98use bitwarden_threading::ThreadBoundRunner;
99
100#[cfg(target_arch = "wasm32")]
101/// Adapter that lets a JavaScript-supplied `WasmStateBridge` implement
102/// [`StateBridgeImpl`]. The trait impl itself is generated by the
103/// `state_bridge!` macro below.
104pub struct WasmStateBridge(pub(crate) ThreadBoundRunner<RawWasmStateBridge>);
105
106#[cfg(target_arch = "wasm32")]
107#[wasm_bindgen]
108impl StateBridgeClient {
109    /// Registers a the state bridge implementation provided by the host environment.
110    pub fn register_bridge_impl(&self, bridge_impl: RawWasmStateBridge) {
111        self.client
112            .internal
113            .state_bridge
114            .register(Box::new(WasmStateBridge(ThreadBoundRunner::new(
115                bridge_impl,
116            ))));
117    }
118}
119
120#[cfg(feature = "uniffi")]
121/// Adapter that lets a foreign-supplied (Swift/Kotlin) implementation of
122/// [`StateBridgeForeignImpl`] act as a [`StateBridgeImpl`]. The trait impl is
123/// generated by the `state_bridge!` macro below.
124pub struct UniffiStateBridge(pub(crate) Arc<dyn StateBridgeForeignImpl>);
125
126#[cfg(feature = "uniffi")]
127#[uniffi::export]
128impl StateBridgeClient {
129    /// Registers the host-supplied state bridge implementation.
130    pub fn register_bridge_impl(&self, bridge_impl: Arc<dyn StateBridgeForeignImpl>) {
131        self.client
132            .internal
133            .state_bridge
134            .register(Box::new(UniffiStateBridge(bridge_impl)));
135    }
136}
137
138// Generates the full state bridge surface for the listed fields.
139//
140// Each field expands to three methods on each of [`StateBridgeImpl`], [`StateBridge`], and
141// [`StateBridgeClient`] (`set_$name`, `get_$name`, `clear_$name`); WASM extern bindings on
142// [`RawWasmStateBridge`]; a [`StateBridgeImpl`] forwarder impl for [`WasmStateBridge`]; the
143// matching `WasmStateBridge` TypeScript interface; and a `#[cfg(test)] pub(crate) mod test_support`
144// containing an `InMemoryStateBridge` test fixture.
145bitwarden_state_bridge_macro::state_bridge! {
146    user_key: SymmetricCryptoKey as ts "SymmetricKey",
147    user_key_id: KeyId as ts "KeyId",
148    persistent_pin_envelope: PasswordProtectedKeyEnvelope as ts "PasswordProtectedKeyEnvelope",
149    ephemeral_pin_envelope: PasswordProtectedKeyEnvelope as ts "PasswordProtectedKeyEnvelope",
150    encrypted_pin: EncString as ts "EncString",
151    v2_upgrade_token: V2UpgradeToken as ts "V2UpgradeToken",
152    account_cryptographic_state: WrappedAccountCryptographicState as ts "WrappedAccountCryptographicState",
153    masterpassword_unlock_data: MasterPasswordUnlockData as ts "MasterPasswordUnlockData",
154    webauthn_prf_unlock_data: WebAuthnPrfUnlockData as ts "WebAuthnPrfUnlockData",
155    kdf_config: Kdf as ts "Kdf",
156}