1use std::collections::HashMap;
8
9use bitwarden_api_api::models::AccountKeysRequestModel;
10#[expect(deprecated)]
11use bitwarden_crypto::{
12 CoseSerializable, CryptoError, DeviceKey, EncString, Kdf, KeyConnectorKey, KeyDecryptable,
13 KeyEncryptable, MasterKey, Pkcs8PrivateKeyBytes, PrimitiveEncryptable, PrivateKey, PublicKey,
14 RotateableKeySet, SignatureAlgorithm, SignedPublicKey, SigningKey, SpkiPublicKeyBytes,
15 SymmetricCryptoKey, TrustDeviceResponse, UnsignedSharedKey, UserKey,
16 dangerous_get_v2_rotated_account_keys, derive_symmetric_key_from_prf,
17 safe::{PasswordProtectedKeyEnvelope, PasswordProtectedKeyEnvelopeError},
18};
19use bitwarden_encoding::B64;
20use bitwarden_error::bitwarden_error;
21use schemars::JsonSchema;
22use serde::{Deserialize, Serialize};
23use tracing::info;
24#[cfg(feature = "wasm")]
25use {tsify::Tsify, wasm_bindgen::prelude::*};
26
27use crate::{
28 Client, NotAuthenticatedError, OrganizationId, UserId, WrongPasswordError,
29 client::{LoginMethod, UserLoginMethod, encryption_settings::EncryptionSettingsError},
30 error::StatefulCryptoError,
31 key_management::{
32 MasterPasswordError, PrivateKeyId, SecurityState, SignedSecurityState, SigningKeyId,
33 SymmetricKeyId,
34 account_cryptographic_state::{
35 AccountCryptographyInitializationError, WrappedAccountCryptographicState,
36 },
37 master_password::{MasterPasswordAuthenticationData, MasterPasswordUnlockData},
38 },
39};
40
41#[allow(missing_docs)]
43#[bitwarden_error(flat)]
44#[derive(Debug, thiserror::Error)]
45pub enum CryptoClientError {
46 #[error(transparent)]
47 NotAuthenticated(#[from] NotAuthenticatedError),
48 #[error(transparent)]
49 Crypto(#[from] bitwarden_crypto::CryptoError),
50 #[error("Invalid KDF settings")]
51 InvalidKdfSettings,
52 #[error(transparent)]
53 PasswordProtectedKeyEnvelope(#[from] PasswordProtectedKeyEnvelopeError),
54 #[error("Invalid PRF input")]
55 InvalidPrfInput,
56}
57
58#[derive(Serialize, Deserialize, Debug)]
60#[serde(rename_all = "camelCase", deny_unknown_fields)]
61#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
62#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
63pub struct InitUserCryptoRequest {
64 pub user_id: Option<UserId>,
66 pub kdf_params: Kdf,
68 pub email: String,
70 pub account_cryptographic_state: WrappedAccountCryptographicState,
73 pub method: InitUserCryptoMethod,
75}
76
77#[derive(Serialize, Deserialize, Debug)]
79#[serde(rename_all = "camelCase", deny_unknown_fields)]
80#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
81#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
82#[allow(clippy::large_enum_variant)]
83pub enum InitUserCryptoMethod {
84 MasterPasswordUnlock {
86 password: String,
88 master_password_unlock: MasterPasswordUnlockData,
90 },
91 DecryptedKey {
93 decrypted_user_key: String,
95 },
96 Pin {
98 pin: String,
100 pin_protected_user_key: EncString,
103 },
104 PinEnvelope {
106 pin: String,
108 pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
110 },
111 AuthRequest {
113 request_private_key: B64,
115 method: AuthRequestMethod,
117 },
118 DeviceKey {
120 device_key: String,
122 protected_device_private_key: EncString,
124 device_protected_user_key: UnsignedSharedKey,
126 },
127 KeyConnector {
129 master_key: B64,
131 user_key: EncString,
133 },
134}
135
136#[derive(Serialize, Deserialize, Debug)]
138#[serde(rename_all = "camelCase", deny_unknown_fields)]
139#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
140#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
141pub enum AuthRequestMethod {
142 UserKey {
144 protected_user_key: UnsignedSharedKey,
146 },
147 MasterKey {
149 protected_master_key: UnsignedSharedKey,
151 auth_request_key: EncString,
153 },
154}
155
156pub(super) async fn initialize_user_crypto(
158 client: &Client,
159 req: InitUserCryptoRequest,
160) -> Result<(), EncryptionSettingsError> {
161 use bitwarden_crypto::{DeviceKey, PinKey};
162
163 use crate::auth::{auth_request_decrypt_master_key, auth_request_decrypt_user_key};
164
165 if let Some(user_id) = req.user_id {
166 client.internal.init_user_id(user_id)?;
167 }
168
169 let account_crypto_state = req.account_cryptographic_state.to_owned();
170 let _span_guard = tracing::info_span!(
171 "User Crypto Initialization",
172 user_id = ?client.internal.get_user_id(),
173 )
174 .entered();
175
176 match req.method {
177 InitUserCryptoMethod::MasterPasswordUnlock {
178 password,
179 master_password_unlock,
180 } => {
181 client
182 .internal
183 .initialize_user_crypto_master_password_unlock(
184 password,
185 master_password_unlock,
186 account_crypto_state,
187 )?;
188 }
189 InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => {
190 let user_key = SymmetricCryptoKey::try_from(decrypted_user_key)?;
191 client
192 .internal
193 .initialize_user_crypto_decrypted_key(user_key, account_crypto_state)?;
194 }
195 InitUserCryptoMethod::Pin {
196 pin,
197 pin_protected_user_key,
198 } => {
199 let pin_key = PinKey::derive(pin.as_bytes(), req.email.as_bytes(), &req.kdf_params)?;
200 client.internal.initialize_user_crypto_pin(
201 pin_key,
202 pin_protected_user_key,
203 account_crypto_state,
204 )?;
205 }
206 InitUserCryptoMethod::PinEnvelope {
207 pin,
208 pin_protected_user_key_envelope,
209 } => {
210 client.internal.initialize_user_crypto_pin_envelope(
211 pin,
212 pin_protected_user_key_envelope,
213 account_crypto_state,
214 )?;
215 }
216 InitUserCryptoMethod::AuthRequest {
217 request_private_key,
218 method,
219 } => {
220 let user_key = match method {
221 AuthRequestMethod::UserKey { protected_user_key } => {
222 auth_request_decrypt_user_key(request_private_key, protected_user_key)?
223 }
224 AuthRequestMethod::MasterKey {
225 protected_master_key,
226 auth_request_key,
227 } => auth_request_decrypt_master_key(
228 request_private_key,
229 protected_master_key,
230 auth_request_key,
231 )?,
232 };
233 client
234 .internal
235 .initialize_user_crypto_decrypted_key(user_key, account_crypto_state)?;
236 }
237 InitUserCryptoMethod::DeviceKey {
238 device_key,
239 protected_device_private_key,
240 device_protected_user_key,
241 } => {
242 let device_key = DeviceKey::try_from(device_key)?;
243 let user_key = device_key
244 .decrypt_user_key(protected_device_private_key, device_protected_user_key)?;
245
246 client
247 .internal
248 .initialize_user_crypto_decrypted_key(user_key, account_crypto_state)?;
249 }
250 InitUserCryptoMethod::KeyConnector {
251 master_key,
252 user_key,
253 } => {
254 let mut bytes = master_key.into_bytes();
255 let master_key = MasterKey::try_from(bytes.as_mut_slice())?;
256
257 client.internal.initialize_user_crypto_key_connector_key(
258 master_key,
259 user_key,
260 account_crypto_state,
261 )?;
262 }
263 }
264
265 info!("User crypto initialized successfully");
266
267 client
268 .internal
269 .set_login_method(LoginMethod::User(UserLoginMethod::Username {
270 client_id: "".to_string(),
271 email: req.email,
272 kdf: req.kdf_params,
273 }));
274
275 Ok(())
276}
277
278#[derive(Serialize, Deserialize, Debug)]
280#[serde(rename_all = "camelCase", deny_unknown_fields)]
281#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
282#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
283pub struct InitOrgCryptoRequest {
284 pub organization_keys: HashMap<OrganizationId, UnsignedSharedKey>,
286}
287
288pub(super) async fn initialize_org_crypto(
290 client: &Client,
291 req: InitOrgCryptoRequest,
292) -> Result<(), EncryptionSettingsError> {
293 let organization_keys = req.organization_keys.into_iter().collect();
294 client.internal.initialize_org_crypto(organization_keys)?;
295 Ok(())
296}
297
298pub(super) async fn get_user_encryption_key(client: &Client) -> Result<B64, CryptoClientError> {
299 let key_store = client.internal.get_key_store();
300 let ctx = key_store.context();
301 #[allow(deprecated)]
303 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
304
305 Ok(user_key.to_base64())
306}
307
308#[derive(Serialize, Deserialize, Debug)]
310#[serde(rename_all = "camelCase", deny_unknown_fields)]
311#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
312#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
313pub struct UpdateKdfResponse {
314 master_password_authentication_data: MasterPasswordAuthenticationData,
316 master_password_unlock_data: MasterPasswordUnlockData,
318 old_master_password_authentication_data: MasterPasswordAuthenticationData,
320}
321
322pub(super) fn make_update_kdf(
323 client: &Client,
324 password: &str,
325 new_kdf: &Kdf,
326) -> Result<UpdateKdfResponse, CryptoClientError> {
327 let key_store = client.internal.get_key_store();
328 let ctx = key_store.context();
329
330 let login_method = client
331 .internal
332 .get_login_method()
333 .ok_or(NotAuthenticatedError)?;
334 let email = match login_method.as_ref() {
335 LoginMethod::User(
336 UserLoginMethod::Username { email, .. } | UserLoginMethod::ApiKey { email, .. },
337 ) => email,
338 #[cfg(feature = "secrets")]
339 LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?,
340 };
341
342 let authentication_data = MasterPasswordAuthenticationData::derive(password, new_kdf, email)
343 .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
344 let unlock_data =
345 MasterPasswordUnlockData::derive(password, new_kdf, email, SymmetricKeyId::User, &ctx)
346 .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
347 let old_authentication_data = MasterPasswordAuthenticationData::derive(
348 password,
349 &client
350 .internal
351 .get_kdf()
352 .map_err(|_| NotAuthenticatedError)?,
353 email,
354 )
355 .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
356
357 Ok(UpdateKdfResponse {
358 master_password_authentication_data: authentication_data,
359 master_password_unlock_data: unlock_data,
360 old_master_password_authentication_data: old_authentication_data,
361 })
362}
363
364#[derive(Serialize, Deserialize, Debug)]
366#[serde(rename_all = "camelCase", deny_unknown_fields)]
367#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
368#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
369pub struct UpdatePasswordResponse {
370 password_hash: B64,
372 new_key: EncString,
374}
375
376pub(super) fn make_update_password(
377 client: &Client,
378 new_password: String,
379) -> Result<UpdatePasswordResponse, CryptoClientError> {
380 let key_store = client.internal.get_key_store();
381 let ctx = key_store.context();
382 #[allow(deprecated)]
384 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
385
386 let login_method = client
387 .internal
388 .get_login_method()
389 .ok_or(NotAuthenticatedError)?;
390
391 let new_master_key = match login_method.as_ref() {
393 LoginMethod::User(
394 UserLoginMethod::Username { email, kdf, .. }
395 | UserLoginMethod::ApiKey { email, kdf, .. },
396 ) => MasterKey::derive(&new_password, email, kdf)?,
397 #[cfg(feature = "secrets")]
398 LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?,
399 };
400
401 let new_key = new_master_key.encrypt_user_key(user_key)?;
402
403 let password_hash = new_master_key.derive_master_key_hash(
404 new_password.as_bytes(),
405 bitwarden_crypto::HashPurpose::ServerAuthorization,
406 );
407
408 Ok(UpdatePasswordResponse {
409 password_hash,
410 new_key,
411 })
412}
413
414#[derive(Serialize, Deserialize, Debug)]
416#[serde(rename_all = "camelCase", deny_unknown_fields)]
417#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
418#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
419pub struct EnrollPinResponse {
420 pub pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
422 pub user_key_encrypted_pin: EncString,
424}
425
426pub(super) fn enroll_pin(
427 client: &Client,
428 pin: String,
429) -> Result<EnrollPinResponse, CryptoClientError> {
430 let key_store = client.internal.get_key_store();
431 let mut ctx = key_store.context_mut();
432
433 let key_envelope = PasswordProtectedKeyEnvelope::seal(SymmetricKeyId::User, &pin, &ctx)?;
434 let encrypted_pin = pin.encrypt(&mut ctx, SymmetricKeyId::User)?;
435 Ok(EnrollPinResponse {
436 pin_protected_user_key_envelope: key_envelope,
437 user_key_encrypted_pin: encrypted_pin,
438 })
439}
440
441#[derive(Serialize, Deserialize, Debug)]
443#[serde(rename_all = "camelCase", deny_unknown_fields)]
444#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
445#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
446pub struct DerivePinKeyResponse {
447 pin_protected_user_key: EncString,
449 encrypted_pin: EncString,
451}
452
453pub(super) fn derive_pin_key(
454 client: &Client,
455 pin: String,
456) -> Result<DerivePinKeyResponse, CryptoClientError> {
457 let key_store = client.internal.get_key_store();
458 let ctx = key_store.context();
459 #[allow(deprecated)]
461 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
462
463 let login_method = client
464 .internal
465 .get_login_method()
466 .ok_or(NotAuthenticatedError)?;
467
468 let pin_protected_user_key = derive_pin_protected_user_key(&pin, &login_method, user_key)?;
469
470 Ok(DerivePinKeyResponse {
471 pin_protected_user_key,
472 encrypted_pin: pin.encrypt_with_key(user_key)?,
473 })
474}
475
476pub(super) fn derive_pin_user_key(
477 client: &Client,
478 encrypted_pin: EncString,
479) -> Result<EncString, CryptoClientError> {
480 let key_store = client.internal.get_key_store();
481 let ctx = key_store.context();
482 #[allow(deprecated)]
484 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
485
486 let pin: String = encrypted_pin.decrypt_with_key(user_key)?;
487 let login_method = client
488 .internal
489 .get_login_method()
490 .ok_or(NotAuthenticatedError)?;
491
492 derive_pin_protected_user_key(&pin, &login_method, user_key)
493}
494
495fn derive_pin_protected_user_key(
496 pin: &str,
497 login_method: &LoginMethod,
498 user_key: &SymmetricCryptoKey,
499) -> Result<EncString, CryptoClientError> {
500 use bitwarden_crypto::PinKey;
501
502 let derived_key = match login_method {
503 LoginMethod::User(
504 UserLoginMethod::Username { email, kdf, .. }
505 | UserLoginMethod::ApiKey { email, kdf, .. },
506 ) => PinKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?,
507 #[cfg(feature = "secrets")]
508 LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?,
509 };
510
511 Ok(derived_key.encrypt_user_key(user_key)?)
512}
513
514pub(super) fn make_prf_user_key_set(
515 client: &Client,
516 prf: B64,
517) -> Result<RotateableKeySet, CryptoClientError> {
518 let prf_key = derive_symmetric_key_from_prf(prf.as_bytes())
519 .map_err(|_| CryptoClientError::InvalidPrfInput)?;
520 let ctx = client.internal.get_key_store().context();
521 let key_set = RotateableKeySet::new(&ctx, &prf_key, SymmetricKeyId::User)?;
522 Ok(key_set)
523}
524
525#[allow(missing_docs)]
526#[bitwarden_error(flat)]
527#[derive(Debug, thiserror::Error)]
528pub enum EnrollAdminPasswordResetError {
529 #[error(transparent)]
530 Crypto(#[from] bitwarden_crypto::CryptoError),
531}
532
533pub(super) fn enroll_admin_password_reset(
534 client: &Client,
535 public_key: B64,
536) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
537 use bitwarden_crypto::PublicKey;
538
539 let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&public_key))?;
540 let key_store = client.internal.get_key_store();
541 let ctx = key_store.context();
542 #[allow(deprecated)]
544 let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
545
546 #[expect(deprecated)]
547 Ok(UnsignedSharedKey::encapsulate_key_unsigned(
548 key,
549 &public_key,
550 )?)
551}
552
553#[derive(Serialize, Deserialize, Debug, JsonSchema)]
555#[serde(rename_all = "camelCase", deny_unknown_fields)]
556#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
557#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
558pub struct DeriveKeyConnectorRequest {
559 pub user_key_encrypted: EncString,
561 pub password: String,
563 pub kdf: Kdf,
565 pub email: String,
567}
568
569#[allow(missing_docs)]
570#[bitwarden_error(flat)]
571#[derive(Debug, thiserror::Error)]
572pub enum DeriveKeyConnectorError {
573 #[error(transparent)]
574 WrongPassword(#[from] WrongPasswordError),
575 #[error(transparent)]
576 Crypto(#[from] bitwarden_crypto::CryptoError),
577}
578
579pub(super) fn derive_key_connector(
581 request: DeriveKeyConnectorRequest,
582) -> Result<B64, DeriveKeyConnectorError> {
583 let master_key = MasterKey::derive(&request.password, &request.email, &request.kdf)?;
584 master_key
585 .decrypt_user_key(request.user_key_encrypted)
586 .map_err(|_| WrongPasswordError)?;
587
588 Ok(master_key.to_base64())
589}
590
591#[derive(Serialize, Deserialize, Debug)]
593#[serde(rename_all = "camelCase", deny_unknown_fields)]
594#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
595#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
596pub struct MakeKeyPairResponse {
597 user_public_key: B64,
599 user_key_encrypted_private_key: EncString,
601}
602
603pub(super) fn make_key_pair(user_key: B64) -> Result<MakeKeyPairResponse, CryptoError> {
604 let user_key = UserKey::new(SymmetricCryptoKey::try_from(user_key)?);
605
606 let key_pair = user_key.make_key_pair()?;
607
608 Ok(MakeKeyPairResponse {
609 user_public_key: key_pair.public,
610 user_key_encrypted_private_key: key_pair.private,
611 })
612}
613
614#[derive(Serialize, Deserialize, Debug)]
616#[serde(rename_all = "camelCase", deny_unknown_fields)]
617#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
618#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
619pub struct VerifyAsymmetricKeysRequest {
620 user_key: B64,
622 user_public_key: B64,
624 user_key_encrypted_private_key: EncString,
626}
627
628#[derive(Serialize, Deserialize, Debug)]
630#[serde(rename_all = "camelCase", deny_unknown_fields)]
631#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
632#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
633pub struct VerifyAsymmetricKeysResponse {
634 private_key_decryptable: bool,
636 valid_private_key: bool,
638}
639
640pub(super) fn verify_asymmetric_keys(
641 request: VerifyAsymmetricKeysRequest,
642) -> Result<VerifyAsymmetricKeysResponse, CryptoError> {
643 #[derive(Debug, thiserror::Error)]
644 enum VerifyError {
645 #[error("Failed to decrypt private key: {0:?}")]
646 DecryptFailed(bitwarden_crypto::CryptoError),
647 #[error("Failed to parse decrypted private key: {0:?}")]
648 ParseFailed(bitwarden_crypto::CryptoError),
649 #[error("Failed to derive a public key: {0:?}")]
650 PublicFailed(bitwarden_crypto::CryptoError),
651 #[error("Derived public key doesn't match")]
652 KeyMismatch,
653 }
654
655 fn verify_inner(
656 user_key: &SymmetricCryptoKey,
657 request: &VerifyAsymmetricKeysRequest,
658 ) -> Result<(), VerifyError> {
659 let decrypted_private_key: Vec<u8> = request
660 .user_key_encrypted_private_key
661 .decrypt_with_key(user_key)
662 .map_err(VerifyError::DecryptFailed)?;
663
664 let decrypted_private_key = Pkcs8PrivateKeyBytes::from(decrypted_private_key);
665 let private_key =
666 PrivateKey::from_der(&decrypted_private_key).map_err(VerifyError::ParseFailed)?;
667
668 let derived_public_key_vec = private_key
669 .to_public_key()
670 .to_der()
671 .map_err(VerifyError::PublicFailed)?;
672
673 let derived_public_key = B64::from(derived_public_key_vec);
674
675 if derived_public_key != request.user_public_key {
676 return Err(VerifyError::KeyMismatch);
677 }
678 Ok(())
679 }
680
681 let user_key = SymmetricCryptoKey::try_from(request.user_key.clone())?;
682
683 Ok(match verify_inner(&user_key, &request) {
684 Ok(_) => VerifyAsymmetricKeysResponse {
685 private_key_decryptable: true,
686 valid_private_key: true,
687 },
688 Err(error) => {
689 tracing::debug!(%error, "User asymmetric keys verification");
690
691 VerifyAsymmetricKeysResponse {
692 private_key_decryptable: !matches!(error, VerifyError::DecryptFailed(_)),
693 valid_private_key: false,
694 }
695 }
696 })
697}
698
699#[derive(Serialize, Deserialize, Debug, Clone)]
701#[serde(rename_all = "camelCase", deny_unknown_fields)]
702#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
703#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
704pub struct UserCryptoV2KeysResponse {
705 user_key: B64,
707
708 private_key: EncString,
710 public_key: B64,
712 signed_public_key: SignedPublicKey,
714
715 signing_key: EncString,
717 verifying_key: B64,
719
720 security_state: SignedSecurityState,
722 security_version: u64,
724}
725
726#[deprecated(note = "Use AccountCryptographicState::rotate instead")]
730pub(crate) fn make_v2_keys_for_v1_user(
731 client: &Client,
732) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
733 let key_store = client.internal.get_key_store();
734 let mut ctx = key_store.context();
735
736 let private_key_id = PrivateKeyId::UserPrivateKey;
738
739 if client.internal.get_security_version() != 1 {
741 return Err(StatefulCryptoError::WrongAccountCryptoVersion {
742 expected: "1".to_string(),
743 got: 2,
744 });
745 }
746
747 if !ctx.has_private_key(PrivateKeyId::UserPrivateKey) {
751 return Err(StatefulCryptoError::Crypto(CryptoError::MissingKeyId(
752 "UserPrivateKey".to_string(),
753 )));
754 }
755
756 #[allow(deprecated)]
757 let private_key = ctx.dangerous_get_private_key(private_key_id)?.clone();
758
759 let user_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
761
762 let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
764 let temporary_signing_key_id = ctx.add_local_signing_key(signing_key.clone());
765
766 let signed_public_key = ctx.make_signed_public_key(private_key_id, temporary_signing_key_id)?;
768 let public_key = private_key.to_public_key();
769
770 let security_state = SecurityState::initialize_for_user(
772 client
773 .internal
774 .get_user_id()
775 .ok_or(StatefulCryptoError::MissingSecurityState)?,
776 );
777 let signed_security_state = security_state.sign(temporary_signing_key_id, &mut ctx)?;
778
779 Ok(UserCryptoV2KeysResponse {
780 user_key: user_key.to_base64(),
781
782 private_key: private_key.to_der()?.encrypt_with_key(&user_key)?,
783 public_key: public_key.to_der()?.into(),
784 signed_public_key,
785
786 signing_key: signing_key.to_cose().encrypt_with_key(&user_key)?,
787 verifying_key: signing_key.to_verifying_key().to_cose().into(),
788
789 security_state: signed_security_state,
790 security_version: security_state.version(),
791 })
792}
793
794#[deprecated(note = "Use AccountCryptographicState::rotate instead")]
799pub(crate) fn get_v2_rotated_account_keys(
800 client: &Client,
801) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
802 let key_store = client.internal.get_key_store();
803 let mut ctx = key_store.context();
804
805 if client.internal.get_security_version() == 1 {
808 return Err(StatefulCryptoError::WrongAccountCryptoVersion {
809 expected: "2+".to_string(),
810 got: 1,
811 });
812 }
813
814 let security_state = client
815 .internal
816 .security_state
817 .read()
818 .expect("RwLock is not poisoned")
819 .to_owned()
820 .ok_or(StatefulCryptoError::MissingSecurityState)?;
823
824 #[expect(deprecated)]
825 let rotated_keys = dangerous_get_v2_rotated_account_keys(
826 PrivateKeyId::UserPrivateKey,
827 SigningKeyId::UserSigningKey,
828 &ctx,
829 )?;
830
831 Ok(UserCryptoV2KeysResponse {
832 user_key: rotated_keys.user_key.to_base64(),
833
834 private_key: rotated_keys.private_key,
835 public_key: rotated_keys.public_key.into(),
836 signed_public_key: rotated_keys.signed_public_key,
837
838 signing_key: rotated_keys.signing_key,
839 verifying_key: rotated_keys.verifying_key.into(),
840
841 security_state: security_state.sign(SigningKeyId::UserSigningKey, &mut ctx)?,
842 security_version: security_state.version(),
843 })
844}
845
846pub struct MakeTdeRegistrationResponse {
848 pub account_cryptographic_state: WrappedAccountCryptographicState,
850 pub user_key: SymmetricCryptoKey,
852 pub account_keys_request: AccountKeysRequestModel,
854 pub trusted_device_keys: TrustDeviceResponse,
856 pub reset_password_key: UnsignedSharedKey,
858}
859
860pub struct MakeJitMasterPasswordRegistrationResponse {
862 pub account_cryptographic_state: WrappedAccountCryptographicState,
864 pub user_key: SymmetricCryptoKey,
866 pub master_password_authentication_data: MasterPasswordAuthenticationData,
868 pub master_password_unlock_data: MasterPasswordUnlockData,
870 pub account_keys_request: AccountKeysRequestModel,
872 pub reset_password_key: UnsignedSharedKey,
874}
875
876#[bitwarden_error(flat)]
878#[derive(Debug, thiserror::Error)]
879pub enum MakeKeysError {
880 #[error("Failed to initialize account cryptography")]
882 AccountCryptographyInitialization(AccountCryptographyInitializationError),
883 #[error("Failed to derive master password")]
885 MasterPasswordDerivation(MasterPasswordError),
886 #[error("Failed to make a request model")]
888 RequestModelCreation,
889 #[error("Cryptography error: {0}")]
891 Crypto(#[from] CryptoError),
892}
893
894pub(crate) fn make_user_tde_registration(
896 client: &Client,
897 user_id: UserId,
898 org_public_key: B64,
899) -> Result<MakeTdeRegistrationResponse, MakeKeysError> {
900 let mut ctx = client.internal.get_key_store().context_mut();
901 let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx, user_id)
902 .map_err(MakeKeysError::AccountCryptographyInitialization)?;
903 #[expect(deprecated)]
905 let device_key = DeviceKey::trust_device(ctx.dangerous_get_symmetric_key(user_key_id)?)?;
906
907 let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&org_public_key))
909 .map_err(MakeKeysError::Crypto)?;
910 #[expect(deprecated)]
911 let admin_reset = UnsignedSharedKey::encapsulate_key_unsigned(
912 ctx.dangerous_get_symmetric_key(user_key_id)?,
913 &public_key,
914 )
915 .map_err(MakeKeysError::Crypto)?;
916
917 let cryptography_state_request_model = wrapped_state
918 .to_request_model(&user_key_id, &mut ctx)
919 .map_err(|_| MakeKeysError::RequestModelCreation)?;
920
921 #[expect(deprecated)]
922 Ok(MakeTdeRegistrationResponse {
923 account_cryptographic_state: wrapped_state,
924 user_key: ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned(),
925 account_keys_request: cryptography_state_request_model,
926 trusted_device_keys: device_key,
927 reset_password_key: admin_reset,
928 })
929}
930
931pub struct MakeKeyConnectorRegistrationResponse {
933 pub account_cryptographic_state: WrappedAccountCryptographicState,
935 pub key_connector_key_wrapped_user_key: EncString,
937 pub user_key: SymmetricCryptoKey,
939 pub account_keys_request: AccountKeysRequestModel,
941 pub key_connector_key: KeyConnectorKey,
943}
944
945pub(crate) fn make_user_key_connector_registration(
947 client: &Client,
948 user_id: UserId,
949) -> Result<MakeKeyConnectorRegistrationResponse, MakeKeysError> {
950 let mut ctx = client.internal.get_key_store().context_mut();
951 let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx, user_id)
952 .map_err(MakeKeysError::AccountCryptographyInitialization)?;
953 #[expect(deprecated)]
954 let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
955
956 let key_connector_key = KeyConnectorKey::make();
958
959 let wrapped_user_key = key_connector_key
960 .encrypt_user_key(&user_key)
961 .map_err(MakeKeysError::Crypto)?;
962
963 let cryptography_state_request_model =
964 wrapped_state
965 .to_request_model(&user_key_id, &mut ctx)
966 .map_err(MakeKeysError::AccountCryptographyInitialization)?;
967
968 Ok(MakeKeyConnectorRegistrationResponse {
969 account_cryptographic_state: wrapped_state,
970 key_connector_key_wrapped_user_key: wrapped_user_key,
971 user_key,
972 account_keys_request: cryptography_state_request_model,
973 key_connector_key,
974 })
975}
976
977pub(crate) fn make_user_jit_master_password_registration(
979 client: &Client,
980 user_id: UserId,
981 master_password: String,
982 salt: String,
983 org_public_key: B64,
984) -> Result<MakeJitMasterPasswordRegistrationResponse, MakeKeysError> {
985 let mut ctx = client.internal.get_key_store().context_mut();
986 let (user_key_id, wrapped_state) = WrappedAccountCryptographicState::make(&mut ctx, user_id)
987 .map_err(MakeKeysError::AccountCryptographyInitialization)?;
988
989 let kdf = Kdf::default_argon2();
990
991 #[expect(deprecated)]
992 let user_key = ctx.dangerous_get_symmetric_key(user_key_id)?.to_owned();
993
994 let master_password_unlock_data =
995 MasterPasswordUnlockData::derive(&master_password, &kdf, &salt, user_key_id, &ctx)
996 .map_err(MakeKeysError::MasterPasswordDerivation)?;
997
998 let master_password_authentication_data =
999 MasterPasswordAuthenticationData::derive(&master_password, &kdf, &salt)
1000 .map_err(MakeKeysError::MasterPasswordDerivation)?;
1001
1002 let cryptography_state_request_model = wrapped_state
1003 .to_request_model(&user_key_id, &mut ctx)
1004 .map_err(|_| MakeKeysError::RequestModelCreation)?;
1005
1006 let public_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(&org_public_key))
1008 .map_err(MakeKeysError::Crypto)?;
1009 let admin_reset_key = UnsignedSharedKey::encapsulate(user_key_id, &public_key, &ctx)
1010 .map_err(MakeKeysError::Crypto)?;
1011
1012 Ok(MakeJitMasterPasswordRegistrationResponse {
1013 account_cryptographic_state: wrapped_state,
1014 user_key,
1015 master_password_unlock_data,
1016 master_password_authentication_data,
1017 account_keys_request: cryptography_state_request_model,
1018 reset_password_key: admin_reset_key,
1019 })
1020}
1021
1022#[cfg(test)]
1023mod tests {
1024 use std::num::NonZeroU32;
1025
1026 use bitwarden_crypto::{
1027 PrivateKey, PublicKeyEncryptionAlgorithm, RsaKeyPair, SymmetricKeyAlgorithm,
1028 };
1029
1030 use super::*;
1031 use crate::Client;
1032
1033 const TEST_VECTOR_USER_KEY_V2_B64: &str = "pQEEAlACHUUoybNAuJoZzqNMxz2bAzoAARFvBIQDBAUGIFggAvGl4ifaUAomQdCdUPpXLHtypiQxHjZwRHeI83caZM4B";
1034 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";
1035 #[allow(unused)]
1036 const TEST_VECTOR_PUBLIC_KEY_V2: &str = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz/+1jPJ1HqcaCdKrTPms8XJcvnmd9alI42U2XF/4GMNTM5KF1gI6snhR/23ZLatZRFMHoK8ZCMSpGNkjLadArz52ldceTvBOhQUiWylkZQ4NfNa3xIYJubXOmkeDyfNuyLxVZvcZOko9PdT+Qx2QxDrFi2XNo2I7aVFd19/COIEkex4mJ0eA3MHFpKCdxYbcTAsGID8+kVR9L84S1JptZoG8x+iB/D3/Q4y02UsQYpFTu0vbPY84YmW03ngJdxWzS8X4/UJI/jaEn5rO4xlU5QcL0l4IybP5LRpE9XEeUHATKVOG7eNfpe9zDfKV2qQoofQMH9VvkWO4psaWDjBSdwIDAQAB";
1037 #[allow(unused)]
1038 const TEST_VECTOR_SIGNED_PUBLIC_KEY_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8BoFkBTqNpYWxnb3JpdGhtAG1jb250ZW50Rm9ybWF0AGlwdWJsaWNLZXlZASYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP/7WM8nUepxoJ0qtM+azxcly+eZ31qUjjZTZcX/gYw1MzkoXWAjqyeFH/bdktq1lEUwegrxkIxKkY2SMtp0CvPnaV1x5O8E6FBSJbKWRlDg181rfEhgm5tc6aR4PJ827IvFVm9xk6Sj091P5DHZDEOsWLZc2jYjtpUV3X38I4gSR7HiYnR4DcwcWkoJ3FhtxMCwYgPz6RVH0vzhLUmm1mgbzH6IH8Pf9DjLTZSxBikVO7S9s9jzhiZbTeeAl3FbNLxfj9Qkj+NoSfms7jGVTlBwvSXgjJs/ktGkT1cR5QcBMpU4bt41+l73MN8pXapCih9Awf1W+RY7imxpYOMFJ3AgMBAAFYQMq/hT4wod2w8xyoM7D86ctuLNX4ZRo+jRHf2sZfaO7QsvonG/ZYuNKF5fq8wpxMRjfoMvnY2TTShbgzLrW8BA4=";
1039 const TEST_VECTOR_SIGNING_KEY_V2: &str = "7.g1gcowE6AAERbwMYZQRQAh1FKMmzQLiaGc6jTMc9m6EFWBhYePc2qkCruHAPXgbzXsIP1WVk11ArbLNYUBpifToURlwHKs1je2BwZ1C/5thz4nyNbL0wDaYkRWI9ex1wvB7KhdzC7ltStEd5QttboTSCaXQROSZaGBPNO5+Bu3sTY8F5qK1pBUo6AHNN";
1040 #[allow(unused)]
1041 const TEST_VECTOR_VERIFYING_KEY_V2: &str =
1042 "pgEBAlAmkP0QgfdMVbIujX55W/yNAycEgQIgBiFYIEM6JxBmjWQTruAm3s6BTaJy1q6BzQetMBacNeRJ0kxR";
1043 const TEST_VECTOR_SECURITY_STATE_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8CoFgkomhlbnRpdHlJZFBHOOw2BI9OQoNq+Vl1xZZKZ3ZlcnNpb24CWEAlchbJR0vmRfShG8On7Q2gknjkw4Dd6MYBLiH4u+/CmfQdmjNZdf6kozgW/6NXyKVNu8dAsKsin+xxXkDyVZoG";
1044
1045 const TEST_USER_EMAIL: &str = "[email protected]";
1046 const TEST_USER_PASSWORD: &str = "asdfasdfasdf";
1047 const TEST_ACCOUNT_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
1048 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=";
1049
1050 #[tokio::test]
1051 async fn test_update_kdf() {
1052 let client = Client::new(None);
1053
1054 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();
1055
1056 let kdf = Kdf::PBKDF2 {
1057 iterations: 100_000.try_into().unwrap(),
1058 };
1059
1060 initialize_user_crypto(
1061 &client,
1062 InitUserCryptoRequest {
1063 user_id: Some(UserId::new_v4()),
1064 kdf_params: kdf.clone(),
1065 email: "[email protected]".into(),
1066 account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1067 method: InitUserCryptoMethod::MasterPasswordUnlock {
1068 password: "asdfasdfasdf".into(),
1069 master_password_unlock: MasterPasswordUnlockData {
1070 kdf: kdf.clone(),
1071 master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1072 salt: "[email protected]".to_string(),
1073 },
1074 },
1075 },
1076 )
1077 .await
1078 .unwrap();
1079
1080 let new_kdf = Kdf::PBKDF2 {
1081 iterations: 600_000.try_into().unwrap(),
1082 };
1083 let new_kdf_response = make_update_kdf(&client, "123412341234", &new_kdf).unwrap();
1084
1085 let client2 = Client::new(None);
1086
1087 initialize_user_crypto(
1088 &client2,
1089 InitUserCryptoRequest {
1090 user_id: Some(UserId::new_v4()),
1091 kdf_params: new_kdf.clone(),
1092 email: "[email protected]".into(),
1093 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1094 private_key: priv_key.to_owned(),
1095 },
1096 method: InitUserCryptoMethod::MasterPasswordUnlock {
1097 password: "123412341234".to_string(),
1098 master_password_unlock: MasterPasswordUnlockData {
1099 kdf: new_kdf.clone(),
1100 master_key_wrapped_user_key: new_kdf_response
1101 .master_password_unlock_data
1102 .master_key_wrapped_user_key,
1103 salt: "[email protected]".to_string(),
1104 },
1105 },
1106 },
1107 )
1108 .await
1109 .unwrap();
1110
1111 let new_hash = client2
1112 .kdf()
1113 .hash_password(
1114 "[email protected]".into(),
1115 "123412341234".into(),
1116 new_kdf.clone(),
1117 bitwarden_crypto::HashPurpose::ServerAuthorization,
1118 )
1119 .await
1120 .unwrap();
1121
1122 assert_eq!(
1123 new_hash,
1124 new_kdf_response
1125 .master_password_authentication_data
1126 .master_password_authentication_hash
1127 );
1128
1129 let client_key = {
1130 let key_store = client.internal.get_key_store();
1131 let ctx = key_store.context();
1132 #[allow(deprecated)]
1133 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1134 .unwrap()
1135 .to_base64()
1136 };
1137
1138 let client2_key = {
1139 let key_store = client2.internal.get_key_store();
1140 let ctx = key_store.context();
1141 #[allow(deprecated)]
1142 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1143 .unwrap()
1144 .to_base64()
1145 };
1146
1147 assert_eq!(client_key, client2_key);
1148 }
1149
1150 #[tokio::test]
1151 async fn test_update_password() {
1152 let client = Client::new(None);
1153
1154 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();
1155
1156 let kdf = Kdf::PBKDF2 {
1157 iterations: 100_000.try_into().unwrap(),
1158 };
1159
1160 initialize_user_crypto(
1161 &client,
1162 InitUserCryptoRequest {
1163 user_id: Some(UserId::new_v4()),
1164 kdf_params: kdf.clone(),
1165 email: "[email protected]".into(),
1166 account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1167 method: InitUserCryptoMethod::MasterPasswordUnlock {
1168 password: "asdfasdfasdf".to_string(),
1169 master_password_unlock: MasterPasswordUnlockData {
1170 kdf: kdf.clone(),
1171 master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1172 salt: "[email protected]".to_string(),
1173 },
1174 },
1175 },
1176 )
1177 .await
1178 .unwrap();
1179
1180 let new_password_response = make_update_password(&client, "123412341234".into()).unwrap();
1181
1182 let client2 = Client::new(None);
1183
1184 initialize_user_crypto(
1185 &client2,
1186 InitUserCryptoRequest {
1187 user_id: Some(UserId::new_v4()),
1188 kdf_params: kdf.clone(),
1189 email: "[email protected]".into(),
1190 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1191 private_key: priv_key.to_owned(),
1192 },
1193 method: InitUserCryptoMethod::MasterPasswordUnlock {
1194 password: "123412341234".into(),
1195 master_password_unlock: MasterPasswordUnlockData {
1196 kdf: kdf.clone(),
1197 master_key_wrapped_user_key: new_password_response.new_key,
1198 salt: "[email protected]".to_string(),
1199 },
1200 },
1201 },
1202 )
1203 .await
1204 .unwrap();
1205
1206 let new_hash = client2
1207 .kdf()
1208 .hash_password(
1209 "[email protected]".into(),
1210 "123412341234".into(),
1211 kdf.clone(),
1212 bitwarden_crypto::HashPurpose::ServerAuthorization,
1213 )
1214 .await
1215 .unwrap();
1216
1217 assert_eq!(new_hash, new_password_response.password_hash);
1218
1219 let client_key = {
1220 let key_store = client.internal.get_key_store();
1221 let ctx = key_store.context();
1222 #[allow(deprecated)]
1223 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1224 .unwrap()
1225 .to_base64()
1226 };
1227
1228 let client2_key = {
1229 let key_store = client2.internal.get_key_store();
1230 let ctx = key_store.context();
1231 #[allow(deprecated)]
1232 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1233 .unwrap()
1234 .to_base64()
1235 };
1236
1237 assert_eq!(client_key, client2_key);
1238 }
1239
1240 #[tokio::test]
1241 async fn test_initialize_user_crypto_pin() {
1242 let client = Client::new(None);
1243
1244 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();
1245
1246 initialize_user_crypto(
1247 &client,
1248 InitUserCryptoRequest {
1249 user_id: Some(UserId::new_v4()),
1250 kdf_params: Kdf::PBKDF2 {
1251 iterations: 100_000.try_into().unwrap(),
1252 },
1253 email: "[email protected]".into(),
1254 account_cryptographic_state: WrappedAccountCryptographicState::V1 { private_key: priv_key.to_owned() },
1255 method: InitUserCryptoMethod::MasterPasswordUnlock {
1256 password: "asdfasdfasdf".into(),
1257 master_password_unlock: MasterPasswordUnlockData {
1258 kdf: Kdf::PBKDF2 {
1259 iterations: 100_000.try_into().unwrap(),
1260 },
1261 master_key_wrapped_user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1262 salt: "[email protected]".to_string(),
1263 },
1264 },
1265 },
1266 )
1267 .await
1268 .unwrap();
1269
1270 let pin_key = derive_pin_key(&client, "1234".into()).unwrap();
1271
1272 let client2 = Client::new(None);
1274 initialize_user_crypto(
1275 &client2,
1276 InitUserCryptoRequest {
1277 user_id: Some(UserId::new_v4()),
1278 kdf_params: Kdf::PBKDF2 {
1279 iterations: 100_000.try_into().unwrap(),
1280 },
1281 email: "[email protected]".into(),
1282 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1283 private_key: priv_key.to_owned(),
1284 },
1285 method: InitUserCryptoMethod::Pin {
1286 pin: "1234".into(),
1287 pin_protected_user_key: pin_key.pin_protected_user_key,
1288 },
1289 },
1290 )
1291 .await
1292 .unwrap();
1293
1294 let client_key = {
1295 let key_store = client.internal.get_key_store();
1296 let ctx = key_store.context();
1297 #[allow(deprecated)]
1298 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1299 .unwrap()
1300 .to_base64()
1301 };
1302
1303 let client2_key = {
1304 let key_store = client2.internal.get_key_store();
1305 let ctx = key_store.context();
1306 #[allow(deprecated)]
1307 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1308 .unwrap()
1309 .to_base64()
1310 };
1311
1312 assert_eq!(client_key, client2_key);
1313
1314 let pin_protected_user_key = derive_pin_user_key(&client, pin_key.encrypted_pin).unwrap();
1316
1317 let client3 = Client::new(None);
1318
1319 initialize_user_crypto(
1320 &client3,
1321 InitUserCryptoRequest {
1322 user_id: Some(UserId::new_v4()),
1323 kdf_params: Kdf::PBKDF2 {
1324 iterations: 100_000.try_into().unwrap(),
1325 },
1326 email: "[email protected]".into(),
1327 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1328 private_key: priv_key.to_owned(),
1329 },
1330 method: InitUserCryptoMethod::Pin {
1331 pin: "1234".into(),
1332 pin_protected_user_key,
1333 },
1334 },
1335 )
1336 .await
1337 .unwrap();
1338
1339 let client_key = {
1340 let key_store = client.internal.get_key_store();
1341 let ctx = key_store.context();
1342 #[allow(deprecated)]
1343 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1344 .unwrap()
1345 .to_base64()
1346 };
1347
1348 let client3_key = {
1349 let key_store = client3.internal.get_key_store();
1350 let ctx = key_store.context();
1351 #[allow(deprecated)]
1352 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1353 .unwrap()
1354 .to_base64()
1355 };
1356
1357 assert_eq!(client_key, client3_key);
1358 }
1359
1360 #[tokio::test]
1361 async fn test_initialize_user_crypto_pin_envelope() {
1362 let user_key = "5yKAZ4TSSEGje54MV5lc5ty6crkqUz4xvl+8Dm/piNLKf6OgRi2H0uzttNTXl9z6ILhkmuIXzGpAVc2YdorHgQ==";
1363 let test_pin = "1234";
1364
1365 let client1 = Client::new(None);
1366 initialize_user_crypto(
1367 &client1,
1368 InitUserCryptoRequest {
1369 user_id: Some(UserId::new_v4()),
1370 kdf_params: Kdf::PBKDF2 {
1371 iterations: 100_000.try_into().unwrap(),
1372 },
1373 email: "[email protected]".into(),
1374 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1375 private_key: make_key_pair(user_key.try_into().unwrap())
1376 .unwrap()
1377 .user_key_encrypted_private_key,
1378 },
1379 method: InitUserCryptoMethod::DecryptedKey {
1380 decrypted_user_key: user_key.to_string(),
1381 },
1382 },
1383 )
1384 .await
1385 .unwrap();
1386
1387 let enroll_response = client1.crypto().enroll_pin(test_pin.to_string()).unwrap();
1388
1389 let client2 = Client::new(None);
1390 initialize_user_crypto(
1391 &client2,
1392 InitUserCryptoRequest {
1393 user_id: Some(UserId::new_v4()),
1394 kdf_params: Kdf::PBKDF2 {
1397 iterations: 600_000.try_into().unwrap(),
1398 },
1399 email: "[email protected]".into(),
1400 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1401 private_key: make_key_pair(user_key.try_into().unwrap())
1402 .unwrap()
1403 .user_key_encrypted_private_key,
1404 },
1405 method: InitUserCryptoMethod::PinEnvelope {
1406 pin: test_pin.to_string(),
1407 pin_protected_user_key_envelope: enroll_response
1408 .pin_protected_user_key_envelope,
1409 },
1410 },
1411 )
1412 .await
1413 .unwrap();
1414 }
1415
1416 #[test]
1417 fn test_enroll_admin_password_reset() {
1418 let client = Client::new(None);
1419
1420 let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap();
1421 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();
1422 client
1423 .internal
1424 .initialize_user_crypto_master_password_unlock(
1425 "asdfasdfasdf".to_string(),
1426 MasterPasswordUnlockData {
1427 kdf: Kdf::PBKDF2 {
1428 iterations: NonZeroU32::new(600_000).unwrap(),
1429 },
1430 master_key_wrapped_user_key: user_key,
1431 salt: "[email protected]".to_string(),
1432 },
1433 WrappedAccountCryptographicState::V1 { private_key },
1434 )
1435 .unwrap();
1436
1437 let public_key: B64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsy7RFHcX3C8Q4/OMmhhbFReYWfB45W9PDTEA8tUZwZmtOiN2RErIS2M1c+K/4HoDJ/TjpbX1f2MZcr4nWvKFuqnZXyewFc+jmvKVewYi+NAu2++vqKq2kKcmMNhwoQDQdQIVy/Uqlp4Cpi2cIwO6ogq5nHNJGR3jm+CpyrafYlbz1bPvL3hbyoGDuG2tgADhyhXUdFuef2oF3wMvn1lAJAvJnPYpMiXUFmj1ejmbwtlxZDrHgUJvUcp7nYdwUKaFoi+sOttHn3u7eZPtNvxMjhSS/X/1xBIzP/mKNLdywH5LoRxniokUk+fV3PYUxJsiU3lV0Trc/tH46jqd8ZGjmwIDAQAB".parse().unwrap();
1438
1439 let encrypted = enroll_admin_password_reset(&client, public_key).unwrap();
1440
1441 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();
1442
1443 let private_key = Pkcs8PrivateKeyBytes::from(private_key.as_bytes());
1444 let private_key = PrivateKey::from_der(&private_key).unwrap();
1445 #[expect(deprecated)]
1446 let decrypted: SymmetricCryptoKey =
1447 encrypted.decapsulate_key_unsigned(&private_key).unwrap();
1448
1449 let key_store = client.internal.get_key_store();
1450 let ctx = key_store.context();
1451 #[allow(deprecated)]
1452 let expected = ctx
1453 .dangerous_get_symmetric_key(SymmetricKeyId::User)
1454 .unwrap();
1455
1456 assert_eq!(decrypted, *expected);
1457 }
1458
1459 #[test]
1460 fn test_derive_key_connector() {
1461 let request = DeriveKeyConnectorRequest {
1462 password: "asdfasdfasdf".to_string(),
1463 email: "[email protected]".to_string(),
1464 kdf: Kdf::PBKDF2 {
1465 iterations: NonZeroU32::new(600_000).unwrap(),
1466 },
1467 user_key_encrypted: "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(),
1468 };
1469
1470 let result = derive_key_connector(request).unwrap();
1471
1472 assert_eq!(
1473 result.to_string(),
1474 "ySXq1RVLKEaV1eoQE/ui9aFKIvXTl9PAXwp1MljfF50="
1475 );
1476 }
1477
1478 fn setup_asymmetric_keys_test() -> (UserKey, RsaKeyPair) {
1479 let master_key = MasterKey::derive(
1480 "asdfasdfasdf",
1481 "[email protected]",
1482 &Kdf::PBKDF2 {
1483 iterations: NonZeroU32::new(600_000).unwrap(),
1484 },
1485 )
1486 .unwrap();
1487 let user_key = (master_key.make_user_key().unwrap()).0;
1488 let key_pair = user_key.make_key_pair().unwrap();
1489
1490 (user_key, key_pair)
1491 }
1492
1493 #[test]
1494 fn test_make_key_pair() {
1495 let (user_key, _) = setup_asymmetric_keys_test();
1496
1497 let response = make_key_pair(user_key.0.to_base64()).unwrap();
1498
1499 assert!(!response.user_public_key.to_string().is_empty());
1500 let encrypted_private_key = response.user_key_encrypted_private_key;
1501 let private_key: Vec<u8> = encrypted_private_key.decrypt_with_key(&user_key.0).unwrap();
1502 assert!(!private_key.is_empty());
1503 }
1504
1505 #[test]
1506 fn test_verify_asymmetric_keys_success() {
1507 let (user_key, key_pair) = setup_asymmetric_keys_test();
1508
1509 let request = VerifyAsymmetricKeysRequest {
1510 user_key: user_key.0.to_base64(),
1511 user_public_key: key_pair.public,
1512 user_key_encrypted_private_key: key_pair.private,
1513 };
1514 let response = verify_asymmetric_keys(request).unwrap();
1515
1516 assert!(response.private_key_decryptable);
1517 assert!(response.valid_private_key);
1518 }
1519
1520 #[test]
1521 fn test_verify_asymmetric_keys_decrypt_failed() {
1522 let (user_key, key_pair) = setup_asymmetric_keys_test();
1523 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();
1524
1525 let request = VerifyAsymmetricKeysRequest {
1526 user_key: user_key.0.to_base64(),
1527 user_public_key: key_pair.public,
1528 user_key_encrypted_private_key: undecryptable_private_key,
1529 };
1530 let response = verify_asymmetric_keys(request).unwrap();
1531
1532 assert!(!response.private_key_decryptable);
1533 assert!(!response.valid_private_key);
1534 }
1535
1536 #[test]
1537 fn test_verify_asymmetric_keys_parse_failed() {
1538 let (user_key, key_pair) = setup_asymmetric_keys_test();
1539
1540 let invalid_private_key = "bad_key".to_string().encrypt_with_key(&user_key.0).unwrap();
1541
1542 let request = VerifyAsymmetricKeysRequest {
1543 user_key: user_key.0.to_base64(),
1544 user_public_key: key_pair.public,
1545 user_key_encrypted_private_key: invalid_private_key,
1546 };
1547 let response = verify_asymmetric_keys(request).unwrap();
1548
1549 assert!(response.private_key_decryptable);
1550 assert!(!response.valid_private_key);
1551 }
1552
1553 #[test]
1554 fn test_verify_asymmetric_keys_key_mismatch() {
1555 let (user_key, key_pair) = setup_asymmetric_keys_test();
1556 let new_key_pair = user_key.make_key_pair().unwrap();
1557
1558 let request = VerifyAsymmetricKeysRequest {
1559 user_key: user_key.0.to_base64(),
1560 user_public_key: key_pair.public,
1561 user_key_encrypted_private_key: new_key_pair.private,
1562 };
1563 let response = verify_asymmetric_keys(request).unwrap();
1564
1565 assert!(response.private_key_decryptable);
1566 assert!(!response.valid_private_key);
1567 }
1568
1569 #[tokio::test]
1570 async fn test_make_v2_keys_for_v1_user() {
1571 let client = Client::new(None);
1572
1573 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();
1574 let encrypted_userkey: EncString = "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap();
1575
1576 initialize_user_crypto(
1577 &client,
1578 InitUserCryptoRequest {
1579 user_id: Some(UserId::new_v4()),
1580 kdf_params: Kdf::PBKDF2 {
1581 iterations: 100_000.try_into().unwrap(),
1582 },
1583 email: "[email protected]".into(),
1584 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1585 private_key: priv_key.to_owned(),
1586 },
1587 method: InitUserCryptoMethod::MasterPasswordUnlock {
1588 password: "asdfasdfasdf".into(),
1589 master_password_unlock: MasterPasswordUnlockData {
1590 kdf: Kdf::PBKDF2 {
1591 iterations: 100_000.try_into().unwrap(),
1592 },
1593 master_key_wrapped_user_key: encrypted_userkey.clone(),
1594 salt: "[email protected]".into(),
1595 },
1596 },
1597 },
1598 )
1599 .await
1600 .unwrap();
1601
1602 let master_key = MasterKey::derive(
1603 "asdfasdfasdf",
1604 "[email protected]",
1605 &Kdf::PBKDF2 {
1606 iterations: NonZeroU32::new(100_000).unwrap(),
1607 },
1608 )
1609 .unwrap();
1610 #[expect(deprecated)]
1611 let enrollment_response = make_v2_keys_for_v1_user(&client).unwrap();
1612 let encrypted_userkey_v2 = master_key
1613 .encrypt_user_key(
1614 &SymmetricCryptoKey::try_from(enrollment_response.clone().user_key).unwrap(),
1615 )
1616 .unwrap();
1617
1618 let client2 = Client::new(None);
1619 initialize_user_crypto(
1620 &client2,
1621 InitUserCryptoRequest {
1622 user_id: Some(UserId::new_v4()),
1623 kdf_params: Kdf::PBKDF2 {
1624 iterations: 100_000.try_into().unwrap(),
1625 },
1626 email: "[email protected]".into(),
1627 account_cryptographic_state: WrappedAccountCryptographicState::V2 {
1628 private_key: enrollment_response.private_key,
1629 signing_key: enrollment_response.signing_key,
1630 security_state: enrollment_response.security_state,
1631 signed_public_key: Some(enrollment_response.signed_public_key),
1632 },
1633 method: InitUserCryptoMethod::MasterPasswordUnlock {
1634 password: "asdfasdfasdf".into(),
1635 master_password_unlock: MasterPasswordUnlockData {
1636 kdf: Kdf::PBKDF2 {
1637 iterations: 100_000.try_into().unwrap(),
1638 },
1639 master_key_wrapped_user_key: encrypted_userkey_v2,
1640 salt: "[email protected]".to_string(),
1641 },
1642 },
1643 },
1644 )
1645 .await
1646 .unwrap();
1647 }
1648
1649 #[tokio::test]
1650 async fn test_make_v2_keys_for_v1_user_with_v2_user_fails() {
1651 let client = Client::new(None);
1652 initialize_user_crypto(
1653 &client,
1654 InitUserCryptoRequest {
1655 user_id: Some(UserId::new_v4()),
1656 kdf_params: Kdf::PBKDF2 {
1657 iterations: 100_000.try_into().unwrap(),
1658 },
1659 email: "[email protected]".into(),
1660 account_cryptographic_state: WrappedAccountCryptographicState::V2 {
1661 private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
1662 signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
1663 security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
1664 signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
1665 },
1666 method: InitUserCryptoMethod::DecryptedKey {
1667 decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
1668 },
1669 },
1670 )
1671 .await
1672 .unwrap();
1673
1674 #[expect(deprecated)]
1675 let result = make_v2_keys_for_v1_user(&client);
1676 assert!(matches!(
1677 result,
1678 Err(StatefulCryptoError::WrongAccountCryptoVersion {
1679 expected: _,
1680 got: _
1681 })
1682 ));
1683 }
1684
1685 #[test]
1686 fn test_get_v2_rotated_account_keys_non_v2_user() {
1687 let client = Client::new(None);
1688 let mut ctx = client.internal.get_key_store().context_mut();
1689 let local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
1690 ctx.persist_symmetric_key(local_key_id, SymmetricKeyId::User)
1691 .unwrap();
1692 drop(ctx);
1693
1694 #[expect(deprecated)]
1695 let result = get_v2_rotated_account_keys(&client);
1696 assert!(matches!(
1697 result,
1698 Err(StatefulCryptoError::WrongAccountCryptoVersion {
1699 expected: _,
1700 got: _
1701 })
1702 ));
1703 }
1704
1705 #[tokio::test]
1706 async fn test_get_v2_rotated_account_keys() {
1707 let client = Client::new(None);
1708 initialize_user_crypto(
1709 &client,
1710 InitUserCryptoRequest {
1711 user_id: Some(UserId::new_v4()),
1712 kdf_params: Kdf::PBKDF2 {
1713 iterations: 100_000.try_into().unwrap(),
1714 },
1715 email: "[email protected]".into(),
1716 account_cryptographic_state: WrappedAccountCryptographicState::V2 {
1717 private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
1718 signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
1719 security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
1720 signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
1721 },
1722 method: InitUserCryptoMethod::DecryptedKey {
1723 decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
1724 },
1725 },
1726 )
1727 .await
1728 .unwrap();
1729
1730 #[expect(deprecated)]
1731 let result = get_v2_rotated_account_keys(&client);
1732 assert!(result.is_ok());
1733 }
1734
1735 #[tokio::test]
1736 async fn test_initialize_user_crypto_master_password_unlock() {
1737 let client = Client::new(None);
1738
1739 initialize_user_crypto(
1740 &client,
1741 InitUserCryptoRequest {
1742 user_id: Some(UserId::new_v4()),
1743 kdf_params: Kdf::PBKDF2 {
1744 iterations: 600_000.try_into().unwrap(),
1745 },
1746 email: TEST_USER_EMAIL.to_string(),
1747 account_cryptographic_state: WrappedAccountCryptographicState::V1 {
1748 private_key: TEST_ACCOUNT_PRIVATE_KEY.parse().unwrap(),
1749 },
1750 method: InitUserCryptoMethod::MasterPasswordUnlock {
1751 password: TEST_USER_PASSWORD.to_string(),
1752 master_password_unlock: MasterPasswordUnlockData {
1753 kdf: Kdf::PBKDF2 {
1754 iterations: 600_000.try_into().unwrap(),
1755 },
1756 master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
1757 salt: TEST_USER_EMAIL.to_string(),
1758 },
1759 },
1760 },
1761 )
1762 .await
1763 .unwrap();
1764
1765 let key_store = client.internal.get_key_store();
1766 let context = key_store.context();
1767 assert!(context.has_symmetric_key(SymmetricKeyId::User));
1768 assert!(context.has_private_key(PrivateKeyId::UserPrivateKey));
1769 let login_method = client.internal.get_login_method().unwrap();
1770 if let LoginMethod::User(UserLoginMethod::Username {
1771 email,
1772 kdf,
1773 client_id,
1774 ..
1775 }) = login_method.as_ref()
1776 {
1777 assert_eq!(*email, TEST_USER_EMAIL);
1778 assert_eq!(
1779 *kdf,
1780 Kdf::PBKDF2 {
1781 iterations: 600_000.try_into().unwrap(),
1782 }
1783 );
1784 assert_eq!(*client_id, "");
1785 } else {
1786 panic!("Expected username login method");
1787 }
1788 }
1789
1790 #[tokio::test]
1791 async fn test_make_user_tde_registration() {
1792 let user_id = UserId::new_v4();
1793 let email = "[email protected]";
1794 let kdf = Kdf::PBKDF2 {
1795 iterations: NonZeroU32::new(600_000).expect("valid iteration count"),
1796 };
1797
1798 let org_key = PrivateKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
1800 let org_public_key_der = org_key
1801 .to_public_key()
1802 .to_der()
1803 .expect("valid public key DER");
1804 let org_public_key = B64::from(org_public_key_der.as_ref().to_vec());
1805
1806 let registration_client = Client::new(None);
1808 let make_keys_response = registration_client
1809 .crypto()
1810 .make_user_tde_registration(user_id, org_public_key)
1811 .expect("TDE registration should succeed");
1812
1813 let unlock_client = Client::new(None);
1815 unlock_client
1816 .crypto()
1817 .initialize_user_crypto(InitUserCryptoRequest {
1818 user_id: Some(user_id),
1819 kdf_params: kdf,
1820 email: email.to_owned(),
1821 account_cryptographic_state: make_keys_response.account_cryptographic_state,
1822 method: InitUserCryptoMethod::DeviceKey {
1823 device_key: make_keys_response
1824 .trusted_device_keys
1825 .device_key
1826 .to_string(),
1827 protected_device_private_key: make_keys_response
1828 .trusted_device_keys
1829 .protected_device_private_key,
1830 device_protected_user_key: make_keys_response
1831 .trusted_device_keys
1832 .protected_user_key,
1833 },
1834 })
1835 .await
1836 .expect("initializing user crypto with TDE device key should succeed");
1837
1838 let retrieved_key = unlock_client
1840 .crypto()
1841 .get_user_encryption_key()
1842 .await
1843 .expect("should be able to get user encryption key");
1844
1845 let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
1847 .expect("retrieved key should be valid symmetric key");
1848
1849 #[expect(deprecated)]
1852 let decrypted_user_key = make_keys_response
1853 .reset_password_key
1854 .decapsulate_key_unsigned(&org_key)
1855 .expect("org key should be able to decrypt admin reset key");
1856 assert_eq!(
1857 retrieved_symmetric_key, decrypted_user_key,
1858 "decrypted admin reset key should match the user's encryption key"
1859 );
1860 }
1861
1862 #[tokio::test]
1863 async fn test_make_user_key_connector_registration_success() {
1864 let user_id = UserId::new_v4();
1865 let email = "[email protected]";
1866 let registration_client = Client::new(None);
1867
1868 let make_keys_response =
1869 make_user_key_connector_registration(®istration_client, user_id);
1870 assert!(make_keys_response.is_ok());
1871 let make_keys_response = make_keys_response.unwrap();
1872
1873 let unlock_client = Client::new(None);
1875 unlock_client
1876 .crypto()
1877 .initialize_user_crypto(InitUserCryptoRequest {
1878 user_id: Some(user_id),
1879 kdf_params: Kdf::default_argon2(),
1880 email: email.to_owned(),
1881 account_cryptographic_state: make_keys_response.account_cryptographic_state,
1882 method: InitUserCryptoMethod::KeyConnector {
1883 user_key: make_keys_response
1884 .key_connector_key_wrapped_user_key
1885 .clone(),
1886 master_key: make_keys_response.key_connector_key.clone().into(),
1887 },
1888 })
1889 .await
1890 .expect("initializing user crypto with key connector key should succeed");
1891
1892 let retrieved_key = unlock_client
1894 .crypto()
1895 .get_user_encryption_key()
1896 .await
1897 .expect("should be able to get user encryption key");
1898
1899 let retrieved_symmetric_key = SymmetricCryptoKey::try_from(retrieved_key)
1901 .expect("retrieved key should be valid symmetric key");
1902
1903 assert_eq!(retrieved_symmetric_key, make_keys_response.user_key);
1904
1905 let decrypted_user_key = make_keys_response
1906 .key_connector_key
1907 .decrypt_user_key(make_keys_response.key_connector_key_wrapped_user_key);
1908 assert_eq!(retrieved_symmetric_key, decrypted_user_key.unwrap());
1909 }
1910}