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