rocket/config/
secret_key.rs

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