Skip to main content

bitwarden_shared_unlock/
lib.rs

1//! # Shared Unlock Protocol
2//!
3//! Synchronizes vault lock state across multiple Bitwarden clients (web, browser extension,
4//! desktop) running in the same session. When a user unlocks their vault on one client, the
5//! unlock propagates to all connected clients.
6//!
7//! ## Leader-Follower Model
8//!
9//! The protocol uses a leader-follower architecture where each client type has exactly one
10//! leader determined by the device hierarchy:
11//!
12//! ```text
13//!   Web Client  ──follows──▶  Browser Extension  ──follows──▶  Desktop App
14//!   CLI Client  ──follows──▶  Desktop App
15//! ```
16//!
17//! - **Leader**: Holds authoritative lock state, broadcasts state changes to all followers.
18//! - **Follower**: Reports local state changes to its leader, applies authoritative updates from
19//!   the leader.
20//!
21//! A client can be both a leader (to clients below it) and a follower (to the client above it)
22//! simultaneously. For example, the browser extension leads web clients while following the
23//! desktop app.
24//!
25//! ## Message Types
26//!
27//! All messages are serialized as CBOR and sent over the IPC transport.
28//!
29//! | Message          | Direction          | Purpose                                           |
30//! |------------------|--------------------|---------------------------------------------------|
31//! | `StartSession`   | Follower → Leader  | Announce presence with current lock state         |
32//! | `LockStateUpdate`| Bidirectional      | Propagate lock/unlock events                      |
33//! | `HeartBeat`      | Bidirectional      | Keep session alive, suppress vault timeout        |
34//!
35//! ## Session Lifecycle
36//!
37//! ### Follower Startup
38//!
39//! ```text
40//!   Follower                          Leader
41//!     │                                 │
42//!     │──StartSession(user, state)─────▶│  Follower announces itself
43//!     │                                 │  Leader applies state if unlocked
44//!     │◀─LockStateUpdate(user, state)───│  Leader responds with authoritative state
45//!     │                                 │
46//! ```
47//!
48//! On startup, the follower sends a `StartSession` for each logged-in user. If the follower
49//! is unlocked and the leader is locked, the leader unlocks using the provided user key.
50//! The leader always responds with a `LockStateUpdate` containing the authoritative state.
51//!
52//! ### Lock/Unlock Propagation
53//!
54//! **User unlocks on follower:**
55//!
56//! ```text
57//!   Follower A                        Leader                         Follower B
58//!     │                                 │                                │
59//!     │──LockStateUpdate(Unlocked)─────▶│                                │
60//!     │                                 │──unlocks locally──             │
61//!     │                                 │──LockStateUpdate(Unlocked)────▶│
62//!     │                                 │                                │──unlocks locally──
63//! ```
64//!
65//! **User locks on leader (via device event):**
66//!
67//! ```text
68//!   Leader                          Follower A                     Follower B
69//!     │                                 │                                │
70//!     │──LockStateUpdate(Locked)───────▶│                                │
71//!     │──LockStateUpdate(Locked)────────┼───────────────────────────────▶│
72//!     │                                 │──locks locally──               │──locks locally──
73//! ```
74//!
75//! ### Heartbeat Keep-Alive
76//!
77//! ```text
78//!   Follower                          Leader
79//!     │                                 │
80//!     │──HeartBeat(user)───────────────▶│  Every N seconds
81//!     │                                 │  Leader updates last-seen timestamp
82//!     │◀─HeartBeat(user)────────────────│  Leader echoes back
83//!     |◀─LockStateUpdate────────────────│  Leader always sends an authoritative state update to prevent desyncs
84//!     │──suppresses vault timeout──     │
85//!     │                                 │
86//! ```
87//!
88//! The follower sends a `HeartBeat` for each logged-in user every [`HEARTBEAT_INTERVAL`]
89//! On receiving the echo, the follower suppresses its vault timeout timer,
90//! keeping the vault unlocked as long as the session is active. Stale sessions are pruned.
91//! If the leader receives a `HeartBeat` from a user it does not know (for example due to a process
92//! reload), it responds with a `RequestSessionStart` message to request the follower to start a
93//! session.
94//!
95//! ## Security Definitions
96//!
97//! - Attacker Model:
98//!   - Attacker gains user-space access to the device while the vault has been locked (steals the
99//!     device)
100//! - Security Goal:
101//!   - Attacker cannot gain access to the vault key material
102//!
103//! This security definition is aimed at stolen or seized devices. Forensics should not uncover
104//! (passively) recorded or otherwise left behind key material. The IPC encryption prevents such a
105//! compromise.
106//!
107//! There is no further protection provided against active attackers running in userspace while the
108//! vault is unlocked on any of the clients on the device.
109//!
110//! - Attacker Model:
111//!   - Attacker controls a website that is not the web vault
112//! - Security Goal:
113//!   - Attacker cannot gain access to the vault key material
114//!
115//! This is met by origin validation.
116
117use bitwarden_core::UserId;
118use bitwarden_crypto::SymmetricCryptoKey;
119use serde::{Deserialize, Serialize};
120
121mod drivers;
122pub use drivers::*;
123mod follower;
124pub use follower::*;
125mod leader;
126pub use leader::*;
127mod message;
128pub use message::*;
129
130/// Wasm support module for shared unlock
131#[cfg(feature = "wasm")]
132pub mod wasm;
133
134/// Interval used by followers to send heartbeat keep-alive messages to their leader.
135pub const HEARTBEAT_INTERVAL: std::time::Duration = std::time::Duration::from_secs(2);
136/// Additional grace period added to the vault timeout when suppressing it on heartbeat
137pub const VAULT_TIMEOUT_GRACE_PERIOD: std::time::Duration = std::time::Duration::from_secs(1);
138
139#[cfg(test)]
140mod tests;
141
142/// Represents the lock state of a user.
143#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
144pub enum LockState {
145    /// The user is locked (does not have a user-key in memory).
146    Locked,
147    /// The user is unlocked (has a user-key in memory).
148    Unlocked {
149        /// The user-key of the unlocked user
150        user_key: SymmetricCryptoKey,
151    },
152}
153
154/// The device (client) has several events that need to be reported to the shared unlock system.
155/// This enum represents the events that need to be reported.
156#[derive(Serialize, Deserialize, zeroize::ZeroizeOnDrop)]
157#[cfg_attr(
158    feature = "wasm",
159    derive(tsify::Tsify),
160    tsify(into_wasm_abi, from_wasm_abi)
161)]
162pub enum DeviceEvent {
163    /// The user with the given user id has been locked manually in the UI
164    ManualLock {
165        #[zeroize(skip)]
166        /// User whose vault was manually locked.
167        user_id: UserId,
168    },
169    /// The user with the given user id has been unlocked manually in the UI
170    ManualUnlock {
171        #[zeroize(skip)]
172        /// User whose vault was manually unlocked.
173        user_id: UserId,
174        /// Raw user key bytes used to unlock the vault.
175        #[tsify(type = "SymmetricKey")]
176        user_key: SymmetricCryptoKey,
177    },
178}