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