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