bitwarden_core/key_management/
crypto.rs

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