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#[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 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 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 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 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 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 pub fn enroll_pin(&self, pin: String) -> Result<EnrollPinResponse, CryptoClientError> {
106 enroll_pin(&self.client, pin)
107 }
108
109 pub fn enroll_pin_with_encrypted_pin(
113 &self,
114 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 #[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 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 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 pub async fn get_user_encryption_key(&self) -> Result<B64, CryptoClientError> {
181 get_user_encryption_key(&self.client).await
182 }
183
184 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 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 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 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 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 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 pub fn derive_key_connector(
241 &self,
242 request: DeriveKeyConnectorRequest,
243 ) -> Result<B64, DeriveKeyConnectorError> {
244 derive_key_connector(request)
245 }
246
247 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 pub fn make_user_key_connector_registration(
261 &self,
262 ) -> Result<MakeKeyConnectorRegistrationResponse, MakeKeysError> {
263 make_user_key_connector_registration(&self.client)
264 }
265
266 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 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 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 (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 (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 (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 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 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 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 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 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 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 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}