bitwarden_core/key_management/
v2_upgrade_token.rs1use bitwarden_api_api::models::{V2UpgradeTokenRequestModel, V2UpgradeTokenResponseModel};
10use bitwarden_crypto::{
11 Decryptable, EncString, KeySlotIds, KeyStoreContext, SymmetricKeyAlgorithm,
12};
13use thiserror::Error;
14
15#[cfg_attr(
17 feature = "wasm",
18 derive(tsify::Tsify),
19 tsify(into_wasm_abi, from_wasm_abi)
20)]
21#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
22#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
23pub struct V2UpgradeToken {
24 pub wrapped_user_key_1: EncString,
26 pub wrapped_user_key_2: EncString,
28}
29
30#[cfg(feature = "wasm")]
31impl TryFrom<wasm_bindgen::JsValue> for V2UpgradeToken {
32 type Error = serde_wasm_bindgen::Error;
33
34 fn try_from(value: wasm_bindgen::JsValue) -> Result<Self, Self::Error> {
35 serde_wasm_bindgen::from_value(value)
36 }
37}
38
39impl V2UpgradeToken {
40 #[bitwarden_logging::instrument(fields(v1_key_id = ?v1_key_id, v2_key_id = ?v2_key_id))]
44 pub fn create<Ids: KeySlotIds>(
45 v1_key_id: Ids::Symmetric,
46 v2_key_id: Ids::Symmetric,
47 ctx: &KeyStoreContext<Ids>,
48 ) -> Result<Self, V2UpgradeTokenError> {
49 if ctx
51 .get_symmetric_key_algorithm(v1_key_id)
52 .map_err(|_| V2UpgradeTokenError::KeyMissing)?
53 != SymmetricKeyAlgorithm::Aes256CbcHmac
54 {
55 return Err(V2UpgradeTokenError::WrongKeyType);
56 }
57
58 if ctx
59 .get_symmetric_key_algorithm(v2_key_id)
60 .map_err(|_| V2UpgradeTokenError::KeyMissing)?
61 != SymmetricKeyAlgorithm::XChaCha20Poly1305
62 {
63 return Err(V2UpgradeTokenError::WrongKeyType);
64 }
65
66 let wrapped_user_key_1 = ctx
68 .wrap_symmetric_key(v2_key_id, v1_key_id)
69 .map_err(|_| V2UpgradeTokenError::EncryptionFailed)?;
70
71 let wrapped_user_key_2 = ctx
73 .wrap_symmetric_key(v1_key_id, v2_key_id)
74 .map_err(|_| V2UpgradeTokenError::EncryptionFailed)?;
75
76 Ok(V2UpgradeToken {
77 wrapped_user_key_1,
78 wrapped_user_key_2,
79 })
80 }
81
82 #[bitwarden_logging::instrument(fields(v2_key_id = ?v2_key_id))]
85 pub fn unwrap_v1<Ids: KeySlotIds>(
86 &self,
87 v2_key_id: Ids::Symmetric,
88 ctx: &mut KeyStoreContext<Ids>,
89 ) -> Result<Ids::Symmetric, V2UpgradeTokenError> {
90 let v1_key_id = ctx
92 .unwrap_symmetric_key(v2_key_id, &self.wrapped_user_key_1)
93 .map_err(|_| V2UpgradeTokenError::DecryptionFailed)?;
94
95 let _: Vec<u8> = self
97 .wrapped_user_key_2
98 .decrypt(ctx, v1_key_id)
99 .map_err(|_| V2UpgradeTokenError::ValidationFailed)?;
100
101 Ok(v1_key_id)
102 }
103
104 #[bitwarden_logging::instrument(fields(v1_key_id = ?v1_key_id))]
107 pub fn unwrap_v2<Ids: KeySlotIds>(
108 &self,
109 v1_key_id: Ids::Symmetric,
110 ctx: &mut KeyStoreContext<Ids>,
111 ) -> Result<Ids::Symmetric, V2UpgradeTokenError> {
112 let v2_key_id = ctx
114 .unwrap_symmetric_key(v1_key_id, &self.wrapped_user_key_2)
115 .map_err(|_| V2UpgradeTokenError::DecryptionFailed)?;
116
117 let _: Vec<u8> = self
119 .wrapped_user_key_1
120 .decrypt(ctx, v2_key_id)
121 .map_err(|_| V2UpgradeTokenError::ValidationFailed)?;
122
123 Ok(v2_key_id)
124 }
125}
126
127impl TryFrom<&V2UpgradeTokenResponseModel> for V2UpgradeToken {
128 type Error = V2UpgradeTokenError;
129
130 fn try_from(response: &V2UpgradeTokenResponseModel) -> Result<Self, Self::Error> {
131 let wrapped_user_key_1 = response
132 .wrapped_user_key1
133 .as_deref()
134 .ok_or(V2UpgradeTokenError::ResponseModelMalformed)?
135 .parse()
136 .map_err(|_| V2UpgradeTokenError::ResponseModelMalformed)?;
137
138 let wrapped_user_key_2 = response
139 .wrapped_user_key2
140 .as_deref()
141 .ok_or(V2UpgradeTokenError::ResponseModelMalformed)?
142 .parse()
143 .map_err(|_| V2UpgradeTokenError::ResponseModelMalformed)?;
144
145 Ok(V2UpgradeToken {
146 wrapped_user_key_1,
147 wrapped_user_key_2,
148 })
149 }
150}
151
152impl From<V2UpgradeToken> for V2UpgradeTokenRequestModel {
153 fn from(token: V2UpgradeToken) -> Self {
154 V2UpgradeTokenRequestModel {
155 wrapped_user_key1: token.wrapped_user_key_1.to_string(),
156 wrapped_user_key2: token.wrapped_user_key_2.to_string(),
157 }
158 }
159}
160
161impl TryFrom<V2UpgradeTokenRequestModel> for V2UpgradeToken {
162 type Error = V2UpgradeTokenError;
163
164 fn try_from(request: V2UpgradeTokenRequestModel) -> Result<Self, Self::Error> {
165 let wrapped_user_key_1 = request
166 .wrapped_user_key1
167 .parse()
168 .map_err(|_| V2UpgradeTokenError::RequestMalformed)?;
169 let wrapped_user_key_2 = request
170 .wrapped_user_key2
171 .parse()
172 .map_err(|_| V2UpgradeTokenError::RequestMalformed)?;
173
174 Ok(V2UpgradeToken {
175 wrapped_user_key_1,
176 wrapped_user_key_2,
177 })
178 }
179}
180
181#[derive(Debug, Error)]
183pub enum V2UpgradeTokenError {
184 #[error("Decryption failed")]
186 DecryptionFailed,
187 #[error("Validation failed")]
189 ValidationFailed,
190 #[error("Serialization error")]
192 Serialization,
193 #[error("Wrong key type")]
195 WrongKeyType,
196 #[error("Key missing")]
198 KeyMissing,
199 #[error("Encryption failed")]
201 EncryptionFailed,
202 #[error("Response model malformed")]
204 ResponseModelMalformed,
205 #[error("Request model malformed")]
207 RequestMalformed,
208}
209
210#[cfg(test)]
211mod tests {
212 use bitwarden_crypto::{KeyStore, SymmetricKeyAlgorithm};
213
214 use super::*;
215 use crate::key_management::KeySlotIds;
216
217 #[test]
218 fn test_create_and_round_trip() {
219 let key_store = KeyStore::<KeySlotIds>::default();
220 let mut ctx = key_store.context_mut();
221
222 let v1_key_id = ctx.generate_symmetric_key();
224 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
225
226 let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
228 .expect("Token creation should succeed");
229
230 let serialized = serde_json::to_string(&token).expect("Serialization should succeed");
232 let deserialized: V2UpgradeToken =
233 serde_json::from_str(&serialized).expect("Deserialization should succeed");
234
235 let unwrapped_v2_id = deserialized
237 .unwrap_v2(v1_key_id, &mut ctx)
238 .expect("Unwrapping V2 should succeed");
239
240 #[allow(deprecated)]
242 let original_v2 = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap();
243 #[allow(deprecated)]
244 let unwrapped_v2 = ctx.dangerous_get_symmetric_key(unwrapped_v2_id).unwrap();
245 assert_eq!(original_v2, unwrapped_v2);
246 }
247
248 #[test]
249 fn test_unwrap_bidirectional() {
250 let key_store = KeyStore::<KeySlotIds>::default();
251 let mut ctx = key_store.context_mut();
252
253 let v1_key_id = ctx.generate_symmetric_key();
255 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
256
257 let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
259 .expect("Token creation should succeed");
260
261 let unwrapped_v2_id = token
263 .unwrap_v2(v1_key_id, &mut ctx)
264 .expect("Unwrapping V2 should succeed");
265
266 let unwrapped_v1_id = token
268 .unwrap_v1(unwrapped_v2_id, &mut ctx)
269 .expect("Unwrapping V1 should succeed");
270
271 #[allow(deprecated)]
273 let original_v1 = ctx.dangerous_get_symmetric_key(v1_key_id).unwrap();
274 #[allow(deprecated)]
275 let original_v2 = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap();
276 #[allow(deprecated)]
277 let unwrapped_v1 = ctx.dangerous_get_symmetric_key(unwrapped_v1_id).unwrap();
278 #[allow(deprecated)]
279 let unwrapped_v2 = ctx.dangerous_get_symmetric_key(unwrapped_v2_id).unwrap();
280
281 assert_eq!(original_v1, unwrapped_v1);
282 assert_eq!(original_v2, unwrapped_v2);
283 }
284
285 #[test]
286 fn test_create_wrong_key_type_error() {
287 let key_store = KeyStore::<KeySlotIds>::default();
288 let mut ctx = key_store.context_mut();
289
290 let v1_key_1 = ctx.generate_symmetric_key();
292 let v1_key_2 = ctx.generate_symmetric_key();
293
294 let result = V2UpgradeToken::create(v1_key_1, v1_key_2, &ctx);
295 assert!(matches!(result, Err(V2UpgradeTokenError::WrongKeyType)));
296 }
297
298 #[test]
299 fn test_serialization_round_trip() {
300 let key_store = KeyStore::<KeySlotIds>::default();
301 let mut ctx = key_store.context_mut();
302
303 let v1_key_id = ctx.generate_symmetric_key();
304 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
305
306 let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
307 .expect("Token creation should succeed");
308
309 let serialized = serde_json::to_string(&token).expect("Serialization should succeed");
311 let json: serde_json::Value =
312 serde_json::from_str(&serialized).expect("Should be valid JSON");
313 assert!(json.is_object());
314 assert!(json.get("wrapped_user_key_1").is_some());
315 assert!(json.get("wrapped_user_key_2").is_some());
316
317 let deserialized: V2UpgradeToken =
319 serde_json::from_str(&serialized).expect("Deserialization should succeed");
320 let reserialized =
321 serde_json::to_string(&deserialized).expect("Reserialization should succeed");
322 assert_eq!(serialized, reserialized);
323 }
324
325 fn build_response_model<Ids: bitwarden_crypto::KeySlotIds>(
326 v1_key_id: Ids::Symmetric,
327 v2_key_id: Ids::Symmetric,
328 ctx: &KeyStoreContext<Ids>,
329 ) -> V2UpgradeTokenResponseModel {
330 let wrapped_user_key_1 = ctx.wrap_symmetric_key(v2_key_id, v1_key_id).unwrap();
331 let wrapped_user_key_2 = ctx.wrap_symmetric_key(v1_key_id, v2_key_id).unwrap();
332 V2UpgradeTokenResponseModel {
333 wrapped_user_key1: Some(wrapped_user_key_1.to_string()),
334 wrapped_user_key2: Some(wrapped_user_key_2.to_string()),
335 }
336 }
337
338 #[test]
339 fn test_from_response_model_missing_wrapped_uk1() {
340 let response = V2UpgradeTokenResponseModel {
341 wrapped_user_key1: None,
342 wrapped_user_key2: None,
343 };
344 assert!(matches!(
345 V2UpgradeToken::try_from(&response),
346 Err(V2UpgradeTokenError::ResponseModelMalformed)
347 ));
348 }
349
350 #[test]
351 fn test_from_response_model_missing_wrapped_uk2() {
352 let key_store = KeyStore::<KeySlotIds>::default();
353 let mut ctx = key_store.context_mut();
354
355 let v1_key_id = ctx.generate_symmetric_key();
356 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
357
358 let mut response = build_response_model(v1_key_id, v2_key_id, &ctx);
359 response.wrapped_user_key2 = None;
360
361 assert!(matches!(
362 V2UpgradeToken::try_from(&response),
363 Err(V2UpgradeTokenError::ResponseModelMalformed)
364 ));
365 }
366
367 #[test]
368 fn test_serde_round_trip() {
369 let key_store = KeyStore::<KeySlotIds>::default();
370 let mut ctx = key_store.context_mut();
371
372 let v1_key_id = ctx.generate_symmetric_key();
373 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
374
375 let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
376 .expect("Token creation should succeed");
377
378 let serialized = serde_json::to_string(&token).expect("Serialization should succeed");
380
381 let deserialized: V2UpgradeToken =
383 serde_json::from_str(&serialized).expect("Deserialization should succeed");
384 let unwrapped_v2_id = deserialized
385 .unwrap_v2(v1_key_id, &mut ctx)
386 .expect("Unwrapping V2 from serde-deserialized token should succeed");
387
388 #[allow(deprecated)]
389 let original_v2 = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap();
390 #[allow(deprecated)]
391 let unwrapped_v2 = ctx.dangerous_get_symmetric_key(unwrapped_v2_id).unwrap();
392 assert_eq!(original_v2, unwrapped_v2);
393 }
394
395 #[test]
396 fn test_from_token_to_request_model() {
397 let key_store = KeyStore::<KeySlotIds>::default();
398 let mut ctx = key_store.context_mut();
399
400 let v1_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
401 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
402
403 let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
404 .expect("Token creation should succeed");
405
406 let expected_wrapped_key1 = token.wrapped_user_key_1.to_string();
407 let expected_wrapped_key2 = token.wrapped_user_key_2.to_string();
408
409 let request_model: V2UpgradeTokenRequestModel = token.into();
410
411 assert_eq!(request_model.wrapped_user_key1, expected_wrapped_key1);
412 assert_eq!(request_model.wrapped_user_key2, expected_wrapped_key2);
413
414 request_model
415 .wrapped_user_key1
416 .parse::<EncString>()
417 .expect("wrapped_user_key1 should be a valid EncString");
418 request_model
419 .wrapped_user_key2
420 .parse::<EncString>()
421 .expect("wrapped_user_key2 should be a valid EncString");
422 }
423}