Skip to main content

bitwarden_core/key_management/
crypto.rs

1//! Mobile specific crypto operations
2//!
3//! This module contains temporary code for handling mobile specific cryptographic operations until
4//! the SDK is fully implemented. When porting functionality from `client` the mobile clients should
5//! be updated to consume the regular code paths and in this module should eventually disappear.
6
7#[cfg(feature = "uniffi")]
8mod reinit_user_crypto;
9use std::collections::HashMap;
10
11use bitwarden_api_api::models::AccountKeysRequestModel;
12#[expect(deprecated)]
13use bitwarden_crypto::{
14    CoseSerializable, CryptoError, DeviceKey, EncString, Kdf, KeyConnectorKey, KeyDecryptable,
15    KeyEncryptable, MasterKey, Pkcs8PrivateKeyBytes, PrimitiveEncryptable, PrivateKey, PublicKey,
16    RotateableKeySet, SignatureAlgorithm, SignedPublicKey, SigningKey, SpkiPublicKeyBytes,
17    SymmetricCryptoKey, TrustDeviceResponse, UnsignedSharedKey, UserKey,
18    dangerous_get_v2_rotated_account_keys, derive_symmetric_key_from_prf,
19    safe::{PasswordProtectedKeyEnvelope, PasswordProtectedKeyEnvelopeError},
20};
21use bitwarden_crypto::{SymmetricKeyAlgorithm, safe::PasswordProtectedKeyEnvelopeNamespace};
22use bitwarden_encoding::B64;
23use bitwarden_error::bitwarden_error;
24#[cfg(feature = "uniffi")]
25pub(super) use reinit_user_crypto::reinit_user_crypto;
26#[cfg(feature = "uniffi")]
27pub use reinit_user_crypto::{ReinitUserCryptoError, ReinitUserCryptoRequest};
28use schemars::JsonSchema;
29use serde::{Deserialize, Serialize};
30use tracing::info;
31#[cfg(feature = "wasm")]
32use {tsify::Tsify, wasm_bindgen::prelude::*};
33
34#[cfg(feature = "wasm")]
35use crate::key_management::wasm_unlock_state::{
36    copy_user_key_to_client_managed_state, get_user_key_from_client_managed_state,
37};
38use crate::{
39    Client, NotAuthenticatedError, OrganizationId, UserId, WrongPasswordError,
40    client::{
41        LoginMethod, UserLoginMethod,
42        encryption_settings::EncryptionSettingsError,
43        persisted_state::{ACCOUNT_CRYPTO_STATE, OrganizationSharedKey},
44    },
45    error::StatefulCryptoError,
46    key_management::{
47        MasterPasswordError, PrivateKeySlotId, SecurityState, SignedSecurityState,
48        SigningKeySlotId, SymmetricKeySlotId, V2UpgradeToken,
49        account_cryptographic_state::{
50            AccountCryptographyInitializationError, WrappedAccountCryptographicState,
51        },
52        local_user_data_key_state::{
53            get_local_user_data_key_from_state, initialize_local_user_data_key_into_state,
54            migrate_local_user_data_key_for_user_key_upgrade,
55        },
56        master_password::{MasterPasswordAuthenticationData, MasterPasswordUnlockData},
57        pin_lock_system::{PinLockSystem, UnlockError},
58    },
59};
60
61/// Catch all error for mobile crypto operations.
62#[allow(missing_docs)]
63#[bitwarden_error(flat)]
64#[derive(Debug, thiserror::Error)]
65pub enum CryptoClientError {
66    #[error(transparent)]
67    NotAuthenticated(#[from] NotAuthenticatedError),
68    #[error(transparent)]
69    Crypto(#[from] bitwarden_crypto::CryptoError),
70    #[error("Invalid KDF settings")]
71    InvalidKdfSettings,
72    #[error(transparent)]
73    PasswordProtectedKeyEnvelope(#[from] PasswordProtectedKeyEnvelopeError),
74    #[error("Invalid PRF input")]
75    InvalidPrfInput,
76    #[error("Invalid upgrade token")]
77    InvalidUpgradeToken,
78    #[error("Upgrade token is required for V1 keys")]
79    UpgradeTokenRequired,
80    #[error("Invalid key type")]
81    InvalidKeyType,
82}
83
84/// State used for initializing the user cryptographic state.
85#[derive(Serialize, Deserialize, Debug)]
86#[serde(rename_all = "camelCase", deny_unknown_fields)]
87#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
88#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
89pub struct InitUserCryptoRequest {
90    /// The user's ID.
91    pub user_id: Option<UserId>,
92    /// The user's KDF parameters, as received from the prelogin request
93    pub kdf_params: Kdf,
94    /// The user's email address
95    pub email: String,
96    /// The user's account cryptographic state, containing their signature and
97    /// public-key-encryption keys, along with the signed security state, protected by the user key
98    pub account_cryptographic_state: WrappedAccountCryptographicState,
99    /// The method to decrypt the user's account symmetric key (user key)
100    pub method: InitUserCryptoMethod,
101    /// Optional V2 upgrade token for automatic key rotation from V1 to V2
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub upgrade_token: Option<V2UpgradeToken>,
104}
105
106/// The crypto method used to initialize the user cryptographic state.
107#[derive(Serialize, Deserialize, Debug)]
108#[serde(rename_all = "camelCase", deny_unknown_fields)]
109#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
110#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
111#[allow(clippy::large_enum_variant)]
112pub enum InitUserCryptoMethod {
113    /// Master Password Unlock
114    MasterPasswordUnlock {
115        /// The user's master password
116        password: String,
117        /// Contains the data needed to unlock with the master password
118        master_password_unlock: MasterPasswordUnlockData,
119    },
120    /// Read the user-key directly from client-managed state
121    /// Note: In contrast to [`InitUserCryptoMethod::DecryptedKey`], this does not update the state
122    /// after initalizing
123    #[cfg(feature = "wasm")]
124    ClientManagedState {},
125    /// Never lock and/or biometric unlock
126    DecryptedKey {
127        /// The user's decrypted encryption key, obtained using `get_user_encryption_key`
128        decrypted_user_key: String,
129    },
130    /// PIN
131    Pin {
132        /// The user's PIN
133        pin: String,
134        /// The user's symmetric crypto key, encrypted with the PIN. Use `derive_pin_key` to obtain
135        /// this.
136        pin_protected_user_key: EncString,
137    },
138    /// PIN state, where the PIN envelope is stored in persistent client-managed state
139    PinState {
140        /// The user's PIN
141        pin: String,
142    },
143    /// PIN Envelope
144    PinEnvelope {
145        /// The user's PIN
146        pin: String,
147        /// The user's symmetric crypto key, encrypted with the PIN-protected key envelope.
148        pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
149    },
150    /// Auth request
151    AuthRequest {
152        /// Private Key generated by the `crate::auth::new_auth_request`.
153        request_private_key: B64,
154        /// The type of auth request
155        method: AuthRequestMethod,
156    },
157    /// Device Key
158    DeviceKey {
159        /// The device's DeviceKey
160        device_key: String,
161        /// The Device Private Key
162        protected_device_private_key: EncString,
163        /// The user's symmetric crypto key, encrypted with the Device Key.
164        device_protected_user_key: UnsignedSharedKey,
165    },
166    /// Key connector
167    KeyConnector {
168        /// Base64 encoded master key, retrieved from the key connector.
169        master_key: B64,
170        /// The user's encrypted symmetric crypto key
171        user_key: EncString,
172    },
173    /// In contrast to key-connector, this does all of the connection with key-connector in the sdk
174    KeyConnectorUrl {
175        /// The url to retrieve the key-connector-key from
176        url: String,
177        /// The encrypted user key, encrypted with the key connector key retrieved from the url
178        key_connector_key_wrapped_user_key: EncString,
179    },
180}
181
182/// Auth requests supports multiple initialization methods.
183#[derive(Serialize, Deserialize, Debug)]
184#[serde(rename_all = "camelCase", deny_unknown_fields)]
185#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
186#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
187pub enum AuthRequestMethod {
188    /// User Key
189    UserKey {
190        /// User Key protected by the private key provided in `AuthRequestResponse`.
191        protected_user_key: UnsignedSharedKey,
192    },
193    /// Master Key
194    MasterKey {
195        /// Master Key protected by the private key provided in `AuthRequestResponse`.
196        protected_master_key: UnsignedSharedKey,
197        /// User Key protected by the MasterKey, provided by the auth response.
198        auth_request_key: EncString,
199    },
200}
201
202/// Initialize the user's cryptographic state.
203#[bitwarden_logging::instrument(err)]
204pub(super) async fn initialize_user_crypto(
205    client: &Client,
206    req: InitUserCryptoRequest,
207) -> Result<(), EncryptionSettingsError> {
208    use bitwarden_crypto::{DeviceKey, PinKey};
209
210    use crate::auth::{auth_request_decrypt_master_key, auth_request_decrypt_user_key};
211
212    if let Some(user_id) = req.user_id {
213        client.internal.init_user_id(user_id).await?;
214    }
215
216    tracing::Span::current().record(
217        "user_id",
218        client.internal.get_user_id().map(|id| id.to_string()),
219    );
220
221    let account_crypto_state = req.account_cryptographic_state.to_owned();
222
223    #[cfg(feature = "wasm")]
224    let should_copy_user_key = matches!(
225        req.method,
226        InitUserCryptoMethod::MasterPasswordUnlock { .. }
227            | InitUserCryptoMethod::DecryptedKey { .. }
228            | InitUserCryptoMethod::PinEnvelope { .. }
229            | InitUserCryptoMethod::PinState { .. }
230            | InitUserCryptoMethod::KeyConnectorUrl { .. }
231            | InitUserCryptoMethod::AuthRequest { .. }
232    );
233
234    match req.method {
235        InitUserCryptoMethod::MasterPasswordUnlock {
236            password,
237            master_password_unlock,
238        } => {
239            client
240                .internal
241                .initialize_user_crypto_master_password_unlock(
242                    password,
243                    master_password_unlock,
244                    account_crypto_state,
245                    &req.upgrade_token,
246                )?;
247        }
248        #[cfg(feature = "wasm")]
249        InitUserCryptoMethod::ClientManagedState {} => {
250            let user_key = get_user_key_from_client_managed_state(client)
251                .await
252                .map_err(|_| EncryptionSettingsError::UserKeyStateRetrievalFailed)?;
253            client.internal.initialize_user_crypto_decrypted_key(
254                user_key,
255                account_crypto_state,
256                &req.upgrade_token,
257            )?;
258        }
259        InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => {
260            let user_key = SymmetricCryptoKey::try_from(decrypted_user_key)?;
261            client.internal.initialize_user_crypto_decrypted_key(
262                user_key,
263                account_crypto_state,
264                &req.upgrade_token,
265            )?;
266        }
267        InitUserCryptoMethod::Pin {
268            pin,
269            pin_protected_user_key,
270        } => {
271            let pin_key = PinKey::derive(pin.as_bytes(), req.email.as_bytes(), &req.kdf_params)?;
272            client.internal.initialize_user_crypto_pin(
273                pin_key,
274                pin_protected_user_key,
275                account_crypto_state,
276                &req.upgrade_token,
277            )?;
278        }
279        InitUserCryptoMethod::PinEnvelope {
280            pin,
281            pin_protected_user_key_envelope,
282        } => {
283            client.internal.initialize_user_crypto_pin_envelope(
284                pin,
285                pin_protected_user_key_envelope,
286                account_crypto_state,
287                &req.upgrade_token,
288            )?;
289        }
290        InitUserCryptoMethod::PinState { pin } => {
291            PinLockSystem::with_client(client)
292                .unlock(pin.as_str())
293                .await
294                .map_err(|err| match err {
295                    UnlockError::PinWrong => EncryptionSettingsError::WrongPin,
296                    _ => EncryptionSettingsError::CryptoInitialization,
297                })?;
298            // Note: PinLockSystem sets the user-key to state, and this section is reading it from
299            // state, then re-setting it via `initialize_user_crypto_decrypted_key`.
300            // This is not ideal and should be refactored in the future.
301            #[allow(deprecated)]
302            let user_key = client
303                .internal
304                .get_key_store()
305                .context()
306                .dangerous_get_symmetric_key(SymmetricKeySlotId::User)?
307                .to_owned();
308            // Otherwise the initialize will fail with a double init error.
309            client
310                .internal
311                .get_key_store()
312                .context_mut()
313                .drop_symmetric_key(SymmetricKeySlotId::User)?;
314
315            client.internal.initialize_user_crypto_decrypted_key(
316                user_key,
317                account_crypto_state,
318                &req.upgrade_token,
319            )?;
320        }
321        InitUserCryptoMethod::AuthRequest {
322            request_private_key,
323            method,
324        } => {
325            let user_key = match method {
326                AuthRequestMethod::UserKey { protected_user_key } => {
327                    auth_request_decrypt_user_key(request_private_key, protected_user_key)?
328                }
329                AuthRequestMethod::MasterKey {
330                    protected_master_key,
331                    auth_request_key,
332                } => auth_request_decrypt_master_key(
333                    request_private_key,
334                    protected_master_key,
335                    auth_request_key,
336                )?,
337            };
338            client.internal.initialize_user_crypto_decrypted_key(
339                user_key,
340                account_crypto_state,
341                &req.upgrade_token,
342            )?;
343        }
344        InitUserCryptoMethod::DeviceKey {
345            device_key,
346            protected_device_private_key,
347            device_protected_user_key,
348        } => {
349            let device_key = DeviceKey::try_from(device_key)?;
350            let user_key = device_key
351                .decrypt_user_key(protected_device_private_key, device_protected_user_key)?;
352
353            client.internal.initialize_user_crypto_decrypted_key(
354                user_key,
355                account_crypto_state,
356                &req.upgrade_token,
357            )?;
358        }
359        InitUserCryptoMethod::KeyConnector {
360            master_key,
361            user_key,
362        } => {
363            let bytes = master_key.into_bytes();
364            let master_key = MasterKey::try_from(bytes)?;
365
366            client.internal.initialize_user_crypto_key_connector_key(
367                master_key,
368                user_key,
369                account_crypto_state,
370                &req.upgrade_token,
371            )?;
372        }
373        InitUserCryptoMethod::KeyConnectorUrl {
374            url,
375            key_connector_key_wrapped_user_key,
376        } => {
377            let api_client = client.internal.get_key_connector_client(url);
378            let key_connector_key_response = api_client
379                .user_keys_api()
380                .get_user_key()
381                .await
382                .map_err(|_| EncryptionSettingsError::KeyConnectorRetrievalFailed)?;
383            let key_connector_key = KeyConnectorKey::try_from(key_connector_key_response)?;
384            let user_key =
385                key_connector_key.decrypt_user_key(key_connector_key_wrapped_user_key)?;
386            client.internal.initialize_user_crypto_decrypted_key(
387                user_key,
388                account_crypto_state,
389                &req.upgrade_token,
390            )?;
391        }
392    }
393
394    #[cfg(feature = "wasm")]
395    if should_copy_user_key {
396        copy_user_key_to_client_managed_state(client)
397            .await
398            .map_err(|_| EncryptionSettingsError::UserKeyStateUpdateFailed)?;
399    }
400
401    on_unlock_handler(client).await?;
402
403    client
404        .internal
405        .set_login_method(LoginMethod::User(UserLoginMethod::Username {
406            client_id: "".to_string(),
407            email: req.email,
408            kdf: req.kdf_params,
409        }))
410        .await;
411
412    if let Ok(setting) = client.internal.state_registry.setting(ACCOUNT_CRYPTO_STATE)
413        && let Err(e) = setting.update(req.account_cryptographic_state).await
414    {
415        tracing::warn!("Failed to persist account crypto state: {e}");
416    }
417
418    info!("User crypto initialized successfully");
419
420    Ok(())
421}
422
423/// Represents the request to initialize the user's organizational cryptographic state.
424#[derive(Serialize, Deserialize, Debug)]
425#[serde(rename_all = "camelCase", deny_unknown_fields)]
426#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
427#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
428pub struct InitOrgCryptoRequest {
429    /// The encryption keys for all the organizations the user is a part of
430    pub organization_keys: HashMap<OrganizationId, UnsignedSharedKey>,
431}
432
433/// Initialize the user's organizational cryptographic state.
434pub(super) async fn initialize_org_crypto(
435    client: &Client,
436    req: InitOrgCryptoRequest,
437) -> Result<(), EncryptionSettingsError> {
438    let organization_keys: Vec<_> = req.organization_keys.into_iter().collect();
439    client
440        .internal
441        .initialize_org_crypto(organization_keys.clone())?;
442
443    // Persist org keys for rehydration
444    if let Ok(repo) = client
445        .internal
446        .state_registry
447        .get::<OrganizationSharedKey>()
448    {
449        for (org_id, key) in organization_keys {
450            if let Err(e) = repo
451                .set(org_id, OrganizationSharedKey { org_id, key })
452                .await
453            {
454                tracing::warn!("Failed to persist org key for {org_id}: {e}");
455            }
456        }
457    }
458
459    Ok(())
460}
461
462pub(super) async fn get_user_encryption_key(client: &Client) -> Result<B64, CryptoClientError> {
463    let key_store = client.internal.get_key_store();
464    let ctx = key_store.context();
465    // This is needed because the clients need access to the user encryption key
466    // in order to set side-effects such as biometrics, and never-lock
467    #[allow(deprecated)]
468    let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
469
470    Ok(user_key.to_base64())
471}
472
473/// Response from the `update_kdf` function
474#[derive(Serialize, Deserialize, Debug)]
475#[serde(rename_all = "camelCase", deny_unknown_fields)]
476#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
477#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
478pub struct UpdateKdfResponse {
479    /// The authentication data for the new KDF setting
480    master_password_authentication_data: MasterPasswordAuthenticationData,
481    /// The unlock data for the new KDF setting
482    master_password_unlock_data: MasterPasswordUnlockData,
483    /// The authentication data for the KDF setting prior to the change
484    old_master_password_authentication_data: MasterPasswordAuthenticationData,
485}
486
487pub(super) async fn make_update_kdf(
488    client: &Client,
489    password: &str,
490    new_kdf: &Kdf,
491) -> Result<UpdateKdfResponse, CryptoClientError> {
492    let login_method = client
493        .internal
494        .get_login_method()
495        .await
496        .ok_or(NotAuthenticatedError)?;
497    let email = match login_method {
498        UserLoginMethod::Username { email, .. } | UserLoginMethod::ApiKey { email, .. } => email,
499    };
500
501    let old_authentication_data = MasterPasswordAuthenticationData::derive(
502        password,
503        &client
504            .internal
505            .get_kdf()
506            .await
507            .map_err(|_| NotAuthenticatedError)?,
508        &email,
509    )
510    .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
511
512    let key_store = client.internal.get_key_store();
513    let ctx = key_store.context();
514
515    let authentication_data = MasterPasswordAuthenticationData::derive(password, new_kdf, &email)
516        .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
517    let unlock_data =
518        MasterPasswordUnlockData::derive(password, new_kdf, &email, SymmetricKeySlotId::User, &ctx)
519            .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
520
521    Ok(UpdateKdfResponse {
522        master_password_authentication_data: authentication_data,
523        master_password_unlock_data: unlock_data,
524        old_master_password_authentication_data: old_authentication_data,
525    })
526}
527
528/// Response from the `make_update_password` function
529#[derive(Serialize, Deserialize, Debug)]
530#[serde(rename_all = "camelCase", deny_unknown_fields)]
531#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
532#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
533pub struct UpdatePasswordResponse {
534    /// Hash of the new password
535    password_hash: B64,
536    /// User key, encrypted with the new password
537    new_key: EncString,
538}
539
540pub(super) async fn make_update_password(
541    client: &Client,
542    new_password: String,
543) -> Result<UpdatePasswordResponse, CryptoClientError> {
544    let login_method = client
545        .internal
546        .get_login_method()
547        .await
548        .ok_or(NotAuthenticatedError)?;
549
550    let key_store = client.internal.get_key_store();
551    let ctx = key_store.context();
552    // FIXME: [PM-18099] Once MasterKey deals with KeySlotIds, this should be updated
553    #[allow(deprecated)]
554    let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
555
556    // Derive a new master key from password
557    let new_master_key = match login_method {
558        UserLoginMethod::Username { email, kdf, .. }
559        | UserLoginMethod::ApiKey { email, kdf, .. } => {
560            MasterKey::derive(&new_password, &email, &kdf)?
561        }
562    };
563
564    let new_key = new_master_key.encrypt_user_key(user_key)?;
565
566    let password_hash = new_master_key.derive_master_key_hash(
567        new_password.as_bytes(),
568        bitwarden_crypto::HashPurpose::ServerAuthorization,
569    );
570
571    Ok(UpdatePasswordResponse {
572        password_hash,
573        new_key,
574    })
575}
576
577/// Request for deriving a pin protected user key
578#[derive(Serialize, Deserialize, Debug)]
579#[serde(rename_all = "camelCase", deny_unknown_fields)]
580#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
581#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
582pub struct EnrollPinResponse {
583    /// [UserKey] protected by PIN
584    pub pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
585    /// PIN protected by [UserKey]
586    pub user_key_encrypted_pin: EncString,
587}
588
589pub(super) fn enroll_pin(
590    client: &Client,
591    pin: String,
592) -> Result<EnrollPinResponse, CryptoClientError> {
593    let key_store = client.internal.get_key_store();
594    let mut ctx = key_store.context_mut();
595
596    let key_envelope = PasswordProtectedKeyEnvelope::seal(
597        SymmetricKeySlotId::User,
598        &pin,
599        PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
600        &ctx,
601    )?;
602    let encrypted_pin = pin.encrypt(&mut ctx, SymmetricKeySlotId::User)?;
603    Ok(EnrollPinResponse {
604        pin_protected_user_key_envelope: key_envelope,
605        user_key_encrypted_pin: encrypted_pin,
606    })
607}
608
609/// Request for deriving a pin protected user key
610#[derive(Serialize, Deserialize, Debug)]
611#[serde(rename_all = "camelCase", deny_unknown_fields)]
612#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
613#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
614pub struct DerivePinKeyResponse {
615    /// [UserKey] protected by PIN
616    pin_protected_user_key: EncString,
617    /// PIN protected by [UserKey]
618    encrypted_pin: EncString,
619}
620
621pub(super) async fn derive_pin_key(
622    client: &Client,
623    pin: String,
624) -> Result<DerivePinKeyResponse, CryptoClientError> {
625    let login_method = client
626        .internal
627        .get_login_method()
628        .await
629        .ok_or(NotAuthenticatedError)?;
630
631    let key_store = client.internal.get_key_store();
632    let ctx = key_store.context();
633    // FIXME: [PM-18099] Once PinKey deals with KeySlotIds, this should be updated
634    #[allow(deprecated)]
635    let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
636
637    let pin_protected_user_key = derive_pin_protected_user_key(&pin, &login_method, user_key)?;
638
639    Ok(DerivePinKeyResponse {
640        pin_protected_user_key,
641        encrypted_pin: pin.encrypt_with_key(user_key)?,
642    })
643}
644
645pub(super) async fn derive_pin_user_key(
646    client: &Client,
647    encrypted_pin: EncString,
648) -> Result<EncString, CryptoClientError> {
649    let login_method = client
650        .internal
651        .get_login_method()
652        .await
653        .ok_or(NotAuthenticatedError)?;
654
655    let key_store = client.internal.get_key_store();
656    let ctx = key_store.context();
657    // FIXME: [PM-18099] Once PinKey deals with KeySlotIds, this should be updated
658    #[allow(deprecated)]
659    let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
660
661    let pin: String = encrypted_pin.decrypt_with_key(user_key)?;
662
663    derive_pin_protected_user_key(&pin, &login_method, user_key)
664}
665
666fn derive_pin_protected_user_key(
667    pin: &str,
668    login_method: &UserLoginMethod,
669    user_key: &SymmetricCryptoKey,
670) -> Result<EncString, CryptoClientError> {
671    use bitwarden_crypto::PinKey;
672
673    let derived_key = match login_method {
674        UserLoginMethod::Username { email, kdf, .. }
675        | UserLoginMethod::ApiKey { email, kdf, .. } => {
676            PinKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?
677        }
678    };
679
680    Ok(derived_key.encrypt_user_key(user_key)?)
681}
682
683pub(super) fn make_prf_user_key_set(
684    client: &Client,
685    prf: B64,
686) -> Result<RotateableKeySet, CryptoClientError> {
687    let prf_key = derive_symmetric_key_from_prf(prf.as_bytes())
688        .map_err(|_| CryptoClientError::InvalidPrfInput)?;
689    let ctx = client.internal.get_key_store().context();
690    let key_set = RotateableKeySet::new(&ctx, &prf_key, SymmetricKeySlotId::User)?;
691    Ok(key_set)
692}
693
694#[allow(missing_docs)]
695#[bitwarden_error(flat)]
696#[derive(Debug, thiserror::Error)]
697pub enum EnrollAdminPasswordResetError {
698    #[error(transparent)]
699    Crypto(#[from] bitwarden_crypto::CryptoError),
700}
701
702pub(super) fn enroll_admin_password_reset(
703    client: &Client,
704    public_key: B64,
705) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
706    use bitwarden_crypto::PublicKey;
707
708    let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&public_key))?;
709    let key_store = client.internal.get_key_store();
710    let ctx = key_store.context();
711    // FIXME: [PM-18110] This should be removed once the key store can handle public key encryption
712    #[allow(deprecated)]
713    let key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
714
715    #[expect(deprecated)]
716    Ok(UnsignedSharedKey::encapsulate_key_unsigned(
717        key,
718        &public_key,
719    )?)
720}
721
722/// Request for migrating an account from password to key connector.
723#[derive(Serialize, Deserialize, Debug, JsonSchema)]
724#[serde(rename_all = "camelCase", deny_unknown_fields)]
725#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
726#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
727pub struct DeriveKeyConnectorRequest {
728    /// Encrypted user key, used to validate the master key
729    pub user_key_encrypted: EncString,
730    /// The user's master password
731    pub password: String,
732    /// The KDF parameters used to derive the master key
733    pub kdf: Kdf,
734    /// The user's email address
735    pub email: String,
736}
737
738#[allow(missing_docs)]
739#[bitwarden_error(flat)]
740#[derive(Debug, thiserror::Error)]
741pub enum DeriveKeyConnectorError {
742    #[error(transparent)]
743    WrongPassword(#[from] WrongPasswordError),
744    #[error(transparent)]
745    Crypto(#[from] bitwarden_crypto::CryptoError),
746}
747
748/// Derive the master key for migrating to the key connector
749pub(super) fn derive_key_connector(
750    request: DeriveKeyConnectorRequest,
751) -> Result<B64, DeriveKeyConnectorError> {
752    let master_key = MasterKey::derive(&request.password, &request.email, &request.kdf)?;
753    master_key
754        .decrypt_user_key(request.user_key_encrypted)
755        .map_err(|_| WrongPasswordError)?;
756
757    Ok(master_key.to_base64())
758}
759
760/// Response from the `make_key_pair` function
761#[derive(Serialize, Deserialize, Debug)]
762#[serde(rename_all = "camelCase", deny_unknown_fields)]
763#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
764#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
765pub struct MakeKeyPairResponse {
766    /// The user's public key
767    user_public_key: B64,
768    /// User's private key, encrypted with the user key
769    user_key_encrypted_private_key: EncString,
770}
771
772pub(super) fn make_key_pair(user_key: B64) -> Result<MakeKeyPairResponse, CryptoError> {
773    let user_key = UserKey::new(SymmetricCryptoKey::try_from(user_key)?);
774
775    let key_pair = user_key.make_key_pair()?;
776
777    Ok(MakeKeyPairResponse {
778        user_public_key: key_pair.public,
779        user_key_encrypted_private_key: key_pair.private,
780    })
781}
782
783/// Request for `verify_asymmetric_keys`.
784#[derive(Serialize, Deserialize, Debug)]
785#[serde(rename_all = "camelCase", deny_unknown_fields)]
786#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
787#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
788pub struct VerifyAsymmetricKeysRequest {
789    /// The user's user key
790    user_key: B64,
791    /// The user's public key
792    user_public_key: B64,
793    /// User's private key, encrypted with the user key
794    user_key_encrypted_private_key: EncString,
795}
796
797/// Response for `verify_asymmetric_keys`.
798#[derive(Serialize, Deserialize, Debug)]
799#[serde(rename_all = "camelCase", deny_unknown_fields)]
800#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
801#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
802pub struct VerifyAsymmetricKeysResponse {
803    /// Whether the user's private key was decryptable by the user key.
804    private_key_decryptable: bool,
805    /// Whether the user's private key was a valid RSA key and matched the public key provided.
806    valid_private_key: bool,
807}
808
809pub(super) fn verify_asymmetric_keys(
810    request: VerifyAsymmetricKeysRequest,
811) -> Result<VerifyAsymmetricKeysResponse, CryptoError> {
812    #[derive(Debug, thiserror::Error)]
813    enum VerifyError {
814        #[error("Failed to decrypt private key: {0:?}")]
815        DecryptFailed(bitwarden_crypto::CryptoError),
816        #[error("Failed to parse decrypted private key: {0:?}")]
817        ParseFailed(bitwarden_crypto::CryptoError),
818        #[error("Failed to derive a public key: {0:?}")]
819        PublicFailed(bitwarden_crypto::CryptoError),
820        #[error("Derived public key doesn't match")]
821        KeyMismatch,
822    }
823
824    fn verify_inner(
825        user_key: &SymmetricCryptoKey,
826        request: &VerifyAsymmetricKeysRequest,
827    ) -> Result<(), VerifyError> {
828        let decrypted_private_key: Vec<u8> = request
829            .user_key_encrypted_private_key
830            .decrypt_with_key(user_key)
831            .map_err(VerifyError::DecryptFailed)?;
832
833        let decrypted_private_key = Pkcs8PrivateKeyBytes::from(decrypted_private_key);
834        let private_key =
835            PrivateKey::from_der(&decrypted_private_key).map_err(VerifyError::ParseFailed)?;
836
837        let derived_public_key_vec = private_key
838            .to_public_key()
839            .to_der()
840            .map_err(VerifyError::PublicFailed)?;
841
842        let derived_public_key = B64::from(derived_public_key_vec);
843
844        if derived_public_key != request.user_public_key {
845            return Err(VerifyError::KeyMismatch);
846        }
847        Ok(())
848    }
849
850    let user_key = SymmetricCryptoKey::try_from(request.user_key.clone())?;
851
852    Ok(match verify_inner(&user_key, &request) {
853        Ok(_) => VerifyAsymmetricKeysResponse {
854            private_key_decryptable: true,
855            valid_private_key: true,
856        },
857        Err(error) => {
858            tracing::debug!(%error, "User asymmetric keys verification");
859
860            VerifyAsymmetricKeysResponse {
861                private_key_decryptable: !matches!(error, VerifyError::DecryptFailed(_)),
862                valid_private_key: false,
863            }
864        }
865    })
866}
867
868/// Response for the `make_keys_for_user_crypto_v2`, containing a set of keys for a user
869#[derive(Serialize, Deserialize, Debug, Clone)]
870#[serde(rename_all = "camelCase", deny_unknown_fields)]
871#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
872#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
873pub struct UserCryptoV2KeysResponse {
874    /// User key
875    user_key: B64,
876
877    /// Wrapped private key
878    private_key: EncString,
879    /// Public key
880    public_key: B64,
881    /// The user's public key, signed by the signing key
882    signed_public_key: SignedPublicKey,
883
884    /// Signing key, encrypted with the user's symmetric key
885    signing_key: EncString,
886    /// Base64 encoded verifying key
887    verifying_key: B64,
888
889    /// The user's signed security state
890    security_state: SignedSecurityState,
891    /// The security state's version
892    security_version: u64,
893}
894
895/// Creates the user's cryptographic state for v2 users. This includes ensuring signature key pair
896/// is present, a signed public key is present, a security state is present and signed, and the user
897/// key is a Cose key.
898#[deprecated(note = "Use AccountCryptographicState::rotate instead")]
899pub(crate) fn make_v2_keys_for_v1_user(
900    client: &Client,
901) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
902    let key_store = client.internal.get_key_store();
903    let mut ctx = key_store.context();
904
905    // Re-use existing private key
906    let private_key_id = PrivateKeySlotId::UserPrivateKey;
907
908    // Ensure that the function is only called for a V1 user.
909    if client.internal.get_security_version() != 1 {
910        return Err(StatefulCryptoError::WrongAccountCryptoVersion {
911            expected: "1".to_string(),
912            got: 2,
913        });
914    }
915
916    // Ensure the user has a private key.
917    // V1 user must have a private key to upgrade. This should be ensured by the client before
918    // calling the upgrade function.
919    if !ctx.has_private_key(PrivateKeySlotId::UserPrivateKey) {
920        return Err(StatefulCryptoError::Crypto(CryptoError::MissingKeyId(
921            "UserPrivateKey".to_string(),
922        )));
923    }
924
925    #[allow(deprecated)]
926    let private_key = ctx.dangerous_get_private_key(private_key_id)?.clone();
927
928    // New user key
929    let user_key = SymmetricCryptoKey::make(SymmetricKeyAlgorithm::XChaCha20Poly1305);
930
931    // New signing key
932    let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
933    let temporary_signing_key_id = ctx.add_local_signing_key(signing_key.clone());
934
935    // Sign existing public key
936    let signed_public_key = ctx.make_signed_public_key(private_key_id, temporary_signing_key_id)?;
937    let public_key = private_key.to_public_key();
938
939    // Initialize security state for the user
940    let security_state = SecurityState::new();
941    let signed_security_state = security_state.sign(temporary_signing_key_id, &mut ctx)?;
942
943    Ok(UserCryptoV2KeysResponse {
944        user_key: user_key.to_base64(),
945
946        private_key: private_key.to_der()?.encrypt_with_key(&user_key)?,
947        public_key: public_key.to_der()?.into(),
948        signed_public_key,
949
950        signing_key: signing_key.to_cose().encrypt_with_key(&user_key)?,
951        verifying_key: signing_key.to_verifying_key().to_cose().into(),
952
953        security_state: signed_security_state,
954        security_version: security_state.version(),
955    })
956}
957
958/// Gets a set of new wrapped account keys for a user, given a new user key.
959///
960/// In the current implementation, it just re-encrypts any existing keys. This function expects a
961/// user to be a v2 user; that is, they have a signing key, a cose user-key, and a private key
962#[deprecated(note = "Use AccountCryptographicState::rotate instead")]
963pub(crate) fn get_v2_rotated_account_keys(
964    client: &Client,
965) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
966    let key_store = client.internal.get_key_store();
967    let mut ctx = key_store.context();
968
969    // Ensure that the function is only called for a V2 user.
970    // V2 users have a security version 2 or higher.
971    if client.internal.get_security_version() == 1 {
972        return Err(StatefulCryptoError::WrongAccountCryptoVersion {
973            expected: "2+".to_string(),
974            got: 1,
975        });
976    }
977
978    let security_state = client
979        .internal
980        .security_state
981        .read()
982        .expect("RwLock is not poisoned")
983        .to_owned()
984        // This cannot occur since the security version check above already ensures that the
985        // security state is present.
986        .ok_or(StatefulCryptoError::MissingSecurityState)?;
987
988    #[expect(deprecated)]
989    let rotated_keys = dangerous_get_v2_rotated_account_keys(
990        PrivateKeySlotId::UserPrivateKey,
991        SigningKeySlotId::UserSigningKey,
992        &ctx,
993    )?;
994
995    Ok(UserCryptoV2KeysResponse {
996        user_key: rotated_keys.user_key.to_base64(),
997
998        private_key: rotated_keys.private_key,
999        public_key: rotated_keys.public_key.into(),
1000        signed_public_key: rotated_keys.signed_public_key,
1001
1002        signing_key: rotated_keys.signing_key,
1003        verifying_key: rotated_keys.verifying_key.into(),
1004
1005        security_state: security_state.sign(SigningKeySlotId::UserSigningKey, &mut ctx)?,
1006        security_version: security_state.version(),
1007    })
1008}
1009
1010/// The response from `make_user_tde_registration`.
1011pub struct MakeTdeRegistrationResponse {
1012    /// The account cryptographic state
1013    pub account_cryptographic_state: WrappedAccountCryptographicState,
1014    /// The user's user key
1015    pub user_key: SymmetricCryptoKey,
1016    /// The request model for the account cryptographic state (also called Account Keys)
1017    pub account_keys_request: AccountKeysRequestModel,
1018    /// The keys needed to set up TDE decryption
1019    pub trusted_device_keys: TrustDeviceResponse,
1020    /// The key needed for admin password reset
1021    pub reset_password_key: UnsignedSharedKey,
1022}
1023
1024/// The response from `make_user_jit_master_password_registration`.
1025pub struct MakeJitMasterPasswordRegistrationResponse {
1026    /// The account cryptographic state
1027    pub account_cryptographic_state: WrappedAccountCryptographicState,
1028    /// The user's user key
1029    pub user_key: SymmetricCryptoKey,
1030    /// The master password unlock data
1031    pub master_password_authentication_data: MasterPasswordAuthenticationData,
1032    /// The master password unlock data
1033    pub master_password_unlock_data: MasterPasswordUnlockData,
1034    /// The request model for the account cryptographic state (also called Account Keys)
1035    pub account_keys_request: AccountKeysRequestModel,
1036    /// The key needed for admin password reset
1037    pub reset_password_key: UnsignedSharedKey,
1038}
1039
1040/// Errors that can occur when making keys for account cryptography registration.
1041#[bitwarden_error(flat)]
1042#[derive(Debug, thiserror::Error)]
1043pub enum MakeKeysError {
1044    /// Failed to initialize account cryptography
1045    #[error("Failed to initialize account cryptography")]
1046    AccountCryptographyInitialization(AccountCryptographyInitializationError),
1047    /// Failed to derive master password
1048    #[error("Failed to derive master password")]
1049    MasterPasswordDerivation(MasterPasswordError),
1050    /// Failed to create request model
1051    #[error("Failed to make a request model")]
1052    RequestModelCreation,
1053    /// Generic crypto error
1054    #[error("Cryptography error: {0}")]
1055    Crypto(#[from] CryptoError),
1056}
1057
1058/// Create the data needed to register for TDE (Trusted Device Enrollment)
1059pub(crate) fn make_user_tde_registration(
1060    client: &Client,
1061    org_public_key: B64,
1062) -> Result<MakeTdeRegistrationResponse, MakeKeysError> {
1063    let mut ctx = client.internal.get_key_store().context_mut();
1064    let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1065        .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1066    // TDE unlock method
1067    #[expect(deprecated)]
1068    let device_key = DeviceKey::trust_device(ctx.dangerous_get_symmetric_key(user_key_id)?)?;
1069
1070    // Account recovery enrollment
1071    let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&org_public_key))
1072        .map_err(MakeKeysError::Crypto)?;
1073    #[expect(deprecated)]
1074    let admin_reset = UnsignedSharedKey::encapsulate_key_unsigned(
1075        ctx.dangerous_get_symmetric_key(user_key_id)?,
1076        &public_key,
1077    )
1078    .map_err(MakeKeysError::Crypto)?;
1079
1080    let cryptography_state_request_model = wrapped_state
1081        .to_request_model(&user_key_id, &mut ctx)
1082        .map_err(|_| MakeKeysError::RequestModelCreation)?;
1083
1084    #[expect(deprecated)]
1085    Ok(MakeTdeRegistrationResponse {
1086        account_cryptographic_state: wrapped_state,
1087        user_key: ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned(),
1088        account_keys_request: cryptography_state_request_model,
1089        trusted_device_keys: device_key,
1090        reset_password_key: admin_reset,
1091    })
1092}
1093
1094/// The response from `make_user_key_connector_registration`.
1095pub struct MakeKeyConnectorRegistrationResponse {
1096    /// The account cryptographic state
1097    pub account_cryptographic_state: WrappedAccountCryptographicState,
1098    /// Encrypted user's user key, wrapped with the key connector key
1099    pub key_connector_key_wrapped_user_key: EncString,
1100    /// The user's user key
1101    pub user_key: SymmetricCryptoKey,
1102    /// The request model for the account cryptographic state (also called Account Keys)
1103    pub account_keys_request: AccountKeysRequestModel,
1104    /// The key connector key used for unlocking
1105    pub key_connector_key: KeyConnectorKey,
1106}
1107
1108/// Create the data needed to register for Key Connector
1109pub(crate) fn make_user_key_connector_registration(
1110    client: &Client,
1111) -> Result<MakeKeyConnectorRegistrationResponse, MakeKeysError> {
1112    let mut ctx = client.internal.get_key_store().context_mut();
1113    let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1114        .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1115    #[expect(deprecated)]
1116    let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
1117
1118    // Key Connector unlock method
1119    let key_connector_key = KeyConnectorKey::make();
1120
1121    let wrapped_user_key = key_connector_key
1122        .encrypt_user_key(&user_key)
1123        .map_err(MakeKeysError::Crypto)?;
1124
1125    let cryptography_state_request_model =
1126        wrapped_state
1127            .to_request_model(&user_key_id, &mut ctx)
1128            .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1129
1130    Ok(MakeKeyConnectorRegistrationResponse {
1131        account_cryptographic_state: wrapped_state,
1132        key_connector_key_wrapped_user_key: wrapped_user_key,
1133        user_key,
1134        account_keys_request: cryptography_state_request_model,
1135        key_connector_key,
1136    })
1137}
1138
1139/// Ensures the [`SymmetricKeySlotId::LocalUserData`] key is loaded into the key store context.
1140///
1141/// On first call the key is generated (wrapping the user key with itself) and persisted to state.
1142/// Subsequent calls are idempotent: if the key already exists in state it is loaded as-is,
1143/// preserving any data that was previously encrypted with it (e.g. after a key rotation).
1144async fn initialize_user_local_data_key(client: &Client) -> Result<(), EncryptionSettingsError> {
1145    let user_id = client
1146        .internal
1147        .get_user_id()
1148        .ok_or(EncryptionSettingsError::LocalUserDataKeyInitFailed)?;
1149
1150    migrate_local_user_data_key_for_user_key_upgrade(client, user_id)
1151        .await
1152        .map_err(|_| EncryptionSettingsError::LocalUserDataMigrationFailed)?;
1153
1154    initialize_local_user_data_key_into_state(client, user_id)
1155        .await
1156        .map_err(|_| EncryptionSettingsError::LocalUserDataKeyInitFailed)?;
1157
1158    let wrapped_key = get_local_user_data_key_from_state(client, user_id)
1159        .await
1160        .map_err(|_| EncryptionSettingsError::LocalUserDataKeyLoadFailed)?;
1161    let mut ctx = client.internal.get_key_store().context_mut();
1162    wrapped_key
1163        .unwrap_to_context(&mut ctx)
1164        .map_err(|_| EncryptionSettingsError::LocalUserDataKeyLoadFailed)
1165}
1166
1167/// Runs the code needed post unlock used by `initialize_user_crypto` and `reinit_user_crypto`.
1168///
1169/// Both code paths leave the SDK in an unlocked state with the active user key in the key store,
1170/// and both need to ensure derived per-user state is consistent with that user key before clients
1171/// can use the session.
1172async fn on_unlock_handler(client: &Client) -> Result<(), EncryptionSettingsError> {
1173    initialize_user_local_data_key(client).await?;
1174    PinLockSystem::on_unlock(&PinLockSystem::with_client(client)).await;
1175    Ok(())
1176}
1177
1178/// Create the data needed to register for JIT master password
1179pub(crate) fn make_user_jit_master_password_registration(
1180    client: &Client,
1181    master_password: String,
1182    salt: String,
1183    org_public_key: B64,
1184) -> Result<MakeJitMasterPasswordRegistrationResponse, MakeKeysError> {
1185    let mut ctx = client.internal.get_key_store().context_mut();
1186    let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1187        .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1188
1189    let kdf = Kdf::default_argon2();
1190
1191    #[expect(deprecated)]
1192    let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
1193
1194    let master_password_unlock_data =
1195        MasterPasswordUnlockData::derive(&master_password, &kdf, &salt, user_key_id, &ctx)
1196            .map_err(MakeKeysError::MasterPasswordDerivation)?;
1197
1198    let master_password_authentication_data =
1199        MasterPasswordAuthenticationData::derive(&master_password, &kdf, &salt)
1200            .map_err(MakeKeysError::MasterPasswordDerivation)?;
1201
1202    let cryptography_state_request_model = wrapped_state
1203        .to_request_model(&user_key_id, &mut ctx)
1204        .map_err(|_| MakeKeysError::RequestModelCreation)?;
1205
1206    // Account recovery enrollment
1207    let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&org_public_key))
1208        .map_err(MakeKeysError::Crypto)?;
1209    let admin_reset_key = UnsignedSharedKey::encapsulate(user_key_id, &public_key, &ctx)
1210        .map_err(MakeKeysError::Crypto)?;
1211
1212    Ok(MakeJitMasterPasswordRegistrationResponse {
1213        account_cryptographic_state: wrapped_state,
1214        user_key,
1215        master_password_unlock_data,
1216        master_password_authentication_data,
1217        account_keys_request: cryptography_state_request_model,
1218        reset_password_key: admin_reset_key,
1219    })
1220}
1221
1222/// Response from `make_user_password_registration`
1223pub struct MakeUserMasterPasswordRegistrationResponse {
1224    /// The wrapped account cryptographic state
1225    pub account_cryptographic_state: WrappedAccountCryptographicState,
1226    /// The master password unlock data
1227    pub master_password_unlock_data: MasterPasswordUnlockData,
1228    /// The master password authentication data
1229    pub master_password_authentication_data: MasterPasswordAuthenticationData,
1230    /// The request model for account cryptographic key state
1231    pub account_keys_request: AccountKeysRequestModel,
1232    /// The user's user key
1233    pub user_key: SymmetricCryptoKey,
1234}
1235
1236/// Creates cryptographic data needed for user master password registration
1237pub(crate) fn make_user_password_registration(
1238    client: &Client,
1239    master_password: String,
1240    salt: String,
1241) -> Result<MakeUserMasterPasswordRegistrationResponse, MakeKeysError> {
1242    // make_user_v2_crypto_state() - Creates user key (xchacha20-poly1305), RSA keypair, ed25519
1243    // signature keypair, and signed security state
1244    let mut ctx = client.internal.get_key_store().context_mut();
1245    let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1246        .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1247
1248    let kdf = Kdf::default_argon2();
1249
1250    #[expect(deprecated)]
1251    let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
1252
1253    let master_password_unlock_data =
1254        MasterPasswordUnlockData::derive(&master_password, &kdf, &salt, user_key_id, &ctx)
1255            .map_err(MakeKeysError::MasterPasswordDerivation)?;
1256
1257    let master_password_authentication_data =
1258        MasterPasswordAuthenticationData::derive(&master_password, &kdf, &salt)
1259            .map_err(MakeKeysError::MasterPasswordDerivation)?;
1260
1261    let account_keys_request = wrapped_state
1262        .to_request_model(&user_key_id, &mut ctx)
1263        .map_err(|_| MakeKeysError::RequestModelCreation)?;
1264
1265    Ok(MakeUserMasterPasswordRegistrationResponse {
1266        account_cryptographic_state: wrapped_state,
1267        master_password_unlock_data,
1268        master_password_authentication_data,
1269        account_keys_request,
1270        user_key,
1271    })
1272}
1273
1274#[cfg(test)]
1275mod tests {
1276    use std::num::NonZeroU32;
1277
1278    use bitwarden_crypto::{
1279        Decryptable, KeyStore, PrivateKey, PublicKeyEncryptionAlgorithm, RsaKeyPair,
1280        SymmetricKeyAlgorithm,
1281    };
1282
1283    use super::*;
1284    use crate::{
1285        Client,
1286        client::test_accounts::{test_bitwarden_com_account, test_bitwarden_com_account_v2},
1287        key_management::{
1288            KeySlotIds, V2UpgradeToken, state_bridge::test_support::InMemoryStateBridge,
1289        },
1290    };
1291
1292    const TEST_VECTOR_USER_KEY_V2_B64: &str = "pQEEAlACHUUoybNAuJoZzqNMxz2bAzoAARFvBIQDBAUGIFggAvGl4ifaUAomQdCdUPpXLHtypiQxHjZwRHeI83caZM4B";
1293    const TEST_VECTOR_PRIVATE_KEY_V2: &str = "7.g1gdowE6AAERbwMZARwEUAIdRSjJs0C4mhnOo0zHPZuhBVgYthGLGqVLPeidY8mNMxpLJn3fyeSxyaWsWQTR6pxmRV2DyGZXly/0l9KK+Rsfetl9wvYIz0O4/RW3R6wf7eGxo5XmicV3WnFsoAmIQObxkKWShxFyjzg+ocKItQDzG7Gp6+MW4biTrAlfK51ML/ZS+PCjLmgI1QQr4eMHjiwA2TBKtKkxfjoTJkMXECpRVLEXOo8/mbIGYkuabbSA7oU+TJ0yXlfKDtD25gnyO7tjW/0JMFUaoEKRJOuKoXTN4n/ks4Hbxk0X5/DzfG05rxWad2UNBjNg7ehW99WrQ+33ckdQFKMQOri/rt8JzzrF1k11/jMJ+Y2TADKNHr91NalnUX+yqZAAe3sRt5Pv5ZhLIwRMKQi/1NrLcsQPRuUnogVSPOoMnE/eD6F70iU60Z6pvm1iBw2IvELZcrs/oxpO2SeCue08fIZW/jNZokbLnm90tQ7QeZTUpiPALhUgfGOa3J9VOJ7jQGCqDjd9CzV2DCVfhKCapeTbldm+RwEWBz5VvorH5vMx1AzbPRJxdIQuxcg3NqRrXrYC7fyZljWaPB9qP1tztiPtd1PpGEgxLByIfR6fqyZMCvOBsWbd0H6NhF8mNVdDw60+skFRdbRBTSCjCtKZeLVuVFb8ioH45PR5oXjtx4atIDzu6DKm6TTMCbR6DjZuZZ8GbwHxuUD2mDD3pAFhaof9kR3lQdjy7Zb4EzUUYskQxzcLPcqzp9ZgB3Rg91SStBCCMhdQ6AnhTy+VTGt/mY5AbBXNRSL6fI0r+P9K8CcEI4bNZCDkwwQr5v4O4ykSUzIvmVU0zKzDngy9bteIZuhkvGUoZlQ9UATNGPhoLfqq2eSvqEXkCbxTVZ5D+Ww9pHmWeVcvoBhcl5MvicfeQt++dY3tPjIfZq87nlugG4HiNbcv9nbVpgwe3v8cFetWXQgnO4uhx8JHSwGoSuxHFZtl2sdahjTHavRHnYjSABEFrViUKgb12UDD5ow1GAL62wVdSJKRf9HlLbJhN3PBxuh5L/E0wy1wGA9ecXtw/R1ktvXZ7RklGAt1TmNzZv6vI2J/CMXvndOX9rEpjKMbwbIDAjQ9PxiWdcnmc5SowT9f6yfIjbjXnRMWWidPAua7sgrtej4HP4Qjz1fpgLMLCRyF97tbMTmsAI5Cuj98Buh9PwcdyXj5SbVuHdJS1ehv9b5SWPsD4pwOm3+otVNK6FTazhoUl47AZoAoQzXfsXxrzqYzvF0yJkCnk9S1dcij1L569gQ43CJO6o6jIZFJvA4EmZDl95ELu+BC+x37Ip8dq4JLPsANDVSqvXO9tfDUIXEx25AaOYhW2KAUoDve/fbsU8d0UZR1o/w+ZrOQwawCIPeVPtbh7KFRVQi/rPI+Abl6XR6qMJbKPegliYGUuGF2oEMEc6QLTsMRCEPuw0S3kxbNfVPqml8nGhB2r8zUHBY1diJEmipVghnwH74gIKnyJ2C9nKjV8noUfKzqyV8vxUX2G5yXgodx8Jn0cWs3XhWuApFla9z4R28W/4jA1jK2WQMlx+b6xKUWgRk8+fYsc0HSt2fDrQ9pLpnjb8ME59RCxSPV++PThpnR2JtastZBZur2hBIJsGILCAmufUU4VC4gBKPhNfu/OK4Ktgz+uQlUa9fEC/FnkpTRQPxHuQjSQSNrIIyW1bIRBtnwjvvvNoui9FZJ";
1294    #[allow(unused)]
1295    const TEST_VECTOR_PUBLIC_KEY_V2: &str = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz/+1jPJ1HqcaCdKrTPms8XJcvnmd9alI42U2XF/4GMNTM5KF1gI6snhR/23ZLatZRFMHoK8ZCMSpGNkjLadArz52ldceTvBOhQUiWylkZQ4NfNa3xIYJubXOmkeDyfNuyLxVZvcZOko9PdT+Qx2QxDrFi2XNo2I7aVFd19/COIEkex4mJ0eA3MHFpKCdxYbcTAsGID8+kVR9L84S1JptZoG8x+iB/D3/Q4y02UsQYpFTu0vbPY84YmW03ngJdxWzS8X4/UJI/jaEn5rO4xlU5QcL0l4IybP5LRpE9XEeUHATKVOG7eNfpe9zDfKV2qQoofQMH9VvkWO4psaWDjBSdwIDAQAB";
1296    #[allow(unused)]
1297    const TEST_VECTOR_SIGNED_PUBLIC_KEY_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8BoFkBTqNpYWxnb3JpdGhtAG1jb250ZW50Rm9ybWF0AGlwdWJsaWNLZXlZASYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP/7WM8nUepxoJ0qtM+azxcly+eZ31qUjjZTZcX/gYw1MzkoXWAjqyeFH/bdktq1lEUwegrxkIxKkY2SMtp0CvPnaV1x5O8E6FBSJbKWRlDg181rfEhgm5tc6aR4PJ827IvFVm9xk6Sj091P5DHZDEOsWLZc2jYjtpUV3X38I4gSR7HiYnR4DcwcWkoJ3FhtxMCwYgPz6RVH0vzhLUmm1mgbzH6IH8Pf9DjLTZSxBikVO7S9s9jzhiZbTeeAl3FbNLxfj9Qkj+NoSfms7jGVTlBwvSXgjJs/ktGkT1cR5QcBMpU4bt41+l73MN8pXapCih9Awf1W+RY7imxpYOMFJ3AgMBAAFYQMq/hT4wod2w8xyoM7D86ctuLNX4ZRo+jRHf2sZfaO7QsvonG/ZYuNKF5fq8wpxMRjfoMvnY2TTShbgzLrW8BA4=";
1298    const TEST_VECTOR_SIGNING_KEY_V2: &str = "7.g1gcowE6AAERbwMYZQRQAh1FKMmzQLiaGc6jTMc9m6EFWBhYePc2qkCruHAPXgbzXsIP1WVk11ArbLNYUBpifToURlwHKs1je2BwZ1C/5thz4nyNbL0wDaYkRWI9ex1wvB7KhdzC7ltStEd5QttboTSCaXQROSZaGBPNO5+Bu3sTY8F5qK1pBUo6AHNN";
1299    #[allow(unused)]
1300    const TEST_VECTOR_VERIFYING_KEY_V2: &str =
1301        "pgEBAlAmkP0QgfdMVbIujX55W/yNAycEgQIgBiFYIEM6JxBmjWQTruAm3s6BTaJy1q6BzQetMBacNeRJ0kxR";
1302    const TEST_VECTOR_SECURITY_STATE_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8CoFgkomhlbnRpdHlJZFBHOOw2BI9OQoNq+Vl1xZZKZ3ZlcnNpb24CWEAlchbJR0vmRfShG8On7Q2gknjkw4Dd6MYBLiH4u+/CmfQdmjNZdf6kozgW/6NXyKVNu8dAsKsin+xxXkDyVZoG";
1303
1304    const TEST_USER_EMAIL: &str = "[email protected]";
1305    const TEST_USER_PASSWORD: &str = "asdfasdfasdf";
1306    const TEST_ACCOUNT_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
1307    const TEST_ACCOUNT_PRIVATE_KEY: &str = "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=";
1308
1309    async fn init_v2_account_with_master_password_and_upgrade_token(
1310        client: &Client,
1311        user_id: UserId,
1312        upgrade_token: V2UpgradeToken,
1313    ) {
1314        initialize_user_crypto(
1315            client,
1316            InitUserCryptoRequest {
1317                user_id: Some(user_id),
1318                kdf_params: Kdf::PBKDF2 {
1319                    iterations: 600_000.try_into().unwrap(),
1320                },
1321                email: TEST_USER_EMAIL.into(),
1322                account_cryptographic_state: WrappedAccountCryptographicState::V2 {
1323                    private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
1324                    signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
1325                    security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
1326                    signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
1327                },
1328                method: InitUserCryptoMethod::MasterPasswordUnlock {
1329                    password: TEST_USER_PASSWORD.into(),
1330                    master_password_unlock: MasterPasswordUnlockData {
1331                        kdf: Kdf::PBKDF2 {
1332                            iterations: 600_000.try_into().unwrap(),
1333                        },
1334                        master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
1335                        salt: TEST_USER_EMAIL.to_string(),
1336                    },
1337                },
1338                upgrade_token: Some(upgrade_token),
1339            },
1340        )
1341        .await
1342        .unwrap();
1343    }
1344
1345    #[tokio::test]
1346    async fn test_update_kdf() {
1347        let client = Client::new_test(None);
1348
1349        let priv_key: EncString = "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw=".parse().unwrap();
1350
1351        let kdf = Kdf::PBKDF2 {
1352            iterations: 100_000.try_into().unwrap(),
1353        };
1354
1355        initialize_user_crypto(
1356            &client,
1357            InitUserCryptoRequest {
1358                user_id: Some(UserId::new_v4()),
1359                kdf_params: kdf.clone(),
1360                email: "[email protected]".into(),
1361                account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1362                method: InitUserCryptoMethod::MasterPasswordUnlock {
1363                    password: "asdfasdfasdf".into(),
1364                    master_password_unlock: MasterPasswordUnlockData {
1365                        kdf: kdf.clone(),
1366                        master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1367                        salt: "[email protected]".to_string(),
1368                    },
1369                },
1370                upgrade_token: None,
1371            },
1372        )
1373            .await
1374            .unwrap();
1375
1376        let new_kdf = Kdf::PBKDF2 {
1377            iterations: 600_000.try_into().unwrap(),
1378        };
1379        let new_kdf_response = make_update_kdf(&client, "123412341234", &new_kdf)
1380            .await
1381            .unwrap();
1382
1383        let client2 = Client::new_test(None);
1384
1385        initialize_user_crypto(
1386            &client2,
1387            InitUserCryptoRequest {
1388                user_id: Some(UserId::new_v4()),
1389                kdf_params: new_kdf.clone(),
1390                email: "[email protected]".into(),
1391                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1392                    private_key: priv_key.to_owned(),
1393                },
1394                method: InitUserCryptoMethod::MasterPasswordUnlock {
1395                    password: "123412341234".to_string(),
1396                    master_password_unlock: MasterPasswordUnlockData {
1397                        kdf: new_kdf.clone(),
1398                        master_key_wrapped_user_key: new_kdf_response
1399                            .master_password_unlock_data
1400                            .master_key_wrapped_user_key,
1401                        salt: "[email protected]".to_string(),
1402                    },
1403                },
1404                upgrade_token: None,
1405            },
1406        )
1407        .await
1408        .unwrap();
1409
1410        let new_hash = client2
1411            .kdf()
1412            .hash_password(
1413                "[email protected]".into(),
1414                "123412341234".into(),
1415                new_kdf.clone(),
1416                bitwarden_crypto::HashPurpose::ServerAuthorization,
1417            )
1418            .await
1419            .unwrap();
1420
1421        assert_eq!(
1422            new_hash,
1423            new_kdf_response
1424                .master_password_authentication_data
1425                .master_password_authentication_hash
1426        );
1427
1428        let client_key = {
1429            let key_store = client.internal.get_key_store();
1430            let ctx = key_store.context();
1431            #[allow(deprecated)]
1432            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1433                .unwrap()
1434                .to_base64()
1435        };
1436
1437        let client2_key = {
1438            let key_store = client2.internal.get_key_store();
1439            let ctx = key_store.context();
1440            #[allow(deprecated)]
1441            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1442                .unwrap()
1443                .to_base64()
1444        };
1445
1446        assert_eq!(client_key, client2_key);
1447    }
1448
1449    #[tokio::test]
1450    async fn test_update_password() {
1451        let client = Client::new_test(None);
1452
1453        let priv_key: EncString = "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw=".parse().unwrap();
1454
1455        let kdf = Kdf::PBKDF2 {
1456            iterations: 100_000.try_into().unwrap(),
1457        };
1458
1459        initialize_user_crypto(
1460            &client,
1461            InitUserCryptoRequest {
1462                user_id: Some(UserId::new_v4()),
1463                kdf_params: kdf.clone(),
1464                email: "[email protected]".into(),
1465                account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1466                method: InitUserCryptoMethod::MasterPasswordUnlock {
1467                    password: "asdfasdfasdf".to_string(),
1468                    master_password_unlock: MasterPasswordUnlockData {
1469                        kdf: kdf.clone(),
1470                        master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1471                        salt: "[email protected]".to_string(),
1472                    },
1473                },
1474                upgrade_token: None,
1475            },
1476        )
1477            .await
1478            .unwrap();
1479
1480        let new_password_response = make_update_password(&client, "123412341234".into())
1481            .await
1482            .unwrap();
1483
1484        let client2 = Client::new_test(None);
1485
1486        initialize_user_crypto(
1487            &client2,
1488            InitUserCryptoRequest {
1489                user_id: Some(UserId::new_v4()),
1490                kdf_params: kdf.clone(),
1491                email: "[email protected]".into(),
1492                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1493                    private_key: priv_key.to_owned(),
1494                },
1495                method: InitUserCryptoMethod::MasterPasswordUnlock {
1496                    password: "123412341234".into(),
1497                    master_password_unlock: MasterPasswordUnlockData {
1498                        kdf: kdf.clone(),
1499                        master_key_wrapped_user_key: new_password_response.new_key,
1500                        salt: "[email protected]".to_string(),
1501                    },
1502                },
1503                upgrade_token: None,
1504            },
1505        )
1506        .await
1507        .unwrap();
1508
1509        let new_hash = client2
1510            .kdf()
1511            .hash_password(
1512                "[email protected]".into(),
1513                "123412341234".into(),
1514                kdf.clone(),
1515                bitwarden_crypto::HashPurpose::ServerAuthorization,
1516            )
1517            .await
1518            .unwrap();
1519
1520        assert_eq!(new_hash, new_password_response.password_hash);
1521
1522        let client_key = {
1523            let key_store = client.internal.get_key_store();
1524            let ctx = key_store.context();
1525            #[allow(deprecated)]
1526            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1527                .unwrap()
1528                .to_base64()
1529        };
1530
1531        let client2_key = {
1532            let key_store = client2.internal.get_key_store();
1533            let ctx = key_store.context();
1534            #[allow(deprecated)]
1535            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1536                .unwrap()
1537                .to_base64()
1538        };
1539
1540        assert_eq!(client_key, client2_key);
1541    }
1542
1543    #[tokio::test]
1544    async fn test_initialize_user_crypto_pin() {
1545        let client = Client::new_test(None);
1546
1547        let priv_key: EncString = "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw=".parse().unwrap();
1548
1549        initialize_user_crypto(
1550            &client,
1551            InitUserCryptoRequest {
1552                user_id: Some(UserId::new_v4()),
1553                kdf_params: Kdf::PBKDF2 {
1554                    iterations: 100_000.try_into().unwrap(),
1555                },
1556                email: "[email protected]".into(),
1557                account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1558                method: InitUserCryptoMethod::MasterPasswordUnlock {
1559                    password: "asdfasdfasdf".into(),
1560                    master_password_unlock: MasterPasswordUnlockData {
1561                        kdf: Kdf::PBKDF2 {
1562                            iterations: 100_000.try_into().unwrap(),
1563                        },
1564                        master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1565                        salt: "[email protected]".to_string(),
1566                    },
1567                },
1568                upgrade_token: None,
1569            },
1570        )
1571            .await
1572            .unwrap();
1573
1574        let pin_key = derive_pin_key(&client, "1234".into()).await.unwrap();
1575
1576        // Verify we can unlock with the pin
1577        let client2 = Client::new_test(None);
1578        initialize_user_crypto(
1579            &client2,
1580            InitUserCryptoRequest {
1581                user_id: Some(UserId::new_v4()),
1582                kdf_params: Kdf::PBKDF2 {
1583                    iterations: 100_000.try_into().unwrap(),
1584                },
1585                email: "[email protected]".into(),
1586                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1587                    private_key: priv_key.to_owned(),
1588                },
1589                method: InitUserCryptoMethod::Pin {
1590                    pin: "1234".into(),
1591                    pin_protected_user_key: pin_key.pin_protected_user_key,
1592                },
1593                upgrade_token: None,
1594            },
1595        )
1596        .await
1597        .unwrap();
1598
1599        let client_key = {
1600            let key_store = client.internal.get_key_store();
1601            let ctx = key_store.context();
1602            #[allow(deprecated)]
1603            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1604                .unwrap()
1605                .to_base64()
1606        };
1607
1608        let client2_key = {
1609            let key_store = client2.internal.get_key_store();
1610            let ctx = key_store.context();
1611            #[allow(deprecated)]
1612            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1613                .unwrap()
1614                .to_base64()
1615        };
1616
1617        assert_eq!(client_key, client2_key);
1618
1619        // Verify we can derive the pin protected user key from the encrypted pin
1620        let pin_protected_user_key = derive_pin_user_key(&client, pin_key.encrypted_pin)
1621            .await
1622            .unwrap();
1623
1624        let client3 = Client::new_test(None);
1625
1626        initialize_user_crypto(
1627            &client3,
1628            InitUserCryptoRequest {
1629                user_id: Some(UserId::new_v4()),
1630                kdf_params: Kdf::PBKDF2 {
1631                    iterations: 100_000.try_into().unwrap(),
1632                },
1633                email: "[email protected]".into(),
1634                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1635                    private_key: priv_key.to_owned(),
1636                },
1637                method: InitUserCryptoMethod::Pin {
1638                    pin: "1234".into(),
1639                    pin_protected_user_key,
1640                },
1641                upgrade_token: None,
1642            },
1643        )
1644        .await
1645        .unwrap();
1646
1647        let client_key = {
1648            let key_store = client.internal.get_key_store();
1649            let ctx = key_store.context();
1650            #[allow(deprecated)]
1651            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1652                .unwrap()
1653                .to_base64()
1654        };
1655
1656        let client3_key = {
1657            let key_store = client3.internal.get_key_store();
1658            let ctx = key_store.context();
1659            #[allow(deprecated)]
1660            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1661                .unwrap()
1662                .to_base64()
1663        };
1664
1665        assert_eq!(client_key, client3_key);
1666    }
1667
1668    #[tokio::test]
1669    async fn test_initialize_user_crypto_pin_envelope() {
1670        let user_key = "5yKAZ4TSSEGje54MV5lc5ty6crkqUz4xvl+8Dm/piNLKf6OgRi2H0uzttNTXl9z6ILhkmuIXzGpAVc2YdorHgQ==";
1671        let test_pin = "1234";
1672
1673        let client1 = Client::new_test(None);
1674        initialize_user_crypto(
1675            &client1,
1676            InitUserCryptoRequest {
1677                user_id: Some(UserId::new_v4()),
1678                kdf_params: Kdf::PBKDF2 {
1679                    iterations: 100_000.try_into().unwrap(),
1680                },
1681                email: "[email protected]".into(),
1682                account_cryptographic_state: {
1683                    let store: KeyStore<KeySlotIds> = KeyStore::default();
1684                    let mut ctx = store.context_mut();
1685                    WrappedAccountCryptographicState::make_v1(&mut ctx)
1686                        .unwrap()
1687                        .1
1688                },
1689                method: InitUserCryptoMethod::DecryptedKey {
1690                    decrypted_user_key: user_key.to_string(),
1691                },
1692                upgrade_token: None,
1693            },
1694        )
1695        .await
1696        .unwrap();
1697
1698        let enroll_response = client1.crypto().enroll_pin(test_pin.to_string()).unwrap();
1699
1700        let client2 = Client::new_test(None);
1701        initialize_user_crypto(
1702            &client2,
1703            InitUserCryptoRequest {
1704                user_id: Some(UserId::new_v4()),
1705                // NOTE: THIS CHANGES KDF SETTINGS. We ensure in this test that even with different
1706                // KDF settings the pin can unlock the user key.
1707                kdf_params: Kdf::PBKDF2 {
1708                    iterations: 600_000.try_into().unwrap(),
1709                },
1710                email: "[email protected]".into(),
1711                account_cryptographic_state: {
1712                    let store: KeyStore<KeySlotIds> = KeyStore::default();
1713                    let mut ctx = store.context_mut();
1714                    WrappedAccountCryptographicState::make_v1(&mut ctx)
1715                        .unwrap()
1716                        .1
1717                },
1718                method: InitUserCryptoMethod::PinEnvelope {
1719                    pin: test_pin.to_string(),
1720                    pin_protected_user_key_envelope: enroll_response
1721                        .pin_protected_user_key_envelope,
1722                },
1723                upgrade_token: None,
1724            },
1725        )
1726        .await
1727        .unwrap();
1728    }
1729
1730    #[tokio::test]
1731    async fn test_initialize_user_crypto_pin_state() {
1732        use crate::key_management::pin_lock_system::PinLockType;
1733
1734        let client1 = Client::init_test_account(test_bitwarden_com_account()).await;
1735        client1
1736            .km_state_bridge()
1737            .register_bridge(Box::new(InMemoryStateBridge::default()));
1738
1739        PinLockSystem::with_client(&client1)
1740            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
1741            .await
1742            .expect("set_pin succeeds");
1743
1744        let persistent_envelope = client1
1745            .km_state_bridge()
1746            .get_persistent_pin_envelope()
1747            .await
1748            .expect("persistent pin envelope present after BFU set_pin");
1749        let encrypted_pin = client1
1750            .km_state_bridge()
1751            .get_encrypted_pin()
1752            .await
1753            .expect("encrypted pin present after set_pin");
1754
1755        // Fresh client simulating an app restart with the same persisted PIN state.
1756        let client2 = Client::init_test_account(test_bitwarden_com_account()).await;
1757        client2
1758            .km_state_bridge()
1759            .register_bridge(Box::new(InMemoryStateBridge::default()));
1760        client2
1761            .km_state_bridge()
1762            .set_persistent_pin_envelope(&persistent_envelope)
1763            .await;
1764        client2
1765            .km_state_bridge()
1766            .set_encrypted_pin(&encrypted_pin)
1767            .await;
1768
1769        let client1_key = {
1770            let key_store = client1.internal.get_key_store();
1771            let ctx = key_store.context();
1772            #[allow(deprecated)]
1773            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1774                .unwrap()
1775                .to_owned()
1776        };
1777
1778        let client2_key = {
1779            let key_store = client2.internal.get_key_store();
1780            let ctx = key_store.context();
1781            #[allow(deprecated)]
1782            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1783                .unwrap()
1784                .to_owned()
1785        };
1786
1787        assert_eq!(client1_key, client2_key);
1788    }
1789
1790    #[test]
1791    fn test_enroll_admin_password_reset() {
1792        let client = Client::new(None);
1793
1794        let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap();
1795        let private_key = "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=".parse().unwrap();
1796        client
1797            .internal
1798            .initialize_user_crypto_master_password_unlock(
1799                "asdfasdfasdf".to_string(),
1800                MasterPasswordUnlockData {
1801                    kdf: Kdf::PBKDF2 {
1802                        iterations: NonZeroU32::new(600_000).unwrap(),
1803                    },
1804                    master_key_wrapped_user_key: user_key,
1805                    salt: "[email protected]".to_string(),
1806                },
1807                WrappedAccountCryptographicState::V1 { private_key },
1808                &None,
1809            )
1810            .unwrap();
1811
1812        let public_key: B64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsy7RFHcX3C8Q4/OMmhhbFReYWfB45W9PDTEA8tUZwZmtOiN2RErIS2M1c+K/4HoDJ/TjpbX1f2MZcr4nWvKFuqnZXyewFc+jmvKVewYi+NAu2++vqKq2kKcmMNhwoQDQdQIVy/Uqlp4Cpi2cIwO6ogq5nHNJGR3jm+CpyrafYlbz1bPvL3hbyoGDuG2tgADhyhXUdFuef2oF3wMvn1lAJAvJnPYpMiXUFmj1ejmbwtlxZDrHgUJvUcp7nYdwUKaFoi+sOttHn3u7eZPtNvxMjhSS/X/1xBIzP/mKNLdywH5LoRxniokUk+fV3PYUxJsiU3lV0Trc/tH46jqd8ZGjmwIDAQAB".parse().unwrap();
1813
1814        let encrypted = enroll_admin_password_reset(&client, public_key).unwrap();
1815
1816        let private_key: B64 = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzLtEUdxfcLxDj84yaGFsVF5hZ8Hjlb08NMQDy1RnBma06I3ZESshLYzVz4r/gegMn9OOltfV/Yxlyvida8oW6qdlfJ7AVz6Oa8pV7BiL40C7b76+oqraQpyYw2HChANB1AhXL9SqWngKmLZwjA7qiCrmcc0kZHeOb4KnKtp9iVvPVs+8veFvKgYO4ba2AAOHKFdR0W55/agXfAy+fWUAkC8mc9ikyJdQWaPV6OZvC2XFkOseBQm9Rynudh3BQpoWiL6w620efe7t5k+02/EyOFJL9f/XEEjM/+Yo0t3LAfkuhHGeKiRST59Xc9hTEmyJTeVXROtz+0fjqOp3xkaObAgMBAAECggEACs4xhnO0HaZhh1/iH7zORMIRXKeyxP2LQiTR8xwN5JJ9wRWmGAR9VasS7EZFTDidIGVME2u/h4s5EqXnhxfO+0gGksVvgNXJ/qw87E8K2216g6ZNo6vSGA7H1GH2voWwejJ4/k/cJug6dz2S402rRAKh2Wong1arYHSkVlQp3diiMa5FHAOSE+Cy09O2ZsaF9IXQYUtlW6AVXFrBEPYH2kvkaPXchh8VETMijo6tbvoKLnUHe+wTaDMls7hy8exjtVyI59r3DNzjy1lNGaGb5QSnFMXR+eHhPZc844Wv02MxC15zKABADrl58gpJyjTl6XpDdHCYGsmGpVGH3X9TQQKBgQDz/9beFjzq59ve6rGwn+EtnQfSsyYT+jr7GN8lNEXb3YOFXBgPhfFIcHRh2R00Vm9w2ApfAx2cd8xm2I6HuvQ1Os7g26LWazvuWY0Qzb+KaCLQTEGH1RnTq6CCG+BTRq/a3J8M4t38GV5TWlzv8wr9U4dl6FR4efjb65HXs1GQ4QKBgQC7/uHfrOTEHrLeIeqEuSl0vWNqEotFKdKLV6xpOvNuxDGbgW4/r/zaxDqt0YBOXmRbQYSEhmO3oy9J6XfE1SUln0gbavZeW0HESCAmUIC88bDnspUwS9RxauqT5aF8ODKN/bNCWCnBM1xyonPOs1oT1nyparJVdQoG//Y7vkB3+wKBgBqLqPq8fKAp3XfhHLfUjREDVoiLyQa/YI9U42IOz9LdxKNLo6p8rgVthpvmnRDGnpUuS+KOWjhdqDVANjF6G3t3DG7WNl8Rh5Gk2H4NhFswfSkgQrjebFLlBy9gjQVCWXt8KSmjvPbiY6q52Aaa8IUjA0YJAregvXxfopxO+/7BAoGARicvEtDp7WWnSc1OPoj6N14VIxgYcI7SyrzE0d/1x3ffKzB5e7qomNpxKzvqrVP8DzG7ydh8jaKPmv1MfF8tpYRy3AhmN3/GYwCnPqT75YYrhcrWcVdax5gmQVqHkFtIQkRSCIftzPLlpMGKha/YBV8c1fvC4LD0NPh/Ynv0gtECgYEAyOZg95/kte0jpgUEgwuMrzkhY/AaUJULFuR5MkyvReEbtSBQwV5tx60+T95PHNiFooWWVXiLMsAgyI2IbkxVR1Pzdri3gWK5CTfqb7kLuaj/B7SGvBa2Sxo478KS5K8tBBBWkITqo+wLC0mn3uZi1dyMWO1zopTA+KtEGF2dtGQ=".parse().unwrap();
1817
1818        let private_key = Pkcs8PrivateKeyBytes::from(private_key.as_bytes());
1819        let private_key = PrivateKey::from_der(&private_key).unwrap();
1820        #[expect(deprecated)]
1821        let decrypted: SymmetricCryptoKey =
1822            encrypted.decapsulate_key_unsigned(&private_key).unwrap();
1823
1824        let key_store = client.internal.get_key_store();
1825        let ctx = key_store.context();
1826        #[allow(deprecated)]
1827        let expected = ctx
1828            .dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1829            .unwrap();
1830
1831        assert_eq!(decrypted, *expected);
1832    }
1833
1834    #[test]
1835    fn test_derive_key_connector() {
1836        let request = DeriveKeyConnectorRequest {
1837            password: "asdfasdfasdf".to_string(),
1838            email: "[email protected]".to_string(),
1839            kdf: Kdf::PBKDF2 {
1840                iterations: NonZeroU32::new(600_000).unwrap(),
1841            },
1842            user_key_encrypted: "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(),
1843        };
1844
1845        let result = derive_key_connector(request).unwrap();
1846
1847        assert_eq!(
1848            result.to_string(),
1849            "ySXq1RVLKEaV1eoQE/ui9aFKIvXTl9PAXwp1MljfF50="
1850        );
1851    }
1852
1853    fn setup_asymmetric_keys_test() -> (UserKey, RsaKeyPair) {
1854        let master_key = MasterKey::derive(
1855            "asdfasdfasdf",
1856            "[email protected]",
1857            &Kdf::PBKDF2 {
1858                iterations: NonZeroU32::new(600_000).unwrap(),
1859            },
1860        )
1861        .unwrap();
1862        let user_key = (master_key.make_user_key().unwrap()).0;
1863        let key_pair = user_key.make_key_pair().unwrap();
1864
1865        (user_key, key_pair)
1866    }
1867
1868    #[test]
1869    fn test_make_key_pair() {
1870        let (user_key, _) = setup_asymmetric_keys_test();
1871
1872        let response = make_key_pair(user_key.0.to_base64()).unwrap();
1873
1874        assert!(!response.user_public_key.to_string().is_empty());
1875        let encrypted_private_key = response.user_key_encrypted_private_key;
1876        let private_key: Vec<u8> = encrypted_private_key.decrypt_with_key(&user_key.0).unwrap();
1877        assert!(!private_key.is_empty());
1878    }
1879
1880    #[test]
1881    fn test_verify_asymmetric_keys_success() {
1882        let (user_key, key_pair) = setup_asymmetric_keys_test();
1883
1884        let request = VerifyAsymmetricKeysRequest {
1885            user_key: user_key.0.to_base64(),
1886            user_public_key: key_pair.public,
1887            user_key_encrypted_private_key: key_pair.private,
1888        };
1889        let response = verify_asymmetric_keys(request).unwrap();
1890
1891        assert!(response.private_key_decryptable);
1892        assert!(response.valid_private_key);
1893    }
1894
1895    #[test]
1896    fn test_verify_asymmetric_keys_decrypt_failed() {
1897        let (user_key, key_pair) = setup_asymmetric_keys_test();
1898        let undecryptable_private_key = "2.cqD39M4erPZ3tWaz2Fng9w==|+Bsp/xvM30oo+HThKN12qirK0A63EjMadcwethCX7kEgfL5nEXgAFsSgRBMpByc1djgpGDMXzUTLOE+FejXRsrEHH/ICZ7jPMgSR+lV64Mlvw3fgvDPQdJ6w3MCmjPueGQtrlPj1K78BkRomN3vQwwRBFUIJhLAnLshTOIFrSghoyG78na7McqVMMD0gmC0zmRaSs2YWu/46ES+2Rp8V5OC4qdeeoJM9MQfaOtmaqv7NRVDeDM3DwoyTJAOcon8eovMKE4jbFPUboiXjNQBkBgjvLhco3lVJnFcQuYgmjqrwuUQRsfAtZjxFXg/RQSH2D+SI5uRaTNQwkL4iJqIw7BIKtI0gxDz6eCVdq/+DLhpImgCV/aaIhF/jkpGqLCceFsYMbuqdULMM1VYKgV+IAuyC65R+wxOaKS+1IevvPnNp7tgKAvT5+shFg8piusj+rQ49daX2SmV2OImwdWMmmX93bcVV0xJ/WYB1yrqmyRUcTwyvX3RQF25P5okIIzFasRp8jXFZe8C6f93yzkn1TPQbp95zF4OsWjfPFVH4hzca07ACt2HjbAB75JakWbFA5MbCF8aOIwIfeLVhVlquQXCldOHCsl22U/f3HTGLB9OS8F83CDAy7qZqpKha9Im8RUhHoyf+lXrky0gyd6un7Ky8NSkVOGd8CEG7bvZfutxv/qtAjEM9/lV78fh8TQIy9GNgioMzplpuzPIJOgMaY/ZFZj6a8H9OMPneN5Je0H/DwHEglSyWy7CMgwcbQgXYGXc8rXTTxL71GUAFHzDr4bAJvf40YnjndoL9tf+oBw8vVNUccoD4cjyOT5w8h7M3Liaxk9/0O8JR98PKxxpv1Xw6XjFCSEHeG2y9FgDUASFR4ZwG1qQBiiLMnJ7e9kvxsdnmasBux9H0tOdhDhAM16Afk3NPPKA8eztJVHJBAfQiaNiUA4LIJ48d8EpUAe2Tvz0WW/gQThplUINDTpvPf+FojLwc5lFwNIPb4CVN1Ui8jOJI5nsOw4BSWJvLzJLxawHxX/sBuK96iXza+4aMH+FqYKt/twpTJtiVXo26sPtHe6xXtp7uO4b+bL9yYUcaAci69L0W8aNdu8iF0lVX6kFn2lOL8dBLRleGvixX9gYEVEsiI7BQBjxEBHW/YMr5F4M4smqCpleZIAxkse1r2fQ33BSOJVQKInt4zzgdKwrxDzuVR7RyiIUuNXHsprKtRHNJrSc4x5kWFUeivahed2hON+Ir/ZvrxYN6nJJPeYYH4uEm1Nn4osUzzfWILlqpmDPK1yYy365T38W8wT0cbdcJrI87ycS37HeB8bzpFJZSY/Dzv48Yy19mDZJHLJLCRqyxNeIlBPsVC8fvxQhzr+ZyS3Wi8Dsa2Sgjt/wd0xPULLCJlb37s+1aWgYYylr9QR1uhXheYfkXFED+saGWwY1jlYL5e2Oo9n3sviBYwJxIZ+RTKFgwlXV5S+Jx/MbDpgnVHP1KaoU6vvzdWYwMChdHV/6PhZVbeT2txq7Qt+zQN59IGrOWf6vlMkHxfUzMTD58CE+xAaz/D05ljHMesLj9hb3MSrymw0PcwoFGWUMIzIQE73pUVYNE7fVHa8HqUOdoxZ5dRZqXRVox1xd9siIPE3e6CuVQIMabTp1YLno=|Y38qtTuCwNLDqFnzJ3Cgbjm1SE15OnhDm9iAMABaQBA=".parse().unwrap();
1899
1900        let request = VerifyAsymmetricKeysRequest {
1901            user_key: user_key.0.to_base64(),
1902            user_public_key: key_pair.public,
1903            user_key_encrypted_private_key: undecryptable_private_key,
1904        };
1905        let response = verify_asymmetric_keys(request).unwrap();
1906
1907        assert!(!response.private_key_decryptable);
1908        assert!(!response.valid_private_key);
1909    }
1910
1911    #[test]
1912    fn test_verify_asymmetric_keys_parse_failed() {
1913        let (user_key, key_pair) = setup_asymmetric_keys_test();
1914
1915        let invalid_private_key = "bad_key".to_string().encrypt_with_key(&user_key.0).unwrap();
1916
1917        let request = VerifyAsymmetricKeysRequest {
1918            user_key: user_key.0.to_base64(),
1919            user_public_key: key_pair.public,
1920            user_key_encrypted_private_key: invalid_private_key,
1921        };
1922        let response = verify_asymmetric_keys(request).unwrap();
1923
1924        assert!(response.private_key_decryptable);
1925        assert!(!response.valid_private_key);
1926    }
1927
1928    #[test]
1929    fn test_verify_asymmetric_keys_key_mismatch() {
1930        let (user_key, key_pair) = setup_asymmetric_keys_test();
1931        let new_key_pair = user_key.make_key_pair().unwrap();
1932
1933        let request = VerifyAsymmetricKeysRequest {
1934            user_key: user_key.0.to_base64(),
1935            user_public_key: key_pair.public,
1936            user_key_encrypted_private_key: new_key_pair.private,
1937        };
1938        let response = verify_asymmetric_keys(request).unwrap();
1939
1940        assert!(response.private_key_decryptable);
1941        assert!(!response.valid_private_key);
1942    }
1943
1944    #[tokio::test]
1945    async fn test_make_v2_keys_for_v1_user() {
1946        let client = Client::new_test(None);
1947
1948        let priv_key: EncString = "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw=".parse().unwrap();
1949        let encrypted_userkey: EncString = "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap();
1950
1951        initialize_user_crypto(
1952            &client,
1953            InitUserCryptoRequest {
1954                user_id: Some(UserId::new_v4()),
1955                kdf_params: Kdf::PBKDF2 {
1956                    iterations: 100_000.try_into().unwrap(),
1957                },
1958                email: "[email protected]".into(),
1959                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1960                    private_key: priv_key.to_owned(),
1961                },
1962                method: InitUserCryptoMethod::MasterPasswordUnlock {
1963                    password: "asdfasdfasdf".into(),
1964                    master_password_unlock: MasterPasswordUnlockData {
1965                        kdf: Kdf::PBKDF2 {
1966                            iterations: 100_000.try_into().unwrap(),
1967                        },
1968                        master_key_wrapped_user_key: encrypted_userkey.clone(),
1969                        salt: "[email protected]".into(),
1970                    },
1971                },
1972                upgrade_token: None,
1973            },
1974        )
1975        .await
1976        .unwrap();
1977
1978        let master_key = MasterKey::derive(
1979            "asdfasdfasdf",
1980            "[email protected]",
1981            &Kdf::PBKDF2 {
1982                iterations: NonZeroU32::new(100_000).unwrap(),
1983            },
1984        )
1985        .unwrap();
1986        #[expect(deprecated)]
1987        let enrollment_response = make_v2_keys_for_v1_user(&client).unwrap();
1988        let encrypted_userkey_v2 = master_key
1989            .encrypt_user_key(
1990                &SymmetricCryptoKey::try_from(enrollment_response.clone().user_key).unwrap(),
1991            )
1992            .unwrap();
1993
1994        let client2 = Client::new_test(None);
1995
1996        initialize_user_crypto(
1997            &client2,
1998            InitUserCryptoRequest {
1999                user_id: Some(UserId::new_v4()),
2000                kdf_params: Kdf::PBKDF2 {
2001                    iterations: 100_000.try_into().unwrap(),
2002                },
2003                email: "[email protected]".into(),
2004                account_cryptographic_state: WrappedAccountCryptographicState::V2 {
2005                    private_key: enrollment_response.private_key,
2006                    signing_key: enrollment_response.signing_key,
2007                    security_state: enrollment_response.security_state,
2008                    signed_public_key: Some(enrollment_response.signed_public_key),
2009                },
2010                method: InitUserCryptoMethod::MasterPasswordUnlock {
2011                    password: "asdfasdfasdf".into(),
2012                    master_password_unlock: MasterPasswordUnlockData {
2013                        kdf: Kdf::PBKDF2 {
2014                            iterations: 100_000.try_into().unwrap(),
2015                        },
2016                        master_key_wrapped_user_key: encrypted_userkey_v2,
2017                        salt: "[email protected]".to_string(),
2018                    },
2019                },
2020                upgrade_token: None,
2021            },
2022        )
2023        .await
2024        .unwrap();
2025    }
2026
2027    #[tokio::test]
2028    async fn test_make_v2_keys_for_v1_user_with_v2_user_fails() {
2029        let client = Client::new_test(None);
2030
2031        initialize_user_crypto(
2032            &client,
2033            InitUserCryptoRequest {
2034                user_id: Some(UserId::new_v4()),
2035                kdf_params: Kdf::PBKDF2 {
2036                    iterations: 100_000.try_into().unwrap(),
2037                },
2038                email: "[email protected]".into(),
2039                account_cryptographic_state: WrappedAccountCryptographicState::V2 {
2040                    private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
2041                    signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
2042                    security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
2043                    signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
2044                },
2045                method: InitUserCryptoMethod::DecryptedKey {
2046                    decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
2047                },
2048                upgrade_token: None,
2049            },
2050        )
2051        .await
2052        .unwrap();
2053
2054        #[expect(deprecated)]
2055        let result = make_v2_keys_for_v1_user(&client);
2056        assert!(matches!(
2057            result,
2058            Err(StatefulCryptoError::WrongAccountCryptoVersion {
2059                expected: _,
2060                got: _
2061            })
2062        ));
2063    }
2064
2065    #[test]
2066    fn test_get_v2_rotated_account_keys_non_v2_user() {
2067        let client = Client::new(None);
2068        let mut ctx = client.internal.get_key_store().context_mut();
2069        let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
2070        ctx.persist_symmetric_key(local_key_id, SymmetricKeySlotId::User)
2071            .unwrap();
2072        drop(ctx);
2073
2074        #[expect(deprecated)]
2075        let result = get_v2_rotated_account_keys(&client);
2076        assert!(matches!(
2077            result,
2078            Err(StatefulCryptoError::WrongAccountCryptoVersion {
2079                expected: _,
2080                got: _
2081            })
2082        ));
2083    }
2084
2085    #[tokio::test]
2086    async fn test_get_v2_rotated_account_keys() {
2087        let client = Client::new_test(None);
2088
2089        initialize_user_crypto(
2090            &client,
2091            InitUserCryptoRequest {
2092                user_id: Some(UserId::new_v4()),
2093                kdf_params: Kdf::PBKDF2 {
2094                    iterations: 100_000.try_into().unwrap(),
2095                },
2096                email: "[email protected]".into(),
2097                account_cryptographic_state: WrappedAccountCryptographicState::V2 {
2098                    private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
2099                    signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
2100                    security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
2101                    signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
2102                },
2103                method: InitUserCryptoMethod::DecryptedKey {
2104                    decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
2105                },
2106                upgrade_token: None,
2107            },
2108        )
2109        .await
2110        .unwrap();
2111
2112        #[expect(deprecated)]
2113        let result = get_v2_rotated_account_keys(&client);
2114        assert!(result.is_ok());
2115    }
2116
2117    #[tokio::test]
2118    async fn test_initialize_user_crypto_master_password_unlock() {
2119        let client = Client::new_test(None);
2120
2121        initialize_user_crypto(
2122            &client,
2123            InitUserCryptoRequest {
2124                user_id: Some(UserId::new_v4()),
2125                kdf_params: Kdf::PBKDF2 {
2126                    iterations: 600_000.try_into().unwrap(),
2127                },
2128                email: TEST_USER_EMAIL.to_string(),
2129                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
2130                    private_key: TEST_ACCOUNT_PRIVATE_KEY.parse().unwrap(),
2131                },
2132                method: InitUserCryptoMethod::MasterPasswordUnlock {
2133                    password: TEST_USER_PASSWORD.to_string(),
2134                    master_password_unlock: MasterPasswordUnlockData {
2135                        kdf: Kdf::PBKDF2 {
2136                            iterations: 600_000.try_into().unwrap(),
2137                        },
2138                        master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
2139                        salt: TEST_USER_EMAIL.to_string(),
2140                    },
2141                },
2142                upgrade_token: None,
2143            },
2144        )
2145        .await
2146        .unwrap();
2147
2148        let key_store = client.internal.get_key_store();
2149        {
2150            let context = key_store.context();
2151            assert!(context.has_symmetric_key(SymmetricKeySlotId::User));
2152            assert!(context.has_private_key(PrivateKeySlotId::UserPrivateKey));
2153        }
2154        let login_method = client.internal.get_login_method().await.unwrap();
2155        if let UserLoginMethod::Username {
2156            email,
2157            kdf,
2158            client_id,
2159            ..
2160        } = login_method
2161        {
2162            assert_eq!(&email, TEST_USER_EMAIL);
2163            assert_eq!(
2164                kdf,
2165                Kdf::PBKDF2 {
2166                    iterations: 600_000.try_into().unwrap(),
2167                }
2168            );
2169            assert_eq!(&client_id, "");
2170        } else {
2171            panic!("Expected username login method");
2172        }
2173    }
2174
2175    #[tokio::test]
2176    async fn test_make_user_tde_registration() {
2177        let user_id = UserId::new_v4();
2178        let email = "[email protected]";
2179        let kdf = Kdf::PBKDF2 {
2180            iterations: NonZeroU32::new(600_000).expect("valid iteration count"),
2181        };
2182
2183        // Generate a mock organization public key for TDE enrollment
2184        let org_key = PrivateKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
2185        let org_public_key_der = org_key
2186            .to_public_key()
2187            .to_der()
2188            .expect("valid public key DER");
2189        let org_public_key = B64::from(org_public_key_der.as_ref().to_vec());
2190
2191        // Create a client and generate TDE registration keys
2192        let registration_client = Client::new_test(None);
2193        let make_keys_response = registration_client
2194            .crypto()
2195            .make_user_tde_registration(org_public_key)
2196            .expect("TDE registration should succeed");
2197
2198        // Initialize a new client using the TDE device key
2199        let unlock_client = Client::new_test(None);
2200        unlock_client
2201            .crypto()
2202            .initialize_user_crypto(InitUserCryptoRequest {
2203                user_id: Some(user_id),
2204                kdf_params: kdf,
2205                email: email.to_owned(),
2206                account_cryptographic_state: make_keys_response.account_cryptographic_state,
2207                method: InitUserCryptoMethod::DeviceKey {
2208                    device_key: make_keys_response
2209                        .trusted_device_keys
2210                        .device_key
2211                        .to_string(),
2212                    protected_device_private_key: make_keys_response
2213                        .trusted_device_keys
2214                        .protected_device_private_key,
2215                    device_protected_user_key: make_keys_response
2216                        .trusted_device_keys
2217                        .protected_user_key,
2218                },
2219                upgrade_token: None,
2220            })
2221            .await
2222            .expect("initializing user crypto with TDE device key should succeed");
2223
2224        // Verify we can retrieve the user encryption key
2225        let retrieved_key = unlock_client
2226            .crypto()
2227            .get_user_encryption_key()
2228            .await
2229            .expect("should be able to get user encryption key");
2230
2231        // The retrieved key should be a valid symmetric key
2232        let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
2233            .expect("retrieved key should be valid symmetric key");
2234
2235        // Verify that the org key can decrypt the admin_reset_key UnsignedSharedKey
2236        // and that the decrypted key matches the user's encryption key
2237        #[expect(deprecated)]
2238        let decrypted_user_key = make_keys_response
2239            .reset_password_key
2240            .decapsulate_key_unsigned(&org_key)
2241            .expect("org key should be able to decrypt admin reset key");
2242        assert_eq!(
2243            retrieved_symmetric_key, decrypted_user_key,
2244            "decrypted admin reset key should match the user's encryption key"
2245        );
2246    }
2247
2248    #[tokio::test]
2249    async fn test_make_user_key_connector_registration_success() {
2250        let user_id = UserId::new_v4();
2251        let email = "[email protected]";
2252        let registration_client = Client::new(None);
2253
2254        let make_keys_response = make_user_key_connector_registration(&registration_client);
2255        assert!(make_keys_response.is_ok());
2256        let make_keys_response = make_keys_response.unwrap();
2257
2258        // Initialize a new client using the key connector key
2259        let unlock_client = Client::new_test(None);
2260        unlock_client
2261            .crypto()
2262            .initialize_user_crypto(InitUserCryptoRequest {
2263                user_id: Some(user_id),
2264                kdf_params: Kdf::default_argon2(),
2265                email: email.to_owned(),
2266                account_cryptographic_state: make_keys_response.account_cryptographic_state,
2267                method: InitUserCryptoMethod::KeyConnector {
2268                    user_key: make_keys_response
2269                        .key_connector_key_wrapped_user_key
2270                        .clone(),
2271                    master_key: make_keys_response.key_connector_key.clone().into(),
2272                },
2273                upgrade_token: None,
2274            })
2275            .await
2276            .expect("initializing user crypto with key connector key should succeed");
2277
2278        // Verify we can retrieve the user encryption key
2279        let retrieved_key = unlock_client
2280            .crypto()
2281            .get_user_encryption_key()
2282            .await
2283            .expect("should be able to get user encryption key");
2284
2285        // The retrieved key should be a valid symmetric key
2286        let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
2287            .expect("retrieved key should be valid symmetric key");
2288
2289        assert_eq!(retrieved_symmetric_key, make_keys_response.user_key);
2290
2291        let decrypted_user_key = make_keys_response
2292            .key_connector_key
2293            .decrypt_user_key(make_keys_response.key_connector_key_wrapped_user_key);
2294        assert_eq!(retrieved_symmetric_key, decrypted_user_key.unwrap());
2295    }
2296
2297    #[tokio::test]
2298    async fn test_initialize_user_crypto_with_upgrade_token_upgrades_v1_to_v2() {
2299        let client1 = Client::init_test_account(test_bitwarden_com_account()).await;
2300
2301        let expected_v2_key =
2302            SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
2303        let upgrade_token = {
2304            let mut ctx = client1.internal.get_key_store().context_mut();
2305            let v2_key_id = ctx.add_local_symmetric_key(expected_v2_key.clone());
2306            V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap()
2307        };
2308
2309        let client2 = Client::new_test(None);
2310        init_v2_account_with_master_password_and_upgrade_token(
2311            &client2,
2312            UserId::new_v4(),
2313            upgrade_token,
2314        )
2315        .await;
2316
2317        // The active user key must now be V2 and match the test-vector V2 key.
2318        let result_key =
2319            SymmetricCryptoKey::try_from(get_user_encryption_key(&client2).await.unwrap()).unwrap();
2320        assert!(
2321            matches!(result_key, SymmetricCryptoKey::XChaCha20Poly1305Key(_)),
2322            "User key should be upgraded to V2 after initialization with upgrade token"
2323        );
2324        assert_eq!(result_key, expected_v2_key);
2325    }
2326
2327    #[tokio::test]
2328    async fn test_initialize_user_crypto_with_upgrade_token_ignored_for_v2_key() {
2329        let dummy_token = {
2330            let key_store = KeyStore::<KeySlotIds>::default();
2331            let mut ctx = key_store.context_mut();
2332            let v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
2333            let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
2334            V2UpgradeToken::create(v1_id, v2_id, &ctx).unwrap()
2335        };
2336
2337        let client = Client::new_test(None);
2338        initialize_user_crypto(
2339            &client,
2340            InitUserCryptoRequest {
2341                user_id: Some(UserId::new_v4()),
2342                kdf_params: Kdf::PBKDF2 {
2343                    iterations: 100_000.try_into().unwrap(),
2344                },
2345                email: "[email protected]".into(),
2346                account_cryptographic_state: WrappedAccountCryptographicState::V2 {
2347                    private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
2348                    signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
2349                    security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
2350                    signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
2351                },
2352                method: InitUserCryptoMethod::DecryptedKey {
2353                    decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
2354                },
2355                upgrade_token: Some(dummy_token),
2356            },
2357        )
2358        .await
2359        .unwrap();
2360
2361        // The upgrade token must have been ignored; the original V2 key must still be active
2362        let result_key =
2363            SymmetricCryptoKey::try_from(get_user_encryption_key(&client).await.unwrap()).unwrap();
2364        assert!(
2365            matches!(result_key, SymmetricCryptoKey::XChaCha20Poly1305Key(_)),
2366            "Upgrade token must be ignored for a V2 user key"
2367        );
2368        let expected_key =
2369            SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
2370        assert_eq!(result_key, expected_key);
2371    }
2372
2373    #[tokio::test]
2374    async fn test_initialize_user_crypto_with_invalid_upgrade_token_fails() {
2375        // Token built with a different V1 key — decryption with the test account's V1 key fails.
2376        let mismatched_token = {
2377            let key_store = KeyStore::<KeySlotIds>::default();
2378            let mut ctx = key_store.context_mut();
2379            let wrong_v1_id = ctx.generate_symmetric_key();
2380            let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
2381            V2UpgradeToken::create(wrong_v1_id, v2_id, &ctx).unwrap()
2382        };
2383
2384        let client = Client::new_test(None);
2385        let result = initialize_user_crypto(
2386            &client,
2387            InitUserCryptoRequest {
2388                user_id: Some(UserId::new_v4()),
2389                kdf_params: Kdf::PBKDF2 {
2390                    iterations: 600_000.try_into().unwrap(),
2391                },
2392                email: "[email protected]".into(),
2393                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
2394                    // The private key is never decrypted because the token fails first.
2395                    private_key: TEST_ACCOUNT_PRIVATE_KEY.parse().unwrap(),
2396                },
2397                method: InitUserCryptoMethod::MasterPasswordUnlock {
2398                    password: "asdfasdfasdf".into(),
2399                    master_password_unlock: MasterPasswordUnlockData {
2400                        kdf: Kdf::PBKDF2 {
2401                            iterations: 600_000.try_into().unwrap(),
2402                        },
2403                        master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
2404                        salt: "[email protected]".to_string(),
2405                    },
2406                },
2407                upgrade_token: Some(mismatched_token),
2408            },
2409        )
2410        .await;
2411
2412        assert!(
2413            matches!(result, Err(EncryptionSettingsError::InvalidUpgradeToken)),
2414            "Initialization with a mismatched upgrade token should fail"
2415        );
2416    }
2417
2418    #[tokio::test]
2419    async fn test_initialize_user_local_data_key_sets_local_user_data_key_equal_to_user_key() {
2420        let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
2421        initialize_user_local_data_key(&client)
2422            .await
2423            .expect("initialize_user_local_data_key should succeed");
2424
2425        // Verify LocalUserData key equals the User key: data encrypted with User
2426        // must be decryptable with LocalUserData.
2427        let key_store = client.internal.get_key_store();
2428        let mut ctx = key_store.context_mut();
2429        let plaintext = "test";
2430        let ciphertext = plaintext
2431            .encrypt(&mut ctx, SymmetricKeySlotId::User)
2432            .expect("encryption with user key should succeed");
2433        let decrypted: String = ciphertext
2434            .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2435            .expect("decryption with local user data key should succeed");
2436        assert_eq!(decrypted, plaintext);
2437    }
2438
2439    #[tokio::test]
2440    async fn test_initialize_org_crypto_persists_org_keys() {
2441        use crate::{OrganizationId, client::persisted_state::OrganizationSharedKey};
2442
2443        let client = Client::init_test_account(test_bitwarden_com_account()).await;
2444
2445        let org_id: OrganizationId = "1bc9ac1e-f5aa-45f2-94bf-b181009709b8".parse().unwrap();
2446
2447        let repo = client
2448            .internal
2449            .state_registry
2450            .get::<OrganizationSharedKey>()
2451            .expect("OrganizationSharedKey repository should be available");
2452
2453        let persisted = repo
2454            .get(org_id)
2455            .await
2456            .expect("repository get should not fail");
2457
2458        let entry = persisted.expect("org key should be persisted after initialize_org_crypto");
2459        assert_eq!(entry.org_id, org_id);
2460    }
2461
2462    #[tokio::test]
2463    async fn test_initialize_user_crypto_persists_account_crypto_state() {
2464        use crate::client::persisted_state::ACCOUNT_CRYPTO_STATE;
2465
2466        let account_crypto_state = WrappedAccountCryptographicState::V1 {
2467            private_key: TEST_ACCOUNT_PRIVATE_KEY.parse().unwrap(),
2468        };
2469
2470        let client = Client::new_test(None);
2471        initialize_user_crypto(
2472            &client,
2473            InitUserCryptoRequest {
2474                user_id: Some(UserId::new_v4()),
2475                kdf_params: Kdf::PBKDF2 {
2476                    iterations: 600_000.try_into().unwrap(),
2477                },
2478                email: TEST_USER_EMAIL.into(),
2479                account_cryptographic_state: account_crypto_state.clone(),
2480                method: InitUserCryptoMethod::MasterPasswordUnlock {
2481                    password: TEST_USER_PASSWORD.into(),
2482                    master_password_unlock: MasterPasswordUnlockData {
2483                        kdf: Kdf::PBKDF2 {
2484                            iterations: 600_000.try_into().unwrap(),
2485                        },
2486                        master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
2487                        salt: TEST_USER_EMAIL.to_string(),
2488                    },
2489                },
2490                upgrade_token: None,
2491            },
2492        )
2493        .await
2494        .unwrap();
2495
2496        let persisted = client
2497            .internal
2498            .state_registry
2499            .setting(ACCOUNT_CRYPTO_STATE)
2500            .expect("ACCOUNT_CRYPTO_STATE setting should be available")
2501            .get()
2502            .await
2503            .expect("setting get should not fail");
2504
2505        assert_eq!(persisted, Some(account_crypto_state));
2506    }
2507
2508    #[tokio::test]
2509    async fn test_initialize_user_local_data_key_idempotent() {
2510        let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
2511        initialize_user_local_data_key(&client)
2512            .await
2513            .expect("first initialization should succeed");
2514
2515        // Encrypt something with the key established on the first call.
2516        let ciphertext = {
2517            let key_store = client.internal.get_key_store();
2518            let mut ctx = key_store.context_mut();
2519            "test"
2520                .encrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2521                .expect("encryption should succeed")
2522        };
2523
2524        initialize_user_local_data_key(&client)
2525            .await
2526            .expect("second initialization should succeed");
2527
2528        // The key must not have changed: data encrypted before the second call
2529        // must still be decryptable.
2530        let key_store = client.internal.get_key_store();
2531        let mut ctx = key_store.context_mut();
2532        let decrypted: String = ciphertext
2533            .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2534            .expect("decryption after second initialization should succeed");
2535        assert_eq!(decrypted, "test");
2536    }
2537
2538    #[tokio::test]
2539    async fn test_initialize_user_crypto_rewraps_local_user_data_key_on_v1_to_v2_upgrade() {
2540        use crate::key_management::LocalUserDataKeyState;
2541
2542        // Bootstrap a V1 client to materialize a V1-wrapped LocalUserDataKey state.
2543        let client_v1 = Client::init_test_account(test_bitwarden_com_account()).await;
2544        let user_id = UserId::new(uuid::uuid!("060000fb-0922-4dd3-b170-6e15cb5df8c8"));
2545
2546        let v1_user_data_key = client_v1
2547            .platform()
2548            .state()
2549            .get::<LocalUserDataKeyState>()
2550            .unwrap()
2551            .get(user_id)
2552            .await
2553            .unwrap()
2554            .expect("V1 init should plant a LocalUserDataKey state");
2555        assert!(
2556            matches!(
2557                v1_user_data_key.wrapped_key,
2558                EncString::Aes256Cbc_HmacSha256_B64 { .. }
2559            ),
2560            "Initial local user data key should use be wrapped with a V1 user key"
2561        );
2562
2563        // Encrypt a payload with the V1-derived LocalUserData key.
2564        let ciphertext = {
2565            let mut ctx = client_v1.internal.get_key_store().context_mut();
2566            "preserved data"
2567                .encrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2568                .unwrap()
2569        };
2570
2571        // Build an upgrade token from the V1 user key to a fresh V2 key.
2572        let v2_key = SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
2573        let upgrade_token = {
2574            let mut ctx = client_v1.internal.get_key_store().context_mut();
2575            let v2_key_id = ctx.add_local_symmetric_key(v2_key.clone());
2576            V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap()
2577        };
2578
2579        // Plant the V1-wrapped state into a fresh client and run init with the upgrade token.
2580        let client_v2 = Client::new_test(None);
2581        let repo = client_v2
2582            .platform()
2583            .state()
2584            .get::<LocalUserDataKeyState>()
2585            .unwrap();
2586        repo.set(user_id, v1_user_data_key.clone()).await.unwrap();
2587        client_v2
2588            .km_state_bridge()
2589            .register_bridge(Box::new(InMemoryStateBridge::default()));
2590        client_v2
2591            .km_state_bridge()
2592            .set_v2_upgrade_token(&upgrade_token.clone())
2593            .await;
2594
2595        init_v2_account_with_master_password_and_upgrade_token(&client_v2, user_id, upgrade_token)
2596            .await;
2597
2598        // The persisted wrapped key must be sealed with the V2 user key.
2599        let rewrapped_state = repo
2600            .get(user_id)
2601            .await
2602            .unwrap()
2603            .expect("LocalUserDataKey state must remain present");
2604        assert!(
2605            matches!(
2606                rewrapped_state.wrapped_key,
2607                EncString::Cose_Encrypt0_B64 { .. }
2608            ),
2609            "Rewrapped key should be sealed with the V2 user key"
2610        );
2611        assert_ne!(rewrapped_state.wrapped_key, v1_user_data_key.wrapped_key);
2612
2613        // Data encrypted before the upgrade must remain decryptable.
2614        let mut ctx = client_v2.internal.get_key_store().context_mut();
2615        let decrypted: String = ciphertext
2616            .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2617            .expect("data encrypted before the upgrade should decrypt after rewrap");
2618        assert_eq!(decrypted, "preserved data");
2619    }
2620
2621    #[tokio::test]
2622    async fn test_initialize_user_crypto_creates_new_local_user_data_key_with_upgrade_token_and_no_existing_state()
2623     {
2624        use crate::key_management::LocalUserDataKeyState;
2625
2626        // Build an upgrade token from a separate V1 client (no state will be planted from it).
2627        let helper = Client::init_test_account(test_bitwarden_com_account()).await;
2628        let v2_key = SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
2629        let upgrade_token = {
2630            let mut ctx = helper.internal.get_key_store().context_mut();
2631            let v2_key_id = ctx.add_local_symmetric_key(v2_key.clone());
2632            V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap()
2633        };
2634
2635        // Fresh client with no planted LocalUserDataKey state.
2636        let user_id = UserId::new_v4();
2637        let client = Client::new_test(None);
2638        client
2639            .km_state_bridge()
2640            .register_bridge(Box::new(InMemoryStateBridge::default()));
2641        client
2642            .km_state_bridge()
2643            .set_v2_upgrade_token(&upgrade_token.clone())
2644            .await;
2645
2646        init_v2_account_with_master_password_and_upgrade_token(&client, user_id, upgrade_token)
2647            .await;
2648
2649        // No existing state → standard fresh-init path: a new wrapped key sealed with V2.
2650        let new_state = client
2651            .platform()
2652            .state()
2653            .get::<LocalUserDataKeyState>()
2654            .unwrap()
2655            .get(user_id)
2656            .await
2657            .unwrap()
2658            .expect("LocalUserDataKey should be created on init");
2659        assert!(matches!(
2660            new_state.wrapped_key,
2661            EncString::Cose_Encrypt0_B64 { .. }
2662        ));
2663    }
2664
2665    #[tokio::test]
2666    async fn test_initialize_user_crypto_leaves_local_user_data_key_unchanged_without_upgrade_token()
2667     {
2668        use crate::key_management::LocalUserDataKeyState;
2669
2670        // First V1 init plants a V1-wrapped state.
2671        let client = Client::init_test_account(test_bitwarden_com_account()).await;
2672        let user_id = UserId::new(uuid::uuid!("060000fb-0922-4dd3-b170-6e15cb5df8c8"));
2673        client
2674            .km_state_bridge()
2675            .register_bridge(Box::new(InMemoryStateBridge::default()));
2676
2677        let repo = client
2678            .platform()
2679            .state()
2680            .get::<LocalUserDataKeyState>()
2681            .unwrap();
2682        let before = repo.get(user_id).await.unwrap().unwrap();
2683
2684        // Re-run initialize_local_user_data_key_into_state; must skip idempotently.
2685        initialize_local_user_data_key_into_state(&client, user_id)
2686            .await
2687            .map_err(|_| "should succeed")
2688            .unwrap();
2689
2690        let after = repo.get(user_id).await.unwrap().unwrap();
2691        assert_eq!(
2692            after.wrapped_key, before.wrapped_key,
2693            "without an upgrade token the wrapped key must not change"
2694        );
2695    }
2696
2697    #[tokio::test]
2698    async fn test_initialize_user_crypto_does_not_rewrap_when_already_v2() {
2699        use crate::key_management::LocalUserDataKeyState;
2700
2701        // V2 init plants a V2-wrapped state.
2702        let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
2703        let user_id = UserId::new(uuid::uuid!("060000fb-0922-4dd3-b170-6e15cb5df8c8"));
2704        client
2705            .km_state_bridge()
2706            .register_bridge(Box::new(InMemoryStateBridge::default()));
2707
2708        let repo = client
2709            .platform()
2710            .state()
2711            .get::<LocalUserDataKeyState>()
2712            .unwrap();
2713        let before = repo.get(user_id).await.unwrap().unwrap();
2714        assert!(matches!(
2715            before.wrapped_key,
2716            EncString::Cose_Encrypt0_B64 { .. }
2717        ));
2718
2719        migrate_local_user_data_key_for_user_key_upgrade(&client, user_id)
2720            .await
2721            .map_err(|_| "should succeed")
2722            .unwrap();
2723
2724        let after = repo.get(user_id).await.unwrap().unwrap();
2725        assert_eq!(
2726            after.wrapped_key, before.wrapped_key,
2727            "an already-V2-wrapped key must not be rewrapped"
2728        );
2729    }
2730
2731    #[tokio::test]
2732    async fn test_make_user_password_registration() {
2733        let user_id = UserId::new_v4();
2734        let registration_client = Client::new(None);
2735
2736        let make_keys_response = registration_client
2737            .crypto()
2738            .make_user_password_registration(
2739                TEST_USER_PASSWORD.to_string(),
2740                TEST_USER_EMAIL.to_string(),
2741            )
2742            .expect("user password registration should succeed");
2743
2744        let unlock_client = Client::new_test(None);
2745        unlock_client
2746            .crypto()
2747            .initialize_user_crypto(InitUserCryptoRequest {
2748                user_id: Some(user_id),
2749                kdf_params: Kdf::default_argon2(),
2750                email: TEST_USER_EMAIL.to_string(),
2751                account_cryptographic_state: make_keys_response.account_cryptographic_state,
2752                method: InitUserCryptoMethod::MasterPasswordUnlock {
2753                    password: TEST_USER_PASSWORD.to_string(),
2754                    master_password_unlock: make_keys_response.master_password_unlock_data.clone(),
2755                },
2756                upgrade_token: None,
2757            })
2758            .await
2759            .expect("initializing user crypto with master password should succeed");
2760
2761        let retrieved_key = unlock_client
2762            .crypto()
2763            .get_user_encryption_key()
2764            .await
2765            .expect("should be able to get user encryption key");
2766
2767        let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
2768            .expect("retrieved key should be valid symmetric key");
2769
2770        let master_key = MasterKey::derive(
2771            TEST_USER_PASSWORD,
2772            TEST_USER_EMAIL,
2773            &make_keys_response.master_password_unlock_data.kdf,
2774        )
2775        .expect("master key should derive");
2776
2777        let decrypted_user_key = master_key
2778            .decrypt_user_key(
2779                make_keys_response
2780                    .master_password_unlock_data
2781                    .master_key_wrapped_user_key
2782                    .clone(),
2783            )
2784            .expect("should decrypt user key");
2785
2786        assert_eq!(retrieved_symmetric_key, decrypted_user_key);
2787    }
2788}