bitwarden_core/auth/
auth_request.rs

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