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#[derive(Debug)]
62pub(crate) enum MigrationFailed {
63    /// Could not read the contained key id from the persistent envelope.
64    EnvelopeMalformed,
65    /// Envelope is V2-encrypted and user key is V2, but the key ids differ.
66    /// V2 -> V2 key rotation is not currently supported here.
67    V2KeyRotationUnsupported,
68    /// Envelope is V2-encrypted but the user key is V1 — inconsistent state.
69    V2EnvelopeWithV1UserKey,
70    /// V1 -> V2 migration is required but no V2 upgrade token is stored.
71    MissingV2UpgradeToken,
72    /// V1 -> V2 migration is required but no encrypted PIN is stored.
73    MissingEncryptedPin,
74    /// V1 -> V2 migration could not decrypt the encrypted PIN via the upgrade
75    /// token.
76    PinDecryption,
77    /// Re-sealing the envelope under the new user key failed.
78    Reenrollment,
79}
80
81/// Provides PIN-based unlock functionality. This includes enrolling into PIN-based unlock,
82/// unlocking using the PIN and handling necessary operations (PIN envelope refreshing when
83/// transitioning to after-first-unlock mode).
84pub struct PinLockSystem<'a> {
85    client: &'a Client,
86}
87
88impl PinLockSystem<'_> {
89    fn key_store(&self) -> &KeyStore<KeySlotIds> {
90        self.client.internal.get_key_store()
91    }
92
93    /// Creates a PIN lock system view for a client instance.
94    pub fn with_client(client: &Client) -> PinLockSystem<'_> {
95        PinLockSystem { client }
96    }
97
98    /// Retrieves the currently active PIN envelope.
99    ///
100    /// If both envelopes are present, the ephemeral envelope is preferred.
101    async fn get_active_pin_envelope(&self) -> Option<PasswordProtectedKeyEnvelope> {
102        let mut pin_protected_key_envelope = self
103            .client
104            .km_state_bridge()
105            .get_ephemeral_pin_envelope()
106            .await;
107        if pin_protected_key_envelope.is_none() {
108            pin_protected_key_envelope = self
109                .client
110                .km_state_bridge()
111                .get_persistent_pin_envelope()
112                .await;
113        }
114        pin_protected_key_envelope
115    }
116
117    /// Attempts to unlock the user key using `pin`.
118    ///
119    /// Returns [`UnlockError::NoPinSet`] if no PIN is configured,
120    /// [`UnlockError::PinWrong`] if `pin` is incorrect, and
121    /// [`UnlockError::InternalError`] for other failures.
122    pub(crate) async fn unlock(&self, pin: &str) -> Result<(), UnlockError> {
123        let pin_envelope = Self::get_active_pin_envelope(self)
124            .await
125            .ok_or(UnlockError::NoPinSet)?;
126
127        // Unseal to key ctx
128        let mut ctx = self.key_store().context_mut();
129        let key_slot = pin_envelope
130            .unseal(
131                pin,
132                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
133                &mut ctx,
134            )
135            .map_err(|e| match e {
136                bitwarden_crypto::safe::PasswordProtectedKeyEnvelopeError::WrongPassword => {
137                    UnlockError::PinWrong
138                }
139                _ => UnlockError::InternalError,
140            })?;
141
142        // The key is currently in the local ctx and would be dropped when ctx goes out of scope.
143        // Persist it to the keystore
144        ctx.persist_symmetric_key(key_slot, SymmetricKeySlotId::User)
145            .map_err(|_| UnlockError::InternalError)
146    }
147
148    /// After a V2 upgrade, when a V2 upgrade token is present and the persistent
149    /// PIN envelope is still encrypted with the V1 user key, this function migrates
150    /// the persistent PIN enrollment to be encrypted with the current user-key.
151    async fn migrate_pin_envelope_if_needed(&self) -> Result<(), MigrationFailed> {
152        let Some(envelope) = self
153            .client
154            .km_state_bridge()
155            .get_persistent_pin_envelope()
156            .await
157        else {
158            return Ok(());
159        };
160
161        let envelope_key_id = envelope
162            .contained_key_id()
163            .map_err(|_| MigrationFailed::EnvelopeMalformed)?;
164        let current_user_key_id = self
165            .key_store()
166            .context()
167            .get_symmetric_key_id(SymmetricKeySlotId::User);
168        // Detect which scenario we are in. A key id being present indicates V2 encryption.
169        // Absence of a key indicates V1 encryption. A key rotation changes the key id.
170        match (envelope_key_id, current_user_key_id) {
171            // Envelope is up-to-date, no migration needed
172            (Some(envelope_key_id), Some(current_user_key_id))
173                if envelope_key_id == current_user_key_id =>
174            {
175                return Ok(());
176            }
177            // V1 -> V2 migration
178            (None, Some(_)) => {}
179            // V2 -> V2 key rotation. Not supported currently.
180            (Some(_), Some(_)) => return Err(MigrationFailed::V2KeyRotationUnsupported),
181            // V2 -> V1 migration. Not possible, something strange happened.
182            (Some(_), None) => return Err(MigrationFailed::V2EnvelopeWithV1UserKey),
183            // V1 -> V1 (unable to see whether key changed)
184            (None, None) => return Ok(()),
185        }
186
187        let token = self
188            .client
189            .km_state_bridge()
190            .get_v2_upgrade_token()
191            .await
192            .ok_or(MigrationFailed::MissingV2UpgradeToken)?;
193        let encrypted_pin = self
194            .client
195            .km_state_bridge()
196            .get_encrypted_pin()
197            .await
198            .ok_or(MigrationFailed::MissingEncryptedPin)?;
199
200        // Attempt to decrypt the previous PIN via the upgrade token.
201        let pin = (|| -> Result<String, ()> {
202            let mut ctx = self.key_store().context_mut();
203            let v1_slot = token
204                .unwrap_v1(SymmetricKeySlotId::User, &mut ctx)
205                .map_err(|_| ())?;
206            encrypted_pin.decrypt(&mut ctx, v1_slot).map_err(|_| ())
207        })()
208        .map_err(|_| MigrationFailed::PinDecryption)?;
209
210        // Do a fresh enrollment with the new user-key
211        self.set_pin(pin, PinLockType::BeforeFirstUnlock)
212            .await
213            .map_err(|_| MigrationFailed::Reenrollment)?;
214
215        Ok(())
216    }
217
218    /// Refreshes in-memory PIN unlock material after a successful non-PIN unlock.
219    ///
220    /// This recreates the ephemeral PIN envelope from the encrypted PIN, when available.
221    pub(crate) async fn on_unlock(&self) {
222        // Remove once all clients, ios, android implement the state bridge
223        if !self.client.km_state_bridge().is_bridge_registered() {
224            return;
225        }
226
227        if let Err(e) = self.migrate_pin_envelope_if_needed().await {
228            warn!("PIN migration failed: {e:?}, unenrolling PIN");
229            self.unset_pin().await;
230            return;
231        }
232
233        let encrypted_pin = self.client.km_state_bridge().get_encrypted_pin().await;
234
235        // If PIN unlock is not enabled, do nothing
236        let Some(encrypted_pin) = encrypted_pin else {
237            return;
238        };
239
240        // Make the fresh PIN envelope
241        let Ok(pin_envelope) = (|| -> Result<PasswordProtectedKeyEnvelope, ()> {
242            let mut ctx = self.key_store().context_mut();
243            let pin: String = encrypted_pin
244                .decrypt(&mut ctx, SymmetricKeySlotId::User)
245                .map_err(|_| ())?;
246            PasswordProtectedKeyEnvelope::seal(
247                SymmetricKeySlotId::User,
248                pin.as_str(),
249                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
250                &ctx,
251            )
252            .map_err(|_| ())
253        })() else {
254            warn!("Failed to create PIN envelope");
255            return;
256        };
257
258        // Store it to memory
259        self.client
260            .km_state_bridge()
261            .set_ephemeral_pin_envelope(&pin_envelope)
262            .await;
263    }
264
265    /// Sets the PIN and stores the generated envelope according to the lock type.
266    pub async fn set_pin(&self, pin: String, lock_type: PinLockType) -> Result<(), ()> {
267        // Clear the existing configuration
268        self.client
269            .km_state_bridge()
270            .clear_persistent_pin_envelope()
271            .await;
272        self.client
273            .km_state_bridge()
274            .clear_ephemeral_pin_envelope()
275            .await;
276        self.client.km_state_bridge().clear_encrypted_pin().await;
277
278        let pin_envelope: PasswordProtectedKeyEnvelope = PasswordProtectedKeyEnvelope::seal(
279            SymmetricKeySlotId::User,
280            pin.as_str(),
281            PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
282            &self.key_store().context_mut(),
283        )
284        .map_err(|_| ())?;
285        let encrypted_pin = pin
286            .encrypt(
287                &mut self.key_store().context_mut(),
288                SymmetricKeySlotId::User,
289            )
290            .map_err(|_| ())?;
291
292        self.client
293            .km_state_bridge()
294            .set_encrypted_pin(&encrypted_pin)
295            .await;
296        self.client
297            .km_state_bridge()
298            .set_ephemeral_pin_envelope(&pin_envelope)
299            .await;
300
301        if lock_type == PinLockType::BeforeFirstUnlock {
302            self.client
303                .km_state_bridge()
304                .set_persistent_pin_envelope(&pin_envelope)
305                .await;
306        }
307
308        Ok(())
309    }
310
311    /// Clears both persistent and ephemeral PIN envelopes.
312    pub async fn unset_pin(&self) {
313        self.client
314            .km_state_bridge()
315            .clear_persistent_pin_envelope()
316            .await;
317        self.client
318            .km_state_bridge()
319            .clear_ephemeral_pin_envelope()
320            .await;
321        self.client.km_state_bridge().clear_encrypted_pin().await;
322    }
323
324    /// Returns the lock type for the currently configured PIN.
325    pub async fn get_pin_lock_type(&self) -> Option<PinLockType> {
326        if self
327            .client
328            .km_state_bridge()
329            .get_persistent_pin_envelope()
330            .await
331            .is_some()
332        {
333            return Some(PinLockType::BeforeFirstUnlock);
334        }
335
336        // Encrypted pin is set for either lock type, persistent pin only for BFU. The ephemeral
337        // envelope may not be set after restarting a client, until the client enters AFU
338        // mode.
339        if self
340            .client
341            .km_state_bridge()
342            .get_encrypted_pin()
343            .await
344            .is_some()
345        {
346            return Some(PinLockType::AfterFirstUnlock);
347        }
348
349        None
350    }
351
352    /// Returns the current PIN unlock status.
353    ///
354    /// If a lock type is configured but no ephemeral envelope is currently present,
355    /// the status is [`PinUnlockStatus::NeedsUnlock`].
356    pub async fn get_pin_status(&self) -> PinUnlockStatus {
357        match Self::get_pin_lock_type(self).await {
358            Some(PinLockType::BeforeFirstUnlock) => {
359                if self.get_active_pin_envelope().await.is_some() {
360                    PinUnlockStatus::Available
361                } else {
362                    PinUnlockStatus::NeedsUnlock
363                }
364            }
365            Some(PinLockType::AfterFirstUnlock) => {
366                if self
367                    .client
368                    .km_state_bridge()
369                    .get_ephemeral_pin_envelope()
370                    .await
371                    .is_some()
372                {
373                    PinUnlockStatus::Available
374                } else {
375                    // This should not happen as AFU should always have the ephemeral envelope, but
376                    // we handle it just in case.
377                    PinUnlockStatus::NeedsUnlock
378                }
379            }
380            None => PinUnlockStatus::NotSet,
381        }
382    }
383
384    /// Returns the configured PIN, if an encrypted PIN is available and decryptable.
385    pub async fn get_pin(&self) -> Option<String> {
386        let encrypted_pin = self.client.km_state_bridge().get_encrypted_pin().await?;
387        encrypted_pin
388            .decrypt(
389                &mut self.client.internal.get_key_store().context_mut(),
390                SymmetricKeySlotId::User,
391            )
392            .ok()
393    }
394
395    /// Validates that the provided PIN can decrypt the stored PIN envelope.
396    pub async fn validate_pin(&self, pin: String) -> bool {
397        let pin_envelope = self.get_active_pin_envelope().await;
398        let Some(pin_envelope) = pin_envelope else {
399            return false;
400        };
401
402        pin_envelope
403            .unseal(
404                pin.as_str(),
405                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
406                &mut self.key_store().context_mut(),
407            )
408            .is_ok()
409    }
410}
411
412#[cfg(test)]
413mod tests {
414    use bitwarden_crypto::{EncString, KeyId, SymmetricKeyAlgorithm};
415
416    use super::*;
417    use crate::key_management::{V2UpgradeToken, state_bridge::test_support::InMemoryStateBridge};
418
419    fn decrypt_encrypted_pin(client: &Client, encrypted_pin: &EncString) -> String {
420        encrypted_pin
421            .decrypt(
422                &mut client.internal.get_key_store().context_mut(),
423                SymmetricKeySlotId::User,
424            )
425            .expect("encrypted pin should decrypt successfully")
426    }
427
428    /// Returns the `KeyId` of the symmetric key currently in `SymmetricKeySlotId::User`.
429    fn user_key_id(client: &Client) -> KeyId {
430        client
431            .internal
432            .get_key_store()
433            .context()
434            .get_symmetric_key_id(SymmetricKeySlotId::User)
435            .expect("user key present")
436    }
437
438    /// Asserts the envelope wraps `expected_key_id` and unseals successfully under `pin`.
439    fn assert_envelope_wraps_user_key(
440        client: &Client,
441        envelope: &PasswordProtectedKeyEnvelope,
442        pin: &str,
443        expected_key_id: &KeyId,
444    ) {
445        assert_eq!(
446            envelope
447                .contained_key_id()
448                .expect("contained key id readable"),
449            Some(expected_key_id.clone()),
450            "envelope wraps a key other than the current user key",
451        );
452        let _ = envelope
453            .unseal(
454                pin,
455                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
456                &mut client.internal.get_key_store().context_mut(),
457            )
458            .expect("envelope unseals with the configured pin");
459    }
460
461    fn client_with_user_key() -> Client {
462        let client = Client::new(None);
463        client
464            .km_state_bridge()
465            .register_bridge(Box::new(InMemoryStateBridge::default()));
466        {
467            let key_store = client.internal.get_key_store();
468            let mut ctx = key_store.context_mut();
469            let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
470            ctx.persist_symmetric_key(user_key, SymmetricKeySlotId::User)
471                .expect("persisting user key should succeed");
472        }
473        client
474    }
475
476    fn seal_envelope(client: &Client, pin: &str) -> PasswordProtectedKeyEnvelope {
477        PasswordProtectedKeyEnvelope::seal(
478            SymmetricKeySlotId::User,
479            pin,
480            PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
481            &client.internal.get_key_store().context_mut(),
482        )
483        .expect("seal succeeds")
484    }
485
486    #[tokio::test]
487    async fn set_pin_bfu_persists_both_envelopes() {
488        let client = client_with_user_key();
489        let user_key_id = user_key_id(&client);
490        let system = PinLockSystem::with_client(&client);
491
492        system
493            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
494            .await
495            .expect("set_pin succeeds");
496
497        let bridge = client.km_state_bridge();
498        let persistent = bridge
499            .get_persistent_pin_envelope()
500            .await
501            .expect("persistent envelope present");
502        let ephemeral = bridge
503            .get_ephemeral_pin_envelope()
504            .await
505            .expect("ephemeral envelope present");
506        let encrypted_pin = bridge
507            .get_encrypted_pin()
508            .await
509            .expect("encrypted pin present");
510
511        assert_envelope_wraps_user_key(&client, &persistent, "1234", &user_key_id);
512        assert_envelope_wraps_user_key(&client, &ephemeral, "1234", &user_key_id);
513        assert_eq!(decrypt_encrypted_pin(&client, &encrypted_pin), "1234");
514
515        assert_eq!(
516            system.get_pin_lock_type().await,
517            Some(PinLockType::BeforeFirstUnlock)
518        );
519        assert_eq!(system.get_pin_status().await, PinUnlockStatus::Available);
520    }
521
522    #[tokio::test]
523    async fn set_pin_afu_persists_only_ephemeral() {
524        let client = client_with_user_key();
525        let user_key_id = user_key_id(&client);
526        let system = PinLockSystem::with_client(&client);
527
528        system
529            .set_pin("1234".into(), PinLockType::AfterFirstUnlock)
530            .await
531            .expect("set_pin succeeds");
532
533        let bridge = client.km_state_bridge();
534        assert!(bridge.get_persistent_pin_envelope().await.is_none());
535        let ephemeral = bridge
536            .get_ephemeral_pin_envelope()
537            .await
538            .expect("ephemeral envelope present");
539        let encrypted_pin = bridge
540            .get_encrypted_pin()
541            .await
542            .expect("encrypted pin present");
543
544        assert_envelope_wraps_user_key(&client, &ephemeral, "1234", &user_key_id);
545        assert_eq!(decrypt_encrypted_pin(&client, &encrypted_pin), "1234");
546
547        assert_eq!(
548            system.get_pin_lock_type().await,
549            Some(PinLockType::AfterFirstUnlock)
550        );
551        assert_eq!(system.get_pin_status().await, PinUnlockStatus::Available);
552    }
553
554    #[tokio::test]
555    async fn set_pin_overwrites_existing_state() {
556        let client = client_with_user_key();
557        let system = PinLockSystem::with_client(&client);
558
559        system
560            .set_pin("first".into(), PinLockType::BeforeFirstUnlock)
561            .await
562            .expect("first set_pin");
563        system
564            .set_pin("second".into(), PinLockType::AfterFirstUnlock)
565            .await
566            .expect("second set_pin");
567
568        let bridge = client.km_state_bridge();
569        assert!(
570            bridge.get_persistent_pin_envelope().await.is_none(),
571            "switching to AFU must clear the persistent envelope"
572        );
573        assert_eq!(
574            system.get_pin_lock_type().await,
575            Some(PinLockType::AfterFirstUnlock)
576        );
577        assert!(system.validate_pin("second".into()).await);
578        assert!(!system.validate_pin("first".into()).await);
579    }
580
581    #[tokio::test]
582    async fn unset_pin_clears_all_state() {
583        let client = client_with_user_key();
584        let system = PinLockSystem::with_client(&client);
585
586        system
587            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
588            .await
589            .expect("set_pin succeeds");
590        system.unset_pin().await;
591
592        let bridge = client.km_state_bridge();
593        assert!(bridge.get_persistent_pin_envelope().await.is_none());
594        assert!(bridge.get_ephemeral_pin_envelope().await.is_none());
595        assert!(bridge.get_encrypted_pin().await.is_none());
596        assert_eq!(system.get_pin_lock_type().await, None);
597        assert_eq!(system.get_pin_status().await, PinUnlockStatus::NotSet);
598    }
599
600    #[tokio::test]
601    async fn unlock_with_correct_pin_persists_user_key() {
602        let client = client_with_user_key();
603        let system = PinLockSystem::with_client(&client);
604
605        let pre_unlock_user_key_id = user_key_id(&client);
606        // Snapshot ciphertext under the original user key, then drop the key from memory.
607        system
608            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
609            .await
610            .expect("set_pin succeeds");
611        client.internal.get_key_store().clear();
612
613        assert!(system.unlock("1234").await.is_ok());
614        let post_unlock_user_key_id = user_key_id(&client);
615        assert_eq!(post_unlock_user_key_id, pre_unlock_user_key_id);
616    }
617
618    #[tokio::test]
619    async fn unlock_with_wrong_pin_returns_pin_wrong() {
620        let client = client_with_user_key();
621        let system = PinLockSystem::with_client(&client);
622        system
623            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
624            .await
625            .expect("set_pin succeeds");
626
627        assert!(matches!(
628            system.unlock("wrong").await,
629            Err(UnlockError::PinWrong)
630        ));
631    }
632
633    #[tokio::test]
634    async fn unlock_with_no_pin_set_returns_no_pin_set() {
635        let client = client_with_user_key();
636        let system = PinLockSystem::with_client(&client);
637
638        assert!(matches!(
639            system.unlock("anything").await,
640            Err(UnlockError::NoPinSet)
641        ));
642    }
643
644    #[tokio::test]
645    async fn unlock_prefers_ephemeral_envelope_over_persistent() {
646        let client = client_with_user_key();
647        let system = PinLockSystem::with_client(&client);
648        system
649            .set_pin("persistent".into(), PinLockType::BeforeFirstUnlock)
650            .await
651            .expect("set_pin succeeds");
652
653        // Replace the ephemeral envelope with one sealed under a different PIN
654        // (same user key still in the slot).
655        let ephemeral = seal_envelope(&client, "ephemeral");
656        client
657            .km_state_bridge()
658            .set_ephemeral_pin_envelope(&ephemeral)
659            .await;
660
661        assert!(system.unlock("ephemeral").await.is_ok());
662        assert!(matches!(
663            system.unlock("persistent").await,
664            Err(UnlockError::PinWrong)
665        ));
666    }
667
668    #[tokio::test]
669    async fn get_pin_status_available_bfu() {
670        let client = client_with_user_key();
671        let system = PinLockSystem::with_client(&client);
672        system
673            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
674            .await
675            .expect("set_pin succeeds");
676
677        // Simulate app restart: ephemeral memory state is gone, only persisted disk state remains.
678        client
679            .km_state_bridge()
680            .clear_ephemeral_pin_envelope()
681            .await;
682
683        assert_eq!(system.get_pin_status().await, PinUnlockStatus::Available);
684        assert_eq!(
685            system.get_pin_lock_type().await,
686            Some(PinLockType::BeforeFirstUnlock)
687        );
688    }
689
690    #[tokio::test]
691    async fn on_unlock_rebuilds_ephemeral_envelope() {
692        let client = client_with_user_key();
693        let user_key_id = user_key_id(&client);
694        let system = PinLockSystem::with_client(&client);
695        system
696            .set_pin("1234".into(), PinLockType::AfterFirstUnlock)
697            .await
698            .expect("set_pin succeeds");
699        client
700            .km_state_bridge()
701            .clear_ephemeral_pin_envelope()
702            .await;
703        assert_eq!(system.get_pin_status().await, PinUnlockStatus::NeedsUnlock);
704
705        system.on_unlock().await;
706
707        let rebuilt = client
708            .km_state_bridge()
709            .get_ephemeral_pin_envelope()
710            .await
711            .expect("on_unlock should restore the ephemeral envelope");
712        assert_envelope_wraps_user_key(&client, &rebuilt, "1234", &user_key_id);
713        assert_eq!(system.get_pin_status().await, PinUnlockStatus::Available);
714        assert!(system.unlock("1234").await.is_ok());
715    }
716
717    #[tokio::test]
718    async fn on_unlock_is_noop_when_no_encrypted_pin() {
719        let client = client_with_user_key();
720        let system = PinLockSystem::with_client(&client);
721
722        system.on_unlock().await;
723
724        assert_eq!(system.get_pin_status().await, PinUnlockStatus::NotSet);
725    }
726
727    #[tokio::test]
728    async fn on_unlock_is_noop_when_bridge_not_registered() {
729        let client = Client::new(None);
730        let system = PinLockSystem::with_client(&client);
731
732        // Must not panic even though no StateBridgeImpl is registered.
733        system.on_unlock().await;
734    }
735
736    #[tokio::test]
737    async fn get_pin_returns_set_pin() {
738        let client = client_with_user_key();
739        let system = PinLockSystem::with_client(&client);
740
741        assert_eq!(system.get_pin().await, None);
742
743        system
744            .set_pin("1234".into(), PinLockType::AfterFirstUnlock)
745            .await
746            .expect("set_pin succeeds");
747        assert_eq!(system.get_pin().await, Some("1234".to_owned()));
748
749        system.unset_pin().await;
750        assert_eq!(system.get_pin().await, None);
751    }
752
753    #[tokio::test]
754    async fn validate_pin_matches_only_correct_pin() {
755        let client = client_with_user_key();
756        let system = PinLockSystem::with_client(&client);
757
758        assert!(!system.validate_pin("anything".into()).await);
759
760        system
761            .set_pin("1234".into(), PinLockType::AfterFirstUnlock)
762            .await
763            .expect("set_pin succeeds");
764        assert!(system.validate_pin("1234".into()).await);
765        assert!(!system.validate_pin("wrong".into()).await);
766    }
767
768    /// Snapshot of the persisted state a client would have after a V1→V2 user-key upgrade,
769    /// before the PIN envelope has been re-sealed.
770    struct V1State {
771        envelope: PasswordProtectedKeyEnvelope,
772        encrypted_pin: EncString,
773        token: V2UpgradeToken,
774    }
775
776    /// Builds a client with a V2 user key in the `User` slot plus the disk-shaped artifacts
777    /// of a prior V1 PIN enrollment: a V1 key sealed in a PIN envelope, an encrypted PIN under that
778    /// V1 key, and a V2 upgrade token tying the two.
779    fn fresh_v1_state_with_v2_user_key(pin: &str) -> (Client, V1State) {
780        let client = Client::new(None);
781        client
782            .km_state_bridge()
783            .register_bridge(Box::new(InMemoryStateBridge::default()));
784
785        let state = {
786            let key_store = client.internal.get_key_store();
787            let mut ctx = key_store.context_mut();
788
789            let v1_local = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
790            let v2_local = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
791
792            let envelope = PasswordProtectedKeyEnvelope::seal(
793                v1_local,
794                pin,
795                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
796                &ctx,
797            )
798            .expect("v1 envelope seals");
799            let encrypted_pin = pin
800                .encrypt(&mut ctx, v1_local)
801                .expect("pin encrypts under v1 key");
802            let token =
803                V2UpgradeToken::create(v1_local, v2_local, &ctx).expect("upgrade token created");
804
805            ctx.persist_symmetric_key(v2_local, SymmetricKeySlotId::User)
806                .expect("persisting v2 user key succeeds");
807
808            V1State {
809                envelope,
810                encrypted_pin,
811                token,
812            }
813        };
814
815        (client, state)
816    }
817
818    /// Like `client_with_user_key`, but installs a V1 (Aes256CbcHmac) user key, so
819    /// `get_symmetric_key_id(User)` returns `None`.
820    fn client_with_v1_user_key() -> Client {
821        let client = Client::new(None);
822        client
823            .km_state_bridge()
824            .register_bridge(Box::new(InMemoryStateBridge::default()));
825        {
826            let key_store = client.internal.get_key_store();
827            let mut ctx = key_store.context_mut();
828            let user_key = ctx.generate_symmetric_key();
829            ctx.persist_symmetric_key(user_key, SymmetricKeySlotId::User)
830                .expect("persisting v1 user key should succeed");
831        }
832        client
833    }
834
835    fn assert_pin_envelopes_equal(
836        envelope_1: &PasswordProtectedKeyEnvelope,
837        envelope_2: &PasswordProtectedKeyEnvelope,
838    ) {
839        assert_eq!(
840            serde_json::to_string(envelope_1).expect("envelope serializes"),
841            serde_json::to_string(envelope_2).expect("envelope serializes"),
842            "envelopes should be identical",
843        );
844    }
845
846    async fn assert_pin_fully_unenrolled(client: &Client) {
847        let bridge = client.km_state_bridge();
848        assert!(bridge.get_persistent_pin_envelope().await.is_none());
849        assert!(bridge.get_ephemeral_pin_envelope().await.is_none());
850        assert!(bridge.get_encrypted_pin().await.is_none());
851        assert_eq!(
852            PinLockSystem::with_client(client).get_pin_status().await,
853            PinUnlockStatus::NotSet,
854        );
855    }
856
857    #[tokio::test]
858    async fn migrate_v1_to_v2_reseals_envelope_with_user_key() {
859        let pin = "1234";
860        let (client, state) = fresh_v1_state_with_v2_user_key(pin);
861        let bridge = client.km_state_bridge();
862        bridge.set_persistent_pin_envelope(&state.envelope).await;
863        bridge.set_encrypted_pin(&state.encrypted_pin).await;
864        bridge.set_v2_upgrade_token(&state.token).await;
865
866        assert_eq!(
867            state.envelope.contained_key_id().expect("readable"),
868            None,
869            "starting envelope is V1 (no key_id)",
870        );
871
872        let user_key_id = user_key_id(&client);
873        let system = PinLockSystem::with_client(&client);
874
875        system
876            .migrate_pin_envelope_if_needed()
877            .await
878            .expect("migration succeeds");
879
880        let persistent = bridge
881            .get_persistent_pin_envelope()
882            .await
883            .expect("persistent envelope present after migration");
884        let ephemeral = bridge
885            .get_ephemeral_pin_envelope()
886            .await
887            .expect("ephemeral envelope present after migration");
888        let encrypted_pin = bridge
889            .get_encrypted_pin()
890            .await
891            .expect("encrypted pin present after migration");
892
893        assert_envelope_wraps_user_key(&client, &persistent, pin, &user_key_id);
894        assert_envelope_wraps_user_key(&client, &ephemeral, pin, &user_key_id);
895        assert_eq!(decrypt_encrypted_pin(&client, &encrypted_pin), pin);
896        assert_eq!(
897            system.get_pin_lock_type().await,
898            Some(PinLockType::BeforeFirstUnlock),
899        );
900        assert_eq!(system.get_pin_status().await, PinUnlockStatus::Available);
901        assert!(system.unlock(pin).await.is_ok());
902    }
903
904    #[tokio::test]
905    async fn on_unlock_triggers_v1_to_v2_migration() {
906        let pin = "1234";
907        let (client, state) = fresh_v1_state_with_v2_user_key(pin);
908        let bridge = client.km_state_bridge();
909        bridge.set_persistent_pin_envelope(&state.envelope).await;
910        bridge.set_encrypted_pin(&state.encrypted_pin).await;
911        bridge.set_v2_upgrade_token(&state.token).await;
912
913        let user_key_id = user_key_id(&client);
914        let system = PinLockSystem::with_client(&client);
915
916        system.on_unlock().await;
917
918        let persistent = bridge
919            .get_persistent_pin_envelope()
920            .await
921            .expect("persistent envelope present after on_unlock");
922        assert_envelope_wraps_user_key(&client, &persistent, pin, &user_key_id);
923        assert!(system.unlock(pin).await.is_ok());
924    }
925
926    #[tokio::test]
927    async fn migrate_no_persistent_envelope_is_noop() {
928        let client = client_with_user_key();
929        let system = PinLockSystem::with_client(&client);
930
931        system
932            .migrate_pin_envelope_if_needed()
933            .await
934            .expect("migration succeeds");
935
936        assert_pin_fully_unenrolled(&client).await;
937    }
938
939    #[tokio::test]
940    async fn migrate_v2_envelope_matching_user_key_is_noop() {
941        let client = client_with_user_key();
942        let system = PinLockSystem::with_client(&client);
943        system
944            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
945            .await
946            .expect("set_pin succeeds");
947
948        let bridge = client.km_state_bridge();
949        let persistent_before = &bridge
950            .get_persistent_pin_envelope()
951            .await
952            .expect("persistent envelope present");
953        let ephemeral_before = &bridge
954            .get_ephemeral_pin_envelope()
955            .await
956            .expect("ephemeral envelope present");
957        let encrypted_pin_before = bridge
958            .get_encrypted_pin()
959            .await
960            .expect("encrypted pin present")
961            .to_string();
962
963        system
964            .migrate_pin_envelope_if_needed()
965            .await
966            .expect("migration succeeds");
967
968        let persistent_after = &bridge
969            .get_persistent_pin_envelope()
970            .await
971            .expect("persistent envelope still present");
972        let ephemeral_after = &bridge
973            .get_ephemeral_pin_envelope()
974            .await
975            .expect("ephemeral envelope still present");
976        let encrypted_pin_after = bridge
977            .get_encrypted_pin()
978            .await
979            .expect("encrypted pin still present")
980            .to_string();
981
982        assert_pin_envelopes_equal(persistent_before, persistent_after);
983        assert_pin_envelopes_equal(ephemeral_before, ephemeral_after);
984        assert_eq!(encrypted_pin_before, encrypted_pin_after);
985    }
986
987    #[tokio::test]
988    async fn migrate_v1_envelope_with_v1_user_key_is_noop() {
989        let pin = "1234";
990        let client = client_with_v1_user_key();
991        let envelope = seal_envelope(&client, pin);
992        let encrypted_pin = pin
993            .encrypt(
994                &mut client.internal.get_key_store().context_mut(),
995                SymmetricKeySlotId::User,
996            )
997            .expect("encrypt under v1 user key");
998
999        let bridge = client.km_state_bridge();
1000        bridge.set_persistent_pin_envelope(&envelope).await;
1001        bridge.set_encrypted_pin(&encrypted_pin).await;
1002
1003        assert_eq!(envelope.contained_key_id().expect("readable"), None);
1004
1005        let persistent_before = &envelope;
1006        let encrypted_pin_before = encrypted_pin.to_string();
1007
1008        let system = PinLockSystem::with_client(&client);
1009        system
1010            .migrate_pin_envelope_if_needed()
1011            .await
1012            .expect("migration succeeds");
1013
1014        let persistent_after = &bridge
1015            .get_persistent_pin_envelope()
1016            .await
1017            .expect("persistent envelope still present");
1018        let encrypted_pin_after = bridge
1019            .get_encrypted_pin()
1020            .await
1021            .expect("encrypted pin still present")
1022            .to_string();
1023        assert_pin_envelopes_equal(persistent_before, persistent_after);
1024        assert_eq!(encrypted_pin_before, encrypted_pin_after);
1025        assert!(bridge.get_ephemeral_pin_envelope().await.is_none());
1026    }
1027
1028    #[tokio::test]
1029    async fn migrate_v2_envelope_not_matching_user_key_returns_v2_key_rotation_unsupported() {
1030        let client = client_with_user_key();
1031        let system = PinLockSystem::with_client(&client);
1032        system
1033            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
1034            .await
1035            .expect("set_pin succeeds");
1036
1037        // Replace the persistent envelope with one sealed under a *different* V2 key.
1038        let mismatched_envelope = {
1039            let mut ctx = client.internal.get_key_store().context_mut();
1040            let other_v2 = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
1041            PasswordProtectedKeyEnvelope::seal(
1042                other_v2,
1043                "1234",
1044                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
1045                &ctx,
1046            )
1047            .expect("seal under other v2 key")
1048        };
1049        client
1050            .km_state_bridge()
1051            .set_persistent_pin_envelope(&mismatched_envelope)
1052            .await;
1053
1054        assert!(matches!(
1055            system.migrate_pin_envelope_if_needed().await,
1056            Err(MigrationFailed::V2KeyRotationUnsupported),
1057        ));
1058    }
1059
1060    #[tokio::test]
1061    async fn migrate_v2_envelope_with_v1_user_key_returns_v2_envelope_with_v1_user_key() {
1062        let client = client_with_v1_user_key();
1063
1064        // Build a V2-sealed envelope using a transient V2 key.
1065        let v2_envelope = {
1066            let key_store = client.internal.get_key_store();
1067            let mut ctx = key_store.context_mut();
1068            let v2_local = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
1069            PasswordProtectedKeyEnvelope::seal(
1070                v2_local,
1071                "1234",
1072                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
1073                &ctx,
1074            )
1075            .expect("seal under v2 key")
1076        };
1077        assert!(
1078            v2_envelope.contained_key_id().expect("readable").is_some(),
1079            "envelope should be V2",
1080        );
1081
1082        let bridge = client.km_state_bridge();
1083        bridge.set_persistent_pin_envelope(&v2_envelope).await;
1084
1085        let system = PinLockSystem::with_client(&client);
1086        assert!(matches!(
1087            system.migrate_pin_envelope_if_needed().await,
1088            Err(MigrationFailed::V2EnvelopeWithV1UserKey),
1089        ));
1090    }
1091
1092    #[tokio::test]
1093    async fn migrate_v1_to_v2_without_upgrade_token_returns_missing_v2_upgrade_token() {
1094        let pin = "1234";
1095        let (client, state) = fresh_v1_state_with_v2_user_key(pin);
1096        let bridge = client.km_state_bridge();
1097        bridge.set_persistent_pin_envelope(&state.envelope).await;
1098        bridge.set_encrypted_pin(&state.encrypted_pin).await;
1099        // Intentionally omit set_v2_upgrade_token.
1100
1101        let system = PinLockSystem::with_client(&client);
1102        assert!(matches!(
1103            system.migrate_pin_envelope_if_needed().await,
1104            Err(MigrationFailed::MissingV2UpgradeToken),
1105        ));
1106    }
1107
1108    #[tokio::test]
1109    async fn migrate_v1_to_v2_without_encrypted_pin_returns_missing_encrypted_pin() {
1110        let pin = "1234";
1111        let (client, state) = fresh_v1_state_with_v2_user_key(pin);
1112        let bridge = client.km_state_bridge();
1113        bridge.set_persistent_pin_envelope(&state.envelope).await;
1114        bridge.set_v2_upgrade_token(&state.token).await;
1115        // Intentionally omit set_encrypted_pin.
1116
1117        let system = PinLockSystem::with_client(&client);
1118        assert!(matches!(
1119            system.migrate_pin_envelope_if_needed().await,
1120            Err(MigrationFailed::MissingEncryptedPin),
1121        ));
1122    }
1123
1124    #[tokio::test]
1125    async fn migrate_v1_to_v2_with_mismatched_upgrade_token_returns_pin_decryption_failure() {
1126        let pin = "1234";
1127        let (client, state) = fresh_v1_state_with_v2_user_key(pin);
1128
1129        // Build an unrelated upgrade token from a different (v1, v2) key pair. Its
1130        // wrapped_user_key_1 is sealed under a V2 key that is *not* in the User slot, so
1131        // unwrap_v1(SymmetricKeySlotId::User, ..) will fail to decrypt it.
1132        let unrelated_token = {
1133            let key_store = bitwarden_crypto::KeyStore::<KeySlotIds>::default();
1134            let mut ctx = key_store.context_mut();
1135            let v1 = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
1136            let v2 = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
1137            V2UpgradeToken::create(v1, v2, &ctx).expect("unrelated token created")
1138        };
1139
1140        let bridge = client.km_state_bridge();
1141        bridge.set_persistent_pin_envelope(&state.envelope).await;
1142        bridge.set_encrypted_pin(&state.encrypted_pin).await;
1143        bridge.set_v2_upgrade_token(&unrelated_token).await;
1144
1145        let system = PinLockSystem::with_client(&client);
1146        assert!(matches!(
1147            system.migrate_pin_envelope_if_needed().await,
1148            Err(MigrationFailed::PinDecryption),
1149        ));
1150    }
1151
1152    #[tokio::test]
1153    async fn on_unlock_unenrolls_when_migration_fails() {
1154        // Reuse the missing-upgrade-token scenario to drive migration failure end-to-end.
1155        let pin = "1234";
1156        let (client, state) = fresh_v1_state_with_v2_user_key(pin);
1157        let bridge = client.km_state_bridge();
1158        bridge.set_persistent_pin_envelope(&state.envelope).await;
1159        bridge.set_encrypted_pin(&state.encrypted_pin).await;
1160        // Intentionally omit set_v2_upgrade_token so migration fails.
1161
1162        let system = PinLockSystem::with_client(&client);
1163        system.on_unlock().await;
1164
1165        assert_pin_fully_unenrolled(&client).await;
1166    }
1167}