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    AsymmetricCryptoKey, CoseSerializable, CryptoError, EncString, Encryptable, Kdf,
12    KeyDecryptable, KeyEncryptable, MasterKey, SignatureAlgorithm, SignedPublicKey, SigningKey,
13    SymmetricCryptoKey, UnsignedSharedKey, UserKey,
14};
15use bitwarden_error::bitwarden_error;
16use schemars::JsonSchema;
17use serde::{Deserialize, Serialize};
18#[cfg(feature = "wasm")]
19use {tsify_next::Tsify, wasm_bindgen::prelude::*};
20
21use crate::{
22    client::{encryption_settings::EncryptionSettingsError, LoginMethod, UserLoginMethod},
23    key_management::{AsymmetricKeyId, SigningKeyId, SymmetricKeyId},
24    Client, NotAuthenticatedError, VaultLockedError, WrongPasswordError,
25};
26
27/// Catch all error for mobile crypto operations.
28#[allow(missing_docs)]
29#[bitwarden_error(flat)]
30#[derive(Debug, thiserror::Error)]
31pub enum CryptoClientError {
32    #[error(transparent)]
33    NotAuthenticated(#[from] NotAuthenticatedError),
34    #[error(transparent)]
35    VaultLocked(#[from] VaultLockedError),
36    #[error(transparent)]
37    Crypto(#[from] bitwarden_crypto::CryptoError),
38}
39
40/// State used for initializing the user cryptographic state.
41#[derive(Serialize, Deserialize, Debug)]
42#[serde(rename_all = "camelCase", deny_unknown_fields)]
43#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
44#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
45pub struct InitUserCryptoRequest {
46    /// The user's ID.
47    pub user_id: Option<uuid::Uuid>,
48    /// The user's KDF parameters, as received from the prelogin request
49    pub kdf_params: Kdf,
50    /// The user's email address
51    pub email: String,
52    /// The user's encrypted private key
53    pub private_key: EncString,
54    /// The user's signing key
55    pub signing_key: Option<EncString>,
56    /// The initialization method to use
57    pub method: InitUserCryptoMethod,
58}
59
60/// The crypto method used to initialize the user cryptographic state.
61#[derive(Serialize, Deserialize, Debug)]
62#[serde(rename_all = "camelCase", deny_unknown_fields)]
63#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
64#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
65pub enum InitUserCryptoMethod {
66    /// Password
67    Password {
68        /// The user's master password
69        password: String,
70        /// The user's encrypted symmetric crypto key
71        user_key: EncString,
72    },
73    /// Never lock and/or biometric unlock
74    DecryptedKey {
75        /// The user's decrypted encryption key, obtained using `get_user_encryption_key`
76        decrypted_user_key: String,
77    },
78    /// PIN
79    Pin {
80        /// The user's PIN
81        pin: String,
82        /// The user's symmetric crypto key, encrypted with the PIN. Use `derive_pin_key` to obtain
83        /// this.
84        pin_protected_user_key: EncString,
85    },
86    /// Auth request
87    AuthRequest {
88        /// Private Key generated by the `crate::auth::new_auth_request`.
89        request_private_key: String,
90        /// The type of auth request
91        method: AuthRequestMethod,
92    },
93    /// Device Key
94    DeviceKey {
95        /// The device's DeviceKey
96        device_key: String,
97        /// The Device Private Key
98        protected_device_private_key: EncString,
99        /// The user's symmetric crypto key, encrypted with the Device Key.
100        device_protected_user_key: UnsignedSharedKey,
101    },
102    /// Key connector
103    KeyConnector {
104        /// Base64 encoded master key, retrieved from the key connector.
105        master_key: String,
106        /// The user's encrypted symmetric crypto key
107        user_key: EncString,
108    },
109}
110
111/// Auth requests supports multiple initialization methods.
112#[derive(Serialize, Deserialize, Debug)]
113#[serde(rename_all = "camelCase", deny_unknown_fields)]
114#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
115#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
116pub enum AuthRequestMethod {
117    /// User Key
118    UserKey {
119        /// User Key protected by the private key provided in `AuthRequestResponse`.
120        protected_user_key: UnsignedSharedKey,
121    },
122    /// Master Key
123    MasterKey {
124        /// Master Key protected by the private key provided in `AuthRequestResponse`.
125        protected_master_key: UnsignedSharedKey,
126        /// User Key protected by the MasterKey, provided by the auth response.
127        auth_request_key: EncString,
128    },
129}
130
131/// Initialize the user's cryptographic state.
132pub(super) async fn initialize_user_crypto(
133    client: &Client,
134    req: InitUserCryptoRequest,
135) -> Result<(), EncryptionSettingsError> {
136    use bitwarden_crypto::{DeviceKey, PinKey};
137
138    use crate::auth::{auth_request_decrypt_master_key, auth_request_decrypt_user_key};
139
140    if let Some(user_id) = req.user_id {
141        client.internal.init_user_id(user_id)?;
142    }
143
144    match req.method {
145        InitUserCryptoMethod::Password { password, user_key } => {
146            let master_key = MasterKey::derive(&password, &req.email, &req.kdf_params)?;
147            client.internal.initialize_user_crypto_master_key(
148                master_key,
149                user_key,
150                req.private_key,
151                req.signing_key,
152            )?;
153        }
154        InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => {
155            let user_key = SymmetricCryptoKey::try_from(decrypted_user_key)?;
156            client.internal.initialize_user_crypto_decrypted_key(
157                user_key,
158                req.private_key,
159                req.signing_key,
160            )?;
161        }
162        InitUserCryptoMethod::Pin {
163            pin,
164            pin_protected_user_key,
165        } => {
166            let pin_key = PinKey::derive(pin.as_bytes(), req.email.as_bytes(), &req.kdf_params)?;
167            client.internal.initialize_user_crypto_pin(
168                pin_key,
169                pin_protected_user_key,
170                req.private_key,
171                req.signing_key,
172            )?;
173        }
174        InitUserCryptoMethod::AuthRequest {
175            request_private_key,
176            method,
177        } => {
178            let user_key = match method {
179                AuthRequestMethod::UserKey { protected_user_key } => {
180                    auth_request_decrypt_user_key(request_private_key, protected_user_key)?
181                }
182                AuthRequestMethod::MasterKey {
183                    protected_master_key,
184                    auth_request_key,
185                } => auth_request_decrypt_master_key(
186                    request_private_key,
187                    protected_master_key,
188                    auth_request_key,
189                )?,
190            };
191            client.internal.initialize_user_crypto_decrypted_key(
192                user_key,
193                req.private_key,
194                req.signing_key,
195            )?;
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.internal.initialize_user_crypto_decrypted_key(
207                user_key,
208                req.private_key,
209                req.signing_key,
210            )?;
211        }
212        InitUserCryptoMethod::KeyConnector {
213            master_key,
214            user_key,
215        } => {
216            let mut master_key_bytes = STANDARD
217                .decode(master_key)
218                .map_err(|_| CryptoError::InvalidKey)?;
219            let master_key = MasterKey::try_from(master_key_bytes.as_mut_slice())?;
220
221            client.internal.initialize_user_crypto_master_key(
222                master_key,
223                user_key,
224                req.private_key,
225                req.signing_key,
226            )?;
227        }
228    }
229
230    client
231        .internal
232        .set_login_method(crate::client::LoginMethod::User(
233            crate::client::UserLoginMethod::Username {
234                client_id: "".to_string(),
235                email: req.email,
236                kdf: req.kdf_params,
237            },
238        ));
239
240    Ok(())
241}
242
243/// Represents the request to initialize the user's organizational cryptographic state.
244#[derive(Serialize, Deserialize, Debug)]
245#[serde(rename_all = "camelCase", deny_unknown_fields)]
246#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
247#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
248pub struct InitOrgCryptoRequest {
249    /// The encryption keys for all the organizations the user is a part of
250    pub organization_keys: HashMap<uuid::Uuid, UnsignedSharedKey>,
251}
252
253/// Initialize the user's organizational cryptographic state.
254pub(super) async fn initialize_org_crypto(
255    client: &Client,
256    req: InitOrgCryptoRequest,
257) -> Result<(), EncryptionSettingsError> {
258    let organization_keys = req.organization_keys.into_iter().collect();
259    client.internal.initialize_org_crypto(organization_keys)?;
260    Ok(())
261}
262
263pub(super) async fn get_user_encryption_key(client: &Client) -> Result<String, CryptoClientError> {
264    let key_store = client.internal.get_key_store();
265    let ctx = key_store.context();
266    // This is needed because the mobile clients need access to the user encryption key
267    #[allow(deprecated)]
268    let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
269
270    Ok(user_key.to_base64())
271}
272
273/// Response from the `update_password` function
274#[derive(Serialize, Deserialize, Debug)]
275#[serde(rename_all = "camelCase", deny_unknown_fields)]
276#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
277#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
278pub struct UpdatePasswordResponse {
279    /// Hash of the new password
280    password_hash: String,
281    /// User key, encrypted with the new password
282    new_key: EncString,
283}
284
285pub(super) fn update_password(
286    client: &Client,
287    new_password: String,
288) -> Result<UpdatePasswordResponse, CryptoClientError> {
289    let key_store = client.internal.get_key_store();
290    let ctx = key_store.context();
291    // FIXME: [PM-18099] Once MasterKey deals with KeyIds, this should be updated
292    #[allow(deprecated)]
293    let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
294
295    let login_method = client
296        .internal
297        .get_login_method()
298        .ok_or(NotAuthenticatedError)?;
299
300    // Derive a new master key from password
301    let new_master_key = match login_method.as_ref() {
302        LoginMethod::User(
303            UserLoginMethod::Username { email, kdf, .. }
304            | UserLoginMethod::ApiKey { email, kdf, .. },
305        ) => MasterKey::derive(&new_password, email, kdf)?,
306        #[cfg(feature = "secrets")]
307        LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?,
308    };
309
310    let new_key = new_master_key.encrypt_user_key(user_key)?;
311
312    let password_hash = new_master_key.derive_master_key_hash(
313        new_password.as_bytes(),
314        bitwarden_crypto::HashPurpose::ServerAuthorization,
315    )?;
316
317    Ok(UpdatePasswordResponse {
318        password_hash,
319        new_key,
320    })
321}
322
323/// Request for deriving a pin protected user key
324#[derive(Serialize, Deserialize, Debug)]
325#[serde(rename_all = "camelCase", deny_unknown_fields)]
326#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
327#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
328pub struct DerivePinKeyResponse {
329    /// [UserKey] protected by PIN
330    pin_protected_user_key: EncString,
331    /// PIN protected by [UserKey]
332    encrypted_pin: EncString,
333}
334
335pub(super) fn derive_pin_key(
336    client: &Client,
337    pin: String,
338) -> Result<DerivePinKeyResponse, CryptoClientError> {
339    let key_store = client.internal.get_key_store();
340    let ctx = key_store.context();
341    // FIXME: [PM-18099] Once PinKey deals with KeyIds, this should be updated
342    #[allow(deprecated)]
343    let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
344
345    let login_method = client
346        .internal
347        .get_login_method()
348        .ok_or(NotAuthenticatedError)?;
349
350    let pin_protected_user_key = derive_pin_protected_user_key(&pin, &login_method, user_key)?;
351
352    Ok(DerivePinKeyResponse {
353        pin_protected_user_key,
354        encrypted_pin: pin.encrypt_with_key(user_key)?,
355    })
356}
357
358pub(super) fn derive_pin_user_key(
359    client: &Client,
360    encrypted_pin: EncString,
361) -> Result<EncString, CryptoClientError> {
362    let key_store = client.internal.get_key_store();
363    let ctx = key_store.context();
364    // FIXME: [PM-18099] Once PinKey deals with KeyIds, this should be updated
365    #[allow(deprecated)]
366    let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
367
368    let pin: String = encrypted_pin.decrypt_with_key(user_key)?;
369    let login_method = client
370        .internal
371        .get_login_method()
372        .ok_or(NotAuthenticatedError)?;
373
374    derive_pin_protected_user_key(&pin, &login_method, user_key)
375}
376
377fn derive_pin_protected_user_key(
378    pin: &str,
379    login_method: &LoginMethod,
380    user_key: &SymmetricCryptoKey,
381) -> Result<EncString, CryptoClientError> {
382    use bitwarden_crypto::PinKey;
383
384    let derived_key = match login_method {
385        LoginMethod::User(
386            UserLoginMethod::Username { email, kdf, .. }
387            | UserLoginMethod::ApiKey { email, kdf, .. },
388        ) => PinKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?,
389        #[cfg(feature = "secrets")]
390        LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?,
391    };
392
393    Ok(derived_key.encrypt_user_key(user_key)?)
394}
395
396#[allow(missing_docs)]
397#[bitwarden_error(flat)]
398#[derive(Debug, thiserror::Error)]
399pub enum EnrollAdminPasswordResetError {
400    #[error(transparent)]
401    VaultLocked(#[from] VaultLockedError),
402    #[error(transparent)]
403    Crypto(#[from] bitwarden_crypto::CryptoError),
404    #[error(transparent)]
405    InvalidBase64(#[from] base64::DecodeError),
406}
407
408pub(super) fn enroll_admin_password_reset(
409    client: &Client,
410    public_key: String,
411) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
412    use base64::{engine::general_purpose::STANDARD, Engine};
413    use bitwarden_crypto::AsymmetricPublicCryptoKey;
414
415    let public_key = AsymmetricPublicCryptoKey::from_der(&STANDARD.decode(public_key)?)?;
416    let key_store = client.internal.get_key_store();
417    let ctx = key_store.context();
418    // FIXME: [PM-18110] This should be removed once the key store can handle public key encryption
419    #[allow(deprecated)]
420    let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
421
422    Ok(UnsignedSharedKey::encapsulate_key_unsigned(
423        key,
424        &public_key,
425    )?)
426}
427
428/// Request for migrating an account from password to key connector.
429#[derive(Serialize, Deserialize, Debug, JsonSchema)]
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 DeriveKeyConnectorRequest {
434    /// Encrypted user key, used to validate the master key
435    pub user_key_encrypted: EncString,
436    /// The user's master password
437    pub password: String,
438    /// The KDF parameters used to derive the master key
439    pub kdf: Kdf,
440    /// The user's email address
441    pub email: String,
442}
443
444#[allow(missing_docs)]
445#[bitwarden_error(flat)]
446#[derive(Debug, thiserror::Error)]
447pub enum DeriveKeyConnectorError {
448    #[error(transparent)]
449    WrongPassword(#[from] WrongPasswordError),
450    #[error(transparent)]
451    Crypto(#[from] bitwarden_crypto::CryptoError),
452}
453
454/// Derive the master key for migrating to the key connector
455pub(super) fn derive_key_connector(
456    request: DeriveKeyConnectorRequest,
457) -> Result<String, DeriveKeyConnectorError> {
458    let master_key = MasterKey::derive(&request.password, &request.email, &request.kdf)?;
459    master_key
460        .decrypt_user_key(request.user_key_encrypted)
461        .map_err(|_| WrongPasswordError)?;
462
463    Ok(master_key.to_base64())
464}
465
466/// Response from the `make_key_pair` function
467#[derive(Serialize, Deserialize, Debug)]
468#[serde(rename_all = "camelCase", deny_unknown_fields)]
469#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
470#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
471pub struct MakeKeyPairResponse {
472    /// The user's public key
473    user_public_key: String,
474    /// User's private key, encrypted with the user key
475    user_key_encrypted_private_key: EncString,
476}
477
478pub(super) fn make_key_pair(user_key: String) -> Result<MakeKeyPairResponse, CryptoError> {
479    let user_key = UserKey::new(SymmetricCryptoKey::try_from(user_key)?);
480
481    let key_pair = user_key.make_key_pair()?;
482
483    Ok(MakeKeyPairResponse {
484        user_public_key: key_pair.public,
485        user_key_encrypted_private_key: key_pair.private,
486    })
487}
488
489/// Request for `verify_asymmetric_keys`.
490#[derive(Serialize, Deserialize, Debug)]
491#[serde(rename_all = "camelCase", deny_unknown_fields)]
492#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
493#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
494pub struct VerifyAsymmetricKeysRequest {
495    /// The user's user key
496    user_key: String,
497    /// The user's public key
498    user_public_key: String,
499    /// User's private key, encrypted with the user key
500    user_key_encrypted_private_key: EncString,
501}
502
503/// Response for `verify_asymmetric_keys`.
504#[derive(Serialize, Deserialize, Debug)]
505#[serde(rename_all = "camelCase", deny_unknown_fields)]
506#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
507#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
508pub struct VerifyAsymmetricKeysResponse {
509    /// Whether the user's private key was decryptable by the user key.
510    private_key_decryptable: bool,
511    /// Whether the user's private key was a valid RSA key and matched the public key provided.
512    valid_private_key: bool,
513}
514
515pub(super) fn verify_asymmetric_keys(
516    request: VerifyAsymmetricKeysRequest,
517) -> Result<VerifyAsymmetricKeysResponse, CryptoError> {
518    #[derive(Debug, thiserror::Error)]
519    enum VerifyError {
520        #[error("Failed to decrypt private key: {0:?}")]
521        DecryptFailed(bitwarden_crypto::CryptoError),
522        #[error("Failed to parse decrypted private key: {0:?}")]
523        ParseFailed(bitwarden_crypto::CryptoError),
524        #[error("Failed to derive a public key: {0:?}")]
525        PublicFailed(bitwarden_crypto::CryptoError),
526        #[error("Derived public key doesn't match")]
527        KeyMismatch,
528    }
529
530    fn verify_inner(
531        user_key: &SymmetricCryptoKey,
532        request: &VerifyAsymmetricKeysRequest,
533    ) -> Result<(), VerifyError> {
534        let decrypted_private_key: Vec<u8> = request
535            .user_key_encrypted_private_key
536            .decrypt_with_key(user_key)
537            .map_err(VerifyError::DecryptFailed)?;
538
539        let private_key = AsymmetricCryptoKey::from_der(&decrypted_private_key)
540            .map_err(VerifyError::ParseFailed)?;
541
542        let derived_public_key_vec = private_key
543            .to_public_key()
544            .to_der()
545            .map_err(VerifyError::PublicFailed)?;
546
547        let derived_public_key = STANDARD.encode(derived_public_key_vec);
548
549        if derived_public_key != request.user_public_key {
550            return Err(VerifyError::KeyMismatch);
551        }
552        Ok(())
553    }
554
555    let user_key = SymmetricCryptoKey::try_from(request.user_key.clone())?;
556
557    Ok(match verify_inner(&user_key, &request) {
558        Ok(_) => VerifyAsymmetricKeysResponse {
559            private_key_decryptable: true,
560            valid_private_key: true,
561        },
562        Err(e) => {
563            log::debug!("User asymmetric keys verification: {}", e);
564
565            VerifyAsymmetricKeysResponse {
566                private_key_decryptable: !matches!(e, VerifyError::DecryptFailed(_)),
567                valid_private_key: false,
568            }
569        }
570    })
571}
572
573/// A new signing key pair along with the signed public key
574#[derive(Serialize, Deserialize, Debug, JsonSchema)]
575#[serde(rename_all = "camelCase", deny_unknown_fields)]
576#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
577#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
578pub struct MakeUserSigningKeysResponse {
579    /// Base64 encoded verifying key
580    verifying_key: String,
581    /// Signing key, encrypted with the user's symmetric key
582    signing_key: EncString,
583    /// The user's public key, signed by the signing key
584    signed_public_key: SignedPublicKey,
585}
586
587/// Makes a new set of signing keys for a user, which should only be done during
588/// once. This also signs the public key with the signing key
589/// and returns the signed public key.
590pub fn make_user_signing_keys_for_enrollment(
591    client: &Client,
592) -> Result<MakeUserSigningKeysResponse, CryptoError> {
593    let key_store = client.internal.get_key_store();
594    let mut ctx = key_store.context();
595
596    // Make new keypair and sign the public key with it
597    let signature_keypair = SigningKey::make(SignatureAlgorithm::Ed25519);
598    let temporary_signature_keypair_id = SigningKeyId::Local("temporary_key_for_rotation");
599    #[allow(deprecated)]
600    ctx.set_signing_key(temporary_signature_keypair_id, signature_keypair.clone())?;
601    let signed_public_key = ctx.make_signed_public_key(
602        AsymmetricKeyId::UserPrivateKey,
603        temporary_signature_keypair_id,
604    )?;
605
606    Ok(MakeUserSigningKeysResponse {
607        verifying_key: STANDARD.encode(signature_keypair.to_verifying_key().to_cose()),
608        // This needs to be changed to use the correct COSE content format before rolling out to
609        // users: https://bitwarden.atlassian.net/browse/PM-22189
610        signing_key: signature_keypair
611            .to_cose()
612            .encrypt(&mut ctx, SymmetricKeyId::User)?,
613        signed_public_key,
614    })
615}
616
617#[cfg(test)]
618mod tests {
619    use std::num::NonZeroU32;
620
621    use bitwarden_crypto::RsaKeyPair;
622
623    use super::*;
624    use crate::Client;
625
626    #[tokio::test]
627    async fn test_update_password() {
628        let client = Client::new(None);
629
630        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();
631
632        let kdf = Kdf::PBKDF2 {
633            iterations: 100_000.try_into().unwrap(),
634        };
635
636        initialize_user_crypto(
637            & client,
638            InitUserCryptoRequest {
639                user_id: Some(uuid::Uuid::new_v4()),
640                kdf_params: kdf.clone(),
641                email: "[email protected]".into(),
642                private_key: priv_key.to_owned(),
643                signing_key: None,
644                method: InitUserCryptoMethod::Password {
645                    password: "asdfasdfasdf".into(),
646                    user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
647                },
648            },
649        )
650        .await
651        .unwrap();
652
653        let new_password_response = update_password(&client, "123412341234".into()).unwrap();
654
655        let client2 = Client::new(None);
656
657        initialize_user_crypto(
658            &client2,
659            InitUserCryptoRequest {
660                user_id: Some(uuid::Uuid::new_v4()),
661                kdf_params: kdf.clone(),
662                email: "[email protected]".into(),
663                private_key: priv_key.to_owned(),
664                signing_key: None,
665                method: InitUserCryptoMethod::Password {
666                    password: "123412341234".into(),
667                    user_key: new_password_response.new_key,
668                },
669            },
670        )
671        .await
672        .unwrap();
673
674        let new_hash = client2
675            .kdf()
676            .hash_password(
677                "[email protected]".into(),
678                "123412341234".into(),
679                kdf.clone(),
680                bitwarden_crypto::HashPurpose::ServerAuthorization,
681            )
682            .await
683            .unwrap();
684
685        assert_eq!(new_hash, new_password_response.password_hash);
686
687        let client_key = {
688            let key_store = client.internal.get_key_store();
689            let ctx = key_store.context();
690            #[allow(deprecated)]
691            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
692                .unwrap()
693                .to_base64()
694        };
695
696        let client2_key = {
697            let key_store = client2.internal.get_key_store();
698            let ctx = key_store.context();
699            #[allow(deprecated)]
700            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
701                .unwrap()
702                .to_base64()
703        };
704
705        assert_eq!(client_key, client2_key);
706    }
707
708    #[tokio::test]
709    async fn test_initialize_user_crypto_pin() {
710        let client = Client::new(None);
711
712        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();
713
714        initialize_user_crypto(
715            & client,
716            InitUserCryptoRequest {
717                user_id: Some(uuid::Uuid::new_v4()),
718                kdf_params: Kdf::PBKDF2 {
719                    iterations: 100_000.try_into().unwrap(),
720                },
721                email: "[email protected]".into(),
722                private_key: priv_key.to_owned(),
723                signing_key: None,
724                method: InitUserCryptoMethod::Password {
725                    password: "asdfasdfasdf".into(),
726                    user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
727                },
728            },
729        )
730        .await
731        .unwrap();
732
733        let pin_key = derive_pin_key(&client, "1234".into()).unwrap();
734
735        // Verify we can unlock with the pin
736        let client2 = Client::new(None);
737        initialize_user_crypto(
738            &client2,
739            InitUserCryptoRequest {
740                user_id: Some(uuid::Uuid::new_v4()),
741                kdf_params: Kdf::PBKDF2 {
742                    iterations: 100_000.try_into().unwrap(),
743                },
744                email: "[email protected]".into(),
745                private_key: priv_key.to_owned(),
746                signing_key: None,
747                method: InitUserCryptoMethod::Pin {
748                    pin: "1234".into(),
749                    pin_protected_user_key: pin_key.pin_protected_user_key,
750                },
751            },
752        )
753        .await
754        .unwrap();
755
756        let client_key = {
757            let key_store = client.internal.get_key_store();
758            let ctx = key_store.context();
759            #[allow(deprecated)]
760            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
761                .unwrap()
762                .to_base64()
763        };
764
765        let client2_key = {
766            let key_store = client2.internal.get_key_store();
767            let ctx = key_store.context();
768            #[allow(deprecated)]
769            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
770                .unwrap()
771                .to_base64()
772        };
773
774        assert_eq!(client_key, client2_key);
775
776        // Verify we can derive the pin protected user key from the encrypted pin
777        let pin_protected_user_key = derive_pin_user_key(&client, pin_key.encrypted_pin).unwrap();
778
779        let client3 = Client::new(None);
780
781        initialize_user_crypto(
782            &client3,
783            InitUserCryptoRequest {
784                user_id: Some(uuid::Uuid::new_v4()),
785                kdf_params: Kdf::PBKDF2 {
786                    iterations: 100_000.try_into().unwrap(),
787                },
788                email: "[email protected]".into(),
789                private_key: priv_key.to_owned(),
790                signing_key: None,
791                method: InitUserCryptoMethod::Pin {
792                    pin: "1234".into(),
793                    pin_protected_user_key,
794                },
795            },
796        )
797        .await
798        .unwrap();
799
800        let client_key = {
801            let key_store = client.internal.get_key_store();
802            let ctx = key_store.context();
803            #[allow(deprecated)]
804            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
805                .unwrap()
806                .to_base64()
807        };
808
809        let client3_key = {
810            let key_store = client3.internal.get_key_store();
811            let ctx = key_store.context();
812            #[allow(deprecated)]
813            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
814                .unwrap()
815                .to_base64()
816        };
817
818        assert_eq!(client_key, client3_key);
819    }
820
821    #[test]
822    fn test_enroll_admin_password_reset() {
823        use base64::{engine::general_purpose::STANDARD, Engine};
824
825        let client = Client::new(None);
826
827        let master_key = MasterKey::derive(
828            "asdfasdfasdf",
829            "[email protected]",
830            &Kdf::PBKDF2 {
831                iterations: NonZeroU32::new(600_000).unwrap(),
832            },
833        )
834        .unwrap();
835
836        let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap();
837        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();
838        client
839            .internal
840            .initialize_user_crypto_master_key(master_key, user_key, private_key, None)
841            .unwrap();
842
843        let public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsy7RFHcX3C8Q4/OMmhhbFReYWfB45W9PDTEA8tUZwZmtOiN2RErIS2M1c+K/4HoDJ/TjpbX1f2MZcr4nWvKFuqnZXyewFc+jmvKVewYi+NAu2++vqKq2kKcmMNhwoQDQdQIVy/Uqlp4Cpi2cIwO6ogq5nHNJGR3jm+CpyrafYlbz1bPvL3hbyoGDuG2tgADhyhXUdFuef2oF3wMvn1lAJAvJnPYpMiXUFmj1ejmbwtlxZDrHgUJvUcp7nYdwUKaFoi+sOttHn3u7eZPtNvxMjhSS/X/1xBIzP/mKNLdywH5LoRxniokUk+fV3PYUxJsiU3lV0Trc/tH46jqd8ZGjmwIDAQAB";
844
845        let encrypted = enroll_admin_password_reset(&client, public_key.to_owned()).unwrap();
846
847        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=";
848        let private_key =
849            AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key).unwrap()).unwrap();
850        let decrypted: SymmetricCryptoKey =
851            encrypted.decapsulate_key_unsigned(&private_key).unwrap();
852
853        let key_store = client.internal.get_key_store();
854        let ctx = key_store.context();
855        #[allow(deprecated)]
856        let expected = ctx
857            .dangerous_get_symmetric_key(SymmetricKeyId::User)
858            .unwrap();
859
860        assert_eq!(decrypted, *expected);
861    }
862
863    #[test]
864    fn test_derive_key_connector() {
865        let request = DeriveKeyConnectorRequest {
866            password: "asdfasdfasdf".to_string(),
867            email: "[email protected]".to_string(),
868            kdf: Kdf::PBKDF2 {
869                iterations: NonZeroU32::new(600_000).unwrap(),
870            },
871            user_key_encrypted: "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(),
872        };
873
874        let result = derive_key_connector(request).unwrap();
875
876        assert_eq!(result, "ySXq1RVLKEaV1eoQE/ui9aFKIvXTl9PAXwp1MljfF50=");
877    }
878
879    fn setup_asymmetric_keys_test() -> (UserKey, RsaKeyPair) {
880        let master_key = MasterKey::derive(
881            "asdfasdfasdf",
882            "[email protected]",
883            &Kdf::PBKDF2 {
884                iterations: NonZeroU32::new(600_000).unwrap(),
885            },
886        )
887        .unwrap();
888        let user_key = (master_key.make_user_key().unwrap()).0;
889        let key_pair = user_key.make_key_pair().unwrap();
890
891        (user_key, key_pair)
892    }
893
894    #[test]
895    fn test_make_key_pair() {
896        let (user_key, _) = setup_asymmetric_keys_test();
897
898        let response = make_key_pair(user_key.0.to_base64()).unwrap();
899
900        assert!(!response.user_public_key.is_empty());
901        let encrypted_private_key = response.user_key_encrypted_private_key;
902        let private_key: Vec<u8> = encrypted_private_key.decrypt_with_key(&user_key.0).unwrap();
903        assert!(!private_key.is_empty());
904    }
905
906    #[test]
907    fn test_verify_asymmetric_keys_success() {
908        let (user_key, key_pair) = setup_asymmetric_keys_test();
909
910        let request = VerifyAsymmetricKeysRequest {
911            user_key: user_key.0.to_base64(),
912            user_public_key: key_pair.public,
913            user_key_encrypted_private_key: key_pair.private,
914        };
915        let response = verify_asymmetric_keys(request).unwrap();
916
917        assert!(response.private_key_decryptable);
918        assert!(response.valid_private_key);
919    }
920
921    #[test]
922    fn test_verify_asymmetric_keys_decrypt_failed() {
923        let (user_key, key_pair) = setup_asymmetric_keys_test();
924        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();
925
926        let request = VerifyAsymmetricKeysRequest {
927            user_key: user_key.0.to_base64(),
928            user_public_key: key_pair.public,
929            user_key_encrypted_private_key: undecryptable_private_key,
930        };
931        let response = verify_asymmetric_keys(request).unwrap();
932
933        assert!(!response.private_key_decryptable);
934        assert!(!response.valid_private_key);
935    }
936
937    #[test]
938    fn test_verify_asymmetric_keys_parse_failed() {
939        let (user_key, key_pair) = setup_asymmetric_keys_test();
940
941        let invalid_private_key = "bad_key"
942            .to_string()
943            .into_bytes()
944            .encrypt_with_key(&user_key.0)
945            .unwrap();
946
947        let request = VerifyAsymmetricKeysRequest {
948            user_key: user_key.0.to_base64(),
949            user_public_key: key_pair.public,
950            user_key_encrypted_private_key: invalid_private_key,
951        };
952        let response = verify_asymmetric_keys(request).unwrap();
953
954        assert!(response.private_key_decryptable);
955        assert!(!response.valid_private_key);
956    }
957
958    #[test]
959    fn test_verify_asymmetric_keys_key_mismatch() {
960        let (user_key, key_pair) = setup_asymmetric_keys_test();
961        let new_key_pair = user_key.make_key_pair().unwrap();
962
963        let request = VerifyAsymmetricKeysRequest {
964            user_key: user_key.0.to_base64(),
965            user_public_key: key_pair.public,
966            user_key_encrypted_private_key: new_key_pair.private,
967        };
968        let response = verify_asymmetric_keys(request).unwrap();
969
970        assert!(response.private_key_decryptable);
971        assert!(!response.valid_private_key);
972    }
973}