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            (SymmetricKeyAlgorithm::Aes256Gcm, _) => Err(CryptoClientError::InvalidKeyType),
347        }
348    }
349}
350
351#[cfg(feature = "uniffi")]
352impl CryptoClient {
353    /// Re-initialize the user's cryptographic state during an unlock session.
354    ///
355    /// Requires the SDK to be unlocked. Replaces the in-memory account
356    /// cryptographic state with the provided one, and upgrades the active user key from V1 to V2.
357    pub async fn reinit_user_crypto(
358        &self,
359        req: ReinitUserCryptoRequest,
360    ) -> Result<(), ReinitUserCryptoError> {
361        reinit_user_crypto(&self.client, req).await
362    }
363}
364
365impl Client {
366    /// Access to crypto functionality.
367    pub fn crypto(&self) -> CryptoClient {
368        CryptoClient {
369            client: self.clone(),
370        }
371    }
372}
373
374#[cfg(test)]
375mod tests {
376    use bitwarden_crypto::{BitwardenLegacyKeyBytes, KeyStore, SymmetricCryptoKey};
377
378    use super::*;
379    use crate::{
380        client::test_accounts::{test_bitwarden_com_account, test_bitwarden_com_account_v2},
381        key_management::{KeySlotIds, V2UpgradeToken},
382    };
383
384    #[tokio::test]
385    async fn test_enroll_pin_envelope() {
386        // Initialize a test client with user crypto
387        let client = Client::init_test_account(test_bitwarden_com_account()).await;
388        let user_key_initial =
389            SymmetricCryptoKey::try_from(client.crypto().get_user_encryption_key().await.unwrap())
390                .unwrap();
391
392        // Enroll with a PIN, then re-enroll
393        let pin = "1234";
394        let enroll_response = client.crypto().enroll_pin(pin.to_string()).unwrap();
395        let re_enroll_response = client
396            .crypto()
397            .enroll_pin_with_encrypted_pin(enroll_response.user_key_encrypted_pin.to_string())
398            .unwrap();
399
400        let secret = BitwardenLegacyKeyBytes::from(
401            client
402                .crypto()
403                .unseal_password_protected_key_envelope(
404                    pin.to_string(),
405                    re_enroll_response.pin_protected_user_key_envelope,
406                )
407                .unwrap(),
408        );
409        let user_key_final = SymmetricCryptoKey::try_from(&secret).expect("valid user key");
410        assert_eq!(user_key_initial, user_key_final);
411    }
412
413    #[test]
414    fn test_get_upgraded_user_key_not_authenticated() {
415        let client = Client::new(None);
416        let result = client.crypto().get_upgraded_user_key(None);
417        assert!(matches!(
418            result,
419            Err(CryptoClientError::NotAuthenticated(_))
420        ));
421    }
422
423    #[tokio::test]
424    async fn test_get_upgraded_user_key_v1_no_token_returns_error() {
425        let client = Client::init_test_account(test_bitwarden_com_account()).await;
426        let result = client.crypto().get_upgraded_user_key(None);
427        assert!(matches!(
428            result,
429            Err(CryptoClientError::UpgradeTokenRequired)
430        ));
431    }
432
433    #[tokio::test]
434    async fn test_get_upgraded_user_key_v1_with_token_returns_v2_key() {
435        let client = Client::init_test_account(test_bitwarden_com_account()).await;
436
437        // Add a fresh V2 key to the client's keystore and build a token linking it to the V1 key
438        let (token, expected_v2_b64) = {
439            let mut ctx = client.internal.get_key_store().context_mut();
440            let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
441            #[allow(deprecated)]
442            let v2_key = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap().clone();
443            let token = V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap();
444            (token, v2_key.to_base64())
445        };
446
447        let result = client.crypto().get_upgraded_user_key(Some(token)).unwrap();
448        assert_eq!(result, expected_v2_b64);
449    }
450
451    #[tokio::test]
452    async fn test_get_upgraded_user_key_v1_invalid_token_returns_error() {
453        let client = Client::init_test_account(test_bitwarden_com_account()).await;
454
455        // Token built with a different V1 key — unwrapping with the client's V1 key will fail
456        let mismatched_token = {
457            let key_store = KeyStore::<KeySlotIds>::default();
458            let mut ctx = key_store.context_mut();
459            let wrong_v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
460            let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
461            V2UpgradeToken::create(wrong_v1_id, v2_id, &ctx).unwrap()
462        };
463
464        let result = client
465            .crypto()
466            .get_upgraded_user_key(Some(mismatched_token));
467        assert!(matches!(
468            result,
469            Err(CryptoClientError::InvalidUpgradeToken)
470        ));
471    }
472
473    #[tokio::test]
474    async fn test_get_upgraded_user_key_already_v2_no_token_returns_v2_key() {
475        let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
476
477        let result = client.crypto().get_upgraded_user_key(None).unwrap();
478        let result_key = SymmetricCryptoKey::try_from(result).unwrap();
479        assert!(
480            matches!(result_key, SymmetricCryptoKey::XChaCha20Poly1305Key(_)),
481            "V2 user should receive a V2 key"
482        );
483    }
484
485    #[tokio::test]
486    async fn test_get_upgraded_user_key_already_v2_with_token_ignored() {
487        let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
488
489        // Build a structurally valid token with unrelated keys; it must be ignored for V2 users.
490        let dummy_token = {
491            let key_store = KeyStore::<KeySlotIds>::default();
492            let mut ctx = key_store.context_mut();
493            let v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
494            let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
495            V2UpgradeToken::create(v1_id, v2_id, &ctx).unwrap()
496        };
497
498        let result_with_token = client
499            .crypto()
500            .get_upgraded_user_key(Some(dummy_token))
501            .unwrap();
502        let result_no_token = client.crypto().get_upgraded_user_key(None).unwrap();
503        assert_eq!(
504            result_with_token, result_no_token,
505            "Token must be ignored for a V2 user"
506        );
507    }
508}