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