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