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