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