rocket/config/
secret_key.rs

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