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