bitwarden_core/key_management/crypto/
reinit_user_crypto.rs1#![cfg(feature = "uniffi")]
5
6use bitwarden_crypto::SymmetricKeyAlgorithm;
7use bitwarden_error::bitwarden_error;
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10use tracing::{debug, error, info, warn};
11
12use crate::{
13 Client,
14 key_management::{
15 SymmetricKeySlotId, V2UpgradeToken,
16 account_cryptographic_state::WrappedAccountCryptographicState,
17 },
18};
19
20#[derive(Serialize, Deserialize, Debug)]
25#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
26pub struct ReinitUserCryptoRequest {
27 pub account_cryptographic_state: WrappedAccountCryptographicState,
29
30 pub upgrade_token: V2UpgradeToken,
34}
35
36#[derive(Debug, Error)]
38#[bitwarden_error(flat)]
39pub enum ReinitUserCryptoError {
40 #[error("The SDK must be unlocked to re-initialize user crypto")]
42 NotUnlocked,
43 #[error(
46 "The provided account cryptographic state is not V2. Re-initialization is only supported for upgrading to V2 encryption."
47 )]
48 InvalidAccountCryptographicState,
49 #[error("Unable to run local migrations after user key upgrade")]
54 LocalMigrationFailed,
55 #[error("Invalid upgrade token")]
58 InvalidUpgradeToken,
59 #[error("Cryptography Initialization error")]
61 CryptoInitialization,
62 #[error("No state bridge registered, re-initialization is not supported")]
65 StateBridgeNotRegistered,
66}
67
68pub(crate) async fn reinit_user_crypto(
80 client: &Client,
81 req: ReinitUserCryptoRequest,
82) -> Result<(), ReinitUserCryptoError> {
83 if !matches!(
84 req.account_cryptographic_state,
85 WrappedAccountCryptographicState::V2 { .. }
86 ) {
87 return Err(ReinitUserCryptoError::InvalidAccountCryptographicState);
88 }
89
90 if !client.internal.state_bridge.is_registered() {
91 warn!("No state bridge registered, re-initialization is not supported.");
92 return Err(ReinitUserCryptoError::StateBridgeNotRegistered);
93 }
94
95 {
96 let mut ctx = client.internal.get_key_store().context_mut();
97
98 if !ctx.has_symmetric_key(SymmetricKeySlotId::User) {
99 return Err(ReinitUserCryptoError::NotUnlocked);
100 }
101
102 let current_algorithm = ctx
103 .get_symmetric_key_algorithm(SymmetricKeySlotId::User)
104 .map_err(|_| ReinitUserCryptoError::CryptoInitialization)?;
105
106 let local_v2_user_key_id = match current_algorithm {
107 SymmetricKeyAlgorithm::Aes256CbcHmac => {
108 info!("V1 user key detected with upgrade token, extracting V2 key");
109 req.upgrade_token
110 .unwrap_v2(SymmetricKeySlotId::User, &mut ctx)
111 .map_err(|_| ReinitUserCryptoError::InvalidUpgradeToken)?
112 }
113 SymmetricKeyAlgorithm::XChaCha20Poly1305 => {
114 debug!("Active user key is already V2, skipping re-initialization.");
118 return Ok(());
119 }
120 SymmetricKeyAlgorithm::Aes256Gcm => {
121 error!("Unexpected AES-256-GCM user key during reinit_user_crypto");
122 return Err(ReinitUserCryptoError::CryptoInitialization);
123 }
124 };
125
126 req.account_cryptographic_state
127 .set_to_context(
128 &client.internal.security_state,
129 local_v2_user_key_id,
130 client.internal.get_key_store(),
131 ctx,
132 )
133 .map_err(|e| {
134 error!(error = ?e, "Failed to set account cryptographic state to context during reinit_user_crypto");
135 ReinitUserCryptoError::CryptoInitialization
136 })?;
137 }
138
139 client
140 .internal
141 .state_bridge
142 .set_v2_upgrade_token(&req.upgrade_token)
143 .await;
144
145 super::on_unlock_handler(client).await.map_err(|e| {
146 error!(error = ?e, "Failure in on_unlock_handler during reinit_user_crypto.");
147 ReinitUserCryptoError::LocalMigrationFailed
148 })?;
149
150 info!("User crypto re-initialized successfully");
151 Ok(())
152}
153
154#[cfg(test)]
155mod tests {
156 use bitwarden_crypto::{EncString, KeyStore, SymmetricCryptoKey, SymmetricKeyAlgorithm};
157
158 use super::*;
159 use crate::{
160 Client, UserId,
161 client::test_accounts::{test_bitwarden_com_account, test_bitwarden_com_account_v2},
162 key_management::{
163 KeySlotIds, PrivateKeySlotId, SigningKeySlotId, V2UpgradeToken,
164 state_bridge::test_support::InMemoryStateBridge,
165 },
166 };
167
168 const TEST_VECTOR_USER_KEY_V2_B64: &str = "pQEEAlACHUUoybNAuJoZzqNMxz2bAzoAARFvBIQDBAUGIFggAvGl4ifaUAomQdCdUPpXLHtypiQxHjZwRHeI83caZM4B";
170 const TEST_VECTOR_PRIVATE_KEY_V2: &str = "7.g1gdowE6AAERbwMZARwEUAIdRSjJs0C4mhnOo0zHPZuhBVgYthGLGqVLPeidY8mNMxpLJn3fyeSxyaWsWQTR6pxmRV2DyGZXly/0l9KK+Rsfetl9wvYIz0O4/RW3R6wf7eGxo5XmicV3WnFsoAmIQObxkKWShxFyjzg+ocKItQDzG7Gp6+MW4biTrAlfK51ML/ZS+PCjLmgI1QQr4eMHjiwA2TBKtKkxfjoTJkMXECpRVLEXOo8/mbIGYkuabbSA7oU+TJ0yXlfKDtD25gnyO7tjW/0JMFUaoEKRJOuKoXTN4n/ks4Hbxk0X5/DzfG05rxWad2UNBjNg7ehW99WrQ+33ckdQFKMQOri/rt8JzzrF1k11/jMJ+Y2TADKNHr91NalnUX+yqZAAe3sRt5Pv5ZhLIwRMKQi/1NrLcsQPRuUnogVSPOoMnE/eD6F70iU60Z6pvm1iBw2IvELZcrs/oxpO2SeCue08fIZW/jNZokbLnm90tQ7QeZTUpiPALhUgfGOa3J9VOJ7jQGCqDjd9CzV2DCVfhKCapeTbldm+RwEWBz5VvorH5vMx1AzbPRJxdIQuxcg3NqRrXrYC7fyZljWaPB9qP1tztiPtd1PpGEgxLByIfR6fqyZMCvOBsWbd0H6NhF8mNVdDw60+skFRdbRBTSCjCtKZeLVuVFb8ioH45PR5oXjtx4atIDzu6DKm6TTMCbR6DjZuZZ8GbwHxuUD2mDD3pAFhaof9kR3lQdjy7Zb4EzUUYskQxzcLPcqzp9ZgB3Rg91SStBCCMhdQ6AnhTy+VTGt/mY5AbBXNRSL6fI0r+P9K8CcEI4bNZCDkwwQr5v4O4ykSUzIvmVU0zKzDngy9bteIZuhkvGUoZlQ9UATNGPhoLfqq2eSvqEXkCbxTVZ5D+Ww9pHmWeVcvoBhcl5MvicfeQt++dY3tPjIfZq87nlugG4HiNbcv9nbVpgwe3v8cFetWXQgnO4uhx8JHSwGoSuxHFZtl2sdahjTHavRHnYjSABEFrViUKgb12UDD5ow1GAL62wVdSJKRf9HlLbJhN3PBxuh5L/E0wy1wGA9ecXtw/R1ktvXZ7RklGAt1TmNzZv6vI2J/CMXvndOX9rEpjKMbwbIDAjQ9PxiWdcnmc5SowT9f6yfIjbjXnRMWWidPAua7sgrtej4HP4Qjz1fpgLMLCRyF97tbMTmsAI5Cuj98Buh9PwcdyXj5SbVuHdJS1ehv9b5SWPsD4pwOm3+otVNK6FTazhoUl47AZoAoQzXfsXxrzqYzvF0yJkCnk9S1dcij1L569gQ43CJO6o6jIZFJvA4EmZDl95ELu+BC+x37Ip8dq4JLPsANDVSqvXO9tfDUIXEx25AaOYhW2KAUoDve/fbsU8d0UZR1o/w+ZrOQwawCIPeVPtbh7KFRVQi/rPI+Abl6XR6qMJbKPegliYGUuGF2oEMEc6QLTsMRCEPuw0S3kxbNfVPqml8nGhB2r8zUHBY1diJEmipVghnwH74gIKnyJ2C9nKjV8noUfKzqyV8vxUX2G5yXgodx8Jn0cWs3XhWuApFla9z4R28W/4jA1jK2WQMlx+b6xKUWgRk8+fYsc0HSt2fDrQ9pLpnjb8ME59RCxSPV++PThpnR2JtastZBZur2hBIJsGILCAmufUU4VC4gBKPhNfu/OK4Ktgz+uQlUa9fEC/FnkpTRQPxHuQjSQSNrIIyW1bIRBtnwjvvvNoui9FZJ";
171 const TEST_VECTOR_SIGNED_PUBLIC_KEY_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8BoFkBTqNpYWxnb3JpdGhtAG1jb250ZW50Rm9ybWF0AGlwdWJsaWNLZXlZASYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP/7WM8nUepxoJ0qtM+azxcly+eZ31qUjjZTZcX/gYw1MzkoXWAjqyeFH/bdktq1lEUwegrxkIxKkY2SMtp0CvPnaV1x5O8E6FBSJbKWRlDg181rfEhgm5tc6aR4PJ827IvFVm9xk6Sj091P5DHZDEOsWLZc2jYjtpUV3X38I4gSR7HiYnR4DcwcWkoJ3FhtxMCwYgPz6RVH0vzhLUmm1mgbzH6IH8Pf9DjLTZSxBikVO7S9s9jzhiZbTeeAl3FbNLxfj9Qkj+NoSfms7jGVTlBwvSXgjJs/ktGkT1cR5QcBMpU4bt41+l73MN8pXapCih9Awf1W+RY7imxpYOMFJ3AgMBAAFYQMq/hT4wod2w8xyoM7D86ctuLNX4ZRo+jRHf2sZfaO7QsvonG/ZYuNKF5fq8wpxMRjfoMvnY2TTShbgzLrW8BA4=";
172 const TEST_VECTOR_SIGNING_KEY_V2: &str = "7.g1gcowE6AAERbwMYZQRQAh1FKMmzQLiaGc6jTMc9m6EFWBhYePc2qkCruHAPXgbzXsIP1WVk11ArbLNYUBpifToURlwHKs1je2BwZ1C/5thz4nyNbL0wDaYkRWI9ex1wvB7KhdzC7ltStEd5QttboTSCaXQROSZaGBPNO5+Bu3sTY8F5qK1pBUo6AHNN";
173 const TEST_VECTOR_SECURITY_STATE_V2: &str = "hFgepAEnAxg8BFAmkP0QgfdMVbIujX55W/yNOgABOH8CoFgkomhlbnRpdHlJZFBHOOw2BI9OQoNq+Vl1xZZKZ3ZlcnNpb24CWEAlchbJR0vmRfShG8On7Q2gknjkw4Dd6MYBLiH4u+/CmfQdmjNZdf6kozgW/6NXyKVNu8dAsKsin+xxXkDyVZoG";
174
175 const TEST_VECTOR_PRIVATE_KEY_V1: &str = "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=";
177
178 fn make_mock_upgrade_token() -> V2UpgradeToken {
179 let key_store = KeyStore::<KeySlotIds>::default();
180 let mut ctx = key_store.context_mut();
181 let v1_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
182 let v2_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
183 V2UpgradeToken::create(v1_id, v2_id, &ctx).unwrap()
184 }
185
186 fn register_in_memory_bridge(client: &Client) {
187 client
188 .km_state_bridge()
189 .register_bridge(Box::new(InMemoryStateBridge::default()));
190 }
191
192 fn assert_active_user_key_is_v2(client: &Client, expected_v2_key: &SymmetricCryptoKey) {
194 let key_store = client.internal.get_key_store();
195 let ctx = key_store.context();
196 let algorithm = ctx
197 .get_symmetric_key_algorithm(SymmetricKeySlotId::User)
198 .unwrap();
199 assert_eq!(
200 algorithm,
201 SymmetricKeyAlgorithm::XChaCha20Poly1305,
202 "user-slot algorithm must be V2 after upgrade"
203 );
204
205 #[allow(deprecated)]
206 let user_key = ctx
207 .dangerous_get_symmetric_key(SymmetricKeySlotId::User)
208 .unwrap();
209 assert_eq!(user_key, expected_v2_key);
210 }
211
212 fn test_vector_v2_account_state() -> WrappedAccountCryptographicState {
215 WrappedAccountCryptographicState::V2 {
216 private_key: TEST_VECTOR_PRIVATE_KEY_V2.parse().unwrap(),
217 signing_key: TEST_VECTOR_SIGNING_KEY_V2.parse().unwrap(),
218 security_state: TEST_VECTOR_SECURITY_STATE_V2.parse().unwrap(),
219 signed_public_key: Some(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2.parse().unwrap()),
220 }
221 }
222
223 fn test_vector_v1_account_state() -> WrappedAccountCryptographicState {
224 WrappedAccountCryptographicState::V1 {
225 private_key: TEST_VECTOR_PRIVATE_KEY_V1.parse().unwrap(),
226 }
227 }
228
229 #[tokio::test]
230 async fn reinit_user_crypto_returns_not_unlocked_when_locked() {
231 let client = Client::new_test(None);
232 register_in_memory_bridge(&client);
233
234 let result = reinit_user_crypto(
235 &client,
236 ReinitUserCryptoRequest {
237 account_cryptographic_state: test_vector_v2_account_state(),
238 upgrade_token: make_mock_upgrade_token(),
239 },
240 )
241 .await;
242
243 assert!(
244 matches!(result, Err(ReinitUserCryptoError::NotUnlocked)),
245 "reinit on a locked SDK must return NotUnlocked, got {result:?}"
246 );
247 }
248
249 #[tokio::test]
250 async fn reinit_user_crypto_is_noop_when_active_user_is_already_v2() {
251 let client = Client::init_test_account(test_bitwarden_com_account_v2()).await;
252 register_in_memory_bridge(&client);
253
254 let result = reinit_user_crypto(
255 &client,
256 ReinitUserCryptoRequest {
257 account_cryptographic_state: test_vector_v2_account_state(),
258 upgrade_token: make_mock_upgrade_token(),
259 },
260 )
261 .await;
262
263 assert!(
264 result.is_ok(),
265 "reinit on an already-V2 user must be a no-op and return Ok, got {result:?}"
266 );
267
268 let expected_v2_key =
269 SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
270 assert_active_user_key_is_v2(&client, &expected_v2_key);
271
272 let upgrade_token = client.internal.state_bridge.get_v2_upgrade_token().await;
273 assert!(
274 upgrade_token.is_none(),
275 "reinit on an already-V2 user must not set the upgrade token"
276 );
277 }
278
279 #[tokio::test]
280 async fn reinit_user_crypto_upgrades_v1_to_v2_with_token() {
281 let client = Client::init_test_account(test_bitwarden_com_account()).await;
282 register_in_memory_bridge(&client);
283
284 let expected_v2_key =
287 SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
288 let upgrade_token = {
289 let mut ctx = client.internal.get_key_store().context_mut();
290 let v2_key_id = ctx.add_local_symmetric_key(expected_v2_key.clone());
291 V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap()
292 };
293
294 reinit_user_crypto(
295 &client,
296 ReinitUserCryptoRequest {
297 account_cryptographic_state: test_vector_v2_account_state(),
298 upgrade_token: upgrade_token.clone(),
299 },
300 )
301 .await
302 .expect("V1→V2 reinit with a valid upgrade token should succeed");
303
304 assert_active_user_key_is_v2(&client, &expected_v2_key);
305
306 assert_eq!(
307 client.internal.get_security_version(),
308 2,
309 "security version must reflect the V2 state"
310 );
311
312 {
313 let key_store = client.internal.get_key_store();
314 let ctx = key_store.context();
315 assert!(
316 ctx.has_signing_key(SigningKeySlotId::UserSigningKey),
317 "user signing key must be set after V1→V2 upgrade"
318 );
319 assert!(
320 ctx.has_private_key(PrivateKeySlotId::UserPrivateKey),
321 "user private key must be set after V1→V2 upgrade"
322 );
323 }
324
325 let stored_token = client
326 .internal
327 .state_bridge
328 .get_v2_upgrade_token()
329 .await
330 .expect("the upgrade token must be set on the state bridge after reinit");
331 assert_eq!(
332 stored_token.wrapped_user_key_1,
333 upgrade_token.wrapped_user_key_1
334 );
335 assert_eq!(
336 stored_token.wrapped_user_key_2,
337 upgrade_token.wrapped_user_key_2
338 );
339 }
340
341 #[tokio::test]
342 async fn reinit_user_crypto_called_twice_with_same_payload_is_noop() {
343 let client = Client::init_test_account(test_bitwarden_com_account()).await;
344 register_in_memory_bridge(&client);
345
346 let expected_v2_key =
347 SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
348 let upgrade_token = {
349 let mut ctx = client.internal.get_key_store().context_mut();
350 let v2_key_id = ctx.add_local_symmetric_key(expected_v2_key.clone());
351 V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap()
352 };
353
354 let request = || ReinitUserCryptoRequest {
355 account_cryptographic_state: test_vector_v2_account_state(),
356 upgrade_token: upgrade_token.clone(),
357 };
358
359 reinit_user_crypto(&client, request())
361 .await
362 .expect("V1→V2 reinit with a valid upgrade token should succeed");
363 assert_active_user_key_is_v2(&client, &expected_v2_key);
364
365 reinit_user_crypto(&client, request())
368 .await
369 .expect("re-applying the same upgrade after success should be a no-op");
370 assert_active_user_key_is_v2(&client, &expected_v2_key);
371 }
372
373 #[tokio::test]
374 async fn reinit_user_crypto_invalid_upgrade_token_returns_error() {
375 let client = Client::init_test_account(test_bitwarden_com_account()).await;
376 register_in_memory_bridge(&client);
377
378 let mismatched_token = make_mock_upgrade_token();
381
382 let result = reinit_user_crypto(
383 &client,
384 ReinitUserCryptoRequest {
385 account_cryptographic_state: test_vector_v2_account_state(),
386 upgrade_token: mismatched_token,
387 },
388 )
389 .await;
390
391 assert!(
392 matches!(result, Err(ReinitUserCryptoError::InvalidUpgradeToken)),
393 "mismatched upgrade token must return InvalidUpgradeToken, got {result:?}"
394 );
395 }
396
397 #[tokio::test]
398 async fn reinit_user_crypto_returns_crypto_initialization_on_key_mismatch() {
399 let client = Client::init_test_account(test_bitwarden_com_account()).await;
400 register_in_memory_bridge(&client);
401
402 let upgrade_token = {
407 let mut ctx = client.internal.get_key_store().context_mut();
408 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
409 V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap()
410 };
411
412 let result = reinit_user_crypto(
413 &client,
414 ReinitUserCryptoRequest {
415 account_cryptographic_state: test_vector_v2_account_state(),
416 upgrade_token,
417 },
418 )
419 .await;
420
421 assert!(
422 matches!(result, Err(ReinitUserCryptoError::CryptoInitialization)),
423 "a V2 key that cannot decrypt the account state must return CryptoInitialization, got {result:?}"
424 );
425
426 let key_store = client.internal.get_key_store();
430 let ctx = key_store.context();
431 assert!(
432 ctx.has_symmetric_key(SymmetricKeySlotId::User),
433 "the original V1 user key must remain in the User slot on failure"
434 );
435 assert_eq!(
436 ctx.get_symmetric_key_algorithm(SymmetricKeySlotId::User)
437 .unwrap(),
438 SymmetricKeyAlgorithm::Aes256CbcHmac,
439 "the User slot must still hold the original V1 key on failure"
440 );
441 }
442
443 #[tokio::test]
444 async fn reinit_user_crypto_returns_invalid_account_state_on_v1_request() {
445 let client = Client::init_test_account(test_bitwarden_com_account()).await;
446
447 let result = reinit_user_crypto(
448 &client,
449 ReinitUserCryptoRequest {
450 account_cryptographic_state: test_vector_v1_account_state(),
451 upgrade_token: make_mock_upgrade_token(),
452 },
453 )
454 .await;
455
456 assert!(
457 matches!(
458 result,
459 Err(ReinitUserCryptoError::InvalidAccountCryptographicState)
460 ),
461 "a V1 account state must return InvalidAccountState, got {result:?}"
462 );
463 }
464
465 #[tokio::test]
466 async fn reinit_user_crypto_returns_state_bridge_not_registered_when_no_bridge() {
467 let client = Client::init_test_account(test_bitwarden_com_account()).await;
468
469 let result = reinit_user_crypto(
470 &client,
471 ReinitUserCryptoRequest {
472 account_cryptographic_state: test_vector_v2_account_state(),
473 upgrade_token: make_mock_upgrade_token(),
474 },
475 )
476 .await;
477
478 assert!(
479 matches!(result, Err(ReinitUserCryptoError::StateBridgeNotRegistered)),
480 "reinit without a registered state bridge must return StateBridgeNotRegistered, got {result:?}"
481 );
482 }
483
484 #[tokio::test]
485 async fn reinit_user_crypto_v1_v2_upgrade_rewraps_local_user_data_key() {
486 use crate::key_management::LocalUserDataKeyState;
487
488 let client = Client::init_test_account(test_bitwarden_com_account()).await;
490 let user_id = UserId::new(uuid::uuid!("060000fb-0922-4dd3-b170-6e15cb5df8c8"));
491 register_in_memory_bridge(&client);
492
493 let v1_user_data_key = client
495 .platform()
496 .state()
497 .get::<LocalUserDataKeyState>()
498 .unwrap()
499 .get(user_id)
500 .await
501 .unwrap()
502 .expect("V1 init should plant a LocalUserDataKey state");
503 assert!(
504 matches!(
505 v1_user_data_key.wrapped_key,
506 EncString::Aes256Cbc_HmacSha256_B64 { .. }
507 ),
508 "initial local user data key should be V1-wrapped"
509 );
510
511 let v2_key = SymmetricCryptoKey::try_from(TEST_VECTOR_USER_KEY_V2_B64.to_string()).unwrap();
512 let upgrade_token = {
513 let mut ctx = client.internal.get_key_store().context_mut();
514 let v2_key_id = ctx.add_local_symmetric_key(v2_key.clone());
515 V2UpgradeToken::create(SymmetricKeySlotId::User, v2_key_id, &ctx).unwrap()
516 };
517
518 reinit_user_crypto(
519 &client,
520 ReinitUserCryptoRequest {
521 account_cryptographic_state: test_vector_v2_account_state(),
522 upgrade_token,
523 },
524 )
525 .await
526 .expect("V1→V2 reinit with a valid upgrade token should succeed");
527
528 let rewrapped_state = client
530 .platform()
531 .state()
532 .get::<LocalUserDataKeyState>()
533 .unwrap()
534 .get(user_id)
535 .await
536 .unwrap()
537 .expect("LocalUserDataKey state must remain present");
538 assert!(
539 matches!(
540 rewrapped_state.wrapped_key,
541 EncString::Cose_Encrypt0_B64 { .. }
542 ),
543 "rewrapped key should be sealed with the V2 user key"
544 );
545 assert_ne!(rewrapped_state.wrapped_key, v1_user_data_key.wrapped_key);
546 }
547}