openzeppelin_relayer/utils/serde/
repository_encryption.rs1use secrets::SecretVec;
17use serde::{Deserialize, Deserializer, Serializer};
18
19use crate::{
20 models::SecretString,
21 utils::{
22 base64_decode, base64_encode, decrypt_sensitive_field_auto,
23 encrypt_sensitive_field_with_aad,
24 },
25};
26
27pub fn serialize_secret_vec<S>(secret: &SecretVec<u8>, serializer: S) -> Result<S::Ok, S::Error>
32where
33 S: Serializer,
34{
35 let base64 = base64_encode(secret.borrow().as_ref());
37
38 let encrypted = encrypt_sensitive_field_with_aad(&base64)
40 .map_err(|e| serde::ser::Error::custom(format!("Encryption failed: {e}")))?;
41
42 serializer.serialize_str(&encrypted)
43}
44
45pub fn deserialize_secret_vec<'de, D>(deserializer: D) -> Result<SecretVec<u8>, D::Error>
50where
51 D: Deserializer<'de>,
52{
53 let encrypted_str = String::deserialize(deserializer)?;
54
55 let base64_str = decrypt_sensitive_field_auto(&encrypted_str)
57 .map_err(|e| serde::de::Error::custom(format!("Decryption failed: {e}")))?;
58
59 let decoded = base64_decode(&base64_str)
61 .map_err(|e| serde::de::Error::custom(format!("Invalid base64: {e}")))?;
62
63 Ok(SecretVec::new(decoded.len(), |v| {
64 v.copy_from_slice(&decoded)
65 }))
66}
67
68pub fn serialize_secret_string<S>(secret: &SecretString, serializer: S) -> Result<S::Ok, S::Error>
73where
74 S: Serializer,
75{
76 let secret_content = secret.to_str();
77
78 let encrypted = encrypt_sensitive_field_with_aad(&secret_content)
80 .map_err(|e| serde::ser::Error::custom(format!("Encryption failed: {e}")))?;
81
82 serializer.serialize_str(&encrypted)
83}
84
85pub fn deserialize_secret_string<'de, D>(deserializer: D) -> Result<SecretString, D::Error>
94where
95 D: Deserializer<'de>,
96{
97 let value_str = String::deserialize(deserializer)?;
98
99 if let Ok(decrypted) = decrypt_sensitive_field_auto(&value_str) {
101 return Ok(SecretString::new(&decrypted));
102 }
103
104 Ok(SecretString::new(&value_str))
106}
107
108pub fn serialize_option_secret_string<S>(
113 secret: &Option<SecretString>,
114 serializer: S,
115) -> Result<S::Ok, S::Error>
116where
117 S: Serializer,
118{
119 match secret {
120 Some(secret_string) => {
121 let secret_content = secret_string.to_str();
122
123 let encrypted = encrypt_sensitive_field_with_aad(&secret_content)
125 .map_err(|e| serde::ser::Error::custom(format!("Encryption failed: {e}")))?;
126
127 serializer.serialize_some(&encrypted)
128 }
129 None => serializer.serialize_none(),
130 }
131}
132
133pub fn deserialize_option_secret_string<'de, D>(
142 deserializer: D,
143) -> Result<Option<SecretString>, D::Error>
144where
145 D: Deserializer<'de>,
146{
147 let opt_value_str: Option<String> = Option::deserialize(deserializer)?;
148
149 match opt_value_str {
150 Some(value_str) => {
151 if let Ok(decrypted) = decrypt_sensitive_field_auto(&value_str) {
153 return Ok(Some(SecretString::new(&decrypted)));
154 }
155
156 Ok(Some(SecretString::new(&value_str)))
158 }
159 None => Ok(None),
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use crate::utils::EncryptionContext;
167 use secrets::SecretVec;
168 use serde_json;
169
170 fn test_aad() -> String {
171 "test-context".to_string()
172 }
173
174 #[test]
175 fn test_serialize_deserialize_secret_string() {
176 let secret = SecretString::new("test-secret-content");
177
178 #[derive(serde::Serialize, serde::Deserialize)]
180 struct TestStruct {
181 #[serde(
182 serialize_with = "serialize_secret_string",
183 deserialize_with = "deserialize_secret_string"
184 )]
185 secret: SecretString,
186 }
187
188 let test_struct = TestStruct {
189 secret: secret.clone(),
190 };
191
192 let serialized = EncryptionContext::with_aad_sync(test_aad(), || {
194 serde_json::to_string(&test_struct).unwrap()
195 });
196
197 let deserialized: TestStruct = EncryptionContext::with_aad_sync(test_aad(), || {
199 serde_json::from_str(&serialized).unwrap()
200 });
201
202 assert_eq!(secret.to_str(), deserialized.secret.to_str());
204 }
205
206 #[test]
207 fn test_serialize_deserialize_secret_vec() {
208 let original_data = vec![1, 2, 3, 4, 5];
209 let secret = SecretVec::new(original_data.len(), |v| v.copy_from_slice(&original_data));
210
211 #[derive(serde::Serialize, serde::Deserialize)]
213 struct TestStruct {
214 #[serde(
215 serialize_with = "serialize_secret_vec",
216 deserialize_with = "deserialize_secret_vec"
217 )]
218 secret_data: SecretVec<u8>,
219 }
220
221 let test_struct = TestStruct {
222 secret_data: secret,
223 };
224
225 let serialized = EncryptionContext::with_aad_sync(test_aad(), || {
227 serde_json::to_string(&test_struct).unwrap()
228 });
229
230 let deserialized: TestStruct = EncryptionContext::with_aad_sync(test_aad(), || {
232 serde_json::from_str(&serialized).unwrap()
233 });
234
235 let original_borrowed = original_data;
237 let deserialized_borrowed = deserialized.secret_data.borrow();
238 assert_eq!(original_borrowed, *deserialized_borrowed);
239 }
240
241 #[test]
242 fn test_serialize_deserialize_option_secret_string_some() {
243 let secret = SecretString::new("test-optional-secret");
244
245 #[derive(serde::Serialize, serde::Deserialize)]
247 struct TestStruct {
248 #[serde(
249 serialize_with = "serialize_option_secret_string",
250 deserialize_with = "deserialize_option_secret_string"
251 )]
252 optional_secret: Option<SecretString>,
253 }
254
255 let test_struct = TestStruct {
256 optional_secret: Some(secret.clone()),
257 };
258
259 let serialized = EncryptionContext::with_aad_sync(test_aad(), || {
261 serde_json::to_string(&test_struct).unwrap()
262 });
263
264 let deserialized: TestStruct = EncryptionContext::with_aad_sync(test_aad(), || {
266 serde_json::from_str(&serialized).unwrap()
267 });
268
269 assert!(deserialized.optional_secret.is_some());
271 assert_eq!(
272 secret.to_str(),
273 deserialized.optional_secret.unwrap().to_str()
274 );
275 }
276
277 #[test]
278 fn test_serialize_deserialize_option_secret_string_none() {
279 let secret: Option<SecretString> = None;
280
281 #[derive(serde::Serialize, serde::Deserialize)]
283 struct TestStruct {
284 #[serde(
285 serialize_with = "serialize_option_secret_string",
286 deserialize_with = "deserialize_option_secret_string"
287 )]
288 optional_secret: Option<SecretString>,
289 }
290
291 let test_struct = TestStruct {
292 optional_secret: secret,
293 };
294
295 let serialized = EncryptionContext::with_aad_sync(test_aad(), || {
297 serde_json::to_string(&test_struct).unwrap()
298 });
299
300 let deserialized: TestStruct = EncryptionContext::with_aad_sync(test_aad(), || {
302 serde_json::from_str(&serialized).unwrap()
303 });
304
305 assert!(deserialized.optional_secret.is_none());
307 }
308
309 #[test]
310 fn test_round_trip_secret_string() {
311 let original = SecretString::new("complex-secret-with-special-chars-!@#$%^&*()");
312
313 #[derive(serde::Serialize, serde::Deserialize)]
314 struct TestStruct {
315 #[serde(
316 serialize_with = "serialize_secret_string",
317 deserialize_with = "deserialize_secret_string"
318 )]
319 secret: SecretString,
320 }
321
322 let test_struct = TestStruct {
323 secret: original.clone(),
324 };
325
326 let json = EncryptionContext::with_aad_sync(test_aad(), || {
328 serde_json::to_string(&test_struct).unwrap()
329 });
330
331 let deserialized: TestStruct =
333 EncryptionContext::with_aad_sync(test_aad(), || serde_json::from_str(&json).unwrap());
334
335 assert_eq!(original.to_str(), deserialized.secret.to_str());
337 }
338
339 #[test]
340 fn test_round_trip_option_secret_string_with_multiple_values() {
341 let test_cases = vec![
342 Some(SecretString::new("test1")),
343 None,
344 Some(SecretString::new("")),
345 Some(SecretString::new("test-with-unicode-🔐")),
346 Some(SecretString::new(&"very-long-secret-".repeat(100))),
347 ];
348
349 #[derive(serde::Serialize, serde::Deserialize)]
350 struct TestStruct {
351 #[serde(
352 serialize_with = "serialize_option_secret_string",
353 deserialize_with = "deserialize_option_secret_string"
354 )]
355 optional_secret: Option<SecretString>,
356 }
357
358 for test_case in test_cases {
359 let test_struct = TestStruct {
360 optional_secret: test_case.clone(),
361 };
362
363 let json = EncryptionContext::with_aad_sync(test_aad(), || {
365 serde_json::to_string(&test_struct).unwrap()
366 });
367
368 let deserialized: TestStruct = EncryptionContext::with_aad_sync(test_aad(), || {
370 serde_json::from_str(&json).unwrap()
371 });
372
373 match (test_case, deserialized.optional_secret) {
375 (Some(original), Some(deserialized_secret)) => {
376 assert_eq!(original.to_str(), deserialized_secret.to_str());
377 }
378 (None, None) => {
379 }
381 _ => panic!("Mismatch between original and deserialized optional secret"),
382 }
383 }
384 }
385
386 #[test]
387 fn test_serialized_content_is_encrypted() {
388 let secret = SecretString::new("plaintext-secret");
389
390 #[derive(serde::Serialize)]
391 struct TestStruct {
392 #[serde(serialize_with = "serialize_secret_string")]
393 secret: SecretString,
394 }
395
396 let test_struct = TestStruct { secret };
397
398 let json = EncryptionContext::with_aad_sync(test_aad(), || {
400 serde_json::to_string(&test_struct).unwrap()
401 });
402
403 assert!(!json.contains("plaintext-secret"));
405
406 let json_value: serde_json::Value = serde_json::from_str(&json).unwrap();
408 let serialized_secret = json_value["secret"].as_str().unwrap();
409
410 assert!(base64_decode(serialized_secret).is_ok());
412 }
413
414 #[test]
415 fn test_serialized_option_content_when_some() {
416 let secret = Some(SecretString::new("plaintext-secret"));
417
418 #[derive(serde::Serialize)]
419 struct TestStruct {
420 #[serde(serialize_with = "serialize_option_secret_string")]
421 optional_secret: Option<SecretString>,
422 }
423
424 let test_struct = TestStruct {
425 optional_secret: secret,
426 };
427
428 let json = EncryptionContext::with_aad_sync(test_aad(), || {
430 serde_json::to_string(&test_struct).unwrap()
431 });
432
433 assert!(!json.contains("plaintext-secret"));
435
436 let json_value: serde_json::Value = serde_json::from_str(&json).unwrap();
438 assert!(json_value["optional_secret"].is_string());
439
440 let serialized_secret = json_value["optional_secret"].as_str().unwrap();
441 assert!(base64_decode(serialized_secret).is_ok());
443 }
444
445 #[test]
446 fn test_serialized_option_content_when_none() {
447 let secret: Option<SecretString> = None;
448
449 #[derive(serde::Serialize)]
450 struct TestStruct {
451 #[serde(serialize_with = "serialize_option_secret_string")]
452 optional_secret: Option<SecretString>,
453 }
454
455 let test_struct = TestStruct {
456 optional_secret: secret,
457 };
458
459 let json = EncryptionContext::with_aad_sync(test_aad(), || {
461 serde_json::to_string(&test_struct).unwrap()
462 });
463
464 let json_value: serde_json::Value = serde_json::from_str(&json).unwrap();
466 assert!(json_value["optional_secret"].is_null());
467 }
468
469 #[test]
470 fn test_serialize_fails_without_context() {
471 let secret = SecretString::new("test-secret");
472
473 #[derive(serde::Serialize)]
474 struct TestStruct {
475 #[serde(serialize_with = "serialize_secret_string")]
476 secret: SecretString,
477 }
478
479 let test_struct = TestStruct { secret };
480
481 let result = serde_json::to_string(&test_struct);
483 assert!(result.is_err());
484 assert!(result
485 .unwrap_err()
486 .to_string()
487 .contains("EncryptionContext not set"));
488 }
489
490 #[test]
491 fn test_deserialize_plain_text_backward_compatibility() {
492 #[derive(serde::Deserialize)]
494 struct TestStruct {
495 #[serde(deserialize_with = "deserialize_secret_string")]
496 project_id: SecretString,
497 #[serde(deserialize_with = "deserialize_secret_string")]
498 location: SecretString,
499 }
500
501 let old_json = r#"{"project_id":"my-project-123","location":"us-central1"}"#;
503
504 let deserialized: TestStruct = serde_json::from_str(old_json).unwrap();
506
507 assert_eq!(*deserialized.project_id.to_str(), "my-project-123");
508 assert_eq!(*deserialized.location.to_str(), "us-central1");
509 }
510
511 #[test]
512 fn test_deserialize_option_plain_text_backward_compatibility() {
513 #[derive(serde::Deserialize)]
515 struct TestStruct {
516 #[serde(deserialize_with = "deserialize_option_secret_string")]
517 field: Option<SecretString>,
518 }
519
520 let json = r#"{"field":"plain-value-with-hyphen"}"#;
522 let deserialized: TestStruct = serde_json::from_str(json).unwrap();
523 assert_eq!(
524 *deserialized.field.unwrap().to_str(),
525 "plain-value-with-hyphen"
526 );
527
528 let json = r#"{"field":null}"#;
530 let deserialized: TestStruct = serde_json::from_str(json).unwrap();
531 assert!(deserialized.field.is_none());
532 }
533
534 #[test]
535 fn test_mixed_format_deserialization() {
536 #[derive(serde::Deserialize)]
538 struct TestStruct {
539 #[serde(deserialize_with = "deserialize_secret_string")]
540 plain_field: SecretString,
541 #[serde(deserialize_with = "deserialize_secret_string")]
542 encrypted_field: SecretString,
543 }
544
545 let encrypted_value = EncryptionContext::with_aad_sync(test_aad(), || {
547 #[derive(serde::Serialize)]
548 struct TempStruct {
549 #[serde(serialize_with = "serialize_secret_string")]
550 field: SecretString,
551 }
552 let temp = TempStruct {
553 field: SecretString::new("encrypted-content"),
554 };
555 serde_json::to_string(&temp).unwrap()
556 });
557
558 let encrypted_value_parsed: serde_json::Value =
559 serde_json::from_str(&encrypted_value).unwrap();
560 let encrypted_field_value = encrypted_value_parsed["field"].as_str().unwrap();
561
562 let mixed_json = format!(
564 r#"{{"plain_field":"plain-text-value","encrypted_field":"{encrypted_field_value}"}}"#
565 );
566
567 let deserialized: TestStruct = EncryptionContext::with_aad_sync(test_aad(), || {
569 serde_json::from_str(&mixed_json).unwrap()
570 });
571
572 assert_eq!(*deserialized.plain_field.to_str(), "plain-text-value");
573 assert_eq!(*deserialized.encrypted_field.to_str(), "encrypted-content");
574 }
575}