secret_vault_value/
value.rs1use std::fmt::{Debug, Display, Formatter};
2use std::future::Future;
3use std::str::Utf8Error;
4use zeroize::*;
5
6#[derive(Zeroize, ZeroizeOnDrop, Eq, PartialEq, Default, Hash)]
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 { (format!("{test_var_to_capture}{str}"), str) })
282 .await;
283
284 assert_eq!(
285 insecure_copy_str.as_str(),
286 format!(
287 "{}{}",
288 test_var_to_capture,
289 mock_secret.sensitive_value_to_str().unwrap()
290 )
291 );
292 }
293
294 #[tokio::test]
295 async fn exposed_function_vec_async() {
296 let mut runner = TestRunner::default();
297 let mock_secret = generate_secret_value()
298 .new_tree(&mut runner)
299 .unwrap()
300 .current();
301
302 let insecure_copy_vec = mock_secret
303 .exposed_in_as_vec_async(|vec| async { (vec.clone(), vec) })
304 .await;
305 assert_eq!(&insecure_copy_vec, mock_secret.ref_sensitive_value());
306
307 let insecure_copy_vec = mock_secret
308 .exposed_in_as_zvec_async(|vec| async move { vec.clone() })
309 .await;
310 assert_eq!(insecure_copy_vec.deref(), mock_secret.ref_sensitive_value());
311 }
312
313 #[tokio::test]
314 async fn exposed_function_vec_async_closure() {
315 let mut runner = TestRunner::default();
316 let mock_secret = generate_secret_value()
317 .new_tree(&mut runner)
318 .unwrap()
319 .current();
320
321 let test_var_to_capture: Vec<u8> = "test-captured".to_string().as_bytes().to_vec();
322
323 let insecure_copy_vec = mock_secret
324 .exposed_in_as_vec_async(|vec| async {
325 ([test_var_to_capture.clone(), vec.clone()].concat(), vec)
326 })
327 .await;
328
329 assert_eq!(
330 insecure_copy_vec,
331 [
332 test_var_to_capture.clone(),
333 mock_secret.ref_sensitive_value().to_vec()
334 ]
335 .concat()
336 );
337 }
338}