Skip to main content

bitwarden_core/key_management/
crypto.rs

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