1use bitwarden_core::key_management::{KeySlotIds, SymmetricKeySlotId};
2use bitwarden_crypto::{KeyStoreContext, PublicKey, SymmetricKeyAlgorithm};
3use tracing::{debug, info, warn};
4
5use super::{
6 RotateUserKeysError,
7 sync::SyncedAccountData,
8 unlock::{V1EmergencyAccessMembership, V1OrganizationMembership},
9};
10
11#[derive(Debug)]
12struct UntrustedKeyError;
13
14fn filter_trusted_organization(
15 org: &[V1OrganizationMembership],
16 trusted_orgs: &[PublicKey],
17) -> Result<Vec<V1OrganizationMembership>, UntrustedKeyError> {
18 org.iter()
19 .map(|o| {
20 let is_trusted = trusted_orgs.iter().any(|tk| tk == &o.public_key);
21 if !is_trusted {
22 warn!(
23 "Aborting because untrusted organization detected with id={}",
24 o.organization_id
25 );
26 Err(UntrustedKeyError)
27 } else {
28 Ok(o.clone())
29 }
30 })
31 .collect::<Result<Vec<V1OrganizationMembership>, UntrustedKeyError>>()
32}
33
34fn filter_trusted_emergency_access(
35 ea: &[V1EmergencyAccessMembership],
36 trusted_emergency_access_user_public_keys: &[PublicKey],
37) -> Result<Vec<V1EmergencyAccessMembership>, UntrustedKeyError> {
38 ea.iter()
39 .map(|e| {
40 let is_trusted = trusted_emergency_access_user_public_keys
41 .iter()
42 .any(|tk| tk == &e.public_key);
43 if !is_trusted {
44 warn!(
45 "Aborting because untrusted emergency access membership detected with id={}",
46 e.id
47 );
48 Err(UntrustedKeyError)
49 } else {
50 Ok(e.to_owned())
51 }
52 })
53 .collect::<Result<Vec<V1EmergencyAccessMembership>, UntrustedKeyError>>()
54}
55
56pub(super) struct RotationContext {
57 pub(super) v1_organization_memberships: Vec<V1OrganizationMembership>,
58 pub(super) v1_emergency_access_memberships: Vec<V1EmergencyAccessMembership>,
59 pub(super) current_user_key_id: SymmetricKeySlotId,
60 pub(super) new_user_key_id: SymmetricKeySlotId,
61}
62
63pub(super) fn make_rotation_context(
64 sync: &SyncedAccountData,
65 trusted_organization_public_keys: &[PublicKey],
66 trusted_emergency_access_public_keys: &[PublicKey],
67 ctx: &mut KeyStoreContext<KeySlotIds>,
68) -> Result<RotationContext, RotateUserKeysError> {
69 let v1_organization_memberships = filter_trusted_organization(
70 sync.organization_memberships.as_slice(),
71 trusted_organization_public_keys,
72 )
73 .map_err(|_| RotateUserKeysError::UntrustedKey)?;
74
75 let v1_emergency_access_memberships = filter_trusted_emergency_access(
76 sync.emergency_access_memberships.as_slice(),
77 trusted_emergency_access_public_keys,
78 )
79 .map_err(|_| RotateUserKeysError::UntrustedKey)?;
80
81 info!(
82 "Existing user cryptographic version {:?}",
83 sync.wrapped_account_cryptographic_state
84 );
85 let current_user_key_id = SymmetricKeySlotId::User;
86
87 debug!("Generating new xchacha20-poly1305 user key for key rotation");
88 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
89
90 Ok(RotationContext {
91 v1_organization_memberships,
92 v1_emergency_access_memberships,
93 current_user_key_id,
94 new_user_key_id,
95 })
96}
97
98#[cfg(test)]
99mod tests {
100 use bitwarden_core::key_management::{
101 KeySlotIds, PrivateKeySlotId, SymmetricKeySlotId,
102 account_cryptographic_state::WrappedAccountCryptographicState,
103 };
104 use bitwarden_crypto::{
105 KeyStore, KeyStoreContext, PublicKeyEncryptionAlgorithm, SymmetricKeyAlgorithm,
106 };
107 use uuid::Uuid;
108
109 use super::{
110 super::{
111 sync::SyncedAccountData,
112 unlock::{V1EmergencyAccessMembership, V1OrganizationMembership},
113 },
114 RotateUserKeysError, filter_trusted_emergency_access, filter_trusted_organization,
115 make_rotation_context,
116 };
117
118 fn make_org_membership(
119 ctx: &mut KeyStoreContext<KeySlotIds>,
120 ) -> (V1OrganizationMembership, PrivateKeySlotId) {
121 let org_private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
122 (
123 V1OrganizationMembership {
124 organization_id: Uuid::new_v4(),
125 name: "Test Org".to_string(),
126 public_key: ctx.get_public_key(org_private_key).expect("key exists"),
127 },
128 org_private_key,
129 )
130 }
131
132 fn make_ea_membership(
133 ctx: &mut KeyStoreContext<KeySlotIds>,
134 ) -> (V1EmergencyAccessMembership, PrivateKeySlotId) {
135 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
136 (
137 V1EmergencyAccessMembership {
138 id: Uuid::new_v4(),
139 name: "Test User".to_string(),
140 public_key: ctx.get_public_key(private_key).expect("key exists"),
141 grantee_id: Uuid::new_v4(),
142 },
143 private_key,
144 )
145 }
146
147 fn assert_org_membership_eq(
148 actual: &V1OrganizationMembership,
149 expected: &V1OrganizationMembership,
150 ) {
151 assert_eq!(actual.organization_id, expected.organization_id);
152 assert_eq!(actual.name, expected.name);
153 assert_eq!(actual.public_key, expected.public_key);
154 }
155
156 fn assert_ea_membership_eq(
157 actual: &V1EmergencyAccessMembership,
158 expected: &V1EmergencyAccessMembership,
159 ) {
160 assert_eq!(actual.id, expected.id);
161 assert_eq!(actual.name, expected.name);
162 assert_eq!(actual.grantee_id, expected.grantee_id);
163 assert_eq!(actual.public_key, expected.public_key);
164 }
165
166 fn make_test_sync(
167 org_memberships: Vec<V1OrganizationMembership>,
168 ea_memberships: Vec<V1EmergencyAccessMembership>,
169 ctx: &mut KeyStoreContext<KeySlotIds>,
170 ) -> SyncedAccountData {
171 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
172 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
173 let wrapped_private_key = ctx.wrap_private_key(user_key, private_key).unwrap();
174 SyncedAccountData {
175 wrapped_account_cryptographic_state: WrappedAccountCryptographicState::V1 {
176 private_key: wrapped_private_key,
177 },
178 folders: vec![],
179 ciphers: vec![],
180 sends: vec![],
181 emergency_access_memberships: ea_memberships,
182 organization_memberships: org_memberships,
183 trusted_devices: vec![],
184 passkeys: vec![],
185 kdf_and_salt: None,
186 }
187 }
188
189 #[test]
190 fn test_filter_trusted_org_empty_list() {
191 let store: KeyStore<KeySlotIds> = KeyStore::default();
192 let mut ctx = store.context_mut();
193 let (org, _) = make_org_membership(&mut ctx);
194 let trusted = [org.public_key.clone()];
195
196 let result = filter_trusted_organization(&[], &trusted);
200
201 assert!(matches!(result, Ok(ref v) if v.is_empty()));
202 }
203
204 #[test]
205 fn test_filter_trusted_org_all_trusted() {
206 let store: KeyStore<KeySlotIds> = KeyStore::default();
207 let mut ctx = store.context_mut();
208 let (org1, _) = make_org_membership(&mut ctx);
209 let (org2, _) = make_org_membership(&mut ctx);
210 let trusted = [org1.public_key.clone(), org2.public_key.clone()];
211 let expected_org1 = org1.clone();
212 let expected_org2 = org2.clone();
213
214 let result = filter_trusted_organization(&[org1, org2], &trusted);
215
216 let memberships = result.unwrap();
217 assert_eq!(memberships.len(), 2);
218 assert_org_membership_eq(&memberships[0], &expected_org1);
219 assert_org_membership_eq(&memberships[1], &expected_org2);
220 }
221
222 #[test]
223 fn test_filter_trusted_org_one_untrusted() {
224 let store: KeyStore<KeySlotIds> = KeyStore::default();
225 let mut ctx = store.context_mut();
226 let (org1, _) = make_org_membership(&mut ctx);
227 let (org2, _) = make_org_membership(&mut ctx);
228 let trusted = [org1.public_key.clone()];
229
230 let result = filter_trusted_organization(&[org1, org2], &trusted);
231
232 assert!(result.is_err());
233 }
234
235 #[test]
236 fn test_filter_trusted_org_empty_trusted_with_orgs() {
237 let store: KeyStore<KeySlotIds> = KeyStore::default();
238 let mut ctx = store.context_mut();
239 let (org, _) = make_org_membership(&mut ctx);
240
241 let result = filter_trusted_organization(&[org], &[]);
242
243 assert!(result.is_err());
244 }
245
246 #[test]
247 fn test_filter_trusted_ea_empty_list() {
248 let store: KeyStore<KeySlotIds> = KeyStore::default();
249 let mut ctx = store.context_mut();
250 let (ea, _) = make_ea_membership(&mut ctx);
251 let trusted = [ea.public_key.clone()];
252
253 let result = filter_trusted_emergency_access(&[], &trusted);
254
255 assert!(matches!(result, Ok(ref v) if v.is_empty()));
256 }
257
258 #[test]
259 fn test_filter_trusted_ea_all_trusted() {
260 let store: KeyStore<KeySlotIds> = KeyStore::default();
261 let mut ctx = store.context_mut();
262 let (ea1, _) = make_ea_membership(&mut ctx);
263 let (ea2, _) = make_ea_membership(&mut ctx);
264 let trusted = [ea1.public_key.clone(), ea2.public_key.clone()];
265 let expected_ea1 = ea1.clone();
266 let expected_ea2 = ea2.clone();
267
268 let result = filter_trusted_emergency_access(&[ea1, ea2], &trusted);
269
270 let memberships = result.expect("should succeed");
271 assert_eq!(memberships.len(), 2);
272 assert_ea_membership_eq(&memberships[0], &expected_ea1);
273 assert_ea_membership_eq(&memberships[1], &expected_ea2);
274 }
275
276 #[test]
277 fn test_filter_trusted_ea_one_untrusted() {
278 let store: KeyStore<KeySlotIds> = KeyStore::default();
279 let mut ctx = store.context_mut();
280 let (ea1, _) = make_ea_membership(&mut ctx);
281 let (ea2, _) = make_ea_membership(&mut ctx);
282 let trusted = [ea1.public_key.clone()];
284
285 let result = filter_trusted_emergency_access(&[ea1, ea2], &trusted);
286
287 assert!(result.is_err());
288 }
289
290 #[test]
291 fn test_filter_trusted_ea_empty_trusted_with_memberships() {
292 let store: KeyStore<KeySlotIds> = KeyStore::default();
293 let mut ctx = store.context_mut();
294 let (ea, _) = make_ea_membership(&mut ctx);
295
296 let result = filter_trusted_emergency_access(&[ea], &[]);
297
298 assert!(result.is_err());
299 }
300
301 #[test]
302 fn test_make_rotation_context_empty_data() {
303 let store: KeyStore<KeySlotIds> = KeyStore::default();
304 let mut ctx = store.context_mut();
305 let sync = make_test_sync(vec![], vec![], &mut ctx);
306
307 let result = make_rotation_context(&sync, &[], &[], &mut ctx);
308
309 let rotation_ctx = result.expect("should succeed");
310 assert!(rotation_ctx.v1_organization_memberships.is_empty());
311 assert!(rotation_ctx.v1_emergency_access_memberships.is_empty());
312 assert_eq!(rotation_ctx.current_user_key_id, SymmetricKeySlotId::User);
313 assert_ne!(
314 rotation_ctx.new_user_key_id,
315 rotation_ctx.current_user_key_id
316 );
317 }
318
319 #[test]
320 fn test_make_rotation_context_trusted_org_and_ea() {
321 let store: KeyStore<KeySlotIds> = KeyStore::default();
322 let mut ctx = store.context_mut();
323 let (org, _) = make_org_membership(&mut ctx);
324 let (ea, _) = make_ea_membership(&mut ctx);
325 let trusted_orgs = [org.public_key.clone()];
326 let trusted_eas = [ea.public_key.clone()];
327 let expected_org = org.clone();
328 let expected_ea = ea.clone();
329 let sync = make_test_sync(vec![org], vec![ea], &mut ctx);
330
331 let result = make_rotation_context(&sync, &trusted_orgs, &trusted_eas, &mut ctx);
332
333 let rotation_ctx = result.expect("should succeed");
334 assert_eq!(rotation_ctx.v1_organization_memberships.len(), 1);
335 assert_org_membership_eq(&rotation_ctx.v1_organization_memberships[0], &expected_org);
336 assert_eq!(rotation_ctx.v1_emergency_access_memberships.len(), 1);
337 assert_ea_membership_eq(
338 &rotation_ctx.v1_emergency_access_memberships[0],
339 &expected_ea,
340 );
341 assert_eq!(rotation_ctx.current_user_key_id, SymmetricKeySlotId::User);
342 assert_ne!(
343 rotation_ctx.new_user_key_id,
344 rotation_ctx.current_user_key_id
345 );
346 }
347
348 #[test]
349 fn test_make_rotation_context_untrusted_org_returns_error() {
350 let store: KeyStore<KeySlotIds> = KeyStore::default();
351 let mut ctx = store.context_mut();
352 let (org, _) = make_org_membership(&mut ctx);
353 let sync = make_test_sync(vec![org], vec![], &mut ctx);
354
355 let result = make_rotation_context(&sync, &[], &[], &mut ctx);
356
357 assert!(matches!(result, Err(RotateUserKeysError::UntrustedKey)));
358 }
359
360 #[test]
361 fn test_make_rotation_context_untrusted_ea_returns_error() {
362 let store: KeyStore<KeySlotIds> = KeyStore::default();
363 let mut ctx = store.context_mut();
364 let (ea, _) = make_ea_membership(&mut ctx);
365 let sync = make_test_sync(vec![], vec![ea], &mut ctx);
366
367 let result = make_rotation_context(&sync, &[], &[], &mut ctx);
368
369 assert!(matches!(result, Err(RotateUserKeysError::UntrustedKey)));
370 }
371}