1#[cfg(feature = "wasm")]
2use bitwarden_crypto::safe::{PasswordProtectedKeyEnvelope, PasswordProtectedKeyEnvelopeNamespace};
3use bitwarden_crypto::{CryptoError, Decryptable, Kdf, RotateableKeySet, SymmetricKeyAlgorithm};
4#[cfg(feature = "internal")]
5use bitwarden_crypto::{EncString, UnsignedSharedKey};
6use bitwarden_encoding::B64;
7#[cfg(feature = "wasm")]
8use wasm_bindgen::prelude::*;
9
10use super::crypto::{
11 DeriveKeyConnectorError, DeriveKeyConnectorRequest, EnrollAdminPasswordResetError,
12 MakeJitMasterPasswordRegistrationResponse, MakeKeyConnectorRegistrationResponse,
13 MakeKeyPairResponse, VerifyAsymmetricKeysRequest, VerifyAsymmetricKeysResponse,
14 derive_key_connector, make_key_pair, make_user_jit_master_password_registration,
15 make_user_key_connector_registration, verify_asymmetric_keys,
16};
17use crate::key_management::V2UpgradeToken;
18#[cfg(feature = "internal")]
19use crate::key_management::{
20 SymmetricKeyId,
21 crypto::{
22 DerivePinKeyResponse, InitOrgCryptoRequest, InitUserCryptoRequest, UpdatePasswordResponse,
23 derive_pin_key, derive_pin_user_key, enroll_admin_password_reset, get_user_encryption_key,
24 initialize_org_crypto, initialize_user_crypto, make_prf_user_key_set,
25 },
26};
27#[expect(deprecated)]
28use crate::{
29 Client,
30 client::encryption_settings::EncryptionSettingsError,
31 error::{NotAuthenticatedError, StatefulCryptoError},
32 key_management::crypto::{
33 CryptoClientError, EnrollPinResponse, MakeKeysError, MakeTdeRegistrationResponse,
34 UpdateKdfResponse, UserCryptoV2KeysResponse, enroll_pin, get_v2_rotated_account_keys,
35 make_update_kdf, make_update_password, make_user_tde_registration,
36 make_v2_keys_for_v1_user,
37 },
38};
39
40#[cfg_attr(feature = "wasm", wasm_bindgen)]
42pub struct CryptoClient {
43 pub(crate) client: crate::Client,
44}
45
46#[cfg_attr(feature = "wasm", wasm_bindgen)]
47impl CryptoClient {
48 pub async fn initialize_user_crypto(
51 &self,
52 req: InitUserCryptoRequest,
53 ) -> Result<(), EncryptionSettingsError> {
54 initialize_user_crypto(&self.client, req).await
55 }
56
57 pub async fn initialize_org_crypto(
60 &self,
61 req: InitOrgCryptoRequest,
62 ) -> Result<(), EncryptionSettingsError> {
63 initialize_org_crypto(&self.client, req).await
64 }
65
66 pub fn make_key_pair(&self, user_key: B64) -> Result<MakeKeyPairResponse, CryptoError> {
69 make_key_pair(user_key)
70 }
71
72 pub fn verify_asymmetric_keys(
76 &self,
77 request: VerifyAsymmetricKeysRequest,
78 ) -> Result<VerifyAsymmetricKeysResponse, CryptoError> {
79 verify_asymmetric_keys(request)
80 }
81
82 pub fn make_keys_for_user_crypto_v2(
84 &self,
85 ) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
86 #[expect(deprecated)]
87 make_v2_keys_for_v1_user(&self.client)
88 }
89
90 pub fn get_v2_rotated_account_keys(
92 &self,
93 ) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
94 #[expect(deprecated)]
95 get_v2_rotated_account_keys(&self.client)
96 }
97
98 pub fn make_update_kdf(
102 &self,
103 password: String,
104 kdf: Kdf,
105 ) -> Result<UpdateKdfResponse, CryptoClientError> {
106 make_update_kdf(&self.client, &password, &kdf)
107 }
108
109 pub fn enroll_pin(&self, pin: String) -> Result<EnrollPinResponse, CryptoClientError> {
113 enroll_pin(&self.client, pin)
114 }
115
116 pub fn enroll_pin_with_encrypted_pin(
120 &self,
121 encrypted_pin: String,
123 ) -> Result<EnrollPinResponse, CryptoClientError> {
124 let encrypted_pin: EncString = encrypted_pin.parse()?;
125 let pin = encrypted_pin.decrypt(
126 &mut self.client.internal.get_key_store().context_mut(),
127 SymmetricKeyId::User,
128 )?;
129 enroll_pin(&self.client, pin)
130 }
131
132 #[cfg(any(feature = "wasm", test))]
135 pub fn unseal_password_protected_key_envelope(
136 &self,
137 pin: String,
138 envelope: PasswordProtectedKeyEnvelope,
139 ) -> Result<Vec<u8>, CryptoClientError> {
140 let mut ctx = self.client.internal.get_key_store().context_mut();
141 let key_slot = envelope.unseal(
142 pin.as_str(),
143 PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
144 &mut ctx,
145 )?;
146 #[allow(deprecated)]
147 let key = ctx.dangerous_get_symmetric_key(key_slot)?;
148 Ok(key.to_encoded().to_vec())
149 }
150}
151
152impl CryptoClient {
153 pub async fn get_user_encryption_key(&self) -> Result<B64, CryptoClientError> {
156 get_user_encryption_key(&self.client).await
157 }
158
159 pub fn make_update_password(
163 &self,
164 new_password: String,
165 ) -> Result<UpdatePasswordResponse, CryptoClientError> {
166 make_update_password(&self.client, new_password)
167 }
168
169 pub fn derive_pin_key(&self, pin: String) -> Result<DerivePinKeyResponse, CryptoClientError> {
173 derive_pin_key(&self.client, pin)
174 }
175
176 pub fn derive_pin_user_key(
179 &self,
180 encrypted_pin: EncString,
181 ) -> Result<EncString, CryptoClientError> {
182 derive_pin_user_key(&self.client, encrypted_pin)
183 }
184
185 pub fn make_prf_user_key_set(&self, prf: B64) -> Result<RotateableKeySet, CryptoClientError> {
188 make_prf_user_key_set(&self.client, prf)
189 }
190
191 pub fn enroll_admin_password_reset(
194 &self,
195 public_key: B64,
196 ) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
197 enroll_admin_password_reset(&self.client, public_key)
198 }
199
200 pub fn derive_key_connector(
202 &self,
203 request: DeriveKeyConnectorRequest,
204 ) -> Result<B64, DeriveKeyConnectorError> {
205 derive_key_connector(request)
206 }
207
208 pub fn make_user_tde_registration(
212 &self,
213 org_public_key: B64,
214 ) -> Result<MakeTdeRegistrationResponse, MakeKeysError> {
215 make_user_tde_registration(&self.client, org_public_key)
216 }
217
218 pub fn make_user_key_connector_registration(
222 &self,
223 ) -> Result<MakeKeyConnectorRegistrationResponse, MakeKeysError> {
224 make_user_key_connector_registration(&self.client)
225 }
226
227 pub fn make_user_jit_master_password_registration(
231 &self,
232 master_password: String,
233 salt: String,
234 org_public_key: B64,
235 ) -> Result<MakeJitMasterPasswordRegistrationResponse, MakeKeysError> {
236 make_user_jit_master_password_registration(
237 &self.client,
238 master_password,
239 salt,
240 org_public_key,
241 )
242 }
243
244 pub fn get_upgraded_user_key(
248 &self,
249 upgrade_token: Option<V2UpgradeToken>,
250 ) -> Result<B64, CryptoClientError> {
251 let mut ctx = self.client.internal.get_key_store().context_mut();
252
253 let algorithm = ctx
254 .get_symmetric_key_algorithm(SymmetricKeyId::User)
255 .map_err(|_| CryptoClientError::NotAuthenticated(NotAuthenticatedError))?;
256
257 match (algorithm, upgrade_token) {
258 (SymmetricKeyAlgorithm::XChaCha20Poly1305, _) => {
260 #[allow(deprecated)]
261 let current_key = ctx
262 .dangerous_get_symmetric_key(SymmetricKeyId::User)
263 .map_err(|_| CryptoClientError::NotAuthenticated(NotAuthenticatedError))?;
264 Ok(current_key.clone().to_base64())
265 }
266 (SymmetricKeyAlgorithm::Aes256CbcHmac, Some(token)) => {
268 let v2_key_id = token
269 .unwrap_v2(SymmetricKeyId::User, &mut ctx)
270 .map_err(|_| CryptoClientError::InvalidUpgradeToken)?;
271 #[allow(deprecated)]
272 let v2_key = ctx
273 .dangerous_get_symmetric_key(v2_key_id)
274 .map_err(|_| CryptoClientError::InvalidUpgradeToken)?;
275 Ok(v2_key.clone().to_base64())
276 }
277 (SymmetricKeyAlgorithm::Aes256CbcHmac, None) => {
279 Err(CryptoClientError::UpgradeTokenRequired)
280 }
281 }
282 }
283}
284
285impl Client {
286 pub fn crypto(&self) -> CryptoClient {
288 CryptoClient {
289 client: self.clone(),
290 }
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use bitwarden_crypto::{BitwardenLegacyKeyBytes, KeyStore, SymmetricCryptoKey};
297
298 use super::*;
299 use crate::{
300 client::test_accounts::{test_bitwarden_com_account, test_bitwarden_com_account_v2},
301 key_management::{KeyIds, V2UpgradeToken},
302 };
303
304 #[tokio::test]
305 async fn test_enroll_pin_envelope() {
306 let client = Client::init_test_account(test_bitwarden_com_account()).await;
308 let user_key_initial =
309 SymmetricCryptoKey::try_from(client.crypto().get_user_encryption_key().await.unwrap())
310 .unwrap();
311
312 let pin = "1234";
314 let enroll_response = client.crypto().enroll_pin(pin.to_string()).unwrap();
315 let re_enroll_response = client
316 .crypto()
317 .enroll_pin_with_encrypted_pin(enroll_response.user_key_encrypted_pin.to_string())
318 .unwrap();
319
320 let secret = BitwardenLegacyKeyBytes::from(
321 client
322 .crypto()
323 .unseal_password_protected_key_envelope(
324 pin.to_string(),
325 re_enroll_response.pin_protected_user_key_envelope,
326 )
327 .unwrap(),
328 );
329 let user_key_final = SymmetricCryptoKey::try_from(&secret).expect("valid user key");
330 assert_eq!(user_key_initial, user_key_final);
331 }
332
333 #[test]
334 fn test_get_upgraded_user_key_not_authenticated() {
335 let client = Client::new(None);
336 let result = client.crypto().get_upgraded_user_key(None);
337 assert!(matches!(
338 result,
339 Err(CryptoClientError::NotAuthenticated(_))
340 ));
341 }
342
343 #[tokio::test]
344 async fn test_get_upgraded_user_key_v1_no_token_returns_error() {
345 let client = Client::init_test_account(test_bitwarden_com_account()).await;
346 let result = client.crypto().get_upgraded_user_key(None);
347 assert!(matches!(
348 result,
349 Err(CryptoClientError::UpgradeTokenRequired)
350 ));
351 }
352
353 #[tokio::test]
354 async fn test_get_upgraded_user_key_v1_with_token_returns_v2_key() {
355 let client = Client::init_test_account(test_bitwarden_com_account()).await;
356
357 let (token, expected_v2_b64) = {
359 let mut ctx = client.internal.get_key_store().context_mut();
360 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
361 #[allow(deprecated)]
362 let v2_key = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap().clone();
363 let token = V2UpgradeToken::create(SymmetricKeyId::User, v2_key_id, &ctx).unwrap();
364 (token, v2_key.to_base64())
365 };
366
367 let result = client.crypto().get_upgraded_user_key(Some(token)).unwrap();
368 assert_eq!(result, expected_v2_b64);
369 }
370
371 #[tokio::test]
372 async fn test_get_upgraded_user_key_v1_invalid_token_returns_error() {
373 let client = Client::init_test_account(test_bitwarden_com_account()).await;
374
375 let mismatched_token = {
377 let key_store = KeyStore::<KeyIds>::default();
378 let mut ctx = key_store.context_mut();
379 let wrong_v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
380 let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
381 V2UpgradeToken::create(wrong_v1_id, v2_id, &ctx).unwrap()
382 };
383
384 let result = client
385 .crypto()
386 .get_upgraded_user_key(Some(mismatched_token));
387 assert!(matches!(
388 result,
389 Err(CryptoClientError::InvalidUpgradeToken)
390 ));
391 }
392
393 #[tokio::test]
394 async fn test_get_upgraded_user_key_already_v2_no_token_returns_v2_key() {
395 let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
396
397 let result = client.crypto().get_upgraded_user_key(None).unwrap();
398 let result_key = SymmetricCryptoKey::try_from(result).unwrap();
399 assert!(
400 matches!(result_key, SymmetricCryptoKey::XChaCha20Poly1305Key(_)),
401 "V2 user should receive a V2 key"
402 );
403 }
404
405 #[tokio::test]
406 async fn test_get_upgraded_user_key_already_v2_with_token_ignored() {
407 let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
408
409 let dummy_token = {
411 let key_store = KeyStore::<KeyIds>::default();
412 let mut ctx = key_store.context_mut();
413 let v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
414 let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
415 V2UpgradeToken::create(v1_id, v2_id, &ctx).unwrap()
416 };
417
418 let result_with_token = client
419 .crypto()
420 .get_upgraded_user_key(Some(dummy_token))
421 .unwrap();
422 let result_no_token = client.crypto().get_upgraded_user_key(None).unwrap();
423 assert_eq!(
424 result_with_token, result_no_token,
425 "Token must be ignored for a V2 user"
426 );
427 }
428}