bitwarden_core/key_management/
v2_upgrade_token.rs1use bitwarden_api_api::models::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
144#[derive(Debug, Error)]
146pub enum V2UpgradeTokenError {
147 #[error("Decryption failed")]
149 DecryptionFailed,
150 #[error("Validation failed")]
152 ValidationFailed,
153 #[error("Serialization error")]
155 Serialization,
156 #[error("Wrong key type")]
158 WrongKeyType,
159 #[error("Key missing")]
161 KeyMissing,
162 #[error("Encryption failed")]
164 EncryptionFailed,
165 #[error("Response model malformed")]
167 ResponseModelMalformed,
168}
169
170#[cfg(test)]
171mod tests {
172 use bitwarden_crypto::{KeyStore, SymmetricKeyAlgorithm};
173
174 use super::*;
175 use crate::key_management::KeyIds;
176
177 #[test]
178 fn test_create_and_round_trip() {
179 let key_store = KeyStore::<KeyIds>::default();
180 let mut ctx = key_store.context_mut();
181
182 let v1_key_id = ctx.generate_symmetric_key();
184 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
185
186 let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
188 .expect("Token creation should succeed");
189
190 let serialized = serde_json::to_string(&token).expect("Serialization should succeed");
192 let deserialized: V2UpgradeToken =
193 serde_json::from_str(&serialized).expect("Deserialization should succeed");
194
195 let unwrapped_v2_id = deserialized
197 .unwrap_v2(v1_key_id, &mut ctx)
198 .expect("Unwrapping V2 should succeed");
199
200 #[allow(deprecated)]
202 let original_v2 = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap();
203 #[allow(deprecated)]
204 let unwrapped_v2 = ctx.dangerous_get_symmetric_key(unwrapped_v2_id).unwrap();
205 assert_eq!(original_v2, unwrapped_v2);
206 }
207
208 #[test]
209 fn test_unwrap_bidirectional() {
210 let key_store = KeyStore::<KeyIds>::default();
211 let mut ctx = key_store.context_mut();
212
213 let v1_key_id = ctx.generate_symmetric_key();
215 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
216
217 let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
219 .expect("Token creation should succeed");
220
221 let unwrapped_v2_id = token
223 .unwrap_v2(v1_key_id, &mut ctx)
224 .expect("Unwrapping V2 should succeed");
225
226 let unwrapped_v1_id = token
228 .unwrap_v1(unwrapped_v2_id, &mut ctx)
229 .expect("Unwrapping V1 should succeed");
230
231 #[allow(deprecated)]
233 let original_v1 = ctx.dangerous_get_symmetric_key(v1_key_id).unwrap();
234 #[allow(deprecated)]
235 let original_v2 = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap();
236 #[allow(deprecated)]
237 let unwrapped_v1 = ctx.dangerous_get_symmetric_key(unwrapped_v1_id).unwrap();
238 #[allow(deprecated)]
239 let unwrapped_v2 = ctx.dangerous_get_symmetric_key(unwrapped_v2_id).unwrap();
240
241 assert_eq!(original_v1, unwrapped_v1);
242 assert_eq!(original_v2, unwrapped_v2);
243 }
244
245 #[test]
246 fn test_create_wrong_key_type_error() {
247 let key_store = KeyStore::<KeyIds>::default();
248 let mut ctx = key_store.context_mut();
249
250 let v1_key_1 = ctx.generate_symmetric_key();
252 let v1_key_2 = ctx.generate_symmetric_key();
253
254 let result = V2UpgradeToken::create(v1_key_1, v1_key_2, &ctx);
255 assert!(matches!(result, Err(V2UpgradeTokenError::WrongKeyType)));
256 }
257
258 #[test]
259 fn test_serialization_round_trip() {
260 let key_store = KeyStore::<KeyIds>::default();
261 let mut ctx = key_store.context_mut();
262
263 let v1_key_id = ctx.generate_symmetric_key();
264 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
265
266 let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
267 .expect("Token creation should succeed");
268
269 let serialized = serde_json::to_string(&token).expect("Serialization should succeed");
271 let json: serde_json::Value =
272 serde_json::from_str(&serialized).expect("Should be valid JSON");
273 assert!(json.is_object());
274 assert!(json.get("wrapped_user_key_1").is_some());
275 assert!(json.get("wrapped_user_key_2").is_some());
276
277 let deserialized: V2UpgradeToken =
279 serde_json::from_str(&serialized).expect("Deserialization should succeed");
280 let reserialized =
281 serde_json::to_string(&deserialized).expect("Reserialization should succeed");
282 assert_eq!(serialized, reserialized);
283 }
284
285 fn build_response_model<Ids: bitwarden_crypto::KeySlotIds>(
286 v1_key_id: Ids::Symmetric,
287 v2_key_id: Ids::Symmetric,
288 ctx: &KeyStoreContext<Ids>,
289 ) -> V2UpgradeTokenResponseModel {
290 let wrapped_user_key_1 = ctx.wrap_symmetric_key(v2_key_id, v1_key_id).unwrap();
291 let wrapped_user_key_2 = ctx.wrap_symmetric_key(v1_key_id, v2_key_id).unwrap();
292 V2UpgradeTokenResponseModel {
293 wrapped_user_key1: Some(wrapped_user_key_1.to_string()),
294 wrapped_user_key2: Some(wrapped_user_key_2.to_string()),
295 }
296 }
297
298 #[test]
299 fn test_from_response_model_missing_wrapped_uk1() {
300 let response = V2UpgradeTokenResponseModel {
301 wrapped_user_key1: None,
302 wrapped_user_key2: None,
303 };
304 assert!(matches!(
305 V2UpgradeToken::try_from(&response),
306 Err(V2UpgradeTokenError::ResponseModelMalformed)
307 ));
308 }
309
310 #[test]
311 fn test_from_response_model_missing_wrapped_uk2() {
312 let key_store = KeyStore::<KeyIds>::default();
313 let mut ctx = key_store.context_mut();
314
315 let v1_key_id = ctx.generate_symmetric_key();
316 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
317
318 let mut response = build_response_model(v1_key_id, v2_key_id, &ctx);
319 response.wrapped_user_key2 = None;
320
321 assert!(matches!(
322 V2UpgradeToken::try_from(&response),
323 Err(V2UpgradeTokenError::ResponseModelMalformed)
324 ));
325 }
326
327 #[test]
328 fn test_serde_round_trip() {
329 let key_store = KeyStore::<KeyIds>::default();
330 let mut ctx = key_store.context_mut();
331
332 let v1_key_id = ctx.generate_symmetric_key();
333 let v2_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
334
335 let token = V2UpgradeToken::create(v1_key_id, v2_key_id, &ctx)
336 .expect("Token creation should succeed");
337
338 let serialized = serde_json::to_string(&token).expect("Serialization should succeed");
340
341 let deserialized: V2UpgradeToken =
343 serde_json::from_str(&serialized).expect("Deserialization should succeed");
344 let unwrapped_v2_id = deserialized
345 .unwrap_v2(v1_key_id, &mut ctx)
346 .expect("Unwrapping V2 from serde-deserialized token should succeed");
347
348 #[allow(deprecated)]
349 let original_v2 = ctx.dangerous_get_symmetric_key(v2_key_id).unwrap();
350 #[allow(deprecated)]
351 let unwrapped_v2 = ctx.dangerous_get_symmetric_key(unwrapped_v2_id).unwrap();
352 assert_eq!(original_v2, unwrapped_v2);
353 }
354}