1use std::fmt;
2
3pub struct Secret(Vec<u8>);
4
5impl Secret {
6 pub fn new(value: impl Into<String>) -> Self {
7 Secret(value.into().into_bytes())
8 }
9
10 pub fn expose(&self) -> &str {
11 std::str::from_utf8(&self.0).unwrap_or_default()
12 }
13
14 fn wipe(&mut self) {
15 for byte in self.0.iter_mut() {
16 *byte = 0;
17 }
18 for slot in self.0.spare_capacity_mut() {
19 slot.write(0);
20 }
21 std::hint::black_box(&mut self.0);
22 }
23}
24
25impl fmt::Debug for Secret {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 f.write_str("Secret(hidden)")
28 }
29}
30
31impl Drop for Secret {
32 fn drop(&mut self) {
33 self.wipe();
34 }
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40
41 #[test]
42 fn a_wiped_secret_is_all_zeroes_up_to_its_capacity() {
43 let mut value = String::with_capacity(64);
44 value.push_str("s3cr3t-value-0042");
45 let mut secret = Secret::new(value);
46 assert_eq!(secret.expose(), "s3cr3t-value-0042");
47 let capacity = secret.0.capacity();
48 secret.wipe();
49 assert_eq!(secret.0.len(), 17);
50 assert_eq!(secret.0.capacity(), capacity);
51 assert!(secret.0.iter().all(|&b| b == 0));
52 assert!(!secret.expose().contains("s3cr3t"));
53 }
54}