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}