1use bitwarden_core::key_management::{KeySlotIds, SymmetricKeySlotId};
2use bitwarden_crypto::{KeyStore, KeyStoreContext, PublicKey, SymmetricKeyAlgorithm};
3use tracing::{debug, info, warn};
4
5use super::{
6 RotateUserKeysError,
7 rotate_user_keys::UpgradeTokenAction,
8 sync::SyncedAccountData,
9 unlock::{V1EmergencyAccessMembership, V1OrganizationMembership},
10};
11
12#[derive(Debug)]
13struct UntrustedKeyError;
14
15fn creates_v2_upgrade_token(
21 current_user_key_id: SymmetricKeySlotId,
22 upgrade_token_action: UpgradeTokenAction,
23 ctx: &KeyStoreContext<KeySlotIds>,
24) -> bool {
25 if matches!(upgrade_token_action, UpgradeTokenAction::Skip) {
26 debug!("UpgradeTokenAction::Skip, no upgrade token is created for this rotation");
27 return false;
28 }
29
30 ctx.is_v1_symmetric_key(current_user_key_id)
31 .unwrap_or(false)
32}
33
34pub(super) fn organization_memberships_needing_trust(
40 organization_memberships: Vec<V1OrganizationMembership>,
41 upgrade_token_action: UpgradeTokenAction,
42 key_store: &KeyStore<KeySlotIds>,
43) -> Vec<V1OrganizationMembership> {
44 let creates_v2_upgrade_token = {
45 let ctx = key_store.context();
46 creates_v2_upgrade_token(SymmetricKeySlotId::User, upgrade_token_action, &ctx)
47 };
48
49 if creates_v2_upgrade_token {
50 info!(
51 "Rotation creates a V2 upgrade token, none of the {} organization public key(s) need confirmation",
52 organization_memberships.len()
53 );
54 return vec![];
55 }
56
57 organization_memberships
58}
59
60fn filter_trusted_organization(
61 org: &[V1OrganizationMembership],
62 trusted_orgs: &[PublicKey],
63) -> Result<Vec<V1OrganizationMembership>, UntrustedKeyError> {
64 org.iter()
65 .map(|o| {
66 let is_trusted = trusted_orgs.iter().any(|tk| tk == &o.public_key);
67 if !is_trusted {
68 warn!(
69 "Aborting because untrusted organization detected with id={}",
70 o.organization_id
71 );
72 Err(UntrustedKeyError)
73 } else {
74 Ok(o.clone())
75 }
76 })
77 .collect::<Result<Vec<V1OrganizationMembership>, UntrustedKeyError>>()
78}
79
80fn filter_trusted_emergency_access(
81 ea: &[V1EmergencyAccessMembership],
82 trusted_emergency_access_user_public_keys: &[PublicKey],
83) -> Result<Vec<V1EmergencyAccessMembership>, UntrustedKeyError> {
84 ea.iter()
85 .map(|e| {
86 let is_trusted = trusted_emergency_access_user_public_keys
87 .iter()
88 .any(|tk| tk == &e.public_key);
89 if !is_trusted {
90 warn!(
91 "Aborting because untrusted emergency access membership detected with id={}",
92 e.id
93 );
94 Err(UntrustedKeyError)
95 } else {
96 Ok(e.to_owned())
97 }
98 })
99 .collect::<Result<Vec<V1EmergencyAccessMembership>, UntrustedKeyError>>()
100}
101
102pub(super) struct RotationContext {
103 pub(super) v1_organization_memberships: Vec<V1OrganizationMembership>,
104 pub(super) v1_emergency_access_memberships: Vec<V1EmergencyAccessMembership>,
105 pub(super) current_user_key_id: SymmetricKeySlotId,
106 pub(super) new_user_key_id: SymmetricKeySlotId,
107 pub(super) creates_v2_upgrade_token: bool,
108}
109
110pub(super) fn make_rotation_context(
115 sync: &SyncedAccountData,
116 trusted_organization_public_keys: &[PublicKey],
117 trusted_emergency_access_public_keys: &[PublicKey],
118 upgrade_token_action: UpgradeTokenAction,
119 ctx: &mut KeyStoreContext<KeySlotIds>,
120) -> Result<RotationContext, RotateUserKeysError> {
121 info!(
122 "Existing user cryptographic version {:?}",
123 sync.wrapped_account_cryptographic_state
124 );
125 let current_user_key_id = SymmetricKeySlotId::User;
126
127 debug!("Generating new XAES-256-GCM user key for key rotation");
128 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
129
130 let creates_v2_upgrade_token =
131 creates_v2_upgrade_token(current_user_key_id, upgrade_token_action, ctx);
132
133 let v1_organization_memberships = if creates_v2_upgrade_token {
134 info!(
137 "Skipping the trust check for {} organization public key(s), the rotation does not use them",
138 sync.organization_memberships.len()
139 );
140 sync.organization_memberships.clone()
141 } else {
142 filter_trusted_organization(
143 sync.organization_memberships.as_slice(),
144 trusted_organization_public_keys,
145 )
146 .map_err(|_| RotateUserKeysError::UntrustedKey)?
147 };
148
149 let v1_emergency_access_memberships = filter_trusted_emergency_access(
150 sync.emergency_access_memberships.as_slice(),
151 trusted_emergency_access_public_keys,
152 )
153 .map_err(|_| RotateUserKeysError::UntrustedKey)?;
154
155 Ok(RotationContext {
156 v1_organization_memberships,
157 v1_emergency_access_memberships,
158 current_user_key_id,
159 new_user_key_id,
160 creates_v2_upgrade_token,
161 })
162}
163
164#[cfg(test)]
165mod tests {
166 use bitwarden_core::key_management::{
167 KeySlotIds, PrivateKeySlotId, SymmetricKeySlotId,
168 account_cryptographic_state::WrappedAccountCryptographicState,
169 };
170 use bitwarden_crypto::{
171 KeyStore, KeyStoreContext, PublicKeyEncryptionAlgorithm, SymmetricKeyAlgorithm,
172 };
173 use uuid::Uuid;
174
175 use super::{
176 super::{
177 sync::SyncedAccountData,
178 unlock::{V1EmergencyAccessMembership, V1OrganizationMembership},
179 },
180 RotateUserKeysError, UpgradeTokenAction, creates_v2_upgrade_token,
181 filter_trusted_emergency_access, filter_trusted_organization, make_rotation_context,
182 organization_memberships_needing_trust,
183 };
184
185 #[test]
186 fn test_creates_v2_upgrade_token_v1_user_key_with_create_if_needed() {
187 let store: KeyStore<KeySlotIds> = KeyStore::default();
188 let mut ctx = store.context_mut();
189
190 let current_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
191
192 assert!(creates_v2_upgrade_token(
193 current_user_key_id,
194 UpgradeTokenAction::CreateIfNeeded,
195 &ctx,
196 ));
197 }
198
199 #[test]
200 fn test_creates_v2_upgrade_token_v1_user_key_with_skip() {
201 let store: KeyStore<KeySlotIds> = KeyStore::default();
202 let mut ctx = store.context_mut();
203
204 let current_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
205
206 assert!(!creates_v2_upgrade_token(
207 current_user_key_id,
208 UpgradeTokenAction::Skip,
209 &ctx,
210 ));
211 }
212
213 #[test]
214 fn test_creates_v2_upgrade_token_v2_user_key_with_create_if_needed() {
215 let store: KeyStore<KeySlotIds> = KeyStore::default();
216 let mut ctx = store.context_mut();
217
218 let current_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
219
220 assert!(!creates_v2_upgrade_token(
221 current_user_key_id,
222 UpgradeTokenAction::CreateIfNeeded,
223 &ctx,
224 ));
225 }
226
227 #[test]
228 fn test_creates_v2_upgrade_token_missing_current_user_key() {
229 let store: KeyStore<KeySlotIds> = KeyStore::default();
230 let ctx = store.context();
231
232 assert!(
233 !creates_v2_upgrade_token(
234 SymmetricKeySlotId::User,
235 UpgradeTokenAction::CreateIfNeeded,
236 &ctx,
237 ),
238 "a user key that cannot be read must not be treated as V1"
239 );
240 }
241
242 fn make_org_membership(
243 ctx: &mut KeyStoreContext<KeySlotIds>,
244 ) -> (V1OrganizationMembership, PrivateKeySlotId) {
245 let org_private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
246 (
247 V1OrganizationMembership {
248 organization_id: Uuid::new_v4(),
249 name: "Test Org".to_string(),
250 public_key: ctx.get_public_key(org_private_key).expect("key exists"),
251 },
252 org_private_key,
253 )
254 }
255
256 fn make_ea_membership(
257 ctx: &mut KeyStoreContext<KeySlotIds>,
258 ) -> (V1EmergencyAccessMembership, PrivateKeySlotId) {
259 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
260 (
261 V1EmergencyAccessMembership {
262 id: Uuid::new_v4(),
263 name: "Test User".to_string(),
264 public_key: ctx.get_public_key(private_key).expect("key exists"),
265 grantee_id: Uuid::new_v4(),
266 },
267 private_key,
268 )
269 }
270
271 fn assert_org_membership_eq(
272 actual: &V1OrganizationMembership,
273 expected: &V1OrganizationMembership,
274 ) {
275 assert_eq!(actual.organization_id, expected.organization_id);
276 assert_eq!(actual.name, expected.name);
277 assert_eq!(actual.public_key, expected.public_key);
278 }
279
280 fn assert_ea_membership_eq(
281 actual: &V1EmergencyAccessMembership,
282 expected: &V1EmergencyAccessMembership,
283 ) {
284 assert_eq!(actual.id, expected.id);
285 assert_eq!(actual.name, expected.name);
286 assert_eq!(actual.grantee_id, expected.grantee_id);
287 assert_eq!(actual.public_key, expected.public_key);
288 }
289
290 fn make_test_sync(
291 org_memberships: Vec<V1OrganizationMembership>,
292 ea_memberships: Vec<V1EmergencyAccessMembership>,
293 ctx: &mut KeyStoreContext<KeySlotIds>,
294 ) -> SyncedAccountData {
295 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
296 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
297 let wrapped_private_key = ctx.wrap_private_key(user_key, private_key).unwrap();
298 SyncedAccountData {
299 wrapped_account_cryptographic_state: WrappedAccountCryptographicState::V1 {
300 private_key: wrapped_private_key,
301 },
302 folders: vec![],
303 ciphers: vec![],
304 sends: vec![],
305 emergency_access_memberships: ea_memberships,
306 organization_memberships: org_memberships,
307 trusted_devices: vec![],
308 passkeys: vec![],
309 kdf_and_salt: None,
310 }
311 }
312
313 #[test]
314 fn test_filter_trusted_org_empty_list() {
315 let store: KeyStore<KeySlotIds> = KeyStore::default();
316 let mut ctx = store.context_mut();
317 let (org, _) = make_org_membership(&mut ctx);
318 let trusted = [org.public_key.clone()];
319
320 let result = filter_trusted_organization(&[], &trusted);
324
325 assert!(matches!(result, Ok(ref v) if v.is_empty()));
326 }
327
328 #[test]
329 fn test_filter_trusted_org_all_trusted() {
330 let store: KeyStore<KeySlotIds> = KeyStore::default();
331 let mut ctx = store.context_mut();
332 let (org1, _) = make_org_membership(&mut ctx);
333 let (org2, _) = make_org_membership(&mut ctx);
334 let trusted = [org1.public_key.clone(), org2.public_key.clone()];
335 let expected_org1 = org1.clone();
336 let expected_org2 = org2.clone();
337
338 let result = filter_trusted_organization(&[org1, org2], &trusted);
339
340 let memberships = result.unwrap();
341 assert_eq!(memberships.len(), 2);
342 assert_org_membership_eq(&memberships[0], &expected_org1);
343 assert_org_membership_eq(&memberships[1], &expected_org2);
344 }
345
346 #[test]
347 fn test_filter_trusted_org_one_untrusted() {
348 let store: KeyStore<KeySlotIds> = KeyStore::default();
349 let mut ctx = store.context_mut();
350 let (org1, _) = make_org_membership(&mut ctx);
351 let (org2, _) = make_org_membership(&mut ctx);
352 let trusted = [org1.public_key.clone()];
353
354 let result = filter_trusted_organization(&[org1, org2], &trusted);
355
356 assert!(result.is_err());
357 }
358
359 #[test]
360 fn test_filter_trusted_org_empty_trusted_with_orgs() {
361 let store: KeyStore<KeySlotIds> = KeyStore::default();
362 let mut ctx = store.context_mut();
363 let (org, _) = make_org_membership(&mut ctx);
364
365 let result = filter_trusted_organization(&[org], &[]);
366
367 assert!(result.is_err());
368 }
369
370 #[test]
371 fn test_filter_trusted_ea_empty_list() {
372 let store: KeyStore<KeySlotIds> = KeyStore::default();
373 let mut ctx = store.context_mut();
374 let (ea, _) = make_ea_membership(&mut ctx);
375 let trusted = [ea.public_key.clone()];
376
377 let result = filter_trusted_emergency_access(&[], &trusted);
378
379 assert!(matches!(result, Ok(ref v) if v.is_empty()));
380 }
381
382 #[test]
383 fn test_filter_trusted_ea_all_trusted() {
384 let store: KeyStore<KeySlotIds> = KeyStore::default();
385 let mut ctx = store.context_mut();
386 let (ea1, _) = make_ea_membership(&mut ctx);
387 let (ea2, _) = make_ea_membership(&mut ctx);
388 let trusted = [ea1.public_key.clone(), ea2.public_key.clone()];
389 let expected_ea1 = ea1.clone();
390 let expected_ea2 = ea2.clone();
391
392 let result = filter_trusted_emergency_access(&[ea1, ea2], &trusted);
393
394 let memberships = result.expect("should succeed");
395 assert_eq!(memberships.len(), 2);
396 assert_ea_membership_eq(&memberships[0], &expected_ea1);
397 assert_ea_membership_eq(&memberships[1], &expected_ea2);
398 }
399
400 #[test]
401 fn test_filter_trusted_ea_one_untrusted() {
402 let store: KeyStore<KeySlotIds> = KeyStore::default();
403 let mut ctx = store.context_mut();
404 let (ea1, _) = make_ea_membership(&mut ctx);
405 let (ea2, _) = make_ea_membership(&mut ctx);
406 let trusted = [ea1.public_key.clone()];
408
409 let result = filter_trusted_emergency_access(&[ea1, ea2], &trusted);
410
411 assert!(result.is_err());
412 }
413
414 #[test]
415 fn test_filter_trusted_ea_empty_trusted_with_memberships() {
416 let store: KeyStore<KeySlotIds> = KeyStore::default();
417 let mut ctx = store.context_mut();
418 let (ea, _) = make_ea_membership(&mut ctx);
419
420 let result = filter_trusted_emergency_access(&[ea], &[]);
421
422 assert!(result.is_err());
423 }
424
425 #[test]
426 fn test_make_rotation_context_empty_data() {
427 let store: KeyStore<KeySlotIds> = KeyStore::default();
428 let mut ctx = store.context_mut();
429 let sync = make_test_sync(vec![], vec![], &mut ctx);
430
431 let result = make_rotation_context(&sync, &[], &[], UpgradeTokenAction::Skip, &mut ctx);
432
433 let rotation_ctx = result.expect("should succeed");
434 assert!(rotation_ctx.v1_organization_memberships.is_empty());
435 assert!(rotation_ctx.v1_emergency_access_memberships.is_empty());
436 assert_eq!(rotation_ctx.current_user_key_id, SymmetricKeySlotId::User);
437 assert_ne!(
438 rotation_ctx.new_user_key_id,
439 rotation_ctx.current_user_key_id
440 );
441 assert!(!rotation_ctx.creates_v2_upgrade_token);
442 }
443
444 #[test]
445 fn test_make_rotation_context_untrusted_org_with_upgrade_token_is_accepted() {
446 let store: KeyStore<KeySlotIds> = KeyStore::default();
447 let mut ctx = store.context_mut();
448 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
449 ctx.persist_symmetric_key(user_key, SymmetricKeySlotId::User)
450 .expect("persisting the user key should work");
451 let (org, _) = make_org_membership(&mut ctx);
452 let expected_org = org.clone();
453 let sync = make_test_sync(vec![org], vec![], &mut ctx);
454
455 let result = make_rotation_context(
456 &sync,
457 &[],
458 &[],
459 UpgradeTokenAction::CreateIfNeeded,
460 &mut ctx,
461 );
462
463 let rotation_ctx = result.expect("should succeed");
464 assert!(rotation_ctx.creates_v2_upgrade_token);
465 assert_eq!(rotation_ctx.v1_organization_memberships.len(), 1);
466 assert_org_membership_eq(&rotation_ctx.v1_organization_memberships[0], &expected_org);
467 }
468
469 #[test]
470 fn test_make_rotation_context_untrusted_org_v2_user_returns_error() {
471 let store: KeyStore<KeySlotIds> = KeyStore::default();
472 let mut ctx = store.context_mut();
473 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XAes256Gcm);
474 ctx.persist_symmetric_key(user_key, SymmetricKeySlotId::User)
475 .expect("persisting the user key should work");
476 let (org, _) = make_org_membership(&mut ctx);
477 let sync = make_test_sync(vec![org], vec![], &mut ctx);
478
479 let result = make_rotation_context(
480 &sync,
481 &[],
482 &[],
483 UpgradeTokenAction::CreateIfNeeded,
484 &mut ctx,
485 );
486
487 assert!(matches!(result, Err(RotateUserKeysError::UntrustedKey)));
488 }
489
490 #[test]
491 fn test_make_rotation_context_untrusted_emergency_access_with_upgrade_token_returns_error() {
492 let store: KeyStore<KeySlotIds> = KeyStore::default();
493 let mut ctx = store.context_mut();
494 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
495 ctx.persist_symmetric_key(user_key, SymmetricKeySlotId::User)
496 .expect("persisting the user key should work");
497 let (ea, _) = make_ea_membership(&mut ctx);
498 let sync = make_test_sync(vec![], vec![ea], &mut ctx);
499
500 let result = make_rotation_context(
501 &sync,
502 &[],
503 &[],
504 UpgradeTokenAction::CreateIfNeeded,
505 &mut ctx,
506 );
507
508 assert!(matches!(result, Err(RotateUserKeysError::UntrustedKey)));
509 }
510
511 #[test]
512 fn test_make_rotation_context_trusted_org_and_ea() {
513 let store: KeyStore<KeySlotIds> = KeyStore::default();
514 let mut ctx = store.context_mut();
515 let (org, _) = make_org_membership(&mut ctx);
516 let (ea, _) = make_ea_membership(&mut ctx);
517 let trusted_orgs = [org.public_key.clone()];
518 let trusted_eas = [ea.public_key.clone()];
519 let expected_org = org.clone();
520 let expected_ea = ea.clone();
521 let sync = make_test_sync(vec![org], vec![ea], &mut ctx);
522
523 let result = make_rotation_context(
524 &sync,
525 &trusted_orgs,
526 &trusted_eas,
527 UpgradeTokenAction::Skip,
528 &mut ctx,
529 );
530
531 let rotation_ctx = result.expect("should succeed");
532 assert_eq!(rotation_ctx.v1_organization_memberships.len(), 1);
533 assert_org_membership_eq(&rotation_ctx.v1_organization_memberships[0], &expected_org);
534 assert_eq!(rotation_ctx.v1_emergency_access_memberships.len(), 1);
535 assert_ea_membership_eq(
536 &rotation_ctx.v1_emergency_access_memberships[0],
537 &expected_ea,
538 );
539 assert_eq!(rotation_ctx.current_user_key_id, SymmetricKeySlotId::User);
540 assert_ne!(
541 rotation_ctx.new_user_key_id,
542 rotation_ctx.current_user_key_id
543 );
544 }
545
546 #[test]
547 fn test_make_rotation_context_untrusted_org_returns_error() {
548 let store: KeyStore<KeySlotIds> = KeyStore::default();
549 let mut ctx = store.context_mut();
550 let (org, _) = make_org_membership(&mut ctx);
551 let sync = make_test_sync(vec![org], vec![], &mut ctx);
552
553 let result = make_rotation_context(&sync, &[], &[], UpgradeTokenAction::Skip, &mut ctx);
554
555 assert!(matches!(result, Err(RotateUserKeysError::UntrustedKey)));
556 }
557
558 #[test]
559 fn test_make_rotation_context_untrusted_ea_returns_error() {
560 let store: KeyStore<KeySlotIds> = KeyStore::default();
561 let mut ctx = store.context_mut();
562 let (ea, _) = make_ea_membership(&mut ctx);
563 let sync = make_test_sync(vec![], vec![ea], &mut ctx);
564
565 let result = make_rotation_context(&sync, &[], &[], UpgradeTokenAction::Skip, &mut ctx);
566
567 assert!(matches!(result, Err(RotateUserKeysError::UntrustedKey)));
568 }
569
570 fn make_store_with_user_key(
572 algorithm: SymmetricKeyAlgorithm,
573 ) -> (KeyStore<KeySlotIds>, V1OrganizationMembership) {
574 let store: KeyStore<KeySlotIds> = KeyStore::default();
575 let org = {
576 let mut ctx = store.context_mut();
577 let user_key = ctx.make_symmetric_key(algorithm);
578 ctx.persist_symmetric_key(user_key, SymmetricKeySlotId::User)
579 .expect("persisting the user key should work");
580 make_org_membership(&mut ctx).0
581 };
582
583 (store, org)
584 }
585
586 #[test]
587 fn test_organization_memberships_needing_trust_v1_user_key_with_create_if_needed_is_empty() {
588 let (store, org) = make_store_with_user_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
589
590 let memberships = organization_memberships_needing_trust(
591 vec![org],
592 UpgradeTokenAction::CreateIfNeeded,
593 &store,
594 );
595
596 assert!(memberships.is_empty());
597 }
598
599 #[test]
600 fn test_organization_memberships_needing_trust_v1_user_key_with_skip() {
601 let (store, org) = make_store_with_user_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
602 let expected_org = org.clone();
603
604 let memberships =
605 organization_memberships_needing_trust(vec![org], UpgradeTokenAction::Skip, &store);
606
607 assert_eq!(memberships.len(), 1);
608 assert_org_membership_eq(&memberships[0], &expected_org);
609 }
610
611 #[test]
612 fn test_organization_memberships_needing_trust_v2_user_key_with_create_if_needed() {
613 let (store, org) = make_store_with_user_key(SymmetricKeyAlgorithm::XAes256Gcm);
614 let expected_org = org.clone();
615
616 let memberships = organization_memberships_needing_trust(
617 vec![org],
618 UpgradeTokenAction::CreateIfNeeded,
619 &store,
620 );
621
622 assert_eq!(memberships.len(), 1);
623 assert_org_membership_eq(&memberships[0], &expected_org);
624 }
625}