Skip to main content

bitwarden_user_crypto_management/key_rotation/
rotation_context.rs

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        // Note this is important to allow for the case where a user has no org memberships, but has
198        // provided a non-empty list of trusted org public keys. For example their
199        // organization membership was removed in the middle of the key rotation process.
200        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        // only ea1 is trusted
284        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}