1use std::str::FromStr;
2
3use bitwarden_core::key_management::{KeySlotIds, PrivateKeySlotId, SymmetricKeySlotId};
4use bitwarden_crypto::{EncString, KeyStore};
5use bitwarden_encoding::B64;
6use bitwarden_vault::{Cipher, CipherView};
7use tracing::{error, info, warn};
8
9use super::KeyPairRegenerationError;
10
11#[bitwarden_logging::instrument(name = "should_regenerate_public_key_encryption_key_pair", err)]
16pub(super) async fn internal_should_regenerate_public_key_encryption_key_pair(
17 key_store: &KeyStore<KeySlotIds>,
18 api_client: &bitwarden_api_api::apis::ApiClient,
19) -> Result<bool, KeyPairRegenerationError> {
20 match check_key_pair(key_store, api_client).await? {
21 KeyPairCheckResult::Valid => Ok(false),
22 KeyPairCheckResult::NeedsRegeneration => Ok(true),
23 KeyPairCheckResult::RegenerateIfUserKeyIsValid => {
24 is_user_key_valid_from_api(key_store, api_client).await
25 }
26 }
27}
28
29#[bitwarden_logging::instrument(
34 name = "should_regenerate_public_key_encryption_key_pair_with_ciphers",
35 err
36)]
37pub(super) async fn internal_should_regenerate_public_key_encryption_key_pair_with_ciphers(
38 key_store: &KeyStore<KeySlotIds>,
39 api_client: &bitwarden_api_api::apis::ApiClient,
40 ciphers: &[Cipher],
41) -> Result<bool, KeyPairRegenerationError> {
42 match check_key_pair(key_store, api_client).await? {
43 KeyPairCheckResult::Valid => Ok(false),
44 KeyPairCheckResult::NeedsRegeneration => Ok(true),
45 KeyPairCheckResult::RegenerateIfUserKeyIsValid => is_user_key_valid(key_store, ciphers),
46 }
47}
48
49enum KeyPairCheckResult {
50 Valid,
51 NeedsRegeneration,
52 RegenerateIfUserKeyIsValid,
55}
56
57async fn check_key_pair(
60 key_store: &KeyStore<KeySlotIds>,
61 api_client: &bitwarden_api_api::apis::ApiClient,
62) -> Result<KeyPairCheckResult, KeyPairRegenerationError> {
63 {
65 let ctx = key_store.context();
66
67 if !ctx.has_symmetric_key(SymmetricKeySlotId::User) {
68 info!("User key not available, skipping key pair regeneration check");
69 return Ok(KeyPairCheckResult::Valid);
70 }
71
72 if !ctx
73 .is_v1_symmetric_key(SymmetricKeySlotId::User)
74 .map_err(|_| KeyPairRegenerationError::Crypto)?
75 {
76 info!("User has non-V1 encryption, key pair regeneration not applicable");
77 return Ok(KeyPairCheckResult::Valid);
78 }
79 }
80
81 let keys_response = match api_client.accounts_api().get_keys().await {
83 Ok(response) => response,
84 Err(bitwarden_api_api::apis::Error::Response(e))
85 if e.status == reqwest::StatusCode::NOT_FOUND =>
86 {
87 info!("User has no public key encryption key pair (404), regeneration needed");
88 return Ok(KeyPairCheckResult::NeedsRegeneration);
89 }
90 Err(e) => {
91 error!("Failed to fetch user keys from server: {e:?}");
92 return Err(KeyPairRegenerationError::Api);
93 }
94 };
95
96 let public_key_str = keys_response.public_key.as_deref();
97 let private_key_str = keys_response.private_key.as_deref();
98
99 let (public_key_str, private_key_str) = match (public_key_str, private_key_str) {
101 (None, None) => {
102 info!("User has no public key encryption key pair, regeneration needed");
103 return Ok(KeyPairCheckResult::NeedsRegeneration);
104 }
105 (Some(_), None) | (None, Some(_)) => {
106 info!(
107 "User has inconsistent public key encryption key pair (one present, one missing), \
108 regeneration needed"
109 );
110 return Ok(KeyPairCheckResult::NeedsRegeneration);
111 }
112 (Some(pub_key), Some(priv_key)) => (pub_key, priv_key),
113 };
114
115 let Ok(encrypted_private_key) = private_key_str.parse::<EncString>() else {
116 info!("User's private key is not a valid encrypted string, regeneration needed");
117 return Ok(KeyPairCheckResult::NeedsRegeneration);
118 };
119
120 {
122 let mut ctx = key_store.context_mut();
123
124 if let Ok(temp_private_key_id) =
125 ctx.unwrap_private_key(SymmetricKeySlotId::User, &encrypted_private_key)
126 {
127 return match verify_public_key_matches(&ctx, temp_private_key_id, public_key_str) {
128 Ok(true) => {
129 info!("User's public key encryption key pair is valid, no regeneration needed");
130 Ok(KeyPairCheckResult::Valid)
131 }
132 Ok(false) => {
133 info!(
134 "User's private key is decryptable but does not match public key, \
135 regeneration needed"
136 );
137 Ok(KeyPairCheckResult::NeedsRegeneration)
138 }
139 Err(_) => {
140 info!(
141 "User's private key is decryptable but public key derivation failed, \
142 regeneration needed"
143 );
144 Ok(KeyPairCheckResult::NeedsRegeneration)
145 }
146 };
147 }
148 }
149
150 Ok(KeyPairCheckResult::RegenerateIfUserKeyIsValid)
152}
153
154async fn is_user_key_valid_from_api(
157 key_store: &KeyStore<KeySlotIds>,
158 api_client: &bitwarden_api_api::apis::ApiClient,
159) -> Result<bool, KeyPairRegenerationError> {
160 let Ok(ciphers_response) = api_client.ciphers_api().get_all().await else {
161 warn!("Failed to fetch ciphers for user key validation, skipping regeneration");
162 return Ok(false);
163 };
164
165 let personal_cipher = ciphers_response
166 .data
167 .into_iter()
168 .flatten()
169 .find(|c| c.organization_id.is_none());
170
171 let Some(cipher_response) = personal_cipher else {
172 warn!("No personal ciphers available for user key validation, skipping regeneration");
173 return Ok(false);
174 };
175
176 let Ok(cipher) = Cipher::try_from(cipher_response) else {
177 warn!("Failed to parse cipher for user key validation, skipping regeneration");
178 return Ok(false);
179 };
180
181 is_user_key_valid(key_store, std::slice::from_ref(&cipher))
182}
183
184fn is_user_key_valid(
187 key_store: &KeyStore<KeySlotIds>,
188 ciphers: &[Cipher],
189) -> Result<bool, KeyPairRegenerationError> {
190 let Some(cipher) = ciphers
191 .iter()
192 .find(|cipher| cipher.organization_id.is_none())
193 else {
194 warn!("No personal ciphers available for user key validation, skipping regeneration");
195 return Ok(false);
196 };
197
198 if key_store.decrypt::<_, _, CipherView>(cipher).is_ok() {
199 info!(
200 "User's private key cannot be decrypted but user key can decrypt vault data, \
201 regeneration needed"
202 );
203 Ok(true)
204 } else {
205 warn!(
206 "User's private key cannot be decrypted and user key cannot decrypt vault data, \
207 skipping regeneration"
208 );
209 Ok(false)
210 }
211}
212
213fn verify_public_key_matches(
216 ctx: &bitwarden_crypto::KeyStoreContext<KeySlotIds>,
217 private_key_id: PrivateKeySlotId,
218 server_public_key_b64: &str,
219) -> Result<bool, KeyPairRegenerationError> {
220 let derived_public_key = ctx
221 .get_public_key(private_key_id)
222 .map_err(|_| KeyPairRegenerationError::Crypto)?;
223 let derived_b64 = B64::from(
224 derived_public_key
225 .to_der()
226 .map_err(|_| KeyPairRegenerationError::Crypto)?,
227 );
228 let server_b64 =
229 B64::from_str(server_public_key_b64).map_err(|_| KeyPairRegenerationError::Crypto)?;
230 Ok(derived_b64.to_string() == server_b64.to_string())
231}
232
233#[cfg(test)]
234mod tests {
235 use bitwarden_api_api::{
236 apis::ApiClient,
237 models::{
238 CipherDetailsResponseModel, CipherDetailsResponseModelListResponseModel,
239 KeysResponseModel,
240 },
241 };
242 use bitwarden_core::{
243 Client,
244 key_management::{KeySlotIds, SymmetricKeySlotId},
245 };
246 use bitwarden_crypto::{
247 EncString, KeyStore, PrimitiveEncryptable, PublicKeyEncryptionAlgorithm,
248 SymmetricKeyAlgorithm,
249 };
250 use bitwarden_encoding::B64;
251
252 use super::*;
253 use crate::UserCryptoManagementClient;
254
255 fn unlocked_v1_key_store() -> KeyStore<KeySlotIds> {
256 let store: KeyStore<KeySlotIds> = KeyStore::default();
257 {
258 let mut ctx = store.context_mut();
259 let local_user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
260 let _ = ctx.persist_symmetric_key(local_user_key, SymmetricKeySlotId::User);
261 }
262 store
263 }
264
265 fn unlocked_v1_client() -> (UserCryptoManagementClient, KeyStore<KeySlotIds>) {
266 let client = Client::new(None);
267 {
268 let key_store = client.internal.get_key_store();
269 let mut ctx = key_store.context_mut();
270 let local_user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
271 let _ = ctx.persist_symmetric_key(local_user_key, SymmetricKeySlotId::User);
272 }
273 let key_store = client.internal.get_key_store().clone();
274 (UserCryptoManagementClient::new(client), key_store)
275 }
276
277 fn make_valid_key_pair(key_store: &KeyStore<KeySlotIds>) -> (String, String) {
278 let mut ctx = key_store.context_mut();
279 let private_key_id = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
280 let wrapped = ctx
281 .wrap_private_key(SymmetricKeySlotId::User, private_key_id)
282 .unwrap();
283 let public_key = ctx.get_public_key(private_key_id).unwrap();
284 let public_key_b64 = B64::from(public_key.to_der().unwrap()).to_string();
285 (wrapped.to_string(), public_key_b64)
286 }
287
288 fn keys_response(public_key: Option<String>, private_key: Option<String>) -> KeysResponseModel {
289 KeysResponseModel {
290 object: None,
291 key: None,
292 public_key,
293 private_key,
294 account_keys: None,
295 }
296 }
297
298 #[tokio::test]
299 async fn test_should_regenerate_no_user_key() {
300 let key_store: KeyStore<KeySlotIds> = KeyStore::default();
301
302 let api_client = ApiClient::new_mocked(|mock| {
303 mock.accounts_api.expect_get_keys().never();
304 });
305
306 let result =
307 internal_should_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
308 .await;
309 assert!(matches!(result, Ok(false)));
310
311 if let ApiClient::Mock(mut mock) = api_client {
312 mock.accounts_api.checkpoint();
313 }
314 }
315
316 #[tokio::test]
317 async fn test_should_regenerate_v2_encryption() {
318 let key_store: KeyStore<KeySlotIds> = KeyStore::default();
319 {
320 let mut ctx = key_store.context_mut();
321 let key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
322 let _ = ctx.persist_symmetric_key(key, SymmetricKeySlotId::User);
323 }
324
325 let api_client = ApiClient::new_mocked(|mock| {
326 mock.accounts_api.expect_get_keys().never();
327 });
328
329 let result =
330 internal_should_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
331 .await;
332 assert!(matches!(result, Ok(false)));
334
335 if let ApiClient::Mock(mut mock) = api_client {
336 mock.accounts_api.checkpoint();
337 }
338 }
339
340 #[tokio::test]
341 async fn test_should_regenerate_get_keys_api_error() {
342 let key_store = unlocked_v1_key_store();
343
344 let api_client = ApiClient::new_mocked(|mock| {
345 mock.accounts_api.expect_get_keys().once().returning(|| {
346 Err(bitwarden_api_api::apis::Error::Response(
347 bitwarden_api_api::apis::ResponseContent {
348 status: reqwest::StatusCode::INTERNAL_SERVER_ERROR,
349 message: "Internal Server Error".to_string(),
350 },
351 ))
352 });
353 });
354
355 let result =
356 internal_should_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
357 .await;
358 assert!(matches!(result, Err(KeyPairRegenerationError::Api)));
359
360 if let ApiClient::Mock(mut mock) = api_client {
361 mock.accounts_api.checkpoint();
362 }
363 }
364
365 #[tokio::test]
366 async fn test_should_regenerate_get_keys_404() {
367 let key_store = unlocked_v1_key_store();
368
369 let api_client = ApiClient::new_mocked(|mock| {
370 mock.accounts_api.expect_get_keys().once().returning(|| {
371 Err(bitwarden_api_api::apis::Error::Response(
372 bitwarden_api_api::apis::ResponseContent {
373 status: reqwest::StatusCode::NOT_FOUND,
374 message: "Not Found".to_string(),
375 },
376 ))
377 });
378 });
379
380 let result =
381 internal_should_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
382 .await;
383 assert!(matches!(result, Ok(true)));
384
385 if let ApiClient::Mock(mut mock) = api_client {
386 mock.accounts_api.checkpoint();
387 }
388 }
389
390 #[tokio::test]
391 async fn test_should_regenerate_no_key_pair_on_server() {
392 let key_store = unlocked_v1_key_store();
393
394 let api_client = ApiClient::new_mocked(|mock| {
395 mock.accounts_api
396 .expect_get_keys()
397 .once()
398 .returning(|| Ok(keys_response(None, None)));
399 });
400
401 let result =
402 internal_should_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
403 .await;
404 assert!(matches!(result, Ok(true)));
405
406 if let ApiClient::Mock(mut mock) = api_client {
407 mock.accounts_api.checkpoint();
408 }
409 }
410
411 #[tokio::test]
412 async fn test_should_regenerate_inconsistent_key_pair_public_only() {
413 let key_store = unlocked_v1_key_store();
414
415 let api_client = ApiClient::new_mocked(|mock| {
416 mock.accounts_api
417 .expect_get_keys()
418 .once()
419 .returning(|| Ok(keys_response(Some("some-public-key".to_string()), None)));
420 });
421
422 let result =
423 internal_should_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
424 .await;
425 assert!(matches!(result, Ok(true)));
426
427 if let ApiClient::Mock(mut mock) = api_client {
428 mock.accounts_api.checkpoint();
429 }
430 }
431
432 #[tokio::test]
433 async fn test_should_regenerate_inconsistent_key_pair_private_only() {
434 let key_store = unlocked_v1_key_store();
435
436 let api_client = ApiClient::new_mocked(|mock| {
437 mock.accounts_api
438 .expect_get_keys()
439 .once()
440 .returning(|| Ok(keys_response(None, Some("some-private-key".to_string()))));
441 });
442
443 let result =
444 internal_should_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
445 .await;
446 assert!(matches!(result, Ok(true)));
447
448 if let ApiClient::Mock(mut mock) = api_client {
449 mock.accounts_api.checkpoint();
450 }
451 }
452
453 #[tokio::test]
454 async fn test_should_regenerate_valid_key_pair() {
455 let key_store = unlocked_v1_key_store();
456 let (wrapped_private_key, public_key_b64) = make_valid_key_pair(&key_store);
457
458 let api_client = ApiClient::new_mocked(|mock| {
459 mock.accounts_api
460 .expect_get_keys()
461 .once()
462 .returning(move || {
463 Ok(keys_response(
464 Some(public_key_b64.clone()),
465 Some(wrapped_private_key.clone()),
466 ))
467 });
468 });
469
470 let result =
471 internal_should_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
472 .await;
473 assert!(matches!(result, Ok(false)));
474
475 if let ApiClient::Mock(mut mock) = api_client {
476 mock.accounts_api.checkpoint();
477 }
478 }
479
480 #[tokio::test]
481 async fn test_should_regenerate_undecryptable_private_key_no_ciphers() {
482 let key_store = unlocked_v1_key_store();
483
484 let api_client = ApiClient::new_mocked(|mock| {
485 mock.accounts_api
486 .expect_get_keys()
487 .once()
488 .returning(|| {
489 Ok(keys_response(
490 Some("some-public-key".to_string()),
491 Some("2.AAAAAAAAAAAAAAAAAAAAAA==|AAAAAAAAAAAAAAAAAAAAAA==|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_string()),
492 ))
493 });
494 mock.ciphers_api.expect_get_all().once().returning(|| {
495 Ok(CipherDetailsResponseModelListResponseModel {
496 object: None,
497 data: Some(vec![]),
498 continuation_token: None,
499 })
500 });
501 });
502
503 let result =
504 internal_should_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
505 .await;
506 assert!(matches!(result, Ok(false)));
507
508 if let ApiClient::Mock(mut mock) = api_client {
509 mock.accounts_api.checkpoint();
510 mock.ciphers_api.checkpoint();
511 }
512 }
513
514 #[tokio::test]
515 async fn test_should_regenerate_undecryptable_private_key_cipher_fetch_fails() {
516 let key_store = unlocked_v1_key_store();
517
518 let api_client = ApiClient::new_mocked(|mock| {
519 mock.accounts_api
520 .expect_get_keys()
521 .once()
522 .returning(|| {
523 Ok(keys_response(
524 Some("some-public-key".to_string()),
525 Some("2.AAAAAAAAAAAAAAAAAAAAAA==|AAAAAAAAAAAAAAAAAAAAAA==|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_string()),
526 ))
527 });
528 mock.ciphers_api.expect_get_all().once().returning(|| {
529 Err(bitwarden_api_api::apis::Error::Serde(
530 serde_json::Error::io(std::io::Error::other("API error")),
531 ))
532 });
533 });
534
535 let result =
536 internal_should_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
537 .await;
538 assert!(matches!(result, Ok(false)));
539
540 if let ApiClient::Mock(mut mock) = api_client {
541 mock.accounts_api.checkpoint();
542 mock.ciphers_api.checkpoint();
543 }
544 }
545
546 #[tokio::test]
547 async fn test_should_regenerate_undecryptable_private_key_with_valid_user_key() {
548 let (_, key_store) = unlocked_v1_client();
549
550 let encrypted_name = {
551 let mut ctx = key_store.context_mut();
552 let name: EncString = "test cipher"
553 .to_string()
554 .encrypt(&mut ctx, SymmetricKeySlotId::User)
555 .unwrap();
556 name.to_string()
557 };
558
559 let api_client = ApiClient::new_mocked(|mock| {
560 mock.accounts_api
561 .expect_get_keys()
562 .once()
563 .returning(|| {
564 Ok(keys_response(
565 Some("some-public-key".to_string()),
566 Some("2.AAAAAAAAAAAAAAAAAAAAAA==|AAAAAAAAAAAAAAAAAAAAAA==|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_string()),
567 ))
568 });
569 mock.ciphers_api.expect_get_all().once().returning(move || {
570 Ok(CipherDetailsResponseModelListResponseModel {
571 object: None,
572 data: Some(vec![CipherDetailsResponseModel {
573 id: Some(uuid::Uuid::new_v4()),
574 name: Some(encrypted_name.clone()),
575 organization_id: None,
576 r#type: Some(bitwarden_api_api::models::CipherType::Login),
577 revision_date: Some("2024-01-01T00:00:00Z".to_string()),
578 creation_date: Some("2024-01-01T00:00:00Z".to_string()),
579 ..CipherDetailsResponseModel::default()
580 }]),
581 continuation_token: None,
582 })
583 });
584 });
585
586 let result =
587 internal_should_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
588 .await;
589 assert!(matches!(result, Ok(true)));
590
591 if let ApiClient::Mock(mut mock) = api_client {
592 mock.accounts_api.checkpoint();
593 mock.ciphers_api.checkpoint();
594 }
595 }
596
597 #[tokio::test]
598 async fn test_should_regenerate_invalid_enc_string() {
599 let key_store = unlocked_v1_key_store();
600
601 let api_client = ApiClient::new_mocked(|mock| {
602 mock.accounts_api.expect_get_keys().once().returning(|| {
603 Ok(keys_response(
604 Some("some-public-key".to_string()),
605 Some("not-a-valid-enc-string".to_string()),
606 ))
607 });
608 });
609
610 let result =
611 internal_should_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
612 .await;
613 assert!(matches!(result, Ok(true)));
614
615 if let ApiClient::Mock(mut mock) = api_client {
616 mock.accounts_api.checkpoint();
617 }
618 }
619
620 #[tokio::test]
621 async fn test_should_regenerate_decryptable_but_malformed_private_key() {
622 let (_, key_store) = unlocked_v1_client();
623
624 let (wrapped_malformed_private_key, encrypted_name) = {
625 let mut ctx = key_store.context_mut();
626 let malformed_private_key: EncString = "not a valid RSA key"
627 .to_string()
628 .encrypt(&mut ctx, SymmetricKeySlotId::User)
629 .unwrap();
630 let name: EncString = "test cipher"
631 .to_string()
632 .encrypt(&mut ctx, SymmetricKeySlotId::User)
633 .unwrap();
634 (malformed_private_key.to_string(), name.to_string())
635 };
636
637 let api_client = ApiClient::new_mocked(|mock| {
638 mock.accounts_api
639 .expect_get_keys()
640 .once()
641 .returning(move || {
642 Ok(keys_response(
643 Some("some-public-key".to_string()),
644 Some(wrapped_malformed_private_key.clone()),
645 ))
646 });
647 mock.ciphers_api.expect_get_all().once().returning(move || {
648 Ok(CipherDetailsResponseModelListResponseModel {
649 object: None,
650 data: Some(vec![CipherDetailsResponseModel {
651 id: Some(uuid::Uuid::new_v4()),
652 name: Some(encrypted_name.clone()),
653 organization_id: None,
654 r#type: Some(bitwarden_api_api::models::CipherType::Login),
655 revision_date: Some("2024-01-01T00:00:00Z".to_string()),
656 creation_date: Some("2024-01-01T00:00:00Z".to_string()),
657 ..CipherDetailsResponseModel::default()
658 }]),
659 continuation_token: None,
660 })
661 });
662 });
663
664 let result =
665 internal_should_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
666 .await;
667 assert!(matches!(result, Ok(true)));
668
669 if let ApiClient::Mock(mut mock) = api_client {
670 mock.accounts_api.checkpoint();
671 mock.ciphers_api.checkpoint();
672 }
673 }
674
675 #[tokio::test]
676 async fn test_should_regenerate_decryptable_but_public_key_mismatched() {
677 let key_store = unlocked_v1_key_store();
678 let (wrapped_private_key, _) = make_valid_key_pair(&key_store);
679 let wrong_public_key = "AAAAAAAAAA==".to_string();
680
681 let api_client = ApiClient::new_mocked(|mock| {
682 mock.accounts_api
683 .expect_get_keys()
684 .once()
685 .returning(move || {
686 Ok(keys_response(
687 Some(wrong_public_key.clone()),
688 Some(wrapped_private_key.clone()),
689 ))
690 });
691 });
692
693 let result =
694 internal_should_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
695 .await;
696 assert!(matches!(result, Ok(true)));
697
698 if let ApiClient::Mock(mut mock) = api_client {
699 mock.accounts_api.checkpoint();
700 }
701 }
702
703 #[tokio::test]
704 async fn test_should_regenerate_decryptable_but_server_public_key_invalid_b64() {
705 let key_store = unlocked_v1_key_store();
706 let (wrapped_private_key, _) = make_valid_key_pair(&key_store);
707 let invalid_b64_public_key = "not valid base64!!!".to_string();
708
709 let api_client = ApiClient::new_mocked(|mock| {
710 mock.accounts_api
711 .expect_get_keys()
712 .once()
713 .returning(move || {
714 Ok(keys_response(
715 Some(invalid_b64_public_key.clone()),
716 Some(wrapped_private_key.clone()),
717 ))
718 });
719 });
720
721 let result =
722 internal_should_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
723 .await;
724 assert!(matches!(result, Ok(true)));
725
726 if let ApiClient::Mock(mut mock) = api_client {
727 mock.accounts_api.checkpoint();
728 }
729 }
730
731 #[tokio::test]
732 async fn test_should_regenerate_with_ciphers_undecryptable_private_key_no_personal_ciphers() {
733 let key_store = unlocked_v1_key_store();
734
735 let api_client = ApiClient::new_mocked(|mock| {
736 mock.accounts_api
737 .expect_get_keys()
738 .once()
739 .returning(|| {
740 Ok(keys_response(
741 Some("some-public-key".to_string()),
742 Some("2.AAAAAAAAAAAAAAAAAAAAAA==|AAAAAAAAAAAAAAAAAAAAAA==|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_string()),
743 ))
744 });
745 });
746
747 let result = internal_should_regenerate_public_key_encryption_key_pair_with_ciphers(
748 &key_store,
749 &api_client,
750 &[],
751 )
752 .await;
753 assert!(matches!(result, Ok(false)));
754
755 if let ApiClient::Mock(mut mock) = api_client {
756 mock.accounts_api.checkpoint();
757 }
758 }
759
760 #[tokio::test]
761 async fn test_should_regenerate_with_ciphers_undecryptable_private_key_and_undecryptable_cipher()
762 {
763 let key_store = unlocked_v1_key_store();
764
765 let cipher_with_wrong_key = {
769 let other_store: KeyStore<KeySlotIds> = KeyStore::default();
770 let mut ctx = other_store.context_mut();
771 let other_user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
772 let _ = ctx.persist_symmetric_key(other_user_key, SymmetricKeySlotId::User);
773 let cipher_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
774 let wrapped_cipher_key: EncString = ctx
775 .wrap_symmetric_key(SymmetricKeySlotId::User, cipher_key)
776 .unwrap();
777 let name: EncString = "test cipher"
778 .to_string()
779 .encrypt(&mut ctx, cipher_key)
780 .unwrap();
781 Cipher {
782 id: None,
783 organization_id: None,
784 folder_id: None,
785 collection_ids: vec![],
786 key: Some(wrapped_cipher_key),
787 name: Some(name),
788 notes: None,
789 r#type: bitwarden_vault::CipherType::Login,
790 login: None,
791 identity: None,
792 card: None,
793 secure_note: None,
794 ssh_key: None,
795 bank_account: None,
796 favorite: false,
797 reprompt: bitwarden_vault::CipherRepromptType::None,
798 organization_use_totp: false,
799 edit: false,
800 permissions: None,
801 view_password: false,
802 local_data: None,
803 attachments: None,
804 fields: None,
805 password_history: None,
806 creation_date: "2024-01-01T00:00:00Z".parse().unwrap(),
807 deleted_date: None,
808 revision_date: "2024-01-01T00:00:00Z".parse().unwrap(),
809 archived_date: None,
810 data: None,
811 drivers_license: None,
812 passport: None,
813 }
814 };
815
816 let undecryptable_private_key = {
818 let other_store: KeyStore<KeySlotIds> = KeyStore::default();
819 let mut ctx = other_store.context_mut();
820 let other_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
821 let _ = ctx.persist_symmetric_key(other_key, SymmetricKeySlotId::User);
822 let enc: EncString = "fake private key"
823 .to_string()
824 .encrypt(&mut ctx, SymmetricKeySlotId::User)
825 .unwrap();
826 enc.to_string()
827 };
828
829 let api_client = ApiClient::new_mocked(|mock| {
830 mock.accounts_api
831 .expect_get_keys()
832 .once()
833 .returning(move || {
834 Ok(keys_response(
835 Some("some-public-key".to_string()),
836 Some(undecryptable_private_key.clone()),
837 ))
838 });
839 });
840
841 let result = internal_should_regenerate_public_key_encryption_key_pair_with_ciphers(
842 &key_store,
843 &api_client,
844 &[cipher_with_wrong_key],
845 )
846 .await;
847 assert!(matches!(result, Ok(false)));
848
849 if let ApiClient::Mock(mut mock) = api_client {
850 mock.accounts_api.checkpoint();
851 }
852 }
853}