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 async fn make_update_kdf(
104 &self,
105 password: String,
106 kdf: Kdf,
107 ) -> Result<UpdateKdfResponse, CryptoClientError> {
108 make_update_kdf(&self.client, &password, &kdf).await
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 pub async fn get_user_encryption_key(&self) -> Result<B64, CryptoClientError> {
190 get_user_encryption_key(&self.client).await
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 get_upgraded_user_key(
287 &self,
288 upgrade_token: Option<V2UpgradeToken>,
289 ) -> Result<B64, CryptoClientError> {
290 let mut ctx = self.client.internal.get_key_store().context_mut();
291
292 let algorithm = ctx
293 .get_symmetric_key_algorithm(SymmetricKeyId::User)
294 .map_err(|_| CryptoClientError::NotAuthenticated(NotAuthenticatedError))?;
295
296 match (algorithm, upgrade_token) {
297 (SymmetricKeyAlgorithm::XChaCha20Poly1305, _) => {
299 #[allow(deprecated)]
300 let current_key = ctx
301 .dangerous_get_symmetric_key(SymmetricKeyId::User)
302 .map_err(|_| CryptoClientError::NotAuthenticated(NotAuthenticatedError))?;
303 Ok(current_key.clone().to_base64())
304 }
305 (SymmetricKeyAlgorithm::Aes256CbcHmac, Some(token)) => {
307 let v2_key_id = token
308 .unwrap_v2(SymmetricKeyId::User, &mut ctx)
309 .map_err(|_| CryptoClientError::InvalidUpgradeToken)?;
310 #[allow(deprecated)]
311 let v2_key = ctx
312 .dangerous_get_symmetric_key(v2_key_id)
313 .map_err(|_| CryptoClientError::InvalidUpgradeToken)?;
314 Ok(v2_key.clone().to_base64())
315 }
316 (SymmetricKeyAlgorithm::Aes256CbcHmac, None) => {
318 Err(CryptoClientError::UpgradeTokenRequired)
319 }
320 }
321 }
322}
323
324impl Client {
325 pub fn crypto(&self) -> CryptoClient {
327 CryptoClient {
328 client: self.clone(),
329 }
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use bitwarden_crypto::{BitwardenLegacyKeyBytes, KeyStore, SymmetricCryptoKey};
336
337 use super::*;
338 use crate::{
339 client::test_accounts::{test_bitwarden_com_account, test_bitwarden_com_account_v2},
340 key_management::{KeyIds, V2UpgradeToken},
341 };
342
343 #[tokio::test]
344 async fn test_enroll_pin_envelope() {
345 let client = Client::init_test_account(test_bitwarden_com_account()).await;
347 let user_key_initial =
348 SymmetricCryptoKey::try_from(client.crypto().get_user_encryption_key().await.unwrap())
349 .unwrap();
350
351 let pin = "1234";
353 let enroll_response = client.crypto().enroll_pin(pin.to_string()).unwrap();
354 let re_enroll_response = client
355 .crypto()
356 .enroll_pin_with_encrypted_pin(enroll_response.user_key_encrypted_pin.to_string())
357 .unwrap();
358
359 let secret = BitwardenLegacyKeyBytes::from(
360 client
361 .crypto()
362 .unseal_password_protected_key_envelope(
363 pin.to_string(),
364 re_enroll_response.pin_protected_user_key_envelope,
365 )
366 .unwrap(),
367 );
368 let user_key_final = SymmetricCryptoKey::try_from(&secret).expect("valid user key");
369 assert_eq!(user_key_initial, user_key_final);
370 }
371
372 #[test]
373 fn test_get_upgraded_user_key_not_authenticated() {
374 let client = Client::new(None);
375 let result = client.crypto().get_upgraded_user_key(None);
376 assert!(matches!(
377 result,
378 Err(CryptoClientError::NotAuthenticated(_))
379 ));
380 }
381
382 #[tokio::test]
383 async fn test_get_upgraded_user_key_v1_no_token_returns_error() {
384 let client = Client::init_test_account(test_bitwarden_com_account()).await;
385 let result = client.crypto().get_upgraded_user_key(None);
386 assert!(matches!(
387 result,
388 Err(CryptoClientError::UpgradeTokenRequired)
389 ));
390 }
391
392 #[tokio::test]
393 async fn test_get_upgraded_user_key_v1_with_token_returns_v2_key() {
394 let client = Client::init_test_account(test_bitwarden_com_account()).await;
395
396 let (token, expected_v2_b64) = {
398 let mut ctx = client.internal.get_key_store().context_mut();
399 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
400 #[allow(deprecated)]
401 let v2_key = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap().clone();
402 let token = V2UpgradeToken::create(SymmetricKeyId::User, v2_key_id, &ctx).unwrap();
403 (token, v2_key.to_base64())
404 };
405
406 let result = client.crypto().get_upgraded_user_key(Some(token)).unwrap();
407 assert_eq!(result, expected_v2_b64);
408 }
409
410 #[tokio::test]
411 async fn test_get_upgraded_user_key_v1_invalid_token_returns_error() {
412 let client = Client::init_test_account(test_bitwarden_com_account()).await;
413
414 let mismatched_token = {
416 let key_store = KeyStore::<KeyIds>::default();
417 let mut ctx = key_store.context_mut();
418 let wrong_v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
419 let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
420 V2UpgradeToken::create(wrong_v1_id, v2_id, &ctx).unwrap()
421 };
422
423 let result = client
424 .crypto()
425 .get_upgraded_user_key(Some(mismatched_token));
426 assert!(matches!(
427 result,
428 Err(CryptoClientError::InvalidUpgradeToken)
429 ));
430 }
431
432 #[tokio::test]
433 async fn test_get_upgraded_user_key_already_v2_no_token_returns_v2_key() {
434 let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
435
436 let result = client.crypto().get_upgraded_user_key(None).unwrap();
437 let result_key = SymmetricCryptoKey::try_from(result).unwrap();
438 assert!(
439 matches!(result_key, SymmetricCryptoKey::XChaCha20Poly1305Key(_)),
440 "V2 user should receive a V2 key"
441 );
442 }
443
444 #[tokio::test]
445 async fn test_get_upgraded_user_key_already_v2_with_token_ignored() {
446 let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
447
448 let dummy_token = {
450 let key_store = KeyStore::<KeyIds>::default();
451 let mut ctx = key_store.context_mut();
452 let v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
453 let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
454 V2UpgradeToken::create(v1_id, v2_id, &ctx).unwrap()
455 };
456
457 let result_with_token = client
458 .crypto()
459 .get_upgraded_user_key(Some(dummy_token))
460 .unwrap();
461 let result_no_token = client.crypto().get_upgraded_user_key(None).unwrap();
462 assert_eq!(
463 result_with_token, result_no_token,
464 "Token must be ignored for a V2 user"
465 );
466 }
467}