bitwarden_user_crypto_management/public_key_encryption_key_pair_regeneration/
regenerate.rs1use bitwarden_api_api::models::KeyRegenerationRequestModel;
2use bitwarden_core::key_management::{KeySlotIds, PrivateKeySlotId, SymmetricKeySlotId};
3use bitwarden_crypto::{KeyStore, PublicKeyEncryptionAlgorithm};
4use bitwarden_encoding::B64;
5use tracing::{error, info, instrument};
6
7use super::KeyPairRegenerationError;
8
9#[instrument(name = "regenerate_public_key_encryption_key_pair", skip_all, err)]
12pub(super) async fn internal_regenerate_public_key_encryption_key_pair(
13 key_store: &KeyStore<KeySlotIds>,
14 api_client: &bitwarden_api_api::apis::ApiClient,
15) -> Result<(), KeyPairRegenerationError> {
16 let (wrapped_private_key, public_key_b64) = {
17 let mut ctx = key_store.context_mut();
18
19 if !ctx
20 .is_v1_symmetric_key(SymmetricKeySlotId::User)
21 .map_err(|_| KeyPairRegenerationError::UserKeyNotAvailable)?
22 {
23 return Err(KeyPairRegenerationError::Crypto);
24 }
25
26 let new_private_key_id = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
27 let wrapped = ctx
28 .wrap_private_key(SymmetricKeySlotId::User, new_private_key_id)
29 .map_err(|_| KeyPairRegenerationError::Crypto)?;
30 let public_key = ctx
31 .get_public_key(new_private_key_id)
32 .map_err(|_| KeyPairRegenerationError::Crypto)?;
33 let public_key_b64 = B64::from(
34 public_key
35 .to_der()
36 .map_err(|_| KeyPairRegenerationError::Crypto)?,
37 )
38 .to_string();
39
40 (wrapped, public_key_b64)
41 };
42
43 info!("Posting regenerated public key encryption key pair to server");
44 let request = KeyRegenerationRequestModel {
45 user_public_key: Some(public_key_b64),
46 user_key_encrypted_user_private_key: Some(wrapped_private_key.to_string()),
47 };
48
49 api_client
50 .accounts_key_management_api()
51 .regenerate_keys(Some(request))
52 .await
53 .map_err(|e| {
54 error!("Failed to post regenerated keys to server: {e:?}");
55 KeyPairRegenerationError::Api
56 })?;
57
58 {
59 let mut ctx = key_store.context_mut();
60
61 let temp_private_key_id = ctx
62 .unwrap_private_key(SymmetricKeySlotId::User, &wrapped_private_key)
63 .map_err(|_| KeyPairRegenerationError::Crypto)?;
64 ctx.persist_private_key(temp_private_key_id, PrivateKeySlotId::UserPrivateKey)
65 .map_err(|_| KeyPairRegenerationError::Crypto)?;
66 }
67
68 info!("Successfully regenerated user public key encryption key pair");
69 Ok(())
70}
71
72#[cfg(test)]
73mod tests {
74 use bitwarden_api_api::{apis::ApiClient, models::KeyRegenerationRequestModel};
75 use bitwarden_core::{
76 Client,
77 key_management::{KeySlotIds, PrivateKeySlotId, SymmetricKeySlotId},
78 };
79 use bitwarden_crypto::{
80 EncString, KeyStore, PublicKeyEncryptionAlgorithm, SymmetricKeyAlgorithm,
81 };
82
83 use super::*;
84 use crate::UserCryptoManagementClient;
85
86 fn unlocked_v1_client() -> (UserCryptoManagementClient, KeyStore<KeySlotIds>) {
87 let client = Client::new(None);
88 {
89 let key_store = client.internal.get_key_store();
90 let mut ctx = key_store.context_mut();
91 let local_user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
92 let _ = ctx.persist_symmetric_key(local_user_key, SymmetricKeySlotId::User);
93 }
94 let key_store = client.internal.get_key_store().clone();
95 (UserCryptoManagementClient::new(client), key_store)
96 }
97
98 fn unlocked_v1_key_store() -> KeyStore<KeySlotIds> {
99 let store: KeyStore<KeySlotIds> = KeyStore::default();
100 {
101 let mut ctx = store.context_mut();
102 let local_user_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
103 let _ = ctx.persist_symmetric_key(local_user_key, SymmetricKeySlotId::User);
104 }
105 store
106 }
107
108 fn make_valid_key_pair(key_store: &KeyStore<KeySlotIds>) -> (String, String) {
109 let mut ctx = key_store.context_mut();
110 let private_key_id = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
111 let wrapped = ctx
112 .wrap_private_key(SymmetricKeySlotId::User, private_key_id)
113 .unwrap();
114 let public_key = ctx.get_public_key(private_key_id).unwrap();
115 let public_key_b64 = B64::from(public_key.to_der().unwrap()).to_string();
116 (wrapped.to_string(), public_key_b64)
117 }
118
119 fn keys_response(
120 public_key: Option<String>,
121 private_key: Option<String>,
122 ) -> bitwarden_api_api::models::KeysResponseModel {
123 bitwarden_api_api::models::KeysResponseModel {
124 object: None,
125 key: None,
126 public_key,
127 private_key,
128 account_keys: None,
129 }
130 }
131
132 #[tokio::test]
135 async fn test_regenerate_success() {
136 let (_, key_store) = unlocked_v1_client();
137
138 let api_client = ApiClient::new_mocked(|mock| {
139 mock.accounts_key_management_api
140 .expect_regenerate_keys()
141 .once()
142 .returning(|body: Option<KeyRegenerationRequestModel>| {
143 let body = body.expect("body should be Some");
144 assert!(
145 body.user_public_key.is_some(),
146 "user_public_key should be present"
147 );
148 let wrapped_key = body
149 .user_key_encrypted_user_private_key
150 .expect("user_key_encrypted_user_private_key should be present");
151 wrapped_key
152 .parse::<EncString>()
153 .expect("should be a valid EncString");
154 Ok(())
155 });
156 });
157
158 internal_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
159 .await
160 .expect("regeneration should succeed");
161
162 let ctx = key_store.context();
163 assert!(
164 ctx.has_private_key(PrivateKeySlotId::UserPrivateKey),
165 "UserPrivateKey should be set after regeneration"
166 );
167
168 if let ApiClient::Mock(mut mock) = api_client {
169 mock.accounts_key_management_api.checkpoint();
170 }
171 }
172
173 #[tokio::test]
174 async fn test_regenerate_api_failure() {
175 let (_, key_store) = unlocked_v1_client();
176
177 let api_client = ApiClient::new_mocked(|mock| {
178 mock.accounts_key_management_api
179 .expect_regenerate_keys()
180 .once()
181 .returning(|_body| {
182 Err(bitwarden_api_api::apis::Error::Serde(
183 serde_json::Error::io(std::io::Error::other("API error")),
184 ))
185 });
186 });
187
188 let result =
189 internal_regenerate_public_key_encryption_key_pair(&key_store, &api_client).await;
190 assert!(matches!(result, Err(KeyPairRegenerationError::Api)));
191
192 {
193 let ctx = key_store.context();
194 assert!(
195 !ctx.has_private_key(PrivateKeySlotId::UserPrivateKey),
196 "UserPrivateKey should NOT be set after API failure"
197 );
198 }
199
200 if let ApiClient::Mock(mut mock) = api_client {
201 mock.accounts_key_management_api.checkpoint();
202 }
203 }
204
205 #[tokio::test]
206 async fn test_regenerate_no_user_key() {
207 let key_store: KeyStore<KeySlotIds> = KeyStore::default();
208
209 let api_client = ApiClient::new_mocked(|mock| {
210 mock.accounts_key_management_api
211 .expect_regenerate_keys()
212 .never();
213 });
214
215 let result =
216 internal_regenerate_public_key_encryption_key_pair(&key_store, &api_client).await;
217 assert!(matches!(
218 result,
219 Err(KeyPairRegenerationError::UserKeyNotAvailable)
220 ));
221
222 if let ApiClient::Mock(mut mock) = api_client {
223 mock.accounts_key_management_api.checkpoint();
224 }
225 }
226
227 #[tokio::test]
228 async fn test_regenerate_v2_user_key() {
229 let key_store: KeyStore<KeySlotIds> = KeyStore::default();
230 {
231 let mut ctx = key_store.context_mut();
232 let key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
233 let _ = ctx.persist_symmetric_key(key, SymmetricKeySlotId::User);
234 }
235
236 let api_client = ApiClient::new_mocked(|mock| {
237 mock.accounts_key_management_api
238 .expect_regenerate_keys()
239 .never();
240 });
241
242 let result =
243 internal_regenerate_public_key_encryption_key_pair(&key_store, &api_client).await;
244 assert!(matches!(result, Err(KeyPairRegenerationError::Crypto)));
245
246 if let ApiClient::Mock(mut mock) = api_client {
247 mock.accounts_key_management_api.checkpoint();
248 }
249 }
250
251 #[tokio::test]
254 async fn test_regenerate_if_needed_no_regeneration() {
255 let key_store = unlocked_v1_key_store();
256 let (wrapped_private_key, public_key_b64) = make_valid_key_pair(&key_store);
257
258 let api_client = ApiClient::new_mocked(|mock| {
259 mock.accounts_api
260 .expect_get_keys()
261 .once()
262 .returning(move || {
263 Ok(keys_response(
264 Some(public_key_b64.clone()),
265 Some(wrapped_private_key.clone()),
266 ))
267 });
268 mock.accounts_key_management_api
269 .expect_regenerate_keys()
270 .never();
271 });
272
273 let should = crate::public_key_encryption_key_pair_regeneration::should_regenerate::internal_should_regenerate_public_key_encryption_key_pair_with_ciphers(
274 &key_store, &api_client, &[],
275 )
276 .await
277 .unwrap();
278 assert!(!should);
279
280 if let ApiClient::Mock(mut mock) = api_client {
281 mock.accounts_api.checkpoint();
282 mock.accounts_key_management_api.checkpoint();
283 }
284 }
285
286 #[tokio::test]
287 async fn test_regenerate_if_needed_performs_regeneration() {
288 let key_store = unlocked_v1_key_store();
289
290 let api_client = ApiClient::new_mocked(|mock| {
291 mock.accounts_api
292 .expect_get_keys()
293 .once()
294 .returning(|| Ok(keys_response(None, None)));
295 mock.accounts_key_management_api
296 .expect_regenerate_keys()
297 .once()
298 .returning(|_body| Ok(()));
299 });
300
301 let should = crate::public_key_encryption_key_pair_regeneration::should_regenerate::internal_should_regenerate_public_key_encryption_key_pair_with_ciphers(
302 &key_store, &api_client, &[],
303 )
304 .await
305 .unwrap();
306 assert!(should);
307
308 internal_regenerate_public_key_encryption_key_pair(&key_store, &api_client)
309 .await
310 .unwrap();
311
312 if let ApiClient::Mock(mut mock) = api_client {
313 mock.accounts_api.checkpoint();
314 mock.accounts_key_management_api.checkpoint();
315 }
316 }
317}