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)]
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}