1use std::collections::HashMap;
8
9use base64::{engine::general_purpose::STANDARD, Engine};
10use bitwarden_crypto::{
11 AsymmetricCryptoKey, CoseSerializable, CryptoError, EncString, Encryptable, Kdf,
12 KeyDecryptable, KeyEncryptable, MasterKey, SignatureAlgorithm, SignedPublicKey, SigningKey,
13 SymmetricCryptoKey, UnsignedSharedKey, UserKey,
14};
15use bitwarden_error::bitwarden_error;
16use schemars::JsonSchema;
17use serde::{Deserialize, Serialize};
18#[cfg(feature = "wasm")]
19use {tsify_next::Tsify, wasm_bindgen::prelude::*};
20
21use crate::{
22 client::{encryption_settings::EncryptionSettingsError, LoginMethod, UserLoginMethod},
23 key_management::{AsymmetricKeyId, SigningKeyId, SymmetricKeyId},
24 Client, NotAuthenticatedError, VaultLockedError, WrongPasswordError,
25};
26
27#[allow(missing_docs)]
29#[bitwarden_error(flat)]
30#[derive(Debug, thiserror::Error)]
31pub enum CryptoClientError {
32 #[error(transparent)]
33 NotAuthenticated(#[from] NotAuthenticatedError),
34 #[error(transparent)]
35 VaultLocked(#[from] VaultLockedError),
36 #[error(transparent)]
37 Crypto(#[from] bitwarden_crypto::CryptoError),
38}
39
40#[derive(Serialize, Deserialize, Debug)]
42#[serde(rename_all = "camelCase", deny_unknown_fields)]
43#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
44#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
45pub struct InitUserCryptoRequest {
46 pub user_id: Option<uuid::Uuid>,
48 pub kdf_params: Kdf,
50 pub email: String,
52 pub private_key: EncString,
54 pub signing_key: Option<EncString>,
56 pub method: InitUserCryptoMethod,
58}
59
60#[derive(Serialize, Deserialize, Debug)]
62#[serde(rename_all = "camelCase", deny_unknown_fields)]
63#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
64#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
65pub enum InitUserCryptoMethod {
66 Password {
68 password: String,
70 user_key: EncString,
72 },
73 DecryptedKey {
75 decrypted_user_key: String,
77 },
78 Pin {
80 pin: String,
82 pin_protected_user_key: EncString,
85 },
86 AuthRequest {
88 request_private_key: String,
90 method: AuthRequestMethod,
92 },
93 DeviceKey {
95 device_key: String,
97 protected_device_private_key: EncString,
99 device_protected_user_key: UnsignedSharedKey,
101 },
102 KeyConnector {
104 master_key: String,
106 user_key: EncString,
108 },
109}
110
111#[derive(Serialize, Deserialize, Debug)]
113#[serde(rename_all = "camelCase", deny_unknown_fields)]
114#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
115#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
116pub enum AuthRequestMethod {
117 UserKey {
119 protected_user_key: UnsignedSharedKey,
121 },
122 MasterKey {
124 protected_master_key: UnsignedSharedKey,
126 auth_request_key: EncString,
128 },
129}
130
131pub(super) async fn initialize_user_crypto(
133 client: &Client,
134 req: InitUserCryptoRequest,
135) -> Result<(), EncryptionSettingsError> {
136 use bitwarden_crypto::{DeviceKey, PinKey};
137
138 use crate::auth::{auth_request_decrypt_master_key, auth_request_decrypt_user_key};
139
140 if let Some(user_id) = req.user_id {
141 client.internal.init_user_id(user_id)?;
142 }
143
144 match req.method {
145 InitUserCryptoMethod::Password { password, user_key } => {
146 let master_key = MasterKey::derive(&password, &req.email, &req.kdf_params)?;
147 client.internal.initialize_user_crypto_master_key(
148 master_key,
149 user_key,
150 req.private_key,
151 req.signing_key,
152 )?;
153 }
154 InitUserCryptoMethod::DecryptedKey { decrypted_user_key } => {
155 let user_key = SymmetricCryptoKey::try_from(decrypted_user_key)?;
156 client.internal.initialize_user_crypto_decrypted_key(
157 user_key,
158 req.private_key,
159 req.signing_key,
160 )?;
161 }
162 InitUserCryptoMethod::Pin {
163 pin,
164 pin_protected_user_key,
165 } => {
166 let pin_key = PinKey::derive(pin.as_bytes(), req.email.as_bytes(), &req.kdf_params)?;
167 client.internal.initialize_user_crypto_pin(
168 pin_key,
169 pin_protected_user_key,
170 req.private_key,
171 req.signing_key,
172 )?;
173 }
174 InitUserCryptoMethod::AuthRequest {
175 request_private_key,
176 method,
177 } => {
178 let user_key = match method {
179 AuthRequestMethod::UserKey { protected_user_key } => {
180 auth_request_decrypt_user_key(request_private_key, protected_user_key)?
181 }
182 AuthRequestMethod::MasterKey {
183 protected_master_key,
184 auth_request_key,
185 } => auth_request_decrypt_master_key(
186 request_private_key,
187 protected_master_key,
188 auth_request_key,
189 )?,
190 };
191 client.internal.initialize_user_crypto_decrypted_key(
192 user_key,
193 req.private_key,
194 req.signing_key,
195 )?;
196 }
197 InitUserCryptoMethod::DeviceKey {
198 device_key,
199 protected_device_private_key,
200 device_protected_user_key,
201 } => {
202 let device_key = DeviceKey::try_from(device_key)?;
203 let user_key = device_key
204 .decrypt_user_key(protected_device_private_key, device_protected_user_key)?;
205
206 client.internal.initialize_user_crypto_decrypted_key(
207 user_key,
208 req.private_key,
209 req.signing_key,
210 )?;
211 }
212 InitUserCryptoMethod::KeyConnector {
213 master_key,
214 user_key,
215 } => {
216 let mut master_key_bytes = STANDARD
217 .decode(master_key)
218 .map_err(|_| CryptoError::InvalidKey)?;
219 let master_key = MasterKey::try_from(master_key_bytes.as_mut_slice())?;
220
221 client.internal.initialize_user_crypto_master_key(
222 master_key,
223 user_key,
224 req.private_key,
225 req.signing_key,
226 )?;
227 }
228 }
229
230 client
231 .internal
232 .set_login_method(crate::client::LoginMethod::User(
233 crate::client::UserLoginMethod::Username {
234 client_id: "".to_string(),
235 email: req.email,
236 kdf: req.kdf_params,
237 },
238 ));
239
240 Ok(())
241}
242
243#[derive(Serialize, Deserialize, Debug)]
245#[serde(rename_all = "camelCase", deny_unknown_fields)]
246#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
247#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
248pub struct InitOrgCryptoRequest {
249 pub organization_keys: HashMap<uuid::Uuid, UnsignedSharedKey>,
251}
252
253pub(super) async fn initialize_org_crypto(
255 client: &Client,
256 req: InitOrgCryptoRequest,
257) -> Result<(), EncryptionSettingsError> {
258 let organization_keys = req.organization_keys.into_iter().collect();
259 client.internal.initialize_org_crypto(organization_keys)?;
260 Ok(())
261}
262
263pub(super) async fn get_user_encryption_key(client: &Client) -> Result<String, CryptoClientError> {
264 let key_store = client.internal.get_key_store();
265 let ctx = key_store.context();
266 #[allow(deprecated)]
268 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
269
270 Ok(user_key.to_base64())
271}
272
273#[derive(Serialize, Deserialize, Debug)]
275#[serde(rename_all = "camelCase", deny_unknown_fields)]
276#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
277#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
278pub struct UpdatePasswordResponse {
279 password_hash: String,
281 new_key: EncString,
283}
284
285pub(super) fn update_password(
286 client: &Client,
287 new_password: String,
288) -> Result<UpdatePasswordResponse, CryptoClientError> {
289 let key_store = client.internal.get_key_store();
290 let ctx = key_store.context();
291 #[allow(deprecated)]
293 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
294
295 let login_method = client
296 .internal
297 .get_login_method()
298 .ok_or(NotAuthenticatedError)?;
299
300 let new_master_key = match login_method.as_ref() {
302 LoginMethod::User(
303 UserLoginMethod::Username { email, kdf, .. }
304 | UserLoginMethod::ApiKey { email, kdf, .. },
305 ) => MasterKey::derive(&new_password, email, kdf)?,
306 #[cfg(feature = "secrets")]
307 LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?,
308 };
309
310 let new_key = new_master_key.encrypt_user_key(user_key)?;
311
312 let password_hash = new_master_key.derive_master_key_hash(
313 new_password.as_bytes(),
314 bitwarden_crypto::HashPurpose::ServerAuthorization,
315 )?;
316
317 Ok(UpdatePasswordResponse {
318 password_hash,
319 new_key,
320 })
321}
322
323#[derive(Serialize, Deserialize, Debug)]
325#[serde(rename_all = "camelCase", deny_unknown_fields)]
326#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
327#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
328pub struct DerivePinKeyResponse {
329 pin_protected_user_key: EncString,
331 encrypted_pin: EncString,
333}
334
335pub(super) fn derive_pin_key(
336 client: &Client,
337 pin: String,
338) -> Result<DerivePinKeyResponse, CryptoClientError> {
339 let key_store = client.internal.get_key_store();
340 let ctx = key_store.context();
341 #[allow(deprecated)]
343 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
344
345 let login_method = client
346 .internal
347 .get_login_method()
348 .ok_or(NotAuthenticatedError)?;
349
350 let pin_protected_user_key = derive_pin_protected_user_key(&pin, &login_method, user_key)?;
351
352 Ok(DerivePinKeyResponse {
353 pin_protected_user_key,
354 encrypted_pin: pin.encrypt_with_key(user_key)?,
355 })
356}
357
358pub(super) fn derive_pin_user_key(
359 client: &Client,
360 encrypted_pin: EncString,
361) -> Result<EncString, CryptoClientError> {
362 let key_store = client.internal.get_key_store();
363 let ctx = key_store.context();
364 #[allow(deprecated)]
366 let user_key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
367
368 let pin: String = encrypted_pin.decrypt_with_key(user_key)?;
369 let login_method = client
370 .internal
371 .get_login_method()
372 .ok_or(NotAuthenticatedError)?;
373
374 derive_pin_protected_user_key(&pin, &login_method, user_key)
375}
376
377fn derive_pin_protected_user_key(
378 pin: &str,
379 login_method: &LoginMethod,
380 user_key: &SymmetricCryptoKey,
381) -> Result<EncString, CryptoClientError> {
382 use bitwarden_crypto::PinKey;
383
384 let derived_key = match login_method {
385 LoginMethod::User(
386 UserLoginMethod::Username { email, kdf, .. }
387 | UserLoginMethod::ApiKey { email, kdf, .. },
388 ) => PinKey::derive(pin.as_bytes(), email.as_bytes(), kdf)?,
389 #[cfg(feature = "secrets")]
390 LoginMethod::ServiceAccount(_) => return Err(NotAuthenticatedError)?,
391 };
392
393 Ok(derived_key.encrypt_user_key(user_key)?)
394}
395
396#[allow(missing_docs)]
397#[bitwarden_error(flat)]
398#[derive(Debug, thiserror::Error)]
399pub enum EnrollAdminPasswordResetError {
400 #[error(transparent)]
401 VaultLocked(#[from] VaultLockedError),
402 #[error(transparent)]
403 Crypto(#[from] bitwarden_crypto::CryptoError),
404 #[error(transparent)]
405 InvalidBase64(#[from] base64::DecodeError),
406}
407
408pub(super) fn enroll_admin_password_reset(
409 client: &Client,
410 public_key: String,
411) -> Result<UnsignedSharedKey, EnrollAdminPasswordResetError> {
412 use base64::{engine::general_purpose::STANDARD, Engine};
413 use bitwarden_crypto::AsymmetricPublicCryptoKey;
414
415 let public_key = AsymmetricPublicCryptoKey::from_der(&STANDARD.decode(public_key)?)?;
416 let key_store = client.internal.get_key_store();
417 let ctx = key_store.context();
418 #[allow(deprecated)]
420 let key = ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)?;
421
422 Ok(UnsignedSharedKey::encapsulate_key_unsigned(
423 key,
424 &public_key,
425 )?)
426}
427
428#[derive(Serialize, Deserialize, Debug, JsonSchema)]
430#[serde(rename_all = "camelCase", deny_unknown_fields)]
431#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
432#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
433pub struct DeriveKeyConnectorRequest {
434 pub user_key_encrypted: EncString,
436 pub password: String,
438 pub kdf: Kdf,
440 pub email: String,
442}
443
444#[allow(missing_docs)]
445#[bitwarden_error(flat)]
446#[derive(Debug, thiserror::Error)]
447pub enum DeriveKeyConnectorError {
448 #[error(transparent)]
449 WrongPassword(#[from] WrongPasswordError),
450 #[error(transparent)]
451 Crypto(#[from] bitwarden_crypto::CryptoError),
452}
453
454pub(super) fn derive_key_connector(
456 request: DeriveKeyConnectorRequest,
457) -> Result<String, DeriveKeyConnectorError> {
458 let master_key = MasterKey::derive(&request.password, &request.email, &request.kdf)?;
459 master_key
460 .decrypt_user_key(request.user_key_encrypted)
461 .map_err(|_| WrongPasswordError)?;
462
463 Ok(master_key.to_base64())
464}
465
466#[derive(Serialize, Deserialize, Debug)]
468#[serde(rename_all = "camelCase", deny_unknown_fields)]
469#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
470#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
471pub struct MakeKeyPairResponse {
472 user_public_key: String,
474 user_key_encrypted_private_key: EncString,
476}
477
478pub(super) fn make_key_pair(user_key: String) -> Result<MakeKeyPairResponse, CryptoError> {
479 let user_key = UserKey::new(SymmetricCryptoKey::try_from(user_key)?);
480
481 let key_pair = user_key.make_key_pair()?;
482
483 Ok(MakeKeyPairResponse {
484 user_public_key: key_pair.public,
485 user_key_encrypted_private_key: key_pair.private,
486 })
487}
488
489#[derive(Serialize, Deserialize, Debug)]
491#[serde(rename_all = "camelCase", deny_unknown_fields)]
492#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
493#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
494pub struct VerifyAsymmetricKeysRequest {
495 user_key: String,
497 user_public_key: String,
499 user_key_encrypted_private_key: EncString,
501}
502
503#[derive(Serialize, Deserialize, Debug)]
505#[serde(rename_all = "camelCase", deny_unknown_fields)]
506#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
507#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
508pub struct VerifyAsymmetricKeysResponse {
509 private_key_decryptable: bool,
511 valid_private_key: bool,
513}
514
515pub(super) fn verify_asymmetric_keys(
516 request: VerifyAsymmetricKeysRequest,
517) -> Result<VerifyAsymmetricKeysResponse, CryptoError> {
518 #[derive(Debug, thiserror::Error)]
519 enum VerifyError {
520 #[error("Failed to decrypt private key: {0:?}")]
521 DecryptFailed(bitwarden_crypto::CryptoError),
522 #[error("Failed to parse decrypted private key: {0:?}")]
523 ParseFailed(bitwarden_crypto::CryptoError),
524 #[error("Failed to derive a public key: {0:?}")]
525 PublicFailed(bitwarden_crypto::CryptoError),
526 #[error("Derived public key doesn't match")]
527 KeyMismatch,
528 }
529
530 fn verify_inner(
531 user_key: &SymmetricCryptoKey,
532 request: &VerifyAsymmetricKeysRequest,
533 ) -> Result<(), VerifyError> {
534 let decrypted_private_key: Vec<u8> = request
535 .user_key_encrypted_private_key
536 .decrypt_with_key(user_key)
537 .map_err(VerifyError::DecryptFailed)?;
538
539 let private_key = AsymmetricCryptoKey::from_der(&decrypted_private_key)
540 .map_err(VerifyError::ParseFailed)?;
541
542 let derived_public_key_vec = private_key
543 .to_public_key()
544 .to_der()
545 .map_err(VerifyError::PublicFailed)?;
546
547 let derived_public_key = STANDARD.encode(derived_public_key_vec);
548
549 if derived_public_key != request.user_public_key {
550 return Err(VerifyError::KeyMismatch);
551 }
552 Ok(())
553 }
554
555 let user_key = SymmetricCryptoKey::try_from(request.user_key.clone())?;
556
557 Ok(match verify_inner(&user_key, &request) {
558 Ok(_) => VerifyAsymmetricKeysResponse {
559 private_key_decryptable: true,
560 valid_private_key: true,
561 },
562 Err(e) => {
563 log::debug!("User asymmetric keys verification: {}", e);
564
565 VerifyAsymmetricKeysResponse {
566 private_key_decryptable: !matches!(e, VerifyError::DecryptFailed(_)),
567 valid_private_key: false,
568 }
569 }
570 })
571}
572
573#[derive(Serialize, Deserialize, Debug, JsonSchema)]
575#[serde(rename_all = "camelCase", deny_unknown_fields)]
576#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
577#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
578pub struct MakeUserSigningKeysResponse {
579 verifying_key: String,
581 signing_key: EncString,
583 signed_public_key: SignedPublicKey,
585}
586
587pub fn make_user_signing_keys_for_enrollment(
591 client: &Client,
592) -> Result<MakeUserSigningKeysResponse, CryptoError> {
593 let key_store = client.internal.get_key_store();
594 let mut ctx = key_store.context();
595
596 let signature_keypair = SigningKey::make(SignatureAlgorithm::Ed25519);
598 let temporary_signature_keypair_id = SigningKeyId::Local("temporary_key_for_rotation");
599 #[allow(deprecated)]
600 ctx.set_signing_key(temporary_signature_keypair_id, signature_keypair.clone())?;
601 let signed_public_key = ctx.make_signed_public_key(
602 AsymmetricKeyId::UserPrivateKey,
603 temporary_signature_keypair_id,
604 )?;
605
606 Ok(MakeUserSigningKeysResponse {
607 verifying_key: STANDARD.encode(signature_keypair.to_verifying_key().to_cose()),
608 signing_key: signature_keypair
611 .to_cose()
612 .encrypt(&mut ctx, SymmetricKeyId::User)?,
613 signed_public_key,
614 })
615}
616
617#[cfg(test)]
618mod tests {
619 use std::num::NonZeroU32;
620
621 use bitwarden_crypto::RsaKeyPair;
622
623 use super::*;
624 use crate::Client;
625
626 #[tokio::test]
627 async fn test_update_password() {
628 let client = Client::new(None);
629
630 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();
631
632 let kdf = Kdf::PBKDF2 {
633 iterations: 100_000.try_into().unwrap(),
634 };
635
636 initialize_user_crypto(
637 & client,
638 InitUserCryptoRequest {
639 user_id: Some(uuid::Uuid::new_v4()),
640 kdf_params: kdf.clone(),
641 email: "[email protected]".into(),
642 private_key: priv_key.to_owned(),
643 signing_key: None,
644 method: InitUserCryptoMethod::Password {
645 password: "asdfasdfasdf".into(),
646 user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
647 },
648 },
649 )
650 .await
651 .unwrap();
652
653 let new_password_response = update_password(&client, "123412341234".into()).unwrap();
654
655 let client2 = Client::new(None);
656
657 initialize_user_crypto(
658 &client2,
659 InitUserCryptoRequest {
660 user_id: Some(uuid::Uuid::new_v4()),
661 kdf_params: kdf.clone(),
662 email: "[email protected]".into(),
663 private_key: priv_key.to_owned(),
664 signing_key: None,
665 method: InitUserCryptoMethod::Password {
666 password: "123412341234".into(),
667 user_key: new_password_response.new_key,
668 },
669 },
670 )
671 .await
672 .unwrap();
673
674 let new_hash = client2
675 .kdf()
676 .hash_password(
677 "[email protected]".into(),
678 "123412341234".into(),
679 kdf.clone(),
680 bitwarden_crypto::HashPurpose::ServerAuthorization,
681 )
682 .await
683 .unwrap();
684
685 assert_eq!(new_hash, new_password_response.password_hash);
686
687 let client_key = {
688 let key_store = client.internal.get_key_store();
689 let ctx = key_store.context();
690 #[allow(deprecated)]
691 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
692 .unwrap()
693 .to_base64()
694 };
695
696 let client2_key = {
697 let key_store = client2.internal.get_key_store();
698 let ctx = key_store.context();
699 #[allow(deprecated)]
700 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
701 .unwrap()
702 .to_base64()
703 };
704
705 assert_eq!(client_key, client2_key);
706 }
707
708 #[tokio::test]
709 async fn test_initialize_user_crypto_pin() {
710 let client = Client::new(None);
711
712 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();
713
714 initialize_user_crypto(
715 & client,
716 InitUserCryptoRequest {
717 user_id: Some(uuid::Uuid::new_v4()),
718 kdf_params: Kdf::PBKDF2 {
719 iterations: 100_000.try_into().unwrap(),
720 },
721 email: "[email protected]".into(),
722 private_key: priv_key.to_owned(),
723 signing_key: None,
724 method: InitUserCryptoMethod::Password {
725 password: "asdfasdfasdf".into(),
726 user_key: "2.u2HDQ/nH2J7f5tYHctZx6Q==|NnUKODz8TPycWJA5svexe1wJIz2VexvLbZh2RDfhj5VI3wP8ZkR0Vicvdv7oJRyLI1GyaZDBCf9CTBunRTYUk39DbZl42Rb+Xmzds02EQhc=|rwuo5wgqvTJf3rgwOUfabUyzqhguMYb3sGBjOYqjevc=".parse().unwrap(),
727 },
728 },
729 )
730 .await
731 .unwrap();
732
733 let pin_key = derive_pin_key(&client, "1234".into()).unwrap();
734
735 let client2 = Client::new(None);
737 initialize_user_crypto(
738 &client2,
739 InitUserCryptoRequest {
740 user_id: Some(uuid::Uuid::new_v4()),
741 kdf_params: Kdf::PBKDF2 {
742 iterations: 100_000.try_into().unwrap(),
743 },
744 email: "[email protected]".into(),
745 private_key: priv_key.to_owned(),
746 signing_key: None,
747 method: InitUserCryptoMethod::Pin {
748 pin: "1234".into(),
749 pin_protected_user_key: pin_key.pin_protected_user_key,
750 },
751 },
752 )
753 .await
754 .unwrap();
755
756 let client_key = {
757 let key_store = client.internal.get_key_store();
758 let ctx = key_store.context();
759 #[allow(deprecated)]
760 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
761 .unwrap()
762 .to_base64()
763 };
764
765 let client2_key = {
766 let key_store = client2.internal.get_key_store();
767 let ctx = key_store.context();
768 #[allow(deprecated)]
769 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
770 .unwrap()
771 .to_base64()
772 };
773
774 assert_eq!(client_key, client2_key);
775
776 let pin_protected_user_key = derive_pin_user_key(&client, pin_key.encrypted_pin).unwrap();
778
779 let client3 = Client::new(None);
780
781 initialize_user_crypto(
782 &client3,
783 InitUserCryptoRequest {
784 user_id: Some(uuid::Uuid::new_v4()),
785 kdf_params: Kdf::PBKDF2 {
786 iterations: 100_000.try_into().unwrap(),
787 },
788 email: "[email protected]".into(),
789 private_key: priv_key.to_owned(),
790 signing_key: None,
791 method: InitUserCryptoMethod::Pin {
792 pin: "1234".into(),
793 pin_protected_user_key,
794 },
795 },
796 )
797 .await
798 .unwrap();
799
800 let client_key = {
801 let key_store = client.internal.get_key_store();
802 let ctx = key_store.context();
803 #[allow(deprecated)]
804 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
805 .unwrap()
806 .to_base64()
807 };
808
809 let client3_key = {
810 let key_store = client3.internal.get_key_store();
811 let ctx = key_store.context();
812 #[allow(deprecated)]
813 ctx.dangerous_get_symmetric_key(SymmetricKeyId::User)
814 .unwrap()
815 .to_base64()
816 };
817
818 assert_eq!(client_key, client3_key);
819 }
820
821 #[test]
822 fn test_enroll_admin_password_reset() {
823 use base64::{engine::general_purpose::STANDARD, Engine};
824
825 let client = Client::new(None);
826
827 let master_key = MasterKey::derive(
828 "asdfasdfasdf",
829 "[email protected]",
830 &Kdf::PBKDF2 {
831 iterations: NonZeroU32::new(600_000).unwrap(),
832 },
833 )
834 .unwrap();
835
836 let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap();
837 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();
838 client
839 .internal
840 .initialize_user_crypto_master_key(master_key, user_key, private_key, None)
841 .unwrap();
842
843 let public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsy7RFHcX3C8Q4/OMmhhbFReYWfB45W9PDTEA8tUZwZmtOiN2RErIS2M1c+K/4HoDJ/TjpbX1f2MZcr4nWvKFuqnZXyewFc+jmvKVewYi+NAu2++vqKq2kKcmMNhwoQDQdQIVy/Uqlp4Cpi2cIwO6ogq5nHNJGR3jm+CpyrafYlbz1bPvL3hbyoGDuG2tgADhyhXUdFuef2oF3wMvn1lAJAvJnPYpMiXUFmj1ejmbwtlxZDrHgUJvUcp7nYdwUKaFoi+sOttHn3u7eZPtNvxMjhSS/X/1xBIzP/mKNLdywH5LoRxniokUk+fV3PYUxJsiU3lV0Trc/tH46jqd8ZGjmwIDAQAB";
844
845 let encrypted = enroll_admin_password_reset(&client, public_key.to_owned()).unwrap();
846
847 let private_key = "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=";
848 let private_key =
849 AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key).unwrap()).unwrap();
850 let decrypted: SymmetricCryptoKey =
851 encrypted.decapsulate_key_unsigned(&private_key).unwrap();
852
853 let key_store = client.internal.get_key_store();
854 let ctx = key_store.context();
855 #[allow(deprecated)]
856 let expected = ctx
857 .dangerous_get_symmetric_key(SymmetricKeyId::User)
858 .unwrap();
859
860 assert_eq!(decrypted, *expected);
861 }
862
863 #[test]
864 fn test_derive_key_connector() {
865 let request = DeriveKeyConnectorRequest {
866 password: "asdfasdfasdf".to_string(),
867 email: "[email protected]".to_string(),
868 kdf: Kdf::PBKDF2 {
869 iterations: NonZeroU32::new(600_000).unwrap(),
870 },
871 user_key_encrypted: "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(),
872 };
873
874 let result = derive_key_connector(request).unwrap();
875
876 assert_eq!(result, "ySXq1RVLKEaV1eoQE/ui9aFKIvXTl9PAXwp1MljfF50=");
877 }
878
879 fn setup_asymmetric_keys_test() -> (UserKey, RsaKeyPair) {
880 let master_key = MasterKey::derive(
881 "asdfasdfasdf",
882 "[email protected]",
883 &Kdf::PBKDF2 {
884 iterations: NonZeroU32::new(600_000).unwrap(),
885 },
886 )
887 .unwrap();
888 let user_key = (master_key.make_user_key().unwrap()).0;
889 let key_pair = user_key.make_key_pair().unwrap();
890
891 (user_key, key_pair)
892 }
893
894 #[test]
895 fn test_make_key_pair() {
896 let (user_key, _) = setup_asymmetric_keys_test();
897
898 let response = make_key_pair(user_key.0.to_base64()).unwrap();
899
900 assert!(!response.user_public_key.is_empty());
901 let encrypted_private_key = response.user_key_encrypted_private_key;
902 let private_key: Vec<u8> = encrypted_private_key.decrypt_with_key(&user_key.0).unwrap();
903 assert!(!private_key.is_empty());
904 }
905
906 #[test]
907 fn test_verify_asymmetric_keys_success() {
908 let (user_key, key_pair) = setup_asymmetric_keys_test();
909
910 let request = VerifyAsymmetricKeysRequest {
911 user_key: user_key.0.to_base64(),
912 user_public_key: key_pair.public,
913 user_key_encrypted_private_key: key_pair.private,
914 };
915 let response = verify_asymmetric_keys(request).unwrap();
916
917 assert!(response.private_key_decryptable);
918 assert!(response.valid_private_key);
919 }
920
921 #[test]
922 fn test_verify_asymmetric_keys_decrypt_failed() {
923 let (user_key, key_pair) = setup_asymmetric_keys_test();
924 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();
925
926 let request = VerifyAsymmetricKeysRequest {
927 user_key: user_key.0.to_base64(),
928 user_public_key: key_pair.public,
929 user_key_encrypted_private_key: undecryptable_private_key,
930 };
931 let response = verify_asymmetric_keys(request).unwrap();
932
933 assert!(!response.private_key_decryptable);
934 assert!(!response.valid_private_key);
935 }
936
937 #[test]
938 fn test_verify_asymmetric_keys_parse_failed() {
939 let (user_key, key_pair) = setup_asymmetric_keys_test();
940
941 let invalid_private_key = "bad_key"
942 .to_string()
943 .into_bytes()
944 .encrypt_with_key(&user_key.0)
945 .unwrap();
946
947 let request = VerifyAsymmetricKeysRequest {
948 user_key: user_key.0.to_base64(),
949 user_public_key: key_pair.public,
950 user_key_encrypted_private_key: invalid_private_key,
951 };
952 let response = verify_asymmetric_keys(request).unwrap();
953
954 assert!(response.private_key_decryptable);
955 assert!(!response.valid_private_key);
956 }
957
958 #[test]
959 fn test_verify_asymmetric_keys_key_mismatch() {
960 let (user_key, key_pair) = setup_asymmetric_keys_test();
961 let new_key_pair = user_key.make_key_pair().unwrap();
962
963 let request = VerifyAsymmetricKeysRequest {
964 user_key: user_key.0.to_base64(),
965 user_public_key: key_pair.public,
966 user_key_encrypted_private_key: new_key_pair.private,
967 };
968 let response = verify_asymmetric_keys(request).unwrap();
969
970 assert!(response.private_key_decryptable);
971 assert!(!response.valid_private_key);
972 }
973}