Skip to main content

bitwarden_core/key_management/
crypto_client.rs

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