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