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
7use std::collections::HashMap;
8
9use bitwarden_api_api::models::AccountKeysRequestModel;
10#[expect(deprecated)]
11use bitwarden_crypto::{
12    CoseSerializable, CryptoError, DeviceKey, EncString, Kdf, KeyConnectorKey, KeyDecryptable,
13    KeyEncryptable, MasterKey, Pkcs8PrivateKeyBytes, PrimitiveEncryptable, PrivateKey, PublicKey,
14    RotateableKeySet, SignatureAlgorithm, SignedPublicKey, SigningKey, SpkiPublicKeyBytes,
15    SymmetricCryptoKey, TrustDeviceResponse, UnsignedSharedKey, UserKey,
16    dangerous_get_v2_rotated_account_keys, derive_symmetric_key_from_prf,
17    safe::{PasswordProtectedKeyEnvelope, PasswordProtectedKeyEnvelopeError},
18};
19use bitwarden_encoding::B64;
20use bitwarden_error::bitwarden_error;
21use schemars::JsonSchema;
22use serde::{Deserialize, Serialize};
23use tracing::info;
24#[cfg(feature = "wasm")]
25use {tsify::Tsify, wasm_bindgen::prelude::*};
26
27use crate::{
28    Client, NotAuthenticatedError, OrganizationId, UserId, WrongPasswordError,
29    client::{LoginMethod, UserLoginMethod, encryption_settings::EncryptionSettingsError},
30    error::StatefulCryptoError,
31    key_management::{
32        MasterPasswordError, PrivateKeyId, SecurityState, SignedSecurityState, SigningKeyId,
33        SymmetricKeyId,
34        account_cryptographic_state::{
35            AccountCryptographyInitializationError, WrappedAccountCryptographicState,
36        },
37        master_password::{MasterPasswordAuthenticationData, MasterPasswordUnlockData},
38    },
39};
40
41/// Catch all error for mobile crypto operations.
42#[allow(missing_docs)]
43#[bitwarden_error(flat)]
44#[derive(Debug, thiserror::Error)]
45pub enum CryptoClientError {
46    #[error(transparent)]
47    NotAuthenticated(#[from] NotAuthenticatedError),
48    #[error(transparent)]
49    Crypto(#[from] bitwarden_crypto::CryptoError),
50    #[error("Invalid KDF settings")]
51    InvalidKdfSettings,
52    #[error(transparent)]
53    PasswordProtectedKeyEnvelope(#[from] PasswordProtectedKeyEnvelopeError),
54    #[error("Invalid PRF input")]
55    InvalidPrfInput,
56}
57
58/// State used for initializing the user cryptographic state.
59#[derive(Serialize, Deserialize, Debug)]
60#[serde(rename_all = "camelCase", deny_unknown_fields)]
61#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
62#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
63pub struct InitUserCryptoRequest {
64    /// The user's ID.
65    pub user_id: Option<UserId>,
66    /// The user's KDF parameters, as received from the prelogin request
67    pub kdf_params: Kdf,
68    /// The user's email address
69    pub email: String,
70    /// The user's account cryptographic state, containing their signature and
71    /// public-key-encryption keys, along with the signed security state, protected by the user key
72    pub account_cryptographic_state: WrappedAccountCryptographicState,
73    /// The method to decrypt the user's account symmetric key (user key)
74    pub method: InitUserCryptoMethod,
75}
76
77/// The crypto method used to initialize the user cryptographic state.
78#[derive(Serialize, Deserialize, Debug)]
79#[serde(rename_all = "camelCase", deny_unknown_fields)]
80#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
81#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
82#[allow(clippy::large_enum_variant)]
83pub enum InitUserCryptoMethod {
84    /// Master Password Unlock
85    MasterPasswordUnlock {
86        /// The user's master password
87        password: String,
88        /// Contains the data needed to unlock with the master password
89        master_password_unlock: MasterPasswordUnlockData,
90    },
91    /// Never lock and/or biometric unlock
92    DecryptedKey {
93        /// The user's decrypted encryption key, obtained using `get_user_encryption_key`
94        decrypted_user_key: String,
95    },
96    /// PIN
97    Pin {
98        /// The user's PIN
99        pin: String,
100        /// The user's symmetric crypto key, encrypted with the PIN. Use `derive_pin_key` to obtain
101        /// this.
102        pin_protected_user_key: EncString,
103    },
104    /// PIN Envelope
105    PinEnvelope {
106        /// The user's PIN
107        pin: String,
108        /// The user's symmetric crypto key, encrypted with the PIN-protected key envelope.
109        pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
110    },
111    /// Auth request
112    AuthRequest {
113        /// Private Key generated by the `crate::auth::new_auth_request`.
114        request_private_key: B64,
115        /// The type of auth request
116        method: AuthRequestMethod,
117    },
118    /// Device Key
119    DeviceKey {
120        /// The device's DeviceKey
121        device_key: String,
122        /// The Device Private Key
123        protected_device_private_key: EncString,
124        /// The user's symmetric crypto key, encrypted with the Device Key.
125        device_protected_user_key: UnsignedSharedKey,
126    },
127    /// Key connector
128    KeyConnector {
129        /// Base64 encoded master key, retrieved from the key connector.
130        master_key: B64,
131        /// The user's encrypted symmetric crypto key
132        user_key: EncString,
133    },
134}
135
136/// Auth requests supports multiple initialization methods.
137#[derive(Serialize, Deserialize, Debug)]
138#[serde(rename_all = "camelCase", deny_unknown_fields)]
139#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
140#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
141pub enum AuthRequestMethod {
142    /// User Key
143    UserKey {
144        /// User Key protected by the private key provided in `AuthRequestResponse`.
145        protected_user_key: UnsignedSharedKey,
146    },
147    /// Master Key
148    MasterKey {
149        /// Master Key protected by the private key provided in `AuthRequestResponse`.
150        protected_master_key: UnsignedSharedKey,
151        /// User Key protected by the MasterKey, provided by the auth response.
152        auth_request_key: EncString,
153    },
154}
155
156/// Initialize the user's cryptographic state.
157pub(super) async fn initialize_user_crypto(
158    client: &Client,
159    req: InitUserCryptoRequest,
160) -> Result<(), EncryptionSettingsError> {
161    use bitwarden_crypto::{DeviceKey, PinKey};
162
163    use crate::auth::{auth_request_decrypt_master_key, auth_request_decrypt_user_key};
164
165    if let Some(user_id) = req.user_id {
166        client.internal.init_user_id(user_id)?;
167    }
168
169    let account_crypto_state = req.account_cryptographic_state.to_owned();
170    let _span_guard = tracing::info_span!(
171        "User Crypto Initialization",
172        user_id = ?client.internal.get_user_id(),
173    )
174    .entered();
175
176    match req.method {
177        InitUserCryptoMethod::MasterPasswordUnlock {
178            password,
179            master_password_unlock,
180        } => {
181            client
182                .internal
183                .initialize_user_crypto_master_password_unlock(
184                    password,
185                    master_password_unlock,
186                    account_crypto_state,
187                )?;
188        }
189        InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => {
190            let user_key = SymmetricCryptoKey::try_from(decrypted_user_key)?;
191            client
192                .internal
193                .initialize_user_crypto_decrypted_key(user_key, account_crypto_state)?;
194        }
195        InitUserCryptoMethod::Pin {
196            pin,
197            pin_protected_user_key,
198        } => {
199            let pin_key = PinKey::derive(pin.as_bytes(), req.email.as_bytes(), &req.kdf_params)?;
200            client.internal.initialize_user_crypto_pin(
201                pin_key,
202                pin_protected_user_key,
203                account_crypto_state,
204            )?;
205        }
206        InitUserCryptoMethod::PinEnvelope {
207            pin,
208            pin_protected_user_key_envelope,
209        } => {
210            client.internal.initialize_user_crypto_pin_envelope(
211                pin,
212                pin_protected_user_key_envelope,
213                account_crypto_state,
214            )?;
215        }
216        InitUserCryptoMethod::AuthRequest {
217            request_private_key,
218            method,
219        } => {
220            let user_key = match method {
221                AuthRequestMethod::UserKey { protected_user_key } => {
222                    auth_request_decrypt_user_key(request_private_key, protected_user_key)?
223                }
224                AuthRequestMethod::MasterKey {
225                    protected_master_key,
226                    auth_request_key,
227                } => auth_request_decrypt_master_key(
228                    request_private_key,
229                    protected_master_key,
230                    auth_request_key,
231                )?,
232            };
233            client
234                .internal
235                .initialize_user_crypto_decrypted_key(user_key, account_crypto_state)?;
236        }
237        InitUserCryptoMethod::DeviceKey {
238            device_key,
239            protected_device_private_key,
240            device_protected_user_key,
241        } => {
242            let device_key = DeviceKey::try_from(device_key)?;
243            let user_key = device_key
244                .decrypt_user_key(protected_device_private_key, device_protected_user_key)?;
245
246            client
247                .internal
248                .initialize_user_crypto_decrypted_key(user_key, account_crypto_state)?;
249        }
250        InitUserCryptoMethod::KeyConnector {
251            master_key,
252            user_key,
253        } => {
254            let mut bytes = master_key.into_bytes();
255            let master_key = MasterKey::try_from(bytes.as_mut_slice())?;
256
257            client.internal.initialize_user_crypto_key_connector_key(
258                master_key,
259                user_key,
260                account_crypto_state,
261            )?;
262        }
263    }
264
265    info!("User crypto initialized successfully");
266
267    client
268        .internal
269        .set_login_method(LoginMethod::User(UserLoginMethod::Username {
270            client_id: "".to_string(),
271            email: req.email,
272            kdf: req.kdf_params,
273        }));
274
275    Ok(())
276}
277
278/// Represents the request to initialize the user's organizational cryptographic state.
279#[derive(Serialize, Deserialize, Debug)]
280#[serde(rename_all = "camelCase", deny_unknown_fields)]
281#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
282#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
283pub struct InitOrgCryptoRequest {
284    /// The encryption keys for all the organizations the user is a part of
285    pub organization_keys: HashMap<OrganizationId, UnsignedSharedKey>,
286}
287
288/// Initialize the user's organizational cryptographic state.
289pub(super) async fn initialize_org_crypto(
290    client: &Client,
291    req: InitOrgCryptoRequest,
292) -> Result<(), EncryptionSettingsError> {
293    let organization_keys = req.organization_keys.into_iter().collect();
294    client.internal.initialize_org_crypto(organization_keys)?;
295    Ok(())
296}
297
298pub(super) async fn get_user_encryption_key(client: &Client) -> Result<B64, CryptoClientError> {
299    let key_store = client.internal.get_key_store();
300    let ctx = key_store.context();
301    // This is needed because the mobile clients need access to the user encryption key
302    #[allow(deprecated)]
303    let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
304
305    Ok(user_key.to_base64())
306}
307
308/// Response from the `update_kdf` function
309#[derive(Serialize, Deserialize, Debug)]
310#[serde(rename_all = "camelCase", deny_unknown_fields)]
311#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
312#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
313pub struct UpdateKdfResponse {
314    /// The authentication data for the new KDF setting
315    master_password_authentication_data: MasterPasswordAuthenticationData,
316    /// The unlock data for the new KDF setting
317    master_password_unlock_data: MasterPasswordUnlockData,
318    /// The authentication data for the KDF setting prior to the change
319    old_master_password_authentication_data: MasterPasswordAuthenticationData,
320}
321
322pub(super) fn make_update_kdf(
323    client: &Client,
324    password: &str,
325    new_kdf: &Kdf,
326) -> Result<UpdateKdfResponse, CryptoClientError> {
327    let key_store = client.internal.get_key_store();
328    let ctx = key_store.context();
329
330    let login_method = client
331        .internal
332        .get_login_method()
333        .ok_or(NotAuthenticatedError)?;
334    let email = match login_method.as_ref() {
335        LoginMethod::User(
336            UserLoginMethod::Username { email, .. } | UserLoginMethod::ApiKey { email, .. },
337        ) => email,
338        #[cfg(feature = "secrets")]
339        LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?,
340    };
341
342    let authentication_data = MasterPasswordAuthenticationData::derive(password, new_kdf, email)
343        .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
344    let unlock_data =
345        MasterPasswordUnlockData::derive(password, new_kdf, email, SymmetricKeyId::User, &ctx)
346            .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
347    let old_authentication_data = MasterPasswordAuthenticationData::derive(
348        password,
349        &client
350            .internal
351            .get_kdf()
352            .map_err(|_| NotAuthenticatedError)?,
353        email,
354    )
355    .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
356
357    Ok(UpdateKdfResponse {
358        master_password_authentication_data: authentication_data,
359        master_password_unlock_data: unlock_data,
360        old_master_password_authentication_data: old_authentication_data,
361    })
362}
363
364/// Response from the `make_update_password` function
365#[derive(Serialize, Deserialize, Debug)]
366#[serde(rename_all = "camelCase", deny_unknown_fields)]
367#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
368#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
369pub struct UpdatePasswordResponse {
370    /// Hash of the new password
371    password_hash: B64,
372    /// User key, encrypted with the new password
373    new_key: EncString,
374}
375
376pub(super) fn make_update_password(
377    client: &Client,
378    new_password: String,
379) -> Result<UpdatePasswordResponse, CryptoClientError> {
380    let key_store = client.internal.get_key_store();
381    let ctx = key_store.context();
382    // FIXME: [PM-18099] Once MasterKey deals with KeyIds, this should be updated
383    #[allow(deprecated)]
384    let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
385
386    let login_method = client
387        .internal
388        .get_login_method()
389        .ok_or(NotAuthenticatedError)?;
390
391    // Derive a new master key from password
392    let new_master_key = match login_method.as_ref() {
393        LoginMethod::User(
394            UserLoginMethod::Username { email, kdf, .. }
395            | UserLoginMethod::ApiKey { email, kdf, .. },
396        ) => MasterKey::derive(&new_password, email, kdf)?,
397        #[cfg(feature = "secrets")]
398        LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?,
399    };
400
401    let new_key = new_master_key.encrypt_user_key(user_key)?;
402
403    let password_hash = new_master_key.derive_master_key_hash(
404        new_password.as_bytes(),
405        bitwarden_crypto::HashPurpose::ServerAuthorization,
406    );
407
408    Ok(UpdatePasswordResponse {
409        password_hash,
410        new_key,
411    })
412}
413
414/// Request for deriving a pin protected user key
415#[derive(Serialize, Deserialize, Debug)]
416#[serde(rename_all = "camelCase", deny_unknown_fields)]
417#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
418#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
419pub struct EnrollPinResponse {
420    /// [UserKey] protected by PIN
421    pub pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
422    /// PIN protected by [UserKey]
423    pub user_key_encrypted_pin: EncString,
424}
425
426pub(super) fn enroll_pin(
427    client: &Client,
428    pin: String,
429) -> Result<EnrollPinResponse, CryptoClientError> {
430    let key_store = client.internal.get_key_store();
431    let mut ctx = key_store.context_mut();
432
433    let key_envelope = PasswordProtectedKeyEnvelope::seal(SymmetricKeyId::User, &pin, &ctx)?;
434    let encrypted_pin = pin.encrypt(&mut ctx, SymmetricKeyId::User)?;
435    Ok(EnrollPinResponse {
436        pin_protected_user_key_envelope: key_envelope,
437        user_key_encrypted_pin: encrypted_pin,
438    })
439}
440
441/// Request for deriving a pin protected user key
442#[derive(Serialize, Deserialize, Debug)]
443#[serde(rename_all = "camelCase", deny_unknown_fields)]
444#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
445#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
446pub struct DerivePinKeyResponse {
447    /// [UserKey] protected by PIN
448    pin_protected_user_key: EncString,
449    /// PIN protected by [UserKey]
450    encrypted_pin: EncString,
451}
452
453pub(super) fn derive_pin_key(
454    client: &Client,
455    pin: String,
456) -> Result<DerivePinKeyResponse, CryptoClientError> {
457    let key_store = client.internal.get_key_store();
458    let ctx = key_store.context();
459    // FIXME: [PM-18099] Once PinKey deals with KeyIds, this should be updated
460    #[allow(deprecated)]
461    let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
462
463    let login_method = client
464        .internal
465        .get_login_method()
466        .ok_or(NotAuthenticatedError)?;
467
468    let pin_protected_user_key = derive_pin_protected_user_key(&pin, &login_method, user_key)?;
469
470    Ok(DerivePinKeyResponse {
471        pin_protected_user_key,
472        encrypted_pin: pin.encrypt_with_key(user_key)?,
473    })
474}
475
476pub(super) fn derive_pin_user_key(
477    client: &Client,
478    encrypted_pin: EncString,
479) -> Result<EncString, CryptoClientError> {
480    let key_store = client.internal.get_key_store();
481    let ctx = key_store.context();
482    // FIXME: [PM-18099] Once PinKey deals with KeyIds, this should be updated
483    #[allow(deprecated)]
484    let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
485
486    let pin: String = encrypted_pin.decrypt_with_key(user_key)?;
487    let login_method = client
488        .internal
489        .get_login_method()
490        .ok_or(NotAuthenticatedError)?;
491
492    derive_pin_protected_user_key(&pin, &login_method, user_key)
493}
494
495fn derive_pin_protected_user_key(
496    pin: &str,
497    login_method: &LoginMethod,
498    user_key: &SymmetricCryptoKey,
499) -> Result<EncString, CryptoClientError> {
500    use bitwarden_crypto::PinKey;
501
502    let derived_key = match login_method {
503        LoginMethod::User(
504            UserLoginMethod::Username { email, kdf, .. }
505            | UserLoginMethod::ApiKey { email, kdf, .. },
506        ) => PinKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?,
507        #[cfg(feature = "secrets")]
508        LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?,
509    };
510
511    Ok(derived_key.encrypt_user_key(user_key)?)
512}
513
514pub(super) fn make_prf_user_key_set(
515    client: &Client,
516    prf: B64,
517) -> Result<RotateableKeySet, CryptoClientError> {
518    let prf_key = derive_symmetric_key_from_prf(prf.as_bytes())
519        .map_err(|_| CryptoClientError::InvalidPrfInput)?;
520    let ctx = client.internal.get_key_store().context();
521    let key_set = RotateableKeySet::new(&ctx, &prf_key, SymmetricKeyId::User)?;
522    Ok(key_set)
523}
524
525#[allow(missing_docs)]
526#[bitwarden_error(flat)]
527#[derive(Debug, thiserror::Error)]
528pub enum EnrollAdminPasswordResetError {
529    #[error(transparent)]
530    Crypto(#[from] bitwarden_crypto::CryptoError),
531}
532
533pub(super) fn enroll_admin_password_reset(
534    client: &Client,
535    public_key: B64,
536) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
537    use bitwarden_crypto::PublicKey;
538
539    let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&public_key))?;
540    let key_store = client.internal.get_key_store();
541    let ctx = key_store.context();
542    // FIXME: [PM-18110] This should be removed once the key store can handle public key encryption
543    #[allow(deprecated)]
544    let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
545
546    #[expect(deprecated)]
547    Ok(UnsignedSharedKey::encapsulate_key_unsigned(
548        key,
549        &public_key,
550    )?)
551}
552
553/// Request for migrating an account from password to key connector.
554#[derive(Serialize, Deserialize, Debug, JsonSchema)]
555#[serde(rename_all = "camelCase", deny_unknown_fields)]
556#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
557#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
558pub struct DeriveKeyConnectorRequest {
559    /// Encrypted user key, used to validate the master key
560    pub user_key_encrypted: EncString,
561    /// The user's master password
562    pub password: String,
563    /// The KDF parameters used to derive the master key
564    pub kdf: Kdf,
565    /// The user's email address
566    pub email: String,
567}
568
569#[allow(missing_docs)]
570#[bitwarden_error(flat)]
571#[derive(Debug, thiserror::Error)]
572pub enum DeriveKeyConnectorError {
573    #[error(transparent)]
574    WrongPassword(#[from] WrongPasswordError),
575    #[error(transparent)]
576    Crypto(#[from] bitwarden_crypto::CryptoError),
577}
578
579/// Derive the master key for migrating to the key connector
580pub(super) fn derive_key_connector(
581    request: DeriveKeyConnectorRequest,
582) -> Result<B64, DeriveKeyConnectorError> {
583    let master_key = MasterKey::derive(&request.password, &request.email, &request.kdf)?;
584    master_key
585        .decrypt_user_key(request.user_key_encrypted)
586        .map_err(|_| WrongPasswordError)?;
587
588    Ok(master_key.to_base64())
589}
590
591/// Response from the `make_key_pair` function
592#[derive(Serialize, Deserialize, Debug)]
593#[serde(rename_all = "camelCase", deny_unknown_fields)]
594#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
595#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
596pub struct MakeKeyPairResponse {
597    /// The user's public key
598    user_public_key: B64,
599    /// User's private key, encrypted with the user key
600    user_key_encrypted_private_key: EncString,
601}
602
603pub(super) fn make_key_pair(user_key: B64) -> Result<MakeKeyPairResponse, CryptoError> {
604    let user_key = UserKey::new(SymmetricCryptoKey::try_from(user_key)?);
605
606    let key_pair = user_key.make_key_pair()?;
607
608    Ok(MakeKeyPairResponse {
609        user_public_key: key_pair.public,
610        user_key_encrypted_private_key: key_pair.private,
611    })
612}
613
614/// Request for `verify_asymmetric_keys`.
615#[derive(Serialize, Deserialize, Debug)]
616#[serde(rename_all = "camelCase", deny_unknown_fields)]
617#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
618#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
619pub struct VerifyAsymmetricKeysRequest {
620    /// The user's user key
621    user_key: B64,
622    /// The user's public key
623    user_public_key: B64,
624    /// User's private key, encrypted with the user key
625    user_key_encrypted_private_key: EncString,
626}
627
628/// Response for `verify_asymmetric_keys`.
629#[derive(Serialize, Deserialize, Debug)]
630#[serde(rename_all = "camelCase", deny_unknown_fields)]
631#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
632#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
633pub struct VerifyAsymmetricKeysResponse {
634    /// Whether the user's private key was decryptable by the user key.
635    private_key_decryptable: bool,
636    /// Whether the user's private key was a valid RSA key and matched the public key provided.
637    valid_private_key: bool,
638}
639
640pub(super) fn verify_asymmetric_keys(
641    request: VerifyAsymmetricKeysRequest,
642) -> Result<VerifyAsymmetricKeysResponse, CryptoError> {
643    #[derive(Debug, thiserror::Error)]
644    enum VerifyError {
645        #[error("Failed to decrypt private key: {0:?}")]
646        DecryptFailed(bitwarden_crypto::CryptoError),
647        #[error("Failed to parse decrypted private key: {0:?}")]
648        ParseFailed(bitwarden_crypto::CryptoError),
649        #[error("Failed to derive a public key: {0:?}")]
650        PublicFailed(bitwarden_crypto::CryptoError),
651        #[error("Derived public key doesn't match")]
652        KeyMismatch,
653    }
654
655    fn verify_inner(
656        user_key: &SymmetricCryptoKey,
657        request: &VerifyAsymmetricKeysRequest,
658    ) -> Result<(), VerifyError> {
659        let decrypted_private_key: Vec<u8> = request
660            .user_key_encrypted_private_key
661            .decrypt_with_key(user_key)
662            .map_err(VerifyError::DecryptFailed)?;
663
664        let decrypted_private_key = Pkcs8PrivateKeyBytes::from(decrypted_private_key);
665        let private_key =
666            PrivateKey::from_der(&decrypted_private_key).map_err(VerifyError::ParseFailed)?;
667
668        let derived_public_key_vec = private_key
669            .to_public_key()
670            .to_der()
671            .map_err(VerifyError::PublicFailed)?;
672
673        let derived_public_key = B64::from(derived_public_key_vec);
674
675        if derived_public_key != request.user_public_key {
676            return Err(VerifyError::KeyMismatch);
677        }
678        Ok(())
679    }
680
681    let user_key = SymmetricCryptoKey::try_from(request.user_key.clone())?;
682
683    Ok(match verify_inner(&user_key, &request) {
684        Ok(_) => VerifyAsymmetricKeysResponse {
685            private_key_decryptable: true,
686            valid_private_key: true,
687        },
688        Err(error) => {
689            tracing::debug!(%error, "User asymmetric keys verification");
690
691            VerifyAsymmetricKeysResponse {
692                private_key_decryptable: !matches!(error, VerifyError::DecryptFailed(_)),
693                valid_private_key: false,
694            }
695        }
696    })
697}
698
699/// Response for the `make_keys_for_user_crypto_v2`, containing a set of keys for a user
700#[derive(Serialize, Deserialize, Debug, Clone)]
701#[serde(rename_all = "camelCase", deny_unknown_fields)]
702#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
703#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
704pub struct UserCryptoV2KeysResponse {
705    /// User key
706    user_key: B64,
707
708    /// Wrapped private key
709    private_key: EncString,
710    /// Public key
711    public_key: B64,
712    /// The user's public key, signed by the signing key
713    signed_public_key: SignedPublicKey,
714
715    /// Signing key, encrypted with the user's symmetric key
716    signing_key: EncString,
717    /// Base64 encoded verifying key
718    verifying_key: B64,
719
720    /// The user's signed security state
721    security_state: SignedSecurityState,
722    /// The security state's version
723    security_version: u64,
724}
725
726/// Creates the user's cryptographic state for v2 users. This includes ensuring signature key pair
727/// is present, a signed public key is present, a security state is present and signed, and the user
728/// key is a Cose key.
729#[deprecated(note = "Use AccountCryptographicState::rotate instead")]
730pub(crate) fn make_v2_keys_for_v1_user(
731    client: &Client,
732) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
733    let key_store = client.internal.get_key_store();
734    let mut ctx = key_store.context();
735
736    // Re-use existing private key
737    let private_key_id = PrivateKeyId::UserPrivateKey;
738
739    // Ensure that the function is only called for a V1 user.
740    if client.internal.get_security_version() != 1 {
741        return Err(StatefulCryptoError::WrongAccountCryptoVersion {
742            expected: "1".to_string(),
743            got: 2,
744        });
745    }
746
747    // Ensure the user has a private key.
748    // V1 user must have a private key to upgrade. This should be ensured by the client before
749    // calling the upgrade function.
750    if !ctx.has_private_key(PrivateKeyId::UserPrivateKey) {
751        return Err(StatefulCryptoError::Crypto(CryptoError::MissingKeyId(
752            "UserPrivateKey".to_string(),
753        )));
754    }
755
756    #[allow(deprecated)]
757    let private_key = ctx.dangerous_get_private_key(private_key_id)?.clone();
758
759    // New user key
760    let user_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
761
762    // New signing key
763    let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
764    let temporary_signing_key_id = ctx.add_local_signing_key(signing_key.clone());
765
766    // Sign existing public key
767    let signed_public_key = ctx.make_signed_public_key(private_key_id, temporary_signing_key_id)?;
768    let public_key = private_key.to_public_key();
769
770    // Initialize security state for the user
771    let security_state = SecurityState::initialize_for_user(
772        client
773            .internal
774            .get_user_id()
775            .ok_or(StatefulCryptoError::MissingSecurityState)?,
776    );
777    let signed_security_state = security_state.sign(temporary_signing_key_id, &mut ctx)?;
778
779    Ok(UserCryptoV2KeysResponse {
780        user_key: user_key.to_base64(),
781
782        private_key: private_key.to_der()?.encrypt_with_key(&user_key)?,
783        public_key: public_key.to_der()?.into(),
784        signed_public_key,
785
786        signing_key: signing_key.to_cose().encrypt_with_key(&user_key)?,
787        verifying_key: signing_key.to_verifying_key().to_cose().into(),
788
789        security_state: signed_security_state,
790        security_version: security_state.version(),
791    })
792}
793
794/// Gets a set of new wrapped account keys for a user, given a new user key.
795///
796/// In the current implementation, it just re-encrypts any existing keys. This function expects a
797/// user to be a v2 user; that is, they have a signing key, a cose user-key, and a private key
798#[deprecated(note = "Use AccountCryptographicState::rotate instead")]
799pub(crate) fn get_v2_rotated_account_keys(
800    client: &Client,
801) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
802    let key_store = client.internal.get_key_store();
803    let mut ctx = key_store.context();
804
805    // Ensure that the function is only called for a V2 user.
806    // V2 users have a security version 2 or higher.
807    if client.internal.get_security_version() == 1 {
808        return Err(StatefulCryptoError::WrongAccountCryptoVersion {
809            expected: "2+".to_string(),
810            got: 1,
811        });
812    }
813
814    let security_state = client
815        .internal
816        .security_state
817        .read()
818        .expect("RwLock is not poisoned")
819        .to_owned()
820        // This cannot occur since the security version check above already ensures that the
821        // security state is present.
822        .ok_or(StatefulCryptoError::MissingSecurityState)?;
823
824    #[expect(deprecated)]
825    let rotated_keys = dangerous_get_v2_rotated_account_keys(
826        PrivateKeyId::UserPrivateKey,
827        SigningKeyId::UserSigningKey,
828        &ctx,
829    )?;
830
831    Ok(UserCryptoV2KeysResponse {
832        user_key: rotated_keys.user_key.to_base64(),
833
834        private_key: rotated_keys.private_key,
835        public_key: rotated_keys.public_key.into(),
836        signed_public_key: rotated_keys.signed_public_key,
837
838        signing_key: rotated_keys.signing_key,
839        verifying_key: rotated_keys.verifying_key.into(),
840
841        security_state: security_state.sign(SigningKeyId::UserSigningKey, &mut ctx)?,
842        security_version: security_state.version(),
843    })
844}
845
846/// The response from `make_user_tde_registration`.
847pub struct MakeTdeRegistrationResponse {
848    /// The account cryptographic state
849    pub account_cryptographic_state: WrappedAccountCryptographicState,
850    /// The user's user key
851    pub user_key: SymmetricCryptoKey,
852    /// The request model for the account cryptographic state (also called Account Keys)
853    pub account_keys_request: AccountKeysRequestModel,
854    /// The keys needed to set up TDE decryption
855    pub trusted_device_keys: TrustDeviceResponse,
856    /// The key needed for admin password reset
857    pub reset_password_key: UnsignedSharedKey,
858}
859
860/// The response from `make_user_jit_master_password_registration`.
861pub struct MakeJitMasterPasswordRegistrationResponse {
862    /// The account cryptographic state
863    pub account_cryptographic_state: WrappedAccountCryptographicState,
864    /// The user's user key
865    pub user_key: SymmetricCryptoKey,
866    /// The master password unlock data
867    pub master_password_authentication_data: MasterPasswordAuthenticationData,
868    /// The master password unlock data
869    pub master_password_unlock_data: MasterPasswordUnlockData,
870    /// The request model for the account cryptographic state (also called Account Keys)
871    pub account_keys_request: AccountKeysRequestModel,
872    /// The key needed for admin password reset
873    pub reset_password_key: UnsignedSharedKey,
874}
875
876/// Errors that can occur when making keys for account cryptography registration.
877#[bitwarden_error(flat)]
878#[derive(Debug, thiserror::Error)]
879pub enum MakeKeysError {
880    /// Failed to initialize account cryptography
881    #[error("Failed to initialize account cryptography")]
882    AccountCryptographyInitialization(AccountCryptographyInitializationError),
883    /// Failed to derive master password
884    #[error("Failed to derive master password")]
885    MasterPasswordDerivation(MasterPasswordError),
886    /// Failed to create request model
887    #[error("Failed to make a request model")]
888    RequestModelCreation,
889    /// Generic crypto error
890    #[error("Cryptography error: {0}")]
891    Crypto(#[from] CryptoError),
892}
893
894/// Create the data needed to register for TDE (Trusted Device Enrollment)
895pub(crate) fn make_user_tde_registration(
896    client: &Client,
897    user_id: UserId,
898    org_public_key: B64,
899) -> Result<MakeTdeRegistrationResponse, MakeKeysError> {
900    let mut ctx = client.internal.get_key_store().context_mut();
901    let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx, user_id)
902        .map_err(MakeKeysError::AccountCryptographyInitialization)?;
903    // TDE unlock method
904    #[expect(deprecated)]
905    let device_key = DeviceKey::trust_device(ctx.dangerous_get_symmetric_key(user_key_id)?)?;
906
907    // Account recovery enrollment
908    let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&org_public_key))
909        .map_err(MakeKeysError::Crypto)?;
910    #[expect(deprecated)]
911    let admin_reset = UnsignedSharedKey::encapsulate_key_unsigned(
912        ctx.dangerous_get_symmetric_key(user_key_id)?,
913        &public_key,
914    )
915    .map_err(MakeKeysError::Crypto)?;
916
917    let cryptography_state_request_model = wrapped_state
918        .to_request_model(&user_key_id, &mut ctx)
919        .map_err(|_| MakeKeysError::RequestModelCreation)?;
920
921    #[expect(deprecated)]
922    Ok(MakeTdeRegistrationResponse {
923        account_cryptographic_state: wrapped_state,
924        user_key: ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned(),
925        account_keys_request: cryptography_state_request_model,
926        trusted_device_keys: device_key,
927        reset_password_key: admin_reset,
928    })
929}
930
931/// The response from `make_user_key_connector_registration`.
932pub struct MakeKeyConnectorRegistrationResponse {
933    /// The account cryptographic state
934    pub account_cryptographic_state: WrappedAccountCryptographicState,
935    /// Encrypted user's user key, wrapped with the key connector key
936    pub key_connector_key_wrapped_user_key: EncString,
937    /// The user's user key
938    pub user_key: SymmetricCryptoKey,
939    /// The request model for the account cryptographic state (also called Account Keys)
940    pub account_keys_request: AccountKeysRequestModel,
941    /// The key connector key used for unlocking
942    pub key_connector_key: KeyConnectorKey,
943}
944
945/// Create the data needed to register for Key Connector
946pub(crate) fn make_user_key_connector_registration(
947    client: &Client,
948    user_id: UserId,
949) -> Result<MakeKeyConnectorRegistrationResponse, MakeKeysError> {
950    let mut ctx = client.internal.get_key_store().context_mut();
951    let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx, user_id)
952        .map_err(MakeKeysError::AccountCryptographyInitialization)?;
953    #[expect(deprecated)]
954    let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
955
956    // Key Connector unlock method
957    let key_connector_key = KeyConnectorKey::make();
958
959    let wrapped_user_key = key_connector_key
960        .encrypt_user_key(&user_key)
961        .map_err(MakeKeysError::Crypto)?;
962
963    let cryptography_state_request_model =
964        wrapped_state
965            .to_request_model(&user_key_id, &mut ctx)
966            .map_err(MakeKeysError::AccountCryptographyInitialization)?;
967
968    Ok(MakeKeyConnectorRegistrationResponse {
969        account_cryptographic_state: wrapped_state,
970        key_connector_key_wrapped_user_key: wrapped_user_key,
971        user_key,
972        account_keys_request: cryptography_state_request_model,
973        key_connector_key,
974    })
975}
976
977/// Create the data needed to register for JIT master password
978pub(crate) fn make_user_jit_master_password_registration(
979    client: &Client,
980    user_id: UserId,
981    master_password: String,
982    salt: String,
983    org_public_key: B64,
984) -> Result<MakeJitMasterPasswordRegistrationResponse, MakeKeysError> {
985    let mut ctx = client.internal.get_key_store().context_mut();
986    let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx, user_id)
987        .map_err(MakeKeysError::AccountCryptographyInitialization)?;
988
989    let kdf = Kdf::default_argon2();
990
991    #[expect(deprecated)]
992    let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
993
994    let master_password_unlock_data =
995        MasterPasswordUnlockData::derive(&master_password, &kdf, &salt, user_key_id, &ctx)
996            .map_err(MakeKeysError::MasterPasswordDerivation)?;
997
998    let master_password_authentication_data =
999        MasterPasswordAuthenticationData::derive(&master_password, &kdf, &salt)
1000            .map_err(MakeKeysError::MasterPasswordDerivation)?;
1001
1002    let cryptography_state_request_model = wrapped_state
1003        .to_request_model(&user_key_id, &mut ctx)
1004        .map_err(|_| MakeKeysError::RequestModelCreation)?;
1005
1006    // Account recovery enrollment
1007    let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&org_public_key))
1008        .map_err(MakeKeysError::Crypto)?;
1009    let admin_reset_key = UnsignedSharedKey::encapsulate(user_key_id, &public_key, &ctx)
1010        .map_err(MakeKeysError::Crypto)?;
1011
1012    Ok(MakeJitMasterPasswordRegistrationResponse {
1013        account_cryptographic_state: wrapped_state,
1014        user_key,
1015        master_password_unlock_data,
1016        master_password_authentication_data,
1017        account_keys_request: cryptography_state_request_model,
1018        reset_password_key: admin_reset_key,
1019    })
1020}
1021
1022#[cfg(test)]
1023mod tests {
1024    use std::num::NonZeroU32;
1025
1026    use bitwarden_crypto::{
1027        PrivateKey, PublicKeyEncryptionAlgorithm, RsaKeyPair, SymmetricKeyAlgorithm,
1028    };
1029
1030    use super::*;
1031    use crate::Client;
1032
1033    const TEST_VECTOR_USER_KEY_V2_B64: &str = "pQEEAlACHUUoybNAuJoZzqNMxz2bAzoAARFvBIQDBAUGIFggAvGl4ifaUAomQdCdUPpXLHtypiQxHjZwRHeI83caZM4B";
1034    const TEST_VECTOR_PRIVATE_KEY_V2: &str = "7.g1gdowE6AAERbwMZARwEUAIdRSjJs0C4mhnOo0zHPZuhBVgYthGLGqVLPeidY8mNMxpLJn3fyeSxyaWsWQTR6pxmRV2DyGZXly/0l9KK+Rsfetl9wvYIz0O4/RW3R6wf7eGxo5XmicV3WnFsoAmIQObxkKWShxFyjzg+ocKItQDzG7Gp6+MW4biTrAlfK51ML/ZS+PCjLmgI1QQr4eMHjiwA2TBKtKkxfjoTJkMXECpRVLEXOo8/mbIGYkuabbSA7oU+TJ0yXlfKDtD25gnyO7tjW/0JMFUaoEKRJOuKoXTN4n/ks4Hbxk0X5/DzfG05rxWad2UNBjNg7ehW99WrQ+33ckdQFKMQOri/rt8JzzrF1k11/jMJ+Y2TADKNHr91NalnUX+yqZAAe3sRt5Pv5ZhLIwRMKQi/1NrLcsQPRuUnogVSPOoMnE/eD6F70iU60Z6pvm1iBw2IvELZcrs/oxpO2SeCue08fIZW/jNZokbLnm90tQ7QeZTUpiPALhUgfGOa3J9VOJ7jQGCqDjd9CzV2DCVfhKCapeTbldm+RwEWBz5VvorH5vMx1AzbPRJxdIQuxcg3NqRrXrYC7fyZljWaPB9qP1tztiPtd1PpGEgxLByIfR6fqyZMCvOBsWbd0H6NhF8mNVdDw60+skFRdbRBTSCjCtKZeLVuVFb8ioH45PR5oXjtx4atIDzu6DKm6TTMCbR6DjZuZZ8GbwHxuUD2mDD3pAFhaof9kR3lQdjy7Zb4EzUUYskQxzcLPcqzp9ZgB3Rg91SStBCCMhdQ6AnhTy+VTGt/mY5AbBXNRSL6fI0r+P9K8CcEI4bNZCDkwwQr5v4O4ykSUzIvmVU0zKzDngy9bteIZuhkvGUoZlQ9UATNGPhoLfqq2eSvqEXkCbxTVZ5D+Ww9pHmWeVcvoBhcl5MvicfeQt++dY3tPjIfZq87nlugG4HiNbcv9nbVpgwe3v8cFetWXQgnO4uhx8JHSwGoSuxHFZtl2sdahjTHavRHnYjSABEFrViUKgb12UDD5ow1GAL62wVdSJKRf9HlLbJhN3PBxuh5L/E0wy1wGA9ecXtw/R1ktvXZ7RklGAt1TmNzZv6vI2J/CMXvndOX9rEpjKMbwbIDAjQ9PxiWdcnmc5SowT9f6yfIjbjXnRMWWidPAua7sgrtej4HP4Qjz1fpgLMLCRyF97tbMTmsAI5Cuj98Buh9PwcdyXj5SbVuHdJS1ehv9b5SWPsD4pwOm3+otVNK6FTazhoUl47AZoAoQzXfsXxrzqYzvF0yJkCnk9S1dcij1L569gQ43CJO6o6jIZFJvA4EmZDl95ELu+BC+x37Ip8dq4JLPsANDVSqvXO9tfDUIXEx25AaOYhW2KAUoDve/fbsU8d0UZR1o/w+ZrOQwawCIPeVPtbh7KFRVQi/rPI+Abl6XR6qMJbKPegliYGUuGF2oEMEc6QLTsMRCEPuw0S3kxbNfVPqml8nGhB2r8zUHBY1diJEmipVghnwH74gIKnyJ2C9nKjV8noUfKzqyV8vxUX2G5yXgodx8Jn0cWs3XhWuApFla9z4R28W/4jA1jK2WQMlx+b6xKUWgRk8+fYsc0HSt2fDrQ9pLpnjb8ME59RCxSPV++PThpnR2JtastZBZur2hBIJsGILCAmufUU4VC4gBKPhNfu/OK4Ktgz+uQlUa9fEC/FnkpTRQPxHuQjSQSNrIIyW1bIRBtnwjvvvNoui9FZJ";
1035    #[allow(unused)]
1036    const TEST_VECTOR_PUBLIC_KEY_V2: &str = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz/+1jPJ1HqcaCdKrTPms8XJcvnmd9alI42U2XF/4GMNTM5KF1gI6snhR/23ZLatZRFMHoK8ZCMSpGNkjLadArz52ldceTvBOhQUiWylkZQ4NfNa3xIYJubXOmkeDyfNuyLxVZvcZOko9PdT+Qx2QxDrFi2XNo2I7aVFd19/COIEkex4mJ0eA3MHFpKCdxYbcTAsGID8+kVR9L84S1JptZoG8x+iB/D3/Q4y02UsQYpFTu0vbPY84YmW03ngJdxWzS8X4/UJI/jaEn5rO4xlU5QcL0l4IybP5LRpE9XEeUHATKVOG7eNfpe9zDfKV2qQoofQMH9VvkWO4psaWDjBSdwIDAQAB";
1037    #[allow(unused)]
1038    const TEST_VECTOR_SIGNED_PUBLIC_KEY_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8BoFkBTqNpYWxnb3JpdGhtAG1jb250ZW50Rm9ybWF0AGlwdWJsaWNLZXlZASYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP/7WM8nUepxoJ0qtM+azxcly+eZ31qUjjZTZcX/gYw1MzkoXWAjqyeFH/bdktq1lEUwegrxkIxKkY2SMtp0CvPnaV1x5O8E6FBSJbKWRlDg181rfEhgm5tc6aR4PJ827IvFVm9xk6Sj091P5DHZDEOsWLZc2jYjtpUV3X38I4gSR7HiYnR4DcwcWkoJ3FhtxMCwYgPz6RVH0vzhLUmm1mgbzH6IH8Pf9DjLTZSxBikVO7S9s9jzhiZbTeeAl3FbNLxfj9Qkj+NoSfms7jGVTlBwvSXgjJs/ktGkT1cR5QcBMpU4bt41+l73MN8pXapCih9Awf1W+RY7imxpYOMFJ3AgMBAAFYQMq/hT4wod2w8xyoM7D86ctuLNX4ZRo+jRHf2sZfaO7QsvonG/ZYuNKF5fq8wpxMRjfoMvnY2TTShbgzLrW8BA4=";
1039    const TEST_VECTOR_SIGNING_KEY_V2: &str = "7.g1gcowE6AAERbwMYZQRQAh1FKMmzQLiaGc6jTMc9m6EFWBhYePc2qkCruHAPXgbzXsIP1WVk11ArbLNYUBpifToURlwHKs1je2BwZ1C/5thz4nyNbL0wDaYkRWI9ex1wvB7KhdzC7ltStEd5QttboTSCaXQROSZaGBPNO5+Bu3sTY8F5qK1pBUo6AHNN";
1040    #[allow(unused)]
1041    const TEST_VECTOR_VERIFYING_KEY_V2: &str =
1042        "pgEBAlAmkP0QgfdMVbIujX55W/yNAycEgQIgBiFYIEM6JxBmjWQTruAm3s6BTaJy1q6BzQetMBacNeRJ0kxR";
1043    const TEST_VECTOR_SECURITY_STATE_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8CoFgkomhlbnRpdHlJZFBHOOw2BI9OQoNq+Vl1xZZKZ3ZlcnNpb24CWEAlchbJR0vmRfShG8On7Q2gknjkw4Dd6MYBLiH4u+/CmfQdmjNZdf6kozgW/6NXyKVNu8dAsKsin+xxXkDyVZoG";
1044
1045    const TEST_USER_EMAIL: &str = "[email protected]";
1046    const TEST_USER_PASSWORD: &str = "asdfasdfasdf";
1047    const TEST_ACCOUNT_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
1048    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=";
1049
1050    #[tokio::test]
1051    async fn test_update_kdf() {
1052        let client = Client::new(None);
1053
1054        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();
1055
1056        let kdf = Kdf::PBKDF2 {
1057            iterations: 100_000.try_into().unwrap(),
1058        };
1059
1060        initialize_user_crypto(
1061            &client,
1062            InitUserCryptoRequest {
1063                user_id: Some(UserId::new_v4()),
1064                kdf_params: kdf.clone(),
1065                email: "[email protected]".into(),
1066                account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1067                method: InitUserCryptoMethod::MasterPasswordUnlock {
1068                    password: "asdfasdfasdf".into(),
1069                    master_password_unlock: MasterPasswordUnlockData {
1070                        kdf: kdf.clone(),
1071                        master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1072                        salt: "[email protected]".to_string(),
1073                    },
1074                },
1075            },
1076        )
1077            .await
1078            .unwrap();
1079
1080        let new_kdf = Kdf::PBKDF2 {
1081            iterations: 600_000.try_into().unwrap(),
1082        };
1083        let new_kdf_response = make_update_kdf(&client, "123412341234", &new_kdf).unwrap();
1084
1085        let client2 = Client::new(None);
1086
1087        initialize_user_crypto(
1088            &client2,
1089            InitUserCryptoRequest {
1090                user_id: Some(UserId::new_v4()),
1091                kdf_params: new_kdf.clone(),
1092                email: "[email protected]".into(),
1093                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1094                    private_key: priv_key.to_owned(),
1095                },
1096                method: InitUserCryptoMethod::MasterPasswordUnlock {
1097                    password: "123412341234".to_string(),
1098                    master_password_unlock: MasterPasswordUnlockData {
1099                        kdf: new_kdf.clone(),
1100                        master_key_wrapped_user_key: new_kdf_response
1101                            .master_password_unlock_data
1102                            .master_key_wrapped_user_key,
1103                        salt: "[email protected]".to_string(),
1104                    },
1105                },
1106            },
1107        )
1108        .await
1109        .unwrap();
1110
1111        let new_hash = client2
1112            .kdf()
1113            .hash_password(
1114                "[email protected]".into(),
1115                "123412341234".into(),
1116                new_kdf.clone(),
1117                bitwarden_crypto::HashPurpose::ServerAuthorization,
1118            )
1119            .await
1120            .unwrap();
1121
1122        assert_eq!(
1123            new_hash,
1124            new_kdf_response
1125                .master_password_authentication_data
1126                .master_password_authentication_hash
1127        );
1128
1129        let client_key = {
1130            let key_store = client.internal.get_key_store();
1131            let ctx = key_store.context();
1132            #[allow(deprecated)]
1133            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1134                .unwrap()
1135                .to_base64()
1136        };
1137
1138        let client2_key = {
1139            let key_store = client2.internal.get_key_store();
1140            let ctx = key_store.context();
1141            #[allow(deprecated)]
1142            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1143                .unwrap()
1144                .to_base64()
1145        };
1146
1147        assert_eq!(client_key, client2_key);
1148    }
1149
1150    #[tokio::test]
1151    async fn test_update_password() {
1152        let client = Client::new(None);
1153
1154        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();
1155
1156        let kdf = Kdf::PBKDF2 {
1157            iterations: 100_000.try_into().unwrap(),
1158        };
1159
1160        initialize_user_crypto(
1161            &client,
1162            InitUserCryptoRequest {
1163                user_id: Some(UserId::new_v4()),
1164                kdf_params: kdf.clone(),
1165                email: "[email protected]".into(),
1166                account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1167                method: InitUserCryptoMethod::MasterPasswordUnlock {
1168                    password: "asdfasdfasdf".to_string(),
1169                    master_password_unlock: MasterPasswordUnlockData {
1170                        kdf: kdf.clone(),
1171                        master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1172                        salt: "[email protected]".to_string(),
1173                    },
1174                },
1175            },
1176        )
1177            .await
1178            .unwrap();
1179
1180        let new_password_response = make_update_password(&client, "123412341234".into()).unwrap();
1181
1182        let client2 = Client::new(None);
1183
1184        initialize_user_crypto(
1185            &client2,
1186            InitUserCryptoRequest {
1187                user_id: Some(UserId::new_v4()),
1188                kdf_params: kdf.clone(),
1189                email: "[email protected]".into(),
1190                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1191                    private_key: priv_key.to_owned(),
1192                },
1193                method: InitUserCryptoMethod::MasterPasswordUnlock {
1194                    password: "123412341234".into(),
1195                    master_password_unlock: MasterPasswordUnlockData {
1196                        kdf: kdf.clone(),
1197                        master_key_wrapped_user_key: new_password_response.new_key,
1198                        salt: "[email protected]".to_string(),
1199                    },
1200                },
1201            },
1202        )
1203        .await
1204        .unwrap();
1205
1206        let new_hash = client2
1207            .kdf()
1208            .hash_password(
1209                "[email protected]".into(),
1210                "123412341234".into(),
1211                kdf.clone(),
1212                bitwarden_crypto::HashPurpose::ServerAuthorization,
1213            )
1214            .await
1215            .unwrap();
1216
1217        assert_eq!(new_hash, new_password_response.password_hash);
1218
1219        let client_key = {
1220            let key_store = client.internal.get_key_store();
1221            let ctx = key_store.context();
1222            #[allow(deprecated)]
1223            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1224                .unwrap()
1225                .to_base64()
1226        };
1227
1228        let client2_key = {
1229            let key_store = client2.internal.get_key_store();
1230            let ctx = key_store.context();
1231            #[allow(deprecated)]
1232            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1233                .unwrap()
1234                .to_base64()
1235        };
1236
1237        assert_eq!(client_key, client2_key);
1238    }
1239
1240    #[tokio::test]
1241    async fn test_initialize_user_crypto_pin() {
1242        let client = Client::new(None);
1243
1244        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();
1245
1246        initialize_user_crypto(
1247            &client,
1248            InitUserCryptoRequest {
1249                user_id: Some(UserId::new_v4()),
1250                kdf_params: Kdf::PBKDF2 {
1251                    iterations: 100_000.try_into().unwrap(),
1252                },
1253                email: "[email protected]".into(),
1254                account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1255                method: InitUserCryptoMethod::MasterPasswordUnlock {
1256                    password: "asdfasdfasdf".into(),
1257                    master_password_unlock: MasterPasswordUnlockData {
1258                        kdf: Kdf::PBKDF2 {
1259                            iterations: 100_000.try_into().unwrap(),
1260                        },
1261                        master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1262                        salt: "[email protected]".to_string(),
1263                    },
1264                },
1265            },
1266        )
1267            .await
1268            .unwrap();
1269
1270        let pin_key = derive_pin_key(&client, "1234".into()).unwrap();
1271
1272        // Verify we can unlock with the pin
1273        let client2 = Client::new(None);
1274        initialize_user_crypto(
1275            &client2,
1276            InitUserCryptoRequest {
1277                user_id: Some(UserId::new_v4()),
1278                kdf_params: Kdf::PBKDF2 {
1279                    iterations: 100_000.try_into().unwrap(),
1280                },
1281                email: "[email protected]".into(),
1282                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1283                    private_key: priv_key.to_owned(),
1284                },
1285                method: InitUserCryptoMethod::Pin {
1286                    pin: "1234".into(),
1287                    pin_protected_user_key: pin_key.pin_protected_user_key,
1288                },
1289            },
1290        )
1291        .await
1292        .unwrap();
1293
1294        let client_key = {
1295            let key_store = client.internal.get_key_store();
1296            let ctx = key_store.context();
1297            #[allow(deprecated)]
1298            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1299                .unwrap()
1300                .to_base64()
1301        };
1302
1303        let client2_key = {
1304            let key_store = client2.internal.get_key_store();
1305            let ctx = key_store.context();
1306            #[allow(deprecated)]
1307            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1308                .unwrap()
1309                .to_base64()
1310        };
1311
1312        assert_eq!(client_key, client2_key);
1313
1314        // Verify we can derive the pin protected user key from the encrypted pin
1315        let pin_protected_user_key = derive_pin_user_key(&client, pin_key.encrypted_pin).unwrap();
1316
1317        let client3 = Client::new(None);
1318
1319        initialize_user_crypto(
1320            &client3,
1321            InitUserCryptoRequest {
1322                user_id: Some(UserId::new_v4()),
1323                kdf_params: Kdf::PBKDF2 {
1324                    iterations: 100_000.try_into().unwrap(),
1325                },
1326                email: "[email protected]".into(),
1327                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1328                    private_key: priv_key.to_owned(),
1329                },
1330                method: InitUserCryptoMethod::Pin {
1331                    pin: "1234".into(),
1332                    pin_protected_user_key,
1333                },
1334            },
1335        )
1336        .await
1337        .unwrap();
1338
1339        let client_key = {
1340            let key_store = client.internal.get_key_store();
1341            let ctx = key_store.context();
1342            #[allow(deprecated)]
1343            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1344                .unwrap()
1345                .to_base64()
1346        };
1347
1348        let client3_key = {
1349            let key_store = client3.internal.get_key_store();
1350            let ctx = key_store.context();
1351            #[allow(deprecated)]
1352            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1353                .unwrap()
1354                .to_base64()
1355        };
1356
1357        assert_eq!(client_key, client3_key);
1358    }
1359
1360    #[tokio::test]
1361    async fn test_initialize_user_crypto_pin_envelope() {
1362        let user_key = "5yKAZ4TSSEGje54MV5lc5ty6crkqUz4xvl+8Dm/piNLKf6OgRi2H0uzttNTXl9z6ILhkmuIXzGpAVc2YdorHgQ==";
1363        let test_pin = "1234";
1364
1365        let client1 = Client::new(None);
1366        initialize_user_crypto(
1367            &client1,
1368            InitUserCryptoRequest {
1369                user_id: Some(UserId::new_v4()),
1370                kdf_params: Kdf::PBKDF2 {
1371                    iterations: 100_000.try_into().unwrap(),
1372                },
1373                email: "[email protected]".into(),
1374                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1375                    private_key: make_key_pair(user_key.try_into().unwrap())
1376                        .unwrap()
1377                        .user_key_encrypted_private_key,
1378                },
1379                method: InitUserCryptoMethod::DecryptedKey {
1380                    decrypted_user_key: user_key.to_string(),
1381                },
1382            },
1383        )
1384        .await
1385        .unwrap();
1386
1387        let enroll_response = client1.crypto().enroll_pin(test_pin.to_string()).unwrap();
1388
1389        let client2 = Client::new(None);
1390        initialize_user_crypto(
1391            &client2,
1392            InitUserCryptoRequest {
1393                user_id: Some(UserId::new_v4()),
1394                // NOTE: THIS CHANGES KDF SETTINGS. We ensure in this test that even with different
1395                // KDF settings the pin can unlock the user key.
1396                kdf_params: Kdf::PBKDF2 {
1397                    iterations: 600_000.try_into().unwrap(),
1398                },
1399                email: "[email protected]".into(),
1400                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1401                    private_key: make_key_pair(user_key.try_into().unwrap())
1402                        .unwrap()
1403                        .user_key_encrypted_private_key,
1404                },
1405                method: InitUserCryptoMethod::PinEnvelope {
1406                    pin: test_pin.to_string(),
1407                    pin_protected_user_key_envelope: enroll_response
1408                        .pin_protected_user_key_envelope,
1409                },
1410            },
1411        )
1412        .await
1413        .unwrap();
1414    }
1415
1416    #[test]
1417    fn test_enroll_admin_password_reset() {
1418        let client = Client::new(None);
1419
1420        let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap();
1421        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();
1422        client
1423            .internal
1424            .initialize_user_crypto_master_password_unlock(
1425                "asdfasdfasdf".to_string(),
1426                MasterPasswordUnlockData {
1427                    kdf: Kdf::PBKDF2 {
1428                        iterations: NonZeroU32::new(600_000).unwrap(),
1429                    },
1430                    master_key_wrapped_user_key: user_key,
1431                    salt: "[email protected]".to_string(),
1432                },
1433                WrappedAccountCryptographicState::V1 { private_key },
1434            )
1435            .unwrap();
1436
1437        let public_key: B64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsy7RFHcX3C8Q4/OMmhhbFReYWfB45W9PDTEA8tUZwZmtOiN2RErIS2M1c+K/4HoDJ/TjpbX1f2MZcr4nWvKFuqnZXyewFc+jmvKVewYi+NAu2++vqKq2kKcmMNhwoQDQdQIVy/Uqlp4Cpi2cIwO6ogq5nHNJGR3jm+CpyrafYlbz1bPvL3hbyoGDuG2tgADhyhXUdFuef2oF3wMvn1lAJAvJnPYpMiXUFmj1ejmbwtlxZDrHgUJvUcp7nYdwUKaFoi+sOttHn3u7eZPtNvxMjhSS/X/1xBIzP/mKNLdywH5LoRxniokUk+fV3PYUxJsiU3lV0Trc/tH46jqd8ZGjmwIDAQAB".parse().unwrap();
1438
1439        let encrypted = enroll_admin_password_reset(&client, public_key).unwrap();
1440
1441        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();
1442
1443        let private_key = Pkcs8PrivateKeyBytes::from(private_key.as_bytes());
1444        let private_key = PrivateKey::from_der(&private_key).unwrap();
1445        #[expect(deprecated)]
1446        let decrypted: SymmetricCryptoKey =
1447            encrypted.decapsulate_key_unsigned(&private_key).unwrap();
1448
1449        let key_store = client.internal.get_key_store();
1450        let ctx = key_store.context();
1451        #[allow(deprecated)]
1452        let expected = ctx
1453            .dangerous_get_symmetric_key(SymmetricKeyId::User)
1454            .unwrap();
1455
1456        assert_eq!(decrypted, *expected);
1457    }
1458
1459    #[test]
1460    fn test_derive_key_connector() {
1461        let request = DeriveKeyConnectorRequest {
1462            password: "asdfasdfasdf".to_string(),
1463            email: "[email protected]".to_string(),
1464            kdf: Kdf::PBKDF2 {
1465                iterations: NonZeroU32::new(600_000).unwrap(),
1466            },
1467            user_key_encrypted: "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(),
1468        };
1469
1470        let result = derive_key_connector(request).unwrap();
1471
1472        assert_eq!(
1473            result.to_string(),
1474            "ySXq1RVLKEaV1eoQE/ui9aFKIvXTl9PAXwp1MljfF50="
1475        );
1476    }
1477
1478    fn setup_asymmetric_keys_test() -> (UserKey, RsaKeyPair) {
1479        let master_key = MasterKey::derive(
1480            "asdfasdfasdf",
1481            "[email protected]",
1482            &Kdf::PBKDF2 {
1483                iterations: NonZeroU32::new(600_000).unwrap(),
1484            },
1485        )
1486        .unwrap();
1487        let user_key = (master_key.make_user_key().unwrap()).0;
1488        let key_pair = user_key.make_key_pair().unwrap();
1489
1490        (user_key, key_pair)
1491    }
1492
1493    #[test]
1494    fn test_make_key_pair() {
1495        let (user_key, _) = setup_asymmetric_keys_test();
1496
1497        let response = make_key_pair(user_key.0.to_base64()).unwrap();
1498
1499        assert!(!response.user_public_key.to_string().is_empty());
1500        let encrypted_private_key = response.user_key_encrypted_private_key;
1501        let private_key: Vec<u8> = encrypted_private_key.decrypt_with_key(&user_key.0).unwrap();
1502        assert!(!private_key.is_empty());
1503    }
1504
1505    #[test]
1506    fn test_verify_asymmetric_keys_success() {
1507        let (user_key, key_pair) = setup_asymmetric_keys_test();
1508
1509        let request = VerifyAsymmetricKeysRequest {
1510            user_key: user_key.0.to_base64(),
1511            user_public_key: key_pair.public,
1512            user_key_encrypted_private_key: key_pair.private,
1513        };
1514        let response = verify_asymmetric_keys(request).unwrap();
1515
1516        assert!(response.private_key_decryptable);
1517        assert!(response.valid_private_key);
1518    }
1519
1520    #[test]
1521    fn test_verify_asymmetric_keys_decrypt_failed() {
1522        let (user_key, key_pair) = setup_asymmetric_keys_test();
1523        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();
1524
1525        let request = VerifyAsymmetricKeysRequest {
1526            user_key: user_key.0.to_base64(),
1527            user_public_key: key_pair.public,
1528            user_key_encrypted_private_key: undecryptable_private_key,
1529        };
1530        let response = verify_asymmetric_keys(request).unwrap();
1531
1532        assert!(!response.private_key_decryptable);
1533        assert!(!response.valid_private_key);
1534    }
1535
1536    #[test]
1537    fn test_verify_asymmetric_keys_parse_failed() {
1538        let (user_key, key_pair) = setup_asymmetric_keys_test();
1539
1540        let invalid_private_key = "bad_key".to_string().encrypt_with_key(&user_key.0).unwrap();
1541
1542        let request = VerifyAsymmetricKeysRequest {
1543            user_key: user_key.0.to_base64(),
1544            user_public_key: key_pair.public,
1545            user_key_encrypted_private_key: invalid_private_key,
1546        };
1547        let response = verify_asymmetric_keys(request).unwrap();
1548
1549        assert!(response.private_key_decryptable);
1550        assert!(!response.valid_private_key);
1551    }
1552
1553    #[test]
1554    fn test_verify_asymmetric_keys_key_mismatch() {
1555        let (user_key, key_pair) = setup_asymmetric_keys_test();
1556        let new_key_pair = user_key.make_key_pair().unwrap();
1557
1558        let request = VerifyAsymmetricKeysRequest {
1559            user_key: user_key.0.to_base64(),
1560            user_public_key: key_pair.public,
1561            user_key_encrypted_private_key: new_key_pair.private,
1562        };
1563        let response = verify_asymmetric_keys(request).unwrap();
1564
1565        assert!(response.private_key_decryptable);
1566        assert!(!response.valid_private_key);
1567    }
1568
1569    #[tokio::test]
1570    async fn test_make_v2_keys_for_v1_user() {
1571        let client = Client::new(None);
1572
1573        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();
1574        let encrypted_userkey: EncString = "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap();
1575
1576        initialize_user_crypto(
1577            &client,
1578            InitUserCryptoRequest {
1579                user_id: Some(UserId::new_v4()),
1580                kdf_params: Kdf::PBKDF2 {
1581                    iterations: 100_000.try_into().unwrap(),
1582                },
1583                email: "[email protected]".into(),
1584                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1585                    private_key: priv_key.to_owned(),
1586                },
1587                method: InitUserCryptoMethod::MasterPasswordUnlock {
1588                    password: "asdfasdfasdf".into(),
1589                    master_password_unlock: MasterPasswordUnlockData {
1590                        kdf: Kdf::PBKDF2 {
1591                            iterations: 100_000.try_into().unwrap(),
1592                        },
1593                        master_key_wrapped_user_key: encrypted_userkey.clone(),
1594                        salt: "[email protected]".into(),
1595                    },
1596                },
1597            },
1598        )
1599        .await
1600        .unwrap();
1601
1602        let master_key = MasterKey::derive(
1603            "asdfasdfasdf",
1604            "[email protected]",
1605            &Kdf::PBKDF2 {
1606                iterations: NonZeroU32::new(100_000).unwrap(),
1607            },
1608        )
1609        .unwrap();
1610        #[expect(deprecated)]
1611        let enrollment_response = make_v2_keys_for_v1_user(&client).unwrap();
1612        let encrypted_userkey_v2 = master_key
1613            .encrypt_user_key(
1614                &SymmetricCryptoKey::try_from(enrollment_response.clone().user_key).unwrap(),
1615            )
1616            .unwrap();
1617
1618        let client2 = Client::new(None);
1619        initialize_user_crypto(
1620            &client2,
1621            InitUserCryptoRequest {
1622                user_id: Some(UserId::new_v4()),
1623                kdf_params: Kdf::PBKDF2 {
1624                    iterations: 100_000.try_into().unwrap(),
1625                },
1626                email: "[email protected]".into(),
1627                account_cryptographic_state: WrappedAccountCryptographicState::V2 {
1628                    private_key: enrollment_response.private_key,
1629                    signing_key: enrollment_response.signing_key,
1630                    security_state: enrollment_response.security_state,
1631                    signed_public_key: Some(enrollment_response.signed_public_key),
1632                },
1633                method: InitUserCryptoMethod::MasterPasswordUnlock {
1634                    password: "asdfasdfasdf".into(),
1635                    master_password_unlock: MasterPasswordUnlockData {
1636                        kdf: Kdf::PBKDF2 {
1637                            iterations: 100_000.try_into().unwrap(),
1638                        },
1639                        master_key_wrapped_user_key: encrypted_userkey_v2,
1640                        salt: "[email protected]".to_string(),
1641                    },
1642                },
1643            },
1644        )
1645        .await
1646        .unwrap();
1647    }
1648
1649    #[tokio::test]
1650    async fn test_make_v2_keys_for_v1_user_with_v2_user_fails() {
1651        let client = Client::new(None);
1652        initialize_user_crypto(
1653            &client,
1654            InitUserCryptoRequest {
1655                user_id: Some(UserId::new_v4()),
1656                kdf_params: Kdf::PBKDF2 {
1657                    iterations: 100_000.try_into().unwrap(),
1658                },
1659                email: "[email protected]".into(),
1660                account_cryptographic_state: WrappedAccountCryptographicState::V2 {
1661                    private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
1662                    signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
1663                    security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
1664                    signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
1665                },
1666                method: InitUserCryptoMethod::DecryptedKey {
1667                    decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
1668                },
1669            },
1670        )
1671        .await
1672        .unwrap();
1673
1674        #[expect(deprecated)]
1675        let result = make_v2_keys_for_v1_user(&client);
1676        assert!(matches!(
1677            result,
1678            Err(StatefulCryptoError::WrongAccountCryptoVersion {
1679                expected: _,
1680                got: _
1681            })
1682        ));
1683    }
1684
1685    #[test]
1686    fn test_get_v2_rotated_account_keys_non_v2_user() {
1687        let client = Client::new(None);
1688        let mut ctx = client.internal.get_key_store().context_mut();
1689        let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
1690        ctx.persist_symmetric_key(local_key_id, SymmetricKeyId::User)
1691            .unwrap();
1692        drop(ctx);
1693
1694        #[expect(deprecated)]
1695        let result = get_v2_rotated_account_keys(&client);
1696        assert!(matches!(
1697            result,
1698            Err(StatefulCryptoError::WrongAccountCryptoVersion {
1699                expected: _,
1700                got: _
1701            })
1702        ));
1703    }
1704
1705    #[tokio::test]
1706    async fn test_get_v2_rotated_account_keys() {
1707        let client = Client::new(None);
1708        initialize_user_crypto(
1709            &client,
1710            InitUserCryptoRequest {
1711                user_id: Some(UserId::new_v4()),
1712                kdf_params: Kdf::PBKDF2 {
1713                    iterations: 100_000.try_into().unwrap(),
1714                },
1715                email: "[email protected]".into(),
1716                account_cryptographic_state: WrappedAccountCryptographicState::V2 {
1717                    private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
1718                    signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
1719                    security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
1720                    signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
1721                },
1722                method: InitUserCryptoMethod::DecryptedKey {
1723                    decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
1724                },
1725            },
1726        )
1727        .await
1728        .unwrap();
1729
1730        #[expect(deprecated)]
1731        let result = get_v2_rotated_account_keys(&client);
1732        assert!(result.is_ok());
1733    }
1734
1735    #[tokio::test]
1736    async fn test_initialize_user_crypto_master_password_unlock() {
1737        let client = Client::new(None);
1738
1739        initialize_user_crypto(
1740            &client,
1741            InitUserCryptoRequest {
1742                user_id: Some(UserId::new_v4()),
1743                kdf_params: Kdf::PBKDF2 {
1744                    iterations: 600_000.try_into().unwrap(),
1745                },
1746                email: TEST_USER_EMAIL.to_string(),
1747                account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1748                    private_key: TEST_ACCOUNT_PRIVATE_KEY.parse().unwrap(),
1749                },
1750                method: InitUserCryptoMethod::MasterPasswordUnlock {
1751                    password: TEST_USER_PASSWORD.to_string(),
1752                    master_password_unlock: MasterPasswordUnlockData {
1753                        kdf: Kdf::PBKDF2 {
1754                            iterations: 600_000.try_into().unwrap(),
1755                        },
1756                        master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
1757                        salt: TEST_USER_EMAIL.to_string(),
1758                    },
1759                },
1760            },
1761        )
1762        .await
1763        .unwrap();
1764
1765        let key_store = client.internal.get_key_store();
1766        let context = key_store.context();
1767        assert!(context.has_symmetric_key(SymmetricKeyId::User));
1768        assert!(context.has_private_key(PrivateKeyId::UserPrivateKey));
1769        let login_method = client.internal.get_login_method().unwrap();
1770        if let LoginMethod::User(UserLoginMethod::Username {
1771            email,
1772            kdf,
1773            client_id,
1774            ..
1775        }) = login_method.as_ref()
1776        {
1777            assert_eq!(*email, TEST_USER_EMAIL);
1778            assert_eq!(
1779                *kdf,
1780                Kdf::PBKDF2 {
1781                    iterations: 600_000.try_into().unwrap(),
1782                }
1783            );
1784            assert_eq!(*client_id, "");
1785        } else {
1786            panic!("Expected username login method");
1787        }
1788    }
1789
1790    #[tokio::test]
1791    async fn test_make_user_tde_registration() {
1792        let user_id = UserId::new_v4();
1793        let email = "[email protected]";
1794        let kdf = Kdf::PBKDF2 {
1795            iterations: NonZeroU32::new(600_000).expect("valid iteration count"),
1796        };
1797
1798        // Generate a mock organization public key for TDE enrollment
1799        let org_key = PrivateKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
1800        let org_public_key_der = org_key
1801            .to_public_key()
1802            .to_der()
1803            .expect("valid public key DER");
1804        let org_public_key = B64::from(org_public_key_der.as_ref().to_vec());
1805
1806        // Create a client and generate TDE registration keys
1807        let registration_client = Client::new(None);
1808        let make_keys_response = registration_client
1809            .crypto()
1810            .make_user_tde_registration(user_id, org_public_key)
1811            .expect("TDE registration should succeed");
1812
1813        // Initialize a new client using the TDE device key
1814        let unlock_client = Client::new(None);
1815        unlock_client
1816            .crypto()
1817            .initialize_user_crypto(InitUserCryptoRequest {
1818                user_id: Some(user_id),
1819                kdf_params: kdf,
1820                email: email.to_owned(),
1821                account_cryptographic_state: make_keys_response.account_cryptographic_state,
1822                method: InitUserCryptoMethod::DeviceKey {
1823                    device_key: make_keys_response
1824                        .trusted_device_keys
1825                        .device_key
1826                        .to_string(),
1827                    protected_device_private_key: make_keys_response
1828                        .trusted_device_keys
1829                        .protected_device_private_key,
1830                    device_protected_user_key: make_keys_response
1831                        .trusted_device_keys
1832                        .protected_user_key,
1833                },
1834            })
1835            .await
1836            .expect("initializing user crypto with TDE device key should succeed");
1837
1838        // Verify we can retrieve the user encryption key
1839        let retrieved_key = unlock_client
1840            .crypto()
1841            .get_user_encryption_key()
1842            .await
1843            .expect("should be able to get user encryption key");
1844
1845        // The retrieved key should be a valid symmetric key
1846        let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
1847            .expect("retrieved key should be valid symmetric key");
1848
1849        // Verify that the org key can decrypt the admin_reset_key UnsignedSharedKey
1850        // and that the decrypted key matches the user's encryption key
1851        #[expect(deprecated)]
1852        let decrypted_user_key = make_keys_response
1853            .reset_password_key
1854            .decapsulate_key_unsigned(&org_key)
1855            .expect("org key should be able to decrypt admin reset key");
1856        assert_eq!(
1857            retrieved_symmetric_key, decrypted_user_key,
1858            "decrypted admin reset key should match the user's encryption key"
1859        );
1860    }
1861
1862    #[tokio::test]
1863    async fn test_make_user_key_connector_registration_success() {
1864        let user_id = UserId::new_v4();
1865        let email = "[email protected]";
1866        let registration_client = Client::new(None);
1867
1868        let make_keys_response =
1869            make_user_key_connector_registration(&registration_client, user_id);
1870        assert!(make_keys_response.is_ok());
1871        let make_keys_response = make_keys_response.unwrap();
1872
1873        // Initialize a new client using the key connector key
1874        let unlock_client = Client::new(None);
1875        unlock_client
1876            .crypto()
1877            .initialize_user_crypto(InitUserCryptoRequest {
1878                user_id: Some(user_id),
1879                kdf_params: Kdf::default_argon2(),
1880                email: email.to_owned(),
1881                account_cryptographic_state: make_keys_response.account_cryptographic_state,
1882                method: InitUserCryptoMethod::KeyConnector {
1883                    user_key: make_keys_response
1884                        .key_connector_key_wrapped_user_key
1885                        .clone(),
1886                    master_key: make_keys_response.key_connector_key.clone().into(),
1887                },
1888            })
1889            .await
1890            .expect("initializing user crypto with key connector key should succeed");
1891
1892        // Verify we can retrieve the user encryption key
1893        let retrieved_key = unlock_client
1894            .crypto()
1895            .get_user_encryption_key()
1896            .await
1897            .expect("should be able to get user encryption key");
1898
1899        // The retrieved key should be a valid symmetric key
1900        let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
1901            .expect("retrieved key should be valid symmetric key");
1902
1903        assert_eq!(retrieved_symmetric_key, make_keys_response.user_key);
1904
1905        let decrypted_user_key = make_keys_response
1906            .key_connector_key
1907            .decrypt_user_key(make_keys_response.key_connector_key_wrapped_user_key);
1908        assert_eq!(retrieved_symmetric_key, decrypted_user_key.unwrap());
1909    }
1910}