diff --git a/core/http/src/cookies.rs b/core/http/src/cookies.rs index e9ebd18f..cb4e5ca8 100644 --- a/core/http/src/cookies.rs +++ b/core/http/src/cookies.rs @@ -24,6 +24,10 @@ mod key { pub fn derive_from(_: &[u8]) -> Self { Key } pub fn generate() -> Self { Key } pub fn try_generate() -> Option { Some(Key) } + pub fn master(&self) -> &[u8] { + static ZERO: &'static [u8; 64] = &[0; 64]; + &ZERO[..] + } } impl PartialEq for Key { diff --git a/core/lib/src/config/config.rs b/core/lib/src/config/config.rs index c896c875..305c3214 100644 --- a/core/lib/src/config/config.rs +++ b/core/lib/src/config/config.rs @@ -223,18 +223,17 @@ impl Config { panic!("aborting due to configuration error(s)") }); - #[cfg(all(feature = "secrets", not(test), not(rocket_unsafe_secret_key)))] { - if !config.secret_key.is_provided() { - if figment.profile() != Self::DEBUG_PROFILE { - crate::logger::try_init(LogLevel::Debug, true, false); - error!("secrets enabled in non-`debug` without `secret_key`"); - info_!("disable `secrets` feature or configure a `secret_key`"); - panic!("aborting due to configuration error(s)") - } - - // in debug, generate a key for a bit more security - config.secret_key = SecretKey::generate().unwrap_or(SecretKey::zero()); + #[cfg(all(feature = "secrets", not(test), not(rocket_unsafe_secret_key)))] + if !config.secret_key.is_provided() { + if figment.profile() != Self::DEBUG_PROFILE { + crate::logger::try_init(LogLevel::Debug, true, false); + error!("secrets enabled in non-`debug` without `secret_key`"); + info_!("disable `secrets` feature or configure a `secret_key`"); + panic!("aborting due to configuration error(s)") } + + // in debug, generate a key for a bit more security + config.secret_key = SecretKey::generate().unwrap_or(SecretKey::zero()); } config @@ -284,6 +283,7 @@ impl Config { false => launch_info_!("tls: {}", Paint::default("disabled").bold()), } + #[cfg(all(feature = "secrets", not(test), not(rocket_unsafe_secret_key)))] if !self.secret_key.is_provided() { warn!("secrets enabled without a configured `secret_key`"); info_!("disable `secrets` feature or configure a `secret_key`"); @@ -313,7 +313,15 @@ impl Provider for Config { #[track_caller] fn data(&self) -> Result> { - Serialized::defaults(self).data() + let mut map: Map = Serialized::defaults(self).data()?; + // We need to special-case `secret_key` since its serializer zeroes. + if !self.secret_key.is_zero() { + if let Some(map) = map.get_mut(&Profile::Default) { + map.insert("secret_key".into(), self.secret_key.master().into()); + } + } + + Ok(map) } fn profile(&self) -> Option { diff --git a/core/lib/src/config/secret_key.rs b/core/lib/src/config/secret_key.rs index e81256c7..45fc0d82 100644 --- a/core/lib/src/config/secret_key.rs +++ b/core/lib/src/config/secret_key.rs @@ -27,7 +27,10 @@ enum Kind { /// let figment = Figment::from(rocket::Config::default()) /// .merge(("secret_key", "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk=")); /// +/// # #[cfg(feature = "secrets")] /// assert!(!rocket::Config::from(figment).secret_key.is_zero()); +/// # #[cfg(not(feature = "secrets"))] +/// # assert!(rocket::Config::from(figment).secret_key.is_zero()); /// /// let figment = Figment::from(rocket::Config::default()) /// .merge(("secret_key", vec![0u8; 64])); @@ -42,16 +45,16 @@ enum Kind { /// /// [private cookies]: https://rocket.rs/master/guide/requests/#private-cookies /// [configuration guide]: https://rocket.rs/master/guide/configuration/#secret-key -#[derive(PartialEq, Clone)] +#[derive(Clone)] pub struct SecretKey { key: Key, - kind: Kind, + provided: bool, } impl SecretKey { /// Returns a secret key that is all zeroes. pub(crate) fn zero() -> SecretKey { - SecretKey { key: Key::from(&[0; 64]), kind: Kind::Zero } + SecretKey { key: Key::from(&[0; 64]), provided: false } } /// Creates a `SecretKey` from a 512-bit `master` key. For security, @@ -70,12 +73,7 @@ impl SecretKey { /// let key = SecretKey::from(&master); /// ``` pub fn from(master: &[u8]) -> SecretKey { - let kind = match master.iter().all(|&b| b == 0) { - true => Kind::Zero, - false => Kind::Provided - }; - - SecretKey { key: Key::from(master), kind } + SecretKey { key: Key::from(master), provided: true } } /// Derives a `SecretKey` from 256 bits of cryptographically random @@ -94,7 +92,7 @@ impl SecretKey { /// let key = SecretKey::derive_from(&material); /// ``` pub fn derive_from(material: &[u8]) -> SecretKey { - SecretKey { key: Key::derive_from(material), kind: Kind::Provided } + SecretKey { key: Key::derive_from(material), provided: true } } /// Attempts to generate a `SecretKey` from randomness retrieved from the @@ -108,7 +106,7 @@ impl SecretKey { /// let key = SecretKey::generate(); /// ``` pub fn generate() -> Option { - Some(SecretKey { key: Key::try_generate()?, kind: Kind::Generated }) + Some(SecretKey { key: Key::try_generate()?, provided: false }) } /// Returns `true` if `self` is the `0`-key. @@ -123,7 +121,7 @@ impl SecretKey { /// assert!(key.is_zero()); /// ``` pub fn is_zero(&self) -> bool { - self.kind == Kind::Zero + self == &Self::zero() } /// Returns `true` if `self` was not automatically generated and is not zero. @@ -142,7 +140,7 @@ impl SecretKey { /// assert!(!key.is_provided()); /// ``` pub fn is_provided(&self) -> bool { - self.kind == Kind::Provided + self.provided && !self.is_zero() } } @@ -155,6 +153,13 @@ impl Deref for SecretKey { } } +impl PartialEq for SecretKey { + fn eq(&self, other: &Self) -> bool { + // `Key::partial_eq()` is a constant-time op. + self.key == other.key + } +} + #[crate::async_trait] impl<'a, 'r> FromRequest<'a, 'r> for &'a SecretKey { type Error = std::convert::Infallible; @@ -228,10 +233,13 @@ impl<'de> Deserialize<'de> for SecretKey { impl fmt::Debug for SecretKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.kind { - Kind::Zero => f.write_str("[zero]"), - Kind::Generated => f.write_str("[generated]"), - Kind::Provided => f.write_str("[provided]"), + if self.is_zero() { + f.write_str("[zero]") + } else { + match self.provided { + true => f.write_str("[provided]"), + false => f.write_str("[generated]"), + } } } } diff --git a/core/lib/tests/config-secret-key-1500.rs b/core/lib/tests/config-secret-key-1500.rs new file mode 100644 index 00000000..4a5f80ba --- /dev/null +++ b/core/lib/tests/config-secret-key-1500.rs @@ -0,0 +1,12 @@ +use rocket::figment::Figment; +use rocket::config::{Config, SecretKey}; + +#[test] +fn secret_key_in_config_not_zero() { + let original_key = SecretKey::generate().expect("get key"); + + let config = Config { secret_key: original_key.clone(), ..Default::default() }; + let figment = Figment::from(config); + let figment_key: SecretKey = figment.extract_inner("secret_key").unwrap(); + assert_eq!(original_key, figment_key); +}