openzeppelin_relayer/utils/serde/
repository_encryption.rs

1//! Helper functions to serialize and deserialize secrets as encrypted base64 for storage
2
3use secrets::SecretVec;
4use serde::{Deserialize, Deserializer, Serializer};
5
6use crate::{
7    models::SecretString,
8    utils::{base64_decode, base64_encode, decrypt_sensitive_field, encrypt_sensitive_field},
9};
10
11/// Helper function to serialize secrets as encrypted base64 for storage
12pub fn serialize_secret_vec<S>(secret: &SecretVec<u8>, serializer: S) -> Result<S::Ok, S::Error>
13where
14    S: Serializer,
15{
16    // First encode the raw secret as base64
17    let base64 = base64_encode(secret.borrow().as_ref());
18
19    // Then encrypt the base64 string for secure storage
20    let encrypted = encrypt_sensitive_field(&base64)
21        .map_err(|e| serde::ser::Error::custom(format!("Encryption failed: {}", e)))?;
22
23    serializer.serialize_str(&encrypted)
24}
25
26/// Helper function to deserialize secrets from encrypted base64 storage
27pub fn deserialize_secret_vec<'de, D>(deserializer: D) -> Result<SecretVec<u8>, D::Error>
28where
29    D: Deserializer<'de>,
30{
31    let encrypted_str = String::deserialize(deserializer)?;
32
33    // First decrypt the encrypted string to get the base64 string
34    let base64_str = decrypt_sensitive_field(&encrypted_str)
35        .map_err(|e| serde::de::Error::custom(format!("Decryption failed: {}", e)))?;
36
37    // Then decode the base64 to get the raw secret bytes
38    let decoded = base64_decode(&base64_str)
39        .map_err(|e| serde::de::Error::custom(format!("Invalid base64: {}", e)))?;
40
41    Ok(SecretVec::new(decoded.len(), |v| {
42        v.copy_from_slice(&decoded)
43    }))
44}
45
46/// Helper function to serialize secrets as encrypted base64 for storage
47pub fn serialize_secret_string<S>(secret: &SecretString, serializer: S) -> Result<S::Ok, S::Error>
48where
49    S: Serializer,
50{
51    let secret_content = secret.to_str();
52    let encrypted = encrypt_sensitive_field(&secret_content)
53        .map_err(|e| serde::ser::Error::custom(format!("Encryption failed: {}", e)))?;
54
55    let encoded = base64_encode(encrypted.as_bytes());
56
57    serializer.serialize_str(&encoded)
58}
59
60/// Helper function to deserialize secrets from encrypted base64 storage
61pub fn deserialize_secret_string<'de, D>(deserializer: D) -> Result<SecretString, D::Error>
62where
63    D: Deserializer<'de>,
64{
65    let base64_str = String::deserialize(deserializer)?;
66
67    // First decode the base64 to get the encrypted bytes
68    let encrypted_bytes = base64_decode(&base64_str)
69        .map_err(|e| serde::de::Error::custom(format!("Invalid base64: {}", e)))?;
70
71    // Convert encrypted bytes back to string
72    let encrypted_str = String::from_utf8(encrypted_bytes)
73        .map_err(|e| serde::de::Error::custom(format!("Invalid UTF-8: {}", e)))?;
74
75    // Then decrypt the encrypted string to get the original content
76    let decrypted = decrypt_sensitive_field(&encrypted_str)
77        .map_err(|e| serde::de::Error::custom(format!("Decryption failed: {}", e)))?;
78
79    Ok(SecretString::new(&decrypted))
80}
81
82/// Helper function to serialize optional secrets as encrypted base64 for storage
83pub fn serialize_option_secret_string<S>(
84    secret: &Option<SecretString>,
85    serializer: S,
86) -> Result<S::Ok, S::Error>
87where
88    S: Serializer,
89{
90    match secret {
91        Some(secret_string) => {
92            let secret_content = secret_string.to_str();
93            let encrypted = encrypt_sensitive_field(&secret_content)
94                .map_err(|e| serde::ser::Error::custom(format!("Encryption failed: {}", e)))?;
95
96            let encoded = base64_encode(encrypted.as_bytes());
97            serializer.serialize_some(&encoded)
98        }
99        None => serializer.serialize_none(),
100    }
101}
102
103/// Helper function to deserialize optional secrets from encrypted base64 storage
104pub fn deserialize_option_secret_string<'de, D>(
105    deserializer: D,
106) -> Result<Option<SecretString>, D::Error>
107where
108    D: Deserializer<'de>,
109{
110    let opt_base64_str: Option<String> = Option::deserialize(deserializer)?;
111
112    match opt_base64_str {
113        Some(base64_str) => {
114            // First decode the base64 to get the encrypted bytes
115            let encrypted_bytes = base64_decode(&base64_str)
116                .map_err(|e| serde::de::Error::custom(format!("Invalid base64: {}", e)))?;
117
118            // Convert encrypted bytes back to string
119            let encrypted_str = String::from_utf8(encrypted_bytes)
120                .map_err(|e| serde::de::Error::custom(format!("Invalid UTF-8: {}", e)))?;
121
122            // Then decrypt the encrypted string to get the original content
123            let decrypted = decrypt_sensitive_field(&encrypted_str)
124                .map_err(|e| serde::de::Error::custom(format!("Decryption failed: {}", e)))?;
125
126            Ok(Some(SecretString::new(&decrypted)))
127        }
128        None => Ok(None),
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use secrets::SecretVec;
136    use serde_json;
137
138    #[test]
139    fn test_serialize_deserialize_secret_string() {
140        let secret = SecretString::new("test-secret-content");
141
142        // Create a test struct that uses the secret string serialization
143        #[derive(serde::Serialize, serde::Deserialize)]
144        struct TestStruct {
145            #[serde(
146                serialize_with = "serialize_secret_string",
147                deserialize_with = "deserialize_secret_string"
148            )]
149            secret: SecretString,
150        }
151
152        let test_struct = TestStruct {
153            secret: secret.clone(),
154        };
155
156        // Test serialization
157        let serialized = serde_json::to_string(&test_struct).unwrap();
158
159        // Test deserialization
160        let deserialized: TestStruct = serde_json::from_str(&serialized).unwrap();
161
162        // Verify content matches
163        assert_eq!(secret.to_str(), deserialized.secret.to_str());
164    }
165
166    #[test]
167    fn test_serialize_deserialize_secret_vec() {
168        let original_data = vec![1, 2, 3, 4, 5];
169        let secret = SecretVec::new(original_data.len(), |v| v.copy_from_slice(&original_data));
170
171        // Create a test struct that uses the secret vec serialization
172        #[derive(serde::Serialize, serde::Deserialize)]
173        struct TestStruct {
174            #[serde(
175                serialize_with = "serialize_secret_vec",
176                deserialize_with = "deserialize_secret_vec"
177            )]
178            secret_data: SecretVec<u8>,
179        }
180
181        let test_struct = TestStruct {
182            secret_data: secret,
183        };
184
185        // Test serialization
186        let serialized = serde_json::to_string(&test_struct).unwrap();
187
188        // Test deserialization
189        let deserialized: TestStruct = serde_json::from_str(&serialized).unwrap();
190
191        // Verify content matches
192        let original_borrowed = original_data;
193        let deserialized_borrowed = deserialized.secret_data.borrow();
194        assert_eq!(original_borrowed, *deserialized_borrowed);
195    }
196
197    #[test]
198    fn test_serialize_deserialize_option_secret_string_some() {
199        let secret = SecretString::new("test-optional-secret");
200
201        // Create a test struct that uses the option secret string serialization
202        #[derive(serde::Serialize, serde::Deserialize)]
203        struct TestStruct {
204            #[serde(
205                serialize_with = "serialize_option_secret_string",
206                deserialize_with = "deserialize_option_secret_string"
207            )]
208            optional_secret: Option<SecretString>,
209        }
210
211        let test_struct = TestStruct {
212            optional_secret: Some(secret.clone()),
213        };
214
215        // Test serialization
216        let serialized = serde_json::to_string(&test_struct).unwrap();
217
218        // Test deserialization
219        let deserialized: TestStruct = serde_json::from_str(&serialized).unwrap();
220
221        // Verify content matches
222        assert!(deserialized.optional_secret.is_some());
223        assert_eq!(
224            secret.to_str(),
225            deserialized.optional_secret.unwrap().to_str()
226        );
227    }
228
229    #[test]
230    fn test_serialize_deserialize_option_secret_string_none() {
231        let secret: Option<SecretString> = None;
232
233        // Create a test struct that uses the option secret string serialization
234        #[derive(serde::Serialize, serde::Deserialize)]
235        struct TestStruct {
236            #[serde(
237                serialize_with = "serialize_option_secret_string",
238                deserialize_with = "deserialize_option_secret_string"
239            )]
240            optional_secret: Option<SecretString>,
241        }
242
243        let test_struct = TestStruct {
244            optional_secret: secret,
245        };
246
247        // Test serialization
248        let serialized = serde_json::to_string(&test_struct).unwrap();
249
250        // Test deserialization
251        let deserialized: TestStruct = serde_json::from_str(&serialized).unwrap();
252
253        // Verify it's None
254        assert!(deserialized.optional_secret.is_none());
255    }
256
257    #[test]
258    fn test_round_trip_secret_string() {
259        let original = SecretString::new("complex-secret-with-special-chars-!@#$%^&*()");
260
261        #[derive(serde::Serialize, serde::Deserialize)]
262        struct TestStruct {
263            #[serde(
264                serialize_with = "serialize_secret_string",
265                deserialize_with = "deserialize_secret_string"
266            )]
267            secret: SecretString,
268        }
269
270        let test_struct = TestStruct {
271            secret: original.clone(),
272        };
273
274        // Serialize to JSON
275        let json = serde_json::to_string(&test_struct).unwrap();
276
277        // Deserialize back
278        let deserialized: TestStruct = serde_json::from_str(&json).unwrap();
279
280        // Verify the content is identical
281        assert_eq!(original.to_str(), deserialized.secret.to_str());
282    }
283
284    #[test]
285    fn test_round_trip_option_secret_string_with_multiple_values() {
286        let test_cases = vec![
287            Some(SecretString::new("test1")),
288            None,
289            Some(SecretString::new("")),
290            Some(SecretString::new("test-with-unicode-🔐")),
291            Some(SecretString::new(&"very-long-secret-".repeat(100))),
292        ];
293
294        #[derive(serde::Serialize, serde::Deserialize)]
295        struct TestStruct {
296            #[serde(
297                serialize_with = "serialize_option_secret_string",
298                deserialize_with = "deserialize_option_secret_string"
299            )]
300            optional_secret: Option<SecretString>,
301        }
302
303        for test_case in test_cases {
304            let test_struct = TestStruct {
305                optional_secret: test_case.clone(),
306            };
307
308            // Serialize to JSON
309            let json = serde_json::to_string(&test_struct).unwrap();
310
311            // Deserialize back
312            let deserialized: TestStruct = serde_json::from_str(&json).unwrap();
313
314            // Verify the content matches
315            match (test_case, deserialized.optional_secret) {
316                (Some(original), Some(deserialized_secret)) => {
317                    assert_eq!(original.to_str(), deserialized_secret.to_str());
318                }
319                (None, None) => {
320                    // Both are None, this is correct
321                }
322                _ => panic!("Mismatch between original and deserialized optional secret"),
323            }
324        }
325    }
326
327    #[test]
328    fn test_serialized_content_is_encrypted() {
329        let secret = SecretString::new("plaintext-secret");
330
331        #[derive(serde::Serialize)]
332        struct TestStruct {
333            #[serde(serialize_with = "serialize_secret_string")]
334            secret: SecretString,
335        }
336
337        let test_struct = TestStruct { secret };
338        let json = serde_json::to_string(&test_struct).unwrap();
339
340        // The serialized JSON should not contain the plaintext secret
341        assert!(!json.contains("plaintext-secret"));
342
343        // It should be base64 encoded (contains only valid base64 characters)
344        let json_value: serde_json::Value = serde_json::from_str(&json).unwrap();
345        let serialized_secret = json_value["secret"].as_str().unwrap();
346
347        // Verify it's valid base64 by attempting to decode it
348        assert!(base64_decode(serialized_secret).is_ok());
349    }
350
351    #[test]
352    fn test_serialized_option_content_when_some() {
353        let secret = Some(SecretString::new("plaintext-secret"));
354
355        #[derive(serde::Serialize)]
356        struct TestStruct {
357            #[serde(serialize_with = "serialize_option_secret_string")]
358            optional_secret: Option<SecretString>,
359        }
360
361        let test_struct = TestStruct {
362            optional_secret: secret,
363        };
364        let json = serde_json::to_string(&test_struct).unwrap();
365
366        // The serialized JSON should not contain the plaintext secret
367        assert!(!json.contains("plaintext-secret"));
368
369        // Parse the JSON to verify structure
370        let json_value: serde_json::Value = serde_json::from_str(&json).unwrap();
371        assert!(json_value["optional_secret"].is_string());
372
373        let serialized_secret = json_value["optional_secret"].as_str().unwrap();
374        // Verify it's valid base64
375        assert!(base64_decode(serialized_secret).is_ok());
376    }
377
378    #[test]
379    fn test_serialized_option_content_when_none() {
380        let secret: Option<SecretString> = None;
381
382        #[derive(serde::Serialize)]
383        struct TestStruct {
384            #[serde(serialize_with = "serialize_option_secret_string")]
385            optional_secret: Option<SecretString>,
386        }
387
388        let test_struct = TestStruct {
389            optional_secret: secret,
390        };
391        let json = serde_json::to_string(&test_struct).unwrap();
392
393        // Parse the JSON to verify structure
394        let json_value: serde_json::Value = serde_json::from_str(&json).unwrap();
395        assert!(json_value["optional_secret"].is_null());
396    }
397}