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
31impl V2UpgradeToken {
32 #[instrument(skip(ctx))]
36 pub fn create<Ids: KeySlotIds>(
37 v1_key_id: Ids::Symmetric,
38 v2_key_id: Ids::Symmetric,
39 ctx: &KeyStoreContext<Ids>,
40 ) -> Result<Self, V2UpgradeTokenError> {
41 if ctx
43 .get_symmetric_key_algorithm(v1_key_id)
44 .map_err(|_| V2UpgradeTokenError::KeyMissing)?
45 != SymmetricKeyAlgorithm::Aes256CbcHmac
46 {
47 return Err(V2UpgradeTokenError::WrongKeyType);
48 }
49
50 if ctx
51 .get_symmetric_key_algorithm(v2_key_id)
52 .map_err(|_| V2UpgradeTokenError::KeyMissing)?
53 != SymmetricKeyAlgorithm::XChaCha20Poly1305
54 {
55 return Err(V2UpgradeTokenError::WrongKeyType);
56 }
57
58 let wrapped_user_key_1 = ctx
60 .wrap_symmetric_key(v2_key_id, v1_key_id)
61 .map_err(|_| V2UpgradeTokenError::EncryptionFailed)?;
62
63 let wrapped_user_key_2 = ctx
65 .wrap_symmetric_key(v1_key_id, v2_key_id)
66 .map_err(|_| V2UpgradeTokenError::EncryptionFailed)?;
67
68 Ok(V2UpgradeToken {
69 wrapped_user_key_1,
70 wrapped_user_key_2,
71 })
72 }
73
74 #[instrument(skip(self, ctx))]
77 pub fn unwrap_v1<Ids: KeySlotIds>(
78 &self,
79 v2_key_id: Ids::Symmetric,
80 ctx: &mut KeyStoreContext<Ids>,
81 ) -> Result<Ids::Symmetric, V2UpgradeTokenError> {
82 let v1_key_id = ctx
84 .unwrap_symmetric_key(v2_key_id, &self.wrapped_user_key_1)
85 .map_err(|_| V2UpgradeTokenError::DecryptionFailed)?;
86
87 let _: Vec<u8> = self
89 .wrapped_user_key_2
90 .decrypt(ctx, v1_key_id)
91 .map_err(|_| V2UpgradeTokenError::ValidationFailed)?;
92
93 Ok(v1_key_id)
94 }
95
96 #[instrument(skip(self, ctx))]
99 pub fn unwrap_v2<Ids: KeySlotIds>(
100 &self,
101 v1_key_id: Ids::Symmetric,
102 ctx: &mut KeyStoreContext<Ids>,
103 ) -> Result<Ids::Symmetric, V2UpgradeTokenError> {
104 let v2_key_id = ctx
106 .unwrap_symmetric_key(v1_key_id, &self.wrapped_user_key_2)
107 .map_err(|_| V2UpgradeTokenError::DecryptionFailed)?;
108
109 let _: Vec<u8> = self
111 .wrapped_user_key_1
112 .decrypt(ctx, v2_key_id)
113 .map_err(|_| V2UpgradeTokenError::ValidationFailed)?;
114
115 Ok(v2_key_id)
116 }
117}
118
119impl TryFrom<&V2UpgradeTokenResponseModel> for V2UpgradeToken {
120 type Error = V2UpgradeTokenError;
121
122 fn try_from(response: &V2UpgradeTokenResponseModel) -> Result<Self, Self::Error> {
123 let wrapped_user_key_1 = response
124 .wrapped_user_key1
125 .as_deref()
126 .ok_or(V2UpgradeTokenError::ResponseModelMalformed)?
127 .parse()
128 .map_err(|_| V2UpgradeTokenError::ResponseModelMalformed)?;
129
130 let wrapped_user_key_2 = response
131 .wrapped_user_key2
132 .as_deref()
133 .ok_or(V2UpgradeTokenError::ResponseModelMalformed)?
134 .parse()
135 .map_err(|_| V2UpgradeTokenError::ResponseModelMalformed)?;
136
137 Ok(V2UpgradeToken {
138 wrapped_user_key_1,
139 wrapped_user_key_2,
140 })
141 }
142}
143
144impl From<V2UpgradeToken> for V2UpgradeTokenRequestModel {
145 fn from(token: V2UpgradeToken) -> Self {
146 V2UpgradeTokenRequestModel {
147 wrapped_user_key1: token.wrapped_user_key_1.to_string(),
148 wrapped_user_key2: token.wrapped_user_key_2.to_string(),
149 }
150 }
151}
152
153#[derive(Debug, Error)]
155pub enum V2UpgradeTokenError {
156 #[error("Decryption failed")]
158 DecryptionFailed,
159 #[error("Validation failed")]
161 ValidationFailed,
162 #[error("Serialization error")]
164 Serialization,
165 #[error("Wrong key type")]
167 WrongKeyType,
168 #[error("Key missing")]
170 KeyMissing,
171 #[error("Encryption failed")]
173 EncryptionFailed,
174 #[error("Response model malformed")]
176 ResponseModelMalformed,
177}
178
179#[cfg(test)]
180mod tests {
181 use bitwarden_crypto::{KeyStore, SymmetricKeyAlgorithm};
182
183 use super::*;
184 use crate::key_management::KeySlotIds;
185
186 #[test]
187 fn test_create_and_round_trip() {
188 let key_store = KeyStore::<KeySlotIds>::default();
189 let mut ctx = key_store.context_mut();
190
191 let v1_key_id = ctx.generate_symmetric_key();
193 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
194
195 let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
197 .expect("Token creation should succeed");
198
199 let serialized = serde_json::to_string(&token).expect("Serialization should succeed");
201 let deserialized: V2UpgradeToken =
202 serde_json::from_str(&serialized).expect("Deserialization should succeed");
203
204 let unwrapped_v2_id = deserialized
206 .unwrap_v2(v1_key_id, &mut ctx)
207 .expect("Unwrapping V2 should succeed");
208
209 #[allow(deprecated)]
211 let original_v2 = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap();
212 #[allow(deprecated)]
213 let unwrapped_v2 = ctx.dangerous_get_symmetric_key(unwrapped_v2_id).unwrap();
214 assert_eq!(original_v2, unwrapped_v2);
215 }
216
217 #[test]
218 fn test_unwrap_bidirectional() {
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 unwrapped_v2_id = token
232 .unwrap_v2(v1_key_id, &mut ctx)
233 .expect("Unwrapping V2 should succeed");
234
235 let unwrapped_v1_id = token
237 .unwrap_v1(unwrapped_v2_id, &mut ctx)
238 .expect("Unwrapping V1 should succeed");
239
240 #[allow(deprecated)]
242 let original_v1 = ctx.dangerous_get_symmetric_key(v1_key_id).unwrap();
243 #[allow(deprecated)]
244 let original_v2 = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap();
245 #[allow(deprecated)]
246 let unwrapped_v1 = ctx.dangerous_get_symmetric_key(unwrapped_v1_id).unwrap();
247 #[allow(deprecated)]
248 let unwrapped_v2 = ctx.dangerous_get_symmetric_key(unwrapped_v2_id).unwrap();
249
250 assert_eq!(original_v1, unwrapped_v1);
251 assert_eq!(original_v2, unwrapped_v2);
252 }
253
254 #[test]
255 fn test_create_wrong_key_type_error() {
256 let key_store = KeyStore::<KeySlotIds>::default();
257 let mut ctx = key_store.context_mut();
258
259 let v1_key_1 = ctx.generate_symmetric_key();
261 let v1_key_2 = ctx.generate_symmetric_key();
262
263 let result = V2UpgradeToken::create(v1_key_1, v1_key_2, &ctx);
264 assert!(matches!(result, Err(V2UpgradeTokenError::WrongKeyType)));
265 }
266
267 #[test]
268 fn test_serialization_round_trip() {
269 let key_store = KeyStore::<KeySlotIds>::default();
270 let mut ctx = key_store.context_mut();
271
272 let v1_key_id = ctx.generate_symmetric_key();
273 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
274
275 let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
276 .expect("Token creation should succeed");
277
278 let serialized = serde_json::to_string(&token).expect("Serialization should succeed");
280 let json: serde_json::Value =
281 serde_json::from_str(&serialized).expect("Should be valid JSON");
282 assert!(json.is_object());
283 assert!(json.get("wrapped_user_key_1").is_some());
284 assert!(json.get("wrapped_user_key_2").is_some());
285
286 let deserialized: V2UpgradeToken =
288 serde_json::from_str(&serialized).expect("Deserialization should succeed");
289 let reserialized =
290 serde_json::to_string(&deserialized).expect("Reserialization should succeed");
291 assert_eq!(serialized, reserialized);
292 }
293
294 fn build_response_model<Ids: bitwarden_crypto::KeySlotIds>(
295 v1_key_id: Ids::Symmetric,
296 v2_key_id: Ids::Symmetric,
297 ctx: &KeyStoreContext<Ids>,
298 ) -> V2UpgradeTokenResponseModel {
299 let wrapped_user_key_1 = ctx.wrap_symmetric_key(v2_key_id, v1_key_id).unwrap();
300 let wrapped_user_key_2 = ctx.wrap_symmetric_key(v1_key_id, v2_key_id).unwrap();
301 V2UpgradeTokenResponseModel {
302 wrapped_user_key1: Some(wrapped_user_key_1.to_string()),
303 wrapped_user_key2: Some(wrapped_user_key_2.to_string()),
304 }
305 }
306
307 #[test]
308 fn test_from_response_model_missing_wrapped_uk1() {
309 let response = V2UpgradeTokenResponseModel {
310 wrapped_user_key1: None,
311 wrapped_user_key2: None,
312 };
313 assert!(matches!(
314 V2UpgradeToken::try_from(&response),
315 Err(V2UpgradeTokenError::ResponseModelMalformed)
316 ));
317 }
318
319 #[test]
320 fn test_from_response_model_missing_wrapped_uk2() {
321 let key_store = KeyStore::<KeySlotIds>::default();
322 let mut ctx = key_store.context_mut();
323
324 let v1_key_id = ctx.generate_symmetric_key();
325 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
326
327 let mut response = build_response_model(v1_key_id, v2_key_id, &ctx);
328 response.wrapped_user_key2 = None;
329
330 assert!(matches!(
331 V2UpgradeToken::try_from(&response),
332 Err(V2UpgradeTokenError::ResponseModelMalformed)
333 ));
334 }
335
336 #[test]
337 fn test_serde_round_trip() {
338 let key_store = KeyStore::<KeySlotIds>::default();
339 let mut ctx = key_store.context_mut();
340
341 let v1_key_id = ctx.generate_symmetric_key();
342 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
343
344 let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
345 .expect("Token creation should succeed");
346
347 let serialized = serde_json::to_string(&token).expect("Serialization should succeed");
349
350 let deserialized: V2UpgradeToken =
352 serde_json::from_str(&serialized).expect("Deserialization should succeed");
353 let unwrapped_v2_id = deserialized
354 .unwrap_v2(v1_key_id, &mut ctx)
355 .expect("Unwrapping V2 from serde-deserialized token should succeed");
356
357 #[allow(deprecated)]
358 let original_v2 = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap();
359 #[allow(deprecated)]
360 let unwrapped_v2 = ctx.dangerous_get_symmetric_key(unwrapped_v2_id).unwrap();
361 assert_eq!(original_v2, unwrapped_v2);
362 }
363
364 #[test]
365 fn test_from_token_to_request_model() {
366 let key_store = KeyStore::<KeySlotIds>::default();
367 let mut ctx = key_store.context_mut();
368
369 let v1_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
370 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
371
372 let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
373 .expect("Token creation should succeed");
374
375 let expected_wrapped_key1 = token.wrapped_user_key_1.to_string();
376 let expected_wrapped_key2 = token.wrapped_user_key_2.to_string();
377
378 let request_model: V2UpgradeTokenRequestModel = token.into();
379
380 assert_eq!(request_model.wrapped_user_key1, expected_wrapped_key1);
381 assert_eq!(request_model.wrapped_user_key2, expected_wrapped_key2);
382
383 request_model
384 .wrapped_user_key1
385 .parse::<EncString>()
386 .expect("wrapped_user_key1 should be a valid EncString");
387 request_model
388 .wrapped_user_key2
389 .parse::<EncString>()
390 .expect("wrapped_user_key2 should be a valid EncString");
391 }
392}