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