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