Skip to main content

bitwarden_core/key_management/
pin_lock_system.rs

1//! Pin-based unlock in Bitwarden works using a `PasswordProtectedKeyEnvelope`, which is sealed with
2//! the PIN and contains the user-key. When unlocking with PIN, the envelope is unsealed with the
3//! PIN and the key is loaded into the key-store.
4//!
5//! There are two modes of PIN-based unlock: Before-first-unlock (BFU) and after-first-unlock (AFU).
6//! In BFU mode, the PIN envelope is persisted to disk. In AFU mode, the PIN envelope is only stored
7//! in memory. The memory copy is always loaded into memory when transitioning from BFU to AFU mode
8//! with an unlock.
9
10use bitwarden_crypto::{
11    Decryptable, KeyStore, PrimitiveEncryptable,
12    safe::{PasswordProtectedKeyEnvelope, PasswordProtectedKeyEnvelopeNamespace},
13};
14use serde::{Deserialize, Serialize};
15use tracing::warn;
16#[cfg(feature = "wasm")]
17use tsify::Tsify;
18#[cfg(feature = "wasm")]
19use wasm_bindgen::prelude::*;
20
21use crate::{
22    Client,
23    key_management::{KeySlotIds, SymmetricKeySlotId},
24};
25
26/// Pin unlock can be configured to use one of two modes. Before-first-unlock and
27/// after-first-unlock. In AFU mode, the PIN is available only after unlocking once with the master
28/// password or another unlock method. In BFU mode, PIN unlock is available right after app start.
29/// For this, the PIN-encrypted vault key is stored on disk.
30#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
31#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
32#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
33pub enum PinLockType {
34    /// Pin unlock is available after app start
35    BeforeFirstUnlock,
36    /// Pin unlock is available after unlocking with another method at least once during the app
37    /// session
38    AfterFirstUnlock,
39}
40
41#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
42#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
43#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
44/// Current availability state for PIN-based unlock.
45pub enum PinUnlockStatus {
46    /// A PIN is configured and the PIN envelope is available for decryption, so PIN-based unlock
47    /// can be attempted.
48    Available,
49    /// A PIN is configured, but the vault must be unlocked using another method first.
50    NeedsUnlock,
51    /// No PIN is configured.
52    NotSet,
53}
54
55pub(crate) enum UnlockError {
56    NoPinSet,
57    PinWrong,
58    InternalError,
59}
60
61/// Provides PIN-based unlock functionality. This includes enrolling into PIN-based unlock,
62/// unlocking using the PIN and handling necessary operations (PIN envelope refreshing when
63/// transitioning to after-first-unlock mode).
64pub struct PinLockSystem<'a> {
65    client: &'a Client,
66}
67
68impl PinLockSystem<'_> {
69    fn key_store(&self) -> &KeyStore<KeySlotIds> {
70        self.client.internal.get_key_store()
71    }
72
73    /// Creates a PIN lock system view for a client instance.
74    pub fn with_client(client: &Client) -> PinLockSystem<'_> {
75        PinLockSystem { client }
76    }
77
78    /// Retrieves the currently active PIN envelope.
79    ///
80    /// If both envelopes are present, the ephemeral envelope is preferred.
81    async fn get_active_pin_envelope(&self) -> Option<PasswordProtectedKeyEnvelope> {
82        let mut pin_protected_key_envelope = self
83            .client
84            .km_state_bridge()
85            .get_ephemeral_pin_envelope()
86            .await;
87        if pin_protected_key_envelope.is_none() {
88            pin_protected_key_envelope = self
89                .client
90                .km_state_bridge()
91                .get_persistent_pin_envelope()
92                .await;
93        }
94        pin_protected_key_envelope
95    }
96
97    /// Attempts to unlock the user key using `pin`.
98    ///
99    /// Returns [`UnlockError::NoPinSet`] if no PIN is configured,
100    /// [`UnlockError::PinWrong`] if `pin` is incorrect, and
101    /// [`UnlockError::InternalError`] for other failures.
102    pub(crate) async fn unlock(&self, pin: &str) -> Result<(), UnlockError> {
103        let pin_envelope = Self::get_active_pin_envelope(self)
104            .await
105            .ok_or(UnlockError::NoPinSet)?;
106
107        // Unseal to key ctx
108        let mut ctx = self.key_store().context_mut();
109        let key_slot = pin_envelope
110            .unseal(
111                pin,
112                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
113                &mut ctx,
114            )
115            .map_err(|e| match e {
116                bitwarden_crypto::safe::PasswordProtectedKeyEnvelopeError::WrongPassword => {
117                    UnlockError::PinWrong
118                }
119                _ => UnlockError::InternalError,
120            })?;
121
122        // The key is currently in the local ctx and would be dropped when ctx goes out of scope.
123        // Persist it to the keystore
124        ctx.persist_symmetric_key(key_slot, SymmetricKeySlotId::User)
125            .map_err(|_| UnlockError::InternalError)
126    }
127
128    /// Refreshes in-memory PIN unlock material after a successful non-PIN unlock.
129    ///
130    /// This recreates the ephemeral PIN envelope from the encrypted PIN, when available.
131    pub(crate) async fn on_unlock(&self) {
132        // Remove once all clients, ios, android implement the state bridge
133        if !self.client.km_state_bridge().is_bridge_registered() {
134            return;
135        }
136
137        let encrypted_pin = self.client.km_state_bridge().get_encrypted_pin().await;
138
139        // If PIN unlock is not enabled, do nothing
140        let Some(encrypted_pin) = encrypted_pin else {
141            return;
142        };
143
144        // Make the fresh PIN envelope
145        let Ok(pin_envelope) = (|| -> Result<PasswordProtectedKeyEnvelope, ()> {
146            let mut ctx = self.key_store().context_mut();
147            let pin: String = encrypted_pin
148                .decrypt(&mut ctx, SymmetricKeySlotId::User)
149                .map_err(|_| ())?;
150            PasswordProtectedKeyEnvelope::seal(
151                SymmetricKeySlotId::User,
152                pin.as_str(),
153                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
154                &ctx,
155            )
156            .map_err(|_| ())
157        })() else {
158            warn!("Failed to create PIN envelope");
159            return;
160        };
161
162        // Store it to memory
163        self.client
164            .km_state_bridge()
165            .set_ephemeral_pin_envelope(&pin_envelope)
166            .await;
167    }
168
169    /// Sets the PIN and stores the generated envelope according to the lock type.
170    pub async fn set_pin(&self, pin: String, lock_type: PinLockType) -> Result<(), ()> {
171        // Clear the existing configuration
172        self.client
173            .km_state_bridge()
174            .clear_persistent_pin_envelope()
175            .await;
176        self.client
177            .km_state_bridge()
178            .clear_ephemeral_pin_envelope()
179            .await;
180        self.client.km_state_bridge().clear_encrypted_pin().await;
181
182        let pin_envelope: PasswordProtectedKeyEnvelope = PasswordProtectedKeyEnvelope::seal(
183            SymmetricKeySlotId::User,
184            pin.as_str(),
185            PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
186            &self.key_store().context_mut(),
187        )
188        .map_err(|_| ())?;
189        let encrypted_pin = pin
190            .encrypt(
191                &mut self.key_store().context_mut(),
192                SymmetricKeySlotId::User,
193            )
194            .map_err(|_| ())?;
195
196        self.client
197            .km_state_bridge()
198            .set_encrypted_pin(&encrypted_pin)
199            .await;
200        self.client
201            .km_state_bridge()
202            .set_ephemeral_pin_envelope(&pin_envelope)
203            .await;
204
205        if lock_type == PinLockType::BeforeFirstUnlock {
206            self.client
207                .km_state_bridge()
208                .set_persistent_pin_envelope(&pin_envelope)
209                .await;
210        }
211
212        Ok(())
213    }
214
215    /// Clears both persistent and ephemeral PIN envelopes.
216    pub async fn unset_pin(&self) {
217        self.client
218            .km_state_bridge()
219            .clear_persistent_pin_envelope()
220            .await;
221        self.client
222            .km_state_bridge()
223            .clear_ephemeral_pin_envelope()
224            .await;
225        self.client.km_state_bridge().clear_encrypted_pin().await;
226    }
227
228    /// Returns the lock type for the currently configured PIN.
229    pub async fn get_pin_lock_type(&self) -> Option<PinLockType> {
230        if self
231            .client
232            .km_state_bridge()
233            .get_persistent_pin_envelope()
234            .await
235            .is_some()
236        {
237            return Some(PinLockType::BeforeFirstUnlock);
238        }
239
240        // Encrypted pin is set for either lock type, persistent pin only for BFU. The ephemeral
241        // envelope may not be set after restarting a client, until the client enters AFU
242        // mode.
243        if self
244            .client
245            .km_state_bridge()
246            .get_encrypted_pin()
247            .await
248            .is_some()
249        {
250            return Some(PinLockType::AfterFirstUnlock);
251        }
252
253        None
254    }
255
256    /// Returns the current PIN unlock status.
257    ///
258    /// If a lock type is configured but no ephemeral envelope is currently present,
259    /// the status is [`PinUnlockStatus::NeedsUnlock`].
260    pub async fn get_pin_status(&self) -> PinUnlockStatus {
261        match Self::get_pin_lock_type(self).await {
262            Some(PinLockType::BeforeFirstUnlock) => {
263                if self.get_active_pin_envelope().await.is_some() {
264                    PinUnlockStatus::Available
265                } else {
266                    PinUnlockStatus::NeedsUnlock
267                }
268            }
269            Some(PinLockType::AfterFirstUnlock) => {
270                if self
271                    .client
272                    .km_state_bridge()
273                    .get_ephemeral_pin_envelope()
274                    .await
275                    .is_some()
276                {
277                    PinUnlockStatus::Available
278                } else {
279                    // This should not happen as AFU should always have the ephemeral envelope, but
280                    // we handle it just in case.
281                    PinUnlockStatus::NeedsUnlock
282                }
283            }
284            None => PinUnlockStatus::NotSet,
285        }
286    }
287
288    /// Returns the configured PIN, if an encrypted PIN is available and decryptable.
289    pub async fn get_pin(&self) -> Option<String> {
290        let encrypted_pin = self.client.km_state_bridge().get_encrypted_pin().await?;
291        encrypted_pin
292            .decrypt(
293                &mut self.client.internal.get_key_store().context_mut(),
294                SymmetricKeySlotId::User,
295            )
296            .ok()
297    }
298
299    /// Validates that the provided PIN can decrypt the stored PIN envelope.
300    pub async fn validate_pin(&self, pin: String) -> bool {
301        let pin_envelope = self.get_active_pin_envelope().await;
302        let Some(pin_envelope) = pin_envelope else {
303            return false;
304        };
305
306        pin_envelope
307            .unseal(
308                pin.as_str(),
309                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
310                &mut self.key_store().context_mut(),
311            )
312            .is_ok()
313    }
314}
315
316#[cfg(test)]
317mod tests {
318    use bitwarden_crypto::{EncString, KeyId, SymmetricKeyAlgorithm};
319
320    use super::*;
321    use crate::key_management::state_bridge::test_support::InMemoryStateBridge;
322
323    fn decrypt_encrypted_pin(client: &Client, encrypted_pin: &EncString) -> String {
324        encrypted_pin
325            .decrypt(
326                &mut client.internal.get_key_store().context_mut(),
327                SymmetricKeySlotId::User,
328            )
329            .expect("encrypted pin should decrypt successfully")
330    }
331
332    /// Returns the `KeyId` of the symmetric key currently in `SymmetricKeySlotId::User`.
333    fn user_key_id(client: &Client) -> KeyId {
334        client
335            .internal
336            .get_key_store()
337            .context()
338            .get_symmetric_key_id(SymmetricKeySlotId::User)
339            .expect("user key present")
340    }
341
342    /// Asserts the envelope wraps `expected_key_id` and unseals successfully under `pin`.
343    fn assert_envelope_wraps_user_key(
344        client: &Client,
345        envelope: &PasswordProtectedKeyEnvelope,
346        pin: &str,
347        expected_key_id: &KeyId,
348    ) {
349        assert_eq!(
350            envelope
351                .contained_key_id()
352                .expect("contained key id readable"),
353            Some(expected_key_id.clone()),
354            "envelope wraps a key other than the current user key",
355        );
356        let _ = envelope
357            .unseal(
358                pin,
359                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
360                &mut client.internal.get_key_store().context_mut(),
361            )
362            .expect("envelope unseals with the configured pin");
363    }
364
365    fn client_with_user_key() -> Client {
366        let client = Client::new(None);
367        client
368            .km_state_bridge()
369            .register_bridge(Box::new(InMemoryStateBridge::default()));
370        {
371            let key_store = client.internal.get_key_store();
372            let mut ctx = key_store.context_mut();
373            let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
374            ctx.persist_symmetric_key(user_key, SymmetricKeySlotId::User)
375                .expect("persisting user key should succeed");
376        }
377        client
378    }
379
380    fn seal_envelope(client: &Client, pin: &str) -> PasswordProtectedKeyEnvelope {
381        PasswordProtectedKeyEnvelope::seal(
382            SymmetricKeySlotId::User,
383            pin,
384            PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
385            &client.internal.get_key_store().context_mut(),
386        )
387        .expect("seal succeeds")
388    }
389
390    #[tokio::test]
391    async fn set_pin_bfu_persists_both_envelopes() {
392        let client = client_with_user_key();
393        let user_key_id = user_key_id(&client);
394        let system = PinLockSystem::with_client(&client);
395
396        system
397            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
398            .await
399            .expect("set_pin succeeds");
400
401        let bridge = client.km_state_bridge();
402        let persistent = bridge
403            .get_persistent_pin_envelope()
404            .await
405            .expect("persistent envelope present");
406        let ephemeral = bridge
407            .get_ephemeral_pin_envelope()
408            .await
409            .expect("ephemeral envelope present");
410        let encrypted_pin = bridge
411            .get_encrypted_pin()
412            .await
413            .expect("encrypted pin present");
414
415        assert_envelope_wraps_user_key(&client, &persistent, "1234", &user_key_id);
416        assert_envelope_wraps_user_key(&client, &ephemeral, "1234", &user_key_id);
417        assert_eq!(decrypt_encrypted_pin(&client, &encrypted_pin), "1234");
418
419        assert_eq!(
420            system.get_pin_lock_type().await,
421            Some(PinLockType::BeforeFirstUnlock)
422        );
423        assert_eq!(system.get_pin_status().await, PinUnlockStatus::Available);
424    }
425
426    #[tokio::test]
427    async fn set_pin_afu_persists_only_ephemeral() {
428        let client = client_with_user_key();
429        let user_key_id = user_key_id(&client);
430        let system = PinLockSystem::with_client(&client);
431
432        system
433            .set_pin("1234".into(), PinLockType::AfterFirstUnlock)
434            .await
435            .expect("set_pin succeeds");
436
437        let bridge = client.km_state_bridge();
438        assert!(bridge.get_persistent_pin_envelope().await.is_none());
439        let ephemeral = bridge
440            .get_ephemeral_pin_envelope()
441            .await
442            .expect("ephemeral envelope present");
443        let encrypted_pin = bridge
444            .get_encrypted_pin()
445            .await
446            .expect("encrypted pin present");
447
448        assert_envelope_wraps_user_key(&client, &ephemeral, "1234", &user_key_id);
449        assert_eq!(decrypt_encrypted_pin(&client, &encrypted_pin), "1234");
450
451        assert_eq!(
452            system.get_pin_lock_type().await,
453            Some(PinLockType::AfterFirstUnlock)
454        );
455        assert_eq!(system.get_pin_status().await, PinUnlockStatus::Available);
456    }
457
458    #[tokio::test]
459    async fn set_pin_overwrites_existing_state() {
460        let client = client_with_user_key();
461        let system = PinLockSystem::with_client(&client);
462
463        system
464            .set_pin("first".into(), PinLockType::BeforeFirstUnlock)
465            .await
466            .expect("first set_pin");
467        system
468            .set_pin("second".into(), PinLockType::AfterFirstUnlock)
469            .await
470            .expect("second set_pin");
471
472        let bridge = client.km_state_bridge();
473        assert!(
474            bridge.get_persistent_pin_envelope().await.is_none(),
475            "switching to AFU must clear the persistent envelope"
476        );
477        assert_eq!(
478            system.get_pin_lock_type().await,
479            Some(PinLockType::AfterFirstUnlock)
480        );
481        assert!(system.validate_pin("second".into()).await);
482        assert!(!system.validate_pin("first".into()).await);
483    }
484
485    #[tokio::test]
486    async fn unset_pin_clears_all_state() {
487        let client = client_with_user_key();
488        let system = PinLockSystem::with_client(&client);
489
490        system
491            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
492            .await
493            .expect("set_pin succeeds");
494        system.unset_pin().await;
495
496        let bridge = client.km_state_bridge();
497        assert!(bridge.get_persistent_pin_envelope().await.is_none());
498        assert!(bridge.get_ephemeral_pin_envelope().await.is_none());
499        assert!(bridge.get_encrypted_pin().await.is_none());
500        assert_eq!(system.get_pin_lock_type().await, None);
501        assert_eq!(system.get_pin_status().await, PinUnlockStatus::NotSet);
502    }
503
504    #[tokio::test]
505    async fn unlock_with_correct_pin_persists_user_key() {
506        let client = client_with_user_key();
507        let system = PinLockSystem::with_client(&client);
508
509        let pre_unlock_user_key_id = user_key_id(&client);
510        // Snapshot ciphertext under the original user key, then drop the key from memory.
511        system
512            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
513            .await
514            .expect("set_pin succeeds");
515        client.internal.get_key_store().clear();
516
517        assert!(system.unlock("1234").await.is_ok());
518        let post_unlock_user_key_id = user_key_id(&client);
519        assert_eq!(post_unlock_user_key_id, pre_unlock_user_key_id);
520    }
521
522    #[tokio::test]
523    async fn unlock_with_wrong_pin_returns_pin_wrong() {
524        let client = client_with_user_key();
525        let system = PinLockSystem::with_client(&client);
526        system
527            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
528            .await
529            .expect("set_pin succeeds");
530
531        assert!(matches!(
532            system.unlock("wrong").await,
533            Err(UnlockError::PinWrong)
534        ));
535    }
536
537    #[tokio::test]
538    async fn unlock_with_no_pin_set_returns_no_pin_set() {
539        let client = client_with_user_key();
540        let system = PinLockSystem::with_client(&client);
541
542        assert!(matches!(
543            system.unlock("anything").await,
544            Err(UnlockError::NoPinSet)
545        ));
546    }
547
548    #[tokio::test]
549    async fn unlock_prefers_ephemeral_envelope_over_persistent() {
550        let client = client_with_user_key();
551        let system = PinLockSystem::with_client(&client);
552        system
553            .set_pin("persistent".into(), PinLockType::BeforeFirstUnlock)
554            .await
555            .expect("set_pin succeeds");
556
557        // Replace the ephemeral envelope with one sealed under a different PIN
558        // (same user key still in the slot).
559        let ephemeral = seal_envelope(&client, "ephemeral");
560        client
561            .km_state_bridge()
562            .set_ephemeral_pin_envelope(&ephemeral)
563            .await;
564
565        assert!(system.unlock("ephemeral").await.is_ok());
566        assert!(matches!(
567            system.unlock("persistent").await,
568            Err(UnlockError::PinWrong)
569        ));
570    }
571
572    #[tokio::test]
573    async fn get_pin_status_available_bfu() {
574        let client = client_with_user_key();
575        let system = PinLockSystem::with_client(&client);
576        system
577            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
578            .await
579            .expect("set_pin succeeds");
580
581        // Simulate app restart: ephemeral memory state is gone, only persisted disk state remains.
582        client
583            .km_state_bridge()
584            .clear_ephemeral_pin_envelope()
585            .await;
586
587        assert_eq!(system.get_pin_status().await, PinUnlockStatus::Available);
588        assert_eq!(
589            system.get_pin_lock_type().await,
590            Some(PinLockType::BeforeFirstUnlock)
591        );
592    }
593
594    #[tokio::test]
595    async fn on_unlock_rebuilds_ephemeral_envelope() {
596        let client = client_with_user_key();
597        let user_key_id = user_key_id(&client);
598        let system = PinLockSystem::with_client(&client);
599        system
600            .set_pin("1234".into(), PinLockType::AfterFirstUnlock)
601            .await
602            .expect("set_pin succeeds");
603        client
604            .km_state_bridge()
605            .clear_ephemeral_pin_envelope()
606            .await;
607        assert_eq!(system.get_pin_status().await, PinUnlockStatus::NeedsUnlock);
608
609        system.on_unlock().await;
610
611        let rebuilt = client
612            .km_state_bridge()
613            .get_ephemeral_pin_envelope()
614            .await
615            .expect("on_unlock should restore the ephemeral envelope");
616        assert_envelope_wraps_user_key(&client, &rebuilt, "1234", &user_key_id);
617        assert_eq!(system.get_pin_status().await, PinUnlockStatus::Available);
618        assert!(system.unlock("1234").await.is_ok());
619    }
620
621    #[tokio::test]
622    async fn on_unlock_is_noop_when_no_encrypted_pin() {
623        let client = client_with_user_key();
624        let system = PinLockSystem::with_client(&client);
625
626        system.on_unlock().await;
627
628        assert_eq!(system.get_pin_status().await, PinUnlockStatus::NotSet);
629    }
630
631    #[tokio::test]
632    async fn on_unlock_is_noop_when_bridge_not_registered() {
633        let client = Client::new(None);
634        let system = PinLockSystem::with_client(&client);
635
636        // Must not panic even though no StateBridgeImpl is registered.
637        system.on_unlock().await;
638    }
639
640    #[tokio::test]
641    async fn get_pin_returns_set_pin() {
642        let client = client_with_user_key();
643        let system = PinLockSystem::with_client(&client);
644
645        assert_eq!(system.get_pin().await, None);
646
647        system
648            .set_pin("1234".into(), PinLockType::AfterFirstUnlock)
649            .await
650            .expect("set_pin succeeds");
651        assert_eq!(system.get_pin().await, Some("1234".to_owned()));
652
653        system.unset_pin().await;
654        assert_eq!(system.get_pin().await, None);
655    }
656
657    #[tokio::test]
658    async fn validate_pin_matches_only_correct_pin() {
659        let client = client_with_user_key();
660        let system = PinLockSystem::with_client(&client);
661
662        assert!(!system.validate_pin("anything".into()).await);
663
664        system
665            .set_pin("1234".into(), PinLockType::AfterFirstUnlock)
666            .await
667            .expect("set_pin succeeds");
668        assert!(system.validate_pin("1234".into()).await);
669        assert!(!system.validate_pin("wrong".into()).await);
670    }
671}