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 rotate_account_cryptographic_state_to_wrapped_model(
39 wrapped_account_cryptographic_state: &WrappedAccountCryptographicState,
40 current_user_key_id: &SymmetricKeySlotId,
41 new_user_key_id: &SymmetricKeySlotId,
42 ctx: &mut bitwarden_crypto::KeyStoreContext<KeySlotIds>,
43) -> Result<WrappedAccountCryptographicStateRequestModel, ()> {
44 let rotated_account_cryptographic_state = rotate_account_cryptographic_state(
45 wrapped_account_cryptographic_state,
46 current_user_key_id,
47 new_user_key_id,
48 ctx,
49 )?;
50
51 debug!("Converting rotated account cryptographic state to wrapped request model",);
52
53 let wrapped_request_model = rotated_account_cryptographic_state
55 .to_wrapped_request_model(new_user_key_id, ctx)
56 .map_err(|_| ())?;
57 Ok(wrapped_request_model)
58}
59
60fn rotate_account_cryptographic_state(
61 wrapped_account_cryptographic_state: &WrappedAccountCryptographicState,
62 current_user_key_id: &SymmetricKeySlotId,
63 new_user_key_id: &SymmetricKeySlotId,
64 ctx: &mut bitwarden_crypto::KeyStoreContext<KeySlotIds>,
65) -> Result<WrappedAccountCryptographicState, ()> {
66 debug!(
67 ?current_user_key_id,
68 ?new_user_key_id,
69 "Rotating account cryptographic state",
70 );
71
72 let rotated_account_cryptographic_state = WrappedAccountCryptographicState::rotate(
75 wrapped_account_cryptographic_state,
76 current_user_key_id,
77 new_user_key_id,
78 ctx,
79 )
80 .map_err(|_| ())?;
81
82 Ok(rotated_account_cryptographic_state)
83}
84
85#[cfg(test)]
86mod tests {
87 use bitwarden_core::key_management::account_cryptographic_state::WrappedAccountCryptographicState;
88 use bitwarden_crypto::{
89 CoseSerializable, KeyStore, PublicKey, PublicKeyEncryptionAlgorithm, SymmetricKeyAlgorithm,
90 };
91 use bitwarden_encoding::B64;
92
93 use super::*;
94
95 fn make_v1_wrapped_state(
98 ctx: &mut bitwarden_crypto::KeyStoreContext<KeySlotIds>,
99 ) -> (
100 SymmetricKeySlotId,
101 PublicKey,
102 WrappedAccountCryptographicState,
103 ) {
104 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
105 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
106 let wrapped_private_key = ctx.wrap_private_key(user_key, private_key).unwrap();
107
108 (
109 user_key,
110 ctx.get_public_key(private_key).unwrap(),
111 WrappedAccountCryptographicState::V1 {
112 private_key: wrapped_private_key,
113 },
114 )
115 }
116
117 fn assert_wrapped_request_model_fields(
118 model: &WrappedAccountCryptographicStateRequestModel,
119 actual_public_key: &B64,
120 ) {
121 let model_public_key = model
122 .public_key_encryption_key_pair
123 .public_key
124 .as_ref()
125 .expect("public_key should be present");
126 assert_eq!(
127 actual_public_key.to_string(),
128 *model_public_key,
129 "Public key should be correctly included in the model"
130 );
131
132 assert!(
134 model
135 .public_key_encryption_key_pair
136 .signed_public_key
137 .is_some(),
138 "signed_public_key should be present for V2 state"
139 );
140
141 assert!(
146 model.signature_key_pair.verifying_key.is_some(),
147 "verifying_key should be present"
148 );
149 assert!(
150 model.signature_key_pair.wrapped_signing_key.is_some(),
151 "wrapped_signing_key should be present"
152 );
153 assert!(
154 model.signature_key_pair.signature_algorithm.is_some(),
155 "signature_algorithm should be present"
156 );
157 assert!(
158 model.security_state.security_state.is_some(),
159 "security_state content should be present"
160 );
161 }
162
163 #[test]
164 fn test_rotate_v1_to_v2_returns_wrapped_request_model() {
165 let store: KeyStore<KeySlotIds> = KeyStore::default();
167 let mut ctx = store.context_mut();
168
169 let (old_user_key_id, public_key, wrapped_state) = make_v1_wrapped_state(&mut ctx);
171
172 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
174
175 let model = rotate_account_cryptographic_state_to_wrapped_model(
176 &wrapped_state,
177 &old_user_key_id,
178 &new_user_key_id,
179 &mut ctx,
180 )
181 .expect("rotate_account_cryptographic_state_to_wrapped_model should succeed");
182
183 let actual_public_key: B64 = public_key.to_der().unwrap().into();
184 assert_wrapped_request_model_fields(&model, &actual_public_key);
185 }
186
187 #[test]
188 fn test_rotate_v2_to_v2_returns_wrapped_request_model() {
189 let store: KeyStore<KeySlotIds> = KeyStore::default();
191 let mut ctx = store.context_mut();
192
193 let (old_user_key_id, wrapped_state) =
195 WrappedAccountCryptographicState::make(&mut ctx).unwrap();
196
197 let private_key_id = match &wrapped_state {
199 WrappedAccountCryptographicState::V2 { private_key, .. } => ctx
200 .unwrap_private_key(old_user_key_id, private_key)
201 .unwrap(),
202 _ => panic!("Expected V2 state"),
203 };
204 let signing_key_id = match &wrapped_state {
205 WrappedAccountCryptographicState::V2 { signing_key, .. } => ctx
206 .unwrap_signing_key(old_user_key_id, signing_key)
207 .unwrap(),
208 _ => panic!("Expected V2 state"),
209 };
210 let public_key = ctx.get_public_key(private_key_id).unwrap();
211 ctx.get_verifying_key(signing_key_id)
212 .expect("verifying key should be obtainable");
213
214 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
216
217 let model = rotate_account_cryptographic_state_to_wrapped_model(
218 &wrapped_state,
219 &old_user_key_id,
220 &new_user_key_id,
221 &mut ctx,
222 )
223 .expect("rotate_account_cryptographic_state_to_wrapped_model should succeed");
224
225 let actual_public_key: B64 = public_key.to_der().unwrap().into();
226 assert_wrapped_request_model_fields(&model, &actual_public_key);
227 }
228
229 #[test]
230 fn test_rotate_v1_to_v2_returns_account_keys_model() {
231 let store: KeyStore<KeySlotIds> = KeyStore::default();
233 let mut ctx = store.context_mut();
234
235 let (old_user_key_id, public_key, wrapped_state) = make_v1_wrapped_state(&mut ctx);
237
238 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
240
241 let model = rotate_account_cryptographic_state_to_request_model(
242 &wrapped_state,
243 &old_user_key_id,
244 &new_user_key_id,
245 &mut ctx,
246 )
247 .expect("rotate_account_cryptographic_state_to_request_model should succeed");
248
249 let actual_public_key: B64 = public_key.to_der().unwrap().into();
250 let public_key_encryption_key_pair = model
251 .public_key_encryption_key_pair
252 .as_ref()
253 .expect("public_key_encryption_key_pair should be present");
254 let model_public_key = public_key_encryption_key_pair
255 .public_key
256 .as_ref()
257 .expect("public_key should be present");
258 assert_eq!(
259 actual_public_key.to_string(),
260 *model_public_key,
261 "Public key should be correctly included in the model"
262 );
263
264 assert!(
266 public_key_encryption_key_pair.signed_public_key.is_some(),
267 "signed_public_key should be present for V2 state"
268 );
269
270 let signature_key_pair = model
277 .signature_key_pair
278 .as_ref()
279 .expect("signature_key_pair should be present for V2 state");
280 assert!(
281 signature_key_pair.verifying_key.is_some(),
282 "verifying_key should be present"
283 );
284 assert!(
285 signature_key_pair.wrapped_signing_key.is_some(),
286 "wrapped_signing_key should be present"
287 );
288 assert!(
289 signature_key_pair.signature_algorithm.is_some(),
290 "signature_algorithm should be present"
291 );
292
293 let security_state = model
295 .security_state
296 .as_ref()
297 .expect("security_state should be present for V2 state");
298 assert!(
299 security_state.security_state.is_some(),
300 "security_state content should be present"
301 );
302 }
303
304 #[test]
305 fn test_rotate_v2_to_v2_returns_account_keys_model() {
306 let store: KeyStore<KeySlotIds> = KeyStore::default();
308 let mut ctx = store.context_mut();
309
310 let (old_user_key_id, wrapped_state) =
312 WrappedAccountCryptographicState::make(&mut ctx).unwrap();
313
314 let private_key_id = match &wrapped_state {
316 WrappedAccountCryptographicState::V2 { private_key, .. } => ctx
317 .unwrap_private_key(old_user_key_id, private_key)
318 .unwrap(),
319 _ => panic!("Expected V2 state"),
320 };
321 let signing_key_id = match &wrapped_state {
322 WrappedAccountCryptographicState::V2 { signing_key, .. } => ctx
323 .unwrap_signing_key(old_user_key_id, signing_key)
324 .unwrap(),
325 _ => panic!("Expected V2 state"),
326 };
327 let public_key = ctx.get_public_key(private_key_id).unwrap();
328 let verifying_key = ctx
329 .get_verifying_key(signing_key_id)
330 .expect("verifying key should be obtainable");
331
332 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
334
335 let model = rotate_account_cryptographic_state_to_request_model(
336 &wrapped_state,
337 &old_user_key_id,
338 &new_user_key_id,
339 &mut ctx,
340 )
341 .expect("rotate_account_cryptographic_state_to_request_model should succeed");
342
343 let actual_public_key: B64 = public_key.to_der().unwrap().into();
344 let public_key_encryption_key_pair = model
345 .public_key_encryption_key_pair
346 .as_ref()
347 .expect("public_key_encryption_key_pair should be present");
348 let model_public_key = public_key_encryption_key_pair
349 .public_key
350 .as_ref()
351 .expect("public_key should be present");
352 assert_eq!(
353 actual_public_key.to_string(),
354 *model_public_key,
355 "Public key should be correctly included in the model"
356 );
357
358 assert!(
360 public_key_encryption_key_pair.signed_public_key.is_some(),
361 "signed_public_key should be present for V2 state"
362 );
363
364 let signature_key_pair = model
371 .signature_key_pair
372 .as_ref()
373 .expect("signature_key_pair should be present for V2 state");
374 let actual_verifying_key: B64 = verifying_key.to_cose().into();
375 let model_verifying_key = signature_key_pair
376 .verifying_key
377 .as_ref()
378 .expect("verifying_key should be present");
379 assert_eq!(
380 actual_verifying_key.to_string(),
381 *model_verifying_key,
382 "Verifying key should be correctly included in the model"
383 );
384
385 assert!(
386 signature_key_pair.wrapped_signing_key.is_some(),
387 "wrapped_signing_key should be present"
388 );
389 assert!(
390 signature_key_pair.signature_algorithm.is_some(),
391 "signature_algorithm should be present"
392 );
393
394 let security_state = model
396 .security_state
397 .as_ref()
398 .expect("security_state should be present for V2 state");
399 assert!(
400 security_state.security_state.is_some(),
401 "security_state content should be present"
402 );
403 }
404}