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 },
49 master_password::{MasterPasswordAuthenticationData, MasterPasswordUnlockData},
50 pin_lock_system::{PinLockSystem, UnlockError},
51 },
52};
53
54#[allow(missing_docs)]
56#[bitwarden_error(flat)]
57#[derive(Debug, thiserror::Error)]
58pub enum CryptoClientError {
59 #[error(transparent)]
60 NotAuthenticated(#[from] NotAuthenticatedError),
61 #[error(transparent)]
62 Crypto(#[from] bitwarden_crypto::CryptoError),
63 #[error("Invalid KDF settings")]
64 InvalidKdfSettings,
65 #[error(transparent)]
66 PasswordProtectedKeyEnvelope(#[from] PasswordProtectedKeyEnvelopeError),
67 #[error("Invalid PRF input")]
68 InvalidPrfInput,
69 #[error("Invalid upgrade token")]
70 InvalidUpgradeToken,
71 #[error("Upgrade token is required for V1 keys")]
72 UpgradeTokenRequired,
73 #[error("Invalid key type")]
74 InvalidKeyType,
75}
76
77#[derive(Serialize, Deserialize, Debug)]
79#[serde(rename_all = "camelCase", deny_unknown_fields)]
80#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
81#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
82pub struct InitUserCryptoRequest {
83 pub user_id: Option<UserId>,
85 pub kdf_params: Kdf,
87 pub email: String,
89 pub account_cryptographic_state: WrappedAccountCryptographicState,
92 pub method: InitUserCryptoMethod,
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub upgrade_token: Option<V2UpgradeToken>,
97}
98
99#[derive(Serialize, Deserialize, Debug)]
101#[serde(rename_all = "camelCase", deny_unknown_fields)]
102#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
103#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
104#[allow(clippy::large_enum_variant)]
105pub enum InitUserCryptoMethod {
106 MasterPasswordUnlock {
108 password: String,
110 master_password_unlock: MasterPasswordUnlockData,
112 },
113 #[cfg(feature = "wasm")]
117 ClientManagedState {},
118 DecryptedKey {
120 decrypted_user_key: String,
122 },
123 Pin {
125 pin: String,
127 pin_protected_user_key: EncString,
130 },
131 PinState {
133 pin: String,
135 },
136 PinEnvelope {
138 pin: String,
140 pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
142 },
143 AuthRequest {
145 request_private_key: B64,
147 method: AuthRequestMethod,
149 },
150 DeviceKey {
152 device_key: String,
154 protected_device_private_key: EncString,
156 device_protected_user_key: UnsignedSharedKey,
158 },
159 KeyConnector {
161 master_key: B64,
163 user_key: EncString,
165 },
166 KeyConnectorUrl {
168 url: String,
170 key_connector_key_wrapped_user_key: EncString,
172 },
173}
174
175#[derive(Serialize, Deserialize, Debug)]
177#[serde(rename_all = "camelCase", deny_unknown_fields)]
178#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
179#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
180pub enum AuthRequestMethod {
181 UserKey {
183 protected_user_key: UnsignedSharedKey,
185 },
186 MasterKey {
188 protected_master_key: UnsignedSharedKey,
190 auth_request_key: EncString,
192 },
193}
194
195#[tracing::instrument(skip_all, err)]
197pub(super) async fn initialize_user_crypto(
198 client: &Client,
199 req: InitUserCryptoRequest,
200) -> Result<(), EncryptionSettingsError> {
201 use bitwarden_crypto::{DeviceKey, PinKey};
202
203 use crate::auth::{auth_request_decrypt_master_key, auth_request_decrypt_user_key};
204
205 if let Some(user_id) = req.user_id {
206 client.internal.init_user_id(user_id).await?;
207 }
208
209 tracing::Span::current().record(
210 "user_id",
211 client.internal.get_user_id().map(|id| id.to_string()),
212 );
213
214 let account_crypto_state = req.account_cryptographic_state.to_owned();
215
216 #[cfg(feature = "wasm")]
217 let should_copy_user_key = matches!(
218 req.method,
219 InitUserCryptoMethod::MasterPasswordUnlock { .. }
220 | InitUserCryptoMethod::DecryptedKey { .. }
221 | InitUserCryptoMethod::PinEnvelope { .. }
222 | InitUserCryptoMethod::PinState { .. }
223 | InitUserCryptoMethod::KeyConnectorUrl { .. }
224 | InitUserCryptoMethod::AuthRequest { .. }
225 );
226
227 match req.method {
228 InitUserCryptoMethod::MasterPasswordUnlock {
229 password,
230 master_password_unlock,
231 } => {
232 client
233 .internal
234 .initialize_user_crypto_master_password_unlock(
235 password,
236 master_password_unlock,
237 account_crypto_state,
238 &req.upgrade_token,
239 )?;
240 }
241 #[cfg(feature = "wasm")]
242 InitUserCryptoMethod::ClientManagedState {} => {
243 let user_key = get_user_key_from_client_managed_state(client)
244 .await
245 .map_err(|_| EncryptionSettingsError::UserKeyStateRetrievalFailed)?;
246 client.internal.initialize_user_crypto_decrypted_key(
247 user_key,
248 account_crypto_state,
249 &req.upgrade_token,
250 )?;
251 }
252 InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => {
253 let user_key = SymmetricCryptoKey::try_from(decrypted_user_key)?;
254 client.internal.initialize_user_crypto_decrypted_key(
255 user_key,
256 account_crypto_state,
257 &req.upgrade_token,
258 )?;
259 }
260 InitUserCryptoMethod::Pin {
261 pin,
262 pin_protected_user_key,
263 } => {
264 let pin_key = PinKey::derive(pin.as_bytes(), req.email.as_bytes(), &req.kdf_params)?;
265 client.internal.initialize_user_crypto_pin(
266 pin_key,
267 pin_protected_user_key,
268 account_crypto_state,
269 &req.upgrade_token,
270 )?;
271 }
272 InitUserCryptoMethod::PinEnvelope {
273 pin,
274 pin_protected_user_key_envelope,
275 } => {
276 client.internal.initialize_user_crypto_pin_envelope(
277 pin,
278 pin_protected_user_key_envelope,
279 account_crypto_state,
280 &req.upgrade_token,
281 )?;
282 }
283 InitUserCryptoMethod::PinState { pin } => {
284 PinLockSystem::with_client(client)
285 .unlock(pin.as_str())
286 .await
287 .map_err(|err| match err {
288 UnlockError::PinWrong => EncryptionSettingsError::WrongPin,
289 _ => EncryptionSettingsError::CryptoInitialization,
290 })?;
291 #[allow(deprecated)]
295 let user_key = client
296 .internal
297 .get_key_store()
298 .context()
299 .dangerous_get_symmetric_key(SymmetricKeySlotId::User)?
300 .to_owned();
301 client.internal.initialize_user_crypto_decrypted_key(
302 user_key,
303 account_crypto_state,
304 &req.upgrade_token,
305 )?;
306 }
307 InitUserCryptoMethod::AuthRequest {
308 request_private_key,
309 method,
310 } => {
311 let user_key = match method {
312 AuthRequestMethod::UserKey { protected_user_key } => {
313 auth_request_decrypt_user_key(request_private_key, protected_user_key)?
314 }
315 AuthRequestMethod::MasterKey {
316 protected_master_key,
317 auth_request_key,
318 } => auth_request_decrypt_master_key(
319 request_private_key,
320 protected_master_key,
321 auth_request_key,
322 )?,
323 };
324 client.internal.initialize_user_crypto_decrypted_key(
325 user_key,
326 account_crypto_state,
327 &req.upgrade_token,
328 )?;
329 }
330 InitUserCryptoMethod::DeviceKey {
331 device_key,
332 protected_device_private_key,
333 device_protected_user_key,
334 } => {
335 let device_key = DeviceKey::try_from(device_key)?;
336 let user_key = device_key
337 .decrypt_user_key(protected_device_private_key, device_protected_user_key)?;
338
339 client.internal.initialize_user_crypto_decrypted_key(
340 user_key,
341 account_crypto_state,
342 &req.upgrade_token,
343 )?;
344 }
345 InitUserCryptoMethod::KeyConnector {
346 master_key,
347 user_key,
348 } => {
349 let bytes = master_key.into_bytes();
350 let master_key = MasterKey::try_from(bytes)?;
351
352 client.internal.initialize_user_crypto_key_connector_key(
353 master_key,
354 user_key,
355 account_crypto_state,
356 &req.upgrade_token,
357 )?;
358 }
359 InitUserCryptoMethod::KeyConnectorUrl {
360 url,
361 key_connector_key_wrapped_user_key,
362 } => {
363 let api_client = client.internal.get_key_connector_client(url);
364 let key_connector_key_response = api_client
365 .user_keys_api()
366 .get_user_key()
367 .await
368 .map_err(|_| EncryptionSettingsError::KeyConnectorRetrievalFailed)?;
369 let key_connector_key = KeyConnectorKey::try_from(key_connector_key_response)?;
370 let user_key =
371 key_connector_key.decrypt_user_key(key_connector_key_wrapped_user_key)?;
372 client.internal.initialize_user_crypto_decrypted_key(
373 user_key,
374 account_crypto_state,
375 &req.upgrade_token,
376 )?;
377 }
378 }
379
380 #[cfg(feature = "wasm")]
381 if should_copy_user_key {
382 copy_user_key_to_client_managed_state(client)
383 .await
384 .map_err(|_| EncryptionSettingsError::UserKeyStateUpdateFailed)?;
385 }
386
387 initialize_user_local_data_key(client).await?;
388 PinLockSystem::on_unlock(&PinLockSystem::with_client(client)).await;
389
390 client
391 .internal
392 .set_login_method(LoginMethod::User(UserLoginMethod::Username {
393 client_id: "".to_string(),
394 email: req.email,
395 kdf: req.kdf_params,
396 }))
397 .await;
398
399 if let Ok(setting) = client.internal.state_registry.setting(ACCOUNT_CRYPTO_STATE)
400 && let Err(e) = setting.update(req.account_cryptographic_state).await
401 {
402 tracing::warn!("Failed to persist account crypto state: {e}");
403 }
404
405 info!("User crypto initialized successfully");
406
407 Ok(())
408}
409
410#[derive(Serialize, Deserialize, Debug)]
412#[serde(rename_all = "camelCase", deny_unknown_fields)]
413#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
414#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
415pub struct InitOrgCryptoRequest {
416 pub organization_keys: HashMap<OrganizationId, UnsignedSharedKey>,
418}
419
420pub(super) async fn initialize_org_crypto(
422 client: &Client,
423 req: InitOrgCryptoRequest,
424) -> Result<(), EncryptionSettingsError> {
425 let organization_keys: Vec<_> = req.organization_keys.into_iter().collect();
426 client
427 .internal
428 .initialize_org_crypto(organization_keys.clone())?;
429
430 if let Ok(repo) = client
432 .internal
433 .state_registry
434 .get::<OrganizationSharedKey>()
435 {
436 for (org_id, key) in organization_keys {
437 if let Err(e) = repo
438 .set(org_id, OrganizationSharedKey { org_id, key })
439 .await
440 {
441 tracing::warn!("Failed to persist org key for {org_id}: {e}");
442 }
443 }
444 }
445
446 Ok(())
447}
448
449pub(super) async fn get_user_encryption_key(client: &Client) -> Result<B64, CryptoClientError> {
450 let key_store = client.internal.get_key_store();
451 let ctx = key_store.context();
452 #[allow(deprecated)]
455 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
456
457 Ok(user_key.to_base64())
458}
459
460#[derive(Serialize, Deserialize, Debug)]
462#[serde(rename_all = "camelCase", deny_unknown_fields)]
463#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
464#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
465pub struct UpdateKdfResponse {
466 master_password_authentication_data: MasterPasswordAuthenticationData,
468 master_password_unlock_data: MasterPasswordUnlockData,
470 old_master_password_authentication_data: MasterPasswordAuthenticationData,
472}
473
474pub(super) async fn make_update_kdf(
475 client: &Client,
476 password: &str,
477 new_kdf: &Kdf,
478) -> Result<UpdateKdfResponse, CryptoClientError> {
479 let login_method = client
480 .internal
481 .get_login_method()
482 .await
483 .ok_or(NotAuthenticatedError)?;
484 let email = match login_method {
485 UserLoginMethod::Username { email, .. } | UserLoginMethod::ApiKey { email, .. } => email,
486 };
487
488 let old_authentication_data = MasterPasswordAuthenticationData::derive(
489 password,
490 &client
491 .internal
492 .get_kdf()
493 .await
494 .map_err(|_| NotAuthenticatedError)?,
495 &email,
496 )
497 .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
498
499 let key_store = client.internal.get_key_store();
500 let ctx = key_store.context();
501
502 let authentication_data = MasterPasswordAuthenticationData::derive(password, new_kdf, &email)
503 .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
504 let unlock_data =
505 MasterPasswordUnlockData::derive(password, new_kdf, &email, SymmetricKeySlotId::User, &ctx)
506 .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
507
508 Ok(UpdateKdfResponse {
509 master_password_authentication_data: authentication_data,
510 master_password_unlock_data: unlock_data,
511 old_master_password_authentication_data: old_authentication_data,
512 })
513}
514
515#[derive(Serialize, Deserialize, Debug)]
517#[serde(rename_all = "camelCase", deny_unknown_fields)]
518#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
519#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
520pub struct UpdatePasswordResponse {
521 password_hash: B64,
523 new_key: EncString,
525}
526
527pub(super) async fn make_update_password(
528 client: &Client,
529 new_password: String,
530) -> Result<UpdatePasswordResponse, CryptoClientError> {
531 let login_method = client
532 .internal
533 .get_login_method()
534 .await
535 .ok_or(NotAuthenticatedError)?;
536
537 let key_store = client.internal.get_key_store();
538 let ctx = key_store.context();
539 #[allow(deprecated)]
541 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
542
543 let new_master_key = match login_method {
545 UserLoginMethod::Username { email, kdf, .. }
546 | UserLoginMethod::ApiKey { email, kdf, .. } => {
547 MasterKey::derive(&new_password, &email, &kdf)?
548 }
549 };
550
551 let new_key = new_master_key.encrypt_user_key(user_key)?;
552
553 let password_hash = new_master_key.derive_master_key_hash(
554 new_password.as_bytes(),
555 bitwarden_crypto::HashPurpose::ServerAuthorization,
556 );
557
558 Ok(UpdatePasswordResponse {
559 password_hash,
560 new_key,
561 })
562}
563
564#[derive(Serialize, Deserialize, Debug)]
566#[serde(rename_all = "camelCase", deny_unknown_fields)]
567#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
568#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
569pub struct EnrollPinResponse {
570 pub pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
572 pub user_key_encrypted_pin: EncString,
574}
575
576pub(super) fn enroll_pin(
577 client: &Client,
578 pin: String,
579) -> Result<EnrollPinResponse, CryptoClientError> {
580 let key_store = client.internal.get_key_store();
581 let mut ctx = key_store.context_mut();
582
583 let key_envelope = PasswordProtectedKeyEnvelope::seal(
584 SymmetricKeySlotId::User,
585 &pin,
586 PasswordProtectedKeyEnvelopeNamespace::PinUnlock,
587 &ctx,
588 )?;
589 let encrypted_pin = pin.encrypt(&mut ctx, SymmetricKeySlotId::User)?;
590 Ok(EnrollPinResponse {
591 pin_protected_user_key_envelope: key_envelope,
592 user_key_encrypted_pin: encrypted_pin,
593 })
594}
595
596#[derive(Serialize, Deserialize, Debug)]
598#[serde(rename_all = "camelCase", deny_unknown_fields)]
599#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
600#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
601pub struct DerivePinKeyResponse {
602 pin_protected_user_key: EncString,
604 encrypted_pin: EncString,
606}
607
608pub(super) async fn derive_pin_key(
609 client: &Client,
610 pin: String,
611) -> Result<DerivePinKeyResponse, CryptoClientError> {
612 let login_method = client
613 .internal
614 .get_login_method()
615 .await
616 .ok_or(NotAuthenticatedError)?;
617
618 let key_store = client.internal.get_key_store();
619 let ctx = key_store.context();
620 #[allow(deprecated)]
622 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
623
624 let pin_protected_user_key = derive_pin_protected_user_key(&pin, &login_method, user_key)?;
625
626 Ok(DerivePinKeyResponse {
627 pin_protected_user_key,
628 encrypted_pin: pin.encrypt_with_key(user_key)?,
629 })
630}
631
632pub(super) async fn derive_pin_user_key(
633 client: &Client,
634 encrypted_pin: EncString,
635) -> Result<EncString, CryptoClientError> {
636 let login_method = client
637 .internal
638 .get_login_method()
639 .await
640 .ok_or(NotAuthenticatedError)?;
641
642 let key_store = client.internal.get_key_store();
643 let ctx = key_store.context();
644 #[allow(deprecated)]
646 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
647
648 let pin: String = encrypted_pin.decrypt_with_key(user_key)?;
649
650 derive_pin_protected_user_key(&pin, &login_method, user_key)
651}
652
653fn derive_pin_protected_user_key(
654 pin: &str,
655 login_method: &UserLoginMethod,
656 user_key: &SymmetricCryptoKey,
657) -> Result<EncString, CryptoClientError> {
658 use bitwarden_crypto::PinKey;
659
660 let derived_key = match login_method {
661 UserLoginMethod::Username { email, kdf, .. }
662 | UserLoginMethod::ApiKey { email, kdf, .. } => {
663 PinKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?
664 }
665 };
666
667 Ok(derived_key.encrypt_user_key(user_key)?)
668}
669
670pub(super) fn make_prf_user_key_set(
671 client: &Client,
672 prf: B64,
673) -> Result<RotateableKeySet, CryptoClientError> {
674 let prf_key = derive_symmetric_key_from_prf(prf.as_bytes())
675 .map_err(|_| CryptoClientError::InvalidPrfInput)?;
676 let ctx = client.internal.get_key_store().context();
677 let key_set = RotateableKeySet::new(&ctx, &prf_key, SymmetricKeySlotId::User)?;
678 Ok(key_set)
679}
680
681#[allow(missing_docs)]
682#[bitwarden_error(flat)]
683#[derive(Debug, thiserror::Error)]
684pub enum EnrollAdminPasswordResetError {
685 #[error(transparent)]
686 Crypto(#[from] bitwarden_crypto::CryptoError),
687}
688
689pub(super) fn enroll_admin_password_reset(
690 client: &Client,
691 public_key: B64,
692) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
693 use bitwarden_crypto::PublicKey;
694
695 let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&public_key))?;
696 let key_store = client.internal.get_key_store();
697 let ctx = key_store.context();
698 #[allow(deprecated)]
700 let key = ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)?;
701
702 #[expect(deprecated)]
703 Ok(UnsignedSharedKey::encapsulate_key_unsigned(
704 key,
705 &public_key,
706 )?)
707}
708
709#[derive(Serialize, Deserialize, Debug, JsonSchema)]
711#[serde(rename_all = "camelCase", deny_unknown_fields)]
712#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
713#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
714pub struct DeriveKeyConnectorRequest {
715 pub user_key_encrypted: EncString,
717 pub password: String,
719 pub kdf: Kdf,
721 pub email: String,
723}
724
725#[allow(missing_docs)]
726#[bitwarden_error(flat)]
727#[derive(Debug, thiserror::Error)]
728pub enum DeriveKeyConnectorError {
729 #[error(transparent)]
730 WrongPassword(#[from] WrongPasswordError),
731 #[error(transparent)]
732 Crypto(#[from] bitwarden_crypto::CryptoError),
733}
734
735pub(super) fn derive_key_connector(
737 request: DeriveKeyConnectorRequest,
738) -> Result<B64, DeriveKeyConnectorError> {
739 let master_key = MasterKey::derive(&request.password, &request.email, &request.kdf)?;
740 master_key
741 .decrypt_user_key(request.user_key_encrypted)
742 .map_err(|_| WrongPasswordError)?;
743
744 Ok(master_key.to_base64())
745}
746
747#[derive(Serialize, Deserialize, Debug)]
749#[serde(rename_all = "camelCase", deny_unknown_fields)]
750#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
751#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
752pub struct MakeKeyPairResponse {
753 user_public_key: B64,
755 user_key_encrypted_private_key: EncString,
757}
758
759pub(super) fn make_key_pair(user_key: B64) -> Result<MakeKeyPairResponse, CryptoError> {
760 let user_key = UserKey::new(SymmetricCryptoKey::try_from(user_key)?);
761
762 let key_pair = user_key.make_key_pair()?;
763
764 Ok(MakeKeyPairResponse {
765 user_public_key: key_pair.public,
766 user_key_encrypted_private_key: key_pair.private,
767 })
768}
769
770#[derive(Serialize, Deserialize, Debug)]
772#[serde(rename_all = "camelCase", deny_unknown_fields)]
773#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
774#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
775pub struct VerifyAsymmetricKeysRequest {
776 user_key: B64,
778 user_public_key: B64,
780 user_key_encrypted_private_key: EncString,
782}
783
784#[derive(Serialize, Deserialize, Debug)]
786#[serde(rename_all = "camelCase", deny_unknown_fields)]
787#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
788#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
789pub struct VerifyAsymmetricKeysResponse {
790 private_key_decryptable: bool,
792 valid_private_key: bool,
794}
795
796pub(super) fn verify_asymmetric_keys(
797 request: VerifyAsymmetricKeysRequest,
798) -> Result<VerifyAsymmetricKeysResponse, CryptoError> {
799 #[derive(Debug, thiserror::Error)]
800 enum VerifyError {
801 #[error("Failed to decrypt private key: {0:?}")]
802 DecryptFailed(bitwarden_crypto::CryptoError),
803 #[error("Failed to parse decrypted private key: {0:?}")]
804 ParseFailed(bitwarden_crypto::CryptoError),
805 #[error("Failed to derive a public key: {0:?}")]
806 PublicFailed(bitwarden_crypto::CryptoError),
807 #[error("Derived public key doesn't match")]
808 KeyMismatch,
809 }
810
811 fn verify_inner(
812 user_key: &SymmetricCryptoKey,
813 request: &VerifyAsymmetricKeysRequest,
814 ) -> Result<(), VerifyError> {
815 let decrypted_private_key: Vec<u8> = request
816 .user_key_encrypted_private_key
817 .decrypt_with_key(user_key)
818 .map_err(VerifyError::DecryptFailed)?;
819
820 let decrypted_private_key = Pkcs8PrivateKeyBytes::from(decrypted_private_key);
821 let private_key =
822 PrivateKey::from_der(&decrypted_private_key).map_err(VerifyError::ParseFailed)?;
823
824 let derived_public_key_vec = private_key
825 .to_public_key()
826 .to_der()
827 .map_err(VerifyError::PublicFailed)?;
828
829 let derived_public_key = B64::from(derived_public_key_vec);
830
831 if derived_public_key != request.user_public_key {
832 return Err(VerifyError::KeyMismatch);
833 }
834 Ok(())
835 }
836
837 let user_key = SymmetricCryptoKey::try_from(request.user_key.clone())?;
838
839 Ok(match verify_inner(&user_key, &request) {
840 Ok(_) => VerifyAsymmetricKeysResponse {
841 private_key_decryptable: true,
842 valid_private_key: true,
843 },
844 Err(error) => {
845 tracing::debug!(%error, "User asymmetric keys verification");
846
847 VerifyAsymmetricKeysResponse {
848 private_key_decryptable: !matches!(error, VerifyError::DecryptFailed(_)),
849 valid_private_key: false,
850 }
851 }
852 })
853}
854
855#[derive(Serialize, Deserialize, Debug, Clone)]
857#[serde(rename_all = "camelCase", deny_unknown_fields)]
858#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
859#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
860pub struct UserCryptoV2KeysResponse {
861 user_key: B64,
863
864 private_key: EncString,
866 public_key: B64,
868 signed_public_key: SignedPublicKey,
870
871 signing_key: EncString,
873 verifying_key: B64,
875
876 security_state: SignedSecurityState,
878 security_version: u64,
880}
881
882#[deprecated(note = "Use AccountCryptographicState::rotate instead")]
886pub(crate) fn make_v2_keys_for_v1_user(
887 client: &Client,
888) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
889 let key_store = client.internal.get_key_store();
890 let mut ctx = key_store.context();
891
892 let private_key_id = PrivateKeySlotId::UserPrivateKey;
894
895 if client.internal.get_security_version() != 1 {
897 return Err(StatefulCryptoError::WrongAccountCryptoVersion {
898 expected: "1".to_string(),
899 got: 2,
900 });
901 }
902
903 if !ctx.has_private_key(PrivateKeySlotId::UserPrivateKey) {
907 return Err(StatefulCryptoError::Crypto(CryptoError::MissingKeyId(
908 "UserPrivateKey".to_string(),
909 )));
910 }
911
912 #[allow(deprecated)]
913 let private_key = ctx.dangerous_get_private_key(private_key_id)?.clone();
914
915 let user_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
917
918 let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
920 let temporary_signing_key_id = ctx.add_local_signing_key(signing_key.clone());
921
922 let signed_public_key = ctx.make_signed_public_key(private_key_id, temporary_signing_key_id)?;
924 let public_key = private_key.to_public_key();
925
926 let security_state = SecurityState::new();
928 let signed_security_state = security_state.sign(temporary_signing_key_id, &mut ctx)?;
929
930 Ok(UserCryptoV2KeysResponse {
931 user_key: user_key.to_base64(),
932
933 private_key: private_key.to_der()?.encrypt_with_key(&user_key)?,
934 public_key: public_key.to_der()?.into(),
935 signed_public_key,
936
937 signing_key: signing_key.to_cose().encrypt_with_key(&user_key)?,
938 verifying_key: signing_key.to_verifying_key().to_cose().into(),
939
940 security_state: signed_security_state,
941 security_version: security_state.version(),
942 })
943}
944
945#[deprecated(note = "Use AccountCryptographicState::rotate instead")]
950pub(crate) fn get_v2_rotated_account_keys(
951 client: &Client,
952) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
953 let key_store = client.internal.get_key_store();
954 let mut ctx = key_store.context();
955
956 if client.internal.get_security_version() == 1 {
959 return Err(StatefulCryptoError::WrongAccountCryptoVersion {
960 expected: "2+".to_string(),
961 got: 1,
962 });
963 }
964
965 let security_state = client
966 .internal
967 .security_state
968 .read()
969 .expect("RwLock is not poisoned")
970 .to_owned()
971 .ok_or(StatefulCryptoError::MissingSecurityState)?;
974
975 #[expect(deprecated)]
976 let rotated_keys = dangerous_get_v2_rotated_account_keys(
977 PrivateKeySlotId::UserPrivateKey,
978 SigningKeySlotId::UserSigningKey,
979 &ctx,
980 )?;
981
982 Ok(UserCryptoV2KeysResponse {
983 user_key: rotated_keys.user_key.to_base64(),
984
985 private_key: rotated_keys.private_key,
986 public_key: rotated_keys.public_key.into(),
987 signed_public_key: rotated_keys.signed_public_key,
988
989 signing_key: rotated_keys.signing_key,
990 verifying_key: rotated_keys.verifying_key.into(),
991
992 security_state: security_state.sign(SigningKeySlotId::UserSigningKey, &mut ctx)?,
993 security_version: security_state.version(),
994 })
995}
996
997pub struct MakeTdeRegistrationResponse {
999 pub account_cryptographic_state: WrappedAccountCryptographicState,
1001 pub user_key: SymmetricCryptoKey,
1003 pub account_keys_request: AccountKeysRequestModel,
1005 pub trusted_device_keys: TrustDeviceResponse,
1007 pub reset_password_key: UnsignedSharedKey,
1009}
1010
1011pub struct MakeJitMasterPasswordRegistrationResponse {
1013 pub account_cryptographic_state: WrappedAccountCryptographicState,
1015 pub user_key: SymmetricCryptoKey,
1017 pub master_password_authentication_data: MasterPasswordAuthenticationData,
1019 pub master_password_unlock_data: MasterPasswordUnlockData,
1021 pub account_keys_request: AccountKeysRequestModel,
1023 pub reset_password_key: UnsignedSharedKey,
1025}
1026
1027#[bitwarden_error(flat)]
1029#[derive(Debug, thiserror::Error)]
1030pub enum MakeKeysError {
1031 #[error("Failed to initialize account cryptography")]
1033 AccountCryptographyInitialization(AccountCryptographyInitializationError),
1034 #[error("Failed to derive master password")]
1036 MasterPasswordDerivation(MasterPasswordError),
1037 #[error("Failed to make a request model")]
1039 RequestModelCreation,
1040 #[error("Cryptography error: {0}")]
1042 Crypto(#[from] CryptoError),
1043}
1044
1045pub(crate) fn make_user_tde_registration(
1047 client: &Client,
1048 org_public_key: B64,
1049) -> Result<MakeTdeRegistrationResponse, MakeKeysError> {
1050 let mut ctx = client.internal.get_key_store().context_mut();
1051 let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1052 .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1053 #[expect(deprecated)]
1055 let device_key = DeviceKey::trust_device(ctx.dangerous_get_symmetric_key(user_key_id)?)?;
1056
1057 let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&org_public_key))
1059 .map_err(MakeKeysError::Crypto)?;
1060 #[expect(deprecated)]
1061 let admin_reset = UnsignedSharedKey::encapsulate_key_unsigned(
1062 ctx.dangerous_get_symmetric_key(user_key_id)?,
1063 &public_key,
1064 )
1065 .map_err(MakeKeysError::Crypto)?;
1066
1067 let cryptography_state_request_model = wrapped_state
1068 .to_request_model(&user_key_id, &mut ctx)
1069 .map_err(|_| MakeKeysError::RequestModelCreation)?;
1070
1071 #[expect(deprecated)]
1072 Ok(MakeTdeRegistrationResponse {
1073 account_cryptographic_state: wrapped_state,
1074 user_key: ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned(),
1075 account_keys_request: cryptography_state_request_model,
1076 trusted_device_keys: device_key,
1077 reset_password_key: admin_reset,
1078 })
1079}
1080
1081pub struct MakeKeyConnectorRegistrationResponse {
1083 pub account_cryptographic_state: WrappedAccountCryptographicState,
1085 pub key_connector_key_wrapped_user_key: EncString,
1087 pub user_key: SymmetricCryptoKey,
1089 pub account_keys_request: AccountKeysRequestModel,
1091 pub key_connector_key: KeyConnectorKey,
1093}
1094
1095pub(crate) fn make_user_key_connector_registration(
1097 client: &Client,
1098) -> Result<MakeKeyConnectorRegistrationResponse, MakeKeysError> {
1099 let mut ctx = client.internal.get_key_store().context_mut();
1100 let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1101 .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1102 #[expect(deprecated)]
1103 let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
1104
1105 let key_connector_key = KeyConnectorKey::make();
1107
1108 let wrapped_user_key = key_connector_key
1109 .encrypt_user_key(&user_key)
1110 .map_err(MakeKeysError::Crypto)?;
1111
1112 let cryptography_state_request_model =
1113 wrapped_state
1114 .to_request_model(&user_key_id, &mut ctx)
1115 .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1116
1117 Ok(MakeKeyConnectorRegistrationResponse {
1118 account_cryptographic_state: wrapped_state,
1119 key_connector_key_wrapped_user_key: wrapped_user_key,
1120 user_key,
1121 account_keys_request: cryptography_state_request_model,
1122 key_connector_key,
1123 })
1124}
1125
1126async fn initialize_user_local_data_key(client: &Client) -> Result<(), EncryptionSettingsError> {
1132 let user_id = client
1133 .internal
1134 .get_user_id()
1135 .ok_or(EncryptionSettingsError::LocalUserDataKeyInitFailed)?;
1136
1137 initialize_local_user_data_key_into_state(client, user_id)
1138 .await
1139 .map_err(|_| EncryptionSettingsError::LocalUserDataKeyInitFailed)?;
1140
1141 let wrapped_key = get_local_user_data_key_from_state(client, user_id)
1142 .await
1143 .map_err(|_| EncryptionSettingsError::LocalUserDataKeyLoadFailed)?;
1144 let mut ctx = client.internal.get_key_store().context_mut();
1145 wrapped_key
1146 .unwrap_to_context(&mut ctx)
1147 .map_err(|_| EncryptionSettingsError::LocalUserDataKeyLoadFailed)
1148}
1149
1150pub(crate) fn make_user_jit_master_password_registration(
1152 client: &Client,
1153 master_password: String,
1154 salt: String,
1155 org_public_key: B64,
1156) -> Result<MakeJitMasterPasswordRegistrationResponse, MakeKeysError> {
1157 let mut ctx = client.internal.get_key_store().context_mut();
1158 let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1159 .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1160
1161 let kdf = Kdf::default_argon2();
1162
1163 #[expect(deprecated)]
1164 let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
1165
1166 let master_password_unlock_data =
1167 MasterPasswordUnlockData::derive(&master_password, &kdf, &salt, user_key_id, &ctx)
1168 .map_err(MakeKeysError::MasterPasswordDerivation)?;
1169
1170 let master_password_authentication_data =
1171 MasterPasswordAuthenticationData::derive(&master_password, &kdf, &salt)
1172 .map_err(MakeKeysError::MasterPasswordDerivation)?;
1173
1174 let cryptography_state_request_model = wrapped_state
1175 .to_request_model(&user_key_id, &mut ctx)
1176 .map_err(|_| MakeKeysError::RequestModelCreation)?;
1177
1178 let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&org_public_key))
1180 .map_err(MakeKeysError::Crypto)?;
1181 let admin_reset_key = UnsignedSharedKey::encapsulate(user_key_id, &public_key, &ctx)
1182 .map_err(MakeKeysError::Crypto)?;
1183
1184 Ok(MakeJitMasterPasswordRegistrationResponse {
1185 account_cryptographic_state: wrapped_state,
1186 user_key,
1187 master_password_unlock_data,
1188 master_password_authentication_data,
1189 account_keys_request: cryptography_state_request_model,
1190 reset_password_key: admin_reset_key,
1191 })
1192}
1193
1194pub struct MakeUserMasterPasswordRegistrationResponse {
1196 pub account_cryptographic_state: WrappedAccountCryptographicState,
1198 pub master_password_unlock_data: MasterPasswordUnlockData,
1200 pub master_password_authentication_data: MasterPasswordAuthenticationData,
1202 pub account_keys_request: AccountKeysRequestModel,
1204 pub user_key: SymmetricCryptoKey,
1206}
1207
1208pub(crate) fn make_user_password_registration(
1210 client: &Client,
1211 master_password: String,
1212 salt: String,
1213) -> Result<MakeUserMasterPasswordRegistrationResponse, MakeKeysError> {
1214 let mut ctx = client.internal.get_key_store().context_mut();
1217 let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx)
1218 .map_err(MakeKeysError::AccountCryptographyInitialization)?;
1219
1220 let kdf = Kdf::default_argon2();
1221
1222 #[expect(deprecated)]
1223 let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
1224
1225 let master_password_unlock_data =
1226 MasterPasswordUnlockData::derive(&master_password, &kdf, &salt, user_key_id, &ctx)
1227 .map_err(MakeKeysError::MasterPasswordDerivation)?;
1228
1229 let master_password_authentication_data =
1230 MasterPasswordAuthenticationData::derive(&master_password, &kdf, &salt)
1231 .map_err(MakeKeysError::MasterPasswordDerivation)?;
1232
1233 let account_keys_request = wrapped_state
1234 .to_request_model(&user_key_id, &mut ctx)
1235 .map_err(|_| MakeKeysError::RequestModelCreation)?;
1236
1237 Ok(MakeUserMasterPasswordRegistrationResponse {
1238 account_cryptographic_state: wrapped_state,
1239 master_password_unlock_data,
1240 master_password_authentication_data,
1241 account_keys_request,
1242 user_key,
1243 })
1244}
1245
1246#[cfg(test)]
1247mod tests {
1248 use std::num::NonZeroU32;
1249
1250 use bitwarden_crypto::{
1251 Decryptable, KeyStore, PrivateKey, PublicKeyEncryptionAlgorithm, RsaKeyPair,
1252 SymmetricKeyAlgorithm,
1253 };
1254
1255 use super::*;
1256 use crate::{
1257 Client,
1258 client::test_accounts::{test_bitwarden_com_account, test_bitwarden_com_account_v2},
1259 key_management::{KeySlotIds, V2UpgradeToken},
1260 };
1261
1262 const TEST_VECTOR_USER_KEY_V2_B64: &str = "pQEEAlACHUUoybNAuJoZzqNMxz2bAzoAARFvBIQDBAUGIFggAvGl4ifaUAomQdCdUPpXLHtypiQxHjZwRHeI83caZM4B";
1263 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";
1264 #[allow(unused)]
1265 const TEST_VECTOR_PUBLIC_KEY_V2: &str = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz/+1jPJ1HqcaCdKrTPms8XJcvnmd9alI42U2XF/4GMNTM5KF1gI6snhR/23ZLatZRFMHoK8ZCMSpGNkjLadArz52ldceTvBOhQUiWylkZQ4NfNa3xIYJubXOmkeDyfNuyLxVZvcZOko9PdT+Qx2QxDrFi2XNo2I7aVFd19/COIEkex4mJ0eA3MHFpKCdxYbcTAsGID8+kVR9L84S1JptZoG8x+iB/D3/Q4y02UsQYpFTu0vbPY84YmW03ngJdxWzS8X4/UJI/jaEn5rO4xlU5QcL0l4IybP5LRpE9XEeUHATKVOG7eNfpe9zDfKV2qQoofQMH9VvkWO4psaWDjBSdwIDAQAB";
1266 #[allow(unused)]
1267 const TEST_VECTOR_SIGNED_PUBLIC_KEY_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8BoFkBTqNpYWxnb3JpdGhtAG1jb250ZW50Rm9ybWF0AGlwdWJsaWNLZXlZASYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP/7WM8nUepxoJ0qtM+azxcly+eZ31qUjjZTZcX/gYw1MzkoXWAjqyeFH/bdktq1lEUwegrxkIxKkY2SMtp0CvPnaV1x5O8E6FBSJbKWRlDg181rfEhgm5tc6aR4PJ827IvFVm9xk6Sj091P5DHZDEOsWLZc2jYjtpUV3X38I4gSR7HiYnR4DcwcWkoJ3FhtxMCwYgPz6RVH0vzhLUmm1mgbzH6IH8Pf9DjLTZSxBikVO7S9s9jzhiZbTeeAl3FbNLxfj9Qkj+NoSfms7jGVTlBwvSXgjJs/ktGkT1cR5QcBMpU4bt41+l73MN8pXapCih9Awf1W+RY7imxpYOMFJ3AgMBAAFYQMq/hT4wod2w8xyoM7D86ctuLNX4ZRo+jRHf2sZfaO7QsvonG/ZYuNKF5fq8wpxMRjfoMvnY2TTShbgzLrW8BA4=";
1268 const TEST_VECTOR_SIGNING_KEY_V2: &str = "7.g1gcowE6AAERbwMYZQRQAh1FKMmzQLiaGc6jTMc9m6EFWBhYePc2qkCruHAPXgbzXsIP1WVk11ArbLNYUBpifToURlwHKs1je2BwZ1C/5thz4nyNbL0wDaYkRWI9ex1wvB7KhdzC7ltStEd5QttboTSCaXQROSZaGBPNO5+Bu3sTY8F5qK1pBUo6AHNN";
1269 #[allow(unused)]
1270 const TEST_VECTOR_VERIFYING_KEY_V2: &str =
1271 "pgEBAlAmkP0QgfdMVbIujX55W/yNAycEgQIgBiFYIEM6JxBmjWQTruAm3s6BTaJy1q6BzQetMBacNeRJ0kxR";
1272 const TEST_VECTOR_SECURITY_STATE_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8CoFgkomhlbnRpdHlJZFBHOOw2BI9OQoNq+Vl1xZZKZ3ZlcnNpb24CWEAlchbJR0vmRfShG8On7Q2gknjkw4Dd6MYBLiH4u+/CmfQdmjNZdf6kozgW/6NXyKVNu8dAsKsin+xxXkDyVZoG";
1273
1274 const TEST_USER_EMAIL: &str = "[email protected]";
1275 const TEST_USER_PASSWORD: &str = "asdfasdfasdf";
1276 const TEST_ACCOUNT_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
1277 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=";
1278
1279 #[tokio::test]
1280 async fn test_update_kdf() {
1281 let client = Client::new_test(None);
1282
1283 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();
1284
1285 let kdf = Kdf::PBKDF2 {
1286 iterations: 100_000.try_into().unwrap(),
1287 };
1288
1289 initialize_user_crypto(
1290 &client,
1291 InitUserCryptoRequest {
1292 user_id: Some(UserId::new_v4()),
1293 kdf_params: kdf.clone(),
1294 email: "[email protected]".into(),
1295 account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1296 method: InitUserCryptoMethod::MasterPasswordUnlock {
1297 password: "asdfasdfasdf".into(),
1298 master_password_unlock: MasterPasswordUnlockData {
1299 kdf: kdf.clone(),
1300 master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1301 salt: "[email protected]".to_string(),
1302 },
1303 },
1304 upgrade_token: None,
1305 },
1306 )
1307 .await
1308 .unwrap();
1309
1310 let new_kdf = Kdf::PBKDF2 {
1311 iterations: 600_000.try_into().unwrap(),
1312 };
1313 let new_kdf_response = make_update_kdf(&client, "123412341234", &new_kdf)
1314 .await
1315 .unwrap();
1316
1317 let client2 = Client::new_test(None);
1318
1319 initialize_user_crypto(
1320 &client2,
1321 InitUserCryptoRequest {
1322 user_id: Some(UserId::new_v4()),
1323 kdf_params: new_kdf.clone(),
1324 email: "[email protected]".into(),
1325 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1326 private_key: priv_key.to_owned(),
1327 },
1328 method: InitUserCryptoMethod::MasterPasswordUnlock {
1329 password: "123412341234".to_string(),
1330 master_password_unlock: MasterPasswordUnlockData {
1331 kdf: new_kdf.clone(),
1332 master_key_wrapped_user_key: new_kdf_response
1333 .master_password_unlock_data
1334 .master_key_wrapped_user_key,
1335 salt: "[email protected]".to_string(),
1336 },
1337 },
1338 upgrade_token: None,
1339 },
1340 )
1341 .await
1342 .unwrap();
1343
1344 let new_hash = client2
1345 .kdf()
1346 .hash_password(
1347 "[email protected]".into(),
1348 "123412341234".into(),
1349 new_kdf.clone(),
1350 bitwarden_crypto::HashPurpose::ServerAuthorization,
1351 )
1352 .await
1353 .unwrap();
1354
1355 assert_eq!(
1356 new_hash,
1357 new_kdf_response
1358 .master_password_authentication_data
1359 .master_password_authentication_hash
1360 );
1361
1362 let client_key = {
1363 let key_store = client.internal.get_key_store();
1364 let ctx = key_store.context();
1365 #[allow(deprecated)]
1366 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1367 .unwrap()
1368 .to_base64()
1369 };
1370
1371 let client2_key = {
1372 let key_store = client2.internal.get_key_store();
1373 let ctx = key_store.context();
1374 #[allow(deprecated)]
1375 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1376 .unwrap()
1377 .to_base64()
1378 };
1379
1380 assert_eq!(client_key, client2_key);
1381 }
1382
1383 #[tokio::test]
1384 async fn test_update_password() {
1385 let client = Client::new_test(None);
1386
1387 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();
1388
1389 let kdf = Kdf::PBKDF2 {
1390 iterations: 100_000.try_into().unwrap(),
1391 };
1392
1393 initialize_user_crypto(
1394 &client,
1395 InitUserCryptoRequest {
1396 user_id: Some(UserId::new_v4()),
1397 kdf_params: kdf.clone(),
1398 email: "[email protected]".into(),
1399 account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1400 method: InitUserCryptoMethod::MasterPasswordUnlock {
1401 password: "asdfasdfasdf".to_string(),
1402 master_password_unlock: MasterPasswordUnlockData {
1403 kdf: kdf.clone(),
1404 master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1405 salt: "[email protected]".to_string(),
1406 },
1407 },
1408 upgrade_token: None,
1409 },
1410 )
1411 .await
1412 .unwrap();
1413
1414 let new_password_response = make_update_password(&client, "123412341234".into())
1415 .await
1416 .unwrap();
1417
1418 let client2 = Client::new_test(None);
1419
1420 initialize_user_crypto(
1421 &client2,
1422 InitUserCryptoRequest {
1423 user_id: Some(UserId::new_v4()),
1424 kdf_params: kdf.clone(),
1425 email: "[email protected]".into(),
1426 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1427 private_key: priv_key.to_owned(),
1428 },
1429 method: InitUserCryptoMethod::MasterPasswordUnlock {
1430 password: "123412341234".into(),
1431 master_password_unlock: MasterPasswordUnlockData {
1432 kdf: kdf.clone(),
1433 master_key_wrapped_user_key: new_password_response.new_key,
1434 salt: "[email protected]".to_string(),
1435 },
1436 },
1437 upgrade_token: None,
1438 },
1439 )
1440 .await
1441 .unwrap();
1442
1443 let new_hash = client2
1444 .kdf()
1445 .hash_password(
1446 "[email protected]".into(),
1447 "123412341234".into(),
1448 kdf.clone(),
1449 bitwarden_crypto::HashPurpose::ServerAuthorization,
1450 )
1451 .await
1452 .unwrap();
1453
1454 assert_eq!(new_hash, new_password_response.password_hash);
1455
1456 let client_key = {
1457 let key_store = client.internal.get_key_store();
1458 let ctx = key_store.context();
1459 #[allow(deprecated)]
1460 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1461 .unwrap()
1462 .to_base64()
1463 };
1464
1465 let client2_key = {
1466 let key_store = client2.internal.get_key_store();
1467 let ctx = key_store.context();
1468 #[allow(deprecated)]
1469 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1470 .unwrap()
1471 .to_base64()
1472 };
1473
1474 assert_eq!(client_key, client2_key);
1475 }
1476
1477 #[tokio::test]
1478 async fn test_initialize_user_crypto_pin() {
1479 let client = Client::new_test(None);
1480
1481 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();
1482
1483 initialize_user_crypto(
1484 &client,
1485 InitUserCryptoRequest {
1486 user_id: Some(UserId::new_v4()),
1487 kdf_params: Kdf::PBKDF2 {
1488 iterations: 100_000.try_into().unwrap(),
1489 },
1490 email: "[email protected]".into(),
1491 account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1492 method: InitUserCryptoMethod::MasterPasswordUnlock {
1493 password: "asdfasdfasdf".into(),
1494 master_password_unlock: MasterPasswordUnlockData {
1495 kdf: Kdf::PBKDF2 {
1496 iterations: 100_000.try_into().unwrap(),
1497 },
1498 master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1499 salt: "[email protected]".to_string(),
1500 },
1501 },
1502 upgrade_token: None,
1503 },
1504 )
1505 .await
1506 .unwrap();
1507
1508 let pin_key = derive_pin_key(&client, "1234".into()).await.unwrap();
1509
1510 let client2 = Client::new_test(None);
1512 initialize_user_crypto(
1513 &client2,
1514 InitUserCryptoRequest {
1515 user_id: Some(UserId::new_v4()),
1516 kdf_params: Kdf::PBKDF2 {
1517 iterations: 100_000.try_into().unwrap(),
1518 },
1519 email: "[email protected]".into(),
1520 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1521 private_key: priv_key.to_owned(),
1522 },
1523 method: InitUserCryptoMethod::Pin {
1524 pin: "1234".into(),
1525 pin_protected_user_key: pin_key.pin_protected_user_key,
1526 },
1527 upgrade_token: None,
1528 },
1529 )
1530 .await
1531 .unwrap();
1532
1533 let client_key = {
1534 let key_store = client.internal.get_key_store();
1535 let ctx = key_store.context();
1536 #[allow(deprecated)]
1537 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1538 .unwrap()
1539 .to_base64()
1540 };
1541
1542 let client2_key = {
1543 let key_store = client2.internal.get_key_store();
1544 let ctx = key_store.context();
1545 #[allow(deprecated)]
1546 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1547 .unwrap()
1548 .to_base64()
1549 };
1550
1551 assert_eq!(client_key, client2_key);
1552
1553 let pin_protected_user_key = derive_pin_user_key(&client, pin_key.encrypted_pin)
1555 .await
1556 .unwrap();
1557
1558 let client3 = Client::new_test(None);
1559
1560 initialize_user_crypto(
1561 &client3,
1562 InitUserCryptoRequest {
1563 user_id: Some(UserId::new_v4()),
1564 kdf_params: Kdf::PBKDF2 {
1565 iterations: 100_000.try_into().unwrap(),
1566 },
1567 email: "[email protected]".into(),
1568 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1569 private_key: priv_key.to_owned(),
1570 },
1571 method: InitUserCryptoMethod::Pin {
1572 pin: "1234".into(),
1573 pin_protected_user_key,
1574 },
1575 upgrade_token: None,
1576 },
1577 )
1578 .await
1579 .unwrap();
1580
1581 let client_key = {
1582 let key_store = client.internal.get_key_store();
1583 let ctx = key_store.context();
1584 #[allow(deprecated)]
1585 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1586 .unwrap()
1587 .to_base64()
1588 };
1589
1590 let client3_key = {
1591 let key_store = client3.internal.get_key_store();
1592 let ctx = key_store.context();
1593 #[allow(deprecated)]
1594 ctx.dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1595 .unwrap()
1596 .to_base64()
1597 };
1598
1599 assert_eq!(client_key, client3_key);
1600 }
1601
1602 #[tokio::test]
1603 async fn test_initialize_user_crypto_pin_envelope() {
1604 let user_key = "5yKAZ4TSSEGje54MV5lc5ty6crkqUz4xvl+8Dm/piNLKf6OgRi2H0uzttNTXl9z6ILhkmuIXzGpAVc2YdorHgQ==";
1605 let test_pin = "1234";
1606
1607 let client1 = Client::new_test(None);
1608 initialize_user_crypto(
1609 &client1,
1610 InitUserCryptoRequest {
1611 user_id: Some(UserId::new_v4()),
1612 kdf_params: Kdf::PBKDF2 {
1613 iterations: 100_000.try_into().unwrap(),
1614 },
1615 email: "[email protected]".into(),
1616 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1617 private_key: make_key_pair(user_key.try_into().unwrap())
1618 .unwrap()
1619 .user_key_encrypted_private_key,
1620 },
1621 method: InitUserCryptoMethod::DecryptedKey {
1622 decrypted_user_key: user_key.to_string(),
1623 },
1624 upgrade_token: None,
1625 },
1626 )
1627 .await
1628 .unwrap();
1629
1630 let enroll_response = client1.crypto().enroll_pin(test_pin.to_string()).unwrap();
1631
1632 let client2 = Client::new_test(None);
1633 initialize_user_crypto(
1634 &client2,
1635 InitUserCryptoRequest {
1636 user_id: Some(UserId::new_v4()),
1637 kdf_params: Kdf::PBKDF2 {
1640 iterations: 600_000.try_into().unwrap(),
1641 },
1642 email: "[email protected]".into(),
1643 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1644 private_key: make_key_pair(user_key.try_into().unwrap())
1645 .unwrap()
1646 .user_key_encrypted_private_key,
1647 },
1648 method: InitUserCryptoMethod::PinEnvelope {
1649 pin: test_pin.to_string(),
1650 pin_protected_user_key_envelope: enroll_response
1651 .pin_protected_user_key_envelope,
1652 },
1653 upgrade_token: None,
1654 },
1655 )
1656 .await
1657 .unwrap();
1658 }
1659
1660 #[test]
1661 fn test_enroll_admin_password_reset() {
1662 let client = Client::new(None);
1663
1664 let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap();
1665 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();
1666 client
1667 .internal
1668 .initialize_user_crypto_master_password_unlock(
1669 "asdfasdfasdf".to_string(),
1670 MasterPasswordUnlockData {
1671 kdf: Kdf::PBKDF2 {
1672 iterations: NonZeroU32::new(600_000).unwrap(),
1673 },
1674 master_key_wrapped_user_key: user_key,
1675 salt: "[email protected]".to_string(),
1676 },
1677 WrappedAccountCryptographicState::V1 { private_key },
1678 &None,
1679 )
1680 .unwrap();
1681
1682 let public_key: B64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsy7RFHcX3C8Q4/OMmhhbFReYWfB45W9PDTEA8tUZwZmtOiN2RErIS2M1c+K/4HoDJ/TjpbX1f2MZcr4nWvKFuqnZXyewFc+jmvKVewYi+NAu2++vqKq2kKcmMNhwoQDQdQIVy/Uqlp4Cpi2cIwO6ogq5nHNJGR3jm+CpyrafYlbz1bPvL3hbyoGDuG2tgADhyhXUdFuef2oF3wMvn1lAJAvJnPYpMiXUFmj1ejmbwtlxZDrHgUJvUcp7nYdwUKaFoi+sOttHn3u7eZPtNvxMjhSS/X/1xBIzP/mKNLdywH5LoRxniokUk+fV3PYUxJsiU3lV0Trc/tH46jqd8ZGjmwIDAQAB".parse().unwrap();
1683
1684 let encrypted = enroll_admin_password_reset(&client, public_key).unwrap();
1685
1686 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();
1687
1688 let private_key = Pkcs8PrivateKeyBytes::from(private_key.as_bytes());
1689 let private_key = PrivateKey::from_der(&private_key).unwrap();
1690 #[expect(deprecated)]
1691 let decrypted: SymmetricCryptoKey =
1692 encrypted.decapsulate_key_unsigned(&private_key).unwrap();
1693
1694 let key_store = client.internal.get_key_store();
1695 let ctx = key_store.context();
1696 #[allow(deprecated)]
1697 let expected = ctx
1698 .dangerous_get_symmetric_key(SymmetricKeySlotId::User)
1699 .unwrap();
1700
1701 assert_eq!(decrypted, *expected);
1702 }
1703
1704 #[test]
1705 fn test_derive_key_connector() {
1706 let request = DeriveKeyConnectorRequest {
1707 password: "asdfasdfasdf".to_string(),
1708 email: "[email protected]".to_string(),
1709 kdf: Kdf::PBKDF2 {
1710 iterations: NonZeroU32::new(600_000).unwrap(),
1711 },
1712 user_key_encrypted: "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(),
1713 };
1714
1715 let result = derive_key_connector(request).unwrap();
1716
1717 assert_eq!(
1718 result.to_string(),
1719 "ySXq1RVLKEaV1eoQE/ui9aFKIvXTl9PAXwp1MljfF50="
1720 );
1721 }
1722
1723 fn setup_asymmetric_keys_test() -> (UserKey, RsaKeyPair) {
1724 let master_key = MasterKey::derive(
1725 "asdfasdfasdf",
1726 "[email protected]",
1727 &Kdf::PBKDF2 {
1728 iterations: NonZeroU32::new(600_000).unwrap(),
1729 },
1730 )
1731 .unwrap();
1732 let user_key = (master_key.make_user_key().unwrap()).0;
1733 let key_pair = user_key.make_key_pair().unwrap();
1734
1735 (user_key, key_pair)
1736 }
1737
1738 #[test]
1739 fn test_make_key_pair() {
1740 let (user_key, _) = setup_asymmetric_keys_test();
1741
1742 let response = make_key_pair(user_key.0.to_base64()).unwrap();
1743
1744 assert!(!response.user_public_key.to_string().is_empty());
1745 let encrypted_private_key = response.user_key_encrypted_private_key;
1746 let private_key: Vec<u8> = encrypted_private_key.decrypt_with_key(&user_key.0).unwrap();
1747 assert!(!private_key.is_empty());
1748 }
1749
1750 #[test]
1751 fn test_verify_asymmetric_keys_success() {
1752 let (user_key, key_pair) = setup_asymmetric_keys_test();
1753
1754 let request = VerifyAsymmetricKeysRequest {
1755 user_key: user_key.0.to_base64(),
1756 user_public_key: key_pair.public,
1757 user_key_encrypted_private_key: key_pair.private,
1758 };
1759 let response = verify_asymmetric_keys(request).unwrap();
1760
1761 assert!(response.private_key_decryptable);
1762 assert!(response.valid_private_key);
1763 }
1764
1765 #[test]
1766 fn test_verify_asymmetric_keys_decrypt_failed() {
1767 let (user_key, key_pair) = setup_asymmetric_keys_test();
1768 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();
1769
1770 let request = VerifyAsymmetricKeysRequest {
1771 user_key: user_key.0.to_base64(),
1772 user_public_key: key_pair.public,
1773 user_key_encrypted_private_key: undecryptable_private_key,
1774 };
1775 let response = verify_asymmetric_keys(request).unwrap();
1776
1777 assert!(!response.private_key_decryptable);
1778 assert!(!response.valid_private_key);
1779 }
1780
1781 #[test]
1782 fn test_verify_asymmetric_keys_parse_failed() {
1783 let (user_key, key_pair) = setup_asymmetric_keys_test();
1784
1785 let invalid_private_key = "bad_key".to_string().encrypt_with_key(&user_key.0).unwrap();
1786
1787 let request = VerifyAsymmetricKeysRequest {
1788 user_key: user_key.0.to_base64(),
1789 user_public_key: key_pair.public,
1790 user_key_encrypted_private_key: invalid_private_key,
1791 };
1792 let response = verify_asymmetric_keys(request).unwrap();
1793
1794 assert!(response.private_key_decryptable);
1795 assert!(!response.valid_private_key);
1796 }
1797
1798 #[test]
1799 fn test_verify_asymmetric_keys_key_mismatch() {
1800 let (user_key, key_pair) = setup_asymmetric_keys_test();
1801 let new_key_pair = user_key.make_key_pair().unwrap();
1802
1803 let request = VerifyAsymmetricKeysRequest {
1804 user_key: user_key.0.to_base64(),
1805 user_public_key: key_pair.public,
1806 user_key_encrypted_private_key: new_key_pair.private,
1807 };
1808 let response = verify_asymmetric_keys(request).unwrap();
1809
1810 assert!(response.private_key_decryptable);
1811 assert!(!response.valid_private_key);
1812 }
1813
1814 #[tokio::test]
1815 async fn test_make_v2_keys_for_v1_user() {
1816 let client = Client::new_test(None);
1817
1818 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();
1819 let encrypted_userkey: EncString = "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap();
1820
1821 initialize_user_crypto(
1822 &client,
1823 InitUserCryptoRequest {
1824 user_id: Some(UserId::new_v4()),
1825 kdf_params: Kdf::PBKDF2 {
1826 iterations: 100_000.try_into().unwrap(),
1827 },
1828 email: "[email protected]".into(),
1829 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1830 private_key: priv_key.to_owned(),
1831 },
1832 method: InitUserCryptoMethod::MasterPasswordUnlock {
1833 password: "asdfasdfasdf".into(),
1834 master_password_unlock: MasterPasswordUnlockData {
1835 kdf: Kdf::PBKDF2 {
1836 iterations: 100_000.try_into().unwrap(),
1837 },
1838 master_key_wrapped_user_key: encrypted_userkey.clone(),
1839 salt: "[email protected]".into(),
1840 },
1841 },
1842 upgrade_token: None,
1843 },
1844 )
1845 .await
1846 .unwrap();
1847
1848 let master_key = MasterKey::derive(
1849 "asdfasdfasdf",
1850 "[email protected]",
1851 &Kdf::PBKDF2 {
1852 iterations: NonZeroU32::new(100_000).unwrap(),
1853 },
1854 )
1855 .unwrap();
1856 #[expect(deprecated)]
1857 let enrollment_response = make_v2_keys_for_v1_user(&client).unwrap();
1858 let encrypted_userkey_v2 = master_key
1859 .encrypt_user_key(
1860 &SymmetricCryptoKey::try_from(enrollment_response.clone().user_key).unwrap(),
1861 )
1862 .unwrap();
1863
1864 let client2 = Client::new_test(None);
1865
1866 initialize_user_crypto(
1867 &client2,
1868 InitUserCryptoRequest {
1869 user_id: Some(UserId::new_v4()),
1870 kdf_params: Kdf::PBKDF2 {
1871 iterations: 100_000.try_into().unwrap(),
1872 },
1873 email: "[email protected]".into(),
1874 account_cryptographic_state: WrappedAccountCryptographicState::V2 {
1875 private_key: enrollment_response.private_key,
1876 signing_key: enrollment_response.signing_key,
1877 security_state: enrollment_response.security_state,
1878 signed_public_key: Some(enrollment_response.signed_public_key),
1879 },
1880 method: InitUserCryptoMethod::MasterPasswordUnlock {
1881 password: "asdfasdfasdf".into(),
1882 master_password_unlock: MasterPasswordUnlockData {
1883 kdf: Kdf::PBKDF2 {
1884 iterations: 100_000.try_into().unwrap(),
1885 },
1886 master_key_wrapped_user_key: encrypted_userkey_v2,
1887 salt: "[email protected]".to_string(),
1888 },
1889 },
1890 upgrade_token: None,
1891 },
1892 )
1893 .await
1894 .unwrap();
1895 }
1896
1897 #[tokio::test]
1898 async fn test_make_v2_keys_for_v1_user_with_v2_user_fails() {
1899 let client = Client::new_test(None);
1900
1901 initialize_user_crypto(
1902 &client,
1903 InitUserCryptoRequest {
1904 user_id: Some(UserId::new_v4()),
1905 kdf_params: Kdf::PBKDF2 {
1906 iterations: 100_000.try_into().unwrap(),
1907 },
1908 email: "[email protected]".into(),
1909 account_cryptographic_state: WrappedAccountCryptographicState::V2 {
1910 private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
1911 signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
1912 security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
1913 signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
1914 },
1915 method: InitUserCryptoMethod::DecryptedKey {
1916 decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
1917 },
1918 upgrade_token: None,
1919 },
1920 )
1921 .await
1922 .unwrap();
1923
1924 #[expect(deprecated)]
1925 let result = make_v2_keys_for_v1_user(&client);
1926 assert!(matches!(
1927 result,
1928 Err(StatefulCryptoError::WrongAccountCryptoVersion {
1929 expected: _,
1930 got: _
1931 })
1932 ));
1933 }
1934
1935 #[test]
1936 fn test_get_v2_rotated_account_keys_non_v2_user() {
1937 let client = Client::new(None);
1938 let mut ctx = client.internal.get_key_store().context_mut();
1939 let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
1940 ctx.persist_symmetric_key(local_key_id, SymmetricKeySlotId::User)
1941 .unwrap();
1942 drop(ctx);
1943
1944 #[expect(deprecated)]
1945 let result = get_v2_rotated_account_keys(&client);
1946 assert!(matches!(
1947 result,
1948 Err(StatefulCryptoError::WrongAccountCryptoVersion {
1949 expected: _,
1950 got: _
1951 })
1952 ));
1953 }
1954
1955 #[tokio::test]
1956 async fn test_get_v2_rotated_account_keys() {
1957 let client = Client::new_test(None);
1958
1959 initialize_user_crypto(
1960 &client,
1961 InitUserCryptoRequest {
1962 user_id: Some(UserId::new_v4()),
1963 kdf_params: Kdf::PBKDF2 {
1964 iterations: 100_000.try_into().unwrap(),
1965 },
1966 email: "[email protected]".into(),
1967 account_cryptographic_state: WrappedAccountCryptographicState::V2 {
1968 private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
1969 signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
1970 security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
1971 signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
1972 },
1973 method: InitUserCryptoMethod::DecryptedKey {
1974 decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
1975 },
1976 upgrade_token: None,
1977 },
1978 )
1979 .await
1980 .unwrap();
1981
1982 #[expect(deprecated)]
1983 let result = get_v2_rotated_account_keys(&client);
1984 assert!(result.is_ok());
1985 }
1986
1987 #[tokio::test]
1988 async fn test_initialize_user_crypto_master_password_unlock() {
1989 let client = Client::new_test(None);
1990
1991 initialize_user_crypto(
1992 &client,
1993 InitUserCryptoRequest {
1994 user_id: Some(UserId::new_v4()),
1995 kdf_params: Kdf::PBKDF2 {
1996 iterations: 600_000.try_into().unwrap(),
1997 },
1998 email: TEST_USER_EMAIL.to_string(),
1999 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
2000 private_key: TEST_ACCOUNT_PRIVATE_KEY.parse().unwrap(),
2001 },
2002 method: InitUserCryptoMethod::MasterPasswordUnlock {
2003 password: TEST_USER_PASSWORD.to_string(),
2004 master_password_unlock: MasterPasswordUnlockData {
2005 kdf: Kdf::PBKDF2 {
2006 iterations: 600_000.try_into().unwrap(),
2007 },
2008 master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
2009 salt: TEST_USER_EMAIL.to_string(),
2010 },
2011 },
2012 upgrade_token: None,
2013 },
2014 )
2015 .await
2016 .unwrap();
2017
2018 let key_store = client.internal.get_key_store();
2019 {
2020 let context = key_store.context();
2021 assert!(context.has_symmetric_key(SymmetricKeySlotId::User));
2022 assert!(context.has_private_key(PrivateKeySlotId::UserPrivateKey));
2023 }
2024 let login_method = client.internal.get_login_method().await.unwrap();
2025 if let UserLoginMethod::Username {
2026 email,
2027 kdf,
2028 client_id,
2029 ..
2030 } = login_method
2031 {
2032 assert_eq!(&email, TEST_USER_EMAIL);
2033 assert_eq!(
2034 kdf,
2035 Kdf::PBKDF2 {
2036 iterations: 600_000.try_into().unwrap(),
2037 }
2038 );
2039 assert_eq!(&client_id, "");
2040 } else {
2041 panic!("Expected username login method");
2042 }
2043 }
2044
2045 #[tokio::test]
2046 async fn test_make_user_tde_registration() {
2047 let user_id = UserId::new_v4();
2048 let email = "[email protected]";
2049 let kdf = Kdf::PBKDF2 {
2050 iterations: NonZeroU32::new(600_000).expect("valid iteration count"),
2051 };
2052
2053 let org_key = PrivateKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
2055 let org_public_key_der = org_key
2056 .to_public_key()
2057 .to_der()
2058 .expect("valid public key DER");
2059 let org_public_key = B64::from(org_public_key_der.as_ref().to_vec());
2060
2061 let registration_client = Client::new_test(None);
2063 let make_keys_response = registration_client
2064 .crypto()
2065 .make_user_tde_registration(org_public_key)
2066 .expect("TDE registration should succeed");
2067
2068 let unlock_client = Client::new_test(None);
2070 unlock_client
2071 .crypto()
2072 .initialize_user_crypto(InitUserCryptoRequest {
2073 user_id: Some(user_id),
2074 kdf_params: kdf,
2075 email: email.to_owned(),
2076 account_cryptographic_state: make_keys_response.account_cryptographic_state,
2077 method: InitUserCryptoMethod::DeviceKey {
2078 device_key: make_keys_response
2079 .trusted_device_keys
2080 .device_key
2081 .to_string(),
2082 protected_device_private_key: make_keys_response
2083 .trusted_device_keys
2084 .protected_device_private_key,
2085 device_protected_user_key: make_keys_response
2086 .trusted_device_keys
2087 .protected_user_key,
2088 },
2089 upgrade_token: None,
2090 })
2091 .await
2092 .expect("initializing user crypto with TDE device key should succeed");
2093
2094 let retrieved_key = unlock_client
2096 .crypto()
2097 .get_user_encryption_key()
2098 .await
2099 .expect("should be able to get user encryption key");
2100
2101 let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
2103 .expect("retrieved key should be valid symmetric key");
2104
2105 #[expect(deprecated)]
2108 let decrypted_user_key = make_keys_response
2109 .reset_password_key
2110 .decapsulate_key_unsigned(&org_key)
2111 .expect("org key should be able to decrypt admin reset key");
2112 assert_eq!(
2113 retrieved_symmetric_key, decrypted_user_key,
2114 "decrypted admin reset key should match the user's encryption key"
2115 );
2116 }
2117
2118 #[tokio::test]
2119 async fn test_make_user_key_connector_registration_success() {
2120 let user_id = UserId::new_v4();
2121 let email = "[email protected]";
2122 let registration_client = Client::new(None);
2123
2124 let make_keys_response = make_user_key_connector_registration(®istration_client);
2125 assert!(make_keys_response.is_ok());
2126 let make_keys_response = make_keys_response.unwrap();
2127
2128 let unlock_client = Client::new_test(None);
2130 unlock_client
2131 .crypto()
2132 .initialize_user_crypto(InitUserCryptoRequest {
2133 user_id: Some(user_id),
2134 kdf_params: Kdf::default_argon2(),
2135 email: email.to_owned(),
2136 account_cryptographic_state: make_keys_response.account_cryptographic_state,
2137 method: InitUserCryptoMethod::KeyConnector {
2138 user_key: make_keys_response
2139 .key_connector_key_wrapped_user_key
2140 .clone(),
2141 master_key: make_keys_response.key_connector_key.clone().into(),
2142 },
2143 upgrade_token: None,
2144 })
2145 .await
2146 .expect("initializing user crypto with key connector key should succeed");
2147
2148 let retrieved_key = unlock_client
2150 .crypto()
2151 .get_user_encryption_key()
2152 .await
2153 .expect("should be able to get user encryption key");
2154
2155 let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
2157 .expect("retrieved key should be valid symmetric key");
2158
2159 assert_eq!(retrieved_symmetric_key, make_keys_response.user_key);
2160
2161 let decrypted_user_key = make_keys_response
2162 .key_connector_key
2163 .decrypt_user_key(make_keys_response.key_connector_key_wrapped_user_key);
2164 assert_eq!(retrieved_symmetric_key, decrypted_user_key.unwrap());
2165 }
2166
2167 #[tokio::test]
2168 async fn test_initialize_user_crypto_with_upgrade_token_upgrades_v1_to_v2() {
2169 let client1 = Client::init_test_account(test_bitwarden_com_account()).await;
2170
2171 let expected_v2_key =
2172 SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
2173 let upgrade_token = {
2174 let mut ctx = client1.internal.get_key_store().context_mut();
2175 let v2_key_id = ctx.add_local_symmetric_key(expected_v2_key.clone());
2176 V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap()
2177 };
2178
2179 let client2 = Client::new_test(None);
2180 initialize_user_crypto(
2181 &client2,
2182 InitUserCryptoRequest {
2183 user_id: Some(UserId::new_v4()),
2184 kdf_params: Kdf::PBKDF2 {
2185 iterations: 600_000.try_into().unwrap(),
2186 },
2187 email: "[email protected]".into(),
2188 account_cryptographic_state: WrappedAccountCryptographicState::V2 {
2189 private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
2190 signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
2191 security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
2192 signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
2193 },
2194 method: InitUserCryptoMethod::MasterPasswordUnlock {
2195 password: "asdfasdfasdf".into(),
2196 master_password_unlock: MasterPasswordUnlockData {
2197 kdf: Kdf::PBKDF2 {
2198 iterations: 600_000.try_into().unwrap(),
2199 },
2200 master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
2201 salt: "[email protected]".to_string(),
2202 },
2203 },
2204 upgrade_token: Some(upgrade_token),
2205 },
2206 )
2207 .await
2208 .unwrap();
2209
2210 let result_key =
2212 SymmetricCryptoKey::try_from(get_user_encryption_key(&client2).await.unwrap()).unwrap();
2213 assert!(
2214 matches!(result_key, SymmetricCryptoKey::XChaCha20Poly1305Key(_)),
2215 "User key should be upgraded to V2 after initialization with upgrade token"
2216 );
2217 assert_eq!(result_key, expected_v2_key);
2218 }
2219
2220 #[tokio::test]
2221 async fn test_initialize_user_crypto_with_upgrade_token_ignored_for_v2_key() {
2222 let dummy_token = {
2223 let key_store = KeyStore::<KeySlotIds>::default();
2224 let mut ctx = key_store.context_mut();
2225 let v1_id = ctx.generate_symmetric_key();
2226 let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
2227 V2UpgradeToken::create(v1_id, v2_id, &ctx).unwrap()
2228 };
2229
2230 let client = Client::new_test(None);
2231 initialize_user_crypto(
2232 &client,
2233 InitUserCryptoRequest {
2234 user_id: Some(UserId::new_v4()),
2235 kdf_params: Kdf::PBKDF2 {
2236 iterations: 100_000.try_into().unwrap(),
2237 },
2238 email: "[email protected]".into(),
2239 account_cryptographic_state: WrappedAccountCryptographicState::V2 {
2240 private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
2241 signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
2242 security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
2243 signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
2244 },
2245 method: InitUserCryptoMethod::DecryptedKey {
2246 decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
2247 },
2248 upgrade_token: Some(dummy_token),
2249 },
2250 )
2251 .await
2252 .unwrap();
2253
2254 let result_key =
2256 SymmetricCryptoKey::try_from(get_user_encryption_key(&client).await.unwrap()).unwrap();
2257 assert!(
2258 matches!(result_key, SymmetricCryptoKey::XChaCha20Poly1305Key(_)),
2259 "Upgrade token must be ignored for a V2 user key"
2260 );
2261 let expected_key =
2262 SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
2263 assert_eq!(result_key, expected_key);
2264 }
2265
2266 #[tokio::test]
2267 async fn test_initialize_user_crypto_with_invalid_upgrade_token_fails() {
2268 let mismatched_token = {
2270 let key_store = KeyStore::<KeySlotIds>::default();
2271 let mut ctx = key_store.context_mut();
2272 let wrong_v1_id = ctx.generate_symmetric_key();
2273 let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
2274 V2UpgradeToken::create(wrong_v1_id, v2_id, &ctx).unwrap()
2275 };
2276
2277 let client = Client::new_test(None);
2278 let result = initialize_user_crypto(
2279 &client,
2280 InitUserCryptoRequest {
2281 user_id: Some(UserId::new_v4()),
2282 kdf_params: Kdf::PBKDF2 {
2283 iterations: 600_000.try_into().unwrap(),
2284 },
2285 email: "[email protected]".into(),
2286 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
2287 private_key: TEST_ACCOUNT_PRIVATE_KEY.parse().unwrap(),
2289 },
2290 method: InitUserCryptoMethod::MasterPasswordUnlock {
2291 password: "asdfasdfasdf".into(),
2292 master_password_unlock: MasterPasswordUnlockData {
2293 kdf: Kdf::PBKDF2 {
2294 iterations: 600_000.try_into().unwrap(),
2295 },
2296 master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
2297 salt: "[email protected]".to_string(),
2298 },
2299 },
2300 upgrade_token: Some(mismatched_token),
2301 },
2302 )
2303 .await;
2304
2305 assert!(
2306 matches!(result, Err(EncryptionSettingsError::InvalidUpgradeToken)),
2307 "Initialization with a mismatched upgrade token should fail"
2308 );
2309 }
2310
2311 #[tokio::test]
2312 async fn test_initialize_user_local_data_key_sets_local_user_data_key_equal_to_user_key() {
2313 let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
2314 initialize_user_local_data_key(&client)
2315 .await
2316 .expect("initialize_user_local_data_key should succeed");
2317
2318 let key_store = client.internal.get_key_store();
2321 let mut ctx = key_store.context_mut();
2322 let plaintext = "test";
2323 let ciphertext = plaintext
2324 .encrypt(&mut ctx, SymmetricKeySlotId::User)
2325 .expect("encryption with user key should succeed");
2326 let decrypted: String = ciphertext
2327 .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2328 .expect("decryption with local user data key should succeed");
2329 assert_eq!(decrypted, plaintext);
2330 }
2331
2332 #[tokio::test]
2333 async fn test_initialize_org_crypto_persists_org_keys() {
2334 use crate::{OrganizationId, client::persisted_state::OrganizationSharedKey};
2335
2336 let client = Client::init_test_account(test_bitwarden_com_account()).await;
2337
2338 let org_id: OrganizationId = "1bc9ac1e-f5aa-45f2-94bf-b181009709b8".parse().unwrap();
2339
2340 let repo = client
2341 .internal
2342 .state_registry
2343 .get::<OrganizationSharedKey>()
2344 .expect("OrganizationSharedKey repository should be available");
2345
2346 let persisted = repo
2347 .get(org_id)
2348 .await
2349 .expect("repository get should not fail");
2350
2351 let entry = persisted.expect("org key should be persisted after initialize_org_crypto");
2352 assert_eq!(entry.org_id, org_id);
2353 }
2354
2355 #[tokio::test]
2356 async fn test_initialize_user_crypto_persists_account_crypto_state() {
2357 use crate::client::persisted_state::ACCOUNT_CRYPTO_STATE;
2358
2359 let account_crypto_state = WrappedAccountCryptographicState::V1 {
2360 private_key: TEST_ACCOUNT_PRIVATE_KEY.parse().unwrap(),
2361 };
2362
2363 let client = Client::new_test(None);
2364 initialize_user_crypto(
2365 &client,
2366 InitUserCryptoRequest {
2367 user_id: Some(UserId::new_v4()),
2368 kdf_params: Kdf::PBKDF2 {
2369 iterations: 600_000.try_into().unwrap(),
2370 },
2371 email: TEST_USER_EMAIL.into(),
2372 account_cryptographic_state: account_crypto_state.clone(),
2373 method: InitUserCryptoMethod::MasterPasswordUnlock {
2374 password: TEST_USER_PASSWORD.into(),
2375 master_password_unlock: MasterPasswordUnlockData {
2376 kdf: Kdf::PBKDF2 {
2377 iterations: 600_000.try_into().unwrap(),
2378 },
2379 master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
2380 salt: TEST_USER_EMAIL.to_string(),
2381 },
2382 },
2383 upgrade_token: None,
2384 },
2385 )
2386 .await
2387 .unwrap();
2388
2389 let persisted = client
2390 .internal
2391 .state_registry
2392 .setting(ACCOUNT_CRYPTO_STATE)
2393 .expect("ACCOUNT_CRYPTO_STATE setting should be available")
2394 .get()
2395 .await
2396 .expect("setting get should not fail");
2397
2398 assert_eq!(persisted, Some(account_crypto_state));
2399 }
2400
2401 #[tokio::test]
2402 async fn test_initialize_user_local_data_key_idempotent() {
2403 let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
2404 initialize_user_local_data_key(&client)
2405 .await
2406 .expect("first initialization should succeed");
2407
2408 let ciphertext = {
2410 let key_store = client.internal.get_key_store();
2411 let mut ctx = key_store.context_mut();
2412 "test"
2413 .encrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2414 .expect("encryption should succeed")
2415 };
2416
2417 initialize_user_local_data_key(&client)
2418 .await
2419 .expect("second initialization should succeed");
2420
2421 let key_store = client.internal.get_key_store();
2424 let mut ctx = key_store.context_mut();
2425 let decrypted: String = ciphertext
2426 .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
2427 .expect("decryption after second initialization should succeed");
2428 assert_eq!(decrypted, "test");
2429 }
2430
2431 #[tokio::test]
2432 async fn test_make_user_password_registration() {
2433 let user_id = UserId::new_v4();
2434 let registration_client = Client::new(None);
2435
2436 let make_keys_response = registration_client
2437 .crypto()
2438 .make_user_password_registration(
2439 TEST_USER_PASSWORD.to_string(),
2440 TEST_USER_EMAIL.to_string(),
2441 )
2442 .expect("user password registration should succeed");
2443
2444 let unlock_client = Client::new_test(None);
2445 unlock_client
2446 .crypto()
2447 .initialize_user_crypto(InitUserCryptoRequest {
2448 user_id: Some(user_id),
2449 kdf_params: Kdf::default_argon2(),
2450 email: TEST_USER_EMAIL.to_string(),
2451 account_cryptographic_state: make_keys_response.account_cryptographic_state,
2452 method: InitUserCryptoMethod::MasterPasswordUnlock {
2453 password: TEST_USER_PASSWORD.to_string(),
2454 master_password_unlock: make_keys_response.master_password_unlock_data.clone(),
2455 },
2456 upgrade_token: None,
2457 })
2458 .await
2459 .expect("initializing user crypto with master password should succeed");
2460
2461 let retrieved_key = unlock_client
2462 .crypto()
2463 .get_user_encryption_key()
2464 .await
2465 .expect("should be able to get user encryption key");
2466
2467 let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
2468 .expect("retrieved key should be valid symmetric key");
2469
2470 let master_key = MasterKey::derive(
2471 TEST_USER_PASSWORD,
2472 TEST_USER_EMAIL,
2473 &make_keys_response.master_password_unlock_data.kdf,
2474 )
2475 .expect("master key should derive");
2476
2477 let decrypted_user_key = master_key
2478 .decrypt_user_key(
2479 make_keys_response
2480 .master_password_unlock_data
2481 .master_key_wrapped_user_key
2482 .clone(),
2483 )
2484 .expect("should decrypt user key");
2485
2486 assert_eq!(retrieved_symmetric_key, decrypted_user_key);
2487 }
2488}