1use std::collections::HashMap;
8
9use bitwarden_api_api::models::AccountKeysRequestModel;
10use bitwarden_crypto::safe::PasswordProtectedKeyEnvelopeNamespace;
11#[expect(deprecated)]
12use bitwarden_crypto::{
13 CoseSerializable, CryptoError, DeviceKey, EncString, Kdf, KeyConnectorKey, KeyDecryptable,
14 KeyEncryptable, MasterKey, Pkcs8PrivateKeyBytes, PrimitiveEncryptable, PrivateKey, PublicKey,
15 RotateableKeySet, SignatureAlgorithm, SignedPublicKey, SigningKey, SpkiPublicKeyBytes,
16 SymmetricCryptoKey, TrustDeviceResponse, UnsignedSharedKey, UserKey,
17 dangerous_get_v2_rotated_account_keys, derive_symmetric_key_from_prf,
18 safe::{PasswordProtectedKeyEnvelope, PasswordProtectedKeyEnvelopeError},
19};
20use bitwarden_encoding::B64;
21use bitwarden_error::bitwarden_error;
22use schemars::JsonSchema;
23use serde::{Deserialize, Serialize};
24use tracing::info;
25#[cfg(feature = "wasm")]
26use {tsify::Tsify, wasm_bindgen::prelude::*};
27
28#[cfg(feature = "wasm")]
29use crate::key_management::wasm_unlock_state::{
30 copy_user_key_to_client_managed_state, get_user_key_from_client_managed_state,
31};
32use crate::{
33 Client, NotAuthenticatedError, OrganizationId, UserId, WrongPasswordError,
34 client::{
35 LoginMethod, UserLoginMethod,
36 encryption_settings::EncryptionSettingsError,
37 persisted_state::{ACCOUNT_CRYPTO_STATE, OrganizationSharedKey},
38 },
39 error::StatefulCryptoError,
40 key_management::{
41 MasterPasswordError, PrivateKeySlotId, SecurityState, SignedSecurityState,
42 SigningKeySlotId, SymmetricKeySlotId, V2UpgradeToken,
43 account_cryptographic_state::{
44 AccountCryptographyInitializationError, WrappedAccountCryptographicState,
45 },
46 local_user_data_key_state::{
47 get_local_user_data_key_from_state, initialize_local_user_data_key_into_state,
48 migrate_local_user_data_key_for_user_key_upgrade,
49 },
50 master_password::{MasterPasswordAuthenticationData, MasterPasswordUnlockData},
51 pin_lock_system::{PinLockSystem, UnlockError},
52 },
53};
54
55#[allow(missing_docs)]
57#[bitwarden_error(flat)]
58#[derive(Debug, thiserror::Error)]
59pub enum CryptoClientError {
60 #[error(transparent)]
61 NotAuthenticated(#[from] NotAuthenticatedError),
62 #[error(transparent)]
63 Crypto(#[from] bitwarden_crypto::CryptoError),
64 #[error("Invalid KDF settings")]
65 InvalidKdfSettings,
66 #[error(transparent)]
67 PasswordProtectedKeyEnvelope(#[from] PasswordProtectedKeyEnvelopeError),
68 #[error("Invalid PRF input")]
69 InvalidPrfInput,
70 #[error("Invalid upgrade token")]
71 InvalidUpgradeToken,
72 #[error("Upgrade token is required for V1 keys")]
73 UpgradeTokenRequired,
74 #[error("Invalid key type")]
75 InvalidKeyType,
76}
77
78#[derive(Serialize, Deserialize, Debug)]
80#[serde(rename_all = "camelCase", deny_unknown_fields)]
81#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
82#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
83pub struct InitUserCryptoRequest {
84 pub user_id: Option<UserId>,
86 pub kdf_params: Kdf,
88 pub email: String,
90 pub account_cryptographic_state: WrappedAccountCryptographicState,
93 pub method: InitUserCryptoMethod,
95 #[serde(skip_serializing_if = "Option::is_none")]
97 pub upgrade_token: Option<V2UpgradeToken>,
98}
99
100#[derive(Serialize, Deserialize, Debug)]
102#[serde(rename_all = "camelCase", deny_unknown_fields)]
103#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
104#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
105#[allow(clippy::large_enum_variant)]
106pub enum InitUserCryptoMethod {
107 MasterPasswordUnlock {
109 password: String,
111 master_password_unlock: MasterPasswordUnlockData,
113 },
114 #[cfg(feature = "wasm")]
118 ClientManagedState {},
119 DecryptedKey {
121 decrypted_user_key: String,
123 },
124 Pin {
126 pin: String,
128 pin_protected_user_key: EncString,
131 },
132 PinState {
134 pin: String,
136 },
137 PinEnvelope {
139 pin: String,
141 pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
143 },
144 AuthRequest {
146 request_private_key: B64,
148 method: AuthRequestMethod,
150 },
151 DeviceKey {
153 device_key: String,
155 protected_device_private_key: EncString,
157 device_protected_user_key: UnsignedSharedKey,
159 },
160 KeyConnector {
162 master_key: B64,
164 user_key: EncString,
166 },
167 KeyConnectorUrl {
169 url: String,
171 key_connector_key_wrapped_user_key: EncString,
173 },
174}
175
176#[derive(Serialize, Deserialize, Debug)]
178#[serde(rename_all = "camelCase", deny_unknown_fields)]
179#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
180#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
181pub enum AuthRequestMethod {
182 UserKey {
184 protected_user_key: UnsignedSharedKey,
186 },
187 MasterKey {
189 protected_master_key: UnsignedSharedKey,
191 auth_request_key: EncString,
193 },
194}
195
196#[tracing::instrument(skip_all, err)]
198pub(super) async fn initialize_user_crypto(
199 client: &Client,
200 req: InitUserCryptoRequest,
201) -> Result<(), EncryptionSettingsError> {
202 use bitwarden_crypto::{DeviceKey, PinKey};
203
204 use crate::auth::{auth_request_decrypt_master_key, auth_request_decrypt_user_key};
205
206 if let Some(user_id) = req.user_id {
207 client.internal.init_user_id(user_id).await?;
208 }
209
210 tracing::Span::current().record(
211 "user_id",
212 client.internal.get_user_id().map(|id| id.to_string()),
213 );
214
215 let account_crypto_state = req.account_cryptographic_state.to_owned();
216
217 #[cfg(feature = "wasm")]
218 let should_copy_user_key = matches!(
219 req.method,
220 InitUserCryptoMethod::MasterPasswordUnlock { .. }
221 | InitUserCryptoMethod::DecryptedKey { .. }
222 | InitUserCryptoMethod::PinEnvelope { .. }
223 | InitUserCryptoMethod::PinState { .. }
224 | InitUserCryptoMethod::KeyConnectorUrl { .. }
225 | InitUserCryptoMethod::AuthRequest { .. }
226 );
227
228 match req.method {
229 InitUserCryptoMethod::MasterPasswordUnlock {
230 password,
231 master_password_unlock,
232 } => {
233 client
234 .internal
235 .initialize_user_crypto_master_password_unlock(
236 password,
237 master_password_unlock,
238 account_crypto_state,
239 &req.upgrade_token,
240 )?;
241 }
242 #[cfg(feature = "wasm")]
243 InitUserCryptoMethod::ClientManagedState {} => {
244 let user_key = get_user_key_from_client_managed_state(client)
245 .await
246 .map_err(|_| EncryptionSettingsError::UserKeyStateRetrievalFailed)?;
247 client.internal.initialize_user_crypto_decrypted_key(
248 user_key,
249 account_crypto_state,
250 &req.upgrade_token,
251 )?;
252 }
253 InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => {
254 let user_key = SymmetricCryptoKey::try_from(decrypted_user_key)?;
255 client.internal.initialize_user_crypto_decrypted_key(
256 user_key,
257 account_crypto_state,
258 &req.upgrade_token,
259 )?;
260 }
261 InitUserCryptoMethod::Pin {
262 pin,
263 pin_protected_user_key,
264 } => {
265 let pin_key = PinKey::derive(pin.as_bytes(), req.email.as_bytes(), &req.kdf_params)?;
266 client.internal.initialize_user_crypto_pin(
267 pin_key,
268 pin_protected_user_key,
269 account_crypto_state,
270 &req.upgrade_token,
271 )?;
272 }
273 InitUserCryptoMethod::PinEnvelope {
274 pin,
275 pin_protected_user_key_envelope,
276 } => {
277 client.internal.initialize_user_crypto_pin_envelope(
278 pin,
279 pin_protected_user_key_envelope,
280 account_crypto_state,
281 &req.upgrade_token,
282 )?;
283 }
284 InitUserCryptoMethod::PinState { pin } => {
285 PinLockSystem::with_client(client)
286 .unlock(pin.as_str())
287 .await
288 .map_err(|err| match err {
289 UnlockError::PinWrong => EncryptionSettingsError::WrongPin,
290 _ => EncryptionSettingsError::CryptoInitialization,
291 })?;
292 #[allow(deprecated)]
296 let user_key = client
297 .internal
298 .get_key_store()
299 .context()
300 .dangerous_get_symmetric_key(SymmetricKeySlotId::User)?
301 .to_owned();
302 client
304 .internal
305 .get_key_store()
306 .context_mut()
307 .drop_symmetric_key(SymmetricKeySlotId::User)?;
308
309 client.internal.initialize_user_crypto_decrypted_key(
310 user_key,
311 account_crypto_state,
312 &req.upgrade_token,
313 )?;
314 }
315 InitUserCryptoMethod::AuthRequest {
316 request_private_key,
317 method,
318 } => {
319 let user_key = match method {
320 AuthRequestMethod::UserKey { protected_user_key } => {
321 auth_request_decrypt_user_key(request_private_key, protected_user_key)?
322 }
323 AuthRequestMethod::MasterKey {
324 protected_master_key,
325 auth_request_key,
326 } => auth_request_decrypt_master_key(
327 request_private_key,
328 protected_master_key,
329 auth_request_key,
330 )?,
331 };
332 client.internal.initialize_user_crypto_decrypted_key(
333 user_key,
334 account_crypto_state,
335 &req.upgrade_token,
336 )?;
337 }
338 InitUserCryptoMethod::DeviceKey {
339 device_key,
340 protected_device_private_key,
341 device_protected_user_key,
342 } => {
343 let device_key = DeviceKey::try_from(device_key)?;
344 let user_key = device_key
345 .decrypt_user_key(protected_device_private_key, device_protected_user_key)?;
346
347 client.internal.initialize_user_crypto_decrypted_key(
348 user_key,
349 account_crypto_state,
350 &req.upgrade_token,
351 )?;
352 }
353 InitUserCryptoMethod::KeyConnector {
354 master_key,
355 user_key,
356 } => {
357 let bytes = master_key.into_bytes();
358 let master_key = MasterKey::try_from(bytes)?;
359
360 client.internal.initialize_user_crypto_key_connector_key(
361 master_key,
362 user_key,
363 account_crypto_state,
364 &req.upgrade_token,
365 )?;
366 }
367 InitUserCryptoMethod::KeyConnectorUrl {
368 url,
369 key_connector_key_wrapped_user_key,
370 } => {
371 let api_client = client.internal.get_key_connector_client(url);
372 let key_connector_key_response = api_client
373 .user_keys_api()
374 .get_user_key()
375 .await
376 .map_err(|_| EncryptionSettingsError::KeyConnectorRetrievalFailed)?;
377 let key_connector_key = KeyConnectorKey::try_from(key_connector_key_response)?;
378 let user_key =
379 key_connector_key.decrypt_user_key(key_connector_key_wrapped_user_key)?;
380 client.internal.initialize_user_crypto_decrypted_key(
381 user_key,
382 account_crypto_state,
383 &req.upgrade_token,
384 )?;
385 }
386 }
387
388 #[cfg(feature = "wasm")]
389 if should_copy_user_key {
390 copy_user_key_to_client_managed_state(client)
391 .await
392 .map_err(|_| EncryptionSettingsError::UserKeyStateUpdateFailed)?;
393 }
394
395 initialize_user_local_data_key(client).await?;
396 PinLockSystem::on_unlock(&PinLockSystem::with_client(client)).await;
397
398 client
399 .internal
400 .set_login_method(LoginMethod::User(UserLoginMethod::Username {
401 client_id: "".to_string(),
402 email: req.email,
403 kdf: req.kdf_params,
404 }))
405 .await;
406
407 if let Ok(setting) = client.internal.state_registry.setting(ACCOUNT_CRYPTO_STATE)
408 && let Err(e) = setting.update(req.account_cryptographic_state).await
409 {
410 tracing::warn!("Failed to persist account crypto state: {e}");
411 }
412
413 info!("User crypto initialized successfully");
414
415 Ok(())
416}
417
418#[derive(Serialize, Deserialize, Debug)]
420#[serde(rename_all = "camelCase", deny_unknown_fields)]
421#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
422#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
423pub struct InitOrgCryptoRequest {
424 pub organization_keys: HashMap<OrganizationId, UnsignedSharedKey>,
426}
427
428pub(super) async fn initialize_org_crypto(
430 client: &Client,
431 req: InitOrgCryptoRequest,
432) -> Result<(), EncryptionSettingsError> {
433 let organization_keys: Vec<_> = req.organization_keys.into_iter().collect();
434 client
435 .internal
436 .initialize_org_crypto(organization_keys.clone())?;
437
438 if let Ok(repo) = client
440 .internal
441 .state_registry
442 .get::<OrganizationSharedKey>()
443 {
444 for (org_id, key) in organization_keys {
445 if let Err(e) = repo
446 .set(org_id, OrganizationSharedKey { org_id, key })
447 .await
448 {
449 tracing::warn!("Failed to persist org key for {org_id}: {e}");
450 }
451 }
452 }
453
454 Ok(())
455}
456
457pub(super) async fn get_user_encryption_key(client: &Client) -> Result<B64, CryptoClientError> {
458 let key_store = client.internal.get_key_store();
459 let ctx = key_store.context();
460 #[allow(deprecated)]
463 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
464
465 Ok(user_key.to_base64())
466}
467
468#[derive(Serialize, Deserialize, Debug)]
470#[serde(rename_all = "camelCase", deny_unknown_fields)]
471#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
472#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
473pub struct UpdateKdfResponse {
474 master_password_authentication_data: MasterPasswordAuthenticationData,
476 master_password_unlock_data: MasterPasswordUnlockData,
478 old_master_password_authentication_data: MasterPasswordAuthenticationData,
480}
481
482pub(super) async fn make_update_kdf(
483 client: &Client,
484 password: &str,
485 new_kdf: &Kdf,
486) -> Result<UpdateKdfResponse, CryptoClientError> {
487 let login_method = client
488 .internal
489 .get_login_method()
490 .await
491 .ok_or(NotAuthenticatedError)?;
492 let email = match login_method {
493 UserLoginMethod::Username { email, .. } | UserLoginMethod::ApiKey { email, .. } => email,
494 };
495
496 let old_authentication_data = MasterPasswordAuthenticationData::derive(
497 password,
498 &client
499 .internal
500 .get_kdf()
501 .await
502 .map_err(|_| NotAuthenticatedError)?,
503 &email,
504 )
505 .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
506
507 let key_store = client.internal.get_key_store();
508 let ctx = key_store.context();
509
510 let authentication_data = MasterPasswordAuthenticationData::derive(password, new_kdf, &email)
511 .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
512 let unlock_data =
513 MasterPasswordUnlockData::derive(password, new_kdf, &email, SymmetricKeySlotId::User, &ctx)
514 .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
515
516 Ok(UpdateKdfResponse {
517 master_password_authentication_data: authentication_data,
518 master_password_unlock_data: unlock_data,
519 old_master_password_authentication_data: old_authentication_data,
520 })
521}
522
523#[derive(Serialize, Deserialize, Debug)]
525#[serde(rename_all = "camelCase", deny_unknown_fields)]
526#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
527#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
528pub struct UpdatePasswordResponse {
529 password_hash: B64,
531 new_key: EncString,
533}
534
535pub(super) async fn make_update_password(
536 client: &Client,
537 new_password: String,
538) -> Result<UpdatePasswordResponse, CryptoClientError> {
539 let login_method = client
540 .internal
541 .get_login_method()
542 .await
543 .ok_or(NotAuthenticatedError)?;
544
545 let key_store = client.internal.get_key_store();
546 let ctx = key_store.context();
547 #[allow(deprecated)]
549 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
550
551 let new_master_key = match login_method {
553 UserLoginMethod::Username { email, kdf, .. }
554 | UserLoginMethod::ApiKey { email, kdf, .. } => {
555 MasterKey::derive(&new_password, &email, &kdf)?
556 }
557 };
558
559 let new_key = new_master_key.encrypt_user_key(user_key)?;
560
561 let password_hash = new_master_key.derive_master_key_hash(
562 new_password.as_bytes(),
563 bitwarden_crypto::HashPurpose::ServerAuthorization,
564 );
565
566 Ok(UpdatePasswordResponse {
567 password_hash,
568 new_key,
569 })
570}
571
572#[derive(Serialize, Deserialize, Debug)]
574#[serde(rename_all = "camelCase", deny_unknown_fields)]
575#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
576#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
577pub struct EnrollPinResponse {
578 pub pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
580 pub user_key_encrypted_pin: EncString,
582}
583
584pub(super) fn enroll_pin(
585 client: &Client,
586 pin: String,
587) -> Result<EnrollPinResponse, CryptoClientError> {
588 let key_store = client.internal.get_key_store();
589 let mut ctx = key_store.context_mut();
590
591 let key_envelope = PasswordProtectedKeyEnvelope::seal(
592 SymmetricKeySlotId::User,
593 &pin,
594 PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
595 &ctx,
596 )?;
597 let encrypted_pin = pin.encrypt(&mut ctx, SymmetricKeySlotId::User)?;
598 Ok(EnrollPinResponse {
599 pin_protected_user_key_envelope: key_envelope,
600 user_key_encrypted_pin: encrypted_pin,
601 })
602}
603
604#[derive(Serialize, Deserialize, Debug)]
606#[serde(rename_all = "camelCase", deny_unknown_fields)]
607#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
608#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
609pub struct DerivePinKeyResponse {
610 pin_protected_user_key: EncString,
612 encrypted_pin: EncString,
614}
615
616pub(super) async fn derive_pin_key(
617 client: &Client,
618 pin: String,
619) -> Result<DerivePinKeyResponse, CryptoClientError> {
620 let login_method = client
621 .internal
622 .get_login_method()
623 .await
624 .ok_or(NotAuthenticatedError)?;
625
626 let key_store = client.internal.get_key_store();
627 let ctx = key_store.context();
628 #[allow(deprecated)]
630 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
631
632 let pin_protected_user_key = derive_pin_protected_user_key(&pin, &login_method, user_key)?;
633
634 Ok(DerivePinKeyResponse {
635 pin_protected_user_key,
636 encrypted_pin: pin.encrypt_with_key(user_key)?,
637 })
638}
639
640pub(super) async fn derive_pin_user_key(
641 client: &Client,
642 encrypted_pin: EncString,
643) -> Result<EncString, CryptoClientError> {
644 let login_method = client
645 .internal
646 .get_login_method()
647 .await
648 .ok_or(NotAuthenticatedError)?;
649
650 let key_store = client.internal.get_key_store();
651 let ctx = key_store.context();
652 #[allow(deprecated)]
654 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
655
656 let pin: String = encrypted_pin.decrypt_with_key(user_key)?;
657
658 derive_pin_protected_user_key(&pin, &login_method, user_key)
659}
660
661fn derive_pin_protected_user_key(
662 pin: &str,
663 login_method: &UserLoginMethod,
664 user_key: &SymmetricCryptoKey,
665) -> Result<EncString, CryptoClientError> {
666 use bitwarden_crypto::PinKey;
667
668 let derived_key = match login_method {
669 UserLoginMethod::Username { email, kdf, .. }
670 | UserLoginMethod::ApiKey { email, kdf, .. } => {
671 PinKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?
672 }
673 };
674
675 Ok(derived_key.encrypt_user_key(user_key)?)
676}
677
678pub(super) fn make_prf_user_key_set(
679 client: &Client,
680 prf: B64,
681) -> Result<RotateableKeySet, CryptoClientError> {
682 let prf_key = derive_symmetric_key_from_prf(prf.as_bytes())
683 .map_err(|_| CryptoClientError::InvalidPrfInput)?;
684 let ctx = client.internal.get_key_store().context();
685 let key_set = RotateableKeySet::new(&ctx, &prf_key, SymmetricKeySlotId::User)?;
686 Ok(key_set)
687}
688
689#[allow(missing_docs)]
690#[bitwarden_error(flat)]
691#[derive(Debug, thiserror::Error)]
692pub enum EnrollAdminPasswordResetError {
693 #[error(transparent)]
694 Crypto(#[from] bitwarden_crypto::CryptoError),
695}
696
697pub(super) fn enroll_admin_password_reset(
698 client: &Client,
699 public_key: B64,
700) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
701 use bitwarden_crypto::PublicKey;
702
703 let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&public_key))?;
704 let key_store = client.internal.get_key_store();
705 let ctx = key_store.context();
706 #[allow(deprecated)]
708 let key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
709
710 #[expect(deprecated)]
711 Ok(UnsignedSharedKey::encapsulate_key_unsigned(
712 key,
713 &public_key,
714 )?)
715}
716
717#[derive(Serialize, Deserialize, Debug, JsonSchema)]
719#[serde(rename_all = "camelCase", deny_unknown_fields)]
720#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
721#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
722pub struct DeriveKeyConnectorRequest {
723 pub user_key_encrypted: EncString,
725 pub password: String,
727 pub kdf: Kdf,
729 pub email: String,
731}
732
733#[allow(missing_docs)]
734#[bitwarden_error(flat)]
735#[derive(Debug, thiserror::Error)]
736pub enum DeriveKeyConnectorError {
737 #[error(transparent)]
738 WrongPassword(#[from] WrongPasswordError),
739 #[error(transparent)]
740 Crypto(#[from] bitwarden_crypto::CryptoError),
741}
742
743pub(super) fn derive_key_connector(
745 request: DeriveKeyConnectorRequest,
746) -> Result<B64, DeriveKeyConnectorError> {
747 let master_key = MasterKey::derive(&request.password, &request.email, &request.kdf)?;
748 master_key
749 .decrypt_user_key(request.user_key_encrypted)
750 .map_err(|_| WrongPasswordError)?;
751
752 Ok(master_key.to_base64())
753}
754
755#[derive(Serialize, Deserialize, Debug)]
757#[serde(rename_all = "camelCase", deny_unknown_fields)]
758#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
759#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
760pub struct MakeKeyPairResponse {
761 user_public_key: B64,
763 user_key_encrypted_private_key: EncString,
765}
766
767pub(super) fn make_key_pair(user_key: B64) -> Result<MakeKeyPairResponse, CryptoError> {
768 let user_key = UserKey::new(SymmetricCryptoKey::try_from(user_key)?);
769
770 let key_pair = user_key.make_key_pair()?;
771
772 Ok(MakeKeyPairResponse {
773 user_public_key: key_pair.public,
774 user_key_encrypted_private_key: key_pair.private,
775 })
776}
777
778#[derive(Serialize, Deserialize, Debug)]
780#[serde(rename_all = "camelCase", deny_unknown_fields)]
781#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
782#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
783pub struct VerifyAsymmetricKeysRequest {
784 user_key: B64,
786 user_public_key: B64,
788 user_key_encrypted_private_key: EncString,
790}
791
792#[derive(Serialize, Deserialize, Debug)]
794#[serde(rename_all = "camelCase", deny_unknown_fields)]
795#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
796#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
797pub struct VerifyAsymmetricKeysResponse {
798 private_key_decryptable: bool,
800 valid_private_key: bool,
802}
803
804pub(super) fn verify_asymmetric_keys(
805 request: VerifyAsymmetricKeysRequest,
806) -> Result<VerifyAsymmetricKeysResponse, CryptoError> {
807 #[derive(Debug, thiserror::Error)]
808 enum VerifyError {
809 #[error("Failed to decrypt private key: {0:?}")]
810 DecryptFailed(bitwarden_crypto::CryptoError),
811 #[error("Failed to parse decrypted private key: {0:?}")]
812 ParseFailed(bitwarden_crypto::CryptoError),
813 #[error("Failed to derive a public key: {0:?}")]
814 PublicFailed(bitwarden_crypto::CryptoError),
815 #[error("Derived public key doesn't match")]
816 KeyMismatch,
817 }
818
819 fn verify_inner(
820 user_key: &SymmetricCryptoKey,
821 request: &VerifyAsymmetricKeysRequest,
822 ) -> Result<(), VerifyError> {
823 let decrypted_private_key: Vec<u8> = request
824 .user_key_encrypted_private_key
825 .decrypt_with_key(user_key)
826 .map_err(VerifyError::DecryptFailed)?;
827
828 let decrypted_private_key = Pkcs8PrivateKeyBytes::from(decrypted_private_key);
829 let private_key =
830 PrivateKey::from_der(&decrypted_private_key).map_err(VerifyError::ParseFailed)?;
831
832 let derived_public_key_vec = private_key
833 .to_public_key()
834 .to_der()
835 .map_err(VerifyError::PublicFailed)?;
836
837 let derived_public_key = B64::from(derived_public_key_vec);
838
839 if derived_public_key != request.user_public_key {
840 return Err(VerifyError::KeyMismatch);
841 }
842 Ok(())
843 }
844
845 let user_key = SymmetricCryptoKey::try_from(request.user_key.clone())?;
846
847 Ok(match verify_inner(&user_key, &request) {
848 Ok(_) => VerifyAsymmetricKeysResponse {
849 private_key_decryptable: true,
850 valid_private_key: true,
851 },
852 Err(error) => {
853 tracing::debug!(%error, "User asymmetric keys verification");
854
855 VerifyAsymmetricKeysResponse {
856 private_key_decryptable: !matches!(error, VerifyError::DecryptFailed(_)),
857 valid_private_key: false,
858 }
859 }
860 })
861}
862
863#[derive(Serialize, Deserialize, Debug, Clone)]
865#[serde(rename_all = "camelCase", deny_unknown_fields)]
866#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
867#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
868pub struct UserCryptoV2KeysResponse {
869 user_key: B64,
871
872 private_key: EncString,
874 public_key: B64,
876 signed_public_key: SignedPublicKey,
878
879 signing_key: EncString,
881 verifying_key: B64,
883
884 security_state: SignedSecurityState,
886 security_version: u64,
888}
889
890#[deprecated(note = "Use AccountCryptographicState::rotate instead")]
894pub(crate) fn make_v2_keys_for_v1_user(
895 client: &Client,
896) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
897 let key_store = client.internal.get_key_store();
898 let mut ctx = key_store.context();
899
900 let private_key_id = PrivateKeySlotId::UserPrivateKey;
902
903 if client.internal.get_security_version() != 1 {
905 return Err(StatefulCryptoError::WrongAccountCryptoVersion {
906 expected: "1".to_string(),
907 got: 2,
908 });
909 }
910
911 if !ctx.has_private_key(PrivateKeySlotId::UserPrivateKey) {
915 return Err(StatefulCryptoError::Crypto(CryptoError::MissingKeyId(
916 "UserPrivateKey".to_string(),
917 )));
918 }
919
920 #[allow(deprecated)]
921 let private_key = ctx.dangerous_get_private_key(private_key_id)?.clone();
922
923 let user_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
925
926 let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
928 let temporary_signing_key_id = ctx.add_local_signing_key(signing_key.clone());
929
930 let signed_public_key = ctx.make_signed_public_key(private_key_id, temporary_signing_key_id)?;
932 let public_key = private_key.to_public_key();
933
934 let security_state = SecurityState::new();
936 let signed_security_state = security_state.sign(temporary_signing_key_id, &mut ctx)?;
937
938 Ok(UserCryptoV2KeysResponse {
939 user_key: user_key.to_base64(),
940
941 private_key: private_key.to_der()?.encrypt_with_key(&user_key)?,
942 public_key: public_key.to_der()?.into(),
943 signed_public_key,
944
945 signing_key: signing_key.to_cose().encrypt_with_key(&user_key)?,
946 verifying_key: signing_key.to_verifying_key().to_cose().into(),
947
948 security_state: signed_security_state,
949 security_version: security_state.version(),
950 })
951}
952
953#[deprecated(note = "Use AccountCryptographicState::rotate instead")]
958pub(crate) fn get_v2_rotated_account_keys(
959 client: &Client,
960) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
961 let key_store = client.internal.get_key_store();
962 let mut ctx = key_store.context();
963
964 if client.internal.get_security_version() == 1 {
967 return Err(StatefulCryptoError::WrongAccountCryptoVersion {
968 expected: "2+".to_string(),
969 got: 1,
970 });
971 }
972
973 let security_state = client
974 .internal
975 .security_state
976 .read()
977 .expect("RwLock is not poisoned")
978 .to_owned()
979 .ok_or(StatefulCryptoError::MissingSecurityState)?;
982
983 #[expect(deprecated)]
984 let rotated_keys = dangerous_get_v2_rotated_account_keys(
985 PrivateKeySlotId::UserPrivateKey,
986 SigningKeySlotId::UserSigningKey,
987 &ctx,
988 )?;
989
990 Ok(UserCryptoV2KeysResponse {
991 user_key: rotated_keys.user_key.to_base64(),
992
993 private_key: rotated_keys.private_key,
994 public_key: rotated_keys.public_key.into(),
995 signed_public_key: rotated_keys.signed_public_key,
996
997 signing_key: rotated_keys.signing_key,
998 verifying_key: rotated_keys.verifying_key.into(),
999
1000 security_state: security_state.sign(SigningKeySlotId::UserSigningKey, &mut ctx)?,
1001 security_version: security_state.version(),
1002 })
1003}
1004
1005pub struct MakeTdeRegistrationResponse {
1007 pub account_cryptographic_state: WrappedAccountCryptographicState,
1009 pub user_key: SymmetricCryptoKey,
1011 pub account_keys_request: AccountKeysRequestModel,
1013 pub trusted_device_keys: TrustDeviceResponse,
1015 pub reset_password_key: UnsignedSharedKey,
1017}
1018
1019pub struct MakeJitMasterPasswordRegistrationResponse {
1021 pub account_cryptographic_state: WrappedAccountCryptographicState,
1023 pub user_key: SymmetricCryptoKey,
1025 pub master_password_authentication_data: MasterPasswordAuthenticationData,
1027 pub master_password_unlock_data: MasterPasswordUnlockData,
1029 pub account_keys_request: AccountKeysRequestModel,
1031 pub reset_password_key: UnsignedSharedKey,
1033}
1034
1035#[bitwarden_error(flat)]
1037#[derive(Debug, thiserror::Error)]
1038pub enum MakeKeysError {
1039 #[error("Failed to initialize account cryptography")]
1041 AccountCryptographyInitialization(AccountCryptographyInitializationError),
1042 #[error("Failed to derive master password")]
1044 MasterPasswordDerivation(MasterPasswordError),
1045 #[error("Failed to make a request model")]
1047 RequestModelCreation,
1048 #[error("Cryptography error: {0}")]
1050 Crypto(#[from] CryptoError),
1051}
1052
1053pub(crate) fn make_user_tde_registration(
1055 client: &Client,
1056 org_public_key: B64,
1057) -> Result<MakeTdeRegistrationResponse, MakeKeysError> {
1058 let mut ctx = client.internal.get_key_store().context_mut();
1059 let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1060 .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1061 #[expect(deprecated)]
1063 let device_key = DeviceKey::trust_device(ctx.dangerous_get_symmetric_key(user_key_id)?)?;
1064
1065 let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&org_public_key))
1067 .map_err(MakeKeysError::Crypto)?;
1068 #[expect(deprecated)]
1069 let admin_reset = UnsignedSharedKey::encapsulate_key_unsigned(
1070 ctx.dangerous_get_symmetric_key(user_key_id)?,
1071 &public_key,
1072 )
1073 .map_err(MakeKeysError::Crypto)?;
1074
1075 let cryptography_state_request_model = wrapped_state
1076 .to_request_model(&user_key_id, &mut ctx)
1077 .map_err(|_| MakeKeysError::RequestModelCreation)?;
1078
1079 #[expect(deprecated)]
1080 Ok(MakeTdeRegistrationResponse {
1081 account_cryptographic_state: wrapped_state,
1082 user_key: ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned(),
1083 account_keys_request: cryptography_state_request_model,
1084 trusted_device_keys: device_key,
1085 reset_password_key: admin_reset,
1086 })
1087}
1088
1089pub struct MakeKeyConnectorRegistrationResponse {
1091 pub account_cryptographic_state: WrappedAccountCryptographicState,
1093 pub key_connector_key_wrapped_user_key: EncString,
1095 pub user_key: SymmetricCryptoKey,
1097 pub account_keys_request: AccountKeysRequestModel,
1099 pub key_connector_key: KeyConnectorKey,
1101}
1102
1103pub(crate) fn make_user_key_connector_registration(
1105 client: &Client,
1106) -> Result<MakeKeyConnectorRegistrationResponse, MakeKeysError> {
1107 let mut ctx = client.internal.get_key_store().context_mut();
1108 let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1109 .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1110 #[expect(deprecated)]
1111 let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
1112
1113 let key_connector_key = KeyConnectorKey::make();
1115
1116 let wrapped_user_key = key_connector_key
1117 .encrypt_user_key(&user_key)
1118 .map_err(MakeKeysError::Crypto)?;
1119
1120 let cryptography_state_request_model =
1121 wrapped_state
1122 .to_request_model(&user_key_id, &mut ctx)
1123 .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1124
1125 Ok(MakeKeyConnectorRegistrationResponse {
1126 account_cryptographic_state: wrapped_state,
1127 key_connector_key_wrapped_user_key: wrapped_user_key,
1128 user_key,
1129 account_keys_request: cryptography_state_request_model,
1130 key_connector_key,
1131 })
1132}
1133
1134async fn initialize_user_local_data_key(client: &Client) -> Result<(), EncryptionSettingsError> {
1140 let user_id = client
1141 .internal
1142 .get_user_id()
1143 .ok_or(EncryptionSettingsError::LocalUserDataKeyInitFailed)?;
1144
1145 migrate_local_user_data_key_for_user_key_upgrade(client, user_id)
1146 .await
1147 .map_err(|_| EncryptionSettingsError::LocalUserDataMigrationFailed)?;
1148
1149 initialize_local_user_data_key_into_state(client, user_id)
1150 .await
1151 .map_err(|_| EncryptionSettingsError::LocalUserDataKeyInitFailed)?;
1152
1153 let wrapped_key = get_local_user_data_key_from_state(client, user_id)
1154 .await
1155 .map_err(|_| EncryptionSettingsError::LocalUserDataKeyLoadFailed)?;
1156 let mut ctx = client.internal.get_key_store().context_mut();
1157 wrapped_key
1158 .unwrap_to_context(&mut ctx)
1159 .map_err(|_| EncryptionSettingsError::LocalUserDataKeyLoadFailed)
1160}
1161
1162pub(crate) fn make_user_jit_master_password_registration(
1164 client: &Client,
1165 master_password: String,
1166 salt: String,
1167 org_public_key: B64,
1168) -> Result<MakeJitMasterPasswordRegistrationResponse, MakeKeysError> {
1169 let mut ctx = client.internal.get_key_store().context_mut();
1170 let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1171 .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1172
1173 let kdf = Kdf::default_argon2();
1174
1175 #[expect(deprecated)]
1176 let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
1177
1178 let master_password_unlock_data =
1179 MasterPasswordUnlockData::derive(&master_password, &kdf, &salt, user_key_id, &ctx)
1180 .map_err(MakeKeysError::MasterPasswordDerivation)?;
1181
1182 let master_password_authentication_data =
1183 MasterPasswordAuthenticationData::derive(&master_password, &kdf, &salt)
1184 .map_err(MakeKeysError::MasterPasswordDerivation)?;
1185
1186 let cryptography_state_request_model = wrapped_state
1187 .to_request_model(&user_key_id, &mut ctx)
1188 .map_err(|_| MakeKeysError::RequestModelCreation)?;
1189
1190 let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&org_public_key))
1192 .map_err(MakeKeysError::Crypto)?;
1193 let admin_reset_key = UnsignedSharedKey::encapsulate(user_key_id, &public_key, &ctx)
1194 .map_err(MakeKeysError::Crypto)?;
1195
1196 Ok(MakeJitMasterPasswordRegistrationResponse {
1197 account_cryptographic_state: wrapped_state,
1198 user_key,
1199 master_password_unlock_data,
1200 master_password_authentication_data,
1201 account_keys_request: cryptography_state_request_model,
1202 reset_password_key: admin_reset_key,
1203 })
1204}
1205
1206pub struct MakeUserMasterPasswordRegistrationResponse {
1208 pub account_cryptographic_state: WrappedAccountCryptographicState,
1210 pub master_password_unlock_data: MasterPasswordUnlockData,
1212 pub master_password_authentication_data: MasterPasswordAuthenticationData,
1214 pub account_keys_request: AccountKeysRequestModel,
1216 pub user_key: SymmetricCryptoKey,
1218}
1219
1220pub(crate) fn make_user_password_registration(
1222 client: &Client,
1223 master_password: String,
1224 salt: String,
1225) -> Result<MakeUserMasterPasswordRegistrationResponse, MakeKeysError> {
1226 let mut ctx = client.internal.get_key_store().context_mut();
1229 let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1230 .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1231
1232 let kdf = Kdf::default_argon2();
1233
1234 #[expect(deprecated)]
1235 let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
1236
1237 let master_password_unlock_data =
1238 MasterPasswordUnlockData::derive(&master_password, &kdf, &salt, user_key_id, &ctx)
1239 .map_err(MakeKeysError::MasterPasswordDerivation)?;
1240
1241 let master_password_authentication_data =
1242 MasterPasswordAuthenticationData::derive(&master_password, &kdf, &salt)
1243 .map_err(MakeKeysError::MasterPasswordDerivation)?;
1244
1245 let account_keys_request = wrapped_state
1246 .to_request_model(&user_key_id, &mut ctx)
1247 .map_err(|_| MakeKeysError::RequestModelCreation)?;
1248
1249 Ok(MakeUserMasterPasswordRegistrationResponse {
1250 account_cryptographic_state: wrapped_state,
1251 master_password_unlock_data,
1252 master_password_authentication_data,
1253 account_keys_request,
1254 user_key,
1255 })
1256}
1257
1258#[cfg(test)]
1259mod tests {
1260 use std::num::NonZeroU32;
1261
1262 use bitwarden_crypto::{
1263 Decryptable, KeyStore, PrivateKey, PublicKeyEncryptionAlgorithm, RsaKeyPair,
1264 SymmetricKeyAlgorithm,
1265 };
1266
1267 use super::*;
1268 use crate::{
1269 Client,
1270 client::test_accounts::{test_bitwarden_com_account, test_bitwarden_com_account_v2},
1271 key_management::{
1272 KeySlotIds, V2UpgradeToken, state_bridge::test_support::InMemoryStateBridge,
1273 },
1274 };
1275
1276 const TEST_VECTOR_USER_KEY_V2_B64: &str = "pQEEAlACHUUoybNAuJoZzqNMxz2bAzoAARFvBIQDBAUGIFggAvGl4ifaUAomQdCdUPpXLHtypiQxHjZwRHeI83caZM4B";
1277 const TEST_VECTOR_PRIVATE_KEY_V2: &str = "7.g1gdowE6AAERbwMZARwEUAIdRSjJs0C4mhnOo0zHPZuhBVgYthGLGqVLPeidY8mNMxpLJn3fyeSxyaWsWQTR6pxmRV2DyGZXly/0l9KK+Rsfetl9wvYIz0O4/RW3R6wf7eGxo5XmicV3WnFsoAmIQObxkKWShxFyjzg+ocKItQDzG7Gp6+MW4biTrAlfK51ML/ZS+PCjLmgI1QQr4eMHjiwA2TBKtKkxfjoTJkMXECpRVLEXOo8/mbIGYkuabbSA7oU+TJ0yXlfKDtD25gnyO7tjW/0JMFUaoEKRJOuKoXTN4n/ks4Hbxk0X5/DzfG05rxWad2UNBjNg7ehW99WrQ+33ckdQFKMQOri/rt8JzzrF1k11/jMJ+Y2TADKNHr91NalnUX+yqZAAe3sRt5Pv5ZhLIwRMKQi/1NrLcsQPRuUnogVSPOoMnE/eD6F70iU60Z6pvm1iBw2IvELZcrs/oxpO2SeCue08fIZW/jNZokbLnm90tQ7QeZTUpiPALhUgfGOa3J9VOJ7jQGCqDjd9CzV2DCVfhKCapeTbldm+RwEWBz5VvorH5vMx1AzbPRJxdIQuxcg3NqRrXrYC7fyZljWaPB9qP1tztiPtd1PpGEgxLByIfR6fqyZMCvOBsWbd0H6NhF8mNVdDw60+skFRdbRBTSCjCtKZeLVuVFb8ioH45PR5oXjtx4atIDzu6DKm6TTMCbR6DjZuZZ8GbwHxuUD2mDD3pAFhaof9kR3lQdjy7Zb4EzUUYskQxzcLPcqzp9ZgB3Rg91SStBCCMhdQ6AnhTy+VTGt/mY5AbBXNRSL6fI0r+P9K8CcEI4bNZCDkwwQr5v4O4ykSUzIvmVU0zKzDngy9bteIZuhkvGUoZlQ9UATNGPhoLfqq2eSvqEXkCbxTVZ5D+Ww9pHmWeVcvoBhcl5MvicfeQt++dY3tPjIfZq87nlugG4HiNbcv9nbVpgwe3v8cFetWXQgnO4uhx8JHSwGoSuxHFZtl2sdahjTHavRHnYjSABEFrViUKgb12UDD5ow1GAL62wVdSJKRf9HlLbJhN3PBxuh5L/E0wy1wGA9ecXtw/R1ktvXZ7RklGAt1TmNzZv6vI2J/CMXvndOX9rEpjKMbwbIDAjQ9PxiWdcnmc5SowT9f6yfIjbjXnRMWWidPAua7sgrtej4HP4Qjz1fpgLMLCRyF97tbMTmsAI5Cuj98Buh9PwcdyXj5SbVuHdJS1ehv9b5SWPsD4pwOm3+otVNK6FTazhoUl47AZoAoQzXfsXxrzqYzvF0yJkCnk9S1dcij1L569gQ43CJO6o6jIZFJvA4EmZDl95ELu+BC+x37Ip8dq4JLPsANDVSqvXO9tfDUIXEx25AaOYhW2KAUoDve/fbsU8d0UZR1o/w+ZrOQwawCIPeVPtbh7KFRVQi/rPI+Abl6XR6qMJbKPegliYGUuGF2oEMEc6QLTsMRCEPuw0S3kxbNfVPqml8nGhB2r8zUHBY1diJEmipVghnwH74gIKnyJ2C9nKjV8noUfKzqyV8vxUX2G5yXgodx8Jn0cWs3XhWuApFla9z4R28W/4jA1jK2WQMlx+b6xKUWgRk8+fYsc0HSt2fDrQ9pLpnjb8ME59RCxSPV++PThpnR2JtastZBZur2hBIJsGILCAmufUU4VC4gBKPhNfu/OK4Ktgz+uQlUa9fEC/FnkpTRQPxHuQjSQSNrIIyW1bIRBtnwjvvvNoui9FZJ";
1278 #[allow(unused)]
1279 const TEST_VECTOR_PUBLIC_KEY_V2: &str = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz/+1jPJ1HqcaCdKrTPms8XJcvnmd9alI42U2XF/4GMNTM5KF1gI6snhR/23ZLatZRFMHoK8ZCMSpGNkjLadArz52ldceTvBOhQUiWylkZQ4NfNa3xIYJubXOmkeDyfNuyLxVZvcZOko9PdT+Qx2QxDrFi2XNo2I7aVFd19/COIEkex4mJ0eA3MHFpKCdxYbcTAsGID8+kVR9L84S1JptZoG8x+iB/D3/Q4y02UsQYpFTu0vbPY84YmW03ngJdxWzS8X4/UJI/jaEn5rO4xlU5QcL0l4IybP5LRpE9XEeUHATKVOG7eNfpe9zDfKV2qQoofQMH9VvkWO4psaWDjBSdwIDAQAB";
1280 #[allow(unused)]
1281 const TEST_VECTOR_SIGNED_PUBLIC_KEY_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8BoFkBTqNpYWxnb3JpdGhtAG1jb250ZW50Rm9ybWF0AGlwdWJsaWNLZXlZASYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP/7WM8nUepxoJ0qtM+azxcly+eZ31qUjjZTZcX/gYw1MzkoXWAjqyeFH/bdktq1lEUwegrxkIxKkY2SMtp0CvPnaV1x5O8E6FBSJbKWRlDg181rfEhgm5tc6aR4PJ827IvFVm9xk6Sj091P5DHZDEOsWLZc2jYjtpUV3X38I4gSR7HiYnR4DcwcWkoJ3FhtxMCwYgPz6RVH0vzhLUmm1mgbzH6IH8Pf9DjLTZSxBikVO7S9s9jzhiZbTeeAl3FbNLxfj9Qkj+NoSfms7jGVTlBwvSXgjJs/ktGkT1cR5QcBMpU4bt41+l73MN8pXapCih9Awf1W+RY7imxpYOMFJ3AgMBAAFYQMq/hT4wod2w8xyoM7D86ctuLNX4ZRo+jRHf2sZfaO7QsvonG/ZYuNKF5fq8wpxMRjfoMvnY2TTShbgzLrW8BA4=";
1282 const TEST_VECTOR_SIGNING_KEY_V2: &str = "7.g1gcowE6AAERbwMYZQRQAh1FKMmzQLiaGc6jTMc9m6EFWBhYePc2qkCruHAPXgbzXsIP1WVk11ArbLNYUBpifToURlwHKs1je2BwZ1C/5thz4nyNbL0wDaYkRWI9ex1wvB7KhdzC7ltStEd5QttboTSCaXQROSZaGBPNO5+Bu3sTY8F5qK1pBUo6AHNN";
1283 #[allow(unused)]
1284 const TEST_VECTOR_VERIFYING_KEY_V2: &str =
1285 "pgEBAlAmkP0QgfdMVbIujX55W/yNAycEgQIgBiFYIEM6JxBmjWQTruAm3s6BTaJy1q6BzQetMBacNeRJ0kxR";
1286 const TEST_VECTOR_SECURITY_STATE_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8CoFgkomhlbnRpdHlJZFBHOOw2BI9OQoNq+Vl1xZZKZ3ZlcnNpb24CWEAlchbJR0vmRfShG8On7Q2gknjkw4Dd6MYBLiH4u+/CmfQdmjNZdf6kozgW/6NXyKVNu8dAsKsin+xxXkDyVZoG";
1287
1288 const TEST_USER_EMAIL: &str = "[email protected]";
1289 const TEST_USER_PASSWORD: &str = "asdfasdfasdf";
1290 const TEST_ACCOUNT_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
1291 const TEST_ACCOUNT_PRIVATE_KEY: &str = "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=";
1292
1293 async fn init_v2_account_with_master_password_and_upgrade_token(
1294 client: &Client,
1295 user_id: UserId,
1296 upgrade_token: V2UpgradeToken,
1297 ) {
1298 initialize_user_crypto(
1299 client,
1300 InitUserCryptoRequest {
1301 user_id: Some(user_id),
1302 kdf_params: Kdf::PBKDF2 {
1303 iterations: 600_000.try_into().unwrap(),
1304 },
1305 email: TEST_USER_EMAIL.into(),
1306 account_cryptographic_state: WrappedAccountCryptographicState::V2 {
1307 private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
1308 signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
1309 security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
1310 signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
1311 },
1312 method: InitUserCryptoMethod::MasterPasswordUnlock {
1313 password: TEST_USER_PASSWORD.into(),
1314 master_password_unlock: MasterPasswordUnlockData {
1315 kdf: Kdf::PBKDF2 {
1316 iterations: 600_000.try_into().unwrap(),
1317 },
1318 master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
1319 salt: TEST_USER_EMAIL.to_string(),
1320 },
1321 },
1322 upgrade_token: Some(upgrade_token),
1323 },
1324 )
1325 .await
1326 .unwrap();
1327 }
1328
1329 #[tokio::test]
1330 async fn test_update_kdf() {
1331 let client = Client::new_test(None);
1332
1333 let priv_key: EncString = "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw=".parse().unwrap();
1334
1335 let kdf = Kdf::PBKDF2 {
1336 iterations: 100_000.try_into().unwrap(),
1337 };
1338
1339 initialize_user_crypto(
1340 &client,
1341 InitUserCryptoRequest {
1342 user_id: Some(UserId::new_v4()),
1343 kdf_params: kdf.clone(),
1344 email: "[email protected]".into(),
1345 account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1346 method: InitUserCryptoMethod::MasterPasswordUnlock {
1347 password: "asdfasdfasdf".into(),
1348 master_password_unlock: MasterPasswordUnlockData {
1349 kdf: kdf.clone(),
1350 master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1351 salt: "[email protected]".to_string(),
1352 },
1353 },
1354 upgrade_token: None,
1355 },
1356 )
1357 .await
1358 .unwrap();
1359
1360 let new_kdf = Kdf::PBKDF2 {
1361 iterations: 600_000.try_into().unwrap(),
1362 };
1363 let new_kdf_response = make_update_kdf(&client, "123412341234", &new_kdf)
1364 .await
1365 .unwrap();
1366
1367 let client2 = Client::new_test(None);
1368
1369 initialize_user_crypto(
1370 &client2,
1371 InitUserCryptoRequest {
1372 user_id: Some(UserId::new_v4()),
1373 kdf_params: new_kdf.clone(),
1374 email: "[email protected]".into(),
1375 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1376 private_key: priv_key.to_owned(),
1377 },
1378 method: InitUserCryptoMethod::MasterPasswordUnlock {
1379 password: "123412341234".to_string(),
1380 master_password_unlock: MasterPasswordUnlockData {
1381 kdf: new_kdf.clone(),
1382 master_key_wrapped_user_key: new_kdf_response
1383 .master_password_unlock_data
1384 .master_key_wrapped_user_key,
1385 salt: "[email protected]".to_string(),
1386 },
1387 },
1388 upgrade_token: None,
1389 },
1390 )
1391 .await
1392 .unwrap();
1393
1394 let new_hash = client2
1395 .kdf()
1396 .hash_password(
1397 "[email protected]".into(),
1398 "123412341234".into(),
1399 new_kdf.clone(),
1400 bitwarden_crypto::HashPurpose::ServerAuthorization,
1401 )
1402 .await
1403 .unwrap();
1404
1405 assert_eq!(
1406 new_hash,
1407 new_kdf_response
1408 .master_password_authentication_data
1409 .master_password_authentication_hash
1410 );
1411
1412 let client_key = {
1413 let key_store = client.internal.get_key_store();
1414 let ctx = key_store.context();
1415 #[allow(deprecated)]
1416 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1417 .unwrap()
1418 .to_base64()
1419 };
1420
1421 let client2_key = {
1422 let key_store = client2.internal.get_key_store();
1423 let ctx = key_store.context();
1424 #[allow(deprecated)]
1425 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1426 .unwrap()
1427 .to_base64()
1428 };
1429
1430 assert_eq!(client_key, client2_key);
1431 }
1432
1433 #[tokio::test]
1434 async fn test_update_password() {
1435 let client = Client::new_test(None);
1436
1437 let priv_key: EncString = "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw=".parse().unwrap();
1438
1439 let kdf = Kdf::PBKDF2 {
1440 iterations: 100_000.try_into().unwrap(),
1441 };
1442
1443 initialize_user_crypto(
1444 &client,
1445 InitUserCryptoRequest {
1446 user_id: Some(UserId::new_v4()),
1447 kdf_params: kdf.clone(),
1448 email: "[email protected]".into(),
1449 account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1450 method: InitUserCryptoMethod::MasterPasswordUnlock {
1451 password: "asdfasdfasdf".to_string(),
1452 master_password_unlock: MasterPasswordUnlockData {
1453 kdf: kdf.clone(),
1454 master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1455 salt: "[email protected]".to_string(),
1456 },
1457 },
1458 upgrade_token: None,
1459 },
1460 )
1461 .await
1462 .unwrap();
1463
1464 let new_password_response = make_update_password(&client, "123412341234".into())
1465 .await
1466 .unwrap();
1467
1468 let client2 = Client::new_test(None);
1469
1470 initialize_user_crypto(
1471 &client2,
1472 InitUserCryptoRequest {
1473 user_id: Some(UserId::new_v4()),
1474 kdf_params: kdf.clone(),
1475 email: "[email protected]".into(),
1476 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1477 private_key: priv_key.to_owned(),
1478 },
1479 method: InitUserCryptoMethod::MasterPasswordUnlock {
1480 password: "123412341234".into(),
1481 master_password_unlock: MasterPasswordUnlockData {
1482 kdf: kdf.clone(),
1483 master_key_wrapped_user_key: new_password_response.new_key,
1484 salt: "[email protected]".to_string(),
1485 },
1486 },
1487 upgrade_token: None,
1488 },
1489 )
1490 .await
1491 .unwrap();
1492
1493 let new_hash = client2
1494 .kdf()
1495 .hash_password(
1496 "[email protected]".into(),
1497 "123412341234".into(),
1498 kdf.clone(),
1499 bitwarden_crypto::HashPurpose::ServerAuthorization,
1500 )
1501 .await
1502 .unwrap();
1503
1504 assert_eq!(new_hash, new_password_response.password_hash);
1505
1506 let client_key = {
1507 let key_store = client.internal.get_key_store();
1508 let ctx = key_store.context();
1509 #[allow(deprecated)]
1510 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1511 .unwrap()
1512 .to_base64()
1513 };
1514
1515 let client2_key = {
1516 let key_store = client2.internal.get_key_store();
1517 let ctx = key_store.context();
1518 #[allow(deprecated)]
1519 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1520 .unwrap()
1521 .to_base64()
1522 };
1523
1524 assert_eq!(client_key, client2_key);
1525 }
1526
1527 #[tokio::test]
1528 async fn test_initialize_user_crypto_pin() {
1529 let client = Client::new_test(None);
1530
1531 let priv_key: EncString = "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw=".parse().unwrap();
1532
1533 initialize_user_crypto(
1534 &client,
1535 InitUserCryptoRequest {
1536 user_id: Some(UserId::new_v4()),
1537 kdf_params: Kdf::PBKDF2 {
1538 iterations: 100_000.try_into().unwrap(),
1539 },
1540 email: "[email protected]".into(),
1541 account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1542 method: InitUserCryptoMethod::MasterPasswordUnlock {
1543 password: "asdfasdfasdf".into(),
1544 master_password_unlock: MasterPasswordUnlockData {
1545 kdf: Kdf::PBKDF2 {
1546 iterations: 100_000.try_into().unwrap(),
1547 },
1548 master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1549 salt: "[email protected]".to_string(),
1550 },
1551 },
1552 upgrade_token: None,
1553 },
1554 )
1555 .await
1556 .unwrap();
1557
1558 let pin_key = derive_pin_key(&client, "1234".into()).await.unwrap();
1559
1560 let client2 = Client::new_test(None);
1562 initialize_user_crypto(
1563 &client2,
1564 InitUserCryptoRequest {
1565 user_id: Some(UserId::new_v4()),
1566 kdf_params: Kdf::PBKDF2 {
1567 iterations: 100_000.try_into().unwrap(),
1568 },
1569 email: "[email protected]".into(),
1570 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1571 private_key: priv_key.to_owned(),
1572 },
1573 method: InitUserCryptoMethod::Pin {
1574 pin: "1234".into(),
1575 pin_protected_user_key: pin_key.pin_protected_user_key,
1576 },
1577 upgrade_token: None,
1578 },
1579 )
1580 .await
1581 .unwrap();
1582
1583 let client_key = {
1584 let key_store = client.internal.get_key_store();
1585 let ctx = key_store.context();
1586 #[allow(deprecated)]
1587 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1588 .unwrap()
1589 .to_base64()
1590 };
1591
1592 let client2_key = {
1593 let key_store = client2.internal.get_key_store();
1594 let ctx = key_store.context();
1595 #[allow(deprecated)]
1596 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1597 .unwrap()
1598 .to_base64()
1599 };
1600
1601 assert_eq!(client_key, client2_key);
1602
1603 let pin_protected_user_key = derive_pin_user_key(&client, pin_key.encrypted_pin)
1605 .await
1606 .unwrap();
1607
1608 let client3 = Client::new_test(None);
1609
1610 initialize_user_crypto(
1611 &client3,
1612 InitUserCryptoRequest {
1613 user_id: Some(UserId::new_v4()),
1614 kdf_params: Kdf::PBKDF2 {
1615 iterations: 100_000.try_into().unwrap(),
1616 },
1617 email: "[email protected]".into(),
1618 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1619 private_key: priv_key.to_owned(),
1620 },
1621 method: InitUserCryptoMethod::Pin {
1622 pin: "1234".into(),
1623 pin_protected_user_key,
1624 },
1625 upgrade_token: None,
1626 },
1627 )
1628 .await
1629 .unwrap();
1630
1631 let client_key = {
1632 let key_store = client.internal.get_key_store();
1633 let ctx = key_store.context();
1634 #[allow(deprecated)]
1635 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1636 .unwrap()
1637 .to_base64()
1638 };
1639
1640 let client3_key = {
1641 let key_store = client3.internal.get_key_store();
1642 let ctx = key_store.context();
1643 #[allow(deprecated)]
1644 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1645 .unwrap()
1646 .to_base64()
1647 };
1648
1649 assert_eq!(client_key, client3_key);
1650 }
1651
1652 #[tokio::test]
1653 async fn test_initialize_user_crypto_pin_envelope() {
1654 let user_key = "5yKAZ4TSSEGje54MV5lc5ty6crkqUz4xvl+8Dm/piNLKf6OgRi2H0uzttNTXl9z6ILhkmuIXzGpAVc2YdorHgQ==";
1655 let test_pin = "1234";
1656
1657 let client1 = Client::new_test(None);
1658 initialize_user_crypto(
1659 &client1,
1660 InitUserCryptoRequest {
1661 user_id: Some(UserId::new_v4()),
1662 kdf_params: Kdf::PBKDF2 {
1663 iterations: 100_000.try_into().unwrap(),
1664 },
1665 email: "[email protected]".into(),
1666 account_cryptographic_state: {
1667 let store: KeyStore<KeySlotIds> = KeyStore::default();
1668 let mut ctx = store.context_mut();
1669 WrappedAccountCryptographicState::make_v1(&mut ctx)
1670 .unwrap()
1671 .1
1672 },
1673 method: InitUserCryptoMethod::DecryptedKey {
1674 decrypted_user_key: user_key.to_string(),
1675 },
1676 upgrade_token: None,
1677 },
1678 )
1679 .await
1680 .unwrap();
1681
1682 let enroll_response = client1.crypto().enroll_pin(test_pin.to_string()).unwrap();
1683
1684 let client2 = Client::new_test(None);
1685 initialize_user_crypto(
1686 &client2,
1687 InitUserCryptoRequest {
1688 user_id: Some(UserId::new_v4()),
1689 kdf_params: Kdf::PBKDF2 {
1692 iterations: 600_000.try_into().unwrap(),
1693 },
1694 email: "[email protected]".into(),
1695 account_cryptographic_state: {
1696 let store: KeyStore<KeySlotIds> = KeyStore::default();
1697 let mut ctx = store.context_mut();
1698 WrappedAccountCryptographicState::make_v1(&mut ctx)
1699 .unwrap()
1700 .1
1701 },
1702 method: InitUserCryptoMethod::PinEnvelope {
1703 pin: test_pin.to_string(),
1704 pin_protected_user_key_envelope: enroll_response
1705 .pin_protected_user_key_envelope,
1706 },
1707 upgrade_token: None,
1708 },
1709 )
1710 .await
1711 .unwrap();
1712 }
1713
1714 #[tokio::test]
1715 async fn test_initialize_user_crypto_pin_state() {
1716 use crate::key_management::pin_lock_system::PinLockType;
1717
1718 let client1 = Client::init_test_account(test_bitwarden_com_account()).await;
1719 client1
1720 .km_state_bridge()
1721 .register_bridge(Box::new(InMemoryStateBridge::default()));
1722
1723 PinLockSystem::with_client(&client1)
1724 .set_pin("1234".into(), PinLockType::BeforeFirstUnlock)
1725 .await
1726 .expect("set_pin succeeds");
1727
1728 let persistent_envelope = client1
1729 .km_state_bridge()
1730 .get_persistent_pin_envelope()
1731 .await
1732 .expect("persistent pin envelope present after BFU set_pin");
1733 let encrypted_pin = client1
1734 .km_state_bridge()
1735 .get_encrypted_pin()
1736 .await
1737 .expect("encrypted pin present after set_pin");
1738
1739 let client2 = Client::init_test_account(test_bitwarden_com_account()).await;
1741 client2
1742 .km_state_bridge()
1743 .register_bridge(Box::new(InMemoryStateBridge::default()));
1744 client2
1745 .km_state_bridge()
1746 .set_persistent_pin_envelope(&persistent_envelope)
1747 .await;
1748 client2
1749 .km_state_bridge()
1750 .set_encrypted_pin(&encrypted_pin)
1751 .await;
1752
1753 let client1_key = {
1754 let key_store = client1.internal.get_key_store();
1755 let ctx = key_store.context();
1756 #[allow(deprecated)]
1757 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1758 .unwrap()
1759 .to_owned()
1760 };
1761
1762 let client2_key = {
1763 let key_store = client2.internal.get_key_store();
1764 let ctx = key_store.context();
1765 #[allow(deprecated)]
1766 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1767 .unwrap()
1768 .to_owned()
1769 };
1770
1771 assert_eq!(client1_key, client2_key);
1772 }
1773
1774 #[test]
1775 fn test_enroll_admin_password_reset() {
1776 let client = Client::new(None);
1777
1778 let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap();
1779 let private_key = "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=".parse().unwrap();
1780 client
1781 .internal
1782 .initialize_user_crypto_master_password_unlock(
1783 "asdfasdfasdf".to_string(),
1784 MasterPasswordUnlockData {
1785 kdf: Kdf::PBKDF2 {
1786 iterations: NonZeroU32::new(600_000).unwrap(),
1787 },
1788 master_key_wrapped_user_key: user_key,
1789 salt: "[email protected]".to_string(),
1790 },
1791 WrappedAccountCryptographicState::V1 { private_key },
1792 &None,
1793 )
1794 .unwrap();
1795
1796 let public_key: B64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsy7RFHcX3C8Q4/OMmhhbFReYWfB45W9PDTEA8tUZwZmtOiN2RErIS2M1c+K/4HoDJ/TjpbX1f2MZcr4nWvKFuqnZXyewFc+jmvKVewYi+NAu2++vqKq2kKcmMNhwoQDQdQIVy/Uqlp4Cpi2cIwO6ogq5nHNJGR3jm+CpyrafYlbz1bPvL3hbyoGDuG2tgADhyhXUdFuef2oF3wMvn1lAJAvJnPYpMiXUFmj1ejmbwtlxZDrHgUJvUcp7nYdwUKaFoi+sOttHn3u7eZPtNvxMjhSS/X/1xBIzP/mKNLdywH5LoRxniokUk+fV3PYUxJsiU3lV0Trc/tH46jqd8ZGjmwIDAQAB".parse().unwrap();
1797
1798 let encrypted = enroll_admin_password_reset(&client, public_key).unwrap();
1799
1800 let private_key: B64 = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzLtEUdxfcLxDj84yaGFsVF5hZ8Hjlb08NMQDy1RnBma06I3ZESshLYzVz4r/gegMn9OOltfV/Yxlyvida8oW6qdlfJ7AVz6Oa8pV7BiL40C7b76+oqraQpyYw2HChANB1AhXL9SqWngKmLZwjA7qiCrmcc0kZHeOb4KnKtp9iVvPVs+8veFvKgYO4ba2AAOHKFdR0W55/agXfAy+fWUAkC8mc9ikyJdQWaPV6OZvC2XFkOseBQm9Rynudh3BQpoWiL6w620efe7t5k+02/EyOFJL9f/XEEjM/+Yo0t3LAfkuhHGeKiRST59Xc9hTEmyJTeVXROtz+0fjqOp3xkaObAgMBAAECggEACs4xhnO0HaZhh1/iH7zORMIRXKeyxP2LQiTR8xwN5JJ9wRWmGAR9VasS7EZFTDidIGVME2u/h4s5EqXnhxfO+0gGksVvgNXJ/qw87E8K2216g6ZNo6vSGA7H1GH2voWwejJ4/k/cJug6dz2S402rRAKh2Wong1arYHSkVlQp3diiMa5FHAOSE+Cy09O2ZsaF9IXQYUtlW6AVXFrBEPYH2kvkaPXchh8VETMijo6tbvoKLnUHe+wTaDMls7hy8exjtVyI59r3DNzjy1lNGaGb5QSnFMXR+eHhPZc844Wv02MxC15zKABADrl58gpJyjTl6XpDdHCYGsmGpVGH3X9TQQKBgQDz/9beFjzq59ve6rGwn+EtnQfSsyYT+jr7GN8lNEXb3YOFXBgPhfFIcHRh2R00Vm9w2ApfAx2cd8xm2I6HuvQ1Os7g26LWazvuWY0Qzb+KaCLQTEGH1RnTq6CCG+BTRq/a3J8M4t38GV5TWlzv8wr9U4dl6FR4efjb65HXs1GQ4QKBgQC7/uHfrOTEHrLeIeqEuSl0vWNqEotFKdKLV6xpOvNuxDGbgW4/r/zaxDqt0YBOXmRbQYSEhmO3oy9J6XfE1SUln0gbavZeW0HESCAmUIC88bDnspUwS9RxauqT5aF8ODKN/bNCWCnBM1xyonPOs1oT1nyparJVdQoG//Y7vkB3+wKBgBqLqPq8fKAp3XfhHLfUjREDVoiLyQa/YI9U42IOz9LdxKNLo6p8rgVthpvmnRDGnpUuS+KOWjhdqDVANjF6G3t3DG7WNl8Rh5Gk2H4NhFswfSkgQrjebFLlBy9gjQVCWXt8KSmjvPbiY6q52Aaa8IUjA0YJAregvXxfopxO+/7BAoGARicvEtDp7WWnSc1OPoj6N14VIxgYcI7SyrzE0d/1x3ffKzB5e7qomNpxKzvqrVP8DzG7ydh8jaKPmv1MfF8tpYRy3AhmN3/GYwCnPqT75YYrhcrWcVdax5gmQVqHkFtIQkRSCIftzPLlpMGKha/YBV8c1fvC4LD0NPh/Ynv0gtECgYEAyOZg95/kte0jpgUEgwuMrzkhY/AaUJULFuR5MkyvReEbtSBQwV5tx60+T95PHNiFooWWVXiLMsAgyI2IbkxVR1Pzdri3gWK5CTfqb7kLuaj/B7SGvBa2Sxo478KS5K8tBBBWkITqo+wLC0mn3uZi1dyMWO1zopTA+KtEGF2dtGQ=".parse().unwrap();
1801
1802 let private_key = Pkcs8PrivateKeyBytes::from(private_key.as_bytes());
1803 let private_key = PrivateKey::from_der(&private_key).unwrap();
1804 #[expect(deprecated)]
1805 let decrypted: SymmetricCryptoKey =
1806 encrypted.decapsulate_key_unsigned(&private_key).unwrap();
1807
1808 let key_store = client.internal.get_key_store();
1809 let ctx = key_store.context();
1810 #[allow(deprecated)]
1811 let expected = ctx
1812 .dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1813 .unwrap();
1814
1815 assert_eq!(decrypted, *expected);
1816 }
1817
1818 #[test]
1819 fn test_derive_key_connector() {
1820 let request = DeriveKeyConnectorRequest {
1821 password: "asdfasdfasdf".to_string(),
1822 email: "[email protected]".to_string(),
1823 kdf: Kdf::PBKDF2 {
1824 iterations: NonZeroU32::new(600_000).unwrap(),
1825 },
1826 user_key_encrypted: "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(),
1827 };
1828
1829 let result = derive_key_connector(request).unwrap();
1830
1831 assert_eq!(
1832 result.to_string(),
1833 "ySXq1RVLKEaV1eoQE/ui9aFKIvXTl9PAXwp1MljfF50="
1834 );
1835 }
1836
1837 fn setup_asymmetric_keys_test() -> (UserKey, RsaKeyPair) {
1838 let master_key = MasterKey::derive(
1839 "asdfasdfasdf",
1840 "[email protected]",
1841 &Kdf::PBKDF2 {
1842 iterations: NonZeroU32::new(600_000).unwrap(),
1843 },
1844 )
1845 .unwrap();
1846 let user_key = (master_key.make_user_key().unwrap()).0;
1847 let key_pair = user_key.make_key_pair().unwrap();
1848
1849 (user_key, key_pair)
1850 }
1851
1852 #[test]
1853 fn test_make_key_pair() {
1854 let (user_key, _) = setup_asymmetric_keys_test();
1855
1856 let response = make_key_pair(user_key.0.to_base64()).unwrap();
1857
1858 assert!(!response.user_public_key.to_string().is_empty());
1859 let encrypted_private_key = response.user_key_encrypted_private_key;
1860 let private_key: Vec<u8> = encrypted_private_key.decrypt_with_key(&user_key.0).unwrap();
1861 assert!(!private_key.is_empty());
1862 }
1863
1864 #[test]
1865 fn test_verify_asymmetric_keys_success() {
1866 let (user_key, key_pair) = setup_asymmetric_keys_test();
1867
1868 let request = VerifyAsymmetricKeysRequest {
1869 user_key: user_key.0.to_base64(),
1870 user_public_key: key_pair.public,
1871 user_key_encrypted_private_key: key_pair.private,
1872 };
1873 let response = verify_asymmetric_keys(request).unwrap();
1874
1875 assert!(response.private_key_decryptable);
1876 assert!(response.valid_private_key);
1877 }
1878
1879 #[test]
1880 fn test_verify_asymmetric_keys_decrypt_failed() {
1881 let (user_key, key_pair) = setup_asymmetric_keys_test();
1882 let undecryptable_private_key = "2.cqD39M4erPZ3tWaz2Fng9w==|+Bsp/xvM30oo+HThKN12qirK0A63EjMadcwethCX7kEgfL5nEXgAFsSgRBMpByc1djgpGDMXzUTLOE+FejXRsrEHH/ICZ7jPMgSR+lV64Mlvw3fgvDPQdJ6w3MCmjPueGQtrlPj1K78BkRomN3vQwwRBFUIJhLAnLshTOIFrSghoyG78na7McqVMMD0gmC0zmRaSs2YWu/46ES+2Rp8V5OC4qdeeoJM9MQfaOtmaqv7NRVDeDM3DwoyTJAOcon8eovMKE4jbFPUboiXjNQBkBgjvLhco3lVJnFcQuYgmjqrwuUQRsfAtZjxFXg/RQSH2D+SI5uRaTNQwkL4iJqIw7BIKtI0gxDz6eCVdq/+DLhpImgCV/aaIhF/jkpGqLCceFsYMbuqdULMM1VYKgV+IAuyC65R+wxOaKS+1IevvPnNp7tgKAvT5+shFg8piusj+rQ49daX2SmV2OImwdWMmmX93bcVV0xJ/WYB1yrqmyRUcTwyvX3RQF25P5okIIzFasRp8jXFZe8C6f93yzkn1TPQbp95zF4OsWjfPFVH4hzca07ACt2HjbAB75JakWbFA5MbCF8aOIwIfeLVhVlquQXCldOHCsl22U/f3HTGLB9OS8F83CDAy7qZqpKha9Im8RUhHoyf+lXrky0gyd6un7Ky8NSkVOGd8CEG7bvZfutxv/qtAjEM9/lV78fh8TQIy9GNgioMzplpuzPIJOgMaY/ZFZj6a8H9OMPneN5Je0H/DwHEglSyWy7CMgwcbQgXYGXc8rXTTxL71GUAFHzDr4bAJvf40YnjndoL9tf+oBw8vVNUccoD4cjyOT5w8h7M3Liaxk9/0O8JR98PKxxpv1Xw6XjFCSEHeG2y9FgDUASFR4ZwG1qQBiiLMnJ7e9kvxsdnmasBux9H0tOdhDhAM16Afk3NPPKA8eztJVHJBAfQiaNiUA4LIJ48d8EpUAe2Tvz0WW/gQThplUINDTpvPf+FojLwc5lFwNIPb4CVN1Ui8jOJI5nsOw4BSWJvLzJLxawHxX/sBuK96iXza+4aMH+FqYKt/twpTJtiVXo26sPtHe6xXtp7uO4b+bL9yYUcaAci69L0W8aNdu8iF0lVX6kFn2lOL8dBLRleGvixX9gYEVEsiI7BQBjxEBHW/YMr5F4M4smqCpleZIAxkse1r2fQ33BSOJVQKInt4zzgdKwrxDzuVR7RyiIUuNXHsprKtRHNJrSc4x5kWFUeivahed2hON+Ir/ZvrxYN6nJJPeYYH4uEm1Nn4osUzzfWILlqpmDPK1yYy365T38W8wT0cbdcJrI87ycS37HeB8bzpFJZSY/Dzv48Yy19mDZJHLJLCRqyxNeIlBPsVC8fvxQhzr+ZyS3Wi8Dsa2Sgjt/wd0xPULLCJlb37s+1aWgYYylr9QR1uhXheYfkXFED+saGWwY1jlYL5e2Oo9n3sviBYwJxIZ+RTKFgwlXV5S+Jx/MbDpgnVHP1KaoU6vvzdWYwMChdHV/6PhZVbeT2txq7Qt+zQN59IGrOWf6vlMkHxfUzMTD58CE+xAaz/D05ljHMesLj9hb3MSrymw0PcwoFGWUMIzIQE73pUVYNE7fVHa8HqUOdoxZ5dRZqXRVox1xd9siIPE3e6CuVQIMabTp1YLno=|Y38qtTuCwNLDqFnzJ3Cgbjm1SE15OnhDm9iAMABaQBA=".parse().unwrap();
1883
1884 let request = VerifyAsymmetricKeysRequest {
1885 user_key: user_key.0.to_base64(),
1886 user_public_key: key_pair.public,
1887 user_key_encrypted_private_key: undecryptable_private_key,
1888 };
1889 let response = verify_asymmetric_keys(request).unwrap();
1890
1891 assert!(!response.private_key_decryptable);
1892 assert!(!response.valid_private_key);
1893 }
1894
1895 #[test]
1896 fn test_verify_asymmetric_keys_parse_failed() {
1897 let (user_key, key_pair) = setup_asymmetric_keys_test();
1898
1899 let invalid_private_key = "bad_key".to_string().encrypt_with_key(&user_key.0).unwrap();
1900
1901 let request = VerifyAsymmetricKeysRequest {
1902 user_key: user_key.0.to_base64(),
1903 user_public_key: key_pair.public,
1904 user_key_encrypted_private_key: invalid_private_key,
1905 };
1906 let response = verify_asymmetric_keys(request).unwrap();
1907
1908 assert!(response.private_key_decryptable);
1909 assert!(!response.valid_private_key);
1910 }
1911
1912 #[test]
1913 fn test_verify_asymmetric_keys_key_mismatch() {
1914 let (user_key, key_pair) = setup_asymmetric_keys_test();
1915 let new_key_pair = user_key.make_key_pair().unwrap();
1916
1917 let request = VerifyAsymmetricKeysRequest {
1918 user_key: user_key.0.to_base64(),
1919 user_public_key: key_pair.public,
1920 user_key_encrypted_private_key: new_key_pair.private,
1921 };
1922 let response = verify_asymmetric_keys(request).unwrap();
1923
1924 assert!(response.private_key_decryptable);
1925 assert!(!response.valid_private_key);
1926 }
1927
1928 #[tokio::test]
1929 async fn test_make_v2_keys_for_v1_user() {
1930 let client = Client::new_test(None);
1931
1932 let priv_key: EncString = "2.kmLY8NJVuiKBFJtNd/ZFpA==|qOodlRXER+9ogCe3yOibRHmUcSNvjSKhdDuztLlucs10jLiNoVVVAc+9KfNErLSpx5wmUF1hBOJM8zwVPjgQTrmnNf/wuDpwiaCxNYb/0v4FygPy7ccAHK94xP1lfqq7U9+tv+/yiZSwgcT+xF0wFpoxQeNdNRFzPTuD9o4134n8bzacD9DV/WjcrXfRjbBCzzuUGj1e78+A7BWN7/5IWLz87KWk8G7O/W4+8PtEzlwkru6Wd1xO19GYU18oArCWCNoegSmcGn7w7NDEXlwD403oY8Oa7ylnbqGE28PVJx+HLPNIdSC6YKXeIOMnVs7Mctd/wXC93zGxAWD6ooTCzHSPVV50zKJmWIG2cVVUS7j35H3rGDtUHLI+ASXMEux9REZB8CdVOZMzp2wYeiOpggebJy6MKOZqPT1R3X0fqF2dHtRFPXrNsVr1Qt6bS9qTyO4ag1/BCvXF3P1uJEsI812BFAne3cYHy5bIOxuozPfipJrTb5WH35bxhElqwT3y/o/6JWOGg3HLDun31YmiZ2HScAsUAcEkA4hhoTNnqy4O2s3yVbCcR7jF7NLsbQc0MDTbnjxTdI4VnqUIn8s2c9hIJy/j80pmO9Bjxp+LQ9a2hUkfHgFhgHxZUVaeGVth8zG2kkgGdrp5VHhxMVFfvB26Ka6q6qE/UcS2lONSv+4T8niVRJz57qwctj8MNOkA3PTEfe/DP/LKMefke31YfT0xogHsLhDkx+mS8FCc01HReTjKLktk/Jh9mXwC5oKwueWWwlxI935ecn+3I2kAuOfMsgPLkoEBlwgiREC1pM7VVX1x8WmzIQVQTHd4iwnX96QewYckGRfNYWz/zwvWnjWlfcg8kRSe+68EHOGeRtC5r27fWLqRc0HNcjwpgHkI/b6czerCe8+07TWql4keJxJxhBYj3iOH7r9ZS8ck51XnOb8tGL1isimAJXodYGzakwktqHAD7MZhS+P02O+6jrg7d+yPC2ZCuS/3TOplYOCHQIhnZtR87PXTUwr83zfOwAwCyv6KP84JUQ45+DItrXLap7nOVZKQ5QxYIlbThAO6eima6Zu5XHfqGPMNWv0bLf5+vAjIa5np5DJrSwz9no/hj6CUh0iyI+SJq4RGI60lKtypMvF6MR3nHLEHOycRUQbZIyTHWl4QQLdHzuwN9lv10ouTEvNr6sFflAX2yb6w3hlCo7oBytH3rJekjb3IIOzBpeTPIejxzVlh0N9OT5MZdh4sNKYHUoWJ8mnfjdM+L4j5Q2Kgk/XiGDgEebkUxiEOQUdVpePF5uSCE+TPav/9FIRGXGiFn6NJMaU7aBsDTFBLloffFLYDpd8/bTwoSvifkj7buwLYM+h/qcnfdy5FWau1cKav+Blq/ZC0qBpo658RTC8ZtseAFDgXoQZuksM10hpP9bzD04Bx30xTGX81QbaSTNwSEEVrOtIhbDrj9OI43KH4O6zLzK+t30QxAv5zjk10RZ4+5SAdYndIlld9Y62opCfPDzRy3ubdve4ZEchpIKWTQvIxq3T5ogOhGaWBVYnkMtM2GVqvWV//46gET5SH/MdcwhACUcZ9kCpMnWH9CyyUwYvTT3UlNyV+DlS27LMPvaw7tx7qa+GfNCoCBd8S4esZpQYK/WReiS8=|pc7qpD42wxyXemdNPuwxbh8iIaryrBPu8f/DGwYdHTw=".parse().unwrap();
1933 let encrypted_userkey: EncString = "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap();
1934
1935 initialize_user_crypto(
1936 &client,
1937 InitUserCryptoRequest {
1938 user_id: Some(UserId::new_v4()),
1939 kdf_params: Kdf::PBKDF2 {
1940 iterations: 100_000.try_into().unwrap(),
1941 },
1942 email: "[email protected]".into(),
1943 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1944 private_key: priv_key.to_owned(),
1945 },
1946 method: InitUserCryptoMethod::MasterPasswordUnlock {
1947 password: "asdfasdfasdf".into(),
1948 master_password_unlock: MasterPasswordUnlockData {
1949 kdf: Kdf::PBKDF2 {
1950 iterations: 100_000.try_into().unwrap(),
1951 },
1952 master_key_wrapped_user_key: encrypted_userkey.clone(),
1953 salt: "[email protected]".into(),
1954 },
1955 },
1956 upgrade_token: None,
1957 },
1958 )
1959 .await
1960 .unwrap();
1961
1962 let master_key = MasterKey::derive(
1963 "asdfasdfasdf",
1964 "[email protected]",
1965 &Kdf::PBKDF2 {
1966 iterations: NonZeroU32::new(100_000).unwrap(),
1967 },
1968 )
1969 .unwrap();
1970 #[expect(deprecated)]
1971 let enrollment_response = make_v2_keys_for_v1_user(&client).unwrap();
1972 let encrypted_userkey_v2 = master_key
1973 .encrypt_user_key(
1974 &SymmetricCryptoKey::try_from(enrollment_response.clone().user_key).unwrap(),
1975 )
1976 .unwrap();
1977
1978 let client2 = Client::new_test(None);
1979
1980 initialize_user_crypto(
1981 &client2,
1982 InitUserCryptoRequest {
1983 user_id: Some(UserId::new_v4()),
1984 kdf_params: Kdf::PBKDF2 {
1985 iterations: 100_000.try_into().unwrap(),
1986 },
1987 email: "[email protected]".into(),
1988 account_cryptographic_state: WrappedAccountCryptographicState::V2 {
1989 private_key: enrollment_response.private_key,
1990 signing_key: enrollment_response.signing_key,
1991 security_state: enrollment_response.security_state,
1992 signed_public_key: Some(enrollment_response.signed_public_key),
1993 },
1994 method: InitUserCryptoMethod::MasterPasswordUnlock {
1995 password: "asdfasdfasdf".into(),
1996 master_password_unlock: MasterPasswordUnlockData {
1997 kdf: Kdf::PBKDF2 {
1998 iterations: 100_000.try_into().unwrap(),
1999 },
2000 master_key_wrapped_user_key: encrypted_userkey_v2,
2001 salt: "[email protected]".to_string(),
2002 },
2003 },
2004 upgrade_token: None,
2005 },
2006 )
2007 .await
2008 .unwrap();
2009 }
2010
2011 #[tokio::test]
2012 async fn test_make_v2_keys_for_v1_user_with_v2_user_fails() {
2013 let client = Client::new_test(None);
2014
2015 initialize_user_crypto(
2016 &client,
2017 InitUserCryptoRequest {
2018 user_id: Some(UserId::new_v4()),
2019 kdf_params: Kdf::PBKDF2 {
2020 iterations: 100_000.try_into().unwrap(),
2021 },
2022 email: "[email protected]".into(),
2023 account_cryptographic_state: WrappedAccountCryptographicState::V2 {
2024 private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
2025 signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
2026 security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
2027 signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
2028 },
2029 method: InitUserCryptoMethod::DecryptedKey {
2030 decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
2031 },
2032 upgrade_token: None,
2033 },
2034 )
2035 .await
2036 .unwrap();
2037
2038 #[expect(deprecated)]
2039 let result = make_v2_keys_for_v1_user(&client);
2040 assert!(matches!(
2041 result,
2042 Err(StatefulCryptoError::WrongAccountCryptoVersion {
2043 expected: _,
2044 got: _
2045 })
2046 ));
2047 }
2048
2049 #[test]
2050 fn test_get_v2_rotated_account_keys_non_v2_user() {
2051 let client = Client::new(None);
2052 let mut ctx = client.internal.get_key_store().context_mut();
2053 let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
2054 ctx.persist_symmetric_key(local_key_id, SymmetricKeySlotId::User)
2055 .unwrap();
2056 drop(ctx);
2057
2058 #[expect(deprecated)]
2059 let result = get_v2_rotated_account_keys(&client);
2060 assert!(matches!(
2061 result,
2062 Err(StatefulCryptoError::WrongAccountCryptoVersion {
2063 expected: _,
2064 got: _
2065 })
2066 ));
2067 }
2068
2069 #[tokio::test]
2070 async fn test_get_v2_rotated_account_keys() {
2071 let client = Client::new_test(None);
2072
2073 initialize_user_crypto(
2074 &client,
2075 InitUserCryptoRequest {
2076 user_id: Some(UserId::new_v4()),
2077 kdf_params: Kdf::PBKDF2 {
2078 iterations: 100_000.try_into().unwrap(),
2079 },
2080 email: "[email protected]".into(),
2081 account_cryptographic_state: WrappedAccountCryptographicState::V2 {
2082 private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
2083 signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
2084 security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
2085 signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
2086 },
2087 method: InitUserCryptoMethod::DecryptedKey {
2088 decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
2089 },
2090 upgrade_token: None,
2091 },
2092 )
2093 .await
2094 .unwrap();
2095
2096 #[expect(deprecated)]
2097 let result = get_v2_rotated_account_keys(&client);
2098 assert!(result.is_ok());
2099 }
2100
2101 #[tokio::test]
2102 async fn test_initialize_user_crypto_master_password_unlock() {
2103 let client = Client::new_test(None);
2104
2105 initialize_user_crypto(
2106 &client,
2107 InitUserCryptoRequest {
2108 user_id: Some(UserId::new_v4()),
2109 kdf_params: Kdf::PBKDF2 {
2110 iterations: 600_000.try_into().unwrap(),
2111 },
2112 email: TEST_USER_EMAIL.to_string(),
2113 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
2114 private_key: TEST_ACCOUNT_PRIVATE_KEY.parse().unwrap(),
2115 },
2116 method: InitUserCryptoMethod::MasterPasswordUnlock {
2117 password: TEST_USER_PASSWORD.to_string(),
2118 master_password_unlock: MasterPasswordUnlockData {
2119 kdf: Kdf::PBKDF2 {
2120 iterations: 600_000.try_into().unwrap(),
2121 },
2122 master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
2123 salt: TEST_USER_EMAIL.to_string(),
2124 },
2125 },
2126 upgrade_token: None,
2127 },
2128 )
2129 .await
2130 .unwrap();
2131
2132 let key_store = client.internal.get_key_store();
2133 {
2134 let context = key_store.context();
2135 assert!(context.has_symmetric_key(SymmetricKeySlotId::User));
2136 assert!(context.has_private_key(PrivateKeySlotId::UserPrivateKey));
2137 }
2138 let login_method = client.internal.get_login_method().await.unwrap();
2139 if let UserLoginMethod::Username {
2140 email,
2141 kdf,
2142 client_id,
2143 ..
2144 } = login_method
2145 {
2146 assert_eq!(&email, TEST_USER_EMAIL);
2147 assert_eq!(
2148 kdf,
2149 Kdf::PBKDF2 {
2150 iterations: 600_000.try_into().unwrap(),
2151 }
2152 );
2153 assert_eq!(&client_id, "");
2154 } else {
2155 panic!("Expected username login method");
2156 }
2157 }
2158
2159 #[tokio::test]
2160 async fn test_make_user_tde_registration() {
2161 let user_id = UserId::new_v4();
2162 let email = "[email protected]";
2163 let kdf = Kdf::PBKDF2 {
2164 iterations: NonZeroU32::new(600_000).expect("valid iteration count"),
2165 };
2166
2167 let org_key = PrivateKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
2169 let org_public_key_der = org_key
2170 .to_public_key()
2171 .to_der()
2172 .expect("valid public key DER");
2173 let org_public_key = B64::from(org_public_key_der.as_ref().to_vec());
2174
2175 let registration_client = Client::new_test(None);
2177 let make_keys_response = registration_client
2178 .crypto()
2179 .make_user_tde_registration(org_public_key)
2180 .expect("TDE registration should succeed");
2181
2182 let unlock_client = Client::new_test(None);
2184 unlock_client
2185 .crypto()
2186 .initialize_user_crypto(InitUserCryptoRequest {
2187 user_id: Some(user_id),
2188 kdf_params: kdf,
2189 email: email.to_owned(),
2190 account_cryptographic_state: make_keys_response.account_cryptographic_state,
2191 method: InitUserCryptoMethod::DeviceKey {
2192 device_key: make_keys_response
2193 .trusted_device_keys
2194 .device_key
2195 .to_string(),
2196 protected_device_private_key: make_keys_response
2197 .trusted_device_keys
2198 .protected_device_private_key,
2199 device_protected_user_key: make_keys_response
2200 .trusted_device_keys
2201 .protected_user_key,
2202 },
2203 upgrade_token: None,
2204 })
2205 .await
2206 .expect("initializing user crypto with TDE device key should succeed");
2207
2208 let retrieved_key = unlock_client
2210 .crypto()
2211 .get_user_encryption_key()
2212 .await
2213 .expect("should be able to get user encryption key");
2214
2215 let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
2217 .expect("retrieved key should be valid symmetric key");
2218
2219 #[expect(deprecated)]
2222 let decrypted_user_key = make_keys_response
2223 .reset_password_key
2224 .decapsulate_key_unsigned(&org_key)
2225 .expect("org key should be able to decrypt admin reset key");
2226 assert_eq!(
2227 retrieved_symmetric_key, decrypted_user_key,
2228 "decrypted admin reset key should match the user's encryption key"
2229 );
2230 }
2231
2232 #[tokio::test]
2233 async fn test_make_user_key_connector_registration_success() {
2234 let user_id = UserId::new_v4();
2235 let email = "[email protected]";
2236 let registration_client = Client::new(None);
2237
2238 let make_keys_response = make_user_key_connector_registration(®istration_client);
2239 assert!(make_keys_response.is_ok());
2240 let make_keys_response = make_keys_response.unwrap();
2241
2242 let unlock_client = Client::new_test(None);
2244 unlock_client
2245 .crypto()
2246 .initialize_user_crypto(InitUserCryptoRequest {
2247 user_id: Some(user_id),
2248 kdf_params: Kdf::default_argon2(),
2249 email: email.to_owned(),
2250 account_cryptographic_state: make_keys_response.account_cryptographic_state,
2251 method: InitUserCryptoMethod::KeyConnector {
2252 user_key: make_keys_response
2253 .key_connector_key_wrapped_user_key
2254 .clone(),
2255 master_key: make_keys_response.key_connector_key.clone().into(),
2256 },
2257 upgrade_token: None,
2258 })
2259 .await
2260 .expect("initializing user crypto with key connector key should succeed");
2261
2262 let retrieved_key = unlock_client
2264 .crypto()
2265 .get_user_encryption_key()
2266 .await
2267 .expect("should be able to get user encryption key");
2268
2269 let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
2271 .expect("retrieved key should be valid symmetric key");
2272
2273 assert_eq!(retrieved_symmetric_key, make_keys_response.user_key);
2274
2275 let decrypted_user_key = make_keys_response
2276 .key_connector_key
2277 .decrypt_user_key(make_keys_response.key_connector_key_wrapped_user_key);
2278 assert_eq!(retrieved_symmetric_key, decrypted_user_key.unwrap());
2279 }
2280
2281 #[tokio::test]
2282 async fn test_initialize_user_crypto_with_upgrade_token_upgrades_v1_to_v2() {
2283 let client1 = Client::init_test_account(test_bitwarden_com_account()).await;
2284
2285 let expected_v2_key =
2286 SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
2287 let upgrade_token = {
2288 let mut ctx = client1.internal.get_key_store().context_mut();
2289 let v2_key_id = ctx.add_local_symmetric_key(expected_v2_key.clone());
2290 V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap()
2291 };
2292
2293 let client2 = Client::new_test(None);
2294 init_v2_account_with_master_password_and_upgrade_token(
2295 &client2,
2296 UserId::new_v4(),
2297 upgrade_token,
2298 )
2299 .await;
2300
2301 let result_key =
2303 SymmetricCryptoKey::try_from(get_user_encryption_key(&client2).await.unwrap()).unwrap();
2304 assert!(
2305 matches!(result_key, SymmetricCryptoKey::XChaCha20Poly1305Key(_)),
2306 "User key should be upgraded to V2 after initialization with upgrade token"
2307 );
2308 assert_eq!(result_key, expected_v2_key);
2309 }
2310
2311 #[tokio::test]
2312 async fn test_initialize_user_crypto_with_upgrade_token_ignored_for_v2_key() {
2313 let dummy_token = {
2314 let key_store = KeyStore::<KeySlotIds>::default();
2315 let mut ctx = key_store.context_mut();
2316 let v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
2317 let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
2318 V2UpgradeToken::create(v1_id, v2_id, &ctx).unwrap()
2319 };
2320
2321 let client = Client::new_test(None);
2322 initialize_user_crypto(
2323 &client,
2324 InitUserCryptoRequest {
2325 user_id: Some(UserId::new_v4()),
2326 kdf_params: Kdf::PBKDF2 {
2327 iterations: 100_000.try_into().unwrap(),
2328 },
2329 email: "[email protected]".into(),
2330 account_cryptographic_state: WrappedAccountCryptographicState::V2 {
2331 private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
2332 signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
2333 security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
2334 signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
2335 },
2336 method: InitUserCryptoMethod::DecryptedKey {
2337 decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
2338 },
2339 upgrade_token: Some(dummy_token),
2340 },
2341 )
2342 .await
2343 .unwrap();
2344
2345 let result_key =
2347 SymmetricCryptoKey::try_from(get_user_encryption_key(&client).await.unwrap()).unwrap();
2348 assert!(
2349 matches!(result_key, SymmetricCryptoKey::XChaCha20Poly1305Key(_)),
2350 "Upgrade token must be ignored for a V2 user key"
2351 );
2352 let expected_key =
2353 SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
2354 assert_eq!(result_key, expected_key);
2355 }
2356
2357 #[tokio::test]
2358 async fn test_initialize_user_crypto_with_invalid_upgrade_token_fails() {
2359 let mismatched_token = {
2361 let key_store = KeyStore::<KeySlotIds>::default();
2362 let mut ctx = key_store.context_mut();
2363 let wrong_v1_id = ctx.generate_symmetric_key();
2364 let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
2365 V2UpgradeToken::create(wrong_v1_id, v2_id, &ctx).unwrap()
2366 };
2367
2368 let client = Client::new_test(None);
2369 let result = initialize_user_crypto(
2370 &client,
2371 InitUserCryptoRequest {
2372 user_id: Some(UserId::new_v4()),
2373 kdf_params: Kdf::PBKDF2 {
2374 iterations: 600_000.try_into().unwrap(),
2375 },
2376 email: "[email protected]".into(),
2377 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
2378 private_key: TEST_ACCOUNT_PRIVATE_KEY.parse().unwrap(),
2380 },
2381 method: InitUserCryptoMethod::MasterPasswordUnlock {
2382 password: "asdfasdfasdf".into(),
2383 master_password_unlock: MasterPasswordUnlockData {
2384 kdf: Kdf::PBKDF2 {
2385 iterations: 600_000.try_into().unwrap(),
2386 },
2387 master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
2388 salt: "[email protected]".to_string(),
2389 },
2390 },
2391 upgrade_token: Some(mismatched_token),
2392 },
2393 )
2394 .await;
2395
2396 assert!(
2397 matches!(result, Err(EncryptionSettingsError::InvalidUpgradeToken)),
2398 "Initialization with a mismatched upgrade token should fail"
2399 );
2400 }
2401
2402 #[tokio::test]
2403 async fn test_initialize_user_local_data_key_sets_local_user_data_key_equal_to_user_key() {
2404 let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
2405 initialize_user_local_data_key(&client)
2406 .await
2407 .expect("initialize_user_local_data_key should succeed");
2408
2409 let key_store = client.internal.get_key_store();
2412 let mut ctx = key_store.context_mut();
2413 let plaintext = "test";
2414 let ciphertext = plaintext
2415 .encrypt(&mut ctx, SymmetricKeySlotId::User)
2416 .expect("encryption with user key should succeed");
2417 let decrypted: String = ciphertext
2418 .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2419 .expect("decryption with local user data key should succeed");
2420 assert_eq!(decrypted, plaintext);
2421 }
2422
2423 #[tokio::test]
2424 async fn test_initialize_org_crypto_persists_org_keys() {
2425 use crate::{OrganizationId, client::persisted_state::OrganizationSharedKey};
2426
2427 let client = Client::init_test_account(test_bitwarden_com_account()).await;
2428
2429 let org_id: OrganizationId = "1bc9ac1e-f5aa-45f2-94bf-b181009709b8".parse().unwrap();
2430
2431 let repo = client
2432 .internal
2433 .state_registry
2434 .get::<OrganizationSharedKey>()
2435 .expect("OrganizationSharedKey repository should be available");
2436
2437 let persisted = repo
2438 .get(org_id)
2439 .await
2440 .expect("repository get should not fail");
2441
2442 let entry = persisted.expect("org key should be persisted after initialize_org_crypto");
2443 assert_eq!(entry.org_id, org_id);
2444 }
2445
2446 #[tokio::test]
2447 async fn test_initialize_user_crypto_persists_account_crypto_state() {
2448 use crate::client::persisted_state::ACCOUNT_CRYPTO_STATE;
2449
2450 let account_crypto_state = WrappedAccountCryptographicState::V1 {
2451 private_key: TEST_ACCOUNT_PRIVATE_KEY.parse().unwrap(),
2452 };
2453
2454 let client = Client::new_test(None);
2455 initialize_user_crypto(
2456 &client,
2457 InitUserCryptoRequest {
2458 user_id: Some(UserId::new_v4()),
2459 kdf_params: Kdf::PBKDF2 {
2460 iterations: 600_000.try_into().unwrap(),
2461 },
2462 email: TEST_USER_EMAIL.into(),
2463 account_cryptographic_state: account_crypto_state.clone(),
2464 method: InitUserCryptoMethod::MasterPasswordUnlock {
2465 password: TEST_USER_PASSWORD.into(),
2466 master_password_unlock: MasterPasswordUnlockData {
2467 kdf: Kdf::PBKDF2 {
2468 iterations: 600_000.try_into().unwrap(),
2469 },
2470 master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
2471 salt: TEST_USER_EMAIL.to_string(),
2472 },
2473 },
2474 upgrade_token: None,
2475 },
2476 )
2477 .await
2478 .unwrap();
2479
2480 let persisted = client
2481 .internal
2482 .state_registry
2483 .setting(ACCOUNT_CRYPTO_STATE)
2484 .expect("ACCOUNT_CRYPTO_STATE setting should be available")
2485 .get()
2486 .await
2487 .expect("setting get should not fail");
2488
2489 assert_eq!(persisted, Some(account_crypto_state));
2490 }
2491
2492 #[tokio::test]
2493 async fn test_initialize_user_local_data_key_idempotent() {
2494 let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
2495 initialize_user_local_data_key(&client)
2496 .await
2497 .expect("first initialization should succeed");
2498
2499 let ciphertext = {
2501 let key_store = client.internal.get_key_store();
2502 let mut ctx = key_store.context_mut();
2503 "test"
2504 .encrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2505 .expect("encryption should succeed")
2506 };
2507
2508 initialize_user_local_data_key(&client)
2509 .await
2510 .expect("second initialization should succeed");
2511
2512 let key_store = client.internal.get_key_store();
2515 let mut ctx = key_store.context_mut();
2516 let decrypted: String = ciphertext
2517 .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2518 .expect("decryption after second initialization should succeed");
2519 assert_eq!(decrypted, "test");
2520 }
2521
2522 #[tokio::test]
2523 async fn test_initialize_user_crypto_rewraps_local_user_data_key_on_v1_to_v2_upgrade() {
2524 use crate::key_management::LocalUserDataKeyState;
2525
2526 let client_v1 = Client::init_test_account(test_bitwarden_com_account()).await;
2528 let user_id = UserId::new(uuid::uuid!("060000fb-0922-4dd3-b170-6e15cb5df8c8"));
2529
2530 let v1_user_data_key = client_v1
2531 .platform()
2532 .state()
2533 .get::<LocalUserDataKeyState>()
2534 .unwrap()
2535 .get(user_id)
2536 .await
2537 .unwrap()
2538 .expect("V1 init should plant a LocalUserDataKey state");
2539 assert!(
2540 matches!(
2541 v1_user_data_key.wrapped_key,
2542 EncString::Aes256Cbc_HmacSha256_B64 { .. }
2543 ),
2544 "Initial local user data key should use be wrapped with a V1 user key"
2545 );
2546
2547 let ciphertext = {
2549 let mut ctx = client_v1.internal.get_key_store().context_mut();
2550 "preserved data"
2551 .encrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2552 .unwrap()
2553 };
2554
2555 let v2_key = SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
2557 let upgrade_token = {
2558 let mut ctx = client_v1.internal.get_key_store().context_mut();
2559 let v2_key_id = ctx.add_local_symmetric_key(v2_key.clone());
2560 V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap()
2561 };
2562
2563 let client_v2 = Client::new_test(None);
2565 let repo = client_v2
2566 .platform()
2567 .state()
2568 .get::<LocalUserDataKeyState>()
2569 .unwrap();
2570 repo.set(user_id, v1_user_data_key.clone()).await.unwrap();
2571 client_v2
2572 .km_state_bridge()
2573 .register_bridge(Box::new(InMemoryStateBridge::default()));
2574 client_v2
2575 .km_state_bridge()
2576 .set_v2_upgrade_token(&upgrade_token.clone())
2577 .await;
2578
2579 init_v2_account_with_master_password_and_upgrade_token(&client_v2, user_id, upgrade_token)
2580 .await;
2581
2582 let rewrapped_state = repo
2584 .get(user_id)
2585 .await
2586 .unwrap()
2587 .expect("LocalUserDataKey state must remain present");
2588 assert!(
2589 matches!(
2590 rewrapped_state.wrapped_key,
2591 EncString::Cose_Encrypt0_B64 { .. }
2592 ),
2593 "Rewrapped key should be sealed with the V2 user key"
2594 );
2595 assert_ne!(rewrapped_state.wrapped_key, v1_user_data_key.wrapped_key);
2596
2597 let mut ctx = client_v2.internal.get_key_store().context_mut();
2599 let decrypted: String = ciphertext
2600 .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2601 .expect("data encrypted before the upgrade should decrypt after rewrap");
2602 assert_eq!(decrypted, "preserved data");
2603 }
2604
2605 #[tokio::test]
2606 async fn test_initialize_user_crypto_creates_new_local_user_data_key_with_upgrade_token_and_no_existing_state()
2607 {
2608 use crate::key_management::LocalUserDataKeyState;
2609
2610 let helper = Client::init_test_account(test_bitwarden_com_account()).await;
2612 let v2_key = SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
2613 let upgrade_token = {
2614 let mut ctx = helper.internal.get_key_store().context_mut();
2615 let v2_key_id = ctx.add_local_symmetric_key(v2_key.clone());
2616 V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap()
2617 };
2618
2619 let user_id = UserId::new_v4();
2621 let client = Client::new_test(None);
2622 client
2623 .km_state_bridge()
2624 .register_bridge(Box::new(InMemoryStateBridge::default()));
2625 client
2626 .km_state_bridge()
2627 .set_v2_upgrade_token(&upgrade_token.clone())
2628 .await;
2629
2630 init_v2_account_with_master_password_and_upgrade_token(&client, user_id, upgrade_token)
2631 .await;
2632
2633 let new_state = client
2635 .platform()
2636 .state()
2637 .get::<LocalUserDataKeyState>()
2638 .unwrap()
2639 .get(user_id)
2640 .await
2641 .unwrap()
2642 .expect("LocalUserDataKey should be created on init");
2643 assert!(matches!(
2644 new_state.wrapped_key,
2645 EncString::Cose_Encrypt0_B64 { .. }
2646 ));
2647 }
2648
2649 #[tokio::test]
2650 async fn test_initialize_user_crypto_leaves_local_user_data_key_unchanged_without_upgrade_token()
2651 {
2652 use crate::key_management::LocalUserDataKeyState;
2653
2654 let client = Client::init_test_account(test_bitwarden_com_account()).await;
2656 let user_id = UserId::new(uuid::uuid!("060000fb-0922-4dd3-b170-6e15cb5df8c8"));
2657 client
2658 .km_state_bridge()
2659 .register_bridge(Box::new(InMemoryStateBridge::default()));
2660
2661 let repo = client
2662 .platform()
2663 .state()
2664 .get::<LocalUserDataKeyState>()
2665 .unwrap();
2666 let before = repo.get(user_id).await.unwrap().unwrap();
2667
2668 initialize_local_user_data_key_into_state(&client, user_id)
2670 .await
2671 .map_err(|_| "should succeed")
2672 .unwrap();
2673
2674 let after = repo.get(user_id).await.unwrap().unwrap();
2675 assert_eq!(
2676 after.wrapped_key, before.wrapped_key,
2677 "without an upgrade token the wrapped key must not change"
2678 );
2679 }
2680
2681 #[tokio::test]
2682 async fn test_initialize_user_crypto_does_not_rewrap_when_already_v2() {
2683 use crate::key_management::LocalUserDataKeyState;
2684
2685 let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
2687 let user_id = UserId::new(uuid::uuid!("060000fb-0922-4dd3-b170-6e15cb5df8c8"));
2688 client
2689 .km_state_bridge()
2690 .register_bridge(Box::new(InMemoryStateBridge::default()));
2691
2692 let repo = client
2693 .platform()
2694 .state()
2695 .get::<LocalUserDataKeyState>()
2696 .unwrap();
2697 let before = repo.get(user_id).await.unwrap().unwrap();
2698 assert!(matches!(
2699 before.wrapped_key,
2700 EncString::Cose_Encrypt0_B64 { .. }
2701 ));
2702
2703 migrate_local_user_data_key_for_user_key_upgrade(&client, user_id)
2704 .await
2705 .map_err(|_| "should succeed")
2706 .unwrap();
2707
2708 let after = repo.get(user_id).await.unwrap().unwrap();
2709 assert_eq!(
2710 after.wrapped_key, before.wrapped_key,
2711 "an already-V2-wrapped key must not be rewrapped"
2712 );
2713 }
2714
2715 #[tokio::test]
2716 async fn test_make_user_password_registration() {
2717 let user_id = UserId::new_v4();
2718 let registration_client = Client::new(None);
2719
2720 let make_keys_response = registration_client
2721 .crypto()
2722 .make_user_password_registration(
2723 TEST_USER_PASSWORD.to_string(),
2724 TEST_USER_EMAIL.to_string(),
2725 )
2726 .expect("user password registration should succeed");
2727
2728 let unlock_client = Client::new_test(None);
2729 unlock_client
2730 .crypto()
2731 .initialize_user_crypto(InitUserCryptoRequest {
2732 user_id: Some(user_id),
2733 kdf_params: Kdf::default_argon2(),
2734 email: TEST_USER_EMAIL.to_string(),
2735 account_cryptographic_state: make_keys_response.account_cryptographic_state,
2736 method: InitUserCryptoMethod::MasterPasswordUnlock {
2737 password: TEST_USER_PASSWORD.to_string(),
2738 master_password_unlock: make_keys_response.master_password_unlock_data.clone(),
2739 },
2740 upgrade_token: None,
2741 })
2742 .await
2743 .expect("initializing user crypto with master password should succeed");
2744
2745 let retrieved_key = unlock_client
2746 .crypto()
2747 .get_user_encryption_key()
2748 .await
2749 .expect("should be able to get user encryption key");
2750
2751 let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
2752 .expect("retrieved key should be valid symmetric key");
2753
2754 let master_key = MasterKey::derive(
2755 TEST_USER_PASSWORD,
2756 TEST_USER_EMAIL,
2757 &make_keys_response.master_password_unlock_data.kdf,
2758 )
2759 .expect("master key should derive");
2760
2761 let decrypted_user_key = master_key
2762 .decrypt_user_key(
2763 make_keys_response
2764 .master_password_unlock_data
2765 .master_key_wrapped_user_key
2766 .clone(),
2767 )
2768 .expect("should decrypt user key");
2769
2770 assert_eq!(retrieved_symmetric_key, decrypted_user_key);
2771 }
2772}