bitwarden_user_crypto_management/key_rotation/
crypto.rs1use bitwarden_api_api::models::{
3 AccountKeysRequestModel, WrappedAccountCryptographicStateRequestModel,
4};
5use bitwarden_core::key_management::{
6 KeySlotIds, SymmetricKeySlotId, account_cryptographic_state::WrappedAccountCryptographicState,
7};
8use tracing::debug;
9
10pub(super) fn rotate_account_cryptographic_state_to_request_model(
14 wrapped_account_cryptographic_state: &WrappedAccountCryptographicState,
15 current_user_key_id: &SymmetricKeySlotId,
16 new_user_key_id: &SymmetricKeySlotId,
17 ctx: &mut bitwarden_crypto::KeyStoreContext<KeySlotIds>,
18) -> Result<AccountKeysRequestModel, ()> {
19 let rotated_account_cryptographic_state = rotate_account_cryptographic_state(
20 wrapped_account_cryptographic_state,
21 current_user_key_id,
22 new_user_key_id,
23 ctx,
24 )?;
25
26 debug!("Converting rotated account cryptographic state to account keys request model",);
27
28 let account_keys_model = rotated_account_cryptographic_state
30 .to_request_model(new_user_key_id, ctx)
31 .map_err(|_| ())?;
32 Ok(account_keys_model)
33}
34
35pub(super) fn account_cryptographic_state_to_wrapped_model(
39 wrapped_account_cryptographic_state: &WrappedAccountCryptographicState,
40 new_user_key_id: &SymmetricKeySlotId,
41 ctx: &mut bitwarden_crypto::KeyStoreContext<KeySlotIds>,
42) -> Result<WrappedAccountCryptographicStateRequestModel, ()> {
43 debug!("Converting rotated account cryptographic state to wrapped request model",);
44
45 let wrapped_request_model = wrapped_account_cryptographic_state
47 .to_wrapped_request_model(new_user_key_id, ctx)
48 .map_err(|_| ())?;
49 Ok(wrapped_request_model)
50}
51
52pub(super) fn rotate_account_cryptographic_state(
53 wrapped_account_cryptographic_state: &WrappedAccountCryptographicState,
54 current_user_key_id: &SymmetricKeySlotId,
55 new_user_key_id: &SymmetricKeySlotId,
56 ctx: &mut bitwarden_crypto::KeyStoreContext<KeySlotIds>,
57) -> Result<WrappedAccountCryptographicState, ()> {
58 debug!(
59 ?current_user_key_id,
60 ?new_user_key_id,
61 "Rotating account cryptographic state",
62 );
63
64 let rotated_account_cryptographic_state = WrappedAccountCryptographicState::rotate(
67 wrapped_account_cryptographic_state,
68 current_user_key_id,
69 new_user_key_id,
70 ctx,
71 )
72 .map_err(|_| ())?;
73
74 Ok(rotated_account_cryptographic_state)
75}
76
77#[cfg(test)]
78mod tests {
79 use bitwarden_core::key_management::account_cryptographic_state::WrappedAccountCryptographicState;
80 use bitwarden_crypto::{
81 CoseSerializable, KeyStore, PublicKey, PublicKeyEncryptionAlgorithm, SymmetricKeyAlgorithm,
82 };
83 use bitwarden_encoding::B64;
84
85 use super::*;
86
87 fn make_v1_wrapped_state(
90 ctx: &mut bitwarden_crypto::KeyStoreContext<KeySlotIds>,
91 ) -> (
92 SymmetricKeySlotId,
93 PublicKey,
94 WrappedAccountCryptographicState,
95 ) {
96 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
97 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
98 let wrapped_private_key = ctx.wrap_private_key(user_key, private_key).unwrap();
99
100 (
101 user_key,
102 ctx.get_public_key(private_key).unwrap(),
103 WrappedAccountCryptographicState::V1 {
104 private_key: wrapped_private_key,
105 },
106 )
107 }
108
109 fn assert_wrapped_request_model_fields(
110 model: &WrappedAccountCryptographicStateRequestModel,
111 actual_public_key: &B64,
112 ) {
113 let model_public_key = model
114 .public_key_encryption_key_pair
115 .public_key
116 .as_ref()
117 .expect("public_key should be present");
118 assert_eq!(
119 actual_public_key.to_string(),
120 *model_public_key,
121 "Public key should be correctly included in the model"
122 );
123
124 assert!(
126 model
127 .public_key_encryption_key_pair
128 .signed_public_key
129 .is_some(),
130 "signed_public_key should be present for V2 state"
131 );
132
133 assert!(
138 model.signature_key_pair.verifying_key.is_some(),
139 "verifying_key should be present"
140 );
141 assert!(
142 model.signature_key_pair.wrapped_signing_key.is_some(),
143 "wrapped_signing_key should be present"
144 );
145 assert!(
146 model.signature_key_pair.signature_algorithm.is_some(),
147 "signature_algorithm should be present"
148 );
149 assert!(
150 model.security_state.security_state.is_some(),
151 "security_state content should be present"
152 );
153 }
154
155 #[test]
156 fn test_rotate_v1_to_v2_returns_wrapped_request_model() {
157 let store: KeyStore<KeySlotIds> = KeyStore::default();
159 let mut ctx = store.context_mut();
160
161 let (old_user_key_id, public_key, wrapped_state) = make_v1_wrapped_state(&mut ctx);
163
164 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
166
167 let rotated_state = rotate_account_cryptographic_state(
168 &wrapped_state,
169 &old_user_key_id,
170 &new_user_key_id,
171 &mut ctx,
172 )
173 .expect("rotate_account_cryptographic_state should succeed");
174 let model = account_cryptographic_state_to_wrapped_model(
175 &rotated_state,
176 &new_user_key_id,
177 &mut ctx,
178 )
179 .expect("rotate_account_cryptographic_state_to_wrapped_model should succeed");
180
181 let actual_public_key: B64 = public_key.to_der().unwrap().into();
182 assert_wrapped_request_model_fields(&model, &actual_public_key);
183 }
184
185 #[test]
186 fn test_rotate_v2_to_v2_returns_wrapped_request_model() {
187 let store: KeyStore<KeySlotIds> = KeyStore::default();
189 let mut ctx = store.context_mut();
190
191 let (old_user_key_id, wrapped_state) =
193 WrappedAccountCryptographicState::make(&mut ctx).unwrap();
194
195 let private_key_id = match &wrapped_state {
197 WrappedAccountCryptographicState::V2 { private_key, .. } => ctx
198 .unwrap_private_key(old_user_key_id, private_key)
199 .unwrap(),
200 _ => panic!("Expected V2 state"),
201 };
202 let signing_key_id = match &wrapped_state {
203 WrappedAccountCryptographicState::V2 { signing_key, .. } => ctx
204 .unwrap_signing_key(old_user_key_id, signing_key)
205 .unwrap(),
206 _ => panic!("Expected V2 state"),
207 };
208 let public_key = ctx.get_public_key(private_key_id).unwrap();
209 ctx.get_verifying_key(signing_key_id)
210 .expect("verifying key should be obtainable");
211
212 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
214
215 let rotated_state = rotate_account_cryptographic_state(
216 &wrapped_state,
217 &old_user_key_id,
218 &new_user_key_id,
219 &mut ctx,
220 )
221 .expect("rotate_account_cryptographic_state should succeed");
222 let model = account_cryptographic_state_to_wrapped_model(
223 &rotated_state,
224 &new_user_key_id,
225 &mut ctx,
226 )
227 .expect("account_cryptographic_state_to_wrapped_model should succeed");
228
229 let actual_public_key: B64 = public_key.to_der().unwrap().into();
230 assert_wrapped_request_model_fields(&model, &actual_public_key);
231 }
232
233 #[test]
234 fn test_rotate_v1_to_v2_returns_account_keys_model() {
235 let store: KeyStore<KeySlotIds> = KeyStore::default();
237 let mut ctx = store.context_mut();
238
239 let (old_user_key_id, public_key, wrapped_state) = make_v1_wrapped_state(&mut ctx);
241
242 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
244
245 let model = rotate_account_cryptographic_state_to_request_model(
246 &wrapped_state,
247 &old_user_key_id,
248 &new_user_key_id,
249 &mut ctx,
250 )
251 .expect("rotate_account_cryptographic_state_to_request_model should succeed");
252
253 let actual_public_key: B64 = public_key.to_der().unwrap().into();
254 let public_key_encryption_key_pair = model
255 .public_key_encryption_key_pair
256 .as_ref()
257 .expect("public_key_encryption_key_pair should be present");
258 let model_public_key = public_key_encryption_key_pair
259 .public_key
260 .as_ref()
261 .expect("public_key should be present");
262 assert_eq!(
263 actual_public_key.to_string(),
264 *model_public_key,
265 "Public key should be correctly included in the model"
266 );
267
268 assert!(
270 public_key_encryption_key_pair.signed_public_key.is_some(),
271 "signed_public_key should be present for V2 state"
272 );
273
274 let signature_key_pair = model
281 .signature_key_pair
282 .as_ref()
283 .expect("signature_key_pair should be present for V2 state");
284 assert!(
285 signature_key_pair.verifying_key.is_some(),
286 "verifying_key should be present"
287 );
288 assert!(
289 signature_key_pair.wrapped_signing_key.is_some(),
290 "wrapped_signing_key should be present"
291 );
292 assert!(
293 signature_key_pair.signature_algorithm.is_some(),
294 "signature_algorithm should be present"
295 );
296
297 let security_state = model
299 .security_state
300 .as_ref()
301 .expect("security_state should be present for V2 state");
302 assert!(
303 security_state.security_state.is_some(),
304 "security_state content should be present"
305 );
306 }
307
308 #[test]
309 fn test_rotate_v2_to_v2_returns_account_keys_model() {
310 let store: KeyStore<KeySlotIds> = KeyStore::default();
312 let mut ctx = store.context_mut();
313
314 let (old_user_key_id, wrapped_state) =
316 WrappedAccountCryptographicState::make(&mut ctx).unwrap();
317
318 let private_key_id = match &wrapped_state {
320 WrappedAccountCryptographicState::V2 { private_key, .. } => ctx
321 .unwrap_private_key(old_user_key_id, private_key)
322 .unwrap(),
323 _ => panic!("Expected V2 state"),
324 };
325 let signing_key_id = match &wrapped_state {
326 WrappedAccountCryptographicState::V2 { signing_key, .. } => ctx
327 .unwrap_signing_key(old_user_key_id, signing_key)
328 .unwrap(),
329 _ => panic!("Expected V2 state"),
330 };
331 let public_key = ctx.get_public_key(private_key_id).unwrap();
332 let verifying_key = ctx
333 .get_verifying_key(signing_key_id)
334 .expect("verifying key should be obtainable");
335
336 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
338
339 let model = rotate_account_cryptographic_state_to_request_model(
340 &wrapped_state,
341 &old_user_key_id,
342 &new_user_key_id,
343 &mut ctx,
344 )
345 .expect("rotate_account_cryptographic_state_to_request_model should succeed");
346
347 let actual_public_key: B64 = public_key.to_der().unwrap().into();
348 let public_key_encryption_key_pair = model
349 .public_key_encryption_key_pair
350 .as_ref()
351 .expect("public_key_encryption_key_pair should be present");
352 let model_public_key = public_key_encryption_key_pair
353 .public_key
354 .as_ref()
355 .expect("public_key should be present");
356 assert_eq!(
357 actual_public_key.to_string(),
358 *model_public_key,
359 "Public key should be correctly included in the model"
360 );
361
362 assert!(
364 public_key_encryption_key_pair.signed_public_key.is_some(),
365 "signed_public_key should be present for V2 state"
366 );
367
368 let signature_key_pair = model
375 .signature_key_pair
376 .as_ref()
377 .expect("signature_key_pair should be present for V2 state");
378 let actual_verifying_key: B64 = verifying_key.to_cose().into();
379 let model_verifying_key = signature_key_pair
380 .verifying_key
381 .as_ref()
382 .expect("verifying_key should be present");
383 assert_eq!(
384 actual_verifying_key.to_string(),
385 *model_verifying_key,
386 "Verifying key should be correctly included in the model"
387 );
388
389 assert!(
390 signature_key_pair.wrapped_signing_key.is_some(),
391 "wrapped_signing_key should be present"
392 );
393 assert!(
394 signature_key_pair.signature_algorithm.is_some(),
395 "signature_algorithm should be present"
396 );
397
398 let security_state = model
400 .security_state
401 .as_ref()
402 .expect("security_state should be present for V2 state");
403 assert!(
404 security_state.security_state.is_some(),
405 "security_state content should be present"
406 );
407 }
408}