1use bitwarden_core::key_management::{KeySlotIds, SymmetricKeySlotId};
2use bitwarden_crypto::{KeyStoreContext, PublicKey};
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::UntrustedKeyError)?;
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::UntrustedKeyError)?;
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 =
89 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::XChaCha20Poly1305);
90
91 Ok(RotationContext {
92 v1_organization_memberships,
93 v1_emergency_access_memberships,
94 current_user_key_id,
95 new_user_key_id,
96 })
97}
98
99#[cfg(test)]
100mod tests {
101 use bitwarden_core::key_management::{
102 KeySlotIds, PrivateKeySlotId, SymmetricKeySlotId,
103 account_cryptographic_state::WrappedAccountCryptographicState,
104 };
105 use bitwarden_crypto::{
106 KeyStore, KeyStoreContext, PublicKeyEncryptionAlgorithm, SymmetricKeyAlgorithm,
107 };
108 use uuid::Uuid;
109
110 use super::{
111 super::{
112 sync::SyncedAccountData,
113 unlock::{V1EmergencyAccessMembership, V1OrganizationMembership},
114 },
115 RotateUserKeysError, filter_trusted_emergency_access, filter_trusted_organization,
116 make_rotation_context,
117 };
118
119 fn make_org_membership(
120 ctx: &mut KeyStoreContext<KeySlotIds>,
121 ) -> (V1OrganizationMembership, PrivateKeySlotId) {
122 let org_private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
123 (
124 V1OrganizationMembership {
125 organization_id: Uuid::new_v4(),
126 name: "Test Org".to_string(),
127 public_key: ctx.get_public_key(org_private_key).expect("key exists"),
128 },
129 org_private_key,
130 )
131 }
132
133 fn make_ea_membership(
134 ctx: &mut KeyStoreContext<KeySlotIds>,
135 ) -> (V1EmergencyAccessMembership, PrivateKeySlotId) {
136 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
137 (
138 V1EmergencyAccessMembership {
139 id: Uuid::new_v4(),
140 name: "Test User".to_string(),
141 public_key: ctx.get_public_key(private_key).expect("key exists"),
142 grantee_id: Uuid::new_v4(),
143 },
144 private_key,
145 )
146 }
147
148 fn assert_org_membership_eq(
149 actual: &V1OrganizationMembership,
150 expected: &V1OrganizationMembership,
151 ) {
152 assert_eq!(actual.organization_id, expected.organization_id);
153 assert_eq!(actual.name, expected.name);
154 assert_eq!(actual.public_key, expected.public_key);
155 }
156
157 fn assert_ea_membership_eq(
158 actual: &V1EmergencyAccessMembership,
159 expected: &V1EmergencyAccessMembership,
160 ) {
161 assert_eq!(actual.id, expected.id);
162 assert_eq!(actual.name, expected.name);
163 assert_eq!(actual.grantee_id, expected.grantee_id);
164 assert_eq!(actual.public_key, expected.public_key);
165 }
166
167 fn make_test_sync(
168 org_memberships: Vec<V1OrganizationMembership>,
169 ea_memberships: Vec<V1EmergencyAccessMembership>,
170 ctx: &mut KeyStoreContext<KeySlotIds>,
171 ) -> SyncedAccountData {
172 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
173 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
174 let wrapped_private_key = ctx.wrap_private_key(user_key, private_key).unwrap();
175 SyncedAccountData {
176 wrapped_account_cryptographic_state: WrappedAccountCryptographicState::V1 {
177 private_key: wrapped_private_key,
178 },
179 folders: vec![],
180 ciphers: vec![],
181 sends: vec![],
182 emergency_access_memberships: ea_memberships,
183 organization_memberships: org_memberships,
184 trusted_devices: vec![],
185 passkeys: vec![],
186 kdf_and_salt: None,
187 }
188 }
189
190 #[test]
191 fn test_filter_trusted_org_empty_list() {
192 let store: KeyStore<KeySlotIds> = KeyStore::default();
193 let mut ctx = store.context_mut();
194 let (org, _) = make_org_membership(&mut ctx);
195 let trusted = [org.public_key.clone()];
196
197 let result = filter_trusted_organization(&[], &trusted);
201
202 assert!(matches!(result, Ok(ref v) if v.is_empty()));
203 }
204
205 #[test]
206 fn test_filter_trusted_org_all_trusted() {
207 let store: KeyStore<KeySlotIds> = KeyStore::default();
208 let mut ctx = store.context_mut();
209 let (org1, _) = make_org_membership(&mut ctx);
210 let (org2, _) = make_org_membership(&mut ctx);
211 let trusted = [org1.public_key.clone(), org2.public_key.clone()];
212 let expected_org1 = org1.clone();
213 let expected_org2 = org2.clone();
214
215 let result = filter_trusted_organization(&[org1, org2], &trusted);
216
217 let memberships = result.unwrap();
218 assert_eq!(memberships.len(), 2);
219 assert_org_membership_eq(&memberships[0], &expected_org1);
220 assert_org_membership_eq(&memberships[1], &expected_org2);
221 }
222
223 #[test]
224 fn test_filter_trusted_org_one_untrusted() {
225 let store: KeyStore<KeySlotIds> = KeyStore::default();
226 let mut ctx = store.context_mut();
227 let (org1, _) = make_org_membership(&mut ctx);
228 let (org2, _) = make_org_membership(&mut ctx);
229 let trusted = [org1.public_key.clone()];
230
231 let result = filter_trusted_organization(&[org1, org2], &trusted);
232
233 assert!(result.is_err());
234 }
235
236 #[test]
237 fn test_filter_trusted_org_empty_trusted_with_orgs() {
238 let store: KeyStore<KeySlotIds> = KeyStore::default();
239 let mut ctx = store.context_mut();
240 let (org, _) = make_org_membership(&mut ctx);
241
242 let result = filter_trusted_organization(&[org], &[]);
243
244 assert!(result.is_err());
245 }
246
247 #[test]
248 fn test_filter_trusted_ea_empty_list() {
249 let store: KeyStore<KeySlotIds> = KeyStore::default();
250 let mut ctx = store.context_mut();
251 let (ea, _) = make_ea_membership(&mut ctx);
252 let trusted = [ea.public_key.clone()];
253
254 let result = filter_trusted_emergency_access(&[], &trusted);
255
256 assert!(matches!(result, Ok(ref v) if v.is_empty()));
257 }
258
259 #[test]
260 fn test_filter_trusted_ea_all_trusted() {
261 let store: KeyStore<KeySlotIds> = KeyStore::default();
262 let mut ctx = store.context_mut();
263 let (ea1, _) = make_ea_membership(&mut ctx);
264 let (ea2, _) = make_ea_membership(&mut ctx);
265 let trusted = [ea1.public_key.clone(), ea2.public_key.clone()];
266 let expected_ea1 = ea1.clone();
267 let expected_ea2 = ea2.clone();
268
269 let result = filter_trusted_emergency_access(&[ea1, ea2], &trusted);
270
271 let memberships = result.expect("should succeed");
272 assert_eq!(memberships.len(), 2);
273 assert_ea_membership_eq(&memberships[0], &expected_ea1);
274 assert_ea_membership_eq(&memberships[1], &expected_ea2);
275 }
276
277 #[test]
278 fn test_filter_trusted_ea_one_untrusted() {
279 let store: KeyStore<KeySlotIds> = KeyStore::default();
280 let mut ctx = store.context_mut();
281 let (ea1, _) = make_ea_membership(&mut ctx);
282 let (ea2, _) = make_ea_membership(&mut ctx);
283 let trusted = [ea1.public_key.clone()];
285
286 let result = filter_trusted_emergency_access(&[ea1, ea2], &trusted);
287
288 assert!(result.is_err());
289 }
290
291 #[test]
292 fn test_filter_trusted_ea_empty_trusted_with_memberships() {
293 let store: KeyStore<KeySlotIds> = KeyStore::default();
294 let mut ctx = store.context_mut();
295 let (ea, _) = make_ea_membership(&mut ctx);
296
297 let result = filter_trusted_emergency_access(&[ea], &[]);
298
299 assert!(result.is_err());
300 }
301
302 #[test]
303 fn test_make_rotation_context_empty_data() {
304 let store: KeyStore<KeySlotIds> = KeyStore::default();
305 let mut ctx = store.context_mut();
306 let sync = make_test_sync(vec![], vec![], &mut ctx);
307
308 let result = make_rotation_context(&sync, &[], &[], &mut ctx);
309
310 let rotation_ctx = result.expect("should succeed");
311 assert!(rotation_ctx.v1_organization_memberships.is_empty());
312 assert!(rotation_ctx.v1_emergency_access_memberships.is_empty());
313 assert_eq!(rotation_ctx.current_user_key_id, SymmetricKeySlotId::User);
314 assert_ne!(
315 rotation_ctx.new_user_key_id,
316 rotation_ctx.current_user_key_id
317 );
318 }
319
320 #[test]
321 fn test_make_rotation_context_trusted_org_and_ea() {
322 let store: KeyStore<KeySlotIds> = KeyStore::default();
323 let mut ctx = store.context_mut();
324 let (org, _) = make_org_membership(&mut ctx);
325 let (ea, _) = make_ea_membership(&mut ctx);
326 let trusted_orgs = [org.public_key.clone()];
327 let trusted_eas = [ea.public_key.clone()];
328 let expected_org = org.clone();
329 let expected_ea = ea.clone();
330 let sync = make_test_sync(vec![org], vec![ea], &mut ctx);
331
332 let result = make_rotation_context(&sync, &trusted_orgs, &trusted_eas, &mut ctx);
333
334 let rotation_ctx = result.expect("should succeed");
335 assert_eq!(rotation_ctx.v1_organization_memberships.len(), 1);
336 assert_org_membership_eq(&rotation_ctx.v1_organization_memberships[0], &expected_org);
337 assert_eq!(rotation_ctx.v1_emergency_access_memberships.len(), 1);
338 assert_ea_membership_eq(
339 &rotation_ctx.v1_emergency_access_memberships[0],
340 &expected_ea,
341 );
342 assert_eq!(rotation_ctx.current_user_key_id, SymmetricKeySlotId::User);
343 assert_ne!(
344 rotation_ctx.new_user_key_id,
345 rotation_ctx.current_user_key_id
346 );
347 }
348
349 #[test]
350 fn test_make_rotation_context_untrusted_org_returns_error() {
351 let store: KeyStore<KeySlotIds> = KeyStore::default();
352 let mut ctx = store.context_mut();
353 let (org, _) = make_org_membership(&mut ctx);
354 let sync = make_test_sync(vec![org], vec![], &mut ctx);
355
356 let result = make_rotation_context(&sync, &[], &[], &mut ctx);
357
358 assert!(matches!(
359 result,
360 Err(RotateUserKeysError::UntrustedKeyError)
361 ));
362 }
363
364 #[test]
365 fn test_make_rotation_context_untrusted_ea_returns_error() {
366 let store: KeyStore<KeySlotIds> = KeyStore::default();
367 let mut ctx = store.context_mut();
368 let (ea, _) = make_ea_membership(&mut ctx);
369 let sync = make_test_sync(vec![], vec![ea], &mut ctx);
370
371 let result = make_rotation_context(&sync, &[], &[], &mut ctx);
372
373 assert!(matches!(
374 result,
375 Err(RotateUserKeysError::UntrustedKeyError)
376 ));
377 }
378}