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#[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 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 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 pub fn make_key_pair(&self, user_key: B64) -> Result<MakeKeyPairResponse, CryptoError> {
77 make_key_pair(user_key)
78 }
79
80 pub fn verify_asymmetric_keys(
84 &self,
85 request: VerifyAsymmetricKeysRequest,
86 ) -> Result<VerifyAsymmetricKeysResponse, CryptoError> {
87 verify_asymmetric_keys(request)
88 }
89
90 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 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 pub async fn make_update_kdf(
112 &self,
113 password: String,
114 kdf: Kdf,
115 ) -> Result<UpdateKdfResponse, CryptoClientError> {
116 make_update_kdf(&self.client, &password, &kdf).await
117 }
118
119 pub fn enroll_pin(&self, pin: String) -> Result<EnrollPinResponse, CryptoClientError> {
123 enroll_pin(&self.client, pin)
124 }
125
126 pub fn enroll_pin_with_encrypted_pin(
130 &self,
131 encrypted_pin: String,
133 ) -> Result<EnrollPinResponse, CryptoClientError> {
134 let encrypted_pin: EncString = encrypted_pin.parse()?;
135 let pin = encrypted_pin.decrypt(
136 &mut self.client.internal.get_key_store().context_mut(),
137 SymmetricKeySlotId::User,
138 )?;
139 enroll_pin(&self.client, pin)
140 }
141
142 #[cfg(any(feature = "wasm", test))]
145 pub fn unseal_password_protected_key_envelope(
146 &self,
147 pin: String,
148 envelope: PasswordProtectedKeyEnvelope,
149 ) -> Result<Vec<u8>, CryptoClientError> {
150 let mut ctx = self.client.internal.get_key_store().context_mut();
151 let key_slot = envelope.unseal(
152 pin.as_str(),
153 PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
154 &mut ctx,
155 )?;
156 #[allow(deprecated)]
157 let key = ctx.dangerous_get_symmetric_key(key_slot)?;
158 Ok(key.to_encoded().to_vec())
159 }
160
161 pub fn encrypt_with_local_user_data_key(
165 &self,
166 plaintext: String,
167 ) -> Result<String, CryptoClientError> {
168 let mut ctx = self.client.internal.get_key_store().context_mut();
169 plaintext
170 .encrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
171 .map_err(CryptoClientError::Crypto)
172 .map(|enc| enc.to_string())
173 }
174
175 pub fn decrypt_with_local_user_data_key(
179 &self,
180 encrypted_plaintext: String,
181 ) -> Result<String, CryptoClientError> {
182 let mut ctx = self.client.internal.get_key_store().context_mut();
183 let encrypted: EncString = encrypted_plaintext
184 .parse()
185 .map_err(CryptoClientError::Crypto)?;
186 encrypted
187 .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
188 .map_err(CryptoClientError::Crypto)
189 }
190
191 pub async fn get_user_encryption_key(&self) -> Result<B64, CryptoClientError> {
198 get_user_encryption_key(&self.client).await
199 }
200
201 pub fn get_key_id_for_symmetric_key(
204 key: Vec<u8>,
205 ) -> Result<Option<Vec<u8>>, CryptoClientError> {
206 let symmetric_key = SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(key))?;
207 Ok(symmetric_key.key_id().map(|id| id.as_slice().to_vec()))
208 }
209}
210
211impl CryptoClient {
212 pub async fn make_update_password(
216 &self,
217 new_password: String,
218 ) -> Result<UpdatePasswordResponse, CryptoClientError> {
219 make_update_password(&self.client, new_password).await
220 }
221
222 pub async fn derive_pin_key(
226 &self,
227 pin: String,
228 ) -> Result<DerivePinKeyResponse, CryptoClientError> {
229 derive_pin_key(&self.client, pin).await
230 }
231
232 pub async fn derive_pin_user_key(
235 &self,
236 encrypted_pin: EncString,
237 ) -> Result<EncString, CryptoClientError> {
238 derive_pin_user_key(&self.client, encrypted_pin).await
239 }
240
241 pub fn make_prf_user_key_set(&self, prf: B64) -> Result<RotateableKeySet, CryptoClientError> {
244 make_prf_user_key_set(&self.client, prf)
245 }
246
247 pub fn enroll_admin_password_reset(
250 &self,
251 public_key: B64,
252 ) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
253 enroll_admin_password_reset(&self.client, public_key)
254 }
255
256 pub fn derive_key_connector(
258 &self,
259 request: DeriveKeyConnectorRequest,
260 ) -> Result<B64, DeriveKeyConnectorError> {
261 derive_key_connector(request)
262 }
263
264 pub fn make_user_tde_registration(
268 &self,
269 org_public_key: B64,
270 ) -> Result<MakeTdeRegistrationResponse, MakeKeysError> {
271 make_user_tde_registration(&self.client, org_public_key)
272 }
273
274 pub fn make_user_key_connector_registration(
278 &self,
279 ) -> Result<MakeKeyConnectorRegistrationResponse, MakeKeysError> {
280 make_user_key_connector_registration(&self.client)
281 }
282
283 pub fn make_user_jit_master_password_registration(
287 &self,
288 master_password: String,
289 salt: String,
290 org_public_key: B64,
291 ) -> Result<MakeJitMasterPasswordRegistrationResponse, MakeKeysError> {
292 make_user_jit_master_password_registration(
293 &self.client,
294 master_password,
295 salt,
296 org_public_key,
297 )
298 }
299
300 pub fn make_user_password_registration(
304 &self,
305 master_password: String,
306 salt: String,
307 ) -> Result<MakeUserMasterPasswordRegistrationResponse, MakeKeysError> {
308 make_user_password_registration(&self.client, master_password, salt)
309 }
310
311 pub fn get_upgraded_user_key(
315 &self,
316 upgrade_token: Option<V2UpgradeToken>,
317 ) -> Result<B64, CryptoClientError> {
318 let mut ctx = self.client.internal.get_key_store().context_mut();
319
320 let algorithm = ctx
321 .get_symmetric_key_algorithm(SymmetricKeySlotId::User)
322 .map_err(|_| CryptoClientError::NotAuthenticated(NotAuthenticatedError))?;
323
324 match (algorithm, upgrade_token) {
325 (SymmetricKeyAlgorithm::XAes256Gcm, _) => {
327 #[allow(deprecated)]
328 let current_key = ctx
329 .dangerous_get_symmetric_key(SymmetricKeySlotId::User)
330 .map_err(|_| CryptoClientError::NotAuthenticated(NotAuthenticatedError))?;
331 Ok(current_key.clone().to_base64())
332 }
333 (SymmetricKeyAlgorithm::Aes256CbcHmac, Some(token)) => {
335 let v2_key_id = token
336 .unwrap_v2(SymmetricKeySlotId::User, &mut ctx)
337 .map_err(|_| CryptoClientError::InvalidUpgradeToken)?;
338 #[allow(deprecated)]
339 let v2_key = ctx
340 .dangerous_get_symmetric_key(v2_key_id)
341 .map_err(|_| CryptoClientError::InvalidUpgradeToken)?;
342 Ok(v2_key.clone().to_base64())
343 }
344 (SymmetricKeyAlgorithm::Aes256CbcHmac, None) => {
346 Err(CryptoClientError::UpgradeTokenRequired)
347 }
348 (SymmetricKeyAlgorithm::XChaCha20Poly1305 | SymmetricKeyAlgorithm::Aes256Gcm, _) => {
349 Err(CryptoClientError::InvalidKeyType)
350 }
351 }
352 }
353}
354
355#[cfg(feature = "uniffi")]
356impl CryptoClient {
357 pub async fn reinit_user_crypto(
362 &self,
363 req: ReinitUserCryptoRequest,
364 ) -> Result<(), ReinitUserCryptoError> {
365 reinit_user_crypto(&self.client, req).await
366 }
367}
368
369impl Client {
370 pub fn crypto(&self) -> CryptoClient {
372 CryptoClient {
373 client: self.clone(),
374 }
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use bitwarden_crypto::{BitwardenLegacyKeyBytes, KeyStore, SymmetricCryptoKey};
381
382 use super::*;
383 use crate::{
384 client::test_accounts::{test_bitwarden_com_account, test_bitwarden_com_account_v2},
385 key_management::{KeySlotIds, V2UpgradeToken},
386 };
387
388 #[tokio::test]
389 async fn test_enroll_pin_envelope() {
390 let client = Client::init_test_account(test_bitwarden_com_account()).await;
392 let user_key_initial =
393 SymmetricCryptoKey::try_from(client.crypto().get_user_encryption_key().await.unwrap())
394 .unwrap();
395
396 let pin = "1234";
398 let enroll_response = client.crypto().enroll_pin(pin.to_string()).unwrap();
399 let re_enroll_response = client
400 .crypto()
401 .enroll_pin_with_encrypted_pin(enroll_response.user_key_encrypted_pin.to_string())
402 .unwrap();
403
404 let secret = BitwardenLegacyKeyBytes::from(
405 client
406 .crypto()
407 .unseal_password_protected_key_envelope(
408 pin.to_string(),
409 re_enroll_response.pin_protected_user_key_envelope,
410 )
411 .unwrap(),
412 );
413 let user_key_final = SymmetricCryptoKey::try_from(&secret).expect("valid user key");
414 assert_eq!(user_key_initial, user_key_final);
415 }
416
417 #[test]
418 fn test_get_upgraded_user_key_not_authenticated() {
419 let client = Client::new(None);
420 let result = client.crypto().get_upgraded_user_key(None);
421 assert!(matches!(
422 result,
423 Err(CryptoClientError::NotAuthenticated(_))
424 ));
425 }
426
427 #[tokio::test]
428 async fn test_get_upgraded_user_key_v1_no_token_returns_error() {
429 let client = Client::init_test_account(test_bitwarden_com_account()).await;
430 let result = client.crypto().get_upgraded_user_key(None);
431 assert!(matches!(
432 result,
433 Err(CryptoClientError::UpgradeTokenRequired)
434 ));
435 }
436
437 #[tokio::test]
438 async fn test_get_upgraded_user_key_v1_with_token_returns_v2_key() {
439 let client = Client::init_test_account(test_bitwarden_com_account()).await;
440
441 let (token, expected_v2_b64) = {
443 let mut ctx = client.internal.get_key_store().context_mut();
444 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
445 #[allow(deprecated)]
446 let v2_key = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap().clone();
447 let token = V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap();
448 (token, v2_key.to_base64())
449 };
450
451 let result = client.crypto().get_upgraded_user_key(Some(token)).unwrap();
452 assert_eq!(result, expected_v2_b64);
453 }
454
455 #[tokio::test]
456 async fn test_get_upgraded_user_key_v1_invalid_token_returns_error() {
457 let client = Client::init_test_account(test_bitwarden_com_account()).await;
458
459 let mismatched_token = {
461 let key_store = KeyStore::<KeySlotIds>::default();
462 let mut ctx = key_store.context_mut();
463 let wrong_v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
464 let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
465 V2UpgradeToken::create(wrong_v1_id, v2_id, &ctx).unwrap()
466 };
467
468 let result = client
469 .crypto()
470 .get_upgraded_user_key(Some(mismatched_token));
471 assert!(matches!(
472 result,
473 Err(CryptoClientError::InvalidUpgradeToken)
474 ));
475 }
476
477 #[tokio::test]
478 async fn test_get_upgraded_user_key_already_v2_no_token_returns_v2_key() {
479 let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
480
481 let result = client.crypto().get_upgraded_user_key(None).unwrap();
482 let result_key = SymmetricCryptoKey::try_from(result).unwrap();
483 assert!(
484 matches!(result_key, SymmetricCryptoKey::XAes256GcmKey(_)),
485 "V2 user should receive a V2 key"
486 );
487 }
488
489 #[tokio::test]
490 async fn test_get_upgraded_user_key_already_v2_with_token_ignored() {
491 let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
492
493 let dummy_token = {
495 let key_store = KeyStore::<KeySlotIds>::default();
496 let mut ctx = key_store.context_mut();
497 let v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
498 let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
499 V2UpgradeToken::create(v1_id, v2_id, &ctx).unwrap()
500 };
501
502 let result_with_token = client
503 .crypto()
504 .get_upgraded_user_key(Some(dummy_token))
505 .unwrap();
506 let result_no_token = client.crypto().get_upgraded_user_key(None).unwrap();
507 assert_eq!(
508 result_with_token, result_no_token,
509 "Token must be ignored for a V2 user"
510 );
511 }
512}