Skip to main content

bitwarden_core/key_management/
crypto_client.rs

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