bitwarden_user_crypto_management/key_rotation/
crypto.rs1use bitwarden_core::{
3 UserId,
4 key_management::{
5 KeyIds, SymmetricKeyId, account_cryptographic_state::WrappedAccountCryptographicState,
6 },
7};
8use tracing::debug;
9
10pub(super) fn rotate_account_cryptographic_state(
14 wrapped_account_cryptographic_state: &WrappedAccountCryptographicState,
15 current_user_key_id: &SymmetricKeyId,
16 new_user_key_id: &SymmetricKeyId,
17 user_id: UserId,
18 ctx: &mut bitwarden_crypto::KeyStoreContext<KeyIds>,
19) -> Result<bitwarden_api_api::models::AccountKeysRequestModel, ()> {
20 debug!(
21 %user_id, ?current_user_key_id, ?new_user_key_id,
22 "Rotating account cryptographic state",
23 );
24
25 let rotated_account_cryptographic_state = WrappedAccountCryptographicState::rotate(
28 wrapped_account_cryptographic_state,
29 current_user_key_id,
30 new_user_key_id,
31 user_id,
32 ctx,
33 )
34 .map_err(|_| ())?;
35
36 debug!(
37 %user_id,
38 "Converting rotated account cryptographic state to request model",
39 );
40
41 let account_keys_model = rotated_account_cryptographic_state
43 .to_request_model(new_user_key_id, ctx)
44 .map_err(|_| ())?;
45 Ok(account_keys_model)
46}
47
48#[cfg(test)]
49mod tests {
50 use bitwarden_core::key_management::account_cryptographic_state::WrappedAccountCryptographicState;
51 use bitwarden_crypto::{
52 CoseSerializable, KeyStore, PublicKey, PublicKeyEncryptionAlgorithm, SymmetricKeyAlgorithm,
53 };
54 use bitwarden_encoding::B64;
55
56 use super::*;
57
58 fn make_v1_wrapped_state(
61 ctx: &mut bitwarden_crypto::KeyStoreContext<KeyIds>,
62 ) -> (SymmetricKeyId, PublicKey, WrappedAccountCryptographicState) {
63 let user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
64 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
65 let wrapped_private_key = ctx.wrap_private_key(user_key, private_key).unwrap();
66
67 (
68 user_key,
69 ctx.get_public_key(private_key).unwrap(),
70 WrappedAccountCryptographicState::V1 {
71 private_key: wrapped_private_key,
72 },
73 )
74 }
75
76 #[test]
77 fn test_rotate_v1_to_v2_returns_account_keys_model() {
78 let store: KeyStore<KeyIds> = KeyStore::default();
80 let mut ctx = store.context_mut();
81
82 let user_id = UserId::new_v4();
84 let (old_user_key_id, public_key, wrapped_state) = make_v1_wrapped_state(&mut ctx);
85
86 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
88
89 let model = rotate_account_cryptographic_state(
90 &wrapped_state,
91 &old_user_key_id,
92 &new_user_key_id,
93 user_id,
94 &mut ctx,
95 )
96 .expect("rotate_account_cryptographic_state should succeed");
97
98 let actual_public_key: B64 = public_key.to_der().unwrap().into();
99 let public_key_encryption_key_pair = model
100 .public_key_encryption_key_pair
101 .as_ref()
102 .expect("public_key_encryption_key_pair should be present");
103 let model_public_key = public_key_encryption_key_pair
104 .public_key
105 .as_ref()
106 .expect("public_key should be present");
107 assert_eq!(
108 actual_public_key.to_string(),
109 *model_public_key,
110 "Public key should be correctly included in the model"
111 );
112
113 assert!(
115 public_key_encryption_key_pair.signed_public_key.is_some(),
116 "signed_public_key should be present for V2 state"
117 );
118
119 let signature_key_pair = model
126 .signature_key_pair
127 .as_ref()
128 .expect("signature_key_pair should be present for V2 state");
129 assert!(
130 signature_key_pair.verifying_key.is_some(),
131 "verifying_key should be present"
132 );
133 assert!(
134 signature_key_pair.wrapped_signing_key.is_some(),
135 "wrapped_signing_key should be present"
136 );
137 assert!(
138 signature_key_pair.signature_algorithm.is_some(),
139 "signature_algorithm should be present"
140 );
141
142 let security_state = model
144 .security_state
145 .as_ref()
146 .expect("security_state should be present for V2 state");
147 assert!(
148 security_state.security_state.is_some(),
149 "security_state content should be present"
150 );
151 }
152
153 #[test]
154 fn test_rotate_v2_to_v2_returns_account_keys_model() {
155 let store: KeyStore<KeyIds> = KeyStore::default();
157 let mut ctx = store.context_mut();
158
159 let user_id = UserId::new_v4();
161 let (old_user_key_id, wrapped_state) =
162 WrappedAccountCryptographicState::make(&mut ctx, user_id).unwrap();
163
164 let private_key_id = match &wrapped_state {
166 WrappedAccountCryptographicState::V2 { private_key, .. } => ctx
167 .unwrap_private_key(old_user_key_id, private_key)
168 .unwrap(),
169 _ => panic!("Expected V2 state"),
170 };
171 let signing_key_id = match &wrapped_state {
172 WrappedAccountCryptographicState::V2 { signing_key, .. } => ctx
173 .unwrap_signing_key(old_user_key_id, signing_key)
174 .unwrap(),
175 _ => panic!("Expected V2 state"),
176 };
177 let public_key = ctx.get_public_key(private_key_id).unwrap();
178 let verifying_key = ctx
179 .get_verifying_key(signing_key_id)
180 .expect("verifying key should be obtainable");
181
182 let new_user_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
184
185 let model = rotate_account_cryptographic_state(
186 &wrapped_state,
187 &old_user_key_id,
188 &new_user_key_id,
189 user_id,
190 &mut ctx,
191 )
192 .expect("rotate_account_cryptographic_state should succeed");
193
194 let actual_public_key: B64 = public_key.to_der().unwrap().into();
195 let public_key_encryption_key_pair = model
196 .public_key_encryption_key_pair
197 .as_ref()
198 .expect("public_key_encryption_key_pair should be present");
199 let model_public_key = public_key_encryption_key_pair
200 .public_key
201 .as_ref()
202 .expect("public_key should be present");
203 assert_eq!(
204 actual_public_key.to_string(),
205 *model_public_key,
206 "Public key should be correctly included in the model"
207 );
208
209 assert!(
211 public_key_encryption_key_pair.signed_public_key.is_some(),
212 "signed_public_key should be present for V2 state"
213 );
214
215 let signature_key_pair = model
222 .signature_key_pair
223 .as_ref()
224 .expect("signature_key_pair should be present for V2 state");
225 let actual_verifying_key: B64 = verifying_key.to_cose().into();
226 let model_verifying_key = signature_key_pair
227 .verifying_key
228 .as_ref()
229 .expect("verifying_key should be present");
230 assert_eq!(
231 actual_verifying_key.to_string(),
232 *model_verifying_key,
233 "Verifying key should be correctly included in the model"
234 );
235
236 assert!(
237 signature_key_pair.wrapped_signing_key.is_some(),
238 "wrapped_signing_key should be present"
239 );
240 assert!(
241 signature_key_pair.signature_algorithm.is_some(),
242 "signature_algorithm should be present"
243 );
244
245 let security_state = model
247 .security_state
248 .as_ref()
249 .expect("security_state should be present for V2 state");
250 assert!(
251 security_state.security_state.is_some(),
252 "security_state content should be present"
253 );
254 }
255}