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#[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 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 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 pub fn make_key_pair(&self, user_key: B64) -> Result<MakeKeyPairResponse, CryptoError> {
71 make_key_pair(user_key)
72 }
73
74 pub fn verify_asymmetric_keys(
78 &self,
79 request: VerifyAsymmetricKeysRequest,
80 ) -> Result<VerifyAsymmetricKeysResponse, CryptoError> {
81 verify_asymmetric_keys(request)
82 }
83
84 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 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 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 pub fn enroll_pin(&self, pin: String) -> Result<EnrollPinResponse, CryptoClientError> {
115 enroll_pin(&self.client, pin)
116 }
117
118 pub fn enroll_pin_with_encrypted_pin(
122 &self,
123 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 #[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 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 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 pub async fn get_user_encryption_key(&self) -> Result<B64, CryptoClientError> {
188 get_user_encryption_key(&self.client).await
189 }
190
191 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 pub fn derive_pin_key(&self, pin: String) -> Result<DerivePinKeyResponse, CryptoClientError> {
205 derive_pin_key(&self.client, pin)
206 }
207
208 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 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 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 pub fn derive_key_connector(
234 &self,
235 request: DeriveKeyConnectorRequest,
236 ) -> Result<B64, DeriveKeyConnectorError> {
237 derive_key_connector(request)
238 }
239
240 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 pub fn make_user_key_connector_registration(
254 &self,
255 ) -> Result<MakeKeyConnectorRegistrationResponse, MakeKeysError> {
256 make_user_key_connector_registration(&self.client)
257 }
258
259 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 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 (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 (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 (SymmetricKeyAlgorithm::Aes256CbcHmac, None) => {
311 Err(CryptoClientError::UpgradeTokenRequired)
312 }
313 }
314 }
315}
316
317impl Client {
318 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 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 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 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 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 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}