bitwarden_core/auth/
auth_request.rs

1use base64::{engine::general_purpose::STANDARD, Engine};
2use bitwarden_crypto::{
3    fingerprint, generate_random_alphanumeric, AsymmetricCryptoKey, AsymmetricPublicCryptoKey,
4    CryptoError, PublicKeyEncryptionAlgorithm, SpkiPublicKeyBytes, UnsignedSharedKey,
5};
6#[cfg(feature = "internal")]
7use bitwarden_crypto::{EncString, SymmetricCryptoKey};
8use thiserror::Error;
9
10#[cfg(feature = "internal")]
11use crate::client::encryption_settings::EncryptionSettingsError;
12use crate::{key_management::SymmetricKeyId, Client, VaultLockedError};
13
14/// Response for `new_auth_request`.
15#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
16pub struct AuthRequestResponse {
17    /// Base64 encoded private key
18    /// This key is temporarily passed back and will most likely not be available in the future
19    pub private_key: String,
20    /// Base64 encoded public key
21    pub public_key: String,
22    /// Fingerprint of the public key
23    pub fingerprint: String,
24    /// Access code
25    pub access_code: String,
26}
27
28/// Initiate a new auth request.
29///
30/// Generates a private key and access code. The pulic key is uploaded to the server and transmitted
31/// to another device. Where the user confirms the validity by confirming the fingerprint. The user
32/// key is then encrypted using the public key and returned to the initiating device.
33pub(crate) fn new_auth_request(email: &str) -> Result<AuthRequestResponse, CryptoError> {
34    let key = AsymmetricCryptoKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
35
36    let spki = key.to_public_key().to_der()?;
37
38    let fingerprint = fingerprint(email, &spki)?;
39    let b64 = STANDARD.encode(&spki);
40
41    Ok(AuthRequestResponse {
42        private_key: STANDARD.encode(key.to_der()?),
43        public_key: b64,
44        fingerprint,
45        access_code: generate_random_alphanumeric(25),
46    })
47}
48
49/// Decrypt the user key using the private key generated previously.
50#[cfg(feature = "internal")]
51pub(crate) fn auth_request_decrypt_user_key(
52    private_key: String,
53    user_key: UnsignedSharedKey,
54) -> Result<SymmetricCryptoKey, EncryptionSettingsError> {
55    let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?.into())?;
56    let key: SymmetricCryptoKey = user_key.decapsulate_key_unsigned(&key)?;
57    Ok(key)
58}
59
60/// Decrypt the user key using the private key generated previously.
61#[cfg(feature = "internal")]
62pub(crate) fn auth_request_decrypt_master_key(
63    private_key: String,
64    master_key: UnsignedSharedKey,
65    user_key: EncString,
66) -> Result<SymmetricCryptoKey, EncryptionSettingsError> {
67    use bitwarden_crypto::MasterKey;
68
69    let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?.into())?;
70    let master_key: SymmetricCryptoKey = master_key.decapsulate_key_unsigned(&key)?;
71    let master_key = MasterKey::try_from(&master_key)?;
72
73    Ok(master_key.decrypt_user_key(user_key)?)
74}
75
76#[allow(missing_docs)]
77#[derive(Debug, Error)]
78pub enum ApproveAuthRequestError {
79    #[error(transparent)]
80    Crypto(#[from] CryptoError),
81    #[error(transparent)]
82    VaultLocked(#[from] VaultLockedError),
83    #[error(transparent)]
84    InvalidBase64(#[from] base64::DecodeError),
85}
86
87/// Approve an auth request.
88///
89/// Encrypts the user key with a public key.
90pub(crate) fn approve_auth_request(
91    client: &Client,
92    public_key: String,
93) -> Result<UnsignedSharedKey, ApproveAuthRequestError> {
94    let public_key = AsymmetricPublicCryptoKey::from_der(&SpkiPublicKeyBytes::from(
95        STANDARD.decode(public_key)?,
96    ))?;
97
98    let key_store = client.internal.get_key_store();
99    let ctx = key_store.context();
100
101    // FIXME: [PM-18110] This should be removed once the key store can handle public key encryption
102    #[allow(deprecated)]
103    let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
104
105    Ok(UnsignedSharedKey::encapsulate_key_unsigned(
106        key,
107        &public_key,
108    )?)
109}
110
111#[cfg(test)]
112mod tests {
113    use std::num::NonZeroU32;
114
115    use bitwarden_crypto::{BitwardenLegacyKeyBytes, Kdf, MasterKey, SpkiPublicKeyBytes};
116
117    use super::*;
118    use crate::{
119        client::internal::UserKeyState,
120        key_management::{
121            crypto::{AuthRequestMethod, InitUserCryptoMethod, InitUserCryptoRequest},
122            SymmetricKeyId,
123        },
124    };
125
126    #[test]
127    fn test_auth_request() {
128        let request = new_auth_request("[email protected]").unwrap();
129
130        let secret = vec![
131            111, 32, 97, 169, 4, 241, 174, 74, 239, 206, 113, 86, 174, 68, 216, 238, 52, 85, 156,
132            27, 134, 149, 54, 55, 91, 147, 45, 130, 131, 237, 51, 31, 191, 106, 155, 14, 160, 82,
133            47, 40, 96, 31, 114, 127, 212, 187, 167, 110, 205, 116, 198, 243, 218, 72, 137, 53,
134            248, 43, 255, 67, 35, 61, 245, 93,
135        ];
136
137        let private_key =
138            AsymmetricCryptoKey::from_der(&STANDARD.decode(&request.private_key).unwrap().into())
139                .unwrap();
140
141        let secret = BitwardenLegacyKeyBytes::from(secret);
142        let encrypted = UnsignedSharedKey::encapsulate_key_unsigned(
143            &SymmetricCryptoKey::try_from(&secret).unwrap(),
144            &private_key.to_public_key(),
145        )
146        .unwrap();
147
148        let decrypted = auth_request_decrypt_user_key(request.private_key, encrypted).unwrap();
149
150        assert_eq!(decrypted.to_encoded().to_vec(), secret.to_vec());
151    }
152
153    #[test]
154    fn test_approve() {
155        let client = Client::new(None);
156
157        let master_key = MasterKey::derive(
158            "asdfasdfasdf",
159            "[email protected]",
160            &Kdf::PBKDF2 {
161                iterations: NonZeroU32::new(600_000).unwrap(),
162            },
163        )
164        .unwrap();
165
166        let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap();
167        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();
168        client
169            .internal
170            .initialize_user_crypto_master_key(
171                master_key,
172                user_key,
173                UserKeyState {
174                    private_key,
175                    signing_key: None,
176                    security_state: None,
177                },
178            )
179            .unwrap();
180
181        let public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvyLRDUwXB4BfQ507D4meFPmwn5zwy3IqTPJO4plrrhnclWahXa240BzyFW9gHgYu+Jrgms5xBfRTBMcEsqqNm7+JpB6C1B6yvnik0DpJgWQw1rwvy4SUYidpR/AWbQi47n/hvnmzI/sQxGddVfvWu1iTKOlf5blbKYAXnUE5DZBGnrWfacNXwRRdtP06tFB0LwDgw+91CeLSJ9py6dm1qX5JIxoO8StJOQl65goLCdrTWlox+0Jh4xFUfCkb+s3px+OhSCzJbvG/hlrSRcUz5GnwlCEyF3v5lfUtV96MJD+78d8pmH6CfFAp2wxKRAbGdk+JccJYO6y6oIXd3Fm7twIDAQAB";
182
183        // Verify fingerprint
184        let pubkey = STANDARD.decode(public_key).unwrap();
185        let pubkey = SpkiPublicKeyBytes::from(pubkey.clone());
186        let fingerprint = fingerprint("[email protected]", &pubkey).unwrap();
187        assert_eq!(fingerprint, "childless-unfair-prowler-dropbox-designate");
188
189        approve_auth_request(&client, public_key.to_owned()).unwrap();
190    }
191
192    #[tokio::test]
193    async fn test_decrypt_user_key() {
194        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=";
195
196        let enc_user_key = "4.dxbd5OMwi/Avy7DQxvLV+Z7kDJgHBtg/jAbgYNO7QU0Zii4rLFNco2lS5aS9z42LTZHc2p5HYwn2ZwkZNfHsQ6//d5q40MDgGYJMKBXOZP62ZHhct1XsvYBmtcUtIOm5j2HSjt2pjEuGAc1LbyGIWRJJQ3Lp1ULbL2m71I+P23GF36JyOM8SUWvpvxE/3+qqVhRFPG2VqMCYa2kLLxwVfUmpV+KKjX1TXsrq6pfJIwHNwHw4h7MSfD8xTy2bx4MiBt638Z9Vt1pGsSQkh9RgPvCbnhuCpZQloUgJ8ByLVEcrlKx3yaaxiQXvte+ZhuOI7rGdjmoVoOzisooje4JgYw==".parse().unwrap();
197        let dec = auth_request_decrypt_user_key(private_key.to_owned(), enc_user_key).unwrap();
198
199        assert_eq!(
200            &dec.to_encoded().to_vec(),
201            &[
202                201, 37, 234, 213, 21, 75, 40, 70, 149, 213, 234, 16, 19, 251, 162, 245, 161, 74,
203                34, 245, 211, 151, 211, 192, 95, 10, 117, 50, 88, 223, 23, 157
204            ]
205        );
206    }
207
208    #[tokio::test]
209    async fn test_decrypt_master_key() {
210        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=";
211
212        let enc_master_key = "4.dxbd5OMwi/Avy7DQxvLV+Z7kDJgHBtg/jAbgYNO7QU0Zii4rLFNco2lS5aS9z42LTZHc2p5HYwn2ZwkZNfHsQ6//d5q40MDgGYJMKBXOZP62ZHhct1XsvYBmtcUtIOm5j2HSjt2pjEuGAc1LbyGIWRJJQ3Lp1ULbL2m71I+P23GF36JyOM8SUWvpvxE/3+qqVhRFPG2VqMCYa2kLLxwVfUmpV+KKjX1TXsrq6pfJIwHNwHw4h7MSfD8xTy2bx4MiBt638Z9Vt1pGsSQkh9RgPvCbnhuCpZQloUgJ8ByLVEcrlKx3yaaxiQXvte+ZhuOI7rGdjmoVoOzisooje4JgYw==".parse().unwrap();
213        let enc_user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap();
214        let dec =
215            auth_request_decrypt_master_key(private_key.to_owned(), enc_master_key, enc_user_key)
216                .unwrap();
217
218        assert_eq!(
219            &dec.to_encoded().to_vec(),
220            &[
221                109, 128, 172, 147, 206, 123, 134, 95, 16, 36, 155, 113, 201, 18, 186, 230, 216,
222                212, 173, 188, 74, 11, 134, 131, 137, 242, 105, 178, 105, 126, 52, 139, 248, 91,
223                215, 21, 128, 91, 226, 222, 165, 67, 251, 34, 83, 81, 77, 147, 225, 76, 13, 41,
224                102, 45, 183, 218, 106, 89, 254, 208, 251, 101, 130, 10,
225            ]
226        );
227    }
228
229    #[tokio::test]
230    async fn test_device_login() {
231        let kdf = Kdf::PBKDF2 {
232            iterations: NonZeroU32::new(600_000).unwrap(),
233        };
234        let email = "[email protected]";
235
236        let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap();
237        let private_key: EncString = "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();
238
239        // Initialize an existing client which is unlocked
240        let existing_device = Client::new(None);
241
242        let master_key = MasterKey::derive("asdfasdfasdf", email, &kdf).unwrap();
243
244        existing_device
245            .internal
246            .initialize_user_crypto_master_key(
247                master_key,
248                user_key,
249                UserKeyState {
250                    private_key: private_key.clone(),
251                    signing_key: None,
252                    security_state: None,
253                },
254            )
255            .unwrap();
256
257        // Initialize a new device which will request to be logged in
258        let new_device = Client::new(None);
259
260        // Initialize an auth request, and approve it on the existing device
261        let auth_req = new_auth_request(email).unwrap();
262        let approved_req = approve_auth_request(&existing_device, auth_req.public_key).unwrap();
263
264        // Unlock the vault using the approved request
265        new_device
266            .crypto()
267            .initialize_user_crypto(InitUserCryptoRequest {
268                user_id: Some(uuid::Uuid::new_v4()),
269                kdf_params: kdf,
270                email: email.to_owned(),
271                private_key,
272                signing_key: None,
273                security_state: None,
274                method: InitUserCryptoMethod::AuthRequest {
275                    request_private_key: auth_req.private_key,
276                    method: AuthRequestMethod::UserKey {
277                        protected_user_key: approved_req,
278                    },
279                },
280            })
281            .await
282            .unwrap();
283
284        // We can validate that the vault is unlocked correctly by confirming the user key is the
285        // same
286
287        let existing_key = {
288            let key_store = existing_device.internal.get_key_store();
289            let ctx = key_store.context();
290            #[allow(deprecated)]
291            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
292                .unwrap()
293                .to_base64()
294        };
295
296        let new_key = {
297            let key_store = new_device.internal.get_key_store();
298            let ctx = key_store.context();
299            #[allow(deprecated)]
300            ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
301                .unwrap()
302                .to_base64()
303        };
304
305        assert_eq!(existing_key, new_key);
306    }
307}