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