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