rocket_community/config/
secret_key.rs

1use std::fmt;
2
3use cookie::Key;
4use serde::{de, ser, Deserialize};
5
6use crate::request::{FromRequest, Outcome, Request};
7
8/// A cryptographically secure secret key.
9///
10/// A `SecretKey` is primarily used by [private cookies]. See the [configuration
11/// guide] for further details. It can be configured from 256-bit random
12/// material or a 512-bit master key, each as either a base64-encoded string or
13/// raw bytes.
14///
15/// ```rust
16/// # extern crate rocket_community as rocket;
17/// use rocket::config::Config;
18///
19/// // NOTE: Don't (!) use this key! Generate your own and keep it private!
20/// //       e.g. via `head -c64 /dev/urandom | base64`
21/// let figment = Config::figment()
22///     # .merge(("secret_key", "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="));
23///     # /*
24///     .merge(("secret_key", "hPrYyЭRiMyµ5sBB1π+CMæ1køFsåqKvBiQJxBVHQk="));
25///     # */
26///
27/// let config = Config::from(figment);
28/// assert!(!config.secret_key.is_zero());
29/// ```
30///
31/// When configured in the debug profile with the `secrets` feature enabled, a
32/// key set as `0` is automatically regenerated at launch time from the OS's
33/// random source if available.
34///
35/// ```rust
36/// # extern crate rocket_community as rocket;
37/// use rocket::config::Config;
38/// use rocket::local::blocking::Client;
39///
40/// let figment = Config::figment()
41///     .merge(("secret_key", vec![0u8; 64]))
42///     .select("debug");
43///
44/// let rocket = rocket::custom(figment);
45/// let client = Client::tracked(rocket).expect("okay in debug");
46/// assert!(!client.rocket().config().secret_key.is_zero());
47/// ```
48///
49/// When running in any other profile with the `secrets` feature enabled,
50/// providing a key of `0` or not provided a key at all results in an error at
51/// launch-time:
52///
53/// ```rust
54/// # extern crate rocket_community as rocket;
55/// use rocket::config::Config;
56/// use rocket::figment::Profile;
57/// use rocket::local::blocking::Client;
58/// use rocket::error::ErrorKind;
59///
60/// let profile = Profile::const_new("staging");
61/// let figment = Config::figment()
62///     .merge(("secret_key", vec![0u8; 64]))
63///     .select(profile.clone());
64///
65/// let rocket = rocket::custom(figment);
66/// let error = Client::tracked(rocket).expect_err("error in non-debug");
67/// assert!(matches!(error.kind(), ErrorKind::InsecureSecretKey(profile)));
68/// ```
69///
70/// [private cookies]: https://rocket.rs/master/guide/requests/#private-cookies
71/// [configuration guide]: https://rocket.rs/master/guide/configuration/#secret-key
72#[derive(Clone)]
73#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
74pub struct SecretKey {
75    pub(crate) key: Key,
76    provided: bool,
77}
78
79impl SecretKey {
80    /// Returns a secret key that is all zeroes.
81    pub(crate) fn zero() -> SecretKey {
82        SecretKey {
83            key: Key::from(&[0; 64]),
84            provided: false,
85        }
86    }
87
88    /// Creates a `SecretKey` from a 512-bit `master` key. For security,
89    /// `master` _must_ be cryptographically random.
90    ///
91    /// # Panics
92    ///
93    /// Panics if `master` < 64 bytes.
94    ///
95    /// # Example
96    ///
97    /// ```rust
98    /// # extern crate rocket_community as rocket;
99    /// use rocket::config::SecretKey;
100    ///
101    /// # let master = vec![0u8; 64];
102    /// let key = SecretKey::from(&master);
103    /// ```
104    pub fn from(master: &[u8]) -> SecretKey {
105        SecretKey {
106            key: Key::from(master),
107            provided: true,
108        }
109    }
110
111    /// Derives a `SecretKey` from 256 bits of cryptographically random
112    /// `material`. For security, `material` _must_ be cryptographically random.
113    ///
114    /// # Panics
115    ///
116    /// Panics if `material` < 32 bytes.
117    ///
118    /// # Example
119    ///
120    /// ```rust
121    /// # extern crate rocket_community as rocket;
122    /// use rocket::config::SecretKey;
123    ///
124    /// # let material = vec![0u8; 32];
125    /// let key = SecretKey::derive_from(&material);
126    /// ```
127    pub fn derive_from(material: &[u8]) -> SecretKey {
128        SecretKey {
129            key: Key::derive_from(material),
130            provided: true,
131        }
132    }
133
134    /// Attempts to generate a `SecretKey` from randomness retrieved from the
135    /// OS. If randomness from the OS isn't available, returns `None`.
136    ///
137    /// # Example
138    ///
139    /// ```rust
140    /// # extern crate rocket_community as rocket;
141    /// use rocket::config::SecretKey;
142    ///
143    /// let key = SecretKey::generate();
144    /// ```
145    pub fn generate() -> Option<SecretKey> {
146        Some(SecretKey {
147            key: Key::try_generate()?,
148            provided: false,
149        })
150    }
151
152    /// Returns `true` if `self` is the `0`-key.
153    ///
154    /// # Example
155    ///
156    /// ```rust
157    /// # extern crate rocket_community as rocket;
158    /// use rocket::config::SecretKey;
159    ///
160    /// let master = vec![0u8; 64];
161    /// let key = SecretKey::from(&master);
162    /// assert!(key.is_zero());
163    /// ```
164    pub fn is_zero(&self) -> bool {
165        self == &Self::zero()
166    }
167
168    /// Returns `true` if `self` was not automatically generated and is not zero.
169    ///
170    /// # Example
171    ///
172    /// ```rust
173    /// # extern crate rocket_community as rocket;
174    /// use rocket::config::SecretKey;
175    ///
176    /// let master = vec![0u8; 64];
177    /// let key = SecretKey::generate().unwrap();
178    /// assert!(!key.is_provided());
179    ///
180    /// let master = vec![0u8; 64];
181    /// let key = SecretKey::from(&master);
182    /// assert!(!key.is_provided());
183    /// ```
184    pub fn is_provided(&self) -> bool {
185        self.provided && !self.is_zero()
186    }
187
188    /// Serialize as `zero` to avoid key leakage.
189    pub(crate) fn serialize_zero<S>(&self, ser: S) -> Result<S::Ok, S::Error>
190    where
191        S: ser::Serializer,
192    {
193        ser.serialize_bytes(&[0; 32][..])
194    }
195}
196
197impl PartialEq for SecretKey {
198    fn eq(&self, other: &Self) -> bool {
199        // `Key::partial_eq()` is a constant-time op.
200        self.key == other.key
201    }
202}
203
204#[crate::async_trait]
205impl<'r> FromRequest<'r> for &'r SecretKey {
206    type Error = std::convert::Infallible;
207
208    async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
209        Outcome::Success(&req.rocket().config().secret_key)
210    }
211}
212
213impl<'de> Deserialize<'de> for SecretKey {
214    fn deserialize<D: de::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
215        use {
216            binascii::{b64decode, hex2bin},
217            de::Unexpected::Str,
218        };
219
220        struct Visitor;
221
222        impl<'de> de::Visitor<'de> for Visitor {
223            type Value = SecretKey;
224
225            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226                f.write_str("256-bit base64 or hex string, or 32-byte slice")
227            }
228
229            fn visit_str<E: de::Error>(self, val: &str) -> Result<SecretKey, E> {
230                let e = |s| E::invalid_value(Str(s), &"256-bit base64 or hex");
231
232                // `binascii` requires a more space than actual output for padding
233                let mut buf = [0u8; 96];
234                let bytes = match val.len() {
235                    44 | 88 => b64decode(val.as_bytes(), &mut buf).map_err(|_| e(val))?,
236                    64 => hex2bin(val.as_bytes(), &mut buf).map_err(|_| e(val))?,
237                    n => Err(E::invalid_length(n, &"44 or 88 for base64, 64 for hex"))?,
238                };
239
240                self.visit_bytes(bytes)
241            }
242
243            fn visit_bytes<E: de::Error>(self, bytes: &[u8]) -> Result<SecretKey, E> {
244                if bytes.len() < 32 {
245                    Err(E::invalid_length(bytes.len(), &"at least 32"))
246                } else if bytes.iter().all(|b| *b == 0) {
247                    Ok(SecretKey::zero())
248                } else if bytes.len() >= 64 {
249                    Ok(SecretKey::from(bytes))
250                } else {
251                    Ok(SecretKey::derive_from(bytes))
252                }
253            }
254
255            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
256            where
257                A: de::SeqAccess<'de>,
258            {
259                let mut bytes = Vec::with_capacity(seq.size_hint().unwrap_or(0));
260                while let Some(byte) = seq.next_element()? {
261                    bytes.push(byte);
262                }
263
264                self.visit_bytes(&bytes)
265            }
266        }
267
268        de.deserialize_any(Visitor)
269    }
270}
271
272impl fmt::Display for SecretKey {
273    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274        if self.is_zero() {
275            f.write_str("[zero]")
276        } else {
277            match self.provided {
278                true => f.write_str("[provided]"),
279                false => f.write_str("[generated]"),
280            }
281        }
282    }
283}
284
285impl fmt::Debug for SecretKey {
286    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287        <Self as fmt::Display>::fmt(self, f)
288    }
289}