bitwarden_user_crypto_management/
key_connector_migration.rs1use bitwarden_api_api::models::KeyConnectorEnrollmentRequestModel;
4use bitwarden_api_key_connector::models::user_key_request_model::UserKeyKeyRequestModel;
5use bitwarden_core::key_management::SymmetricKeyId;
6use bitwarden_crypto::{EncString, KeyConnectorKey};
7use bitwarden_encoding::B64;
8use bitwarden_error::bitwarden_error;
9use thiserror::Error;
10use tracing::{error, info};
11#[cfg(feature = "wasm")]
12use wasm_bindgen::prelude::*;
13
14use crate::UserCryptoManagementClient;
15
16#[cfg_attr(feature = "wasm", wasm_bindgen)]
17impl UserCryptoManagementClient {
18 pub async fn migrate_to_key_connector(
22 &self,
23 key_connector_url: String,
24 ) -> Result<(), MigrateToKeyConnectorError> {
25 let internal = &self.client.internal;
26 let api_configuration = internal.get_api_configurations();
27 let key_connector_api_client = internal.get_key_connector_client(key_connector_url);
28
29 internal_migrate_to_key_connector(
30 self,
31 &api_configuration.api_client,
32 &key_connector_api_client,
33 )
34 .await
35 }
36}
37
38async fn internal_migrate_to_key_connector(
39 user_crypto_management_client: &UserCryptoManagementClient,
40 api_client: &bitwarden_api_api::apis::ApiClient,
41 key_connector_api_client: &bitwarden_api_key_connector::apis::ApiClient,
42) -> Result<(), MigrateToKeyConnectorError> {
43 let key_connector_key = KeyConnectorKey::make();
55
56 let key_connector_key_wrapped_user_key = {
58 let key_store = user_crypto_management_client
59 .client
60 .internal
61 .get_key_store();
62 let ctx = key_store.context();
63 key_connector_key
64 .wrap_user_key(SymmetricKeyId::User, &ctx)
65 .map_err(|_| MigrateToKeyConnectorError::UserKeyNotAvailable)?
66 };
67
68 info!("Posting key connector key to key connector server");
70 post_key_connector_key_to_key_connector(key_connector_api_client, key_connector_key).await?;
71
72 info!("Posting wrapped user key for key connector migration");
74 enroll_user_into_key_connector(api_client, key_connector_key_wrapped_user_key).await?;
75
76 info!("Successfully migrated account to key connector unlock");
77 Ok(())
78}
79
80async fn enroll_user_into_key_connector(
81 api_client: &bitwarden_api_api::apis::ApiClient,
82 key_connector_key_wrapped_user_key: EncString,
83) -> Result<(), MigrateToKeyConnectorError> {
84 let request = KeyConnectorEnrollmentRequestModel {
85 key_connector_key_wrapped_user_key: Some(key_connector_key_wrapped_user_key.to_string()),
86 };
87
88 api_client
89 .accounts_key_management_api()
90 .post_enroll_to_key_connector(Some(request))
91 .await
92 .map_err(|e| {
93 error!("Failed to post key connector migration request: {e:?}");
94 MigrateToKeyConnectorError::ApiError
95 })
96}
97
98async fn post_key_connector_key_to_key_connector(
99 key_connector_api_client: &bitwarden_api_key_connector::apis::ApiClient,
100 key_connector_key: KeyConnectorKey,
101) -> Result<(), MigrateToKeyConnectorError> {
102 let encoded_key_connector_key: B64 = key_connector_key.into();
103 let request = UserKeyKeyRequestModel {
104 key: encoded_key_connector_key.to_string(),
105 };
106
107 let result = if key_connector_api_client
110 .user_keys_api()
111 .get_user_key()
112 .await
113 .is_ok()
114 {
115 info!("User's key connector key exists, updating");
116 key_connector_api_client
117 .user_keys_api()
118 .put_user_key(request)
119 .await
120 } else {
121 info!("User's key connector key does not exist, creating");
122 key_connector_api_client
123 .user_keys_api()
124 .post_user_key(request)
125 .await
126 };
127
128 result.map_err(|e| {
129 error!("Failed to post key connector key to key connector server: {e:?}");
130 MigrateToKeyConnectorError::KeyConnectorApiError
131 })
132}
133
134#[derive(Debug, Error)]
135#[bitwarden_error(flat)]
136pub enum MigrateToKeyConnectorError {
137 #[error("Current user key is not available")]
138 UserKeyNotAvailable,
139 #[error("Cryptographic error during key connector migration")]
140 CryptoError,
141 #[error("Bitwarden API call failed during key connector migration")]
142 ApiError,
143 #[error("Key Connector API call failed during key connector migration")]
144 KeyConnectorApiError,
145}
146
147#[cfg(test)]
148mod tests {
149 use bitwarden_api_api::apis::ApiClient;
150 use bitwarden_core::Client;
151 use bitwarden_crypto::EncString;
152
153 use super::*;
154
155 fn unlocked_client() -> UserCryptoManagementClient {
156 let client = Client::new(None);
157 {
158 let key_store = client.internal.get_key_store();
159 let mut ctx = key_store.context_mut();
160 let local_user_key =
161 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::Aes256CbcHmac);
162 let _ = ctx.persist_symmetric_key(local_user_key, SymmetricKeyId::User);
163 }
164
165 UserCryptoManagementClient::new(client)
166 }
167
168 #[tokio::test]
169 async fn test_migrate_to_key_connector_success() {
170 let user_crypto_management_client = unlocked_client();
171
172 let api_client = ApiClient::new_mocked(|mock| {
173 mock.accounts_key_management_api
174 .expect_post_enroll_to_key_connector()
175 .once()
176 .returning(move |body| {
177 let body = body.expect("body should be Some");
178 let wrapped_key = body
179 .key_connector_key_wrapped_user_key
180 .expect("key_connector_key_wrapped_user_key should be Some");
181 wrapped_key
182 .parse::<EncString>()
183 .expect("key_connector_key_wrapped_user_key should be a valid EncString");
184 Ok(())
185 });
186 });
187
188 let key_connector_api_client =
189 bitwarden_api_key_connector::apis::ApiClient::new_mocked(|mock| {
190 mock.user_keys_api
191 .expect_get_user_key()
192 .once()
193 .returning(move || {
194 Err(bitwarden_api_key_connector::apis::Error::ResponseError(
195 bitwarden_api_key_connector::apis::ResponseContent {
196 status: reqwest::StatusCode::NOT_FOUND,
197 content: "Not Found".to_string(),
198 },
199 ))
200 });
201 mock.user_keys_api
202 .expect_post_user_key()
203 .once()
204 .returning(move |_body| Ok(()));
205 });
206
207 let result = internal_migrate_to_key_connector(
208 &user_crypto_management_client,
209 &api_client,
210 &key_connector_api_client,
211 )
212 .await;
213
214 assert!(result.is_ok());
215
216 if let ApiClient::Mock(mut mock) = api_client {
217 mock.accounts_key_management_api.checkpoint();
218 }
219 if let bitwarden_api_key_connector::apis::ApiClient::Mock(mut mock) =
220 key_connector_api_client
221 {
222 mock.user_keys_api.checkpoint();
223 }
224 }
225
226 #[tokio::test]
227 async fn test_migrate_to_key_connector_key_connector_api_failure() {
228 let user_crypto_management_client = unlocked_client();
229
230 let api_client = ApiClient::new_mocked(|mock| {
231 mock.accounts_key_management_api
232 .expect_post_enroll_to_key_connector()
233 .never();
234 });
235
236 let key_connector_api_client =
237 bitwarden_api_key_connector::apis::ApiClient::new_mocked(|mock| {
238 mock.user_keys_api
239 .expect_get_user_key()
240 .once()
241 .returning(move || {
242 Err(bitwarden_api_key_connector::apis::Error::ResponseError(
243 bitwarden_api_key_connector::apis::ResponseContent {
244 status: reqwest::StatusCode::NOT_FOUND,
245 content: "Not Found".to_string(),
246 },
247 ))
248 });
249 mock.user_keys_api
250 .expect_post_user_key()
251 .once()
252 .returning(move |_body| {
253 Err(bitwarden_api_key_connector::apis::Error::Serde(
254 serde_json::Error::io(std::io::Error::other("API error")),
255 ))
256 });
257 });
258
259 let result = internal_migrate_to_key_connector(
260 &user_crypto_management_client,
261 &api_client,
262 &key_connector_api_client,
263 )
264 .await;
265
266 assert!(matches!(
267 result,
268 Err(MigrateToKeyConnectorError::KeyConnectorApiError)
269 ));
270
271 if let ApiClient::Mock(mut mock) = api_client {
272 mock.accounts_key_management_api.checkpoint();
273 }
274 if let bitwarden_api_key_connector::apis::ApiClient::Mock(mut mock) =
275 key_connector_api_client
276 {
277 mock.user_keys_api.checkpoint();
278 }
279 }
280
281 #[tokio::test]
282 async fn test_migrate_to_key_connector_api_failure() {
283 let user_crypto_management_client = unlocked_client();
284
285 let api_client = ApiClient::new_mocked(|mock| {
286 mock.accounts_key_management_api
287 .expect_post_enroll_to_key_connector()
288 .once()
289 .returning(move |body| {
290 let body = body.expect("body should be Some");
291 let wrapped_key = body
292 .key_connector_key_wrapped_user_key
293 .expect("key_connector_key_wrapped_user_key should be Some");
294 wrapped_key
295 .parse::<EncString>()
296 .expect("key_connector_key_wrapped_user_key should be a valid EncString");
297 Err(bitwarden_api_api::apis::Error::Serde(
298 serde_json::Error::io(std::io::Error::other("API error")),
299 ))
300 });
301 });
302
303 let key_connector_api_client =
304 bitwarden_api_key_connector::apis::ApiClient::new_mocked(|mock| {
305 mock.user_keys_api
306 .expect_get_user_key()
307 .once()
308 .returning(move || {
309 Err(bitwarden_api_key_connector::apis::Error::ResponseError(
310 bitwarden_api_key_connector::apis::ResponseContent {
311 status: reqwest::StatusCode::NOT_FOUND,
312 content: "Not Found".to_string(),
313 },
314 ))
315 });
316 mock.user_keys_api
317 .expect_post_user_key()
318 .once()
319 .returning(move |_body| Ok(()));
320 });
321
322 let result = internal_migrate_to_key_connector(
323 &user_crypto_management_client,
324 &api_client,
325 &key_connector_api_client,
326 )
327 .await;
328
329 assert!(matches!(result, Err(MigrateToKeyConnectorError::ApiError)));
330
331 if let ApiClient::Mock(mut mock) = api_client {
332 mock.accounts_key_management_api.checkpoint();
333 }
334 if let bitwarden_api_key_connector::apis::ApiClient::Mock(mut mock) =
335 key_connector_api_client
336 {
337 mock.user_keys_api.checkpoint();
338 }
339 }
340
341 #[tokio::test]
342 async fn test_migrate_to_key_connector_user_key_not_available() {
343 let user_crypto_management_client = UserCryptoManagementClient::new(Client::new(None));
344
345 let api_client = ApiClient::new_mocked(|mock| {
346 mock.accounts_key_management_api
347 .expect_post_enroll_to_key_connector()
348 .never();
349 });
350
351 let key_connector_api_client =
352 bitwarden_api_key_connector::apis::ApiClient::new_mocked(|mock| {
353 mock.user_keys_api.expect_get_user_key().never();
354 mock.user_keys_api.expect_post_user_key().never();
355 mock.user_keys_api.expect_put_user_key().never();
356 });
357
358 let result = internal_migrate_to_key_connector(
359 &user_crypto_management_client,
360 &api_client,
361 &key_connector_api_client,
362 )
363 .await;
364
365 assert!(matches!(
366 result,
367 Err(MigrateToKeyConnectorError::UserKeyNotAvailable)
368 ));
369
370 if let ApiClient::Mock(mut mock) = api_client {
371 mock.accounts_key_management_api.checkpoint();
372 }
373 if let bitwarden_api_key_connector::apis::ApiClient::Mock(mut mock) =
374 key_connector_api_client
375 {
376 mock.user_keys_api.checkpoint();
377 }
378 }
379}