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