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