bitwarden_user_crypto_management/
key_id_backfill.rs1use bitwarden_api_api::models::SetUserKeyIdRequestModel;
9use bitwarden_core::key_management::SymmetricKeySlotId;
10use bitwarden_crypto::KeyId;
11use bitwarden_error::bitwarden_error;
12use thiserror::Error;
13use tracing::{error, info};
14#[cfg(feature = "wasm")]
15use wasm_bindgen::prelude::*;
16
17use crate::UserCryptoManagementClient;
18
19#[derive(Debug, Error)]
21#[bitwarden_error(flat)]
22pub enum KeyIdBackfillError {
23 #[error("User key is not available in key store")]
25 UserKeyNotAvailable,
26 #[error("The current user key has no key id to backfill")]
28 NoKeyId,
29 #[error("No state bridge registered, the user key id backfill is not supported")]
31 StateBridgeNotRegistered,
32 #[error("API call failed during user key id backfill")]
34 Api,
35}
36
37#[cfg_attr(feature = "wasm", wasm_bindgen)]
38#[cfg_attr(feature = "uniffi", uniffi::export(async_runtime = "tokio"))]
39impl UserCryptoManagementClient {
40 pub async fn user_key_id_needs_backfill(&self) -> Result<bool, KeyIdBackfillError> {
42 let state_bridge = self.client.km_state_bridge();
43 if !state_bridge.is_bridge_registered() {
44 return Err(KeyIdBackfillError::StateBridgeNotRegistered);
45 }
46
47 if let Some(recorded) = state_bridge.get_user_key_id().await {
50 info!(?recorded, "Server already knows the user key id");
51 return Ok(false);
52 }
53
54 Ok(self.current_user_key_id()?.is_some())
55 }
56
57 pub async fn user_key_id_backfill(&self) -> Result<(), KeyIdBackfillError> {
65 let state_bridge = self.client.km_state_bridge();
66 if !state_bridge.is_bridge_registered() {
67 return Err(KeyIdBackfillError::StateBridgeNotRegistered);
68 }
69
70 let user_key_id = self
71 .current_user_key_id()?
72 .ok_or(KeyIdBackfillError::NoKeyId)?;
73
74 info!("Recording the user key id with the server");
75 self.client
76 .internal
77 .get_api_configurations()
78 .api_client
79 .accounts_key_management_api()
80 .post_user_key_id(Some(SetUserKeyIdRequestModel {
81 user_key_id: user_key_id.to_string(),
82 }))
83 .await
84 .map_err(|e| {
85 error!("Failed to post the user key id: {e:?}");
86 KeyIdBackfillError::Api
87 })?;
88
89 state_bridge.set_user_key_id(&user_key_id).await;
91
92 Ok(())
93 }
94}
95
96impl UserCryptoManagementClient {
97 fn current_user_key_id(&self) -> Result<Option<KeyId>, KeyIdBackfillError> {
99 let key_store = self.client.internal.get_key_store();
100 let ctx = key_store.context();
101
102 if !ctx.has_symmetric_key(SymmetricKeySlotId::User) {
103 return Err(KeyIdBackfillError::UserKeyNotAvailable);
104 }
105
106 Ok(ctx.get_symmetric_key_id(SymmetricKeySlotId::User))
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use std::sync::Arc;
113
114 use bitwarden_api_api::apis::ApiClient;
115 use bitwarden_core::{
116 Client, client::internal::ApiConfigurations,
117 key_management::state_bridge::test_support::InMemoryStateBridge,
118 };
119 use bitwarden_crypto::SymmetricKeyAlgorithm;
120
121 use super::*;
122 use crate::UserCryptoManagementClientExt;
123
124 fn client_with_user_key(algorithm: SymmetricKeyAlgorithm, api_client: ApiClient) -> Client {
127 let client = Client::builder()
128 .with_api_configurations(Arc::new(ApiConfigurations::from_api_client(api_client)))
129 .build();
130 client
131 .km_state_bridge()
132 .register_bridge(Box::new(InMemoryStateBridge::default()));
133 {
134 let key_store = client.internal.get_key_store();
135 let mut ctx = key_store.context_mut();
136 let local = ctx.make_symmetric_key(algorithm);
137 ctx.persist_symmetric_key(local, SymmetricKeySlotId::User)
138 .unwrap();
139 }
140 client
141 }
142
143 fn no_api_calls() -> ApiClient {
145 ApiClient::new_mocked(|mock| {
146 mock.accounts_key_management_api
147 .expect_post_user_key_id()
148 .never();
149 })
150 }
151
152 fn user_key_id(client: &Client) -> KeyId {
153 client
154 .internal
155 .get_key_store()
156 .context()
157 .get_symmetric_key_id(SymmetricKeySlotId::User)
158 .expect("a user key has a key id")
159 }
160
161 #[tokio::test]
162 async fn test_needs_backfill_when_server_has_no_key_id() {
163 let client = client_with_user_key(SymmetricKeyAlgorithm::XAes256Gcm, no_api_calls());
164
165 assert!(
166 client
167 .user_crypto_management()
168 .user_key_id_needs_backfill()
169 .await
170 .unwrap()
171 );
172 }
173
174 #[tokio::test]
175 async fn test_no_backfill_when_server_already_knows_the_key_id() {
176 let client = client_with_user_key(SymmetricKeyAlgorithm::XAes256Gcm, no_api_calls());
177 let key_id = user_key_id(&client);
178 client.km_state_bridge().set_user_key_id(&key_id).await;
179
180 assert!(
181 !client
182 .user_crypto_management()
183 .user_key_id_needs_backfill()
184 .await
185 .unwrap()
186 );
187 }
188
189 #[tokio::test]
190 async fn test_needs_backfill_for_a_v1_user_key() {
191 let client = client_with_user_key(SymmetricKeyAlgorithm::Aes256CbcHmac, no_api_calls());
193
194 assert!(
195 client
196 .user_crypto_management()
197 .user_key_id_needs_backfill()
198 .await
199 .unwrap()
200 );
201 }
202
203 #[tokio::test]
204 async fn test_needs_backfill_without_a_user_key_errors() {
205 let client = Client::new(None);
206 client
207 .km_state_bridge()
208 .register_bridge(Box::new(InMemoryStateBridge::default()));
209
210 assert!(matches!(
211 client
212 .user_crypto_management()
213 .user_key_id_needs_backfill()
214 .await,
215 Err(KeyIdBackfillError::UserKeyNotAvailable)
216 ));
217 }
218
219 #[tokio::test]
220 async fn test_needs_backfill_without_a_state_bridge_errors() {
221 let client = Client::new(None);
222
223 assert!(matches!(
224 client
225 .user_crypto_management()
226 .user_key_id_needs_backfill()
227 .await,
228 Err(KeyIdBackfillError::StateBridgeNotRegistered)
229 ));
230 }
231
232 async fn assert_backfill_posts_and_stores(algorithm: SymmetricKeyAlgorithm) {
235 let posted = Arc::new(std::sync::Mutex::new(None));
237 let recorder = posted.clone();
238 let api_client = ApiClient::new_mocked(|mock| {
239 mock.accounts_key_management_api
240 .expect_post_user_key_id()
241 .once()
242 .returning(move |body| {
243 let body = body.expect("body should be Some");
244 *recorder.lock().unwrap() = Some(body.user_key_id);
245 Ok(())
246 });
247 });
248 let client = client_with_user_key(algorithm, api_client);
249 let expected = user_key_id(&client).to_string();
250
251 client
252 .user_crypto_management()
253 .user_key_id_backfill()
254 .await
255 .unwrap();
256
257 assert_eq!(posted.lock().unwrap().as_deref(), Some(expected.as_str()));
258 let stored = client.km_state_bridge().get_user_key_id().await.unwrap();
259 assert_eq!(stored.to_string(), expected);
260 }
261
262 #[tokio::test]
263 async fn test_backfill_posts_the_key_id_and_stores_it() {
264 assert_backfill_posts_and_stores(SymmetricKeyAlgorithm::XAes256Gcm).await;
265 }
266
267 #[tokio::test]
268 async fn test_backfill_posts_the_key_id_and_stores_it_for_a_v1_user_key() {
269 assert_backfill_posts_and_stores(SymmetricKeyAlgorithm::Aes256CbcHmac).await;
270 }
271
272 #[tokio::test]
273 async fn test_backfill_leaves_state_untouched_when_the_api_fails() {
274 let api_client = ApiClient::new_mocked(|mock| {
275 mock.accounts_key_management_api
276 .expect_post_user_key_id()
277 .once()
278 .returning(|_| {
279 Err(bitwarden_api_api::apis::Error::Response(
280 bitwarden_api_api::apis::ResponseContent {
281 status: reqwest::StatusCode::BAD_REQUEST,
282 message: "Bad Request".to_string(),
283 },
284 ))
285 });
286 });
287 let client = client_with_user_key(SymmetricKeyAlgorithm::XAes256Gcm, api_client);
288
289 assert!(matches!(
290 client.user_crypto_management().user_key_id_backfill().await,
291 Err(KeyIdBackfillError::Api)
292 ));
293 assert!(client.km_state_bridge().get_user_key_id().await.is_none());
294 }
295}