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