secret_vault_value/
value.rs

1use std::fmt::{Debug, Display, Formatter};
2use std::future::Future;
3use std::str::Utf8Error;
4use zeroize::*;
5
6#[derive(Zeroize, ZeroizeOnDrop, Eq, PartialEq, Default)]
7pub struct SecretValue(Vec<u8>);
8
9impl SecretValue {
10    pub fn new(src: Vec<u8>) -> Self {
11        Self(src)
12    }
13
14    pub fn ref_sensitive_value(&self) -> &Vec<u8> {
15        &self.0
16    }
17
18    pub fn ref_sensitive_value_mut(&mut self) -> &mut Vec<u8> {
19        &mut self.0
20    }
21
22    pub fn sensitive_value_to_str(&self) -> Result<&str, Utf8Error> {
23        std::str::from_utf8(&self.0)
24    }
25
26    pub fn secure_clear(&mut self) {
27        self.0.zeroize();
28        self.0.clear();
29    }
30
31    pub fn as_sensitive_str(&self) -> &str {
32        self.sensitive_value_to_str().unwrap()
33    }
34
35    pub fn as_sensitive_bytes(&self) -> &[u8] {
36        self.ref_sensitive_value()
37    }
38
39    pub fn exposed_in_as_str<T, Z: Zeroize, FN>(&self, f: FN) -> T
40    where
41        FN: Fn(String) -> (T, Z),
42    {
43        let decoded_as_string = self.sensitive_value_to_str().unwrap().to_string();
44        let (result, mut zeroizable) = f(decoded_as_string);
45        zeroizable.zeroize();
46        result
47    }
48
49    pub fn exposed_in_as_zstr<T, FN>(&self, f: FN) -> T
50    where
51        FN: Fn(Zeroizing<String>) -> T,
52    {
53        let decoded_as_string = Zeroizing::new(self.sensitive_value_to_str().unwrap().to_string());
54        f(decoded_as_string)
55    }
56
57    pub fn exposed_in_as_vec<T, Z: Zeroize, FN>(&self, f: FN) -> T
58    where
59        FN: Fn(Vec<u8>) -> (T, Z),
60    {
61        let (result, mut zeroizable) = f(self.0.clone());
62        zeroizable.zeroize();
63        result
64    }
65
66    pub fn exposed_in_as_zvec<T, FN>(&self, f: FN) -> T
67    where
68        FN: Fn(Zeroizing<Vec<u8>>) -> T,
69    {
70        f(Zeroizing::new(self.0.clone()))
71    }
72
73    pub async fn exposed_in_as_str_async<T, Z: Zeroize, FN, FI>(&self, f: FN) -> T
74    where
75        FN: Fn(String) -> FI,
76        FI: Future<Output = (T, Z)>,
77    {
78        let decoded_as_string = self.sensitive_value_to_str().unwrap().to_string();
79        let (result, mut zeroizable) = f(decoded_as_string).await;
80        zeroizable.zeroize();
81        result
82    }
83
84    pub async fn exposed_in_as_zstr_async<T, FN, FI>(&self, f: FN) -> T
85    where
86        FN: Fn(Zeroizing<String>) -> FI,
87        FI: Future<Output = T>,
88    {
89        let decoded_as_string = Zeroizing::new(self.sensitive_value_to_str().unwrap().to_string());
90        f(decoded_as_string).await
91    }
92
93    pub async fn exposed_in_as_vec_async<T, Z: Zeroize, FN, FI>(&self, f: FN) -> T
94    where
95        FN: Fn(Vec<u8>) -> FI,
96        FI: Future<Output = (T, Z)>,
97    {
98        let (result, mut zeroizable) = f(self.0.clone()).await;
99        zeroizable.zeroize();
100        result
101    }
102
103    pub async fn exposed_in_as_zvec_async<T, FN, FI>(&self, f: FN) -> T
104    where
105        FN: Fn(Zeroizing<Vec<u8>>) -> FI,
106        FI: Future<Output = T>,
107    {
108        f(Zeroizing::new(self.0.clone())).await
109    }
110}
111
112impl From<String> for SecretValue {
113    fn from(mut str: String) -> Self {
114        let result = Self(str.as_bytes().to_vec());
115        str.zeroize();
116        result
117    }
118}
119
120impl From<&mut String> for SecretValue {
121    fn from(str: &mut String) -> Self {
122        let result = Self(str.as_bytes().to_vec());
123        str.zeroize();
124        result
125    }
126}
127
128impl From<&Zeroizing<String>> for SecretValue {
129    fn from(str: &Zeroizing<String>) -> Self {
130        Self(str.as_bytes().to_vec())
131    }
132}
133
134impl From<Vec<u8>> for SecretValue {
135    fn from(vec: Vec<u8>) -> Self {
136        Self(vec)
137    }
138}
139
140impl From<&mut Vec<u8>> for SecretValue {
141    fn from(vec: &mut Vec<u8>) -> Self {
142        let result = Self(vec.clone());
143        vec.zeroize();
144        vec.clear();
145        result
146    }
147}
148
149impl From<&Zeroizing<Vec<u8>>> for SecretValue {
150    fn from(vec: &Zeroizing<Vec<u8>>) -> Self {
151        Self(vec.to_vec())
152    }
153}
154
155impl From<&str> for SecretValue {
156    fn from(str: &str) -> Self {
157        Self(str.as_bytes().to_vec())
158    }
159}
160
161impl Clone for SecretValue {
162    fn clone(&self) -> Self {
163        SecretValue::new(self.ref_sensitive_value().clone())
164    }
165}
166
167impl Display for SecretValue {
168    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
169        write!(f, "***")
170    }
171}
172
173impl Debug for SecretValue {
174    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
175        write!(f, "***")
176    }
177}
178
179#[cfg(test)]
180mod test {
181    use super::*;
182    use proptest::prelude::*;
183    use proptest::test_runner::TestRunner;
184    use std::ops::Deref;
185
186    fn generate_secret_value() -> BoxedStrategy<SecretValue> {
187        ("[a-zA-Z0-9]*")
188            .prop_map(|(mock_secret_str)| SecretValue::new(mock_secret_str.as_bytes().to_vec()))
189            .boxed()
190    }
191
192    proptest! {
193        #[test]
194        fn secret_is_not_leaking_in_fmt(mock_secret_value in generate_secret_value()) {
195            assert_eq!(format!("{}",mock_secret_value), "***");
196            assert_eq!(format!("{:?}",mock_secret_value), "***");
197            assert_eq!(format!("{:#?}",mock_secret_value), "***");
198        }
199
200        #[test]
201        fn secret_follows_partial_eq(mock_secret_str in "[a-zA-Z0-9]*") {
202            let mock_secret1 = SecretValue::new(mock_secret_str.as_bytes().to_vec());
203            let mock_secret2 = SecretValue::new(mock_secret_str.as_bytes().to_vec());
204            assert_eq!(mock_secret1, mock_secret2);
205        }
206
207        #[test]
208        fn exposed_function_str(mock_secret_value in generate_secret_value())  {
209            let insecure_copy_str =
210            mock_secret_value.exposed_in_as_str(|str| {
211                (str.clone(), str)
212            });
213            assert_eq!(insecure_copy_str.as_str(), mock_secret_value.sensitive_value_to_str().unwrap());
214        }
215
216        #[test]
217        fn exposed_function_zstr(mock_secret_value in generate_secret_value())  {
218            let insecure_copy_str =
219            mock_secret_value.exposed_in_as_zstr(|str| {
220                str.clone()
221            });
222            assert_eq!(insecure_copy_str.as_str(), mock_secret_value.sensitive_value_to_str().unwrap());
223        }
224
225        #[test]
226        fn exposed_function_vec(mock_secret_value in generate_secret_value())  {
227            let insecure_copy_vec =
228                mock_secret_value.exposed_in_as_vec(|vec| {
229                    (vec.clone(), vec)
230                });
231            assert_eq!(&insecure_copy_vec, mock_secret_value.ref_sensitive_value());
232        }
233
234
235        #[test]
236        fn exposed_function_zvec(mock_secret_value in generate_secret_value())  {
237            let insecure_copy_vec =
238                mock_secret_value.exposed_in_as_zvec(|vec| {
239                    vec.clone()
240                });
241            assert_eq!(insecure_copy_vec.deref(), mock_secret_value.ref_sensitive_value());
242        }
243    }
244
245    #[tokio::test]
246    async fn exposed_function_str_async() {
247        let mut runner = TestRunner::default();
248        let mock_secret = generate_secret_value()
249            .new_tree(&mut runner)
250            .unwrap()
251            .current();
252
253        let insecure_copy_str = mock_secret
254            .exposed_in_as_str_async(|str| async { (str.clone(), str) })
255            .await;
256        assert_eq!(
257            insecure_copy_str.as_str(),
258            mock_secret.sensitive_value_to_str().unwrap()
259        );
260
261        let insecure_copy_str = mock_secret
262            .exposed_in_as_zstr_async(|str| async move { str.clone() })
263            .await;
264        assert_eq!(
265            insecure_copy_str.as_str(),
266            mock_secret.sensitive_value_to_str().unwrap()
267        );
268    }
269
270    #[tokio::test]
271    async fn exposed_function_str_async_closure() {
272        let mut runner = TestRunner::default();
273        let mock_secret = generate_secret_value()
274            .new_tree(&mut runner)
275            .unwrap()
276            .current();
277
278        let test_var_to_capture: String = "test-captured".to_string();
279
280        let insecure_copy_str = mock_secret
281            .exposed_in_as_str_async(|str| async {
282                (format!("{}{}", test_var_to_capture, str), str)
283            })
284            .await;
285
286        assert_eq!(
287            insecure_copy_str.as_str(),
288            format!(
289                "{}{}",
290                test_var_to_capture,
291                mock_secret.sensitive_value_to_str().unwrap()
292            )
293        );
294    }
295
296    #[tokio::test]
297    async fn exposed_function_vec_async() {
298        let mut runner = TestRunner::default();
299        let mock_secret = generate_secret_value()
300            .new_tree(&mut runner)
301            .unwrap()
302            .current();
303
304        let insecure_copy_vec = mock_secret
305            .exposed_in_as_vec_async(|vec| async { (vec.clone(), vec) })
306            .await;
307        assert_eq!(&insecure_copy_vec, mock_secret.ref_sensitive_value());
308
309        let insecure_copy_vec = mock_secret
310            .exposed_in_as_zvec_async(|vec| async move { vec.clone() })
311            .await;
312        assert_eq!(insecure_copy_vec.deref(), mock_secret.ref_sensitive_value());
313    }
314
315    #[tokio::test]
316    async fn exposed_function_vec_async_closure() {
317        let mut runner = TestRunner::default();
318        let mock_secret = generate_secret_value()
319            .new_tree(&mut runner)
320            .unwrap()
321            .current();
322
323        let test_var_to_capture: Vec<u8> = "test-captured".to_string().as_bytes().to_vec();
324
325        let insecure_copy_vec = mock_secret
326            .exposed_in_as_vec_async(|vec| async {
327                ([test_var_to_capture.clone(), vec.clone()].concat(), vec)
328            })
329            .await;
330
331        assert_eq!(
332            insecure_copy_vec,
333            [
334                test_var_to_capture.clone(),
335                mock_secret.ref_sensitive_value().to_vec()
336            ]
337            .concat()
338        );
339    }
340}