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///
475/// Note: This is deprecated and will be removed after key-connector fully uses sdk
476#[derive(Serialize, Deserialize, Debug)]
477#[serde(rename_all = "camelCase", deny_unknown_fields)]
478#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
479#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
480pub struct UpdateKdfResponse {
481    /// The authentication data for the new KDF setting
482    master_password_authentication_data: MasterPasswordAuthenticationData,
483    /// The unlock data for the new KDF setting
484    master_password_unlock_data: MasterPasswordUnlockData,
485    /// The authentication data for the KDF setting prior to the change
486    old_master_password_authentication_data: MasterPasswordAuthenticationData,
487}
488
489// Note: This is deprecated and will be removed after key-connector fully uses sdk
490pub(super) async fn make_update_kdf(
491    client: &Client,
492    password: &str,
493    new_kdf: &Kdf,
494) -> Result<UpdateKdfResponse, CryptoClientError> {
495    let login_method = client
496        .internal
497        .get_login_method()
498        .await
499        .ok_or(NotAuthenticatedError)?;
500    let email = match login_method {
501        UserLoginMethod::Username { email, .. } | UserLoginMethod::ApiKey { email, .. } => email,
502    };
503
504    let old_authentication_data = MasterPasswordAuthenticationData::derive(
505        password,
506        &client
507            .internal
508            .get_kdf()
509            .await
510            .map_err(|_| NotAuthenticatedError)?,
511        &email,
512    )
513    .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
514
515    let key_store = client.internal.get_key_store();
516    let ctx = key_store.context();
517
518    let authentication_data = MasterPasswordAuthenticationData::derive(password, new_kdf, &email)
519        .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
520    let unlock_data =
521        MasterPasswordUnlockData::derive(password, new_kdf, &email, SymmetricKeySlotId::User, &ctx)
522            .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
523
524    Ok(UpdateKdfResponse {
525        master_password_authentication_data: authentication_data,
526        master_password_unlock_data: unlock_data,
527        old_master_password_authentication_data: old_authentication_data,
528    })
529}
530
531/// Response from the `make_update_password` function
532#[derive(Serialize, Deserialize, Debug)]
533#[serde(rename_all = "camelCase", deny_unknown_fields)]
534#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
535#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
536pub struct UpdatePasswordResponse {
537    /// Hash of the new password
538    password_hash: B64,
539    /// User key, encrypted with the new password
540    new_key: EncString,
541}
542
543pub(super) async fn make_update_password(
544    client: &Client,
545    new_password: String,
546) -> Result<UpdatePasswordResponse, CryptoClientError> {
547    let login_method = client
548        .internal
549        .get_login_method()
550        .await
551        .ok_or(NotAuthenticatedError)?;
552
553    let key_store = client.internal.get_key_store();
554    let ctx = key_store.context();
555    // FIXME: [PM-18099] Once MasterKey deals with KeySlotIds, this should be updated
556    #[allow(deprecated)]
557    let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
558
559    // Derive a new master key from password
560    let new_master_key = match login_method {
561        UserLoginMethod::Username { email, kdf, .. }
562        | UserLoginMethod::ApiKey { email, kdf, .. } => {
563            MasterKey::derive(&new_password, &email, &kdf)?
564        }
565    };
566
567    let new_key = new_master_key.encrypt_user_key(user_key)?;
568
569    let password_hash = new_master_key.derive_master_key_hash(
570        new_password.as_bytes(),
571        bitwarden_crypto::HashPurpose::ServerAuthorization,
572    );
573
574    Ok(UpdatePasswordResponse {
575        password_hash,
576        new_key,
577    })
578}
579
580/// Request for deriving a pin protected user key
581#[derive(Serialize, Deserialize, Debug)]
582#[serde(rename_all = "camelCase", deny_unknown_fields)]
583#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
584#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
585pub struct EnrollPinResponse {
586    /// [UserKey] protected by PIN
587    pub pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
588    /// PIN protected by [UserKey]
589    pub user_key_encrypted_pin: EncString,
590}
591
592pub(super) fn enroll_pin(
593    client: &Client,
594    pin: String,
595) -> Result<EnrollPinResponse, CryptoClientError> {
596    let key_store = client.internal.get_key_store();
597    let mut ctx = key_store.context_mut();
598
599    let key_envelope = PasswordProtectedKeyEnvelope::seal(
600        SymmetricKeySlotId::User,
601        &pin,
602        PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
603        &ctx,
604    )?;
605    let encrypted_pin = pin.encrypt(&mut ctx, SymmetricKeySlotId::User)?;
606    Ok(EnrollPinResponse {
607        pin_protected_user_key_envelope: key_envelope,
608        user_key_encrypted_pin: encrypted_pin,
609    })
610}
611
612/// Request for deriving a pin protected user key
613#[derive(Serialize, Deserialize, Debug)]
614#[serde(rename_all = "camelCase", deny_unknown_fields)]
615#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
616#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
617pub struct DerivePinKeyResponse {
618    /// [UserKey] protected by PIN
619    pin_protected_user_key: EncString,
620    /// PIN protected by [UserKey]
621    encrypted_pin: EncString,
622}
623
624pub(super) async fn derive_pin_key(
625    client: &Client,
626    pin: String,
627) -> Result<DerivePinKeyResponse, CryptoClientError> {
628    let login_method = client
629        .internal
630        .get_login_method()
631        .await
632        .ok_or(NotAuthenticatedError)?;
633
634    let key_store = client.internal.get_key_store();
635    let ctx = key_store.context();
636    // FIXME: [PM-18099] Once PinKey deals with KeySlotIds, this should be updated
637    #[allow(deprecated)]
638    let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
639
640    let pin_protected_user_key = derive_pin_protected_user_key(&pin, &login_method, user_key)?;
641
642    Ok(DerivePinKeyResponse {
643        pin_protected_user_key,
644        encrypted_pin: pin.encrypt_with_key(user_key)?,
645    })
646}
647
648pub(super) async fn derive_pin_user_key(
649    client: &Client,
650    encrypted_pin: EncString,
651) -> Result<EncString, CryptoClientError> {
652    let login_method = client
653        .internal
654        .get_login_method()
655        .await
656        .ok_or(NotAuthenticatedError)?;
657
658    let key_store = client.internal.get_key_store();
659    let ctx = key_store.context();
660    // FIXME: [PM-18099] Once PinKey deals with KeySlotIds, this should be updated
661    #[allow(deprecated)]
662    let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
663
664    let pin: String = encrypted_pin.decrypt_with_key(user_key)?;
665
666    derive_pin_protected_user_key(&pin, &login_method, user_key)
667}
668
669fn derive_pin_protected_user_key(
670    pin: &str,
671    login_method: &UserLoginMethod,
672    user_key: &SymmetricCryptoKey,
673) -> Result<EncString, CryptoClientError> {
674    use bitwarden_crypto::PinKey;
675
676    let derived_key = match login_method {
677        UserLoginMethod::Username { email, kdf, .. }
678        | UserLoginMethod::ApiKey { email, kdf, .. } => {
679            PinKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?
680        }
681    };
682
683    Ok(derived_key.encrypt_user_key(user_key)?)
684}
685
686pub(super) fn make_prf_user_key_set(
687    client: &Client,
688    prf: B64,
689) -> Result<RotateableKeySet, CryptoClientError> {
690    let prf_key = derive_symmetric_key_from_prf(prf.as_bytes())
691        .map_err(|_| CryptoClientError::InvalidPrfInput)?;
692    let ctx = client.internal.get_key_store().context();
693    let key_set = RotateableKeySet::new(&ctx, &prf_key, SymmetricKeySlotId::User)?;
694    Ok(key_set)
695}
696
697#[allow(missing_docs)]
698#[bitwarden_error(flat)]
699#[derive(Debug, thiserror::Error)]
700pub enum EnrollAdminPasswordResetError {
701    #[error(transparent)]
702    Crypto(#[from] bitwarden_crypto::CryptoError),
703}
704
705pub(super) fn enroll_admin_password_reset(
706    client: &Client,
707    public_key: B64,
708) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
709    use bitwarden_crypto::PublicKey;
710
711    let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&public_key))?;
712    let key_store = client.internal.get_key_store();
713    let ctx = key_store.context();
714    // FIXME: [PM-18110] This should be removed once the key store can handle public key encryption
715    #[allow(deprecated)]
716    let key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
717
718    #[expect(deprecated)]
719    Ok(UnsignedSharedKey::encapsulate_key_unsigned(
720        key,
721        &public_key,
722    )?)
723}
724
725/// Request for migrating an account from password to key connector.
726#[derive(Serialize, Deserialize, Debug, JsonSchema)]
727#[serde(rename_all = "camelCase", deny_unknown_fields)]
728#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
729#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
730pub struct DeriveKeyConnectorRequest {
731    /// Encrypted user key, used to validate the master key
732    pub user_key_encrypted: EncString,
733    /// The user's master password
734    pub password: String,
735    /// The KDF parameters used to derive the master key
736    pub kdf: Kdf,
737    /// The user's email address
738    pub email: String,
739}
740
741#[allow(missing_docs)]
742#[bitwarden_error(flat)]
743#[derive(Debug, thiserror::Error)]
744pub enum DeriveKeyConnectorError {
745    #[error(transparent)]
746    WrongPassword(#[from] WrongPasswordError),
747    #[error(transparent)]
748    Crypto(#[from] bitwarden_crypto::CryptoError),
749}
750
751/// Derive the master key for migrating to the key connector
752pub(super) fn derive_key_connector(
753    request: DeriveKeyConnectorRequest,
754) -> Result<B64, DeriveKeyConnectorError> {
755    let master_key = MasterKey::derive(&request.password, &request.email, &request.kdf)?;
756    master_key
757        .decrypt_user_key(request.user_key_encrypted)
758        .map_err(|_| WrongPasswordError)?;
759
760    Ok(master_key.to_base64())
761}
762
763/// Response from the `make_key_pair` function
764#[derive(Serialize, Deserialize, Debug)]
765#[serde(rename_all = "camelCase", deny_unknown_fields)]
766#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
767#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
768pub struct MakeKeyPairResponse {
769    /// The user's public key
770    user_public_key: B64,
771    /// User's private key, encrypted with the user key
772    user_key_encrypted_private_key: EncString,
773}
774
775pub(super) fn make_key_pair(user_key: B64) -> Result<MakeKeyPairResponse, CryptoError> {
776    let user_key = UserKey::new(SymmetricCryptoKey::try_from(user_key)?);
777
778    let key_pair = user_key.make_key_pair()?;
779
780    Ok(MakeKeyPairResponse {
781        user_public_key: key_pair.public,
782        user_key_encrypted_private_key: key_pair.private,
783    })
784}
785
786/// Request for `verify_asymmetric_keys`.
787#[derive(Serialize, Deserialize, Debug)]
788#[serde(rename_all = "camelCase", deny_unknown_fields)]
789#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
790#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
791pub struct VerifyAsymmetricKeysRequest {
792    /// The user's user key
793    user_key: B64,
794    /// The user's public key
795    user_public_key: B64,
796    /// User's private key, encrypted with the user key
797    user_key_encrypted_private_key: EncString,
798}
799
800/// Response for `verify_asymmetric_keys`.
801#[derive(Serialize, Deserialize, Debug)]
802#[serde(rename_all = "camelCase", deny_unknown_fields)]
803#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
804#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
805pub struct VerifyAsymmetricKeysResponse {
806    /// Whether the user's private key was decryptable by the user key.
807    private_key_decryptable: bool,
808    /// Whether the user's private key was a valid RSA key and matched the public key provided.
809    valid_private_key: bool,
810}
811
812pub(super) fn verify_asymmetric_keys(
813    request: VerifyAsymmetricKeysRequest,
814) -> Result<VerifyAsymmetricKeysResponse, CryptoError> {
815    #[derive(Debug, thiserror::Error)]
816    enum VerifyError {
817        #[error("Failed to decrypt private key: {0:?}")]
818        DecryptFailed(bitwarden_crypto::CryptoError),
819        #[error("Failed to parse decrypted private key: {0:?}")]
820        ParseFailed(bitwarden_crypto::CryptoError),
821        #[error("Failed to derive a public key: {0:?}")]
822        PublicFailed(bitwarden_crypto::CryptoError),
823        #[error("Derived public key doesn't match")]
824        KeyMismatch,
825    }
826
827    fn verify_inner(
828        user_key: &SymmetricCryptoKey,
829        request: &VerifyAsymmetricKeysRequest,
830    ) -> Result<(), VerifyError> {
831        let decrypted_private_key: Vec<u8> = request
832            .user_key_encrypted_private_key
833            .decrypt_with_key(user_key)
834            .map_err(VerifyError::DecryptFailed)?;
835
836        let decrypted_private_key = Pkcs8PrivateKeyBytes::from(decrypted_private_key);
837        let private_key =
838            PrivateKey::from_der(&decrypted_private_key).map_err(VerifyError::ParseFailed)?;
839
840        let derived_public_key_vec = private_key
841            .to_public_key()
842            .to_der()
843            .map_err(VerifyError::PublicFailed)?;
844
845        let derived_public_key = B64::from(derived_public_key_vec);
846
847        if derived_public_key != request.user_public_key {
848            return Err(VerifyError::KeyMismatch);
849        }
850        Ok(())
851    }
852
853    let user_key = SymmetricCryptoKey::try_from(request.user_key.clone())?;
854
855    Ok(match verify_inner(&user_key, &request) {
856        Ok(_) => VerifyAsymmetricKeysResponse {
857            private_key_decryptable: true,
858            valid_private_key: true,
859        },
860        Err(error) => {
861            tracing::debug!(%error, "User asymmetric keys verification");
862
863            VerifyAsymmetricKeysResponse {
864                private_key_decryptable: !matches!(error, VerifyError::DecryptFailed(_)),
865                valid_private_key: false,
866            }
867        }
868    })
869}
870
871/// Response for the `make_keys_for_user_crypto_v2`, containing a set of keys for a user
872#[derive(Serialize, Deserialize, Debug, Clone)]
873#[serde(rename_all = "camelCase", deny_unknown_fields)]
874#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
875#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
876pub struct UserCryptoV2KeysResponse {
877    /// User key
878    user_key: B64,
879
880    /// Wrapped private key
881    private_key: EncString,
882    /// Public key
883    public_key: B64,
884    /// The user's public key, signed by the signing key
885    signed_public_key: SignedPublicKey,
886
887    /// Signing key, encrypted with the user's symmetric key
888    signing_key: EncString,
889    /// Base64 encoded verifying key
890    verifying_key: B64,
891
892    /// The user's signed security state
893    security_state: SignedSecurityState,
894    /// The security state's version
895    security_version: u64,
896}
897
898/// Creates the user's cryptographic state for v2 users. This includes ensuring signature key pair
899/// is present, a signed public key is present, a security state is present and signed, and the user
900/// key is a Cose key.
901#[deprecated(note = "Use AccountCryptographicState::rotate instead")]
902pub(crate) fn make_v2_keys_for_v1_user(
903    client: &Client,
904) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
905    let key_store = client.internal.get_key_store();
906    let mut ctx = key_store.context();
907
908    // Re-use existing private key
909    let private_key_id = PrivateKeySlotId::UserPrivateKey;
910
911    // Ensure that the function is only called for a V1 user.
912    if client.internal.get_security_version() != 1 {
913        return Err(StatefulCryptoError::WrongAccountCryptoVersion {
914            expected: "1".to_string(),
915            got: 2,
916        });
917    }
918
919    // Ensure the user has a private key.
920    // V1 user must have a private key to upgrade. This should be ensured by the client before
921    // calling the upgrade function.
922    if !ctx.has_private_key(PrivateKeySlotId::UserPrivateKey) {
923        return Err(StatefulCryptoError::Crypto(CryptoError::MissingKeyId(
924            "UserPrivateKey".to_string(),
925        )));
926    }
927
928    #[allow(deprecated)]
929    let private_key = ctx.dangerous_get_private_key(private_key_id)?.clone();
930
931    // New user key
932    let user_key = SymmetricCryptoKey::make(SymmetricKeyAlgorithm::XAes256Gcm);
933
934    // New signing key
935    let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
936    let temporary_signing_key_id = ctx.add_local_signing_key(signing_key.clone());
937
938    // Sign existing public key
939    let signed_public_key = ctx.make_signed_public_key(private_key_id, temporary_signing_key_id)?;
940    let public_key = private_key.to_public_key();
941
942    // Initialize security state for the user
943    let security_state = SecurityState::new();
944    let signed_security_state = security_state.sign(temporary_signing_key_id, &mut ctx)?;
945
946    Ok(UserCryptoV2KeysResponse {
947        user_key: user_key.to_base64(),
948
949        private_key: private_key.to_der()?.encrypt_with_key(&user_key)?,
950        public_key: public_key.to_der()?.into(),
951        signed_public_key,
952
953        signing_key: signing_key.to_cose().encrypt_with_key(&user_key)?,
954        verifying_key: signing_key.to_verifying_key().to_cose().into(),
955
956        security_state: signed_security_state,
957        security_version: security_state.version(),
958    })
959}
960
961/// Gets a set of new wrapped account keys for a user, given a new user key.
962///
963/// In the current implementation, it just re-encrypts any existing keys. This function expects a
964/// user to be a v2 user; that is, they have a signing key, a cose user-key, and a private key
965#[deprecated(note = "Use AccountCryptographicState::rotate instead")]
966pub(crate) fn get_v2_rotated_account_keys(
967    client: &Client,
968) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
969    let key_store = client.internal.get_key_store();
970    let mut ctx = key_store.context();
971
972    // Ensure that the function is only called for a V2 user.
973    // V2 users have a security version 2 or higher.
974    if client.internal.get_security_version() == 1 {
975        return Err(StatefulCryptoError::WrongAccountCryptoVersion {
976            expected: "2+".to_string(),
977            got: 1,
978        });
979    }
980
981    let security_state = client
982        .internal
983        .security_state
984        .read()
985        .expect("RwLock is not poisoned")
986        .to_owned()
987        // This cannot occur since the security version check above already ensures that the
988        // security state is present.
989        .ok_or(StatefulCryptoError::MissingSecurityState)?;
990
991    #[expect(deprecated)]
992    let rotated_keys = dangerous_get_v2_rotated_account_keys(
993        PrivateKeySlotId::UserPrivateKey,
994        SigningKeySlotId::UserSigningKey,
995        &ctx,
996    )?;
997
998    Ok(UserCryptoV2KeysResponse {
999        user_key: rotated_keys.user_key.to_base64(),
1000
1001        private_key: rotated_keys.private_key,
1002        public_key: rotated_keys.public_key.into(),
1003        signed_public_key: rotated_keys.signed_public_key,
1004
1005        signing_key: rotated_keys.signing_key,
1006        verifying_key: rotated_keys.verifying_key.into(),
1007
1008        security_state: security_state.sign(SigningKeySlotId::UserSigningKey, &mut ctx)?,
1009        security_version: security_state.version(),
1010    })
1011}
1012
1013/// The response from `make_user_tde_registration`.
1014pub struct MakeTdeRegistrationResponse {
1015    /// The account cryptographic state
1016    pub account_cryptographic_state: WrappedAccountCryptographicState,
1017    /// The user's user key
1018    pub user_key: SymmetricCryptoKey,
1019    /// The request model for the account cryptographic state (also called Account Keys)
1020    pub account_keys_request: AccountKeysRequestModel,
1021    /// The keys needed to set up TDE decryption
1022    pub trusted_device_keys: TrustDeviceResponse,
1023    /// The key needed for admin password reset
1024    pub reset_password_key: UnsignedSharedKey,
1025}
1026
1027/// The response from `make_user_jit_master_password_registration`.
1028pub struct MakeJitMasterPasswordRegistrationResponse {
1029    /// The account cryptographic state
1030    pub account_cryptographic_state: WrappedAccountCryptographicState,
1031    /// The user's user key
1032    pub user_key: SymmetricCryptoKey,
1033    /// The master password unlock data
1034    pub master_password_authentication_data: MasterPasswordAuthenticationData,
1035    /// The master password unlock data
1036    pub master_password_unlock_data: MasterPasswordUnlockData,
1037    /// The request model for the account cryptographic state (also called Account Keys)
1038    pub account_keys_request: AccountKeysRequestModel,
1039    /// The key needed for admin password reset
1040    pub reset_password_key: UnsignedSharedKey,
1041}
1042
1043/// Errors that can occur when making keys for account cryptography registration.
1044#[bitwarden_error(flat)]
1045#[derive(Debug, thiserror::Error)]
1046pub enum MakeKeysError {
1047    /// Failed to initialize account cryptography
1048    #[error("Failed to initialize account cryptography")]
1049    AccountCryptographyInitialization(AccountCryptographyInitializationError),
1050    /// Failed to derive master password
1051    #[error("Failed to derive master password")]
1052    MasterPasswordDerivation(MasterPasswordError),
1053    /// Failed to create request model
1054    #[error("Failed to make a request model")]
1055    RequestModelCreation,
1056    /// Generic crypto error
1057    #[error("Cryptography error: {0}")]
1058    Crypto(#[from] CryptoError),
1059}
1060
1061/// Create the data needed to register for TDE (Trusted Device Enrollment)
1062pub(crate) fn make_user_tde_registration(
1063    client: &Client,
1064    org_public_key: B64,
1065) -> Result<MakeTdeRegistrationResponse, MakeKeysError> {
1066    let mut ctx = client.internal.get_key_store().context_mut();
1067    let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1068        .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1069    // TDE unlock method
1070    #[expect(deprecated)]
1071    let device_key = DeviceKey::trust_device(ctx.dangerous_get_symmetric_key(user_key_id)?)?;
1072
1073    // Account recovery enrollment
1074    let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&org_public_key))
1075        .map_err(MakeKeysError::Crypto)?;
1076    #[expect(deprecated)]
1077    let admin_reset = UnsignedSharedKey::encapsulate_key_unsigned(
1078        ctx.dangerous_get_symmetric_key(user_key_id)?,
1079        &public_key,
1080    )
1081    .map_err(MakeKeysError::Crypto)?;
1082
1083    let cryptography_state_request_model = wrapped_state
1084        .to_request_model(&user_key_id, &mut ctx)
1085        .map_err(|_| MakeKeysError::RequestModelCreation)?;
1086
1087    #[expect(deprecated)]
1088    Ok(MakeTdeRegistrationResponse {
1089        account_cryptographic_state: wrapped_state,
1090        user_key: ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned(),
1091        account_keys_request: cryptography_state_request_model,
1092        trusted_device_keys: device_key,
1093        reset_password_key: admin_reset,
1094    })
1095}
1096
1097/// The response from `make_user_key_connector_registration`.
1098pub struct MakeKeyConnectorRegistrationResponse {
1099    /// The account cryptographic state
1100    pub account_cryptographic_state: WrappedAccountCryptographicState,
1101    /// Encrypted user's user key, wrapped with the key connector key
1102    pub key_connector_key_wrapped_user_key: EncString,
1103    /// The user's user key
1104    pub user_key: SymmetricCryptoKey,
1105    /// The request model for the account cryptographic state (also called Account Keys)
1106    pub account_keys_request: AccountKeysRequestModel,
1107    /// The key connector key used for unlocking
1108    pub key_connector_key: KeyConnectorKey,
1109}
1110
1111/// Create the data needed to register for Key Connector
1112pub(crate) fn make_user_key_connector_registration(
1113    client: &Client,
1114) -> Result<MakeKeyConnectorRegistrationResponse, MakeKeysError> {
1115    let mut ctx = client.internal.get_key_store().context_mut();
1116    let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1117        .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1118    #[expect(deprecated)]
1119    let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
1120
1121    // Key Connector unlock method
1122    let key_connector_key = KeyConnectorKey::make();
1123
1124    let wrapped_user_key = key_connector_key
1125        .encrypt_user_key(&user_key)
1126        .map_err(MakeKeysError::Crypto)?;
1127
1128    let cryptography_state_request_model =
1129        wrapped_state
1130            .to_request_model(&user_key_id, &mut ctx)
1131            .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1132
1133    Ok(MakeKeyConnectorRegistrationResponse {
1134        account_cryptographic_state: wrapped_state,
1135        key_connector_key_wrapped_user_key: wrapped_user_key,
1136        user_key,
1137        account_keys_request: cryptography_state_request_model,
1138        key_connector_key,
1139    })
1140}
1141
1142/// Ensures the [`SymmetricKeySlotId::LocalUserData`] key is loaded into the key store context.
1143///
1144/// On first call the key is generated (wrapping the user key with itself) and persisted to state.
1145/// Subsequent calls are idempotent: if the key already exists in state it is loaded as-is,
1146/// preserving any data that was previously encrypted with it (e.g. after a key rotation).
1147async fn initialize_user_local_data_key(client: &Client) -> Result<(), EncryptionSettingsError> {
1148    let user_id = client
1149        .internal
1150        .get_user_id()
1151        .ok_or(EncryptionSettingsError::LocalUserDataKeyInitFailed)?;
1152
1153    migrate_local_user_data_key_for_user_key_upgrade(client, user_id)
1154        .await
1155        .map_err(|_| EncryptionSettingsError::LocalUserDataMigrationFailed)?;
1156
1157    initialize_local_user_data_key_into_state(client, user_id)
1158        .await
1159        .map_err(|_| EncryptionSettingsError::LocalUserDataKeyInitFailed)?;
1160
1161    let wrapped_key = get_local_user_data_key_from_state(client, user_id)
1162        .await
1163        .map_err(|_| EncryptionSettingsError::LocalUserDataKeyLoadFailed)?;
1164    let mut ctx = client.internal.get_key_store().context_mut();
1165    wrapped_key
1166        .unwrap_to_context(&mut ctx)
1167        .map_err(|_| EncryptionSettingsError::LocalUserDataKeyLoadFailed)
1168}
1169
1170/// Runs the code needed post unlock used by `initialize_user_crypto` and `reinit_user_crypto`.
1171///
1172/// Both code paths leave the SDK in an unlocked state with the active user key in the key store,
1173/// and both need to ensure derived per-user state is consistent with that user key before clients
1174/// can use the session.
1175async fn on_unlock_handler(client: &Client) -> Result<(), EncryptionSettingsError> {
1176    initialize_user_local_data_key(client).await?;
1177    PinLockSystem::on_unlock(&PinLockSystem::with_client(client)).await;
1178    Ok(())
1179}
1180
1181/// Create the data needed to register for JIT master password
1182pub(crate) fn make_user_jit_master_password_registration(
1183    client: &Client,
1184    master_password: String,
1185    salt: String,
1186    org_public_key: B64,
1187) -> Result<MakeJitMasterPasswordRegistrationResponse, MakeKeysError> {
1188    let mut ctx = client.internal.get_key_store().context_mut();
1189    let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1190        .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1191
1192    let kdf = ctx.cipher_suite().default_kdf_for_new_account();
1193
1194    #[expect(deprecated)]
1195    let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
1196
1197    let master_password_unlock_data =
1198        MasterPasswordUnlockData::derive(&master_password, &kdf, &salt, user_key_id, &ctx)
1199            .map_err(MakeKeysError::MasterPasswordDerivation)?;
1200
1201    let master_password_authentication_data =
1202        MasterPasswordAuthenticationData::derive(&master_password, &kdf, &salt)
1203            .map_err(MakeKeysError::MasterPasswordDerivation)?;
1204
1205    let cryptography_state_request_model = wrapped_state
1206        .to_request_model(&user_key_id, &mut ctx)
1207        .map_err(|_| MakeKeysError::RequestModelCreation)?;
1208
1209    // Account recovery enrollment
1210    let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&org_public_key))
1211        .map_err(MakeKeysError::Crypto)?;
1212    let admin_reset_key = UnsignedSharedKey::encapsulate(user_key_id, &public_key, &ctx)
1213        .map_err(MakeKeysError::Crypto)?;
1214
1215    Ok(MakeJitMasterPasswordRegistrationResponse {
1216        account_cryptographic_state: wrapped_state,
1217        user_key,
1218        master_password_unlock_data,
1219        master_password_authentication_data,
1220        account_keys_request: cryptography_state_request_model,
1221        reset_password_key: admin_reset_key,
1222    })
1223}
1224
1225/// Response from `make_user_password_registration`
1226pub struct MakeUserMasterPasswordRegistrationResponse {
1227    /// The wrapped account cryptographic state
1228    pub account_cryptographic_state: WrappedAccountCryptographicState,
1229    /// The master password unlock data
1230    pub master_password_unlock_data: MasterPasswordUnlockData,
1231    /// The master password authentication data
1232    pub master_password_authentication_data: MasterPasswordAuthenticationData,
1233    /// The request model for account cryptographic key state
1234    pub account_keys_request: AccountKeysRequestModel,
1235    /// The user's user key
1236    pub user_key: SymmetricCryptoKey,
1237}
1238
1239/// Creates cryptographic data needed for user master password registration
1240pub(crate) fn make_user_password_registration(
1241    client: &Client,
1242    master_password: String,
1243    salt: String,
1244) -> Result<MakeUserMasterPasswordRegistrationResponse, MakeKeysError> {
1245    // make_user_v2_crypto_state() - Creates user key (XAES-256-GCM), RSA key pair, ML-DSA
1246    // signing key pair, and signed security state
1247    let mut ctx = client.internal.get_key_store().context_mut();
1248    let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1249        .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1250
1251    let kdf = ctx.cipher_suite().default_kdf_for_new_account();
1252
1253    #[expect(deprecated)]
1254    let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
1255
1256    let master_password_unlock_data =
1257        MasterPasswordUnlockData::derive(&master_password, &kdf, &salt, user_key_id, &ctx)
1258            .map_err(MakeKeysError::MasterPasswordDerivation)?;
1259
1260    let master_password_authentication_data =
1261        MasterPasswordAuthenticationData::derive(&master_password, &kdf, &salt)
1262            .map_err(MakeKeysError::MasterPasswordDerivation)?;
1263
1264    let account_keys_request = wrapped_state
1265        .to_request_model(&user_key_id, &mut ctx)
1266        .map_err(|_| MakeKeysError::RequestModelCreation)?;
1267
1268    Ok(MakeUserMasterPasswordRegistrationResponse {
1269        account_cryptographic_state: wrapped_state,
1270        master_password_unlock_data,
1271        master_password_authentication_data,
1272        account_keys_request,
1273        user_key,
1274    })
1275}
1276
1277#[cfg(test)]
1278mod tests {
1279    use std::num::NonZeroU32;
1280
1281    use bitwarden_crypto::{
1282        Decryptable, KeyStore, PrivateKey, PublicKeyEncryptionAlgorithm, RsaKeyPair,
1283        SymmetricKeyAlgorithm,
1284    };
1285
1286    use super::*;
1287    use crate::{
1288        Client,
1289        client::test_accounts::{test_bitwarden_com_account, test_bitwarden_com_account_v2},
1290        key_management::{
1291            KeySlotIds, V2UpgradeToken, state_bridge::test_support::InMemoryStateBridge,
1292        },
1293    };
1294
1295    const TEST_VECTOR_USER_KEY_V2_B64: &str = "pQEEAlCxZkKFDpp70P5mWPmOjf3xAzoAARF5BIQDBAUGIFggCFcd6XLISUfLaITyU9yimrYHacdS5XhBayO2663jdSUB";
1296    const TEST_VECTOR_PRIVATE_KEY_V2: &str = "7.g1gdowE6AAEReQMZARwEULFmQoUOmnvQ/mZY+Y6N/fGhBVgYe16rmgYXX3Orgo6y5U5Z8eb+JHTGfcivWQTR+1rVWtHJhEm8G/AtE78Ud3S8qxZmstUKhC5u9xgPvx2e8Fe8QL80Dv0WoEsy0XEb+5EFd8xDlu7OBuCVv2MaoJ/XzAkbpn9IT1vMCPhvRuaktIWMNrQgJ1jnmqjTGObftA02sHnj938tLRNfilw8ln/PBO2GBZQVzTUYfnc+mBeedGyZAxhSxyUwtFB8h3HC/t9BGtLT/bm83Df8rwTc+rGFL5r+T6vczQ+6hvF6kKpUb37XwgLEDsc+J4UTb+4zHaDcTioOYq6Hki8PrsN9PWL57nkhRMi3fKgfz8GDtY+pjp7D9HYV6OMuveSK9l+h16enJwiFDy6XEx+eth4aHPT5hybnOfTWbkEIhUmPD3K2JKvUUxeL9Z6e1EtSylVitO4Lit485KYaY8VASW4MnAzPOUQVwZ4jowHr5X8g0jVtHiLeUuOwDGcqjO/q6//tkiCwjW/W79jk4eqMtqPbOl0XelYVmM4KZCslPZ+2IYS56g/gl8Q2Oj9UGq7QJCsZvV9rBNa4wS3uC9atoWWRqO2PTWkVTurakkK3Fc9VP2bC1lJaWoWVjYpyJJVZh77ktpD3VrFdrT62+de0iaWUAtAr/1ALToNzoTYu3ihyGb6FZMN//XLTKk8GhZGVCluEDClHnziBxCX7Qg/0HRiU7EjsYGhpFnmG2XkvZQb9Pds8gucTbmbUeVfjXZ/IOLm16G/tdit2VIf80zcsvhgxTYys4Cm12N+62fM3aT5L9lqWvBYOMDksy00/3uLPzWbLFWbKItaC1c+bceGS7UDrLim6Pm/Voo2jXCi6EHpXX2/THrJybRDwqmQi7UVWXR3aPx//q9busEXxRyeu0m4lq2AjhQWhOvfPjpJzNX1hRE9Bu7UKYJhUF6DAsXFFKpob0LoARpcjGLFLcO61yV6He2nQFAa+ULXxhrKbISzqO3Q2xMs2p3jQ4Ctm0T+03w9Y5/Yf1qNKaL6AayA2nf0thYgh+OHNEnnkFwvBnTyB5B32E+/cUy7bb3329Pz7h+ruLo5IhGZM5GiEjF4vOSZmZJZ1t2eR4U7oxX0VTpwFPPBUQ3O7A5C2l0g/pGCFda4QlgR5qRA09kaAd9VBSJbQABGH0zWlXNPAjPQ6M9CxxTv9lM/72RSzTvnJqjQNpWGQjYuTi++EN5QZ37Nmlcw9eSa6X1C97ADndWV46dlFowUUDXiczi+Q0bZmFtpvkRg0TWlicS/cURLIfpG7sGwgqIis5R4haQ+RDB1+4oC0xmncWqy7vMESW6trh+icEL2PybwGPnzdngUqEIw5fG9huX3BmxbJjukSjWWk2CH8AaY2lHRXttzpOhpfP9c1cmrwXXUuHwTFMiKdmdwSqGbgebUP25kB9priXO88Jri3Wb739KRV5M2k6/9AspCwpOqlKN6MZm2vElNI+cXSWMHeX3666p4ALr7Vu7+q7iw4s4cO09MMJWsaiTaZBsVRhdoocsej+091JM/yJ29TVDJEMp2vEiia8HQ4k2bH9W9XCB71cpygRMYTFRDJ3Yjly4MYg7whBQnkeu8IYagCY6UZ60V73qhKRZJKuiV6ZTC+objnMPMmi9Kd05WmYFab8ZDP8s4yhU0WJNXdZGwpX7pnoi0T+g/y94sfZNGs5QuKgNEX";
1297    #[allow(unused)]
1298    const TEST_VECTOR_PUBLIC_KEY_V2: &str = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz/+1jPJ1HqcaCdKrTPms8XJcvnmd9alI42U2XF/4GMNTM5KF1gI6snhR/23ZLatZRFMHoK8ZCMSpGNkjLadArz52ldceTvBOhQUiWylkZQ4NfNa3xIYJubXOmkeDyfNuyLxVZvcZOko9PdT+Qx2QxDrFi2XNo2I7aVFd19/COIEkex4mJ0eA3MHFpKCdxYbcTAsGID8+kVR9L84S1JptZoG8x+iB/D3/Q4y02UsQYpFTu0vbPY84YmW03ngJdxWzS8X4/UJI/jaEn5rO4xlU5QcL0l4IybP5LRpE9XEeUHATKVOG7eNfpe9zDfKV2qQoofQMH9VvkWO4psaWDjBSdwIDAQAB";
1299    #[allow(unused)]
1300    const TEST_VECTOR_SIGNED_PUBLIC_KEY_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8BoFkBTqNpYWxnb3JpdGhtAG1jb250ZW50Rm9ybWF0AGlwdWJsaWNLZXlZASYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP/7WM8nUepxoJ0qtM+azxcly+eZ31qUjjZTZcX/gYw1MzkoXWAjqyeFH/bdktq1lEUwegrxkIxKkY2SMtp0CvPnaV1x5O8E6FBSJbKWRlDg181rfEhgm5tc6aR4PJ827IvFVm9xk6Sj091P5DHZDEOsWLZc2jYjtpUV3X38I4gSR7HiYnR4DcwcWkoJ3FhtxMCwYgPz6RVH0vzhLUmm1mgbzH6IH8Pf9DjLTZSxBikVO7S9s9jzhiZbTeeAl3FbNLxfj9Qkj+NoSfms7jGVTlBwvSXgjJs/ktGkT1cR5QcBMpU4bt41+l73MN8pXapCih9Awf1W+RY7imxpYOMFJ3AgMBAAFYQMq/hT4wod2w8xyoM7D86ctuLNX4ZRo+jRHf2sZfaO7QsvonG/ZYuNKF5fq8wpxMRjfoMvnY2TTShbgzLrW8BA4=";
1301    const TEST_VECTOR_SIGNING_KEY_V2: &str = "7.g1gcowE6AAEReQMYZQRQsWZChQ6ae9D+Zlj5jo398aEFWBj8Gg/gn4tQKWO3nq5e/2p9gkzIrKD829RYT3aEUIDOetEtnFqRuQ3Cz13693WqDnKHM5Buzi6LcTsxo1jphYR7vlE5nYLjCpOCAftPN1oLfs5SCNkwwMENhujpVftfDzciE99aLEJDS9A=";
1302    #[allow(unused)]
1303    const TEST_VECTOR_VERIFYING_KEY_V2: &str =
1304        "pgEBAlAmkP0QgfdMVbIujX55W/yNAycEgQIgBiFYIEM6JxBmjWQTruAm3s6BTaJy1q6BzQetMBacNeRJ0kxR";
1305    const TEST_VECTOR_SECURITY_STATE_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8CoFgkomhlbnRpdHlJZFBHOOw2BI9OQoNq+Vl1xZZKZ3ZlcnNpb24CWEAlchbJR0vmRfShG8On7Q2gknjkw4Dd6MYBLiH4u+/CmfQdmjNZdf6kozgW/6NXyKVNu8dAsKsin+xxXkDyVZoG";
1306
1307    const TEST_USER_EMAIL: &str = "[email protected]";
1308    const TEST_USER_PASSWORD: &str = "asdfasdfasdf";
1309    const TEST_ACCOUNT_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
1310    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=";
1311
1312    async fn init_v2_account_with_master_password_and_upgrade_token(
1313        client: &Client,
1314        user_id: UserId,
1315        upgrade_token: V2UpgradeToken,
1316    ) {
1317        initialize_user_crypto(
1318            client,
1319            InitUserCryptoRequest {
1320                user_id: Some(user_id),
1321                kdf_params: Kdf::PBKDF2 {
1322                    iterations: 600_000.try_into().unwrap(),
1323                },
1324                email: TEST_USER_EMAIL.into(),
1325                account_cryptographic_state: WrappedAccountCryptographicState::V2 {
1326                    private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
1327                    signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
1328                    security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
1329                    signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
1330                },
1331                method: InitUserCryptoMethod::MasterPasswordUnlock {
1332                    password: TEST_USER_PASSWORD.into(),
1333                    master_password_unlock: MasterPasswordUnlockData {
1334                        kdf: Kdf::PBKDF2 {
1335                            iterations: 600_000.try_into().unwrap(),
1336                        },
1337                        master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
1338                        salt: TEST_USER_EMAIL.to_string(),
1339                    },
1340                },
1341                upgrade_token: Some(upgrade_token),
1342            },
1343        )
1344        .await
1345        .unwrap();
1346    }
1347
1348    #[tokio::test]
1349    async fn test_update_kdf() {
1350        let client = Client::new_test(None);
1351
1352        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();
1353
1354        let kdf = Kdf::PBKDF2 {
1355            iterations: 100_000.try_into().unwrap(),
1356        };
1357
1358        initialize_user_crypto(
1359            &client,
1360            InitUserCryptoRequest {
1361                user_id: Some(UserId::new_v4()),
1362                kdf_params: kdf.clone(),
1363                email: "[email protected]".into(),
1364                account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1365                method: InitUserCryptoMethod::MasterPasswordUnlock {
1366                    password: "asdfasdfasdf".into(),
1367                    master_password_unlock: MasterPasswordUnlockData {
1368                        kdf: kdf.clone(),
1369                        master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1370                        salt: "[email protected]".to_string(),
1371                    },
1372                },
1373                upgrade_token: None,
1374            },
1375        )
1376            .await
1377            .unwrap();
1378
1379        let new_kdf = Kdf::PBKDF2 {
1380            iterations: 600_000.try_into().unwrap(),
1381        };
1382        let new_kdf_response = make_update_kdf(&client, "123412341234", &new_kdf)
1383            .await
1384            .unwrap();
1385
1386        let client2 = Client::new_test(None);
1387
1388        initialize_user_crypto(
1389            &client2,
1390            InitUserCryptoRequest {
1391                user_id: Some(UserId::new_v4()),
1392                kdf_params: new_kdf.clone(),
1393                email: "[email protected]".into(),
1394                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1395                    private_key: priv_key.to_owned(),
1396                },
1397                method: InitUserCryptoMethod::MasterPasswordUnlock {
1398                    password: "123412341234".to_string(),
1399                    master_password_unlock: MasterPasswordUnlockData {
1400                        kdf: new_kdf.clone(),
1401                        master_key_wrapped_user_key: new_kdf_response
1402                            .master_password_unlock_data
1403                            .master_key_wrapped_user_key,
1404                        salt: "[email protected]".to_string(),
1405                    },
1406                },
1407                upgrade_token: None,
1408            },
1409        )
1410        .await
1411        .unwrap();
1412
1413        let new_hash = client2
1414            .kdf()
1415            .hash_password(
1416                "[email protected]".into(),
1417                "123412341234".into(),
1418                new_kdf.clone(),
1419                bitwarden_crypto::HashPurpose::ServerAuthorization,
1420            )
1421            .await
1422            .unwrap();
1423
1424        assert_eq!(
1425            new_hash,
1426            new_kdf_response
1427                .master_password_authentication_data
1428                .master_password_authentication_hash
1429        );
1430
1431        let client_key = {
1432            let key_store = client.internal.get_key_store();
1433            let ctx = key_store.context();
1434            #[allow(deprecated)]
1435            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1436                .unwrap()
1437                .to_base64()
1438        };
1439
1440        let client2_key = {
1441            let key_store = client2.internal.get_key_store();
1442            let ctx = key_store.context();
1443            #[allow(deprecated)]
1444            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1445                .unwrap()
1446                .to_base64()
1447        };
1448
1449        assert_eq!(client_key, client2_key);
1450    }
1451
1452    #[tokio::test]
1453    async fn test_update_password() {
1454        let client = Client::new_test(None);
1455
1456        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();
1457
1458        let kdf = Kdf::PBKDF2 {
1459            iterations: 100_000.try_into().unwrap(),
1460        };
1461
1462        initialize_user_crypto(
1463            &client,
1464            InitUserCryptoRequest {
1465                user_id: Some(UserId::new_v4()),
1466                kdf_params: kdf.clone(),
1467                email: "[email protected]".into(),
1468                account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1469                method: InitUserCryptoMethod::MasterPasswordUnlock {
1470                    password: "asdfasdfasdf".to_string(),
1471                    master_password_unlock: MasterPasswordUnlockData {
1472                        kdf: kdf.clone(),
1473                        master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1474                        salt: "[email protected]".to_string(),
1475                    },
1476                },
1477                upgrade_token: None,
1478            },
1479        )
1480            .await
1481            .unwrap();
1482
1483        let new_password_response = make_update_password(&client, "123412341234".into())
1484            .await
1485            .unwrap();
1486
1487        let client2 = Client::new_test(None);
1488
1489        initialize_user_crypto(
1490            &client2,
1491            InitUserCryptoRequest {
1492                user_id: Some(UserId::new_v4()),
1493                kdf_params: kdf.clone(),
1494                email: "[email protected]".into(),
1495                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1496                    private_key: priv_key.to_owned(),
1497                },
1498                method: InitUserCryptoMethod::MasterPasswordUnlock {
1499                    password: "123412341234".into(),
1500                    master_password_unlock: MasterPasswordUnlockData {
1501                        kdf: kdf.clone(),
1502                        master_key_wrapped_user_key: new_password_response.new_key,
1503                        salt: "[email protected]".to_string(),
1504                    },
1505                },
1506                upgrade_token: None,
1507            },
1508        )
1509        .await
1510        .unwrap();
1511
1512        let new_hash = client2
1513            .kdf()
1514            .hash_password(
1515                "[email protected]".into(),
1516                "123412341234".into(),
1517                kdf.clone(),
1518                bitwarden_crypto::HashPurpose::ServerAuthorization,
1519            )
1520            .await
1521            .unwrap();
1522
1523        assert_eq!(new_hash, new_password_response.password_hash);
1524
1525        let client_key = {
1526            let key_store = client.internal.get_key_store();
1527            let ctx = key_store.context();
1528            #[allow(deprecated)]
1529            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1530                .unwrap()
1531                .to_base64()
1532        };
1533
1534        let client2_key = {
1535            let key_store = client2.internal.get_key_store();
1536            let ctx = key_store.context();
1537            #[allow(deprecated)]
1538            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1539                .unwrap()
1540                .to_base64()
1541        };
1542
1543        assert_eq!(client_key, client2_key);
1544    }
1545
1546    #[tokio::test]
1547    async fn test_initialize_user_crypto_pin() {
1548        let client = Client::new_test(None);
1549
1550        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();
1551
1552        initialize_user_crypto(
1553            &client,
1554            InitUserCryptoRequest {
1555                user_id: Some(UserId::new_v4()),
1556                kdf_params: Kdf::PBKDF2 {
1557                    iterations: 100_000.try_into().unwrap(),
1558                },
1559                email: "[email protected]".into(),
1560                account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1561                method: InitUserCryptoMethod::MasterPasswordUnlock {
1562                    password: "asdfasdfasdf".into(),
1563                    master_password_unlock: MasterPasswordUnlockData {
1564                        kdf: Kdf::PBKDF2 {
1565                            iterations: 100_000.try_into().unwrap(),
1566                        },
1567                        master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1568                        salt: "[email protected]".to_string(),
1569                    },
1570                },
1571                upgrade_token: None,
1572            },
1573        )
1574            .await
1575            .unwrap();
1576
1577        let pin_key = derive_pin_key(&client, "1234".into()).await.unwrap();
1578
1579        // Verify we can unlock with the pin
1580        let client2 = Client::new_test(None);
1581        initialize_user_crypto(
1582            &client2,
1583            InitUserCryptoRequest {
1584                user_id: Some(UserId::new_v4()),
1585                kdf_params: Kdf::PBKDF2 {
1586                    iterations: 100_000.try_into().unwrap(),
1587                },
1588                email: "[email protected]".into(),
1589                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1590                    private_key: priv_key.to_owned(),
1591                },
1592                method: InitUserCryptoMethod::Pin {
1593                    pin: "1234".into(),
1594                    pin_protected_user_key: pin_key.pin_protected_user_key,
1595                },
1596                upgrade_token: None,
1597            },
1598        )
1599        .await
1600        .unwrap();
1601
1602        let client_key = {
1603            let key_store = client.internal.get_key_store();
1604            let ctx = key_store.context();
1605            #[allow(deprecated)]
1606            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1607                .unwrap()
1608                .to_base64()
1609        };
1610
1611        let client2_key = {
1612            let key_store = client2.internal.get_key_store();
1613            let ctx = key_store.context();
1614            #[allow(deprecated)]
1615            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1616                .unwrap()
1617                .to_base64()
1618        };
1619
1620        assert_eq!(client_key, client2_key);
1621
1622        // Verify we can derive the pin protected user key from the encrypted pin
1623        let pin_protected_user_key = derive_pin_user_key(&client, pin_key.encrypted_pin)
1624            .await
1625            .unwrap();
1626
1627        let client3 = Client::new_test(None);
1628
1629        initialize_user_crypto(
1630            &client3,
1631            InitUserCryptoRequest {
1632                user_id: Some(UserId::new_v4()),
1633                kdf_params: Kdf::PBKDF2 {
1634                    iterations: 100_000.try_into().unwrap(),
1635                },
1636                email: "[email protected]".into(),
1637                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1638                    private_key: priv_key.to_owned(),
1639                },
1640                method: InitUserCryptoMethod::Pin {
1641                    pin: "1234".into(),
1642                    pin_protected_user_key,
1643                },
1644                upgrade_token: None,
1645            },
1646        )
1647        .await
1648        .unwrap();
1649
1650        let client_key = {
1651            let key_store = client.internal.get_key_store();
1652            let ctx = key_store.context();
1653            #[allow(deprecated)]
1654            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1655                .unwrap()
1656                .to_base64()
1657        };
1658
1659        let client3_key = {
1660            let key_store = client3.internal.get_key_store();
1661            let ctx = key_store.context();
1662            #[allow(deprecated)]
1663            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1664                .unwrap()
1665                .to_base64()
1666        };
1667
1668        assert_eq!(client_key, client3_key);
1669    }
1670
1671    #[tokio::test]
1672    async fn test_initialize_user_crypto_pin_envelope() {
1673        let user_key = "5yKAZ4TSSEGje54MV5lc5ty6crkqUz4xvl+8Dm/piNLKf6OgRi2H0uzttNTXl9z6ILhkmuIXzGpAVc2YdorHgQ==";
1674        let test_pin = "1234";
1675
1676        let client1 = Client::new_test(None);
1677        initialize_user_crypto(
1678            &client1,
1679            InitUserCryptoRequest {
1680                user_id: Some(UserId::new_v4()),
1681                kdf_params: Kdf::PBKDF2 {
1682                    iterations: 100_000.try_into().unwrap(),
1683                },
1684                email: "[email protected]".into(),
1685                account_cryptographic_state: {
1686                    let store: KeyStore<KeySlotIds> = KeyStore::default();
1687                    let mut ctx = store.context_mut();
1688                    WrappedAccountCryptographicState::make_v1(&mut ctx)
1689                        .unwrap()
1690                        .1
1691                },
1692                method: InitUserCryptoMethod::DecryptedKey {
1693                    decrypted_user_key: user_key.to_string(),
1694                },
1695                upgrade_token: None,
1696            },
1697        )
1698        .await
1699        .unwrap();
1700
1701        let enroll_response = client1.crypto().enroll_pin(test_pin.to_string()).unwrap();
1702
1703        let client2 = Client::new_test(None);
1704        initialize_user_crypto(
1705            &client2,
1706            InitUserCryptoRequest {
1707                user_id: Some(UserId::new_v4()),
1708                // NOTE: THIS CHANGES KDF SETTINGS. We ensure in this test that even with different
1709                // KDF settings the pin can unlock the user key.
1710                kdf_params: Kdf::PBKDF2 {
1711                    iterations: 600_000.try_into().unwrap(),
1712                },
1713                email: "[email protected]".into(),
1714                account_cryptographic_state: {
1715                    let store: KeyStore<KeySlotIds> = KeyStore::default();
1716                    let mut ctx = store.context_mut();
1717                    WrappedAccountCryptographicState::make_v1(&mut ctx)
1718                        .unwrap()
1719                        .1
1720                },
1721                method: InitUserCryptoMethod::PinEnvelope {
1722                    pin: test_pin.to_string(),
1723                    pin_protected_user_key_envelope: enroll_response
1724                        .pin_protected_user_key_envelope,
1725                },
1726                upgrade_token: None,
1727            },
1728        )
1729        .await
1730        .unwrap();
1731    }
1732
1733    #[tokio::test]
1734    async fn test_initialize_user_crypto_pin_state() {
1735        use crate::key_management::pin_lock_system::PinLockType;
1736
1737        let client1 = Client::init_test_account(test_bitwarden_com_account()).await;
1738        client1
1739            .km_state_bridge()
1740            .register_bridge(Box::new(InMemoryStateBridge::default()));
1741
1742        PinLockSystem::with_client(&client1)
1743            .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
1744            .await
1745            .expect("set_pin succeeds");
1746
1747        let persistent_envelope = client1
1748            .km_state_bridge()
1749            .get_persistent_pin_envelope()
1750            .await
1751            .expect("persistent pin envelope present after BFU set_pin");
1752        let encrypted_pin = client1
1753            .km_state_bridge()
1754            .get_encrypted_pin()
1755            .await
1756            .expect("encrypted pin present after set_pin");
1757
1758        // Fresh client simulating an app restart with the same persisted PIN state.
1759        let client2 = Client::init_test_account(test_bitwarden_com_account()).await;
1760        client2
1761            .km_state_bridge()
1762            .register_bridge(Box::new(InMemoryStateBridge::default()));
1763        client2
1764            .km_state_bridge()
1765            .set_persistent_pin_envelope(&persistent_envelope)
1766            .await;
1767        client2
1768            .km_state_bridge()
1769            .set_encrypted_pin(&encrypted_pin)
1770            .await;
1771
1772        let client1_key = {
1773            let key_store = client1.internal.get_key_store();
1774            let ctx = key_store.context();
1775            #[allow(deprecated)]
1776            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1777                .unwrap()
1778                .to_owned()
1779        };
1780
1781        let client2_key = {
1782            let key_store = client2.internal.get_key_store();
1783            let ctx = key_store.context();
1784            #[allow(deprecated)]
1785            ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1786                .unwrap()
1787                .to_owned()
1788        };
1789
1790        assert_eq!(client1_key, client2_key);
1791    }
1792
1793    #[test]
1794    fn test_enroll_admin_password_reset() {
1795        let client = Client::new(None);
1796
1797        let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap();
1798        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();
1799        client
1800            .internal
1801            .initialize_user_crypto_master_password_unlock(
1802                "asdfasdfasdf".to_string(),
1803                MasterPasswordUnlockData {
1804                    kdf: Kdf::PBKDF2 {
1805                        iterations: NonZeroU32::new(600_000).unwrap(),
1806                    },
1807                    master_key_wrapped_user_key: user_key,
1808                    salt: "[email protected]".to_string(),
1809                },
1810                WrappedAccountCryptographicState::V1 { private_key },
1811                &None,
1812            )
1813            .unwrap();
1814
1815        let public_key: B64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsy7RFHcX3C8Q4/OMmhhbFReYWfB45W9PDTEA8tUZwZmtOiN2RErIS2M1c+K/4HoDJ/TjpbX1f2MZcr4nWvKFuqnZXyewFc+jmvKVewYi+NAu2++vqKq2kKcmMNhwoQDQdQIVy/Uqlp4Cpi2cIwO6ogq5nHNJGR3jm+CpyrafYlbz1bPvL3hbyoGDuG2tgADhyhXUdFuef2oF3wMvn1lAJAvJnPYpMiXUFmj1ejmbwtlxZDrHgUJvUcp7nYdwUKaFoi+sOttHn3u7eZPtNvxMjhSS/X/1xBIzP/mKNLdywH5LoRxniokUk+fV3PYUxJsiU3lV0Trc/tH46jqd8ZGjmwIDAQAB".parse().unwrap();
1816
1817        let encrypted = enroll_admin_password_reset(&client, public_key).unwrap();
1818
1819        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();
1820
1821        let private_key = Pkcs8PrivateKeyBytes::from(private_key.as_bytes());
1822        let private_key = PrivateKey::from_der(&private_key).unwrap();
1823        #[expect(deprecated)]
1824        let decrypted: SymmetricCryptoKey =
1825            encrypted.decapsulate_key_unsigned(&private_key).unwrap();
1826
1827        let key_store = client.internal.get_key_store();
1828        let ctx = key_store.context();
1829        #[allow(deprecated)]
1830        let expected = ctx
1831            .dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1832            .unwrap();
1833
1834        assert_eq!(decrypted, *expected);
1835    }
1836
1837    #[test]
1838    fn test_derive_key_connector() {
1839        let request = DeriveKeyConnectorRequest {
1840            password: "asdfasdfasdf".to_string(),
1841            email: "[email protected]".to_string(),
1842            kdf: Kdf::PBKDF2 {
1843                iterations: NonZeroU32::new(600_000).unwrap(),
1844            },
1845            user_key_encrypted: "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(),
1846        };
1847
1848        let result = derive_key_connector(request).unwrap();
1849
1850        assert_eq!(
1851            result.to_string(),
1852            "ySXq1RVLKEaV1eoQE/ui9aFKIvXTl9PAXwp1MljfF50="
1853        );
1854    }
1855
1856    fn setup_asymmetric_keys_test() -> (UserKey, RsaKeyPair) {
1857        let master_key = MasterKey::derive(
1858            "asdfasdfasdf",
1859            "[email protected]",
1860            &Kdf::PBKDF2 {
1861                iterations: NonZeroU32::new(600_000).unwrap(),
1862            },
1863        )
1864        .unwrap();
1865        let user_key = (master_key.make_user_key().unwrap()).0;
1866        let key_pair = user_key.make_key_pair().unwrap();
1867
1868        (user_key, key_pair)
1869    }
1870
1871    #[test]
1872    fn test_make_key_pair() {
1873        let (user_key, _) = setup_asymmetric_keys_test();
1874
1875        let response = make_key_pair(user_key.0.to_base64()).unwrap();
1876
1877        assert!(!response.user_public_key.to_string().is_empty());
1878        let encrypted_private_key = response.user_key_encrypted_private_key;
1879        let private_key: Vec<u8> = encrypted_private_key.decrypt_with_key(&user_key.0).unwrap();
1880        assert!(!private_key.is_empty());
1881    }
1882
1883    #[test]
1884    fn test_verify_asymmetric_keys_success() {
1885        let (user_key, key_pair) = setup_asymmetric_keys_test();
1886
1887        let request = VerifyAsymmetricKeysRequest {
1888            user_key: user_key.0.to_base64(),
1889            user_public_key: key_pair.public,
1890            user_key_encrypted_private_key: key_pair.private,
1891        };
1892        let response = verify_asymmetric_keys(request).unwrap();
1893
1894        assert!(response.private_key_decryptable);
1895        assert!(response.valid_private_key);
1896    }
1897
1898    #[test]
1899    fn test_verify_asymmetric_keys_decrypt_failed() {
1900        let (user_key, key_pair) = setup_asymmetric_keys_test();
1901        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();
1902
1903        let request = VerifyAsymmetricKeysRequest {
1904            user_key: user_key.0.to_base64(),
1905            user_public_key: key_pair.public,
1906            user_key_encrypted_private_key: undecryptable_private_key,
1907        };
1908        let response = verify_asymmetric_keys(request).unwrap();
1909
1910        assert!(!response.private_key_decryptable);
1911        assert!(!response.valid_private_key);
1912    }
1913
1914    #[test]
1915    fn test_verify_asymmetric_keys_parse_failed() {
1916        let (user_key, key_pair) = setup_asymmetric_keys_test();
1917
1918        let invalid_private_key = "bad_key".to_string().encrypt_with_key(&user_key.0).unwrap();
1919
1920        let request = VerifyAsymmetricKeysRequest {
1921            user_key: user_key.0.to_base64(),
1922            user_public_key: key_pair.public,
1923            user_key_encrypted_private_key: invalid_private_key,
1924        };
1925        let response = verify_asymmetric_keys(request).unwrap();
1926
1927        assert!(response.private_key_decryptable);
1928        assert!(!response.valid_private_key);
1929    }
1930
1931    #[test]
1932    fn test_verify_asymmetric_keys_key_mismatch() {
1933        let (user_key, key_pair) = setup_asymmetric_keys_test();
1934        let new_key_pair = user_key.make_key_pair().unwrap();
1935
1936        let request = VerifyAsymmetricKeysRequest {
1937            user_key: user_key.0.to_base64(),
1938            user_public_key: key_pair.public,
1939            user_key_encrypted_private_key: new_key_pair.private,
1940        };
1941        let response = verify_asymmetric_keys(request).unwrap();
1942
1943        assert!(response.private_key_decryptable);
1944        assert!(!response.valid_private_key);
1945    }
1946
1947    #[tokio::test]
1948    async fn test_make_v2_keys_for_v1_user() {
1949        let client = Client::new_test(None);
1950
1951        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();
1952        let encrypted_userkey: EncString = "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap();
1953
1954        initialize_user_crypto(
1955            &client,
1956            InitUserCryptoRequest {
1957                user_id: Some(UserId::new_v4()),
1958                kdf_params: Kdf::PBKDF2 {
1959                    iterations: 100_000.try_into().unwrap(),
1960                },
1961                email: "[email protected]".into(),
1962                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1963                    private_key: priv_key.to_owned(),
1964                },
1965                method: InitUserCryptoMethod::MasterPasswordUnlock {
1966                    password: "asdfasdfasdf".into(),
1967                    master_password_unlock: MasterPasswordUnlockData {
1968                        kdf: Kdf::PBKDF2 {
1969                            iterations: 100_000.try_into().unwrap(),
1970                        },
1971                        master_key_wrapped_user_key: encrypted_userkey.clone(),
1972                        salt: "[email protected]".into(),
1973                    },
1974                },
1975                upgrade_token: None,
1976            },
1977        )
1978        .await
1979        .unwrap();
1980
1981        let master_key = MasterKey::derive(
1982            "asdfasdfasdf",
1983            "[email protected]",
1984            &Kdf::PBKDF2 {
1985                iterations: NonZeroU32::new(100_000).unwrap(),
1986            },
1987        )
1988        .unwrap();
1989        #[expect(deprecated)]
1990        let enrollment_response = make_v2_keys_for_v1_user(&client).unwrap();
1991        let encrypted_userkey_v2 = master_key
1992            .encrypt_user_key(
1993                &SymmetricCryptoKey::try_from(enrollment_response.clone().user_key).unwrap(),
1994            )
1995            .unwrap();
1996
1997        let client2 = Client::new_test(None);
1998
1999        initialize_user_crypto(
2000            &client2,
2001            InitUserCryptoRequest {
2002                user_id: Some(UserId::new_v4()),
2003                kdf_params: Kdf::PBKDF2 {
2004                    iterations: 100_000.try_into().unwrap(),
2005                },
2006                email: "[email protected]".into(),
2007                account_cryptographic_state: WrappedAccountCryptographicState::V2 {
2008                    private_key: enrollment_response.private_key,
2009                    signing_key: enrollment_response.signing_key,
2010                    security_state: enrollment_response.security_state,
2011                    signed_public_key: Some(enrollment_response.signed_public_key),
2012                },
2013                method: InitUserCryptoMethod::MasterPasswordUnlock {
2014                    password: "asdfasdfasdf".into(),
2015                    master_password_unlock: MasterPasswordUnlockData {
2016                        kdf: Kdf::PBKDF2 {
2017                            iterations: 100_000.try_into().unwrap(),
2018                        },
2019                        master_key_wrapped_user_key: encrypted_userkey_v2,
2020                        salt: "[email protected]".to_string(),
2021                    },
2022                },
2023                upgrade_token: None,
2024            },
2025        )
2026        .await
2027        .unwrap();
2028    }
2029
2030    #[tokio::test]
2031    async fn test_make_v2_keys_for_v1_user_with_v2_user_fails() {
2032        let client = Client::new_test(None);
2033
2034        initialize_user_crypto(
2035            &client,
2036            InitUserCryptoRequest {
2037                user_id: Some(UserId::new_v4()),
2038                kdf_params: Kdf::PBKDF2 {
2039                    iterations: 100_000.try_into().unwrap(),
2040                },
2041                email: "[email protected]".into(),
2042                account_cryptographic_state: WrappedAccountCryptographicState::V2 {
2043                    private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
2044                    signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
2045                    security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
2046                    signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
2047                },
2048                method: InitUserCryptoMethod::DecryptedKey {
2049                    decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
2050                },
2051                upgrade_token: None,
2052            },
2053        )
2054        .await
2055        .unwrap();
2056
2057        #[expect(deprecated)]
2058        let result = make_v2_keys_for_v1_user(&client);
2059        assert!(matches!(
2060            result,
2061            Err(StatefulCryptoError::WrongAccountCryptoVersion {
2062                expected: _,
2063                got: _
2064            })
2065        ));
2066    }
2067
2068    #[test]
2069    fn test_get_v2_rotated_account_keys_non_v2_user() {
2070        let client = Client::new(None);
2071        let mut ctx = client.internal.get_key_store().context_mut();
2072        let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
2073        ctx.persist_symmetric_key(local_key_id, SymmetricKeySlotId::User)
2074            .unwrap();
2075        drop(ctx);
2076
2077        #[expect(deprecated)]
2078        let result = get_v2_rotated_account_keys(&client);
2079        assert!(matches!(
2080            result,
2081            Err(StatefulCryptoError::WrongAccountCryptoVersion {
2082                expected: _,
2083                got: _
2084            })
2085        ));
2086    }
2087
2088    #[tokio::test]
2089    async fn test_get_v2_rotated_account_keys() {
2090        let client = Client::new_test(None);
2091
2092        initialize_user_crypto(
2093            &client,
2094            InitUserCryptoRequest {
2095                user_id: Some(UserId::new_v4()),
2096                kdf_params: Kdf::PBKDF2 {
2097                    iterations: 100_000.try_into().unwrap(),
2098                },
2099                email: "[email protected]".into(),
2100                account_cryptographic_state: WrappedAccountCryptographicState::V2 {
2101                    private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
2102                    signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
2103                    security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
2104                    signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
2105                },
2106                method: InitUserCryptoMethod::DecryptedKey {
2107                    decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
2108                },
2109                upgrade_token: None,
2110            },
2111        )
2112        .await
2113        .unwrap();
2114
2115        #[expect(deprecated)]
2116        let result = get_v2_rotated_account_keys(&client);
2117        assert!(result.is_ok());
2118    }
2119
2120    #[tokio::test]
2121    async fn test_initialize_user_crypto_master_password_unlock() {
2122        let client = Client::new_test(None);
2123
2124        initialize_user_crypto(
2125            &client,
2126            InitUserCryptoRequest {
2127                user_id: Some(UserId::new_v4()),
2128                kdf_params: Kdf::PBKDF2 {
2129                    iterations: 600_000.try_into().unwrap(),
2130                },
2131                email: TEST_USER_EMAIL.to_string(),
2132                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
2133                    private_key: TEST_ACCOUNT_PRIVATE_KEY.parse().unwrap(),
2134                },
2135                method: InitUserCryptoMethod::MasterPasswordUnlock {
2136                    password: TEST_USER_PASSWORD.to_string(),
2137                    master_password_unlock: MasterPasswordUnlockData {
2138                        kdf: Kdf::PBKDF2 {
2139                            iterations: 600_000.try_into().unwrap(),
2140                        },
2141                        master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
2142                        salt: TEST_USER_EMAIL.to_string(),
2143                    },
2144                },
2145                upgrade_token: None,
2146            },
2147        )
2148        .await
2149        .unwrap();
2150
2151        let key_store = client.internal.get_key_store();
2152        {
2153            let context = key_store.context();
2154            assert!(context.has_symmetric_key(SymmetricKeySlotId::User));
2155            assert!(context.has_private_key(PrivateKeySlotId::UserPrivateKey));
2156        }
2157        let login_method = client.internal.get_login_method().await.unwrap();
2158        if let UserLoginMethod::Username {
2159            email,
2160            kdf,
2161            client_id,
2162            ..
2163        } = login_method
2164        {
2165            assert_eq!(&email, TEST_USER_EMAIL);
2166            assert_eq!(
2167                kdf,
2168                Kdf::PBKDF2 {
2169                    iterations: 600_000.try_into().unwrap(),
2170                }
2171            );
2172            assert_eq!(&client_id, "");
2173        } else {
2174            panic!("Expected username login method");
2175        }
2176    }
2177
2178    #[tokio::test]
2179    async fn test_make_user_tde_registration() {
2180        let user_id = UserId::new_v4();
2181        let email = "[email protected]";
2182        let kdf = Kdf::PBKDF2 {
2183            iterations: NonZeroU32::new(600_000).expect("valid iteration count"),
2184        };
2185
2186        // Generate a mock organization public key for TDE enrollment
2187        let org_key = PrivateKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
2188        let org_public_key_der = org_key
2189            .to_public_key()
2190            .to_der()
2191            .expect("valid public key DER");
2192        let org_public_key = B64::from(org_public_key_der.as_ref().to_vec());
2193
2194        // Create a client and generate TDE registration keys
2195        let registration_client = Client::new_test(None);
2196        let make_keys_response = registration_client
2197            .crypto()
2198            .make_user_tde_registration(org_public_key)
2199            .expect("TDE registration should succeed");
2200
2201        // Initialize a new client using the TDE device key
2202        let unlock_client = Client::new_test(None);
2203        unlock_client
2204            .crypto()
2205            .initialize_user_crypto(InitUserCryptoRequest {
2206                user_id: Some(user_id),
2207                kdf_params: kdf,
2208                email: email.to_owned(),
2209                account_cryptographic_state: make_keys_response.account_cryptographic_state,
2210                method: InitUserCryptoMethod::DeviceKey {
2211                    device_key: make_keys_response
2212                        .trusted_device_keys
2213                        .device_key
2214                        .to_string(),
2215                    protected_device_private_key: make_keys_response
2216                        .trusted_device_keys
2217                        .protected_device_private_key,
2218                    device_protected_user_key: make_keys_response
2219                        .trusted_device_keys
2220                        .protected_user_key,
2221                },
2222                upgrade_token: None,
2223            })
2224            .await
2225            .expect("initializing user crypto with TDE device key should succeed");
2226
2227        // Verify we can retrieve the user encryption key
2228        let retrieved_key = unlock_client
2229            .crypto()
2230            .get_user_encryption_key()
2231            .await
2232            .expect("should be able to get user encryption key");
2233
2234        // The retrieved key should be a valid symmetric key
2235        let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
2236            .expect("retrieved key should be valid symmetric key");
2237
2238        // Verify that the org key can decrypt the admin_reset_key UnsignedSharedKey
2239        // and that the decrypted key matches the user's encryption key
2240        #[expect(deprecated)]
2241        let decrypted_user_key = make_keys_response
2242            .reset_password_key
2243            .decapsulate_key_unsigned(&org_key)
2244            .expect("org key should be able to decrypt admin reset key");
2245        assert_eq!(
2246            retrieved_symmetric_key, decrypted_user_key,
2247            "decrypted admin reset key should match the user's encryption key"
2248        );
2249    }
2250
2251    #[tokio::test]
2252    async fn test_make_user_key_connector_registration_success() {
2253        let user_id = UserId::new_v4();
2254        let email = "[email protected]";
2255        let registration_client = Client::new(None);
2256
2257        let make_keys_response = make_user_key_connector_registration(&registration_client);
2258        assert!(make_keys_response.is_ok());
2259        let make_keys_response = make_keys_response.unwrap();
2260
2261        // Initialize a new client using the key connector key
2262        let unlock_client = Client::new_test(None);
2263        unlock_client
2264            .crypto()
2265            .initialize_user_crypto(InitUserCryptoRequest {
2266                user_id: Some(user_id),
2267                kdf_params: Kdf::default_argon2(),
2268                email: email.to_owned(),
2269                account_cryptographic_state: make_keys_response.account_cryptographic_state,
2270                method: InitUserCryptoMethod::KeyConnector {
2271                    user_key: make_keys_response
2272                        .key_connector_key_wrapped_user_key
2273                        .clone(),
2274                    master_key: make_keys_response.key_connector_key.clone().into(),
2275                },
2276                upgrade_token: None,
2277            })
2278            .await
2279            .expect("initializing user crypto with key connector key should succeed");
2280
2281        // Verify we can retrieve the user encryption key
2282        let retrieved_key = unlock_client
2283            .crypto()
2284            .get_user_encryption_key()
2285            .await
2286            .expect("should be able to get user encryption key");
2287
2288        // The retrieved key should be a valid symmetric key
2289        let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
2290            .expect("retrieved key should be valid symmetric key");
2291
2292        assert_eq!(retrieved_symmetric_key, make_keys_response.user_key);
2293
2294        let decrypted_user_key = make_keys_response
2295            .key_connector_key
2296            .decrypt_user_key(make_keys_response.key_connector_key_wrapped_user_key);
2297        assert_eq!(retrieved_symmetric_key, decrypted_user_key.unwrap());
2298    }
2299
2300    #[tokio::test]
2301    async fn test_initialize_user_crypto_with_upgrade_token_upgrades_v1_to_v2() {
2302        let client1 = Client::init_test_account(test_bitwarden_com_account()).await;
2303
2304        let expected_v2_key =
2305            SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
2306        let upgrade_token = {
2307            let mut ctx = client1.internal.get_key_store().context_mut();
2308            let v2_key_id = ctx.add_local_symmetric_key(expected_v2_key.clone());
2309            V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap()
2310        };
2311
2312        let client2 = Client::new_test(None);
2313        init_v2_account_with_master_password_and_upgrade_token(
2314            &client2,
2315            UserId::new_v4(),
2316            upgrade_token,
2317        )
2318        .await;
2319
2320        // The active user key must now be V2 and match the test-vector V2 key.
2321        let result_key =
2322            SymmetricCryptoKey::try_from(get_user_encryption_key(&client2).await.unwrap()).unwrap();
2323        assert!(
2324            matches!(result_key, SymmetricCryptoKey::XAes256GcmKey(_)),
2325            "User key should be upgraded to V2 after initialization with upgrade token"
2326        );
2327        assert_eq!(result_key, expected_v2_key);
2328    }
2329
2330    #[tokio::test]
2331    async fn test_initialize_user_crypto_with_upgrade_token_ignored_for_v2_key() {
2332        let dummy_token = {
2333            let key_store = KeyStore::<KeySlotIds>::default();
2334            let mut ctx = key_store.context_mut();
2335            let v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
2336            let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
2337            V2UpgradeToken::create(v1_id, v2_id, &ctx).unwrap()
2338        };
2339
2340        let client = Client::new_test(None);
2341        initialize_user_crypto(
2342            &client,
2343            InitUserCryptoRequest {
2344                user_id: Some(UserId::new_v4()),
2345                kdf_params: Kdf::PBKDF2 {
2346                    iterations: 100_000.try_into().unwrap(),
2347                },
2348                email: "[email protected]".into(),
2349                account_cryptographic_state: WrappedAccountCryptographicState::V2 {
2350                    private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
2351                    signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
2352                    security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
2353                    signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
2354                },
2355                method: InitUserCryptoMethod::DecryptedKey {
2356                    decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
2357                },
2358                upgrade_token: Some(dummy_token),
2359            },
2360        )
2361        .await
2362        .unwrap();
2363
2364        // The upgrade token must have been ignored; the original V2 key must still be active
2365        let result_key =
2366            SymmetricCryptoKey::try_from(get_user_encryption_key(&client).await.unwrap()).unwrap();
2367        assert!(
2368            matches!(result_key, SymmetricCryptoKey::XAes256GcmKey(_)),
2369            "Upgrade token must be ignored for a V2 user key"
2370        );
2371        let expected_key =
2372            SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
2373        assert_eq!(result_key, expected_key);
2374    }
2375
2376    #[tokio::test]
2377    async fn test_initialize_user_crypto_with_invalid_upgrade_token_fails() {
2378        // Token built with a different V1 key — decryption with the test account's V1 key fails.
2379        let mismatched_token = {
2380            let key_store = KeyStore::<KeySlotIds>::default();
2381            let mut ctx = key_store.context_mut();
2382            let wrong_v1_id = ctx.generate_symmetric_key();
2383            let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
2384            V2UpgradeToken::create(wrong_v1_id, v2_id, &ctx).unwrap()
2385        };
2386
2387        let client = Client::new_test(None);
2388        let result = initialize_user_crypto(
2389            &client,
2390            InitUserCryptoRequest {
2391                user_id: Some(UserId::new_v4()),
2392                kdf_params: Kdf::PBKDF2 {
2393                    iterations: 600_000.try_into().unwrap(),
2394                },
2395                email: "[email protected]".into(),
2396                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
2397                    // The private key is never decrypted because the token fails first.
2398                    private_key: TEST_ACCOUNT_PRIVATE_KEY.parse().unwrap(),
2399                },
2400                method: InitUserCryptoMethod::MasterPasswordUnlock {
2401                    password: "asdfasdfasdf".into(),
2402                    master_password_unlock: MasterPasswordUnlockData {
2403                        kdf: Kdf::PBKDF2 {
2404                            iterations: 600_000.try_into().unwrap(),
2405                        },
2406                        master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
2407                        salt: "[email protected]".to_string(),
2408                    },
2409                },
2410                upgrade_token: Some(mismatched_token),
2411            },
2412        )
2413        .await;
2414
2415        assert!(
2416            matches!(result, Err(EncryptionSettingsError::InvalidUpgradeToken)),
2417            "Initialization with a mismatched upgrade token should fail"
2418        );
2419    }
2420
2421    #[tokio::test]
2422    async fn test_initialize_user_local_data_key_sets_local_user_data_key_equal_to_user_key() {
2423        let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
2424        initialize_user_local_data_key(&client)
2425            .await
2426            .expect("initialize_user_local_data_key should succeed");
2427
2428        // Verify LocalUserData key equals the User key: data encrypted with User
2429        // must be decryptable with LocalUserData.
2430        let key_store = client.internal.get_key_store();
2431        let mut ctx = key_store.context_mut();
2432        let plaintext = "test";
2433        let ciphertext = plaintext
2434            .encrypt(&mut ctx, SymmetricKeySlotId::User)
2435            .expect("encryption with user key should succeed");
2436        let decrypted: String = ciphertext
2437            .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2438            .expect("decryption with local user data key should succeed");
2439        assert_eq!(decrypted, plaintext);
2440    }
2441
2442    #[tokio::test]
2443    async fn test_initialize_org_crypto_persists_org_keys() {
2444        use crate::{OrganizationId, client::persisted_state::OrganizationSharedKey};
2445
2446        let client = Client::init_test_account(test_bitwarden_com_account()).await;
2447
2448        let org_id: OrganizationId = "1bc9ac1e-f5aa-45f2-94bf-b181009709b8".parse().unwrap();
2449
2450        let repo = client
2451            .internal
2452            .state_registry
2453            .get::<OrganizationSharedKey>()
2454            .expect("OrganizationSharedKey repository should be available");
2455
2456        let persisted = repo
2457            .get(org_id)
2458            .await
2459            .expect("repository get should not fail");
2460
2461        let entry = persisted.expect("org key should be persisted after initialize_org_crypto");
2462        assert_eq!(entry.org_id, org_id);
2463    }
2464
2465    #[tokio::test]
2466    async fn test_initialize_user_crypto_persists_account_crypto_state() {
2467        use crate::client::persisted_state::ACCOUNT_CRYPTO_STATE;
2468
2469        let account_crypto_state = WrappedAccountCryptographicState::V1 {
2470            private_key: TEST_ACCOUNT_PRIVATE_KEY.parse().unwrap(),
2471        };
2472
2473        let client = Client::new_test(None);
2474        initialize_user_crypto(
2475            &client,
2476            InitUserCryptoRequest {
2477                user_id: Some(UserId::new_v4()),
2478                kdf_params: Kdf::PBKDF2 {
2479                    iterations: 600_000.try_into().unwrap(),
2480                },
2481                email: TEST_USER_EMAIL.into(),
2482                account_cryptographic_state: account_crypto_state.clone(),
2483                method: InitUserCryptoMethod::MasterPasswordUnlock {
2484                    password: TEST_USER_PASSWORD.into(),
2485                    master_password_unlock: MasterPasswordUnlockData {
2486                        kdf: Kdf::PBKDF2 {
2487                            iterations: 600_000.try_into().unwrap(),
2488                        },
2489                        master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
2490                        salt: TEST_USER_EMAIL.to_string(),
2491                    },
2492                },
2493                upgrade_token: None,
2494            },
2495        )
2496        .await
2497        .unwrap();
2498
2499        let persisted = client
2500            .internal
2501            .state_registry
2502            .setting(ACCOUNT_CRYPTO_STATE)
2503            .expect("ACCOUNT_CRYPTO_STATE setting should be available")
2504            .get()
2505            .await
2506            .expect("setting get should not fail");
2507
2508        assert_eq!(persisted, Some(account_crypto_state));
2509    }
2510
2511    #[tokio::test]
2512    async fn test_initialize_user_local_data_key_idempotent() {
2513        let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
2514        initialize_user_local_data_key(&client)
2515            .await
2516            .expect("first initialization should succeed");
2517
2518        // Encrypt something with the key established on the first call.
2519        let ciphertext = {
2520            let key_store = client.internal.get_key_store();
2521            let mut ctx = key_store.context_mut();
2522            "test"
2523                .encrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2524                .expect("encryption should succeed")
2525        };
2526
2527        initialize_user_local_data_key(&client)
2528            .await
2529            .expect("second initialization should succeed");
2530
2531        // The key must not have changed: data encrypted before the second call
2532        // must still be decryptable.
2533        let key_store = client.internal.get_key_store();
2534        let mut ctx = key_store.context_mut();
2535        let decrypted: String = ciphertext
2536            .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2537            .expect("decryption after second initialization should succeed");
2538        assert_eq!(decrypted, "test");
2539    }
2540
2541    #[tokio::test]
2542    async fn test_initialize_user_crypto_rewraps_local_user_data_key_on_v1_to_v2_upgrade() {
2543        use crate::key_management::LocalUserDataKeyState;
2544
2545        // Bootstrap a V1 client to materialize a V1-wrapped LocalUserDataKey state.
2546        let client_v1 = Client::init_test_account(test_bitwarden_com_account()).await;
2547        let user_id = UserId::new(uuid::uuid!("060000fb-0922-4dd3-b170-6e15cb5df8c8"));
2548
2549        let v1_user_data_key = client_v1
2550            .platform()
2551            .state()
2552            .get::<LocalUserDataKeyState>()
2553            .unwrap()
2554            .get(user_id)
2555            .await
2556            .unwrap()
2557            .expect("V1 init should plant a LocalUserDataKey state");
2558        assert!(
2559            matches!(
2560                v1_user_data_key.wrapped_key,
2561                EncString::Aes256Cbc_HmacSha256_B64 { .. }
2562            ),
2563            "Initial local user data key should use be wrapped with a V1 user key"
2564        );
2565
2566        // Encrypt a payload with the V1-derived LocalUserData key.
2567        let ciphertext = {
2568            let mut ctx = client_v1.internal.get_key_store().context_mut();
2569            "preserved data"
2570                .encrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2571                .unwrap()
2572        };
2573
2574        // Build an upgrade token from the V1 user key to a fresh V2 key.
2575        let v2_key = SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
2576        let upgrade_token = {
2577            let mut ctx = client_v1.internal.get_key_store().context_mut();
2578            let v2_key_id = ctx.add_local_symmetric_key(v2_key.clone());
2579            V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap()
2580        };
2581
2582        // Plant the V1-wrapped state into a fresh client and run init with the upgrade token.
2583        let client_v2 = Client::new_test(None);
2584        let repo = client_v2
2585            .platform()
2586            .state()
2587            .get::<LocalUserDataKeyState>()
2588            .unwrap();
2589        repo.set(user_id, v1_user_data_key.clone()).await.unwrap();
2590        client_v2
2591            .km_state_bridge()
2592            .register_bridge(Box::new(InMemoryStateBridge::default()));
2593        client_v2
2594            .km_state_bridge()
2595            .set_v2_upgrade_token(&upgrade_token.clone())
2596            .await;
2597
2598        init_v2_account_with_master_password_and_upgrade_token(&client_v2, user_id, upgrade_token)
2599            .await;
2600
2601        // The persisted wrapped key must be sealed with the V2 user key.
2602        let rewrapped_state = repo
2603            .get(user_id)
2604            .await
2605            .unwrap()
2606            .expect("LocalUserDataKey state must remain present");
2607        assert!(
2608            matches!(
2609                rewrapped_state.wrapped_key,
2610                EncString::Cose_Encrypt0_B64 { .. }
2611            ),
2612            "Rewrapped key should be sealed with the V2 user key"
2613        );
2614        assert_ne!(rewrapped_state.wrapped_key, v1_user_data_key.wrapped_key);
2615
2616        // Data encrypted before the upgrade must remain decryptable.
2617        let mut ctx = client_v2.internal.get_key_store().context_mut();
2618        let decrypted: String = ciphertext
2619            .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2620            .expect("data encrypted before the upgrade should decrypt after rewrap");
2621        assert_eq!(decrypted, "preserved data");
2622    }
2623
2624    #[tokio::test]
2625    async fn test_initialize_user_crypto_creates_new_local_user_data_key_with_upgrade_token_and_no_existing_state()
2626     {
2627        use crate::key_management::LocalUserDataKeyState;
2628
2629        // Build an upgrade token from a separate V1 client (no state will be planted from it).
2630        let helper = Client::init_test_account(test_bitwarden_com_account()).await;
2631        let v2_key = SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
2632        let upgrade_token = {
2633            let mut ctx = helper.internal.get_key_store().context_mut();
2634            let v2_key_id = ctx.add_local_symmetric_key(v2_key.clone());
2635            V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap()
2636        };
2637
2638        // Fresh client with no planted LocalUserDataKey state.
2639        let user_id = UserId::new_v4();
2640        let client = Client::new_test(None);
2641        client
2642            .km_state_bridge()
2643            .register_bridge(Box::new(InMemoryStateBridge::default()));
2644        client
2645            .km_state_bridge()
2646            .set_v2_upgrade_token(&upgrade_token.clone())
2647            .await;
2648
2649        init_v2_account_with_master_password_and_upgrade_token(&client, user_id, upgrade_token)
2650            .await;
2651
2652        // No existing state → standard fresh-init path: a new wrapped key sealed with V2.
2653        let new_state = client
2654            .platform()
2655            .state()
2656            .get::<LocalUserDataKeyState>()
2657            .unwrap()
2658            .get(user_id)
2659            .await
2660            .unwrap()
2661            .expect("LocalUserDataKey should be created on init");
2662        assert!(matches!(
2663            new_state.wrapped_key,
2664            EncString::Cose_Encrypt0_B64 { .. }
2665        ));
2666    }
2667
2668    #[tokio::test]
2669    async fn test_initialize_user_crypto_leaves_local_user_data_key_unchanged_without_upgrade_token()
2670     {
2671        use crate::key_management::LocalUserDataKeyState;
2672
2673        // First V1 init plants a V1-wrapped state.
2674        let client = Client::init_test_account(test_bitwarden_com_account()).await;
2675        let user_id = UserId::new(uuid::uuid!("060000fb-0922-4dd3-b170-6e15cb5df8c8"));
2676        client
2677            .km_state_bridge()
2678            .register_bridge(Box::new(InMemoryStateBridge::default()));
2679
2680        let repo = client
2681            .platform()
2682            .state()
2683            .get::<LocalUserDataKeyState>()
2684            .unwrap();
2685        let before = repo.get(user_id).await.unwrap().unwrap();
2686
2687        // Re-run initialize_local_user_data_key_into_state; must skip idempotently.
2688        initialize_local_user_data_key_into_state(&client, user_id)
2689            .await
2690            .map_err(|_| "should succeed")
2691            .unwrap();
2692
2693        let after = repo.get(user_id).await.unwrap().unwrap();
2694        assert_eq!(
2695            after.wrapped_key, before.wrapped_key,
2696            "without an upgrade token the wrapped key must not change"
2697        );
2698    }
2699
2700    #[tokio::test]
2701    async fn test_initialize_user_crypto_does_not_rewrap_when_already_v2() {
2702        use crate::key_management::LocalUserDataKeyState;
2703
2704        // V2 init plants a V2-wrapped state.
2705        let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
2706        let user_id = UserId::new(uuid::uuid!("060000fb-0922-4dd3-b170-6e15cb5df8c8"));
2707        client
2708            .km_state_bridge()
2709            .register_bridge(Box::new(InMemoryStateBridge::default()));
2710
2711        let repo = client
2712            .platform()
2713            .state()
2714            .get::<LocalUserDataKeyState>()
2715            .unwrap();
2716        let before = repo.get(user_id).await.unwrap().unwrap();
2717        assert!(matches!(
2718            before.wrapped_key,
2719            EncString::Cose_Encrypt0_B64 { .. }
2720        ));
2721
2722        migrate_local_user_data_key_for_user_key_upgrade(&client, user_id)
2723            .await
2724            .map_err(|_| "should succeed")
2725            .unwrap();
2726
2727        let after = repo.get(user_id).await.unwrap().unwrap();
2728        assert_eq!(
2729            after.wrapped_key, before.wrapped_key,
2730            "an already-V2-wrapped key must not be rewrapped"
2731        );
2732    }
2733
2734    #[tokio::test]
2735    async fn test_make_user_password_registration() {
2736        let user_id = UserId::new_v4();
2737        let registration_client = Client::new(None);
2738
2739        let make_keys_response = registration_client
2740            .crypto()
2741            .make_user_password_registration(
2742                TEST_USER_PASSWORD.to_string(),
2743                TEST_USER_EMAIL.to_string(),
2744            )
2745            .expect("user password registration should succeed");
2746
2747        let unlock_client = Client::new_test(None);
2748        unlock_client
2749            .crypto()
2750            .initialize_user_crypto(InitUserCryptoRequest {
2751                user_id: Some(user_id),
2752                kdf_params: Kdf::default_argon2(),
2753                email: TEST_USER_EMAIL.to_string(),
2754                account_cryptographic_state: make_keys_response.account_cryptographic_state,
2755                method: InitUserCryptoMethod::MasterPasswordUnlock {
2756                    password: TEST_USER_PASSWORD.to_string(),
2757                    master_password_unlock: make_keys_response.master_password_unlock_data.clone(),
2758                },
2759                upgrade_token: None,
2760            })
2761            .await
2762            .expect("initializing user crypto with master password should succeed");
2763
2764        let retrieved_key = unlock_client
2765            .crypto()
2766            .get_user_encryption_key()
2767            .await
2768            .expect("should be able to get user encryption key");
2769
2770        let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
2771            .expect("retrieved key should be valid symmetric key");
2772
2773        let master_key = MasterKey::derive(
2774            TEST_USER_PASSWORD,
2775            TEST_USER_EMAIL,
2776            &make_keys_response.master_password_unlock_data.kdf,
2777        )
2778        .expect("master key should derive");
2779
2780        let decrypted_user_key = master_key
2781            .decrypt_user_key(
2782                make_keys_response
2783                    .master_password_unlock_data
2784                    .master_key_wrapped_user_key
2785                    .clone(),
2786            )
2787            .expect("should decrypt user key");
2788
2789        assert_eq!(retrieved_symmetric_key, decrypted_user_key);
2790    }
2791}