1use std::collections::HashMap;
8
9use bitwarden_crypto::{
10 AsymmetricCryptoKey, CoseSerializable, CryptoError, EncString, Kdf, KeyDecryptable,
11 KeyEncryptable, MasterKey, Pkcs8PrivateKeyBytes, PrimitiveEncryptable, SignatureAlgorithm,
12 SignedPublicKey, SigningKey, SpkiPublicKeyBytes, SymmetricCryptoKey, UnsignedSharedKey,
13 UserKey, dangerous_get_v2_rotated_account_keys,
14 safe::{PasswordProtectedKeyEnvelope, PasswordProtectedKeyEnvelopeError},
15};
16use bitwarden_encoding::B64;
17use bitwarden_error::bitwarden_error;
18use schemars::JsonSchema;
19use serde::{Deserialize, Serialize};
20#[cfg(feature = "wasm")]
21use {tsify::Tsify, wasm_bindgen::prelude::*};
22
23use crate::{
24 Client, NotAuthenticatedError, OrganizationId, UserId, WrongPasswordError,
25 client::{LoginMethod, UserLoginMethod, encryption_settings::EncryptionSettingsError},
26 error::StatefulCryptoError,
27 key_management::{
28 AsymmetricKeyId, SecurityState, SignedSecurityState, SigningKeyId, SymmetricKeyId,
29 master_password::{MasterPasswordAuthenticationData, MasterPasswordUnlockData},
30 },
31};
32
33#[allow(missing_docs)]
35#[bitwarden_error(flat)]
36#[derive(Debug, thiserror::Error)]
37pub enum CryptoClientError {
38 #[error(transparent)]
39 NotAuthenticated(#[from] NotAuthenticatedError),
40 #[error(transparent)]
41 Crypto(#[from] bitwarden_crypto::CryptoError),
42 #[error("Invalid KDF settings")]
43 InvalidKdfSettings,
44 #[error(transparent)]
45 PasswordProtectedKeyEnvelope(#[from] PasswordProtectedKeyEnvelopeError),
46}
47
48#[derive(Serialize, Deserialize, Debug)]
50#[serde(rename_all = "camelCase", deny_unknown_fields)]
51#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
52#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
53pub struct InitUserCryptoRequest {
54 pub user_id: Option<UserId>,
56 pub kdf_params: Kdf,
58 pub email: String,
60 pub private_key: EncString,
62 pub signing_key: Option<EncString>,
64 pub security_state: Option<SignedSecurityState>,
66 pub method: InitUserCryptoMethod,
68}
69
70#[derive(Serialize, Deserialize, Debug)]
72#[serde(rename_all = "camelCase", deny_unknown_fields)]
73#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
74#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
75#[allow(clippy::large_enum_variant)]
76pub enum InitUserCryptoMethod {
77 Password {
79 password: String,
81 user_key: EncString,
83 },
84 MasterPasswordUnlock {
86 password: String,
88 master_password_unlock: MasterPasswordUnlockData,
90 },
91 DecryptedKey {
93 decrypted_user_key: String,
95 },
96 Pin {
98 pin: String,
100 pin_protected_user_key: EncString,
103 },
104 PinEnvelope {
106 pin: String,
108 pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
110 },
111 AuthRequest {
113 request_private_key: B64,
115 method: AuthRequestMethod,
117 },
118 DeviceKey {
120 device_key: String,
122 protected_device_private_key: EncString,
124 device_protected_user_key: UnsignedSharedKey,
126 },
127 KeyConnector {
129 master_key: B64,
131 user_key: EncString,
133 },
134}
135
136#[derive(Serialize, Deserialize, Debug)]
138#[serde(rename_all = "camelCase", deny_unknown_fields)]
139#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
140#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
141pub enum AuthRequestMethod {
142 UserKey {
144 protected_user_key: UnsignedSharedKey,
146 },
147 MasterKey {
149 protected_master_key: UnsignedSharedKey,
151 auth_request_key: EncString,
153 },
154}
155
156pub(super) async fn initialize_user_crypto(
158 client: &Client,
159 req: InitUserCryptoRequest,
160) -> Result<(), EncryptionSettingsError> {
161 use bitwarden_crypto::{DeviceKey, PinKey};
162
163 use crate::auth::{auth_request_decrypt_master_key, auth_request_decrypt_user_key};
164
165 if let Some(user_id) = req.user_id {
166 client.internal.init_user_id(user_id)?;
167 }
168
169 let key_state = (&req).into();
170
171 match req.method {
172 InitUserCryptoMethod::Password { password, user_key } => {
173 let master_key = MasterKey::derive(&password, &req.email, &req.kdf_params)?;
174 client
175 .internal
176 .initialize_user_crypto_master_key(master_key, user_key, key_state)?;
177 }
178 InitUserCryptoMethod::MasterPasswordUnlock {
179 password,
180 master_password_unlock,
181 } => {
182 client
183 .internal
184 .initialize_user_crypto_master_password_unlock(
185 password,
186 master_password_unlock,
187 key_state,
188 )?;
189 }
190 InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => {
191 let user_key = SymmetricCryptoKey::try_from(decrypted_user_key)?;
192 client
193 .internal
194 .initialize_user_crypto_decrypted_key(user_key, key_state)?;
195 }
196 InitUserCryptoMethod::Pin {
197 pin,
198 pin_protected_user_key,
199 } => {
200 let pin_key = PinKey::derive(pin.as_bytes(), req.email.as_bytes(), &req.kdf_params)?;
201 client.internal.initialize_user_crypto_pin(
202 pin_key,
203 pin_protected_user_key,
204 key_state,
205 )?;
206 }
207 InitUserCryptoMethod::PinEnvelope {
208 pin,
209 pin_protected_user_key_envelope,
210 } => {
211 client.internal.initialize_user_crypto_pin_envelope(
212 pin,
213 pin_protected_user_key_envelope,
214 key_state,
215 )?;
216 }
217 InitUserCryptoMethod::AuthRequest {
218 request_private_key,
219 method,
220 } => {
221 let user_key = match method {
222 AuthRequestMethod::UserKey { protected_user_key } => {
223 auth_request_decrypt_user_key(request_private_key, protected_user_key)?
224 }
225 AuthRequestMethod::MasterKey {
226 protected_master_key,
227 auth_request_key,
228 } => auth_request_decrypt_master_key(
229 request_private_key,
230 protected_master_key,
231 auth_request_key,
232 )?,
233 };
234 client
235 .internal
236 .initialize_user_crypto_decrypted_key(user_key, key_state)?;
237 }
238 InitUserCryptoMethod::DeviceKey {
239 device_key,
240 protected_device_private_key,
241 device_protected_user_key,
242 } => {
243 let device_key = DeviceKey::try_from(device_key)?;
244 let user_key = device_key
245 .decrypt_user_key(protected_device_private_key, device_protected_user_key)?;
246
247 client
248 .internal
249 .initialize_user_crypto_decrypted_key(user_key, key_state)?;
250 }
251 InitUserCryptoMethod::KeyConnector {
252 master_key,
253 user_key,
254 } => {
255 let mut bytes = master_key.into_bytes();
256 let master_key = MasterKey::try_from(bytes.as_mut_slice())?;
257
258 client
259 .internal
260 .initialize_user_crypto_master_key(master_key, user_key, key_state)?;
261 }
262 }
263
264 client
265 .internal
266 .set_login_method(LoginMethod::User(UserLoginMethod::Username {
267 client_id: "".to_string(),
268 email: req.email,
269 kdf: req.kdf_params,
270 }));
271
272 Ok(())
273}
274
275#[derive(Serialize, Deserialize, Debug)]
277#[serde(rename_all = "camelCase", deny_unknown_fields)]
278#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
279#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
280pub struct InitOrgCryptoRequest {
281 pub organization_keys: HashMap<OrganizationId, UnsignedSharedKey>,
283}
284
285pub(super) async fn initialize_org_crypto(
287 client: &Client,
288 req: InitOrgCryptoRequest,
289) -> Result<(), EncryptionSettingsError> {
290 let organization_keys = req.organization_keys.into_iter().collect();
291 client.internal.initialize_org_crypto(organization_keys)?;
292 Ok(())
293}
294
295pub(super) async fn get_user_encryption_key(client: &Client) -> Result<B64, CryptoClientError> {
296 let key_store = client.internal.get_key_store();
297 let ctx = key_store.context();
298 #[allow(deprecated)]
300 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
301
302 Ok(user_key.to_base64())
303}
304
305#[derive(Serialize, Deserialize, Debug)]
307#[serde(rename_all = "camelCase", deny_unknown_fields)]
308#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
309#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
310pub struct UpdateKdfResponse {
311 master_password_authentication_data: MasterPasswordAuthenticationData,
313 master_password_unlock_data: MasterPasswordUnlockData,
315 old_master_password_authentication_data: MasterPasswordAuthenticationData,
317}
318
319pub(super) fn make_update_kdf(
320 client: &Client,
321 password: &str,
322 new_kdf: &Kdf,
323) -> Result<UpdateKdfResponse, CryptoClientError> {
324 let key_store = client.internal.get_key_store();
325 let ctx = key_store.context();
326 #[allow(deprecated)]
328 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
329
330 let login_method = client
331 .internal
332 .get_login_method()
333 .ok_or(NotAuthenticatedError)?;
334 let email = match login_method.as_ref() {
335 LoginMethod::User(
336 UserLoginMethod::Username { email, .. } | UserLoginMethod::ApiKey { email, .. },
337 ) => email,
338 #[cfg(feature = "secrets")]
339 LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?,
340 };
341
342 let authentication_data = MasterPasswordAuthenticationData::derive(password, new_kdf, email)
343 .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
344 let unlock_data = MasterPasswordUnlockData::derive(password, new_kdf, email, user_key)
345 .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
346 let old_authentication_data = MasterPasswordAuthenticationData::derive(
347 password,
348 &client
349 .internal
350 .get_kdf()
351 .map_err(|_| NotAuthenticatedError)?,
352 email,
353 )
354 .map_err(|_| CryptoClientError::InvalidKdfSettings)?;
355
356 Ok(UpdateKdfResponse {
357 master_password_authentication_data: authentication_data,
358 master_password_unlock_data: unlock_data,
359 old_master_password_authentication_data: old_authentication_data,
360 })
361}
362
363#[derive(Serialize, Deserialize, Debug)]
365#[serde(rename_all = "camelCase", deny_unknown_fields)]
366#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
367#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
368pub struct UpdatePasswordResponse {
369 password_hash: B64,
371 new_key: EncString,
373}
374
375pub(super) fn make_update_password(
376 client: &Client,
377 new_password: String,
378) -> Result<UpdatePasswordResponse, CryptoClientError> {
379 let key_store = client.internal.get_key_store();
380 let ctx = key_store.context();
381 #[allow(deprecated)]
383 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
384
385 let login_method = client
386 .internal
387 .get_login_method()
388 .ok_or(NotAuthenticatedError)?;
389
390 let new_master_key = match login_method.as_ref() {
392 LoginMethod::User(
393 UserLoginMethod::Username { email, kdf, .. }
394 | UserLoginMethod::ApiKey { email, kdf, .. },
395 ) => MasterKey::derive(&new_password, email, kdf)?,
396 #[cfg(feature = "secrets")]
397 LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?,
398 };
399
400 let new_key = new_master_key.encrypt_user_key(user_key)?;
401
402 let password_hash = new_master_key.derive_master_key_hash(
403 new_password.as_bytes(),
404 bitwarden_crypto::HashPurpose::ServerAuthorization,
405 );
406
407 Ok(UpdatePasswordResponse {
408 password_hash,
409 new_key,
410 })
411}
412
413#[derive(Serialize, Deserialize, Debug)]
415#[serde(rename_all = "camelCase", deny_unknown_fields)]
416#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
417#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
418pub struct EnrollPinResponse {
419 pub pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
421 pub user_key_encrypted_pin: EncString,
423}
424
425pub(super) fn enroll_pin(
426 client: &Client,
427 pin: String,
428) -> Result<EnrollPinResponse, CryptoClientError> {
429 let key_store = client.internal.get_key_store();
430 let mut ctx = key_store.context_mut();
431
432 let key_envelope = PasswordProtectedKeyEnvelope::seal(SymmetricKeyId::User, &pin, &ctx)?;
433 let encrypted_pin = pin.encrypt(&mut ctx, SymmetricKeyId::User)?;
434 Ok(EnrollPinResponse {
435 pin_protected_user_key_envelope: key_envelope,
436 user_key_encrypted_pin: encrypted_pin,
437 })
438}
439
440#[derive(Serialize, Deserialize, Debug)]
442#[serde(rename_all = "camelCase", deny_unknown_fields)]
443#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
444#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
445pub struct DerivePinKeyResponse {
446 pin_protected_user_key: EncString,
448 encrypted_pin: EncString,
450}
451
452pub(super) fn derive_pin_key(
453 client: &Client,
454 pin: String,
455) -> Result<DerivePinKeyResponse, CryptoClientError> {
456 let key_store = client.internal.get_key_store();
457 let ctx = key_store.context();
458 #[allow(deprecated)]
460 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
461
462 let login_method = client
463 .internal
464 .get_login_method()
465 .ok_or(NotAuthenticatedError)?;
466
467 let pin_protected_user_key = derive_pin_protected_user_key(&pin, &login_method, user_key)?;
468
469 Ok(DerivePinKeyResponse {
470 pin_protected_user_key,
471 encrypted_pin: pin.encrypt_with_key(user_key)?,
472 })
473}
474
475pub(super) fn derive_pin_user_key(
476 client: &Client,
477 encrypted_pin: EncString,
478) -> Result<EncString, CryptoClientError> {
479 let key_store = client.internal.get_key_store();
480 let ctx = key_store.context();
481 #[allow(deprecated)]
483 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
484
485 let pin: String = encrypted_pin.decrypt_with_key(user_key)?;
486 let login_method = client
487 .internal
488 .get_login_method()
489 .ok_or(NotAuthenticatedError)?;
490
491 derive_pin_protected_user_key(&pin, &login_method, user_key)
492}
493
494fn derive_pin_protected_user_key(
495 pin: &str,
496 login_method: &LoginMethod,
497 user_key: &SymmetricCryptoKey,
498) -> Result<EncString, CryptoClientError> {
499 use bitwarden_crypto::PinKey;
500
501 let derived_key = match login_method {
502 LoginMethod::User(
503 UserLoginMethod::Username { email, kdf, .. }
504 | UserLoginMethod::ApiKey { email, kdf, .. },
505 ) => PinKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?,
506 #[cfg(feature = "secrets")]
507 LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?,
508 };
509
510 Ok(derived_key.encrypt_user_key(user_key)?)
511}
512
513#[allow(missing_docs)]
514#[bitwarden_error(flat)]
515#[derive(Debug, thiserror::Error)]
516pub enum EnrollAdminPasswordResetError {
517 #[error(transparent)]
518 Crypto(#[from] bitwarden_crypto::CryptoError),
519}
520
521pub(super) fn enroll_admin_password_reset(
522 client: &Client,
523 public_key: B64,
524) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
525 use bitwarden_crypto::AsymmetricPublicCryptoKey;
526
527 let public_key = AsymmetricPublicCryptoKey::from_der(&SpkiPublicKeyBytes::from(&public_key))?;
528 let key_store = client.internal.get_key_store();
529 let ctx = key_store.context();
530 #[allow(deprecated)]
532 let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
533
534 Ok(UnsignedSharedKey::encapsulate_key_unsigned(
535 key,
536 &public_key,
537 )?)
538}
539
540#[derive(Serialize, Deserialize, Debug, JsonSchema)]
542#[serde(rename_all = "camelCase", deny_unknown_fields)]
543#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
544#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
545pub struct DeriveKeyConnectorRequest {
546 pub user_key_encrypted: EncString,
548 pub password: String,
550 pub kdf: Kdf,
552 pub email: String,
554}
555
556#[allow(missing_docs)]
557#[bitwarden_error(flat)]
558#[derive(Debug, thiserror::Error)]
559pub enum DeriveKeyConnectorError {
560 #[error(transparent)]
561 WrongPassword(#[from] WrongPasswordError),
562 #[error(transparent)]
563 Crypto(#[from] bitwarden_crypto::CryptoError),
564}
565
566pub(super) fn derive_key_connector(
568 request: DeriveKeyConnectorRequest,
569) -> Result<B64, DeriveKeyConnectorError> {
570 let master_key = MasterKey::derive(&request.password, &request.email, &request.kdf)?;
571 master_key
572 .decrypt_user_key(request.user_key_encrypted)
573 .map_err(|_| WrongPasswordError)?;
574
575 Ok(master_key.to_base64())
576}
577
578#[derive(Serialize, Deserialize, Debug)]
580#[serde(rename_all = "camelCase", deny_unknown_fields)]
581#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
582#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
583pub struct MakeKeyPairResponse {
584 user_public_key: B64,
586 user_key_encrypted_private_key: EncString,
588}
589
590pub(super) fn make_key_pair(user_key: B64) -> Result<MakeKeyPairResponse, CryptoError> {
591 let user_key = UserKey::new(SymmetricCryptoKey::try_from(user_key)?);
592
593 let key_pair = user_key.make_key_pair()?;
594
595 Ok(MakeKeyPairResponse {
596 user_public_key: key_pair.public,
597 user_key_encrypted_private_key: key_pair.private,
598 })
599}
600
601#[derive(Serialize, Deserialize, Debug)]
603#[serde(rename_all = "camelCase", deny_unknown_fields)]
604#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
605#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
606pub struct VerifyAsymmetricKeysRequest {
607 user_key: B64,
609 user_public_key: B64,
611 user_key_encrypted_private_key: EncString,
613}
614
615#[derive(Serialize, Deserialize, Debug)]
617#[serde(rename_all = "camelCase", deny_unknown_fields)]
618#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
619#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
620pub struct VerifyAsymmetricKeysResponse {
621 private_key_decryptable: bool,
623 valid_private_key: bool,
625}
626
627pub(super) fn verify_asymmetric_keys(
628 request: VerifyAsymmetricKeysRequest,
629) -> Result<VerifyAsymmetricKeysResponse, CryptoError> {
630 #[derive(Debug, thiserror::Error)]
631 enum VerifyError {
632 #[error("Failed to decrypt private key: {0:?}")]
633 DecryptFailed(bitwarden_crypto::CryptoError),
634 #[error("Failed to parse decrypted private key: {0:?}")]
635 ParseFailed(bitwarden_crypto::CryptoError),
636 #[error("Failed to derive a public key: {0:?}")]
637 PublicFailed(bitwarden_crypto::CryptoError),
638 #[error("Derived public key doesn't match")]
639 KeyMismatch,
640 }
641
642 fn verify_inner(
643 user_key: &SymmetricCryptoKey,
644 request: &VerifyAsymmetricKeysRequest,
645 ) -> Result<(), VerifyError> {
646 let decrypted_private_key: Vec<u8> = request
647 .user_key_encrypted_private_key
648 .decrypt_with_key(user_key)
649 .map_err(VerifyError::DecryptFailed)?;
650
651 let decrypted_private_key = Pkcs8PrivateKeyBytes::from(decrypted_private_key);
652 let private_key = AsymmetricCryptoKey::from_der(&decrypted_private_key)
653 .map_err(VerifyError::ParseFailed)?;
654
655 let derived_public_key_vec = private_key
656 .to_public_key()
657 .to_der()
658 .map_err(VerifyError::PublicFailed)?;
659
660 let derived_public_key = B64::from(derived_public_key_vec);
661
662 if derived_public_key != request.user_public_key {
663 return Err(VerifyError::KeyMismatch);
664 }
665 Ok(())
666 }
667
668 let user_key = SymmetricCryptoKey::try_from(request.user_key.clone())?;
669
670 Ok(match verify_inner(&user_key, &request) {
671 Ok(_) => VerifyAsymmetricKeysResponse {
672 private_key_decryptable: true,
673 valid_private_key: true,
674 },
675 Err(e) => {
676 log::debug!("User asymmetric keys verification: {e}");
677
678 VerifyAsymmetricKeysResponse {
679 private_key_decryptable: !matches!(e, VerifyError::DecryptFailed(_)),
680 valid_private_key: false,
681 }
682 }
683 })
684}
685
686#[derive(Serialize, Deserialize, Debug, Clone)]
688#[serde(rename_all = "camelCase", deny_unknown_fields)]
689#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
690#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
691pub struct UserCryptoV2KeysResponse {
692 user_key: B64,
694
695 private_key: EncString,
697 public_key: B64,
699 signed_public_key: SignedPublicKey,
701
702 signing_key: EncString,
704 verifying_key: B64,
706
707 security_state: SignedSecurityState,
709 security_version: u64,
711}
712
713pub(crate) fn make_v2_keys_for_v1_user(
717 client: &Client,
718) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
719 let key_store = client.internal.get_key_store();
720 let mut ctx = key_store.context();
721
722 let private_key_id = AsymmetricKeyId::UserPrivateKey;
724
725 if client.internal.get_security_version() != 1 {
727 return Err(StatefulCryptoError::WrongAccountCryptoVersion {
728 expected: "1".to_string(),
729 got: 2,
730 });
731 }
732
733 if !ctx.has_asymmetric_key(AsymmetricKeyId::UserPrivateKey) {
737 return Err(StatefulCryptoError::Crypto(CryptoError::MissingKeyId(
738 "UserPrivateKey".to_string(),
739 )));
740 }
741
742 #[allow(deprecated)]
743 let private_key = ctx.dangerous_get_asymmetric_key(private_key_id)?.clone();
744
745 let user_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
747
748 let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
750 let temporary_signing_key_id = ctx.add_local_signing_key(signing_key.clone())?;
751
752 let signed_public_key = ctx.make_signed_public_key(private_key_id, temporary_signing_key_id)?;
754 let public_key = private_key.to_public_key();
755
756 let security_state = SecurityState::initialize_for_user(
758 client
759 .internal
760 .get_user_id()
761 .ok_or(StatefulCryptoError::MissingSecurityState)?,
762 );
763 let signed_security_state = security_state.sign(temporary_signing_key_id, &mut ctx)?;
764
765 Ok(UserCryptoV2KeysResponse {
766 user_key: user_key.to_base64(),
767
768 private_key: private_key.to_der()?.encrypt_with_key(&user_key)?,
769 public_key: public_key.to_der()?.into(),
770 signed_public_key,
771
772 signing_key: signing_key.to_cose().encrypt_with_key(&user_key)?,
773 verifying_key: signing_key.to_verifying_key().to_cose().into(),
774
775 security_state: signed_security_state,
776 security_version: security_state.version(),
777 })
778}
779
780pub(crate) fn get_v2_rotated_account_keys(
785 client: &Client,
786) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
787 let key_store = client.internal.get_key_store();
788 let mut ctx = key_store.context();
789
790 if client.internal.get_security_version() == 1 {
793 return Err(StatefulCryptoError::WrongAccountCryptoVersion {
794 expected: "2+".to_string(),
795 got: 1,
796 });
797 }
798
799 let security_state = client
800 .internal
801 .security_state
802 .read()
803 .expect("RwLock is not poisoned")
804 .to_owned()
805 .ok_or(StatefulCryptoError::MissingSecurityState)?;
808
809 let rotated_keys = dangerous_get_v2_rotated_account_keys(
810 AsymmetricKeyId::UserPrivateKey,
811 SigningKeyId::UserSigningKey,
812 &ctx,
813 )?;
814
815 Ok(UserCryptoV2KeysResponse {
816 user_key: rotated_keys.user_key.to_base64(),
817
818 private_key: rotated_keys.private_key,
819 public_key: rotated_keys.public_key.into(),
820 signed_public_key: rotated_keys.signed_public_key,
821
822 signing_key: rotated_keys.signing_key,
823 verifying_key: rotated_keys.verifying_key.into(),
824
825 security_state: security_state.sign(SigningKeyId::UserSigningKey, &mut ctx)?,
826 security_version: security_state.version(),
827 })
828}
829
830#[cfg(test)]
831mod tests {
832 use std::num::NonZeroU32;
833
834 use bitwarden_crypto::RsaKeyPair;
835
836 use super::*;
837 use crate::{Client, client::internal::UserKeyState};
838 const TEST_VECTOR_USER_KEY_V2_B64: &str = "pQEEAlACHUUoybNAuJoZzqNMxz2bAzoAARFvBIQDBAUGIFggAvGl4ifaUAomQdCdUPpXLHtypiQxHjZwRHeI83caZM4B";
839 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";
840 #[allow(unused)]
841 const TEST_VECTOR_PUBLIC_KEY_V2: &str = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz/+1jPJ1HqcaCdKrTPms8XJcvnmd9alI42U2XF/4GMNTM5KF1gI6snhR/23ZLatZRFMHoK8ZCMSpGNkjLadArz52ldceTvBOhQUiWylkZQ4NfNa3xIYJubXOmkeDyfNuyLxVZvcZOko9PdT+Qx2QxDrFi2XNo2I7aVFd19/COIEkex4mJ0eA3MHFpKCdxYbcTAsGID8+kVR9L84S1JptZoG8x+iB/D3/Q4y02UsQYpFTu0vbPY84YmW03ngJdxWzS8X4/UJI/jaEn5rO4xlU5QcL0l4IybP5LRpE9XEeUHATKVOG7eNfpe9zDfKV2qQoofQMH9VvkWO4psaWDjBSdwIDAQAB";
842 #[allow(unused)]
843 const TEST_VECTOR_SIGNED_PUBLIC_KEY_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8BoFkBTqNpYWxnb3JpdGhtAG1jb250ZW50Rm9ybWF0AGlwdWJsaWNLZXlZASYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP/7WM8nUepxoJ0qtM+azxcly+eZ31qUjjZTZcX/gYw1MzkoXWAjqyeFH/bdktq1lEUwegrxkIxKkY2SMtp0CvPnaV1x5O8E6FBSJbKWRlDg181rfEhgm5tc6aR4PJ827IvFVm9xk6Sj091P5DHZDEOsWLZc2jYjtpUV3X38I4gSR7HiYnR4DcwcWkoJ3FhtxMCwYgPz6RVH0vzhLUmm1mgbzH6IH8Pf9DjLTZSxBikVO7S9s9jzhiZbTeeAl3FbNLxfj9Qkj+NoSfms7jGVTlBwvSXgjJs/ktGkT1cR5QcBMpU4bt41+l73MN8pXapCih9Awf1W+RY7imxpYOMFJ3AgMBAAFYQMq/hT4wod2w8xyoM7D86ctuLNX4ZRo+jRHf2sZfaO7QsvonG/ZYuNKF5fq8wpxMRjfoMvnY2TTShbgzLrW8BA4=";
844 const TEST_VECTOR_SIGNING_KEY_V2: &str = "7.g1gcowE6AAERbwMYZQRQAh1FKMmzQLiaGc6jTMc9m6EFWBhYePc2qkCruHAPXgbzXsIP1WVk11ArbLNYUBpifToURlwHKs1je2BwZ1C/5thz4nyNbL0wDaYkRWI9ex1wvB7KhdzC7ltStEd5QttboTSCaXQROSZaGBPNO5+Bu3sTY8F5qK1pBUo6AHNN";
845 #[allow(unused)]
846 const TEST_VECTOR_VERIFYING_KEY_V2: &str =
847 "pgEBAlAmkP0QgfdMVbIujX55W/yNAycEgQIgBiFYIEM6JxBmjWQTruAm3s6BTaJy1q6BzQetMBacNeRJ0kxR";
848 const TEST_VECTOR_SECURITY_STATE_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8CoFgkomhlbnRpdHlJZFBHOOw2BI9OQoNq+Vl1xZZKZ3ZlcnNpb24CWEAlchbJR0vmRfShG8On7Q2gknjkw4Dd6MYBLiH4u+/CmfQdmjNZdf6kozgW/6NXyKVNu8dAsKsin+xxXkDyVZoG";
849
850 const TEST_USER_EMAIL: &str = "[email protected]";
851 const TEST_USER_PASSWORD: &str = "asdfasdfasdf";
852 const TEST_ACCOUNT_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
853 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=";
854
855 #[tokio::test]
856 async fn test_update_kdf() {
857 let client = Client::new(None);
858
859 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();
860
861 let kdf = Kdf::PBKDF2 {
862 iterations: 100_000.try_into().unwrap(),
863 };
864
865 initialize_user_crypto(
866 & client,
867 InitUserCryptoRequest {
868 user_id: Some(UserId::new_v4()),
869 kdf_params: kdf.clone(),
870 email: "[email protected]".into(),
871 private_key: priv_key.to_owned(),
872 signing_key: None,
873 security_state: None,
874 method: InitUserCryptoMethod::Password {
875 password: "asdfasdfasdf".into(),
876 user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
877 },
878 },
879 )
880 .await
881 .unwrap();
882
883 let new_kdf = Kdf::PBKDF2 {
884 iterations: 600_000.try_into().unwrap(),
885 };
886 let new_kdf_response = make_update_kdf(&client, "123412341234", &new_kdf).unwrap();
887
888 let client2 = Client::new(None);
889
890 initialize_user_crypto(
891 &client2,
892 InitUserCryptoRequest {
893 user_id: Some(UserId::new_v4()),
894 kdf_params: new_kdf.clone(),
895 email: "[email protected]".into(),
896 private_key: priv_key.to_owned(),
897 signing_key: None,
898 security_state: None,
899 method: InitUserCryptoMethod::Password {
900 password: "123412341234".into(),
901 user_key: new_kdf_response
902 .master_password_unlock_data
903 .master_key_wrapped_user_key,
904 },
905 },
906 )
907 .await
908 .unwrap();
909
910 let new_hash = client2
911 .kdf()
912 .hash_password(
913 "[email protected]".into(),
914 "123412341234".into(),
915 new_kdf.clone(),
916 bitwarden_crypto::HashPurpose::ServerAuthorization,
917 )
918 .await
919 .unwrap();
920
921 assert_eq!(
922 new_hash,
923 new_kdf_response
924 .master_password_authentication_data
925 .master_password_authentication_hash
926 );
927
928 let client_key = {
929 let key_store = client.internal.get_key_store();
930 let ctx = key_store.context();
931 #[allow(deprecated)]
932 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
933 .unwrap()
934 .to_base64()
935 };
936
937 let client2_key = {
938 let key_store = client2.internal.get_key_store();
939 let ctx = key_store.context();
940 #[allow(deprecated)]
941 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
942 .unwrap()
943 .to_base64()
944 };
945
946 assert_eq!(client_key, client2_key);
947 }
948
949 #[tokio::test]
950 async fn test_update_password() {
951 let client = Client::new(None);
952
953 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();
954
955 let kdf = Kdf::PBKDF2 {
956 iterations: 100_000.try_into().unwrap(),
957 };
958
959 initialize_user_crypto(
960 &client,
961 InitUserCryptoRequest {
962 user_id: Some(UserId::new_v4()),
963 kdf_params: kdf.clone(),
964 email: "[email protected]".into(),
965 private_key: priv_key.to_owned(),
966 signing_key: None,
967 security_state: None,
968 method: InitUserCryptoMethod::Password {
969 password: "asdfasdfasdf".into(),
970 user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
971 },
972 },
973 )
974 .await
975 .unwrap();
976
977 let new_password_response = make_update_password(&client, "123412341234".into()).unwrap();
978
979 let client2 = Client::new(None);
980
981 initialize_user_crypto(
982 &client2,
983 InitUserCryptoRequest {
984 user_id: Some(UserId::new_v4()),
985 kdf_params: kdf.clone(),
986 email: "[email protected]".into(),
987 private_key: priv_key.to_owned(),
988 signing_key: None,
989 security_state: None,
990 method: InitUserCryptoMethod::Password {
991 password: "123412341234".into(),
992 user_key: new_password_response.new_key,
993 },
994 },
995 )
996 .await
997 .unwrap();
998
999 let new_hash = client2
1000 .kdf()
1001 .hash_password(
1002 "[email protected]".into(),
1003 "123412341234".into(),
1004 kdf.clone(),
1005 bitwarden_crypto::HashPurpose::ServerAuthorization,
1006 )
1007 .await
1008 .unwrap();
1009
1010 assert_eq!(new_hash, new_password_response.password_hash);
1011
1012 let client_key = {
1013 let key_store = client.internal.get_key_store();
1014 let ctx = key_store.context();
1015 #[allow(deprecated)]
1016 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1017 .unwrap()
1018 .to_base64()
1019 };
1020
1021 let client2_key = {
1022 let key_store = client2.internal.get_key_store();
1023 let ctx = key_store.context();
1024 #[allow(deprecated)]
1025 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1026 .unwrap()
1027 .to_base64()
1028 };
1029
1030 assert_eq!(client_key, client2_key);
1031 }
1032
1033 #[tokio::test]
1034 async fn test_initialize_user_crypto_pin() {
1035 let client = Client::new(None);
1036
1037 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();
1038
1039 initialize_user_crypto(
1040 &client,
1041 InitUserCryptoRequest {
1042 user_id: Some(UserId::new_v4()),
1043 kdf_params: Kdf::PBKDF2 {
1044 iterations: 100_000.try_into().unwrap(),
1045 },
1046 email: "[email protected]".into(),
1047 private_key: priv_key.to_owned(),
1048 signing_key: None,
1049 security_state: None,
1050 method: InitUserCryptoMethod::Password {
1051 password: "asdfasdfasdf".into(),
1052 user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
1053 },
1054 },
1055 )
1056 .await
1057 .unwrap();
1058
1059 let pin_key = derive_pin_key(&client, "1234".into()).unwrap();
1060
1061 let client2 = Client::new(None);
1063 initialize_user_crypto(
1064 &client2,
1065 InitUserCryptoRequest {
1066 user_id: Some(UserId::new_v4()),
1067 kdf_params: Kdf::PBKDF2 {
1068 iterations: 100_000.try_into().unwrap(),
1069 },
1070 email: "[email protected]".into(),
1071 private_key: priv_key.to_owned(),
1072 signing_key: None,
1073 security_state: None,
1074 method: InitUserCryptoMethod::Pin {
1075 pin: "1234".into(),
1076 pin_protected_user_key: pin_key.pin_protected_user_key,
1077 },
1078 },
1079 )
1080 .await
1081 .unwrap();
1082
1083 let client_key = {
1084 let key_store = client.internal.get_key_store();
1085 let ctx = key_store.context();
1086 #[allow(deprecated)]
1087 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1088 .unwrap()
1089 .to_base64()
1090 };
1091
1092 let client2_key = {
1093 let key_store = client2.internal.get_key_store();
1094 let ctx = key_store.context();
1095 #[allow(deprecated)]
1096 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1097 .unwrap()
1098 .to_base64()
1099 };
1100
1101 assert_eq!(client_key, client2_key);
1102
1103 let pin_protected_user_key = derive_pin_user_key(&client, pin_key.encrypted_pin).unwrap();
1105
1106 let client3 = Client::new(None);
1107
1108 initialize_user_crypto(
1109 &client3,
1110 InitUserCryptoRequest {
1111 user_id: Some(UserId::new_v4()),
1112 kdf_params: Kdf::PBKDF2 {
1113 iterations: 100_000.try_into().unwrap(),
1114 },
1115 email: "[email protected]".into(),
1116 private_key: priv_key.to_owned(),
1117 signing_key: None,
1118 security_state: None,
1119 method: InitUserCryptoMethod::Pin {
1120 pin: "1234".into(),
1121 pin_protected_user_key,
1122 },
1123 },
1124 )
1125 .await
1126 .unwrap();
1127
1128 let client_key = {
1129 let key_store = client.internal.get_key_store();
1130 let ctx = key_store.context();
1131 #[allow(deprecated)]
1132 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1133 .unwrap()
1134 .to_base64()
1135 };
1136
1137 let client3_key = {
1138 let key_store = client3.internal.get_key_store();
1139 let ctx = key_store.context();
1140 #[allow(deprecated)]
1141 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
1142 .unwrap()
1143 .to_base64()
1144 };
1145
1146 assert_eq!(client_key, client3_key);
1147 }
1148
1149 #[tokio::test]
1150 async fn test_initialize_user_crypto_pin_envelope() {
1151 let user_key = "5yKAZ4TSSEGje54MV5lc5ty6crkqUz4xvl+8Dm/piNLKf6OgRi2H0uzttNTXl9z6ILhkmuIXzGpAVc2YdorHgQ==";
1152 let test_pin = "1234";
1153
1154 let client1 = Client::new(None);
1155 initialize_user_crypto(
1156 &client1,
1157 InitUserCryptoRequest {
1158 user_id: Some(UserId::new_v4()),
1159 kdf_params: Kdf::PBKDF2 {
1160 iterations: 100_000.try_into().unwrap(),
1161 },
1162 email: "[email protected]".into(),
1163 private_key: make_key_pair(user_key.try_into().unwrap())
1164 .unwrap()
1165 .user_key_encrypted_private_key,
1166 signing_key: None,
1167 security_state: None,
1168 method: InitUserCryptoMethod::DecryptedKey {
1169 decrypted_user_key: user_key.to_string(),
1170 },
1171 },
1172 )
1173 .await
1174 .unwrap();
1175
1176 let enroll_response = client1.crypto().enroll_pin(test_pin.to_string()).unwrap();
1177
1178 let client2 = Client::new(None);
1179 initialize_user_crypto(
1180 &client2,
1181 InitUserCryptoRequest {
1182 user_id: Some(UserId::new_v4()),
1183 kdf_params: Kdf::PBKDF2 {
1186 iterations: 600_000.try_into().unwrap(),
1187 },
1188 email: "[email protected]".into(),
1189 private_key: make_key_pair(user_key.try_into().unwrap())
1190 .unwrap()
1191 .user_key_encrypted_private_key,
1192 signing_key: None,
1193 security_state: None,
1194 method: InitUserCryptoMethod::PinEnvelope {
1195 pin: test_pin.to_string(),
1196 pin_protected_user_key_envelope: enroll_response
1197 .pin_protected_user_key_envelope,
1198 },
1199 },
1200 )
1201 .await
1202 .unwrap();
1203 }
1204
1205 #[test]
1206 fn test_enroll_admin_password_reset() {
1207 let client = Client::new(None);
1208
1209 let master_key = MasterKey::derive(
1210 "asdfasdfasdf",
1211 "[email protected]",
1212 &Kdf::PBKDF2 {
1213 iterations: NonZeroU32::new(600_000).unwrap(),
1214 },
1215 )
1216 .unwrap();
1217
1218 let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap();
1219 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();
1220 client
1221 .internal
1222 .initialize_user_crypto_master_key(
1223 master_key,
1224 user_key,
1225 UserKeyState {
1226 private_key,
1227 signing_key: None,
1228 security_state: None,
1229 },
1230 )
1231 .unwrap();
1232
1233 let public_key: B64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsy7RFHcX3C8Q4/OMmhhbFReYWfB45W9PDTEA8tUZwZmtOiN2RErIS2M1c+K/4HoDJ/TjpbX1f2MZcr4nWvKFuqnZXyewFc+jmvKVewYi+NAu2++vqKq2kKcmMNhwoQDQdQIVy/Uqlp4Cpi2cIwO6ogq5nHNJGR3jm+CpyrafYlbz1bPvL3hbyoGDuG2tgADhyhXUdFuef2oF3wMvn1lAJAvJnPYpMiXUFmj1ejmbwtlxZDrHgUJvUcp7nYdwUKaFoi+sOttHn3u7eZPtNvxMjhSS/X/1xBIzP/mKNLdywH5LoRxniokUk+fV3PYUxJsiU3lV0Trc/tH46jqd8ZGjmwIDAQAB".parse().unwrap();
1234
1235 let encrypted = enroll_admin_password_reset(&client, public_key).unwrap();
1236
1237 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();
1238
1239 let private_key = Pkcs8PrivateKeyBytes::from(private_key.as_bytes());
1240 let private_key = AsymmetricCryptoKey::from_der(&private_key).unwrap();
1241 let decrypted: SymmetricCryptoKey =
1242 encrypted.decapsulate_key_unsigned(&private_key).unwrap();
1243
1244 let key_store = client.internal.get_key_store();
1245 let ctx = key_store.context();
1246 #[allow(deprecated)]
1247 let expected = ctx
1248 .dangerous_get_symmetric_key(SymmetricKeyId::User)
1249 .unwrap();
1250
1251 assert_eq!(decrypted, *expected);
1252 }
1253
1254 #[test]
1255 fn test_derive_key_connector() {
1256 let request = DeriveKeyConnectorRequest {
1257 password: "asdfasdfasdf".to_string(),
1258 email: "[email protected]".to_string(),
1259 kdf: Kdf::PBKDF2 {
1260 iterations: NonZeroU32::new(600_000).unwrap(),
1261 },
1262 user_key_encrypted: "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(),
1263 };
1264
1265 let result = derive_key_connector(request).unwrap();
1266
1267 assert_eq!(
1268 result.to_string(),
1269 "ySXq1RVLKEaV1eoQE/ui9aFKIvXTl9PAXwp1MljfF50="
1270 );
1271 }
1272
1273 fn setup_asymmetric_keys_test() -> (UserKey, RsaKeyPair) {
1274 let master_key = MasterKey::derive(
1275 "asdfasdfasdf",
1276 "[email protected]",
1277 &Kdf::PBKDF2 {
1278 iterations: NonZeroU32::new(600_000).unwrap(),
1279 },
1280 )
1281 .unwrap();
1282 let user_key = (master_key.make_user_key().unwrap()).0;
1283 let key_pair = user_key.make_key_pair().unwrap();
1284
1285 (user_key, key_pair)
1286 }
1287
1288 #[test]
1289 fn test_make_key_pair() {
1290 let (user_key, _) = setup_asymmetric_keys_test();
1291
1292 let response = make_key_pair(user_key.0.to_base64()).unwrap();
1293
1294 assert!(!response.user_public_key.to_string().is_empty());
1295 let encrypted_private_key = response.user_key_encrypted_private_key;
1296 let private_key: Vec<u8> = encrypted_private_key.decrypt_with_key(&user_key.0).unwrap();
1297 assert!(!private_key.is_empty());
1298 }
1299
1300 #[test]
1301 fn test_verify_asymmetric_keys_success() {
1302 let (user_key, key_pair) = setup_asymmetric_keys_test();
1303
1304 let request = VerifyAsymmetricKeysRequest {
1305 user_key: user_key.0.to_base64(),
1306 user_public_key: key_pair.public,
1307 user_key_encrypted_private_key: key_pair.private,
1308 };
1309 let response = verify_asymmetric_keys(request).unwrap();
1310
1311 assert!(response.private_key_decryptable);
1312 assert!(response.valid_private_key);
1313 }
1314
1315 #[test]
1316 fn test_verify_asymmetric_keys_decrypt_failed() {
1317 let (user_key, key_pair) = setup_asymmetric_keys_test();
1318 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();
1319
1320 let request = VerifyAsymmetricKeysRequest {
1321 user_key: user_key.0.to_base64(),
1322 user_public_key: key_pair.public,
1323 user_key_encrypted_private_key: undecryptable_private_key,
1324 };
1325 let response = verify_asymmetric_keys(request).unwrap();
1326
1327 assert!(!response.private_key_decryptable);
1328 assert!(!response.valid_private_key);
1329 }
1330
1331 #[test]
1332 fn test_verify_asymmetric_keys_parse_failed() {
1333 let (user_key, key_pair) = setup_asymmetric_keys_test();
1334
1335 let invalid_private_key = "bad_key".to_string().encrypt_with_key(&user_key.0).unwrap();
1336
1337 let request = VerifyAsymmetricKeysRequest {
1338 user_key: user_key.0.to_base64(),
1339 user_public_key: key_pair.public,
1340 user_key_encrypted_private_key: invalid_private_key,
1341 };
1342 let response = verify_asymmetric_keys(request).unwrap();
1343
1344 assert!(response.private_key_decryptable);
1345 assert!(!response.valid_private_key);
1346 }
1347
1348 #[test]
1349 fn test_verify_asymmetric_keys_key_mismatch() {
1350 let (user_key, key_pair) = setup_asymmetric_keys_test();
1351 let new_key_pair = user_key.make_key_pair().unwrap();
1352
1353 let request = VerifyAsymmetricKeysRequest {
1354 user_key: user_key.0.to_base64(),
1355 user_public_key: key_pair.public,
1356 user_key_encrypted_private_key: new_key_pair.private,
1357 };
1358 let response = verify_asymmetric_keys(request).unwrap();
1359
1360 assert!(response.private_key_decryptable);
1361 assert!(!response.valid_private_key);
1362 }
1363
1364 #[tokio::test]
1365 async fn test_make_v2_keys_for_v1_user() {
1366 let client = Client::new(None);
1367
1368 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();
1369 let encrypted_userkey: EncString = "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap();
1370
1371 initialize_user_crypto(
1372 &client,
1373 InitUserCryptoRequest {
1374 user_id: Some(UserId::new_v4()),
1375 kdf_params: Kdf::PBKDF2 {
1376 iterations: 100_000.try_into().unwrap(),
1377 },
1378 email: "[email protected]".into(),
1379 private_key: priv_key,
1380 signing_key: None,
1381 security_state: None,
1382 method: InitUserCryptoMethod::Password {
1383 password: "asdfasdfasdf".into(),
1384 user_key: encrypted_userkey.clone(),
1385 },
1386 },
1387 )
1388 .await
1389 .unwrap();
1390
1391 let master_key = MasterKey::derive(
1392 "asdfasdfasdf",
1393 "[email protected]",
1394 &Kdf::PBKDF2 {
1395 iterations: NonZeroU32::new(100_000).unwrap(),
1396 },
1397 )
1398 .unwrap();
1399 let enrollment_response = make_v2_keys_for_v1_user(&client).unwrap();
1400 let encrypted_userkey_v2 = master_key
1401 .encrypt_user_key(
1402 &SymmetricCryptoKey::try_from(enrollment_response.clone().user_key).unwrap(),
1403 )
1404 .unwrap();
1405
1406 let client2 = Client::new(None);
1407 initialize_user_crypto(
1408 &client2,
1409 InitUserCryptoRequest {
1410 user_id: Some(UserId::new_v4()),
1411 kdf_params: Kdf::PBKDF2 {
1412 iterations: 100_000.try_into().unwrap(),
1413 },
1414 email: "[email protected]".into(),
1415 private_key: enrollment_response.private_key,
1416 signing_key: Some(enrollment_response.signing_key),
1417 security_state: Some(enrollment_response.security_state),
1418 method: InitUserCryptoMethod::Password {
1419 password: "asdfasdfasdf".into(),
1420 user_key: encrypted_userkey_v2,
1421 },
1422 },
1423 )
1424 .await
1425 .unwrap();
1426 }
1427
1428 #[tokio::test]
1429 async fn test_make_v2_keys_for_v1_user_with_v2_user_fails() {
1430 let client = Client::new(None);
1431 #[allow(deprecated)]
1432 client
1433 .internal
1434 .get_key_store()
1435 .context_mut()
1436 .set_symmetric_key(
1437 SymmetricKeyId::User,
1438 SymmetricCryptoKey::make_aes256_cbc_hmac_key(),
1439 )
1440 .unwrap();
1441 initialize_user_crypto(
1442 &client,
1443 InitUserCryptoRequest {
1444 user_id: Some(UserId::new_v4()),
1445 kdf_params: Kdf::PBKDF2 {
1446 iterations: 100_000.try_into().unwrap(),
1447 },
1448 email: "[email protected]".into(),
1449 private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
1450 signing_key: Some(TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap()),
1451 security_state: Some(TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap()),
1452 method: InitUserCryptoMethod::DecryptedKey {
1453 decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
1454 },
1455 },
1456 )
1457 .await
1458 .unwrap();
1459
1460 let result = make_v2_keys_for_v1_user(&client);
1461 assert!(matches!(
1462 result,
1463 Err(StatefulCryptoError::WrongAccountCryptoVersion {
1464 expected: _,
1465 got: _
1466 })
1467 ));
1468 }
1469
1470 #[test]
1471 fn test_get_v2_rotated_account_keys_non_v2_user() {
1472 let client = Client::new(None);
1473 #[allow(deprecated)]
1474 client
1475 .internal
1476 .get_key_store()
1477 .context_mut()
1478 .set_symmetric_key(
1479 SymmetricKeyId::User,
1480 SymmetricCryptoKey::make_aes256_cbc_hmac_key(),
1481 )
1482 .unwrap();
1483
1484 let result = get_v2_rotated_account_keys(&client);
1485 assert!(matches!(
1486 result,
1487 Err(StatefulCryptoError::WrongAccountCryptoVersion {
1488 expected: _,
1489 got: _
1490 })
1491 ));
1492 }
1493
1494 #[tokio::test]
1495 async fn test_get_v2_rotated_account_keys() {
1496 let client = Client::new(None);
1497 #[allow(deprecated)]
1498 client
1499 .internal
1500 .get_key_store()
1501 .context_mut()
1502 .set_symmetric_key(
1503 SymmetricKeyId::User,
1504 SymmetricCryptoKey::make_aes256_cbc_hmac_key(),
1505 )
1506 .unwrap();
1507 initialize_user_crypto(
1508 &client,
1509 InitUserCryptoRequest {
1510 user_id: Some(UserId::new_v4()),
1511 kdf_params: Kdf::PBKDF2 {
1512 iterations: 100_000.try_into().unwrap(),
1513 },
1514 email: "[email protected]".into(),
1515 private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
1516 signing_key: Some(TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap()),
1517 security_state: Some(TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap()),
1518 method: InitUserCryptoMethod::DecryptedKey {
1519 decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
1520 },
1521 },
1522 )
1523 .await
1524 .unwrap();
1525
1526 assert!(get_v2_rotated_account_keys(&client).is_ok());
1527 }
1528
1529 #[tokio::test]
1530 async fn test_initialize_user_crypto_master_password_unlock() {
1531 let client = Client::new(None);
1532
1533 initialize_user_crypto(
1534 &client,
1535 InitUserCryptoRequest {
1536 user_id: Some(UserId::new_v4()),
1537 kdf_params: Kdf::PBKDF2 {
1538 iterations: 600_000.try_into().unwrap(),
1539 },
1540 email: TEST_USER_EMAIL.to_string(),
1541 private_key: TEST_ACCOUNT_PRIVATE_KEY.parse().unwrap(),
1542 signing_key: None,
1543 security_state: None,
1544 method: InitUserCryptoMethod::MasterPasswordUnlock {
1545 password: TEST_USER_PASSWORD.to_string(),
1546 master_password_unlock: MasterPasswordUnlockData {
1547 kdf: Kdf::PBKDF2 {
1548 iterations: 600_000.try_into().unwrap(),
1549 },
1550 master_key_wrapped_user_key: TEST_ACCOUNT_USER_KEY.parse().unwrap(),
1551 salt: TEST_USER_EMAIL.to_string(),
1552 },
1553 },
1554 },
1555 )
1556 .await
1557 .unwrap();
1558
1559 let key_store = client.internal.get_key_store();
1560 let context = key_store.context();
1561 assert!(context.has_symmetric_key(SymmetricKeyId::User));
1562 assert!(context.has_asymmetric_key(AsymmetricKeyId::UserPrivateKey));
1563 let login_method = client.internal.get_login_method().unwrap();
1564 if let LoginMethod::User(UserLoginMethod::Username {
1565 email,
1566 kdf,
1567 client_id,
1568 ..
1569 }) = login_method.as_ref()
1570 {
1571 assert_eq!(*email, TEST_USER_EMAIL);
1572 assert_eq!(
1573 *kdf,
1574 Kdf::PBKDF2 {
1575 iterations: 600_000.try_into().unwrap(),
1576 }
1577 );
1578 assert_eq!(*client_id, "");
1579 } else {
1580 panic!("Expected username login method");
1581 }
1582 }
1583}