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#[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 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 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 pub fn make_key_pair(&self, user_key: B64) -> Result<MakeKeyPairResponse, CryptoError> {
73 make_key_pair(user_key)
74 }
75
76 pub fn verify_asymmetric_keys(
80 &self,
81 request: VerifyAsymmetricKeysRequest,
82 ) -> Result<VerifyAsymmetricKeysResponse, CryptoError> {
83 verify_asymmetric_keys(request)
84 }
85
86 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 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 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 pub fn enroll_pin(&self, pin: String) -> Result<EnrollPinResponse, CryptoClientError> {
117 enroll_pin(&self.client, pin)
118 }
119
120 pub fn enroll_pin_with_encrypted_pin(
124 &self,
125 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 #[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 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 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 pub async fn get_user_encryption_key(&self) -> Result<B64, CryptoClientError> {
192 get_user_encryption_key(&self.client).await
193 }
194
195 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 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 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 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 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 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 pub fn derive_key_connector(
252 &self,
253 request: DeriveKeyConnectorRequest,
254 ) -> Result<B64, DeriveKeyConnectorError> {
255 derive_key_connector(request)
256 }
257
258 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 pub fn make_user_key_connector_registration(
272 &self,
273 ) -> Result<MakeKeyConnectorRegistrationResponse, MakeKeysError> {
274 make_user_key_connector_registration(&self.client)
275 }
276
277 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 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 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 (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 (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 (SymmetricKeyAlgorithm::Aes256CbcHmac, None) => {
340 Err(CryptoClientError::UpgradeTokenRequired)
341 }
342 }
343 }
344}
345
346impl Client {
347 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 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 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 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 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 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}