Skip to main content

bitwarden_sensitive_value/
sensitive.rs

1/// A wrapper type that marks the inner value as secret. This is used to prevent accidental logging
2/// of secrets by overriding the `Debug` and `Display` implementations to redact the value. This
3/// redaction can be bypassed by enabling the `dangerous-crypto-debug` feature.
4pub struct Sensitive<T>(pub(crate) T);
5
6/// A trait for types that can expose their inner secret value. This is implemented for
7/// `Sensitive<T>` and can be implemented for other wrapper types as needed. The `expose` and
8/// `expose_owned` methods are intentionally explicit and require justification in comments to
9/// prevent accidental misuse.
10pub trait ExposeSensitive {
11    /// The type of the inner value that is being wrapped. This is used to define the return type of
12    /// the `expose` and `expose_owned` methods.
13    type Exposed;
14
15    /// Explicitly borrow the secret value. This exposes the secret to logging. This should be used
16    /// exactly only when interacting with APIs we do not control. Each usage of `expose` MUST have
17    /// a comment justifying why it is necessary and acknowledging that the appropriate checks have
18    /// been performed.
19    fn expose(&self) -> &Self::Exposed;
20
21    /// Consume the wrapper and return the inner value. This exposes the secret to logging. This
22    /// should be used exactly only when interacting with APIs we do not control. Each usage of
23    /// `expose` MUST have a comment justifying why it is necessary and acknowledging that the
24    /// appropriate checks have been performed.
25    fn expose_owned(self) -> Self::Exposed;
26}
27
28impl<T> ExposeSensitive for Sensitive<T> {
29    type Exposed = T;
30
31    /// Explicitly borrow the wrapped value. This exposes the secret to logging. This should
32    /// be used exactly only when interacting with APIs we do not control. Each usage of expose
33    /// MUST have a comment justifying why it is necessary and acknowledging that the appropriate
34    /// checks have been performed.
35    /// ```
36    /// use bitwarden_sensitive_value::{ExposeSensitive, Sensitive};
37    /// # fn pbkdf2(_password: &[u8], _salt: &[u8], _rounds: u32) -> [u8; 32] { [0u8; 32] }
38    /// fn hash_password(password: &Sensitive<String>) -> [u8; 32] {
39    ///    // EXPOSE: We need to pass the password to pbkdf2, because `pbkdf2` does not support the
40    ///    // sensitive type and is an external crate. It is safe to do so because the library does
41    ///    // not log data.
42    ///    let exposed = password.expose();
43    ///    pbkdf2(exposed.as_bytes(), b"salt", 100_000)
44    /// }
45    /// ```
46    fn expose(&self) -> &T {
47        &self.0
48    }
49
50    /// Consume the wrapper and return the inner value. This exposes the secret to logging.
51    fn expose_owned(self) -> T {
52        self.0
53    }
54}
55
56impl<T> PartialEq for Sensitive<T>
57where
58    T: PartialEq,
59{
60    fn eq(&self, other: &Self) -> bool {
61        self.0.eq(&other.0)
62    }
63}
64
65impl<T> Clone for Sensitive<T>
66where
67    T: Clone,
68{
69    fn clone(&self) -> Self {
70        Self(self.0.clone())
71    }
72}
73
74impl<T> From<T> for Sensitive<T> {
75    fn from(value: T) -> Self {
76        Self(value)
77    }
78}
79
80impl<T: serde::Serialize> serde::Serialize for Sensitive<T> {
81    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
82        self.0.serialize(serializer)
83    }
84}
85
86impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for Sensitive<T> {
87    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
88        Ok(Self(T::deserialize(deserializer)?))
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn from_wraps_value_and_expose_returns_it() {
98        let sensitive = Sensitive::from(42u32);
99        assert_eq!(sensitive.expose(), &42);
100        assert_eq!(sensitive.expose_owned(), 42);
101    }
102
103    #[test]
104    fn from_str_creates_sensitive_string() {
105        let sensitive: Sensitive<String> = Sensitive::from("secret");
106        assert_eq!(sensitive.expose(), "secret");
107    }
108
109    #[test]
110    fn partial_eq_compares_inner_values() {
111        assert_eq!(Sensitive::from(1u32), Sensitive::from(1u32));
112        assert_ne!(Sensitive::from(1u32), Sensitive::from(2u32));
113    }
114
115    #[cfg(not(feature = "dangerous-crypto-debug"))]
116    #[test]
117    fn debug_is_redacted() {
118        let sensitive = Sensitive::from("secret".to_string());
119        assert_eq!(format!("{sensitive:?}"), "[REDACTED]");
120    }
121
122    #[cfg(not(feature = "dangerous-crypto-debug"))]
123    #[test]
124    fn display_is_redacted() {
125        let sensitive = Sensitive::from("secret".to_string());
126        assert_eq!(format!("{sensitive}"), "[REDACTED]");
127    }
128
129    #[test]
130    fn serde_json_round_trips_transparently() {
131        let sensitive = Sensitive::from("secret".to_string());
132
133        let serialized = serde_json::to_string(&sensitive).unwrap();
134        assert_eq!(serialized, "\"secret\"");
135
136        let deserialized: Sensitive<String> = serde_json::from_str(&serialized).unwrap();
137        assert_eq!(deserialized, sensitive);
138    }
139}