1use std::collections::HashMap;
8
9use bitwarden_crypto::{
10 dangerous_get_v2_rotated_account_keys, safe::PasswordProtectedKeyEnvelopeError,
11 AsymmetricCryptoKey, CoseSerializable, CryptoError, EncString, Kdf, KeyDecryptable,
12 KeyEncryptable, MasterKey, Pkcs8PrivateKeyBytes, PrimitiveEncryptable, SignatureAlgorithm,
13 SignedPublicKey, SigningKey, SpkiPublicKeyBytes, SymmetricCryptoKey, UnsignedSharedKey,
14 UserKey,
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::{encryption_settings::EncryptionSettingsError, LoginMethod, UserLoginMethod},
25 error::StatefulCryptoError,
26 key_management::{
27 non_generic_wrappers::PasswordProtectedKeyEnvelope, AsymmetricKeyId, SecurityState,
28 SignedSecurityState, SigningKeyId, SymmetricKeyId,
29 },
30 Client, NotAuthenticatedError, OrganizationId, UserId, VaultLockedError, WrongPasswordError,
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 VaultLocked(#[from] VaultLockedError),
42 #[error(transparent)]
43 Crypto(#[from] bitwarden_crypto::CryptoError),
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 DecryptedKey {
86 decrypted_user_key: String,
88 },
89 Pin {
91 pin: String,
93 pin_protected_user_key: EncString,
96 },
97 PinEnvelope {
99 pin: String,
101 pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
103 },
104 AuthRequest {
106 request_private_key: B64,
108 method: AuthRequestMethod,
110 },
111 DeviceKey {
113 device_key: String,
115 protected_device_private_key: EncString,
117 device_protected_user_key: UnsignedSharedKey,
119 },
120 KeyConnector {
122 master_key: B64,
124 user_key: EncString,
126 },
127}
128
129#[derive(Serialize, Deserialize, Debug)]
131#[serde(rename_all = "camelCase", deny_unknown_fields)]
132#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
133#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
134pub enum AuthRequestMethod {
135 UserKey {
137 protected_user_key: UnsignedSharedKey,
139 },
140 MasterKey {
142 protected_master_key: UnsignedSharedKey,
144 auth_request_key: EncString,
146 },
147}
148
149pub(super) async fn initialize_user_crypto(
151 client: &Client,
152 req: InitUserCryptoRequest,
153) -> Result<(), EncryptionSettingsError> {
154 use bitwarden_crypto::{DeviceKey, PinKey};
155
156 use crate::auth::{auth_request_decrypt_master_key, auth_request_decrypt_user_key};
157
158 if let Some(user_id) = req.user_id {
159 client.internal.init_user_id(user_id)?;
160 }
161
162 let key_state = (&req).into();
163
164 match req.method {
165 InitUserCryptoMethod::Password { password, user_key } => {
166 let master_key = MasterKey::derive(&password, &req.email, &req.kdf_params)?;
167 client
168 .internal
169 .initialize_user_crypto_master_key(master_key, user_key, key_state)?;
170 }
171 InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => {
172 let user_key = SymmetricCryptoKey::try_from(decrypted_user_key)?;
173 client
174 .internal
175 .initialize_user_crypto_decrypted_key(user_key, key_state)?;
176 }
177 InitUserCryptoMethod::Pin {
178 pin,
179 pin_protected_user_key,
180 } => {
181 let pin_key = PinKey::derive(pin.as_bytes(), req.email.as_bytes(), &req.kdf_params)?;
182 client.internal.initialize_user_crypto_pin(
183 pin_key,
184 pin_protected_user_key,
185 key_state,
186 )?;
187 }
188 InitUserCryptoMethod::PinEnvelope {
189 pin,
190 pin_protected_user_key_envelope,
191 } => {
192 client.internal.initialize_user_crypto_pin_envelope(
193 pin,
194 pin_protected_user_key_envelope,
195 key_state,
196 )?;
197 }
198 InitUserCryptoMethod::AuthRequest {
199 request_private_key,
200 method,
201 } => {
202 let user_key = match method {
203 AuthRequestMethod::UserKey { protected_user_key } => {
204 auth_request_decrypt_user_key(request_private_key, protected_user_key)?
205 }
206 AuthRequestMethod::MasterKey {
207 protected_master_key,
208 auth_request_key,
209 } => auth_request_decrypt_master_key(
210 request_private_key,
211 protected_master_key,
212 auth_request_key,
213 )?,
214 };
215 client
216 .internal
217 .initialize_user_crypto_decrypted_key(user_key, key_state)?;
218 }
219 InitUserCryptoMethod::DeviceKey {
220 device_key,
221 protected_device_private_key,
222 device_protected_user_key,
223 } => {
224 let device_key = DeviceKey::try_from(device_key)?;
225 let user_key = device_key
226 .decrypt_user_key(protected_device_private_key, device_protected_user_key)?;
227
228 client
229 .internal
230 .initialize_user_crypto_decrypted_key(user_key, key_state)?;
231 }
232 InitUserCryptoMethod::KeyConnector {
233 master_key,
234 user_key,
235 } => {
236 let mut bytes = master_key.as_bytes().to_vec();
237 let master_key = MasterKey::try_from(bytes.as_mut_slice())?;
238
239 client
240 .internal
241 .initialize_user_crypto_master_key(master_key, user_key, key_state)?;
242 }
243 }
244
245 client
246 .internal
247 .set_login_method(crate::client::LoginMethod::User(
248 crate::client::UserLoginMethod::Username {
249 client_id: "".to_string(),
250 email: req.email,
251 kdf: req.kdf_params,
252 },
253 ));
254
255 Ok(())
256}
257
258#[derive(Serialize, Deserialize, Debug)]
260#[serde(rename_all = "camelCase", deny_unknown_fields)]
261#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
262#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
263pub struct InitOrgCryptoRequest {
264 pub organization_keys: HashMap<OrganizationId, UnsignedSharedKey>,
266}
267
268pub(super) async fn initialize_org_crypto(
270 client: &Client,
271 req: InitOrgCryptoRequest,
272) -> Result<(), EncryptionSettingsError> {
273 let organization_keys = req.organization_keys.into_iter().collect();
274 client.internal.initialize_org_crypto(organization_keys)?;
275 Ok(())
276}
277
278pub(super) async fn get_user_encryption_key(client: &Client) -> Result<B64, CryptoClientError> {
279 let key_store = client.internal.get_key_store();
280 let ctx = key_store.context();
281 #[allow(deprecated)]
283 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
284
285 Ok(user_key.to_base64())
286}
287
288#[derive(Serialize, Deserialize, Debug)]
290#[serde(rename_all = "camelCase", deny_unknown_fields)]
291#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
292#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
293pub struct UpdatePasswordResponse {
294 password_hash: B64,
296 new_key: EncString,
298}
299
300pub(super) fn update_password(
301 client: &Client,
302 new_password: String,
303) -> Result<UpdatePasswordResponse, CryptoClientError> {
304 let key_store = client.internal.get_key_store();
305 let ctx = key_store.context();
306 #[allow(deprecated)]
308 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
309
310 let login_method = client
311 .internal
312 .get_login_method()
313 .ok_or(NotAuthenticatedError)?;
314
315 let new_master_key = match login_method.as_ref() {
317 LoginMethod::User(
318 UserLoginMethod::Username { email, kdf, .. }
319 | UserLoginMethod::ApiKey { email, kdf, .. },
320 ) => MasterKey::derive(&new_password, email, kdf)?,
321 #[cfg(feature = "secrets")]
322 LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?,
323 };
324
325 let new_key = new_master_key.encrypt_user_key(user_key)?;
326
327 let password_hash = new_master_key.derive_master_key_hash(
328 new_password.as_bytes(),
329 bitwarden_crypto::HashPurpose::ServerAuthorization,
330 );
331
332 Ok(UpdatePasswordResponse {
333 password_hash,
334 new_key,
335 })
336}
337
338#[derive(Serialize, Deserialize, Debug)]
340#[serde(rename_all = "camelCase", deny_unknown_fields)]
341#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
342#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
343pub struct EnrollPinResponse {
344 pub pin_protected_user_key_envelope: PasswordProtectedKeyEnvelope,
346 pub user_key_encrypted_pin: EncString,
348}
349
350pub(super) fn enroll_pin(
351 client: &Client,
352 pin: String,
353) -> Result<EnrollPinResponse, CryptoClientError> {
354 let key_store = client.internal.get_key_store();
355 let mut ctx = key_store.context_mut();
356
357 let key_envelope =
358 PasswordProtectedKeyEnvelope(bitwarden_crypto::safe::PasswordProtectedKeyEnvelope::seal(
359 SymmetricKeyId::User,
360 &pin,
361 &ctx,
362 )?);
363 let encrypted_pin = pin.encrypt(&mut ctx, SymmetricKeyId::User)?;
364 Ok(EnrollPinResponse {
365 pin_protected_user_key_envelope: key_envelope,
366 user_key_encrypted_pin: encrypted_pin,
367 })
368}
369
370#[derive(Serialize, Deserialize, Debug)]
372#[serde(rename_all = "camelCase", deny_unknown_fields)]
373#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
374#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
375pub struct DerivePinKeyResponse {
376 pin_protected_user_key: EncString,
378 encrypted_pin: EncString,
380}
381
382pub(super) fn derive_pin_key(
383 client: &Client,
384 pin: String,
385) -> Result<DerivePinKeyResponse, CryptoClientError> {
386 let key_store = client.internal.get_key_store();
387 let ctx = key_store.context();
388 #[allow(deprecated)]
390 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
391
392 let login_method = client
393 .internal
394 .get_login_method()
395 .ok_or(NotAuthenticatedError)?;
396
397 let pin_protected_user_key = derive_pin_protected_user_key(&pin, &login_method, user_key)?;
398
399 Ok(DerivePinKeyResponse {
400 pin_protected_user_key,
401 encrypted_pin: pin.encrypt_with_key(user_key)?,
402 })
403}
404
405pub(super) fn derive_pin_user_key(
406 client: &Client,
407 encrypted_pin: EncString,
408) -> Result<EncString, CryptoClientError> {
409 let key_store = client.internal.get_key_store();
410 let ctx = key_store.context();
411 #[allow(deprecated)]
413 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
414
415 let pin: String = encrypted_pin.decrypt_with_key(user_key)?;
416 let login_method = client
417 .internal
418 .get_login_method()
419 .ok_or(NotAuthenticatedError)?;
420
421 derive_pin_protected_user_key(&pin, &login_method, user_key)
422}
423
424fn derive_pin_protected_user_key(
425 pin: &str,
426 login_method: &LoginMethod,
427 user_key: &SymmetricCryptoKey,
428) -> Result<EncString, CryptoClientError> {
429 use bitwarden_crypto::PinKey;
430
431 let derived_key = match login_method {
432 LoginMethod::User(
433 UserLoginMethod::Username { email, kdf, .. }
434 | UserLoginMethod::ApiKey { email, kdf, .. },
435 ) => PinKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?,
436 #[cfg(feature = "secrets")]
437 LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?,
438 };
439
440 Ok(derived_key.encrypt_user_key(user_key)?)
441}
442
443#[allow(missing_docs)]
444#[bitwarden_error(flat)]
445#[derive(Debug, thiserror::Error)]
446pub enum EnrollAdminPasswordResetError {
447 #[error(transparent)]
448 VaultLocked(#[from] VaultLockedError),
449 #[error(transparent)]
450 Crypto(#[from] bitwarden_crypto::CryptoError),
451}
452
453pub(super) fn enroll_admin_password_reset(
454 client: &Client,
455 public_key: B64,
456) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
457 use bitwarden_crypto::AsymmetricPublicCryptoKey;
458
459 let public_key = AsymmetricPublicCryptoKey::from_der(&SpkiPublicKeyBytes::from(&public_key))?;
460 let key_store = client.internal.get_key_store();
461 let ctx = key_store.context();
462 #[allow(deprecated)]
464 let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
465
466 Ok(UnsignedSharedKey::encapsulate_key_unsigned(
467 key,
468 &public_key,
469 )?)
470}
471
472#[derive(Serialize, Deserialize, Debug, JsonSchema)]
474#[serde(rename_all = "camelCase", deny_unknown_fields)]
475#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
476#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
477pub struct DeriveKeyConnectorRequest {
478 pub user_key_encrypted: EncString,
480 pub password: String,
482 pub kdf: Kdf,
484 pub email: String,
486}
487
488#[allow(missing_docs)]
489#[bitwarden_error(flat)]
490#[derive(Debug, thiserror::Error)]
491pub enum DeriveKeyConnectorError {
492 #[error(transparent)]
493 WrongPassword(#[from] WrongPasswordError),
494 #[error(transparent)]
495 Crypto(#[from] bitwarden_crypto::CryptoError),
496}
497
498pub(super) fn derive_key_connector(
500 request: DeriveKeyConnectorRequest,
501) -> Result<B64, DeriveKeyConnectorError> {
502 let master_key = MasterKey::derive(&request.password, &request.email, &request.kdf)?;
503 master_key
504 .decrypt_user_key(request.user_key_encrypted)
505 .map_err(|_| WrongPasswordError)?;
506
507 Ok(master_key.to_base64())
508}
509
510#[derive(Serialize, Deserialize, Debug)]
512#[serde(rename_all = "camelCase", deny_unknown_fields)]
513#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
514#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
515pub struct MakeKeyPairResponse {
516 user_public_key: B64,
518 user_key_encrypted_private_key: EncString,
520}
521
522pub(super) fn make_key_pair(user_key: B64) -> Result<MakeKeyPairResponse, CryptoError> {
523 let user_key = UserKey::new(SymmetricCryptoKey::try_from(user_key)?);
524
525 let key_pair = user_key.make_key_pair()?;
526
527 Ok(MakeKeyPairResponse {
528 user_public_key: key_pair.public,
529 user_key_encrypted_private_key: key_pair.private,
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 VerifyAsymmetricKeysRequest {
539 user_key: B64,
541 user_public_key: B64,
543 user_key_encrypted_private_key: EncString,
545}
546
547#[derive(Serialize, Deserialize, Debug)]
549#[serde(rename_all = "camelCase", deny_unknown_fields)]
550#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
551#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
552pub struct VerifyAsymmetricKeysResponse {
553 private_key_decryptable: bool,
555 valid_private_key: bool,
557}
558
559pub(super) fn verify_asymmetric_keys(
560 request: VerifyAsymmetricKeysRequest,
561) -> Result<VerifyAsymmetricKeysResponse, CryptoError> {
562 #[derive(Debug, thiserror::Error)]
563 enum VerifyError {
564 #[error("Failed to decrypt private key: {0:?}")]
565 DecryptFailed(bitwarden_crypto::CryptoError),
566 #[error("Failed to parse decrypted private key: {0:?}")]
567 ParseFailed(bitwarden_crypto::CryptoError),
568 #[error("Failed to derive a public key: {0:?}")]
569 PublicFailed(bitwarden_crypto::CryptoError),
570 #[error("Derived public key doesn't match")]
571 KeyMismatch,
572 }
573
574 fn verify_inner(
575 user_key: &SymmetricCryptoKey,
576 request: &VerifyAsymmetricKeysRequest,
577 ) -> Result<(), VerifyError> {
578 let decrypted_private_key: Vec<u8> = request
579 .user_key_encrypted_private_key
580 .decrypt_with_key(user_key)
581 .map_err(VerifyError::DecryptFailed)?;
582
583 let decrypted_private_key = Pkcs8PrivateKeyBytes::from(decrypted_private_key);
584 let private_key = AsymmetricCryptoKey::from_der(&decrypted_private_key)
585 .map_err(VerifyError::ParseFailed)?;
586
587 let derived_public_key_vec = private_key
588 .to_public_key()
589 .to_der()
590 .map_err(VerifyError::PublicFailed)?;
591
592 let derived_public_key = B64::from(derived_public_key_vec);
593
594 if derived_public_key != request.user_public_key {
595 return Err(VerifyError::KeyMismatch);
596 }
597 Ok(())
598 }
599
600 let user_key = SymmetricCryptoKey::try_from(request.user_key.clone())?;
601
602 Ok(match verify_inner(&user_key, &request) {
603 Ok(_) => VerifyAsymmetricKeysResponse {
604 private_key_decryptable: true,
605 valid_private_key: true,
606 },
607 Err(e) => {
608 log::debug!("User asymmetric keys verification: {e}");
609
610 VerifyAsymmetricKeysResponse {
611 private_key_decryptable: !matches!(e, VerifyError::DecryptFailed(_)),
612 valid_private_key: false,
613 }
614 }
615 })
616}
617
618#[derive(Serialize, Deserialize, Debug, Clone)]
620#[serde(rename_all = "camelCase", deny_unknown_fields)]
621#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
622#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
623pub struct UserCryptoV2KeysResponse {
624 user_key: B64,
626
627 private_key: EncString,
629 public_key: B64,
631 signed_public_key: SignedPublicKey,
633
634 signing_key: EncString,
636 verifying_key: B64,
638
639 security_state: SignedSecurityState,
641 security_version: u64,
643}
644
645pub(crate) fn make_v2_keys_for_v1_user(
649 client: &Client,
650) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
651 let key_store = client.internal.get_key_store();
652 let mut ctx = key_store.context();
653
654 let temporary_user_key_id = SymmetricKeyId::Local("temporary_user_key");
655 let temporary_signing_key_id = SigningKeyId::Local("temporary_signing_key");
656 let private_key_id = AsymmetricKeyId::UserPrivateKey;
658
659 if client.internal.get_security_version() != 1 {
661 return Err(StatefulCryptoError::WrongAccountCryptoVersion {
662 expected: "1".to_string(),
663 got: 2,
664 });
665 }
666
667 if !ctx.has_asymmetric_key(AsymmetricKeyId::UserPrivateKey) {
671 return Err(StatefulCryptoError::CryptoError(CryptoError::MissingKeyId(
672 "UserPrivateKey".to_string(),
673 )));
674 }
675
676 #[allow(deprecated)]
677 let private_key = ctx.dangerous_get_asymmetric_key(private_key_id)?.clone();
678
679 let user_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
681 #[allow(deprecated)]
682 ctx.set_symmetric_key(temporary_user_key_id, user_key.clone())?;
683
684 let signing_key = SigningKey::make(SignatureAlgorithm::Ed25519);
686 #[allow(deprecated)]
687 ctx.set_signing_key(temporary_signing_key_id, signing_key.clone())?;
688
689 let signed_public_key = ctx.make_signed_public_key(private_key_id, temporary_signing_key_id)?;
691 let public_key = private_key.to_public_key();
692
693 let security_state = SecurityState::initialize_for_user(
695 client
696 .internal
697 .get_user_id()
698 .ok_or(StatefulCryptoError::MissingSecurityState)?,
699 );
700 let signed_security_state = security_state.sign(temporary_signing_key_id, &mut ctx)?;
701
702 Ok(UserCryptoV2KeysResponse {
703 user_key: user_key.to_base64(),
704
705 private_key: private_key.to_der()?.encrypt_with_key(&user_key)?,
706 public_key: public_key.to_der()?.into(),
707 signed_public_key,
708
709 signing_key: signing_key.to_cose().encrypt_with_key(&user_key)?,
710 verifying_key: signing_key.to_verifying_key().to_cose().into(),
711
712 security_state: signed_security_state,
713 security_version: security_state.version(),
714 })
715}
716
717pub(crate) fn get_v2_rotated_account_keys(
722 client: &Client,
723) -> Result<UserCryptoV2KeysResponse, StatefulCryptoError> {
724 let key_store = client.internal.get_key_store();
725 let mut ctx = key_store.context();
726
727 if client.internal.get_security_version() == 1 {
730 return Err(StatefulCryptoError::WrongAccountCryptoVersion {
731 expected: "2+".to_string(),
732 got: 1,
733 });
734 }
735
736 let security_state = client
737 .internal
738 .security_state
739 .read()
740 .expect("RwLock is not poisoned")
741 .to_owned()
742 .ok_or(StatefulCryptoError::MissingSecurityState)?;
745
746 let rotated_keys = dangerous_get_v2_rotated_account_keys(
747 AsymmetricKeyId::UserPrivateKey,
748 SigningKeyId::UserSigningKey,
749 &ctx,
750 )?;
751
752 Ok(UserCryptoV2KeysResponse {
753 user_key: rotated_keys.user_key.to_base64(),
754
755 private_key: rotated_keys.private_key,
756 public_key: rotated_keys.public_key.into(),
757 signed_public_key: rotated_keys.signed_public_key,
758
759 signing_key: rotated_keys.signing_key,
760 verifying_key: rotated_keys.verifying_key.into(),
761
762 security_state: security_state.sign(SigningKeyId::UserSigningKey, &mut ctx)?,
763 security_version: security_state.version(),
764 })
765}
766
767#[cfg(test)]
768mod tests {
769 use std::num::NonZeroU32;
770
771 use bitwarden_crypto::RsaKeyPair;
772
773 use super::*;
774 use crate::{client::internal::UserKeyState, Client};
775 const TEST_VECTOR_USER_KEY_V2_B64: &str = "pQEEAlACHUUoybNAuJoZzqNMxz2bAzoAARFvBIQDBAUGIFggAvGl4ifaUAomQdCdUPpXLHtypiQxHjZwRHeI83caZM4B";
776 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";
777 #[allow(unused)]
778 const TEST_VECTOR_PUBLIC_KEY_V2: &str = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz/+1jPJ1HqcaCdKrTPms8XJcvnmd9alI42U2XF/4GMNTM5KF1gI6snhR/23ZLatZRFMHoK8ZCMSpGNkjLadArz52ldceTvBOhQUiWylkZQ4NfNa3xIYJubXOmkeDyfNuyLxVZvcZOko9PdT+Qx2QxDrFi2XNo2I7aVFd19/COIEkex4mJ0eA3MHFpKCdxYbcTAsGID8+kVR9L84S1JptZoG8x+iB/D3/Q4y02UsQYpFTu0vbPY84YmW03ngJdxWzS8X4/UJI/jaEn5rO4xlU5QcL0l4IybP5LRpE9XEeUHATKVOG7eNfpe9zDfKV2qQoofQMH9VvkWO4psaWDjBSdwIDAQAB";
779 #[allow(unused)]
780 const TEST_VECTOR_SIGNED_PUBLIC_KEY_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8BoFkBTqNpYWxnb3JpdGhtAG1jb250ZW50Rm9ybWF0AGlwdWJsaWNLZXlZASYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP/7WM8nUepxoJ0qtM+azxcly+eZ31qUjjZTZcX/gYw1MzkoXWAjqyeFH/bdktq1lEUwegrxkIxKkY2SMtp0CvPnaV1x5O8E6FBSJbKWRlDg181rfEhgm5tc6aR4PJ827IvFVm9xk6Sj091P5DHZDEOsWLZc2jYjtpUV3X38I4gSR7HiYnR4DcwcWkoJ3FhtxMCwYgPz6RVH0vzhLUmm1mgbzH6IH8Pf9DjLTZSxBikVO7S9s9jzhiZbTeeAl3FbNLxfj9Qkj+NoSfms7jGVTlBwvSXgjJs/ktGkT1cR5QcBMpU4bt41+l73MN8pXapCih9Awf1W+RY7imxpYOMFJ3AgMBAAFYQMq/hT4wod2w8xyoM7D86ctuLNX4ZRo+jRHf2sZfaO7QsvonG/ZYuNKF5fq8wpxMRjfoMvnY2TTShbgzLrW8BA4=";
781 const TEST_VECTOR_SIGNING_KEY_V2: &str = "7.g1gcowE6AAERbwMYZQRQAh1FKMmzQLiaGc6jTMc9m6EFWBhYePc2qkCruHAPXgbzXsIP1WVk11ArbLNYUBpifToURlwHKs1je2BwZ1C/5thz4nyNbL0wDaYkRWI9ex1wvB7KhdzC7ltStEd5QttboTSCaXQROSZaGBPNO5+Bu3sTY8F5qK1pBUo6AHNN";
782 #[allow(unused)]
783 const TEST_VECTOR_VERIFYING_KEY_V2: &str =
784 "pgEBAlAmkP0QgfdMVbIujX55W/yNAycEgQIgBiFYIEM6JxBmjWQTruAm3s6BTaJy1q6BzQetMBacNeRJ0kxR";
785 const TEST_VECTOR_SECURITY_STATE_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8CoFgkomhlbnRpdHlJZFBHOOw2BI9OQoNq+Vl1xZZKZ3ZlcnNpb24CWEAlchbJR0vmRfShG8On7Q2gknjkw4Dd6MYBLiH4u+/CmfQdmjNZdf6kozgW/6NXyKVNu8dAsKsin+xxXkDyVZoG";
786
787 #[tokio::test]
788 async fn test_update_password() {
789 let client = Client::new(None);
790
791 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();
792
793 let kdf = Kdf::PBKDF2 {
794 iterations: 100_000.try_into().unwrap(),
795 };
796
797 initialize_user_crypto(
798 & client,
799 InitUserCryptoRequest {
800 user_id: Some(UserId::new_v4()),
801 kdf_params: kdf.clone(),
802 email: "[email protected]".into(),
803 private_key: priv_key.to_owned(),
804 signing_key: None,
805 security_state: None,
806 method: InitUserCryptoMethod::Password {
807 password: "asdfasdfasdf".into(),
808 user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
809 },
810 },
811 )
812 .await
813 .unwrap();
814
815 let new_password_response = update_password(&client, "123412341234".into()).unwrap();
816
817 let client2 = Client::new(None);
818
819 initialize_user_crypto(
820 &client2,
821 InitUserCryptoRequest {
822 user_id: Some(UserId::new_v4()),
823 kdf_params: kdf.clone(),
824 email: "[email protected]".into(),
825 private_key: priv_key.to_owned(),
826 signing_key: None,
827 security_state: None,
828 method: InitUserCryptoMethod::Password {
829 password: "123412341234".into(),
830 user_key: new_password_response.new_key,
831 },
832 },
833 )
834 .await
835 .unwrap();
836
837 let new_hash = client2
838 .kdf()
839 .hash_password(
840 "[email protected]".into(),
841 "123412341234".into(),
842 kdf.clone(),
843 bitwarden_crypto::HashPurpose::ServerAuthorization,
844 )
845 .await
846 .unwrap();
847
848 assert_eq!(new_hash, new_password_response.password_hash);
849
850 let client_key = {
851 let key_store = client.internal.get_key_store();
852 let ctx = key_store.context();
853 #[allow(deprecated)]
854 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
855 .unwrap()
856 .to_base64()
857 };
858
859 let client2_key = {
860 let key_store = client2.internal.get_key_store();
861 let ctx = key_store.context();
862 #[allow(deprecated)]
863 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
864 .unwrap()
865 .to_base64()
866 };
867
868 assert_eq!(client_key, client2_key);
869 }
870
871 #[tokio::test]
872 async fn test_initialize_user_crypto_pin() {
873 let client = Client::new(None);
874
875 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();
876
877 initialize_user_crypto(
878 & client,
879 InitUserCryptoRequest {
880 user_id: Some(UserId::new_v4()),
881 kdf_params: Kdf::PBKDF2 {
882 iterations: 100_000.try_into().unwrap(),
883 },
884 email: "[email protected]".into(),
885 private_key: priv_key.to_owned(),
886 signing_key: None,
887 security_state: None,
888 method: InitUserCryptoMethod::Password {
889 password: "asdfasdfasdf".into(),
890 user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
891 },
892 },
893 )
894 .await
895 .unwrap();
896
897 let pin_key = derive_pin_key(&client, "1234".into()).unwrap();
898
899 let client2 = Client::new(None);
901 initialize_user_crypto(
902 &client2,
903 InitUserCryptoRequest {
904 user_id: Some(UserId::new_v4()),
905 kdf_params: Kdf::PBKDF2 {
906 iterations: 100_000.try_into().unwrap(),
907 },
908 email: "[email protected]".into(),
909 private_key: priv_key.to_owned(),
910 signing_key: None,
911 security_state: None,
912 method: InitUserCryptoMethod::Pin {
913 pin: "1234".into(),
914 pin_protected_user_key: pin_key.pin_protected_user_key,
915 },
916 },
917 )
918 .await
919 .unwrap();
920
921 let client_key = {
922 let key_store = client.internal.get_key_store();
923 let ctx = key_store.context();
924 #[allow(deprecated)]
925 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
926 .unwrap()
927 .to_base64()
928 };
929
930 let client2_key = {
931 let key_store = client2.internal.get_key_store();
932 let ctx = key_store.context();
933 #[allow(deprecated)]
934 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
935 .unwrap()
936 .to_base64()
937 };
938
939 assert_eq!(client_key, client2_key);
940
941 let pin_protected_user_key = derive_pin_user_key(&client, pin_key.encrypted_pin).unwrap();
943
944 let client3 = Client::new(None);
945
946 initialize_user_crypto(
947 &client3,
948 InitUserCryptoRequest {
949 user_id: Some(UserId::new_v4()),
950 kdf_params: Kdf::PBKDF2 {
951 iterations: 100_000.try_into().unwrap(),
952 },
953 email: "[email protected]".into(),
954 private_key: priv_key.to_owned(),
955 signing_key: None,
956 security_state: None,
957 method: InitUserCryptoMethod::Pin {
958 pin: "1234".into(),
959 pin_protected_user_key,
960 },
961 },
962 )
963 .await
964 .unwrap();
965
966 let client_key = {
967 let key_store = client.internal.get_key_store();
968 let ctx = key_store.context();
969 #[allow(deprecated)]
970 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
971 .unwrap()
972 .to_base64()
973 };
974
975 let client3_key = {
976 let key_store = client3.internal.get_key_store();
977 let ctx = key_store.context();
978 #[allow(deprecated)]
979 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
980 .unwrap()
981 .to_base64()
982 };
983
984 assert_eq!(client_key, client3_key);
985 }
986
987 #[tokio::test]
988 async fn test_initialize_user_crypto_pin_envelope() {
989 let user_key = "5yKAZ4TSSEGje54MV5lc5ty6crkqUz4xvl+8Dm/piNLKf6OgRi2H0uzttNTXl9z6ILhkmuIXzGpAVc2YdorHgQ==";
990 let test_pin = "1234";
991
992 let client1 = Client::new(None);
993 initialize_user_crypto(
994 &client1,
995 InitUserCryptoRequest {
996 user_id: Some(UserId::new_v4()),
997 kdf_params: Kdf::PBKDF2 {
998 iterations: 100_000.try_into().unwrap(),
999 },
1000 email: "[email protected]".into(),
1001 private_key: make_key_pair(user_key.try_into().unwrap())
1002 .unwrap()
1003 .user_key_encrypted_private_key,
1004 signing_key: None,
1005 security_state: None,
1006 method: InitUserCryptoMethod::DecryptedKey {
1007 decrypted_user_key: user_key.to_string(),
1008 },
1009 },
1010 )
1011 .await
1012 .unwrap();
1013
1014 let enroll_response = client1.crypto().enroll_pin(test_pin.to_string()).unwrap();
1015
1016 let client2 = Client::new(None);
1017 initialize_user_crypto(
1018 &client2,
1019 InitUserCryptoRequest {
1020 user_id: Some(UserId::new_v4()),
1021 kdf_params: Kdf::PBKDF2 {
1024 iterations: 600_000.try_into().unwrap(),
1025 },
1026 email: "[email protected]".into(),
1027 private_key: make_key_pair(user_key.try_into().unwrap())
1028 .unwrap()
1029 .user_key_encrypted_private_key,
1030 signing_key: None,
1031 security_state: None,
1032 method: InitUserCryptoMethod::PinEnvelope {
1033 pin: test_pin.to_string(),
1034 pin_protected_user_key_envelope: enroll_response
1035 .pin_protected_user_key_envelope,
1036 },
1037 },
1038 )
1039 .await
1040 .unwrap();
1041 }
1042
1043 #[test]
1044 fn test_enroll_admin_password_reset() {
1045 let client = Client::new(None);
1046
1047 let master_key = MasterKey::derive(
1048 "asdfasdfasdf",
1049 "[email protected]",
1050 &Kdf::PBKDF2 {
1051 iterations: NonZeroU32::new(600_000).unwrap(),
1052 },
1053 )
1054 .unwrap();
1055
1056 let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap();
1057 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();
1058 client
1059 .internal
1060 .initialize_user_crypto_master_key(
1061 master_key,
1062 user_key,
1063 UserKeyState {
1064 private_key,
1065 signing_key: None,
1066 security_state: None,
1067 },
1068 )
1069 .unwrap();
1070
1071 let public_key: B64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsy7RFHcX3C8Q4/OMmhhbFReYWfB45W9PDTEA8tUZwZmtOiN2RErIS2M1c+K/4HoDJ/TjpbX1f2MZcr4nWvKFuqnZXyewFc+jmvKVewYi+NAu2++vqKq2kKcmMNhwoQDQdQIVy/Uqlp4Cpi2cIwO6ogq5nHNJGR3jm+CpyrafYlbz1bPvL3hbyoGDuG2tgADhyhXUdFuef2oF3wMvn1lAJAvJnPYpMiXUFmj1ejmbwtlxZDrHgUJvUcp7nYdwUKaFoi+sOttHn3u7eZPtNvxMjhSS/X/1xBIzP/mKNLdywH5LoRxniokUk+fV3PYUxJsiU3lV0Trc/tH46jqd8ZGjmwIDAQAB".parse().unwrap();
1072
1073 let encrypted = enroll_admin_password_reset(&client, public_key).unwrap();
1074
1075 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();
1076
1077 let private_key = Pkcs8PrivateKeyBytes::from(private_key.as_bytes());
1078 let private_key = AsymmetricCryptoKey::from_der(&private_key).unwrap();
1079 let decrypted: SymmetricCryptoKey =
1080 encrypted.decapsulate_key_unsigned(&private_key).unwrap();
1081
1082 let key_store = client.internal.get_key_store();
1083 let ctx = key_store.context();
1084 #[allow(deprecated)]
1085 let expected = ctx
1086 .dangerous_get_symmetric_key(SymmetricKeyId::User)
1087 .unwrap();
1088
1089 assert_eq!(decrypted, *expected);
1090 }
1091
1092 #[test]
1093 fn test_derive_key_connector() {
1094 let request = DeriveKeyConnectorRequest {
1095 password: "asdfasdfasdf".to_string(),
1096 email: "[email protected]".to_string(),
1097 kdf: Kdf::PBKDF2 {
1098 iterations: NonZeroU32::new(600_000).unwrap(),
1099 },
1100 user_key_encrypted: "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(),
1101 };
1102
1103 let result = derive_key_connector(request).unwrap();
1104
1105 assert_eq!(
1106 result.to_string(),
1107 "ySXq1RVLKEaV1eoQE/ui9aFKIvXTl9PAXwp1MljfF50="
1108 );
1109 }
1110
1111 fn setup_asymmetric_keys_test() -> (UserKey, RsaKeyPair) {
1112 let master_key = MasterKey::derive(
1113 "asdfasdfasdf",
1114 "[email protected]",
1115 &Kdf::PBKDF2 {
1116 iterations: NonZeroU32::new(600_000).unwrap(),
1117 },
1118 )
1119 .unwrap();
1120 let user_key = (master_key.make_user_key().unwrap()).0;
1121 let key_pair = user_key.make_key_pair().unwrap();
1122
1123 (user_key, key_pair)
1124 }
1125
1126 #[test]
1127 fn test_make_key_pair() {
1128 let (user_key, _) = setup_asymmetric_keys_test();
1129
1130 let response = make_key_pair(user_key.0.to_base64()).unwrap();
1131
1132 assert!(!response.user_public_key.to_string().is_empty());
1133 let encrypted_private_key = response.user_key_encrypted_private_key;
1134 let private_key: Vec<u8> = encrypted_private_key.decrypt_with_key(&user_key.0).unwrap();
1135 assert!(!private_key.is_empty());
1136 }
1137
1138 #[test]
1139 fn test_verify_asymmetric_keys_success() {
1140 let (user_key, key_pair) = setup_asymmetric_keys_test();
1141
1142 let request = VerifyAsymmetricKeysRequest {
1143 user_key: user_key.0.to_base64(),
1144 user_public_key: key_pair.public,
1145 user_key_encrypted_private_key: key_pair.private,
1146 };
1147 let response = verify_asymmetric_keys(request).unwrap();
1148
1149 assert!(response.private_key_decryptable);
1150 assert!(response.valid_private_key);
1151 }
1152
1153 #[test]
1154 fn test_verify_asymmetric_keys_decrypt_failed() {
1155 let (user_key, key_pair) = setup_asymmetric_keys_test();
1156 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();
1157
1158 let request = VerifyAsymmetricKeysRequest {
1159 user_key: user_key.0.to_base64(),
1160 user_public_key: key_pair.public,
1161 user_key_encrypted_private_key: undecryptable_private_key,
1162 };
1163 let response = verify_asymmetric_keys(request).unwrap();
1164
1165 assert!(!response.private_key_decryptable);
1166 assert!(!response.valid_private_key);
1167 }
1168
1169 #[test]
1170 fn test_verify_asymmetric_keys_parse_failed() {
1171 let (user_key, key_pair) = setup_asymmetric_keys_test();
1172
1173 let invalid_private_key = "bad_key".to_string().encrypt_with_key(&user_key.0).unwrap();
1174
1175 let request = VerifyAsymmetricKeysRequest {
1176 user_key: user_key.0.to_base64(),
1177 user_public_key: key_pair.public,
1178 user_key_encrypted_private_key: invalid_private_key,
1179 };
1180 let response = verify_asymmetric_keys(request).unwrap();
1181
1182 assert!(response.private_key_decryptable);
1183 assert!(!response.valid_private_key);
1184 }
1185
1186 #[test]
1187 fn test_verify_asymmetric_keys_key_mismatch() {
1188 let (user_key, key_pair) = setup_asymmetric_keys_test();
1189 let new_key_pair = user_key.make_key_pair().unwrap();
1190
1191 let request = VerifyAsymmetricKeysRequest {
1192 user_key: user_key.0.to_base64(),
1193 user_public_key: key_pair.public,
1194 user_key_encrypted_private_key: new_key_pair.private,
1195 };
1196 let response = verify_asymmetric_keys(request).unwrap();
1197
1198 assert!(response.private_key_decryptable);
1199 assert!(!response.valid_private_key);
1200 }
1201
1202 #[tokio::test]
1203 async fn test_make_v2_keys_for_v1_user() {
1204 let client = Client::new(None);
1205
1206 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();
1207 let encrypted_userkey: EncString = "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap();
1208
1209 initialize_user_crypto(
1210 &client,
1211 InitUserCryptoRequest {
1212 user_id: Some(UserId::new_v4()),
1213 kdf_params: Kdf::PBKDF2 {
1214 iterations: 100_000.try_into().unwrap(),
1215 },
1216 email: "[email protected]".into(),
1217 private_key: priv_key,
1218 signing_key: None,
1219 security_state: None,
1220 method: InitUserCryptoMethod::Password {
1221 password: "asdfasdfasdf".into(),
1222 user_key: encrypted_userkey.clone(),
1223 },
1224 },
1225 )
1226 .await
1227 .unwrap();
1228
1229 let master_key = MasterKey::derive(
1230 "asdfasdfasdf",
1231 "[email protected]",
1232 &Kdf::PBKDF2 {
1233 iterations: NonZeroU32::new(100_000).unwrap(),
1234 },
1235 )
1236 .unwrap();
1237 let enrollment_response = make_v2_keys_for_v1_user(&client).unwrap();
1238 let encrypted_userkey_v2 = master_key
1239 .encrypt_user_key(
1240 &SymmetricCryptoKey::try_from(enrollment_response.clone().user_key).unwrap(),
1241 )
1242 .unwrap();
1243
1244 let client2 = Client::new(None);
1245 initialize_user_crypto(
1246 &client2,
1247 InitUserCryptoRequest {
1248 user_id: Some(UserId::new_v4()),
1249 kdf_params: Kdf::PBKDF2 {
1250 iterations: 100_000.try_into().unwrap(),
1251 },
1252 email: "[email protected]".into(),
1253 private_key: enrollment_response.private_key,
1254 signing_key: Some(enrollment_response.signing_key),
1255 security_state: Some(enrollment_response.security_state),
1256 method: InitUserCryptoMethod::Password {
1257 password: "asdfasdfasdf".into(),
1258 user_key: encrypted_userkey_v2,
1259 },
1260 },
1261 )
1262 .await
1263 .unwrap();
1264 }
1265
1266 #[tokio::test]
1267 async fn test_make_v2_keys_for_v1_user_with_v2_user_fails() {
1268 let client = Client::new(None);
1269 #[allow(deprecated)]
1270 client
1271 .internal
1272 .get_key_store()
1273 .context_mut()
1274 .set_symmetric_key(
1275 SymmetricKeyId::User,
1276 SymmetricCryptoKey::make_aes256_cbc_hmac_key(),
1277 )
1278 .unwrap();
1279 initialize_user_crypto(
1280 &client,
1281 InitUserCryptoRequest {
1282 user_id: Some(UserId::new_v4()),
1283 kdf_params: Kdf::PBKDF2 {
1284 iterations: 100_000.try_into().unwrap(),
1285 },
1286 email: "[email protected]".into(),
1287 private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
1288 signing_key: Some(TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap()),
1289 security_state: Some(TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap()),
1290 method: InitUserCryptoMethod::DecryptedKey {
1291 decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
1292 },
1293 },
1294 )
1295 .await
1296 .unwrap();
1297
1298 let result = make_v2_keys_for_v1_user(&client);
1299 assert!(matches!(
1300 result,
1301 Err(StatefulCryptoError::WrongAccountCryptoVersion {
1302 expected: _,
1303 got: _
1304 })
1305 ));
1306 }
1307
1308 #[test]
1309 fn test_get_v2_rotated_account_keys_non_v2_user() {
1310 let client = Client::new(None);
1311 #[allow(deprecated)]
1312 client
1313 .internal
1314 .get_key_store()
1315 .context_mut()
1316 .set_symmetric_key(
1317 SymmetricKeyId::User,
1318 SymmetricCryptoKey::make_aes256_cbc_hmac_key(),
1319 )
1320 .unwrap();
1321
1322 let result = get_v2_rotated_account_keys(&client);
1323 assert!(matches!(
1324 result,
1325 Err(StatefulCryptoError::WrongAccountCryptoVersion {
1326 expected: _,
1327 got: _
1328 })
1329 ));
1330 }
1331
1332 #[tokio::test]
1333 async fn test_get_v2_rotated_account_keys() {
1334 let client = Client::new(None);
1335 #[allow(deprecated)]
1336 client
1337 .internal
1338 .get_key_store()
1339 .context_mut()
1340 .set_symmetric_key(
1341 SymmetricKeyId::User,
1342 SymmetricCryptoKey::make_aes256_cbc_hmac_key(),
1343 )
1344 .unwrap();
1345 initialize_user_crypto(
1346 &client,
1347 InitUserCryptoRequest {
1348 user_id: Some(UserId::new_v4()),
1349 kdf_params: Kdf::PBKDF2 {
1350 iterations: 100_000.try_into().unwrap(),
1351 },
1352 email: "[email protected]".into(),
1353 private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
1354 signing_key: Some(TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap()),
1355 security_state: Some(TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap()),
1356 method: InitUserCryptoMethod::DecryptedKey {
1357 decrypted_user_key: TEST_VECTOR_USER_KEY_V2_B64.to_string(),
1358 },
1359 },
1360 )
1361 .await
1362 .unwrap();
1363
1364 assert!(get_v2_rotated_account_keys(&client).is_ok());
1365 }
1366}