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(
110 &self,
111 password: String,
112 kdf: Kdf,
113 ) -> Result<UpdateKdfResponse, CryptoClientError> {
114 make_update_kdf(&self.client, &password, &kdf).await
115 }
116
117 pub fn enroll_pin(&self, pin: String) -> Result<EnrollPinResponse, CryptoClientError> {
121 enroll_pin(&self.client, pin)
122 }
123
124 pub fn enroll_pin_with_encrypted_pin(
128 &self,
129 encrypted_pin: String,
131 ) -> Result<EnrollPinResponse, CryptoClientError> {
132 let encrypted_pin: EncString = encrypted_pin.parse()?;
133 let pin = encrypted_pin.decrypt(
134 &mut self.client.internal.get_key_store().context_mut(),
135 SymmetricKeySlotId::User,
136 )?;
137 enroll_pin(&self.client, pin)
138 }
139
140 #[cfg(any(feature = "wasm", test))]
143 pub fn unseal_password_protected_key_envelope(
144 &self,
145 pin: String,
146 envelope: PasswordProtectedKeyEnvelope,
147 ) -> Result<Vec<u8>, CryptoClientError> {
148 let mut ctx = self.client.internal.get_key_store().context_mut();
149 let key_slot = envelope.unseal(
150 pin.as_str(),
151 PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
152 &mut ctx,
153 )?;
154 #[allow(deprecated)]
155 let key = ctx.dangerous_get_symmetric_key(key_slot)?;
156 Ok(key.to_encoded().to_vec())
157 }
158
159 pub fn encrypt_with_local_user_data_key(
163 &self,
164 plaintext: String,
165 ) -> Result<String, CryptoClientError> {
166 let mut ctx = self.client.internal.get_key_store().context_mut();
167 plaintext
168 .encrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
169 .map_err(CryptoClientError::Crypto)
170 .map(|enc| enc.to_string())
171 }
172
173 pub fn decrypt_with_local_user_data_key(
177 &self,
178 encrypted_plaintext: String,
179 ) -> Result<String, CryptoClientError> {
180 let mut ctx = self.client.internal.get_key_store().context_mut();
181 let encrypted: EncString = encrypted_plaintext
182 .parse()
183 .map_err(CryptoClientError::Crypto)?;
184 encrypted
185 .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
186 .map_err(CryptoClientError::Crypto)
187 }
188
189 pub async fn get_user_encryption_key(&self) -> Result<B64, CryptoClientError> {
196 get_user_encryption_key(&self.client).await
197 }
198
199 pub fn get_key_id_for_symmetric_key(
202 key: Vec<u8>,
203 ) -> Result<Option<Vec<u8>>, CryptoClientError> {
204 let symmetric_key = SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(key))?;
205 Ok(symmetric_key.key_id().map(|id| id.as_slice().to_vec()))
206 }
207}
208
209impl CryptoClient {
210 pub async fn make_update_password(
214 &self,
215 new_password: String,
216 ) -> Result<UpdatePasswordResponse, CryptoClientError> {
217 make_update_password(&self.client, new_password).await
218 }
219
220 pub async fn derive_pin_key(
224 &self,
225 pin: String,
226 ) -> Result<DerivePinKeyResponse, CryptoClientError> {
227 derive_pin_key(&self.client, pin).await
228 }
229
230 pub async fn derive_pin_user_key(
233 &self,
234 encrypted_pin: EncString,
235 ) -> Result<EncString, CryptoClientError> {
236 derive_pin_user_key(&self.client, encrypted_pin).await
237 }
238
239 pub fn make_prf_user_key_set(&self, prf: B64) -> Result<RotateableKeySet, CryptoClientError> {
242 make_prf_user_key_set(&self.client, prf)
243 }
244
245 pub fn enroll_admin_password_reset(
248 &self,
249 public_key: B64,
250 ) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
251 enroll_admin_password_reset(&self.client, public_key)
252 }
253
254 pub fn derive_key_connector(
256 &self,
257 request: DeriveKeyConnectorRequest,
258 ) -> Result<B64, DeriveKeyConnectorError> {
259 derive_key_connector(request)
260 }
261
262 pub fn make_user_tde_registration(
266 &self,
267 org_public_key: B64,
268 ) -> Result<MakeTdeRegistrationResponse, MakeKeysError> {
269 make_user_tde_registration(&self.client, org_public_key)
270 }
271
272 pub fn make_user_key_connector_registration(
276 &self,
277 ) -> Result<MakeKeyConnectorRegistrationResponse, MakeKeysError> {
278 make_user_key_connector_registration(&self.client)
279 }
280
281 pub fn make_user_jit_master_password_registration(
285 &self,
286 master_password: String,
287 salt: String,
288 org_public_key: B64,
289 ) -> Result<MakeJitMasterPasswordRegistrationResponse, MakeKeysError> {
290 make_user_jit_master_password_registration(
291 &self.client,
292 master_password,
293 salt,
294 org_public_key,
295 )
296 }
297
298 pub fn make_user_password_registration(
302 &self,
303 master_password: String,
304 salt: String,
305 ) -> Result<MakeUserMasterPasswordRegistrationResponse, MakeKeysError> {
306 make_user_password_registration(&self.client, master_password, salt)
307 }
308
309 pub fn get_upgraded_user_key(
313 &self,
314 upgrade_token: Option<V2UpgradeToken>,
315 ) -> Result<B64, CryptoClientError> {
316 let mut ctx = self.client.internal.get_key_store().context_mut();
317
318 let algorithm = ctx
319 .get_symmetric_key_algorithm(SymmetricKeySlotId::User)
320 .map_err(|_| CryptoClientError::NotAuthenticated(NotAuthenticatedError))?;
321
322 match (algorithm, upgrade_token) {
323 (SymmetricKeyAlgorithm::XChaCha20Poly1305, _) => {
325 #[allow(deprecated)]
326 let current_key = ctx
327 .dangerous_get_symmetric_key(SymmetricKeySlotId::User)
328 .map_err(|_| CryptoClientError::NotAuthenticated(NotAuthenticatedError))?;
329 Ok(current_key.clone().to_base64())
330 }
331 (SymmetricKeyAlgorithm::Aes256CbcHmac, Some(token)) => {
333 let v2_key_id = token
334 .unwrap_v2(SymmetricKeySlotId::User, &mut ctx)
335 .map_err(|_| CryptoClientError::InvalidUpgradeToken)?;
336 #[allow(deprecated)]
337 let v2_key = ctx
338 .dangerous_get_symmetric_key(v2_key_id)
339 .map_err(|_| CryptoClientError::InvalidUpgradeToken)?;
340 Ok(v2_key.clone().to_base64())
341 }
342 (SymmetricKeyAlgorithm::Aes256CbcHmac, None) => {
344 Err(CryptoClientError::UpgradeTokenRequired)
345 }
346 }
347 }
348}
349
350#[cfg(feature = "uniffi")]
351impl CryptoClient {
352 pub async fn reinit_user_crypto(
357 &self,
358 req: ReinitUserCryptoRequest,
359 ) -> Result<(), ReinitUserCryptoError> {
360 reinit_user_crypto(&self.client, req).await
361 }
362}
363
364impl Client {
365 pub fn crypto(&self) -> CryptoClient {
367 CryptoClient {
368 client: self.clone(),
369 }
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use bitwarden_crypto::{BitwardenLegacyKeyBytes, KeyStore, SymmetricCryptoKey};
376
377 use super::*;
378 use crate::{
379 client::test_accounts::{test_bitwarden_com_account, test_bitwarden_com_account_v2},
380 key_management::{KeySlotIds, V2UpgradeToken},
381 };
382
383 #[tokio::test]
384 async fn test_enroll_pin_envelope() {
385 let client = Client::init_test_account(test_bitwarden_com_account()).await;
387 let user_key_initial =
388 SymmetricCryptoKey::try_from(client.crypto().get_user_encryption_key().await.unwrap())
389 .unwrap();
390
391 let pin = "1234";
393 let enroll_response = client.crypto().enroll_pin(pin.to_string()).unwrap();
394 let re_enroll_response = client
395 .crypto()
396 .enroll_pin_with_encrypted_pin(enroll_response.user_key_encrypted_pin.to_string())
397 .unwrap();
398
399 let secret = BitwardenLegacyKeyBytes::from(
400 client
401 .crypto()
402 .unseal_password_protected_key_envelope(
403 pin.to_string(),
404 re_enroll_response.pin_protected_user_key_envelope,
405 )
406 .unwrap(),
407 );
408 let user_key_final = SymmetricCryptoKey::try_from(&secret).expect("valid user key");
409 assert_eq!(user_key_initial, user_key_final);
410 }
411
412 #[test]
413 fn test_get_upgraded_user_key_not_authenticated() {
414 let client = Client::new(None);
415 let result = client.crypto().get_upgraded_user_key(None);
416 assert!(matches!(
417 result,
418 Err(CryptoClientError::NotAuthenticated(_))
419 ));
420 }
421
422 #[tokio::test]
423 async fn test_get_upgraded_user_key_v1_no_token_returns_error() {
424 let client = Client::init_test_account(test_bitwarden_com_account()).await;
425 let result = client.crypto().get_upgraded_user_key(None);
426 assert!(matches!(
427 result,
428 Err(CryptoClientError::UpgradeTokenRequired)
429 ));
430 }
431
432 #[tokio::test]
433 async fn test_get_upgraded_user_key_v1_with_token_returns_v2_key() {
434 let client = Client::init_test_account(test_bitwarden_com_account()).await;
435
436 let (token, expected_v2_b64) = {
438 let mut ctx = client.internal.get_key_store().context_mut();
439 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
440 #[allow(deprecated)]
441 let v2_key = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap().clone();
442 let token = V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap();
443 (token, v2_key.to_base64())
444 };
445
446 let result = client.crypto().get_upgraded_user_key(Some(token)).unwrap();
447 assert_eq!(result, expected_v2_b64);
448 }
449
450 #[tokio::test]
451 async fn test_get_upgraded_user_key_v1_invalid_token_returns_error() {
452 let client = Client::init_test_account(test_bitwarden_com_account()).await;
453
454 let mismatched_token = {
456 let key_store = KeyStore::<KeySlotIds>::default();
457 let mut ctx = key_store.context_mut();
458 let wrong_v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
459 let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
460 V2UpgradeToken::create(wrong_v1_id, v2_id, &ctx).unwrap()
461 };
462
463 let result = client
464 .crypto()
465 .get_upgraded_user_key(Some(mismatched_token));
466 assert!(matches!(
467 result,
468 Err(CryptoClientError::InvalidUpgradeToken)
469 ));
470 }
471
472 #[tokio::test]
473 async fn test_get_upgraded_user_key_already_v2_no_token_returns_v2_key() {
474 let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
475
476 let result = client.crypto().get_upgraded_user_key(None).unwrap();
477 let result_key = SymmetricCryptoKey::try_from(result).unwrap();
478 assert!(
479 matches!(result_key, SymmetricCryptoKey::XChaCha20Poly1305Key(_)),
480 "V2 user should receive a V2 key"
481 );
482 }
483
484 #[tokio::test]
485 async fn test_get_upgraded_user_key_already_v2_with_token_ignored() {
486 let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
487
488 let dummy_token = {
490 let key_store = KeyStore::<KeySlotIds>::default();
491 let mut ctx = key_store.context_mut();
492 let v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
493 let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
494 V2UpgradeToken::create(v1_id, v2_id, &ctx).unwrap()
495 };
496
497 let result_with_token = client
498 .crypto()
499 .get_upgraded_user_key(Some(dummy_token))
500 .unwrap();
501 let result_no_token = client.crypto().get_upgraded_user_key(None).unwrap();
502 assert_eq!(
503 result_with_token, result_no_token,
504 "Token must be ignored for a V2 user"
505 );
506 }
507}