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}