1use bitwarden_api_api::models::RotateUserKeysRequestModel;
3use bitwarden_core::key_management::{
4 KeySlotIds, V2UpgradeToken, account_cryptographic_state::WrappedAccountCryptographicState,
5};
6use bitwarden_crypto::{KeyConnectorKey, KeyStore, PublicKey, SymmetricCryptoKey};
7use serde::{Deserialize, Serialize};
8use tracing::{info, instrument};
9#[cfg(feature = "wasm")]
10use tsify::Tsify;
11#[cfg(feature = "wasm")]
12use wasm_bindgen::prelude::*;
13
14use crate::{
15 UserCryptoManagementClient,
16 key_rotation::{
17 RotateUserKeysError,
18 crypto::{
19 account_cryptographic_state_to_wrapped_model, rotate_account_cryptographic_state,
20 },
21 data::{check_for_old_attachments, reencrypt_data},
22 rotation_context::make_rotation_context,
23 sync::{SyncedAccountData, sync_current_account_data},
24 unlock::{ReencryptCommonUnlockDataInput, reencrypt_common_unlock_data},
25 unlock_method::{PrimaryUnlockMethod, reencrypt_unlock_method_data},
26 },
27};
28
29#[derive(Serialize, Deserialize, Clone)]
30#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
31#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
32pub enum KeyRotationMethod {
33 Password { password: String },
35 KeyConnector { key_connector_url: String },
37 Tde,
39}
40
41#[derive(Serialize, Deserialize, Clone, PartialEq)]
42#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
43#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
44pub enum UpgradeTokenAction {
45 Skip,
47 CreateIfNeeded,
50}
51
52#[derive(Serialize, Deserialize, Clone)]
53#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
54#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
55pub struct RotateUserKeysRequest {
56 pub key_rotation_method: KeyRotationMethod,
57 pub trusted_emergency_access_public_keys: Vec<PublicKey>,
58 pub trusted_organization_public_keys: Vec<PublicKey>,
59 pub upgrade_token_action: UpgradeTokenAction,
60}
61
62#[cfg_attr(feature = "wasm", wasm_bindgen)]
63impl UserCryptoManagementClient {
64 pub async fn rotate_user_keys(
66 &self,
67 request: RotateUserKeysRequest,
68 ) -> Result<(), RotateUserKeysError> {
69 let api_client = &self.client.internal.get_api_configurations().api_client;
70 let key_store = self.client.internal.get_key_store();
71
72 let sync = sync_current_account_data(api_client)
73 .await
74 .map_err(|_| RotateUserKeysError::Api)?;
75
76 let wrapped_account_cryptographic_state = self
77 .regenerate_public_key_encryption_key_pair_if_needed_with_ciphers(&sync.ciphers)
78 .await
79 .map_err(|_| RotateUserKeysError::Crypto)?
80 .unwrap_or_else(|| sync.wrapped_account_cryptographic_state.clone());
81
82 let key_connector_api_client =
83 if let KeyRotationMethod::KeyConnector { key_connector_url } =
84 &request.key_rotation_method
85 {
86 Some(
87 self.client
88 .internal
89 .get_key_connector_client(key_connector_url.clone()),
90 )
91 } else {
92 None
93 };
94
95 internal_rotate_user_keys(
96 key_store,
97 api_client,
98 &self.client.km_state_bridge(),
99 key_connector_api_client.as_ref(),
100 request,
101 wrapped_account_cryptographic_state,
102 sync,
103 )
104 .await
105 }
106}
107
108struct StateUpdate {
111 user_key: SymmetricCryptoKey,
112 account_cryptographic_state: WrappedAccountCryptographicState,
113 upgrade_token: Option<V2UpgradeToken>,
114}
115
116#[instrument(name = "rotate_user_keys", level = "info", skip_all, err)]
117async fn internal_rotate_user_keys(
118 key_store: &KeyStore<KeySlotIds>,
119 api_client: &bitwarden_api_api::apis::ApiClient,
120 state_bridge: &bitwarden_core::key_management::state_bridge::StateBridgeClient,
121 key_connector_api_client: Option<&bitwarden_api_key_connector::apis::ApiClient>,
122 request: RotateUserKeysRequest,
123 wrapped_account_cryptographic_state: WrappedAccountCryptographicState,
124 sync: SyncedAccountData,
125) -> Result<(), RotateUserKeysError> {
126 check_for_old_attachments(&sync.ciphers)?;
128
129 let key_connector_key = if matches!(
132 request.key_rotation_method,
133 KeyRotationMethod::KeyConnector { .. }
134 ) {
135 let key_connector_client =
136 key_connector_api_client.ok_or(RotateUserKeysError::KeyConnectorApi)?;
137 info!("Fetching Key Connector key for key rotation");
138 let response = key_connector_client
139 .user_keys_api()
140 .get_user_key()
141 .await
142 .map_err(|_| RotateUserKeysError::KeyConnectorApi)?;
143 let key_connector_key =
144 KeyConnectorKey::try_from(response).map_err(|_| RotateUserKeysError::Crypto)?;
145 Some(key_connector_key)
146 } else {
147 None
148 };
149
150 let (post_request, state_bridge_update) = {
152 let mut ctx = key_store.context_mut();
153
154 let rotation_context = make_rotation_context(
155 &sync,
156 request.trusted_organization_public_keys.as_slice(),
157 request.trusted_emergency_access_public_keys.as_slice(),
158 &mut ctx,
159 )?;
160
161 info!("Rotating account cryptographic state for user key rotation");
162 let wrapped_account_cryptographic_state = rotate_account_cryptographic_state(
163 &wrapped_account_cryptographic_state,
164 &rotation_context.current_user_key_id,
165 &rotation_context.new_user_key_id,
166 &mut ctx,
167 )
168 .map_err(|_| RotateUserKeysError::Crypto)?;
169 let wrapped_account_cryptographic_state_request_model =
170 account_cryptographic_state_to_wrapped_model(
171 &wrapped_account_cryptographic_state,
172 &rotation_context.new_user_key_id,
173 &mut ctx,
174 )
175 .map_err(|_| RotateUserKeysError::Crypto)?;
176
177 info!("Re-encrypting account data for user key rotation");
178 let account_data_model = reencrypt_data(
179 sync.folders.as_slice(),
180 sync.ciphers.as_slice(),
181 sync.sends.as_slice(),
182 rotation_context.current_user_key_id,
183 rotation_context.new_user_key_id,
184 &mut ctx,
185 )
186 .map_err(|_| RotateUserKeysError::Crypto)?;
187
188 info!("Re-encrypting account primary unlock method for user key rotation");
189 let unlock_method_input = PrimaryUnlockMethod::from_key_rotation_method(
190 request.key_rotation_method,
191 &sync,
192 key_connector_key,
193 )?;
194 let unlock_method_data = reencrypt_unlock_method_data(
195 unlock_method_input,
196 rotation_context.new_user_key_id,
197 &mut ctx,
198 )
199 .map_err(|_| RotateUserKeysError::Crypto)?;
200
201 info!("Re-encrypting account common unlock data for user key rotation");
202 let common_unlock_data = reencrypt_common_unlock_data(
203 ReencryptCommonUnlockDataInput {
204 trusted_organization_keys: rotation_context.v1_organization_memberships,
205 trusted_emergency_access_keys: rotation_context.v1_emergency_access_memberships,
206 webauthn_credentials: sync.passkeys,
207 trusted_devices: sync.trusted_devices,
208 },
209 rotation_context.current_user_key_id,
210 rotation_context.new_user_key_id,
211 request.upgrade_token_action,
212 &mut ctx,
213 )
214 .map_err(|_| RotateUserKeysError::Crypto)?;
215
216 (
217 RotateUserKeysRequestModel {
218 wrapped_account_cryptographic_state: Box::new(
219 wrapped_account_cryptographic_state_request_model,
220 ),
221 account_data: Box::new(account_data_model),
222 unlock_data: Box::new(common_unlock_data.clone()),
223 unlock_method_data: Box::new(unlock_method_data),
224 },
225 StateUpdate {
226 #[allow(deprecated)]
227 user_key: ctx
228 .dangerous_get_symmetric_key(rotation_context.new_user_key_id)
229 .map_err(|_| RotateUserKeysError::Crypto)?
230 .to_owned(),
231 account_cryptographic_state: wrapped_account_cryptographic_state,
232 upgrade_token: common_unlock_data
233 .v2_upgrade_token
234 .clone()
235 .map(|t| (*t).try_into())
236 .transpose()
237 .map_err(|_| RotateUserKeysError::Crypto)?,
238 },
239 )
240 };
241
242 info!("Posting rotated user account keys and data to server");
243 api_client
244 .accounts_key_management_api()
245 .rotate_user_keys(Some(post_request))
246 .await
247 .map_err(|_| RotateUserKeysError::Api)?;
248 info!("Successfully rotated user account keys and data");
249
250 if let Some(upgrade_token) = state_bridge_update.upgrade_token.as_ref() {
251 info!("Writing new cryptographic data to state");
252 state_bridge
253 .set_account_cryptographic_state(&state_bridge_update.account_cryptographic_state)
254 .await;
255 state_bridge.set_v2_upgrade_token(upgrade_token).await;
256 state_bridge
257 .set_user_key(&state_bridge_update.user_key)
258 .await;
259 }
262
263 Ok(())
264}
265
266#[cfg(test)]
267mod tests {
268 use std::str::FromStr;
269
270 use bitwarden_api_api::{apis::ApiClient, models::UnlockMethod};
271 use bitwarden_core::{
272 Client,
273 key_management::{
274 KeySlotIds, PrivateKeySlotId, SymmetricKeySlotId,
275 account_cryptographic_state::WrappedAccountCryptographicState,
276 state_bridge::{StateBridgeClient, test_support::InMemoryStateBridge},
277 },
278 };
279 use bitwarden_crypto::{
280 Decryptable, EncString, Kdf, KeyStore, PublicKeyEncryptionAlgorithm, SymmetricKeyAlgorithm,
281 UnsignedSharedKey,
282 };
283 use bitwarden_vault::{Attachment, Cipher, CipherType};
284 use chrono::DateTime;
285
286 use super::*;
287 use crate::key_rotation::partial_rotateable_keyset::PartialRotateableKeyset;
288
289 fn make_state_bridge() -> StateBridgeClient {
290 let client = Client::new(None);
291 let bridge = client.km_state_bridge();
292 bridge.register_bridge(Box::new(InMemoryStateBridge::default()));
293 bridge
294 }
295
296 fn make_test_key_store_and_synced_data() -> (KeyStore<KeySlotIds>, SyncedAccountData) {
297 let store: KeyStore<KeySlotIds> = KeyStore::default();
298 let wrapped_private_key = {
299 let mut ctx = store.context_mut();
300 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
301 let _ = ctx.persist_symmetric_key(user_key, SymmetricKeySlotId::User);
302 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
303 ctx.wrap_private_key(SymmetricKeySlotId::User, private_key)
304 .unwrap()
305 };
306
307 let sync = SyncedAccountData {
308 wrapped_account_cryptographic_state: WrappedAccountCryptographicState::V1 {
309 private_key: wrapped_private_key,
310 },
311 folders: vec![],
312 ciphers: vec![],
313 sends: vec![],
314 emergency_access_memberships: vec![],
315 organization_memberships: vec![],
316 trusted_devices: vec![],
317 passkeys: vec![],
318 kdf_and_salt: Some((
319 Kdf::PBKDF2 {
320 iterations: std::num::NonZeroU32::new(600000).unwrap(),
321 },
322 "test_salt".to_string(),
323 )),
324 };
325
326 (store, sync)
327 }
328
329 fn make_test_key_store_and_synced_data_with_trusted_devices()
330 -> (KeyStore<KeySlotIds>, SyncedAccountData, Vec<u8>) {
331 let store: KeyStore<KeySlotIds> = KeyStore::default();
332 let (trusted_device_keyset, wrapped_private_key, public_key) = {
333 let mut ctx = store.context_mut();
334 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
335 let _ = ctx.persist_symmetric_key(user_key, SymmetricKeySlotId::User);
336 let (trusted_device_keyset, device_private_key) =
337 PartialRotateableKeyset::make_test_keyset(SymmetricKeySlotId::User, &mut ctx);
338 let _ = ctx.persist_private_key(device_private_key, PrivateKeySlotId::UserPrivateKey);
339 let wrapped_private_key = ctx
340 .wrap_private_key(SymmetricKeySlotId::User, PrivateKeySlotId::UserPrivateKey)
341 .unwrap();
342 (
343 trusted_device_keyset,
344 wrapped_private_key,
345 ctx.get_public_key(PrivateKeySlotId::UserPrivateKey)
346 .expect("Retrieving the public key should work."),
347 )
348 };
349
350 let sync = SyncedAccountData {
351 wrapped_account_cryptographic_state: WrappedAccountCryptographicState::V1 {
352 private_key: wrapped_private_key,
353 },
354 folders: vec![],
355 ciphers: vec![],
356 sends: vec![],
357 emergency_access_memberships: vec![],
358 organization_memberships: vec![],
359 trusted_devices: vec![trusted_device_keyset],
360 passkeys: vec![],
361 kdf_and_salt: Some((
362 Kdf::PBKDF2 {
363 iterations: std::num::NonZeroU32::new(600000).unwrap(),
364 },
365 "test_salt".to_string(),
366 )),
367 };
368
369 (
370 store,
371 sync,
372 public_key
373 .to_der()
374 .expect("Generating DER serialization should work")
375 .to_vec(),
376 )
377 }
378
379 #[tokio::test]
380 async fn test_rotate_user_keys_tde_success_rotates_common_unlock_data() {
381 let (key_store, sync, public_key_der) =
382 make_test_key_store_and_synced_data_with_trusted_devices();
383 let key_store_clone = key_store.clone();
384
385 let api_client = ApiClient::new_mocked(|mock| {
386 mock.accounts_key_management_api
387 .expect_rotate_user_keys()
388 .once()
389 .returning(move |req| {
390 let req = req.expect("request body should be present");
391 assert_eq!(req.unlock_method_data.unlock_method, UnlockMethod::Tde);
392 assert!(req.unlock_method_data.master_password_unlock_data.is_none());
393 assert!(
394 req.unlock_method_data
395 .key_connector_key_wrapped_user_key
396 .is_none()
397 );
398
399 let device_unlock_data = req
400 .unlock_data
401 .device_key_unlock_data
402 .expect("device unlock data should be present");
403 assert_eq!(device_unlock_data.len(), 1);
404 let rotated_device = &device_unlock_data[0];
405
406 let encrypted_user_key: UnsignedSharedKey = rotated_device
407 .encrypted_user_key
408 .parse()
409 .expect("encrypted user key should parse");
410 let encrypted_public_key: EncString = rotated_device
411 .encrypted_public_key
412 .parse()
413 .expect("encrypted public key should parse");
414 let mut ctx = key_store_clone.context_mut();
415 let rotated_user_key_id = encrypted_user_key
416 .decapsulate(PrivateKeySlotId::UserPrivateKey, &mut ctx)
417 .expect("rotated device user key should decapsulate");
418 let decrypted_public_key: Vec<u8> = encrypted_public_key
419 .decrypt(&mut ctx, rotated_user_key_id)
420 .expect("rotated device public key should decrypt");
421 assert_eq!(decrypted_public_key, public_key_der);
422 Ok(())
423 });
424 });
425
426 let state_bridge = make_state_bridge();
427 let result = internal_rotate_user_keys(
428 &key_store,
429 &api_client,
430 &state_bridge,
431 None,
432 RotateUserKeysRequest {
433 key_rotation_method: KeyRotationMethod::Tde,
434 trusted_organization_public_keys: vec![],
435 trusted_emergency_access_public_keys: vec![],
436 upgrade_token_action: UpgradeTokenAction::Skip,
437 },
438 sync.wrapped_account_cryptographic_state.clone(),
439 sync,
440 )
441 .await;
442
443 assert!(result.is_ok());
444 if let ApiClient::Mock(mut mock) = api_client {
445 mock.accounts_key_management_api.checkpoint();
446 }
447 }
448
449 #[tokio::test]
450 async fn test_rotate_user_keys_master_password_success() {
451 let (key_store, sync) = make_test_key_store_and_synced_data();
452 let api_client = ApiClient::new_mocked(|mock| {
453 mock.accounts_key_management_api
454 .expect_rotate_user_keys()
455 .once()
456 .returning(|_| Ok(()));
457 });
458
459 let state_bridge = make_state_bridge();
460 let result = internal_rotate_user_keys(
461 &key_store,
462 &api_client,
463 &state_bridge,
464 None,
465 RotateUserKeysRequest {
466 key_rotation_method: KeyRotationMethod::Password {
467 password: "test_password".to_string(),
468 },
469 trusted_organization_public_keys: vec![],
470 trusted_emergency_access_public_keys: vec![],
471 upgrade_token_action: UpgradeTokenAction::Skip,
472 },
473 sync.wrapped_account_cryptographic_state.clone(),
474 sync,
475 )
476 .await;
477
478 assert!(result.is_ok());
479 if let ApiClient::Mock(mut mock) = api_client {
480 mock.accounts_key_management_api.checkpoint();
481 }
482 }
483
484 #[tokio::test]
485 async fn test_rotate_user_keys_post_api_failure_returns_api_error() {
486 let (key_store, sync) = make_test_key_store_and_synced_data();
487 let api_client = ApiClient::new_mocked(|mock| {
488 mock.accounts_key_management_api
489 .expect_rotate_user_keys()
490 .once()
491 .returning(|_| {
492 Err(serde_json::Error::io(std::io::Error::other("API error")).into())
493 });
494 });
495
496 let state_bridge = make_state_bridge();
497 let result = internal_rotate_user_keys(
498 &key_store,
499 &api_client,
500 &state_bridge,
501 None,
502 RotateUserKeysRequest {
503 key_rotation_method: KeyRotationMethod::Password {
504 password: "test_password".to_string(),
505 },
506 trusted_organization_public_keys: vec![],
507 trusted_emergency_access_public_keys: vec![],
508 upgrade_token_action: UpgradeTokenAction::Skip,
509 },
510 sync.wrapped_account_cryptographic_state.clone(),
511 sync,
512 )
513 .await;
514
515 assert!(matches!(result, Err(RotateUserKeysError::Api)));
516 if let ApiClient::Mock(mut mock) = api_client {
517 mock.accounts_key_management_api.checkpoint();
518 }
519 }
520
521 #[tokio::test]
522 async fn test_rotate_user_keys_upgrade_token_action_skip_omits_token() {
523 let (key_store, sync) = make_test_key_store_and_synced_data();
524 let api_client = ApiClient::new_mocked(|mock| {
525 mock.accounts_key_management_api
526 .expect_rotate_user_keys()
527 .once()
528 .returning(|req| {
529 let req = req.expect("request body should be present");
530 assert!(
531 req.unlock_data.v2_upgrade_token.is_none(),
532 "upgrade_token_action Skip, should omit the v2_upgrade_token"
533 );
534 Ok(())
535 });
536 });
537
538 let state_bridge = make_state_bridge();
539 let result = internal_rotate_user_keys(
540 &key_store,
541 &api_client,
542 &state_bridge,
543 None,
544 RotateUserKeysRequest {
545 key_rotation_method: KeyRotationMethod::Password {
546 password: "test_password".to_string(),
547 },
548 trusted_organization_public_keys: vec![],
549 trusted_emergency_access_public_keys: vec![],
550 upgrade_token_action: UpgradeTokenAction::Skip,
551 },
552 sync.wrapped_account_cryptographic_state.clone(),
553 sync,
554 )
555 .await;
556
557 assert!(result.is_ok());
558 if let ApiClient::Mock(mut mock) = api_client {
559 mock.accounts_key_management_api.checkpoint();
560 }
561 }
562
563 #[tokio::test]
564 async fn test_rotate_user_keys_upgrade_token_action_create_if_needed_includes_token() {
565 let (key_store, sync) = make_test_key_store_and_synced_data();
566 let api_client = ApiClient::new_mocked(|mock| {
567 mock.accounts_key_management_api
568 .expect_rotate_user_keys()
569 .once()
570 .returning(|req| {
571 let req = req.expect("request body should be present");
572 assert!(
573 req.unlock_data.v2_upgrade_token.is_some(),
574 "upgrade_token_action CreateIfNeeded, should include a v2_upgrade_token for V1 -> V2 rotations"
575 );
576 Ok(())
577 });
578 });
579
580 let state_bridge = make_state_bridge();
581 let result = internal_rotate_user_keys(
582 &key_store,
583 &api_client,
584 &state_bridge,
585 None,
586 RotateUserKeysRequest {
587 key_rotation_method: KeyRotationMethod::Password {
588 password: "test_password".to_string(),
589 },
590 trusted_organization_public_keys: vec![],
591 trusted_emergency_access_public_keys: vec![],
592 upgrade_token_action: UpgradeTokenAction::CreateIfNeeded,
593 },
594 sync.wrapped_account_cryptographic_state.clone(),
595 sync,
596 )
597 .await;
598
599 assert!(result.is_ok());
600 if let ApiClient::Mock(mut mock) = api_client {
601 mock.accounts_key_management_api.checkpoint();
602 }
603 }
604
605 #[tokio::test]
606 async fn test_rotate_user_keys_writes_state_when_upgrade_token_present() {
607 let (key_store, sync) = make_test_key_store_and_synced_data();
608 let api_client = ApiClient::new_mocked(|mock| {
609 mock.accounts_key_management_api
610 .expect_rotate_user_keys()
611 .once()
612 .returning(|_| Ok(()));
613 });
614
615 let state_bridge = make_state_bridge();
616 assert!(state_bridge.get_v2_upgrade_token().await.is_none());
617 assert!(
618 state_bridge
619 .get_account_cryptographic_state()
620 .await
621 .is_none()
622 );
623 assert!(state_bridge.get_user_key().await.is_none());
624
625 let result = internal_rotate_user_keys(
626 &key_store,
627 &api_client,
628 &state_bridge,
629 None,
630 RotateUserKeysRequest {
631 key_rotation_method: KeyRotationMethod::Password {
632 password: "test_password".to_string(),
633 },
634 trusted_organization_public_keys: vec![],
635 trusted_emergency_access_public_keys: vec![],
636 upgrade_token_action: UpgradeTokenAction::CreateIfNeeded,
637 },
638 sync.wrapped_account_cryptographic_state.clone(),
639 sync,
640 )
641 .await;
642
643 assert!(result.is_ok());
644 assert!(
645 state_bridge.get_v2_upgrade_token().await.is_some(),
646 "state bridge should hold the v2 upgrade token after V1 -> V2 rotation"
647 );
648 assert!(
649 state_bridge
650 .get_account_cryptographic_state()
651 .await
652 .is_some(),
653 "state bridge should hold the rotated account cryptographic state"
654 );
655 assert!(
656 state_bridge.get_user_key().await.is_some(),
657 "state bridge should hold the rotated user key"
658 );
659 if let ApiClient::Mock(mut mock) = api_client {
660 mock.accounts_key_management_api.checkpoint();
661 }
662 }
663
664 #[tokio::test]
665 async fn test_rotate_user_keys_skips_state_writes_when_no_upgrade_token() {
666 let (key_store, sync) = make_test_key_store_and_synced_data();
667 let api_client = ApiClient::new_mocked(|mock| {
668 mock.accounts_key_management_api
669 .expect_rotate_user_keys()
670 .once()
671 .returning(|_| Ok(()));
672 });
673
674 let state_bridge = make_state_bridge();
675 let result = internal_rotate_user_keys(
676 &key_store,
677 &api_client,
678 &state_bridge,
679 None,
680 RotateUserKeysRequest {
681 key_rotation_method: KeyRotationMethod::Password {
682 password: "test_password".to_string(),
683 },
684 trusted_organization_public_keys: vec![],
685 trusted_emergency_access_public_keys: vec![],
686 upgrade_token_action: UpgradeTokenAction::Skip,
687 },
688 sync.wrapped_account_cryptographic_state.clone(),
689 sync,
690 )
691 .await;
692
693 assert!(result.is_ok());
694 assert!(
695 state_bridge.get_v2_upgrade_token().await.is_none(),
696 "without an upgrade token, the state bridge must not be written"
697 );
698 assert!(
699 state_bridge
700 .get_account_cryptographic_state()
701 .await
702 .is_none()
703 );
704 assert!(state_bridge.get_user_key().await.is_none());
705 if let ApiClient::Mock(mut mock) = api_client {
706 mock.accounts_key_management_api.checkpoint();
707 }
708 }
709
710 #[tokio::test]
711 async fn test_rotate_user_keys_old_attachments_returns_error() {
712 let (key_store, mut sync) = make_test_key_store_and_synced_data();
713 let enc_string = "2.STIyTrfDZN/JXNDN9zNEMw==|NDLum8BHZpPNYhJo9ggSkg==|UCsCLlBO3QzdPwvMAWs2VVwuE6xwOx/vxOooPObqnEw=";
714
715 sync.ciphers = vec![Cipher {
717 id: None,
718 organization_id: None,
719 folder_id: None,
720 collection_ids: vec![],
721 r#type: CipherType::Login,
722 login: None,
723 identity: None,
724 card: None,
725 secure_note: None,
726 ssh_key: None,
727 bank_account: None,
728 drivers_license: None,
729 passport: None,
730 favorite: false,
731 reprompt: Default::default(),
732 organization_use_totp: false,
733 edit: false,
734 permissions: None,
735 view_password: false,
736 name: enc_string.parse().unwrap(),
737 revision_date: DateTime::from_str("2024-01-01T00:00:00Z").unwrap(),
738 archived_date: None,
739 creation_date: DateTime::from_str("2024-01-01T00:00:00Z").unwrap(),
740 attachments: Some(vec![Attachment {
741 id: None,
742 url: None,
743 size: None,
744 size_name: None,
745 file_name: None,
746 key: None, }]),
748 fields: None,
749 key: None,
750 notes: None,
751 local_data: None,
752 password_history: None,
753 deleted_date: None,
754 data: None,
755 }];
756
757 let api_client = ApiClient::new_mocked(|mock| {
758 mock.accounts_key_management_api
760 .expect_rotate_user_keys()
761 .never();
762 });
763
764 let state_bridge = make_state_bridge();
765 let result = internal_rotate_user_keys(
766 &key_store,
767 &api_client,
768 &state_bridge,
769 None,
770 RotateUserKeysRequest {
771 key_rotation_method: KeyRotationMethod::Password {
772 password: "test_password".to_string(),
773 },
774 trusted_organization_public_keys: vec![],
775 trusted_emergency_access_public_keys: vec![],
776 upgrade_token_action: UpgradeTokenAction::Skip,
777 },
778 sync.wrapped_account_cryptographic_state.clone(),
779 sync,
780 )
781 .await;
782
783 assert!(matches!(result, Err(RotateUserKeysError::OldAttachments)));
784 if let ApiClient::Mock(mut mock) = api_client {
785 mock.accounts_key_management_api.checkpoint();
786 }
787 }
788
789 #[tokio::test]
790 async fn test_rotate_user_keys_key_connector_success() {
791 let (key_store, sync) = make_test_key_store_and_synced_data();
792
793 let key_connector_key = KeyConnectorKey::make();
794 let key_connector_api_client = bitwarden_api_key_connector::apis::ApiClient::new_mocked(
795 |mock| {
796 let key_connector_key_clone = key_connector_key.clone();
797 mock.user_keys_api
798 .expect_get_user_key()
799 .once()
800 .returning(move || {
801 let encoded: bitwarden_encoding::B64 =
802 key_connector_key_clone.clone().into();
803 Ok(
804 bitwarden_api_key_connector::models::user_key_response_model::UserKeyResponseModel {
805 key: encoded.to_string(),
806 },
807 )
808 });
809 },
810 );
811
812 let api_client = ApiClient::new_mocked(|mock| {
813 mock.accounts_key_management_api
814 .expect_rotate_user_keys()
815 .once()
816 .returning(|req| {
817 let req = req.expect("request body should be present");
818 assert!(
819 req.unlock_method_data
820 .key_connector_key_wrapped_user_key
821 .is_some(),
822 "key_connector_key_wrapped_user_key should be set for KC rotation"
823 );
824 assert!(
825 req.unlock_method_data.master_password_unlock_data.is_none(),
826 "master_password_unlock_data should be None for KC rotation"
827 );
828 Ok(())
829 });
830 });
831
832 let state_bridge = make_state_bridge();
833 let result = internal_rotate_user_keys(
834 &key_store,
835 &api_client,
836 &state_bridge,
837 Some(&key_connector_api_client),
838 RotateUserKeysRequest {
839 key_rotation_method: KeyRotationMethod::KeyConnector {
840 key_connector_url: "https://kc.example.com".to_string(),
841 },
842 trusted_organization_public_keys: vec![],
843 trusted_emergency_access_public_keys: vec![],
844 upgrade_token_action: UpgradeTokenAction::Skip,
845 },
846 sync.wrapped_account_cryptographic_state.clone(),
847 sync,
848 )
849 .await;
850
851 assert!(result.is_ok());
852 if let ApiClient::Mock(mut mock) = api_client {
853 mock.accounts_key_management_api.checkpoint();
854 }
855 if let bitwarden_api_key_connector::apis::ApiClient::Mock(mut mock) =
856 key_connector_api_client
857 {
858 mock.user_keys_api.checkpoint();
859 }
860 }
861
862 #[tokio::test]
863 async fn test_rotate_user_keys_key_connector_api_failure() {
864 let (key_store, sync) = make_test_key_store_and_synced_data();
865
866 let key_connector_api_client =
867 bitwarden_api_key_connector::apis::ApiClient::new_mocked(|mock| {
868 mock.user_keys_api
869 .expect_get_user_key()
870 .once()
871 .returning(move || {
872 Err(bitwarden_api_key_connector::apis::Error::ResponseError(
873 bitwarden_api_key_connector::apis::ResponseContent {
874 status: reqwest::StatusCode::INTERNAL_SERVER_ERROR,
875 content: "Server Error".to_string(),
876 },
877 ))
878 });
879 });
880
881 let api_client = ApiClient::new_mocked(|mock| {
882 mock.accounts_key_management_api
883 .expect_rotate_user_keys()
884 .never();
885 });
886
887 let state_bridge = make_state_bridge();
888 let result = internal_rotate_user_keys(
889 &key_store,
890 &api_client,
891 &state_bridge,
892 Some(&key_connector_api_client),
893 RotateUserKeysRequest {
894 key_rotation_method: KeyRotationMethod::KeyConnector {
895 key_connector_url: "https://kc.example.com".to_string(),
896 },
897 trusted_organization_public_keys: vec![],
898 trusted_emergency_access_public_keys: vec![],
899 upgrade_token_action: UpgradeTokenAction::Skip,
900 },
901 sync.wrapped_account_cryptographic_state.clone(),
902 sync,
903 )
904 .await;
905
906 assert!(matches!(result, Err(RotateUserKeysError::KeyConnectorApi)));
907 if let ApiClient::Mock(mut mock) = api_client {
908 mock.accounts_key_management_api.checkpoint();
909 }
910 if let bitwarden_api_key_connector::apis::ApiClient::Mock(mut mock) =
911 key_connector_api_client
912 {
913 mock.user_keys_api.checkpoint();
914 }
915 }
916}