11//! A configuration value that must not be printed.
22
33use std:: fmt;
4- use std:: ops:: Deref ;
54
6- /// The text a redacted secret renders as.
5+ /// What a secret renders as instead of its value .
76const REDACTED : & str = "[redacted]" ;
87
98/// A string that redacts itself when printed or serialised.
109///
11- /// Redacted by construction rather than by a hand-written `Debug` on each holder: a struct grows
12- /// fields, and a `Debug` listing them by hand goes stale the first time someone forgets one.
13- ///
14- /// Serialising redacts as well, which is what output that might be logged needs. [`Secret::revealed`]
15- /// opts out for one serialisation, and only for that one.
10+ /// The value is reachable only through [`Secret::expose_secret`], named so that reading it is a visible act.
11+ /// There is deliberately no `Deref` or `AsRef`, which would let it out silently; `rustls` guards a
12+ /// private key the same way, with `secret_der` as the only way in.
1613#[ derive( Clone , Default , PartialEq , Eq , Hash ) ]
1714pub struct Secret ( String ) ;
1815
@@ -23,32 +20,16 @@ impl Secret {
2320 }
2421
2522 /// The value itself, for the code that has to use it.
26- pub fn expose ( & self ) -> & str {
23+ ///
24+ /// Named in full so that a call site reads as the deliberate act it is, following the convention
25+ /// the `secrecy` crate set.
26+ pub fn expose_secret ( & self ) -> & str {
2727 & self . 0
2828 }
2929
30- /// Borrow this secret for a single serialisation in clear.
31- ///
32- /// The borrow is what makes it safe: revealing is a property of one call site, not of the value,
33- /// so it cannot be carried along by a clone or outlive the expression that asked for it.
34- ///
35- /// For output that is itself protected and has to be read back. `Debug` still redacts, because a
36- /// log is never that output.
37- pub fn revealed ( & self ) -> Revealed < ' _ > {
38- Revealed ( self )
39- }
40- }
41-
42- /// A [`Secret`] borrowed for one serialisation in clear. See [`Secret::revealed`].
43- ///
44- /// Deliberately not `Copy` or `Clone`: serialising takes it by reference, so nothing needs to
45- /// duplicate it, and a handle that cannot be passed around keeps revealing where it was asked for.
46- pub struct Revealed < ' a > ( & ' a Secret ) ;
47-
48- // Redacted here too: this type exists to widen serialisation, not printing.
49- impl fmt:: Debug for Revealed < ' _ > {
50- fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
51- self . 0 . fmt ( f)
30+ /// Whether no secret was given, which a caller may ask without reading one.
31+ pub fn is_empty ( & self ) -> bool {
32+ self . 0 . is_empty ( )
5233 }
5334}
5435
@@ -62,14 +43,6 @@ impl fmt::Debug for Secret {
6243 }
6344}
6445
65- impl Deref for Secret {
66- type Target = str ;
67-
68- fn deref ( & self ) -> & Self :: Target {
69- & self . 0
70- }
71- }
72-
7346impl From < String > for Secret {
7447 fn from ( value : String ) -> Self {
7548 Self :: new ( value)
@@ -84,6 +57,7 @@ impl From<&str> for Secret {
8457
8558#[ cfg( feature = "serde" ) ]
8659impl serde:: Serialize for Secret {
60+ /// Redacts, so that a configuration dumped for diagnosis carries no credential.
8761 fn serialize < S : serde:: Serializer > ( & self , serializer : S ) -> Result < S :: Ok , S :: Error > {
8862 if self . 0 . is_empty ( ) {
8963 serializer. serialize_str ( "" )
@@ -93,24 +67,15 @@ impl serde::Serialize for Secret {
9367 }
9468}
9569
96- #[ cfg( feature = "serde" ) ]
97- impl serde:: Serialize for Revealed < ' _ > {
98- fn serialize < S : serde:: Serializer > ( & self , serializer : S ) -> Result < S :: Ok , S :: Error > {
99- serializer. serialize_str ( & self . 0 . 0 )
100- }
101- }
102-
10370#[ cfg( feature = "serde" ) ]
10471impl < ' de > serde:: Deserialize < ' de > for Secret {
105- /// Refuses the redaction marker, so that reading back a redacted dump fails where it can be
106- /// understood rather than later, as an unexplained rejection by whatever the secret authenticates
107- /// against.
72+ /// Refuses the redaction marker, so that reading back a dump fails where it can be understood
73+ /// rather than later, as an unexplained rejection by whatever the secret authenticates against.
10874 fn deserialize < D : serde:: Deserializer < ' de > > ( deserializer : D ) -> Result < Self , D :: Error > {
10975 let value = String :: deserialize ( deserializer) ?;
11076 if value == REDACTED {
11177 return Err ( serde:: de:: Error :: custom ( format ! (
112- "`{REDACTED}` is what a secret serialises to unless `Secret::revealed` was used; \
113- this input cannot be a secret"
78+ "`{REDACTED}` is what a secret serialises to, so this input cannot be one"
11479 ) ) ) ;
11580 }
11681 Ok ( Self :: new ( value) )
@@ -124,8 +89,6 @@ mod tests {
12489 #[ test]
12590 fn debug_never_shows_the_value ( ) {
12691 assert_eq ! ( format!( "{:?}" , Secret :: new( "hunter2" ) ) , REDACTED ) ;
127- // Including through the wrapper that widens serialisation.
128- assert_eq ! ( format!( "{:?}" , Secret :: new( "hunter2" ) . revealed( ) ) , REDACTED ) ;
12992 }
13093
13194 #[ test]
@@ -135,27 +98,18 @@ mod tests {
13598 }
13699
137100 #[ test]
138- fn the_value_is_available_to_the_code_that_needs_it ( ) {
139- assert_eq ! ( Secret :: new( "hunter2" ) . expose( ) , "hunter2" ) ;
140- assert_eq ! ( & * Secret :: new( "hunter2" ) , "hunter2" ) ;
101+ fn the_value_is_available_only_to_the_code_that_asks_for_it ( ) {
102+ assert_eq ! ( Secret :: new( "hunter2" ) . expose_secret( ) , "hunter2" ) ;
103+ // Emptiness is answerable without reading the value.
104+ assert ! ( Secret :: default ( ) . is_empty( ) ) ;
105+ assert ! ( !Secret :: new( "hunter2" ) . is_empty( ) ) ;
141106 }
142107
143108 #[ cfg( feature = "serde" ) ]
144109 #[ test]
145- fn serialisation_redacts_unless_the_call_site_asks_otherwise ( ) {
146- let secret = Secret :: new ( "hunter2" ) ;
147-
148- assert_eq ! (
149- serde_json:: to_string( & secret) . expect( "serialise" ) ,
150- format!( "\" {REDACTED}\" " )
151- ) ;
152- assert_eq ! (
153- serde_json:: to_string( & secret. revealed( ) ) . expect( "serialise" ) ,
154- "\" hunter2\" "
155- ) ;
156- // Revealing one call site leaves the secret itself untouched, which is the whole point.
110+ fn serialisation_redacts ( ) {
157111 assert_eq ! (
158- serde_json:: to_string( & secret ) . expect( "serialise" ) ,
112+ serde_json:: to_string( & Secret :: new ( "hunter2" ) ) . expect( "serialise" ) ,
159113 format!( "\" {REDACTED}\" " )
160114 ) ;
161115 }
@@ -167,8 +121,8 @@ mod tests {
167121 . expect_err ( "the marker must not be taken for a secret" ) ;
168122
169123 assert ! (
170- error. to_string( ) . contains( "revealed " ) ,
171- "the message should say what to do : {error}"
124+ error. to_string( ) . contains( "cannot be one " ) ,
125+ "the message should say why : {error}"
172126 ) ;
173127 }
174128}
0 commit comments