Skip to main content

bitwarden_core/key_management/
crypto_client.rs

1use bitwarden_crypto::{
2    BitwardenLegacyKeyBytes, Decryptable, Kdf, PrimitiveEncryptable, RotateableKeySet,
3    SymmetricCryptoKey, SymmetricKeyAlgorithm,
4};
5#[cfg(feature = "internal")]
6use bitwarden_crypto::{EncString, UnsignedSharedKey};
7use bitwarden_encoding::B64;
8
9use super::crypto::{
10    DeriveKeyConnectorError, DeriveKeyConnectorRequest, EnrollAdminPasswordResetError,
11    MakeJitMasterPasswordRegistrationResponse, MakeKeyConnectorRegistrationResponse,
12    MakeUserMasterPasswordRegistrationResponse, derive_key_connector,
13    make_user_jit_master_password_registration, make_user_key_connector_registration,
14    make_user_password_registration,
15};
16#[cfg(any(feature = "uniffi", feature = "wasm"))]
17use crate::key_management::crypto::{
18    ReinitUserCryptoError, ReinitUserCryptoRequest, reinit_user_crypto,
19};
20#[cfg(feature = "internal")]
21use crate::key_management::{
22    SymmetricKeySlotId,
23    crypto::{
24        DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest, UpdatePasswordResponse,
25        derive_pin_key, derive_pin_user_key, enroll_admin_password_reset, get_user_encryption_key,
26        initialize_org_crypto, initialize_user_crypto, make_prf_user_key_set,
27    },
28};
29use crate::{
30    Client,
31    client::encryption_settings::EncryptionSettingsError,
32    error::NotAuthenticatedError,
33    key_management::{
34        V2UpgradeToken,
35        crypto::{
36            CryptoClientError, EnrollPinResponse, MakeKeysError, MakeTdeRegistrationResponse,
37            UpdateKdfResponse, enroll_pin, make_update_kdf, make_update_password,
38            make_user_tde_registration,
39        },
40    },
41};
42
43/// A client for the crypto operations.
44#[bitwarden_ffi::wasm_object]
45pub struct CryptoClient {
46    pub(crate) client: crate::Client,
47}
48
49#[bitwarden_ffi::wasm_export]
50impl CryptoClient {
51    /// Initialization method for the user crypto. Needs to be called before any other crypto
52    /// operations.
53    pub async fn initialize_user_crypto(
54        &self,
55        req: InitUserCryptoRequest,
56    ) -> Result<(), EncryptionSettingsError> {
57        initialize_user_crypto(&self.client, req).await
58    }
59
60    /// Initialization method for the organization crypto. Needs to be called after
61    /// `initialize_user_crypto` but before any other crypto operations.
62    pub async fn initialize_org_crypto(
63        &self,
64        req: InitOrgCryptoRequest,
65    ) -> Result<(), EncryptionSettingsError> {
66        initialize_org_crypto(&self.client, req).await
67    }
68
69    /// Create the data necessary to update the user's kdf settings. The user's encryption key is
70    /// re-encrypted for the password under the new kdf settings. This returns the re-encrypted
71    /// user key and the new password hash but does not update sdk state.
72    ///
73    /// Note: This is deprecated. Please use the user-crypto-management client instead.
74    pub async fn make_update_kdf(
75        &self,
76        password: String,
77        kdf: Kdf,
78    ) -> Result<UpdateKdfResponse, CryptoClientError> {
79        make_update_kdf(&self.client, &password, &kdf).await
80    }
81
82    /// Protects the current user key with the provided PIN. The result can be stored and later
83    /// used to initialize another client instance by using the PIN and the PIN key with
84    /// `initialize_user_crypto`.
85    pub fn enroll_pin(&self, pin: String) -> Result<EnrollPinResponse, CryptoClientError> {
86        enroll_pin(&self.client, pin)
87    }
88
89    /// Protects the current user key with the provided PIN. The result can be stored and later
90    /// used to initialize another client instance by using the PIN and the PIN key with
91    /// `initialize_user_crypto`. The provided pin is encrypted with the user key.
92    pub fn enroll_pin_with_encrypted_pin(
93        &self,
94        // Note: This will be replaced by `EncString` with https://bitwarden.atlassian.net/browse/PM-24775
95        encrypted_pin: String,
96    ) -> Result<EnrollPinResponse, CryptoClientError> {
97        let encrypted_pin: EncString = encrypted_pin.parse()?;
98        let pin = encrypted_pin.decrypt(
99            &mut self.client.internal.get_key_store().context_mut(),
100            SymmetricKeySlotId::User,
101        )?;
102        enroll_pin(&self.client, pin)
103    }
104
105    /// A stop gap-solution for encrypting with the local user data key, until the WASM client's
106    /// password generator history encryption and email forwarders encryption is fully migrated to
107    /// SDK.
108    pub fn encrypt_with_local_user_data_key(
109        &self,
110        plaintext: String,
111    ) -> Result<String, CryptoClientError> {
112        let mut ctx = self.client.internal.get_key_store().context_mut();
113        plaintext
114            .encrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
115            .map_err(CryptoClientError::Crypto)
116            .map(|enc| enc.to_string())
117    }
118
119    /// A stop gap-solution for decrypting with the local user data key, until the WASM client's
120    /// password generator history encryption and email forwarders encryption is fully migrated to
121    /// SDK.
122    pub fn decrypt_with_local_user_data_key(
123        &self,
124        encrypted_plaintext: String,
125    ) -> Result<String, CryptoClientError> {
126        let mut ctx = self.client.internal.get_key_store().context_mut();
127        let encrypted: EncString = encrypted_plaintext
128            .parse()
129            .map_err(CryptoClientError::Crypto)?;
130        encrypted
131            .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
132            .map_err(CryptoClientError::Crypto)
133    }
134
135    /// ⚠️⚠️⚠️ HAZMAT WARNING: DO NOT USE THIS ⚠️⚠️⚠️
136    ///
137    /// Get the uses's decrypted encryption key. Note: It's very important
138    /// to keep this key safe, as it can be used to decrypt all of the user's data. It is
139    /// only permitted to use for a transition period where side effects such as biometrics
140    /// and never-lock are set from within the client code.
141    pub async fn get_user_encryption_key(&self) -> Result<B64, CryptoClientError> {
142        get_user_encryption_key(&self.client).await
143    }
144
145    /// Takes a raw key and returns the corresponding key id. This is used for the biometrics
146    /// subsystem and should be removed after moving over biometric management to the SDK.
147    pub fn get_key_id_for_symmetric_key(
148        key: Vec<u8>,
149    ) -> Result<Option<Vec<u8>>, CryptoClientError> {
150        let symmetric_key = SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(key))?;
151        Ok(symmetric_key.key_id().map(|id| id.as_slice().to_vec()))
152    }
153}
154
155impl CryptoClient {
156    /// Create the data necessary to update the user's password. The user's encryption key is
157    /// re-encrypted with the new password. This returns the new encrypted user key and the new
158    /// password hash but does not update sdk state.
159    pub async fn make_update_password(
160        &self,
161        new_password: String,
162    ) -> Result<UpdatePasswordResponse, CryptoClientError> {
163        make_update_password(&self.client, new_password).await
164    }
165
166    /// Generates a PIN protected user key from the provided PIN. The result can be stored and later
167    /// used to initialize another client instance by using the PIN and the PIN key with
168    /// `initialize_user_crypto`.
169    pub async fn derive_pin_key(
170        &self,
171        pin: String,
172    ) -> Result<DerivePinKeyResponse, CryptoClientError> {
173        derive_pin_key(&self.client, pin).await
174    }
175
176    /// Derives the pin protected user key from encrypted pin. Used when pin requires master
177    /// password on first unlock.
178    pub async fn derive_pin_user_key(
179        &self,
180        encrypted_pin: EncString,
181    ) -> Result<EncString, CryptoClientError> {
182        derive_pin_user_key(&self.client, encrypted_pin).await
183    }
184
185    /// Creates a new rotateable key set for the current user key protected
186    /// by a key derived from the given PRF.
187    pub fn make_prf_user_key_set(&self, prf: B64) -> Result<RotateableKeySet, CryptoClientError> {
188        make_prf_user_key_set(&self.client, prf)
189    }
190
191    /// Prepares the account for being enrolled in the admin password reset feature. This encrypts
192    /// the users [UserKey][bitwarden_crypto::UserKey] with the organization's public key.
193    pub fn enroll_admin_password_reset(
194        &self,
195        public_key: B64,
196    ) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
197        enroll_admin_password_reset(&self.client, public_key)
198    }
199
200    /// Derive the master key for migrating to the key connector
201    pub fn derive_key_connector(
202        &self,
203        request: DeriveKeyConnectorRequest,
204    ) -> Result<B64, DeriveKeyConnectorError> {
205        derive_key_connector(request)
206    }
207
208    /// Creates a new V2 account cryptographic state for TDE registration.
209    /// This generates fresh cryptographic keys (private key, signing key, signed public key,
210    /// and security state) wrapped with a new user key.
211    pub fn make_user_tde_registration(
212        &self,
213        org_public_key: B64,
214    ) -> Result<MakeTdeRegistrationResponse, MakeKeysError> {
215        make_user_tde_registration(&self.client, org_public_key)
216    }
217
218    /// Creates a new V2 account cryptographic state for Key Connector registration.
219    /// This generates fresh cryptographic keys (private key, signing key, signed public key,
220    /// and security state) wrapped with a new user key.
221    pub fn make_user_key_connector_registration(
222        &self,
223    ) -> Result<MakeKeyConnectorRegistrationResponse, MakeKeysError> {
224        make_user_key_connector_registration(&self.client)
225    }
226
227    /// Creates a new V2 account cryptographic state for SSO JIT master password registration.
228    /// This generates fresh cryptographic keys (private key, signing key, signed public key,
229    /// and security state) wrapped with a new user key.
230    pub fn make_user_jit_master_password_registration(
231        &self,
232        master_password: String,
233        salt: String,
234        org_public_key: B64,
235    ) -> Result<MakeJitMasterPasswordRegistrationResponse, MakeKeysError> {
236        make_user_jit_master_password_registration(
237            &self.client,
238            master_password,
239            salt,
240            org_public_key,
241        )
242    }
243
244    /// Creates new V2 account cryptographic state for password-based registration
245    /// This generates fresh cryptographic keys (private key, signing key, signed public key,
246    /// security state) wrapped with a new user key.
247    pub fn make_user_password_registration(
248        &self,
249        master_password: String,
250        salt: String,
251    ) -> Result<MakeUserMasterPasswordRegistrationResponse, MakeKeysError> {
252        make_user_password_registration(&self.client, master_password, salt)
253    }
254
255    /// Gets the upgraded V2 user key using an upgrade token.
256    /// If the current key is already V2, returns it directly.
257    /// If the current key is V1 and a token is provided, extracts the V2 key.
258    pub fn get_upgraded_user_key(
259        &self,
260        upgrade_token: Option<V2UpgradeToken>,
261    ) -> Result<B64, CryptoClientError> {
262        let mut ctx = self.client.internal.get_key_store().context_mut();
263
264        let algorithm = ctx
265            .get_symmetric_key_algorithm(SymmetricKeySlotId::User)
266            .map_err(|_| CryptoClientError::NotAuthenticated(NotAuthenticatedError))?;
267
268        match (algorithm, upgrade_token) {
269            // Already V2, return current key
270            (SymmetricKeyAlgorithm::XAes256Gcm, _) => {
271                #[allow(deprecated)]
272                let current_key = ctx
273                    .dangerous_get_symmetric_key(SymmetricKeySlotId::User)
274                    .map_err(|_| CryptoClientError::NotAuthenticated(NotAuthenticatedError))?;
275                Ok(current_key.clone().to_base64())
276            }
277            // V1 with token, extract V2
278            (SymmetricKeyAlgorithm::Aes256CbcHmac, Some(token)) => {
279                let v2_key_id = token
280                    .unwrap_v2(SymmetricKeySlotId::User, &mut ctx)
281                    .map_err(|_| CryptoClientError::InvalidUpgradeToken)?;
282                #[allow(deprecated)]
283                let v2_key = ctx
284                    .dangerous_get_symmetric_key(v2_key_id)
285                    .map_err(|_| CryptoClientError::InvalidUpgradeToken)?;
286                Ok(v2_key.clone().to_base64())
287            }
288            // V1 without token, error
289            (SymmetricKeyAlgorithm::Aes256CbcHmac, None) => {
290                Err(CryptoClientError::UpgradeTokenRequired)
291            }
292            (SymmetricKeyAlgorithm::XChaCha20Poly1305 | SymmetricKeyAlgorithm::Aes256Gcm, _) => {
293                Err(CryptoClientError::InvalidKeyType)
294            }
295        }
296    }
297}
298
299#[cfg(any(feature = "uniffi", feature = "wasm"))]
300#[bitwarden_ffi::wasm_export]
301impl CryptoClient {
302    /// Re-initialize the user's cryptographic state during an unlock session.
303    ///
304    /// Requires the SDK to be unlocked. Replaces the in-memory account
305    /// cryptographic state with the provided one, and upgrades the active user key from V1 to V2.
306    pub async fn reinit_user_crypto(
307        &self,
308        req: ReinitUserCryptoRequest,
309    ) -> Result<(), ReinitUserCryptoError> {
310        reinit_user_crypto(&self.client, req).await
311    }
312}
313
314impl Client {
315    /// Access to crypto functionality.
316    pub fn crypto(&self) -> CryptoClient {
317        CryptoClient {
318            client: self.clone(),
319        }
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use bitwarden_crypto::{
326        KeyStore, SymmetricCryptoKey, safe::PasswordProtectedKeyEnvelopeNamespace,
327    };
328
329    use super::*;
330    use crate::{
331        client::test_accounts::{test_bitwarden_com_account, test_bitwarden_com_account_v2},
332        key_management::{KeySlotIds, V2UpgradeToken},
333    };
334
335    #[tokio::test]
336    async fn test_enroll_pin_envelope() {
337        // Initialize a test client with user crypto
338        let client = Client::init_test_account(test_bitwarden_com_account()).await;
339
340        // Enroll with a PIN, then re-enroll
341        let pin = "1234";
342        let enroll_response = client.crypto().enroll_pin(pin.to_string()).unwrap();
343        let re_enroll_response = client
344            .crypto()
345            .enroll_pin_with_encrypted_pin(enroll_response.user_key_encrypted_pin.to_string())
346            .unwrap();
347
348        let mut ctx = client.internal.get_key_store().context_mut();
349        let unsealed_user_key = re_enroll_response
350            .pin_protected_user_key_envelope
351            .unseal(
352                pin,
353                PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
354                &mut ctx,
355            )
356            .unwrap();
357        ctx.assert_symmetric_keys_equal(unsealed_user_key, SymmetricKeySlotId::User);
358    }
359
360    #[test]
361    fn test_get_upgraded_user_key_not_authenticated() {
362        let client = Client::new(None);
363        let result = client.crypto().get_upgraded_user_key(None);
364        assert!(matches!(
365            result,
366            Err(CryptoClientError::NotAuthenticated(_))
367        ));
368    }
369
370    #[tokio::test]
371    async fn test_get_upgraded_user_key_v1_no_token_returns_error() {
372        let client = Client::init_test_account(test_bitwarden_com_account()).await;
373        let result = client.crypto().get_upgraded_user_key(None);
374        assert!(matches!(
375            result,
376            Err(CryptoClientError::UpgradeTokenRequired)
377        ));
378    }
379
380    #[tokio::test]
381    async fn test_get_upgraded_user_key_v1_with_token_returns_v2_key() {
382        let client = Client::init_test_account(test_bitwarden_com_account()).await;
383
384        // Add a fresh V2 key to the client's keystore and build a token linking it to the V1 key
385        let (token, expected_v2_b64) = {
386            let mut ctx = client.internal.get_key_store().context_mut();
387            let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
388            #[allow(deprecated)]
389            let v2_key = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap().clone();
390            let token = V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap();
391            (token, v2_key.to_base64())
392        };
393
394        let result = client.crypto().get_upgraded_user_key(Some(token)).unwrap();
395        assert_eq!(result, expected_v2_b64);
396    }
397
398    #[tokio::test]
399    async fn test_get_upgraded_user_key_v1_invalid_token_returns_error() {
400        let client = Client::init_test_account(test_bitwarden_com_account()).await;
401
402        // Token built with a different V1 key — unwrapping with the client's V1 key will fail
403        let mismatched_token = {
404            let key_store = KeyStore::<KeySlotIds>::default();
405            let mut ctx = key_store.context_mut();
406            let wrong_v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
407            let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
408            V2UpgradeToken::create(wrong_v1_id, v2_id, &ctx).unwrap()
409        };
410
411        let result = client
412            .crypto()
413            .get_upgraded_user_key(Some(mismatched_token));
414        assert!(matches!(
415            result,
416            Err(CryptoClientError::InvalidUpgradeToken)
417        ));
418    }
419
420    #[tokio::test]
421    async fn test_get_upgraded_user_key_already_v2_no_token_returns_v2_key() {
422        let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
423
424        let result = client.crypto().get_upgraded_user_key(None).unwrap();
425        let result_key = SymmetricCryptoKey::try_from(result).unwrap();
426        assert!(
427            matches!(result_key, SymmetricCryptoKey::XAes256GcmKey(_)),
428            "V2 user should receive a V2 key"
429        );
430    }
431
432    #[tokio::test]
433    async fn test_get_upgraded_user_key_already_v2_with_token_ignored() {
434        let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
435
436        // Build a structurally valid token with unrelated keys; it must be ignored for V2 users.
437        let dummy_token = {
438            let key_store = KeyStore::<KeySlotIds>::default();
439            let mut ctx = key_store.context_mut();
440            let v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
441            let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
442            V2UpgradeToken::create(v1_id, v2_id, &ctx).unwrap()
443        };
444
445        let result_with_token = client
446            .crypto()
447            .get_upgraded_user_key(Some(dummy_token))
448            .unwrap();
449        let result_no_token = client.crypto().get_upgraded_user_key(None).unwrap();
450        assert_eq!(
451            result_with_token, result_no_token,
452            "Token must be ignored for a V2 user"
453        );
454    }
455}