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