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