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