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, KeyId, KeyStore, PrimitiveEncryptable, SymmetricKeyAlgorithm,
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, PartialEq, Eq)]
62pub(crate) enum MigrationFailed {
63    /// Vault is locked
64    Locked,
65    /// Could not read the contained key id from the persistent envelope.
66    EnvelopeMalformed,
67    /// Envelope and user key are both V2, but the key ids differ.
68    /// V2 -> V2 key rotation is not currently supported here.
69    V2KeyRotationUnsupported,
70    /// The envelope holds a key that is not the current V1 user key, and no recovery path exists.
71    EnvelopeWithKeyIdWithV1UserKey,
72    /// V1 -> V2 migration is required but no V2 upgrade token is stored.
73    MissingV2UpgradeToken,
74    /// Re-enrollment is required but no encrypted PIN is stored.
75    MissingEncryptedPin,
76    /// Re-enrollment could not decrypt the encrypted PIN.
77    PinDecryption,
78    /// Re-sealing the envelope under the new user key failed.
79    Reenrollment,
80}
81
82/// What [`PinLockSystem::migrate_pin_envelope_if_needed`] should do with the persistent PIN
83/// envelope, decided from key ids alone.
84#[derive(Debug, PartialEq, Eq)]
85enum PinEnvelopeAction {
86    /// The envelope already holds the current user key. Nothing to do.
87    UpToDate,
88    /// The envelope holds the current V1 user key but was sealed before V1 keys had derived key
89    /// ids, so it carries none. Re-enroll under the same key so the envelope gains one. This is
90    /// not a key change and needs no upgrade token.
91    BackfillKeyId,
92    /// The envelope holds the previous V1 user key. Re-enroll under the current V2 user key. The
93    /// upgrade token is used to confirm the envelope really is V1 before anything is rewritten.
94    MigrateV1ToV2,
95    /// Terminal failure; no migration is possible.
96    Failed(MigrationFailed),
97}
98
99/// Decides what to do with the persistent PIN envelope.
100///
101/// - No key id at all means the envelope predates derived key ids, which means it was sealed under
102///   a V1 key — V2 keys have always had one.
103/// - A key id that differs from the user key's is ambiguous, and only the V2 upgrade token can
104///   resolve it.
105fn classify_pin_envelope(
106    envelope_key_id: Option<&KeyId>,
107    current_user_key_id: &KeyId,
108    user_key_is_v1: bool,
109) -> PinEnvelopeAction {
110    match envelope_key_id {
111        Some(envelope_key_id) if envelope_key_id == current_user_key_id => {
112            PinEnvelopeAction::UpToDate
113        }
114        None if user_key_is_v1 => PinEnvelopeAction::BackfillKeyId,
115        None => PinEnvelopeAction::MigrateV1ToV2,
116        Some(_) if user_key_is_v1 => {
117            PinEnvelopeAction::Failed(MigrationFailed::EnvelopeWithKeyIdWithV1UserKey)
118        }
119        Some(_) => PinEnvelopeAction::MigrateV1ToV2,
120    }
121}
122
123/// Provides PIN-based unlock functionality. This includes enrolling into PIN-based unlock,
124/// unlocking using the PIN and handling necessary operations (PIN envelope refreshing when
125/// transitioning to after-first-unlock mode).
126pub struct PinLockSystem<'a> {
127    client: &'a Client,
128}
129
130impl PinLockSystem<'_> {
131    fn key_store(&self) -> &KeyStore<KeySlotIds> {
132        self.client.internal.get_key_store()
133    }
134
135    /// Creates a PIN lock system view for a client instance.
136    pub fn with_client(client: &Client) -> PinLockSystem<'_> {
137        PinLockSystem { client }
138    }
139
140    /// Retrieves the currently active PIN envelope.
141    ///
142    /// If both envelopes are present, the ephemeral envelope is preferred.
143    async fn get_active_pin_envelope(&self) -> Option<PasswordProtectedKeyEnvelope> {
144        let mut pin_protected_key_envelope = self
145            .client
146            .km_state_bridge()
147            .get_ephemeral_pin_envelope()
148            .await;
149        if pin_protected_key_envelope.is_none() {
150            pin_protected_key_envelope = self
151                .client
152                .km_state_bridge()
153                .get_persistent_pin_envelope()
154                .await;
155        }
156        pin_protected_key_envelope
157    }
158
159    /// Attempts to unlock the user key using `pin`.
160    ///
161    /// Returns [`UnlockError::NoPinSet`] if no PIN is configured,
162    /// [`UnlockError::PinWrong`] if `pin` is incorrect, and
163    /// [`UnlockError::InternalError`] for other failures.
164    pub(crate) async fn unlock(&self, pin: &str) -> Result<(), UnlockError> {
165        let pin_envelope = Self::get_active_pin_envelope(self)
166            .await
167            .ok_or(UnlockError::NoPinSet)?;
168
169        // Unseal to key ctx
170        let mut ctx = self.key_store().context_mut();
171        let key_slot = pin_envelope
172            .unseal(
173                pin,
174                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
175                &mut ctx,
176            )
177            .map_err(|e| match e {
178                bitwarden_crypto::safe::PasswordProtectedKeyEnvelopeError::WrongPassword => {
179                    UnlockError::PinWrong
180                }
181                _ => UnlockError::InternalError,
182            })?;
183
184        // The key is currently in the local ctx and would be dropped when ctx goes out of scope.
185        // Persist it to the keystore
186        ctx.persist_symmetric_key(key_slot, SymmetricKeySlotId::User)
187            .map_err(|_| UnlockError::InternalError)
188    }
189
190    /// Brings the persistent PIN envelope in line with the current user key.
191    ///
192    /// This covers two cases, both of which end in a fresh enrollment and differ only in how the
193    /// previous PIN is recovered:
194    ///
195    /// - After a V2 upgrade, when a V2 upgrade token is present and the persistent PIN envelope is
196    ///   still encrypted with the V1 user key, the enrollment is migrated to the current user key.
197    /// - When the envelope was sealed before V1 keys had derived key ids, it is re-enrolled under
198    ///   the same, unchanged key so that it gains one.
199    ///
200    /// See [`classify_pin_envelope`] for how the two are told apart.
201    async fn migrate_pin_envelope_if_needed(&self) -> Result<(), MigrationFailed> {
202        let Some(envelope) = self
203            .client
204            .km_state_bridge()
205            .get_persistent_pin_envelope()
206            .await
207        else {
208            return Ok(());
209        };
210
211        let envelope_key_id = envelope
212            .contained_key_id()
213            .map_err(|_| MigrationFailed::EnvelopeMalformed)?;
214        // Scoped so the context is dropped before the awaits below.
215        let (current_user_key_id, user_key_is_v1) = {
216            let ctx = self.key_store().context();
217            (
218                ctx.get_symmetric_key_id(SymmetricKeySlotId::User)
219                    .ok_or(MigrationFailed::Locked)?,
220                matches!(
221                    ctx.get_symmetric_key_algorithm(SymmetricKeySlotId::User),
222                    Ok(SymmetricKeyAlgorithm::Aes256CbcHmac)
223                ),
224            )
225        };
226
227        // Recover the previous PIN, in the way the classified action calls for.
228        let pin: String = match classify_pin_envelope(
229            envelope_key_id.as_ref(),
230            &current_user_key_id,
231            user_key_is_v1,
232        ) {
233            PinEnvelopeAction::UpToDate => return Ok(()),
234            PinEnvelopeAction::Failed(error) => return Err(error),
235
236            // The user key has not changed, so the stored PIN is still encrypted under it.
237            PinEnvelopeAction::BackfillKeyId => {
238                let encrypted_pin = self
239                    .client
240                    .km_state_bridge()
241                    .get_encrypted_pin()
242                    .await
243                    .ok_or(MigrationFailed::MissingEncryptedPin)?;
244                encrypted_pin
245                    .decrypt(
246                        &mut self.key_store().context_mut(),
247                        SymmetricKeySlotId::User,
248                    )
249                    .map_err(|_| MigrationFailed::PinDecryption)?
250            }
251
252            // The stored PIN is encrypted under the previous V1 user key, which has to be
253            // recovered from the upgrade token first.
254            PinEnvelopeAction::MigrateV1ToV2 => {
255                let token = self
256                    .client
257                    .km_state_bridge()
258                    .get_v2_upgrade_token()
259                    .await
260                    .ok_or(MigrationFailed::MissingV2UpgradeToken)?;
261                let encrypted_pin = self
262                    .client
263                    .km_state_bridge()
264                    .get_encrypted_pin()
265                    .await
266                    .ok_or(MigrationFailed::MissingEncryptedPin)?;
267
268                // The unwrapped V1 key only lives in this context, so unwrapping it, identifying
269                // the envelope with it, and decrypting the PIN all have to share one context.
270                let mut ctx = self.key_store().context_mut();
271                let v1_slot = token
272                    .unwrap_v1(SymmetricKeySlotId::User, &mut ctx)
273                    .map_err(|_| MigrationFailed::PinDecryption)?;
274
275                // A V1 envelope holds the key the upgrade token unwraps. An envelope with no key id
276                // predates derived key ids and is taken to be V1; one holding any other
277                // key is a V2 -> V2 rotation.
278                if envelope_key_id.is_some() && envelope_key_id != ctx.get_symmetric_key_id(v1_slot)
279                {
280                    return Err(MigrationFailed::V2KeyRotationUnsupported);
281                }
282
283                encrypted_pin
284                    .decrypt(&mut ctx, v1_slot)
285                    .map_err(|_| MigrationFailed::PinDecryption)?
286            }
287        };
288
289        // Do a fresh enrollment with the current user-key
290        self.set_pin(pin, PinLockType::BeforeFirstUnlock)
291            .await
292            .map_err(|_| MigrationFailed::Reenrollment)?;
293
294        Ok(())
295    }
296
297    /// Refreshes in-memory PIN unlock material after a successful non-PIN unlock.
298    ///
299    /// This recreates the ephemeral PIN envelope from the encrypted PIN, when available.
300    pub(crate) async fn on_unlock(&self) {
301        // Remove once all clients, ios, android implement the state bridge
302        if !self.client.km_state_bridge().is_bridge_registered() {
303            return;
304        }
305
306        if let Err(e) = self.migrate_pin_envelope_if_needed().await {
307            warn!("PIN migration failed: {e:?}, unenrolling PIN");
308            self.unset_pin().await;
309            return;
310        }
311
312        let encrypted_pin = self.client.km_state_bridge().get_encrypted_pin().await;
313
314        // If PIN unlock is not enabled, do nothing
315        let Some(encrypted_pin) = encrypted_pin else {
316            return;
317        };
318
319        // Make the fresh PIN envelope
320        let Ok(pin_envelope) = (|| -> Result<PasswordProtectedKeyEnvelope, ()> {
321            let mut ctx = self.key_store().context_mut();
322            let pin: String = encrypted_pin
323                .decrypt(&mut ctx, SymmetricKeySlotId::User)
324                .map_err(|_| ())?;
325            PasswordProtectedKeyEnvelope::seal(
326                SymmetricKeySlotId::User,
327                pin.as_str(),
328                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
329                &ctx,
330            )
331            .map_err(|_| ())
332        })() else {
333            warn!("Failed to create PIN envelope");
334            return;
335        };
336
337        // Store it to memory
338        self.client
339            .km_state_bridge()
340            .set_ephemeral_pin_envelope(&pin_envelope)
341            .await;
342    }
343
344    /// Sets the PIN and stores the generated envelope according to the lock type.
345    pub async fn set_pin(&self, pin: String, lock_type: PinLockType) -> Result<(), ()> {
346        // Clear the existing configuration
347        self.client
348            .km_state_bridge()
349            .clear_persistent_pin_envelope()
350            .await;
351        self.client
352            .km_state_bridge()
353            .clear_ephemeral_pin_envelope()
354            .await;
355        self.client.km_state_bridge().clear_encrypted_pin().await;
356
357        let pin_envelope: PasswordProtectedKeyEnvelope = PasswordProtectedKeyEnvelope::seal(
358            SymmetricKeySlotId::User,
359            pin.as_str(),
360            PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
361            &self.key_store().context_mut(),
362        )
363        .map_err(|_| ())?;
364        let encrypted_pin = pin
365            .encrypt(
366                &mut self.key_store().context_mut(),
367                SymmetricKeySlotId::User,
368            )
369            .map_err(|_| ())?;
370
371        self.client
372            .km_state_bridge()
373            .set_encrypted_pin(&encrypted_pin)
374            .await;
375        self.client
376            .km_state_bridge()
377            .set_ephemeral_pin_envelope(&pin_envelope)
378            .await;
379
380        if lock_type == PinLockType::BeforeFirstUnlock {
381            self.client
382                .km_state_bridge()
383                .set_persistent_pin_envelope(&pin_envelope)
384                .await;
385        }
386
387        Ok(())
388    }
389
390    /// Clears both persistent and ephemeral PIN envelopes.
391    pub async fn unset_pin(&self) {
392        self.client
393            .km_state_bridge()
394            .clear_persistent_pin_envelope()
395            .await;
396        self.client
397            .km_state_bridge()
398            .clear_ephemeral_pin_envelope()
399            .await;
400        self.client.km_state_bridge().clear_encrypted_pin().await;
401    }
402
403    /// Returns the lock type for the currently configured PIN.
404    pub async fn get_pin_lock_type(&self) -> Option<PinLockType> {
405        if self
406            .client
407            .km_state_bridge()
408            .get_persistent_pin_envelope()
409            .await
410            .is_some()
411        {
412            return Some(PinLockType::BeforeFirstUnlock);
413        }
414
415        // Encrypted pin is set for either lock type, persistent pin only for BFU. The ephemeral
416        // envelope may not be set after restarting a client, until the client enters AFU
417        // mode.
418        if self
419            .client
420            .km_state_bridge()
421            .get_encrypted_pin()
422            .await
423            .is_some()
424        {
425            return Some(PinLockType::AfterFirstUnlock);
426        }
427
428        None
429    }
430
431    /// Returns the current PIN unlock status.
432    ///
433    /// If a lock type is configured but no ephemeral envelope is currently present,
434    /// the status is [`PinUnlockStatus::NeedsUnlock`].
435    pub async fn get_pin_status(&self) -> PinUnlockStatus {
436        match Self::get_pin_lock_type(self).await {
437            Some(PinLockType::BeforeFirstUnlock) => {
438                if self.get_active_pin_envelope().await.is_some() {
439                    PinUnlockStatus::Available
440                } else {
441                    PinUnlockStatus::NeedsUnlock
442                }
443            }
444            Some(PinLockType::AfterFirstUnlock) => {
445                if self
446                    .client
447                    .km_state_bridge()
448                    .get_ephemeral_pin_envelope()
449                    .await
450                    .is_some()
451                {
452                    PinUnlockStatus::Available
453                } else {
454                    // This should not happen as AFU should always have the ephemeral envelope, but
455                    // we handle it just in case.
456                    PinUnlockStatus::NeedsUnlock
457                }
458            }
459            None => PinUnlockStatus::NotSet,
460        }
461    }
462
463    /// Returns the configured PIN, if an encrypted PIN is available and decryptable.
464    pub async fn get_pin(&self) -> Option<String> {
465        let encrypted_pin = self.client.km_state_bridge().get_encrypted_pin().await?;
466        encrypted_pin
467            .decrypt(
468                &mut self.client.internal.get_key_store().context_mut(),
469                SymmetricKeySlotId::User,
470            )
471            .ok()
472    }
473
474    /// Validates that the provided PIN can decrypt the stored PIN envelope.
475    pub async fn validate_pin(&self, pin: String) -> bool {
476        let pin_envelope = self.get_active_pin_envelope().await;
477        let Some(pin_envelope) = pin_envelope else {
478            return false;
479        };
480
481        pin_envelope
482            .unseal(
483                pin.as_str(),
484                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
485                &mut self.key_store().context_mut(),
486            )
487            .is_ok()
488    }
489}
490
491#[cfg(test)]
492mod tests {
493    use bitwarden_crypto::{EncString, KeyId, SymmetricKeyAlgorithm};
494
495    use super::*;
496    use crate::key_management::{V2UpgradeToken, state_bridge::test_support::InMemoryStateBridge};
497
498    fn decrypt_encrypted_pin(client: &Client, encrypted_pin: &EncString) -> String {
499        encrypted_pin
500            .decrypt(
501                &mut client.internal.get_key_store().context_mut(),
502                SymmetricKeySlotId::User,
503            )
504            .expect("encrypted pin should decrypt successfully")
505    }
506
507    /// The PIN [`TESTVECTOR_LEGACY_ENVELOPE`] was sealed with.
508    const TESTVECTOR_LEGACY_ENVELOPE_PIN: &str = "1234";
509    /// A `PinUnlock` envelope sealed under an AES-CBC-HMAC (V1) key by a client predating derived
510    /// key ids, so it carries no contained key id.
511    ///
512    /// The migration never unseals this envelope — it reads the contained key id and then
513    /// re-enrolls from the key store — so the key sealed inside is arbitrary and deliberately
514    /// unrelated to the user key the tests install.
515    ///
516    /// The current seal path always writes a contained key id, so re-recording this requires
517    /// temporarily changing `set_contained_key_id(&mut header, key_to_seal.key_id())` in
518    /// `bitwarden-crypto/src/safe/password_protected_key_envelope.rs` to pass `None`, sealing a
519    /// `PinUnlock` envelope for an `Aes256CbcHmac` key with [`TESTVECTOR_LEGACY_ENVELOPE_PIN`],
520    /// printing `Vec::from(&envelope)`, and then reverting that change.
521    const TESTVECTOR_LEGACY_ENVELOPE: &[u8] = &[
522        132, 88, 52, 164, 1, 3, 3, 120, 34, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47,
523        120, 46, 98, 105, 116, 119, 97, 114, 100, 101, 110, 46, 108, 101, 103, 97, 99, 121, 45,
524        107, 101, 121, 58, 0, 1, 56, 129, 1, 58, 0, 1, 56, 128, 1, 161, 5, 76, 148, 49, 223, 205,
525        195, 252, 91, 216, 65, 200, 121, 104, 88, 80, 89, 120, 16, 161, 14, 97, 191, 97, 138, 42,
526        102, 234, 49, 186, 2, 255, 31, 4, 232, 178, 100, 53, 37, 181, 172, 129, 193, 51, 109, 8,
527        160, 29, 254, 181, 242, 102, 73, 229, 89, 150, 227, 252, 120, 156, 71, 202, 200, 241, 74,
528        241, 206, 16, 155, 83, 49, 242, 13, 209, 10, 217, 251, 164, 244, 69, 41, 52, 9, 192, 140,
529        248, 251, 244, 84, 154, 15, 100, 222, 102, 117, 185, 129, 131, 71, 161, 1, 58, 0, 1, 21,
530        87, 165, 1, 58, 0, 1, 21, 87, 58, 0, 1, 21, 89, 3, 58, 0, 1, 21, 90, 26, 0, 1, 0, 0, 58, 0,
531        1, 21, 91, 4, 58, 0, 1, 21, 88, 80, 64, 184, 87, 20, 40, 186, 214, 56, 87, 53, 118, 100, 5,
532        21, 13, 3, 246,
533    ];
534
535    /// Parses [`TESTVECTOR_LEGACY_ENVELOPE`], asserting it really has no contained key id.
536    fn legacy_envelope() -> PasswordProtectedKeyEnvelope {
537        let envelope = PasswordProtectedKeyEnvelope::try_from(&TESTVECTOR_LEGACY_ENVELOPE.to_vec())
538            .expect("legacy envelope test vector parses");
539        assert_eq!(
540            envelope.contained_key_id().expect("readable"),
541            None,
542            "legacy envelope test vector must have no contained key id",
543        );
544        envelope
545    }
546
547    /// Returns the `KeyId` of the symmetric key currently in `SymmetricKeySlotId::User`.
548    fn user_key_id(client: &Client) -> KeyId {
549        client
550            .internal
551            .get_key_store()
552            .context()
553            .get_symmetric_key_id(SymmetricKeySlotId::User)
554            .expect("user key present")
555    }
556
557    /// Asserts the envelope wraps `expected_key_id` and unseals successfully under `pin`.
558    fn assert_envelope_wraps_user_key(
559        client: &Client,
560        envelope: &PasswordProtectedKeyEnvelope,
561        pin: &str,
562        expected_key_id: &KeyId,
563    ) {
564        assert_eq!(
565            envelope
566                .contained_key_id()
567                .expect("contained key id readable"),
568            Some(expected_key_id.clone()),
569            "envelope wraps a key other than the current user key",
570        );
571        let _ = envelope
572            .unseal(
573                pin,
574                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
575                &mut client.internal.get_key_store().context_mut(),
576            )
577            .expect("envelope unseals with the configured pin");
578    }
579
580    fn client_with_user_key() -> Client {
581        let client = Client::new(None);
582        client
583            .km_state_bridge()
584            .register_bridge(Box::new(InMemoryStateBridge::default()));
585        {
586            let key_store = client.internal.get_key_store();
587            let mut ctx = key_store.context_mut();
588            let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
589            ctx.persist_symmetric_key(user_key, SymmetricKeySlotId::User)
590                .expect("persisting user key should succeed");
591        }
592        client
593    }
594
595    fn seal_envelope(client: &Client, pin: &str) -> PasswordProtectedKeyEnvelope {
596        PasswordProtectedKeyEnvelope::seal(
597            SymmetricKeySlotId::User,
598            pin,
599            PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
600            &client.internal.get_key_store().context_mut(),
601        )
602        .expect("seal succeeds")
603    }
604
605    #[tokio::test]
606    async fn set_pin_bfu_persists_both_envelopes() {
607        let client = client_with_user_key();
608        let user_key_id = user_key_id(&client);
609        let system = PinLockSystem::with_client(&client);
610
611        system
612            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
613            .await
614            .expect("set_pin succeeds");
615
616        let bridge = client.km_state_bridge();
617        let persistent = bridge
618            .get_persistent_pin_envelope()
619            .await
620            .expect("persistent envelope present");
621        let ephemeral = bridge
622            .get_ephemeral_pin_envelope()
623            .await
624            .expect("ephemeral envelope present");
625        let encrypted_pin = bridge
626            .get_encrypted_pin()
627            .await
628            .expect("encrypted pin present");
629
630        assert_envelope_wraps_user_key(&client, &persistent, "1234", &user_key_id);
631        assert_envelope_wraps_user_key(&client, &ephemeral, "1234", &user_key_id);
632        assert_eq!(decrypt_encrypted_pin(&client, &encrypted_pin), "1234");
633
634        assert_eq!(
635            system.get_pin_lock_type().await,
636            Some(PinLockType::BeforeFirstUnlock)
637        );
638        assert_eq!(system.get_pin_status().await, PinUnlockStatus::Available);
639    }
640
641    #[tokio::test]
642    async fn set_pin_afu_persists_only_ephemeral() {
643        let client = client_with_user_key();
644        let user_key_id = user_key_id(&client);
645        let system = PinLockSystem::with_client(&client);
646
647        system
648            .set_pin("1234".into(), PinLockType::AfterFirstUnlock)
649            .await
650            .expect("set_pin succeeds");
651
652        let bridge = client.km_state_bridge();
653        assert!(bridge.get_persistent_pin_envelope().await.is_none());
654        let ephemeral = bridge
655            .get_ephemeral_pin_envelope()
656            .await
657            .expect("ephemeral envelope present");
658        let encrypted_pin = bridge
659            .get_encrypted_pin()
660            .await
661            .expect("encrypted pin present");
662
663        assert_envelope_wraps_user_key(&client, &ephemeral, "1234", &user_key_id);
664        assert_eq!(decrypt_encrypted_pin(&client, &encrypted_pin), "1234");
665
666        assert_eq!(
667            system.get_pin_lock_type().await,
668            Some(PinLockType::AfterFirstUnlock)
669        );
670        assert_eq!(system.get_pin_status().await, PinUnlockStatus::Available);
671    }
672
673    #[tokio::test]
674    async fn set_pin_overwrites_existing_state() {
675        let client = client_with_user_key();
676        let system = PinLockSystem::with_client(&client);
677
678        system
679            .set_pin("first".into(), PinLockType::BeforeFirstUnlock)
680            .await
681            .expect("first set_pin");
682        system
683            .set_pin("second".into(), PinLockType::AfterFirstUnlock)
684            .await
685            .expect("second set_pin");
686
687        let bridge = client.km_state_bridge();
688        assert!(
689            bridge.get_persistent_pin_envelope().await.is_none(),
690            "switching to AFU must clear the persistent envelope"
691        );
692        assert_eq!(
693            system.get_pin_lock_type().await,
694            Some(PinLockType::AfterFirstUnlock)
695        );
696        assert!(system.validate_pin("second".into()).await);
697        assert!(!system.validate_pin("first".into()).await);
698    }
699
700    #[tokio::test]
701    async fn unset_pin_clears_all_state() {
702        let client = client_with_user_key();
703        let system = PinLockSystem::with_client(&client);
704
705        system
706            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
707            .await
708            .expect("set_pin succeeds");
709        system.unset_pin().await;
710
711        let bridge = client.km_state_bridge();
712        assert!(bridge.get_persistent_pin_envelope().await.is_none());
713        assert!(bridge.get_ephemeral_pin_envelope().await.is_none());
714        assert!(bridge.get_encrypted_pin().await.is_none());
715        assert_eq!(system.get_pin_lock_type().await, None);
716        assert_eq!(system.get_pin_status().await, PinUnlockStatus::NotSet);
717    }
718
719    #[tokio::test]
720    async fn unlock_with_correct_pin_persists_user_key() {
721        let client = client_with_user_key();
722        let system = PinLockSystem::with_client(&client);
723
724        let pre_unlock_user_key_id = user_key_id(&client);
725        // Snapshot ciphertext under the original user key, then drop the key from memory.
726        system
727            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
728            .await
729            .expect("set_pin succeeds");
730        client.internal.get_key_store().clear();
731
732        assert!(system.unlock("1234").await.is_ok());
733        let post_unlock_user_key_id = user_key_id(&client);
734        assert_eq!(post_unlock_user_key_id, pre_unlock_user_key_id);
735    }
736
737    #[tokio::test]
738    async fn unlock_with_wrong_pin_returns_pin_wrong() {
739        let client = client_with_user_key();
740        let system = PinLockSystem::with_client(&client);
741        system
742            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
743            .await
744            .expect("set_pin succeeds");
745
746        assert!(matches!(
747            system.unlock("wrong").await,
748            Err(UnlockError::PinWrong)
749        ));
750    }
751
752    #[tokio::test]
753    async fn unlock_with_no_pin_set_returns_no_pin_set() {
754        let client = client_with_user_key();
755        let system = PinLockSystem::with_client(&client);
756
757        assert!(matches!(
758            system.unlock("anything").await,
759            Err(UnlockError::NoPinSet)
760        ));
761    }
762
763    #[tokio::test]
764    async fn unlock_prefers_ephemeral_envelope_over_persistent() {
765        let client = client_with_user_key();
766        let system = PinLockSystem::with_client(&client);
767        system
768            .set_pin("persistent".into(), PinLockType::BeforeFirstUnlock)
769            .await
770            .expect("set_pin succeeds");
771
772        // Replace the ephemeral envelope with one sealed under a different PIN
773        // (same user key still in the slot).
774        let ephemeral = seal_envelope(&client, "ephemeral");
775        client
776            .km_state_bridge()
777            .set_ephemeral_pin_envelope(&ephemeral)
778            .await;
779
780        assert!(system.unlock("ephemeral").await.is_ok());
781        assert!(matches!(
782            system.unlock("persistent").await,
783            Err(UnlockError::PinWrong)
784        ));
785    }
786
787    #[tokio::test]
788    async fn get_pin_status_available_bfu() {
789        let client = client_with_user_key();
790        let system = PinLockSystem::with_client(&client);
791        system
792            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
793            .await
794            .expect("set_pin succeeds");
795
796        // Simulate app restart: ephemeral memory state is gone, only persisted disk state remains.
797        client
798            .km_state_bridge()
799            .clear_ephemeral_pin_envelope()
800            .await;
801
802        assert_eq!(system.get_pin_status().await, PinUnlockStatus::Available);
803        assert_eq!(
804            system.get_pin_lock_type().await,
805            Some(PinLockType::BeforeFirstUnlock)
806        );
807    }
808
809    #[tokio::test]
810    async fn on_unlock_rebuilds_ephemeral_envelope() {
811        let client = client_with_user_key();
812        let user_key_id = user_key_id(&client);
813        let system = PinLockSystem::with_client(&client);
814        system
815            .set_pin("1234".into(), PinLockType::AfterFirstUnlock)
816            .await
817            .expect("set_pin succeeds");
818        client
819            .km_state_bridge()
820            .clear_ephemeral_pin_envelope()
821            .await;
822        assert_eq!(system.get_pin_status().await, PinUnlockStatus::NeedsUnlock);
823
824        system.on_unlock().await;
825
826        let rebuilt = client
827            .km_state_bridge()
828            .get_ephemeral_pin_envelope()
829            .await
830            .expect("on_unlock should restore the ephemeral envelope");
831        assert_envelope_wraps_user_key(&client, &rebuilt, "1234", &user_key_id);
832        assert_eq!(system.get_pin_status().await, PinUnlockStatus::Available);
833        assert!(system.unlock("1234").await.is_ok());
834    }
835
836    #[tokio::test]
837    async fn on_unlock_is_noop_when_no_encrypted_pin() {
838        let client = client_with_user_key();
839        let system = PinLockSystem::with_client(&client);
840
841        system.on_unlock().await;
842
843        assert_eq!(system.get_pin_status().await, PinUnlockStatus::NotSet);
844    }
845
846    #[tokio::test]
847    async fn on_unlock_is_noop_when_bridge_not_registered() {
848        let client = Client::new(None);
849        let system = PinLockSystem::with_client(&client);
850
851        // Must not panic even though no StateBridgeImpl is registered.
852        system.on_unlock().await;
853    }
854
855    #[tokio::test]
856    async fn get_pin_returns_set_pin() {
857        let client = client_with_user_key();
858        let system = PinLockSystem::with_client(&client);
859
860        assert_eq!(system.get_pin().await, None);
861
862        system
863            .set_pin("1234".into(), PinLockType::AfterFirstUnlock)
864            .await
865            .expect("set_pin succeeds");
866        assert_eq!(system.get_pin().await, Some("1234".to_owned()));
867
868        system.unset_pin().await;
869        assert_eq!(system.get_pin().await, None);
870    }
871
872    #[tokio::test]
873    async fn validate_pin_matches_only_correct_pin() {
874        let client = client_with_user_key();
875        let system = PinLockSystem::with_client(&client);
876
877        assert!(!system.validate_pin("anything".into()).await);
878
879        system
880            .set_pin("1234".into(), PinLockType::AfterFirstUnlock)
881            .await
882            .expect("set_pin succeeds");
883        assert!(system.validate_pin("1234".into()).await);
884        assert!(!system.validate_pin("wrong".into()).await);
885    }
886
887    /// Snapshot of the persisted state a client would have after a V1→V2 user-key upgrade,
888    /// before the PIN envelope has been re-sealed.
889    struct V1State {
890        envelope: PasswordProtectedKeyEnvelope,
891        encrypted_pin: EncString,
892        token: V2UpgradeToken,
893    }
894
895    /// Builds a client with a V2 user key in the `User` slot plus the disk-shaped artifacts
896    /// of a prior V1 PIN enrollment: a V1 key sealed in a PIN envelope, an encrypted PIN under that
897    /// V1 key, and a V2 upgrade token tying the two.
898    fn fresh_v1_state_with_v2_user_key(pin: &str) -> (Client, V1State) {
899        let client = Client::new(None);
900        client
901            .km_state_bridge()
902            .register_bridge(Box::new(InMemoryStateBridge::default()));
903
904        let state = {
905            let key_store = client.internal.get_key_store();
906            let mut ctx = key_store.context_mut();
907
908            let v1_local = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
909            let v2_local = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
910
911            let envelope = PasswordProtectedKeyEnvelope::seal(
912                v1_local,
913                pin,
914                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
915                &ctx,
916            )
917            .expect("v1 envelope seals");
918            let encrypted_pin = pin
919                .encrypt(&mut ctx, v1_local)
920                .expect("pin encrypts under v1 key");
921            let token =
922                V2UpgradeToken::create(v1_local, v2_local, &ctx).expect("upgrade token created");
923
924            ctx.persist_symmetric_key(v2_local, SymmetricKeySlotId::User)
925                .expect("persisting v2 user key succeeds");
926
927            V1State {
928                envelope,
929                encrypted_pin,
930                token,
931            }
932        };
933
934        (client, state)
935    }
936
937    /// Like `client_with_user_key`, but installs a V1 (Aes256CbcHmac) user key, so
938    /// `get_symmetric_key_id(User)` returns `None`.
939    fn client_with_v1_user_key() -> Client {
940        let client = Client::new(None);
941        client
942            .km_state_bridge()
943            .register_bridge(Box::new(InMemoryStateBridge::default()));
944        {
945            let key_store = client.internal.get_key_store();
946            let mut ctx = key_store.context_mut();
947            let user_key = ctx.generate_symmetric_key();
948            ctx.persist_symmetric_key(user_key, SymmetricKeySlotId::User)
949                .expect("persisting v1 user key should succeed");
950        }
951        client
952    }
953
954    fn assert_pin_envelopes_equal(
955        envelope_1: &PasswordProtectedKeyEnvelope,
956        envelope_2: &PasswordProtectedKeyEnvelope,
957    ) {
958        assert_eq!(
959            serde_json::to_string(envelope_1).expect("envelope serializes"),
960            serde_json::to_string(envelope_2).expect("envelope serializes"),
961            "envelopes should be identical",
962        );
963    }
964
965    async fn assert_pin_fully_unenrolled(client: &Client) {
966        let bridge = client.km_state_bridge();
967        assert!(bridge.get_persistent_pin_envelope().await.is_none());
968        assert!(bridge.get_ephemeral_pin_envelope().await.is_none());
969        assert!(bridge.get_encrypted_pin().await.is_none());
970        assert_eq!(
971            PinLockSystem::with_client(client).get_pin_status().await,
972            PinUnlockStatus::NotSet,
973        );
974    }
975
976    // ------------------------------------------------------------------------------------
977    // PIN envelope migration
978    //
979    // These follow the cases in `classify_pin_envelope`: the classifier in isolation first, then
980    // each action and failure branch end to end, in the same order.
981    // ------------------------------------------------------------------------------------
982
983    fn test_key_id(byte: u8) -> KeyId {
984        KeyId::from([byte; 16])
985    }
986
987    #[test]
988    fn classify_matching_key_id_is_up_to_date() {
989        for user_key_is_v1 in [true, false] {
990            assert_eq!(
991                classify_pin_envelope(Some(&test_key_id(1)), &test_key_id(1), user_key_is_v1),
992                PinEnvelopeAction::UpToDate,
993                "user_key_is_v1 = {user_key_is_v1}",
994            );
995        }
996    }
997
998    #[test]
999    fn classify_missing_envelope_key_id_with_v1_user_key_backfills() {
1000        assert_eq!(
1001            classify_pin_envelope(None, &test_key_id(1), true),
1002            PinEnvelopeAction::BackfillKeyId,
1003        );
1004    }
1005
1006    #[test]
1007    fn classify_missing_envelope_key_id_with_v2_user_key_migrates() {
1008        assert_eq!(
1009            classify_pin_envelope(None, &test_key_id(1), false),
1010            PinEnvelopeAction::MigrateV1ToV2,
1011        );
1012    }
1013
1014    #[test]
1015    fn classify_differing_key_id_with_v1_user_key_fails() {
1016        assert_eq!(
1017            classify_pin_envelope(Some(&test_key_id(1)), &test_key_id(2), true),
1018            PinEnvelopeAction::Failed(MigrationFailed::EnvelopeWithKeyIdWithV1UserKey),
1019        );
1020    }
1021
1022    #[test]
1023    fn classify_differing_key_id_with_v2_user_key_migrates() {
1024        assert_eq!(
1025            classify_pin_envelope(Some(&test_key_id(1)), &test_key_id(2), false),
1026            PinEnvelopeAction::MigrateV1ToV2,
1027        );
1028    }
1029
1030    /// Classification needs a user key to compare against, so a locked vault is rejected before
1031    /// [`classify_pin_envelope`] is reached.
1032    #[tokio::test]
1033    async fn migrate_without_user_key_fails_locked() {
1034        let client = Client::new(None);
1035        client
1036            .km_state_bridge()
1037            .register_bridge(Box::new(InMemoryStateBridge::default()));
1038        client
1039            .km_state_bridge()
1040            .set_persistent_pin_envelope(&legacy_envelope())
1041            .await;
1042
1043        let system = PinLockSystem::with_client(&client);
1044        assert_eq!(
1045            system.migrate_pin_envelope_if_needed().await,
1046            Err(MigrationFailed::Locked),
1047        );
1048    }
1049
1050    #[tokio::test]
1051    async fn migrate_without_persistent_envelope_is_noop() {
1052        let client = client_with_user_key();
1053        let system = PinLockSystem::with_client(&client);
1054
1055        system
1056            .migrate_pin_envelope_if_needed()
1057            .await
1058            .expect("migration succeeds");
1059
1060        assert_pin_fully_unenrolled(&client).await;
1061    }
1062
1063    /// The envelope already holds the current V2 user key.
1064    #[tokio::test]
1065    async fn migrate_up_to_date_v2_envelope_is_noop() {
1066        let client = client_with_user_key();
1067        let system = PinLockSystem::with_client(&client);
1068        system
1069            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
1070            .await
1071            .expect("set_pin succeeds");
1072
1073        let bridge = client.km_state_bridge();
1074        let persistent_before = &bridge
1075            .get_persistent_pin_envelope()
1076            .await
1077            .expect("persistent envelope present");
1078        let ephemeral_before = &bridge
1079            .get_ephemeral_pin_envelope()
1080            .await
1081            .expect("ephemeral envelope present");
1082        let encrypted_pin_before = bridge
1083            .get_encrypted_pin()
1084            .await
1085            .expect("encrypted pin present")
1086            .to_string();
1087
1088        system
1089            .migrate_pin_envelope_if_needed()
1090            .await
1091            .expect("migration succeeds");
1092
1093        let persistent_after = &bridge
1094            .get_persistent_pin_envelope()
1095            .await
1096            .expect("persistent envelope still present");
1097        let ephemeral_after = &bridge
1098            .get_ephemeral_pin_envelope()
1099            .await
1100            .expect("ephemeral envelope still present");
1101        let encrypted_pin_after = bridge
1102            .get_encrypted_pin()
1103            .await
1104            .expect("encrypted pin still present")
1105            .to_string();
1106
1107        assert_pin_envelopes_equal(persistent_before, persistent_after);
1108        assert_pin_envelopes_equal(ephemeral_before, ephemeral_after);
1109        assert_eq!(encrypted_pin_before, encrypted_pin_after);
1110    }
1111
1112    /// A V1 envelope sealed by a current client carries the V1 key's derived key id, so it
1113    /// already matches the user key.
1114    #[tokio::test]
1115    async fn migrate_up_to_date_v1_envelope_is_noop() {
1116        let pin = "1234";
1117        let client = client_with_v1_user_key();
1118        let envelope = seal_envelope(&client, pin);
1119        let encrypted_pin = pin
1120            .encrypt(
1121                &mut client.internal.get_key_store().context_mut(),
1122                SymmetricKeySlotId::User,
1123            )
1124            .expect("encrypt under v1 user key");
1125
1126        let bridge = client.km_state_bridge();
1127        bridge.set_persistent_pin_envelope(&envelope).await;
1128        bridge.set_encrypted_pin(&encrypted_pin).await;
1129
1130        assert_eq!(
1131            envelope.contained_key_id().expect("readable"),
1132            Some(user_key_id(&client)),
1133            "a freshly sealed V1 envelope carries the V1 key's derived key id",
1134        );
1135
1136        let persistent_before = &envelope;
1137        let encrypted_pin_before = encrypted_pin.to_string();
1138
1139        let system = PinLockSystem::with_client(&client);
1140        system
1141            .migrate_pin_envelope_if_needed()
1142            .await
1143            .expect("migration succeeds");
1144
1145        let persistent_after = &bridge
1146            .get_persistent_pin_envelope()
1147            .await
1148            .expect("persistent envelope still present");
1149        let encrypted_pin_after = bridge
1150            .get_encrypted_pin()
1151            .await
1152            .expect("encrypted pin still present")
1153            .to_string();
1154        assert_pin_envelopes_equal(persistent_before, persistent_after);
1155        assert_eq!(encrypted_pin_before, encrypted_pin_after);
1156        assert!(bridge.get_ephemeral_pin_envelope().await.is_none());
1157    }
1158
1159    /// The envelope predates derived key ids and the user key is unchanged, so it is re-enrolled
1160    /// under that same key purely to gain a key id. This is the state every existing V1 PIN user
1161    /// is in, so it must never fail the unlock.
1162    #[tokio::test]
1163    async fn migrate_legacy_envelope_with_v1_user_key_backfills_key_id() {
1164        let pin = TESTVECTOR_LEGACY_ENVELOPE_PIN;
1165        let client = client_with_v1_user_key();
1166        let encrypted_pin = pin
1167            .encrypt(
1168                &mut client.internal.get_key_store().context_mut(),
1169                SymmetricKeySlotId::User,
1170            )
1171            .expect("encrypt under v1 user key");
1172
1173        let bridge = client.km_state_bridge();
1174        bridge.set_persistent_pin_envelope(&legacy_envelope()).await;
1175        bridge.set_encrypted_pin(&encrypted_pin).await;
1176
1177        let user_key_id = user_key_id(&client);
1178        let system = PinLockSystem::with_client(&client);
1179        system
1180            .migrate_pin_envelope_if_needed()
1181            .await
1182            .expect("migration succeeds");
1183
1184        let persistent = bridge
1185            .get_persistent_pin_envelope()
1186            .await
1187            .expect("persistent envelope present after backfill");
1188        assert_envelope_wraps_user_key(&client, &persistent, pin, &user_key_id);
1189        assert!(system.unlock(pin).await.is_ok());
1190    }
1191
1192    /// A backfill with no PIN to re-enroll with.
1193    #[tokio::test]
1194    async fn migrate_legacy_envelope_with_v1_user_key_without_encrypted_pin_fails() {
1195        let client = client_with_v1_user_key();
1196        let bridge = client.km_state_bridge();
1197        bridge.set_persistent_pin_envelope(&legacy_envelope()).await;
1198        // Intentionally omit set_encrypted_pin.
1199
1200        let system = PinLockSystem::with_client(&client);
1201        assert_eq!(
1202            system.migrate_pin_envelope_if_needed().await,
1203            Err(MigrationFailed::MissingEncryptedPin),
1204        );
1205    }
1206
1207    /// The envelope holds some key other than the V1 user key. The upgrade token is unwrapped
1208    /// with the user key, so a V1 user key leaves no way to recover the other one.
1209    #[tokio::test]
1210    async fn migrate_envelope_with_v1_user_key_and_other_key_id_fails() {
1211        let client = client_with_v1_user_key();
1212
1213        // Build a V2-sealed envelope using a transient V2 key.
1214        let v2_envelope = {
1215            let key_store = client.internal.get_key_store();
1216            let mut ctx = key_store.context_mut();
1217            let v2_local = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
1218            PasswordProtectedKeyEnvelope::seal(
1219                v2_local,
1220                "1234",
1221                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
1222                &ctx,
1223            )
1224            .expect("seal under v2 key")
1225        };
1226        assert!(
1227            v2_envelope.contained_key_id().expect("readable").is_some(),
1228            "envelope should be V2",
1229        );
1230
1231        let bridge = client.km_state_bridge();
1232        bridge.set_persistent_pin_envelope(&v2_envelope).await;
1233
1234        let system = PinLockSystem::with_client(&client);
1235        assert_eq!(
1236            system.migrate_pin_envelope_if_needed().await,
1237            Err(MigrationFailed::EnvelopeWithKeyIdWithV1UserKey),
1238        );
1239    }
1240
1241    /// The envelope holds the V1 key the upgrade token unwraps.
1242    #[tokio::test]
1243    async fn migrate_v1_envelope_with_v2_user_key_reseals_with_user_key() {
1244        let pin = "1234";
1245        let (client, state) = fresh_v1_state_with_v2_user_key(pin);
1246        let bridge = client.km_state_bridge();
1247        bridge.set_persistent_pin_envelope(&state.envelope).await;
1248        bridge.set_encrypted_pin(&state.encrypted_pin).await;
1249        bridge.set_v2_upgrade_token(&state.token).await;
1250
1251        let user_key_id = user_key_id(&client);
1252        assert_ne!(
1253            state.envelope.contained_key_id().expect("readable"),
1254            Some(user_key_id.clone()),
1255            "starting envelope holds the V1 key, not the current V2 user key",
1256        );
1257
1258        let system = PinLockSystem::with_client(&client);
1259        system
1260            .migrate_pin_envelope_if_needed()
1261            .await
1262            .expect("migration succeeds");
1263
1264        let persistent = bridge
1265            .get_persistent_pin_envelope()
1266            .await
1267            .expect("persistent envelope present after migration");
1268        let ephemeral = bridge
1269            .get_ephemeral_pin_envelope()
1270            .await
1271            .expect("ephemeral envelope present after migration");
1272        let encrypted_pin = bridge
1273            .get_encrypted_pin()
1274            .await
1275            .expect("encrypted pin present after migration");
1276
1277        assert_envelope_wraps_user_key(&client, &persistent, pin, &user_key_id);
1278        assert_envelope_wraps_user_key(&client, &ephemeral, pin, &user_key_id);
1279        assert_eq!(decrypt_encrypted_pin(&client, &encrypted_pin), pin);
1280        assert_eq!(
1281            system.get_pin_lock_type().await,
1282            Some(PinLockType::BeforeFirstUnlock),
1283        );
1284        assert_eq!(system.get_pin_status().await, PinUnlockStatus::Available);
1285        assert!(system.unlock(pin).await.is_ok());
1286    }
1287
1288    #[tokio::test]
1289    async fn migrate_v1_envelope_with_v2_user_key_without_token_fails() {
1290        let pin = "1234";
1291        let (client, state) = fresh_v1_state_with_v2_user_key(pin);
1292        let bridge = client.km_state_bridge();
1293        bridge.set_persistent_pin_envelope(&state.envelope).await;
1294        bridge.set_encrypted_pin(&state.encrypted_pin).await;
1295        // Intentionally omit set_v2_upgrade_token.
1296
1297        let system = PinLockSystem::with_client(&client);
1298        assert_eq!(
1299            system.migrate_pin_envelope_if_needed().await,
1300            Err(MigrationFailed::MissingV2UpgradeToken),
1301        );
1302    }
1303
1304    #[tokio::test]
1305    async fn migrate_v1_envelope_with_v2_user_key_without_encrypted_pin_fails() {
1306        let pin = "1234";
1307        let (client, state) = fresh_v1_state_with_v2_user_key(pin);
1308        let bridge = client.km_state_bridge();
1309        bridge.set_persistent_pin_envelope(&state.envelope).await;
1310        bridge.set_v2_upgrade_token(&state.token).await;
1311        // Intentionally omit set_encrypted_pin.
1312
1313        let system = PinLockSystem::with_client(&client);
1314        assert_eq!(
1315            system.migrate_pin_envelope_if_needed().await,
1316            Err(MigrationFailed::MissingEncryptedPin),
1317        );
1318    }
1319
1320    #[tokio::test]
1321    async fn migrate_v1_envelope_with_v2_user_key_with_mismatched_token_fails() {
1322        let pin = "1234";
1323        let (client, state) = fresh_v1_state_with_v2_user_key(pin);
1324
1325        // Build an unrelated upgrade token from a different (v1, v2) key pair. Its
1326        // wrapped_user_key_1 is sealed under a V2 key that is *not* in the User slot, so
1327        // unwrap_v1(SymmetricKeySlotId::User, ..) will fail to decrypt it.
1328        let unrelated_token = {
1329            let key_store = bitwarden_crypto::KeyStore::<KeySlotIds>::default();
1330            let mut ctx = key_store.context_mut();
1331            let v1 = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
1332            let v2 = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
1333            V2UpgradeToken::create(v1, v2, &ctx).expect("unrelated token created")
1334        };
1335
1336        let bridge = client.km_state_bridge();
1337        bridge.set_persistent_pin_envelope(&state.envelope).await;
1338        bridge.set_encrypted_pin(&state.encrypted_pin).await;
1339        bridge.set_v2_upgrade_token(&unrelated_token).await;
1340
1341        let system = PinLockSystem::with_client(&client);
1342        assert_eq!(
1343            system.migrate_pin_envelope_if_needed().await,
1344            Err(MigrationFailed::PinDecryption),
1345        );
1346    }
1347
1348    /// The token unwraps, but the envelope holds neither that V1 key nor the current user key, so
1349    /// the user key was rotated rather than upgraded.
1350    #[tokio::test]
1351    async fn migrate_v2_envelope_with_rotated_user_key_fails() {
1352        let client = client_with_user_key();
1353        let system = PinLockSystem::with_client(&client);
1354        system
1355            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
1356            .await
1357            .expect("set_pin succeeds");
1358
1359        // Replace the persistent envelope with one sealed under a *different* V2 key, and supply
1360        // an upgrade token for the current user key so the rotation check is actually reached.
1361        let (mismatched_envelope, token) = {
1362            let mut ctx = client.internal.get_key_store().context_mut();
1363            let other_v2 = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
1364            let envelope = PasswordProtectedKeyEnvelope::seal(
1365                other_v2,
1366                "1234",
1367                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
1368                &ctx,
1369            )
1370            .expect("seal under other v2 key");
1371
1372            let v1 = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
1373            let token = V2UpgradeToken::create(v1, SymmetricKeySlotId::User, &ctx)
1374                .expect("upgrade token created");
1375            (envelope, token)
1376        };
1377
1378        let bridge = client.km_state_bridge();
1379        bridge
1380            .set_persistent_pin_envelope(&mismatched_envelope)
1381            .await;
1382        bridge.set_v2_upgrade_token(&token).await;
1383
1384        assert_eq!(
1385            system.migrate_pin_envelope_if_needed().await,
1386            Err(MigrationFailed::V2KeyRotationUnsupported),
1387        );
1388    }
1389
1390    /// Without a token the rotation above cannot be distinguished from a V1 envelope, so the
1391    /// missing token is reported instead.
1392    #[tokio::test]
1393    async fn migrate_v2_envelope_with_rotated_user_key_without_token_fails() {
1394        let client = client_with_user_key();
1395        let system = PinLockSystem::with_client(&client);
1396        system
1397            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
1398            .await
1399            .expect("set_pin succeeds");
1400
1401        let mismatched_envelope = {
1402            let mut ctx = client.internal.get_key_store().context_mut();
1403            let other_v2 = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
1404            PasswordProtectedKeyEnvelope::seal(
1405                other_v2,
1406                "1234",
1407                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
1408                &ctx,
1409            )
1410            .expect("seal under other v2 key")
1411        };
1412        client
1413            .km_state_bridge()
1414            .set_persistent_pin_envelope(&mismatched_envelope)
1415            .await;
1416
1417        assert_eq!(
1418            system.migrate_pin_envelope_if_needed().await,
1419            Err(MigrationFailed::MissingV2UpgradeToken),
1420        );
1421    }
1422
1423    #[tokio::test]
1424    async fn on_unlock_triggers_migration() {
1425        let pin = "1234";
1426        let (client, state) = fresh_v1_state_with_v2_user_key(pin);
1427        let bridge = client.km_state_bridge();
1428        bridge.set_persistent_pin_envelope(&state.envelope).await;
1429        bridge.set_encrypted_pin(&state.encrypted_pin).await;
1430        bridge.set_v2_upgrade_token(&state.token).await;
1431
1432        let user_key_id = user_key_id(&client);
1433        let system = PinLockSystem::with_client(&client);
1434
1435        system.on_unlock().await;
1436
1437        let persistent = bridge
1438            .get_persistent_pin_envelope()
1439            .await
1440            .expect("persistent envelope present after on_unlock");
1441        assert_envelope_wraps_user_key(&client, &persistent, pin, &user_key_id);
1442        assert!(system.unlock(pin).await.is_ok());
1443    }
1444
1445    #[tokio::test]
1446    async fn on_unlock_unenrolls_when_migration_fails() {
1447        // Reuse the missing-upgrade-token scenario to drive migration failure end-to-end.
1448        let pin = "1234";
1449        let (client, state) = fresh_v1_state_with_v2_user_key(pin);
1450        let bridge = client.km_state_bridge();
1451        bridge.set_persistent_pin_envelope(&state.envelope).await;
1452        bridge.set_encrypted_pin(&state.encrypted_pin).await;
1453        // Intentionally omit set_v2_upgrade_token so migration fails.
1454
1455        let system = PinLockSystem::with_client(&client);
1456        system.on_unlock().await;
1457
1458        assert_pin_fully_unenrolled(&client).await;
1459    }
1460}