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