Skip to main content

form_net/extract/
secret.rs

1//! `Secret`: a text field that is wiped on drop and never printed.
2
3use alloc::string::String;
4use core::fmt;
5
6use super::{FromField, ValueError};
7
8/// A text field that holds a secret (a password, a token).
9///
10/// No `Display`, no `Clone` (a clone is a second copy to wipe), no way to
11/// take the `String` out: [`expose`](Self::expose) is the only way to read
12/// it, which makes every read easy to find in review. The bytes are wiped
13/// on drop with crypt-io's volatile wipe; `Debug` prints `Secret(..)`.
14///
15/// # Examples
16///
17/// ```
18/// use form_net::Secret;
19///
20/// let s = Secret::from(String::from("hunter2"));
21/// assert_eq!(s.expose(), "hunter2");
22/// assert_eq!(format!("{s:?}"), "Secret(..)");
23/// ```
24pub struct Secret(String);
25
26impl Secret {
27    /// The secret text.
28    #[must_use]
29    pub fn expose(&self) -> &str {
30        &self.0
31    }
32
33    /// Its length in bytes.
34    #[must_use]
35    pub fn len(&self) -> usize {
36        self.0.len()
37    }
38
39    /// Whether it is empty.
40    #[must_use]
41    pub fn is_empty(&self) -> bool {
42        self.0.is_empty()
43    }
44}
45
46impl From<String> for Secret {
47    fn from(s: String) -> Secret {
48        Secret(s)
49    }
50}
51
52impl fmt::Debug for Secret {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        f.write_str("Secret(..)")
55    }
56}
57
58impl Drop for Secret {
59    fn drop(&mut self) {
60        crate::wipe::wipe_string(&mut self.0);
61    }
62}
63
64impl FromField for Secret {
65    type Slot = Option<Secret>;
66
67    fn accept_text(slot: &mut Self::Slot, value: &str) -> Result<(), ValueError> {
68        if slot.is_some() {
69            return Err(ValueError::Duplicate);
70        }
71        *slot = Some(Secret(String::from(value)));
72        Ok(())
73    }
74
75    fn finish(slot: Self::Slot) -> Result<Self, ValueError> {
76        slot.ok_or(ValueError::Missing)
77    }
78}