diff --git a/Cargo.lock b/Cargo.lock index ac06d7cc..456ec76c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5367,6 +5367,7 @@ dependencies = [ "regex", "reqwest 0.13.4", "scrypt", + "secrecy", "serde", "serde_json", "serde_with", @@ -6687,6 +6688,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + [[package]] name = "security-framework" version = "3.7.0" diff --git a/Cargo.toml b/Cargo.toml index fdc5a056..56dce23d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,7 @@ scrypt = "0.11.0" subtle = "2.6" unicode-normalization = "0.1.25" zeroize = "1.8.2" +secrecy = "0.10" uuid = { version = "1.19", features = ["serde", "v4"] } unsigned-varint = { version = "0.8", features = ["futures"] } serde_with = { version = "3.16", features = ["hex", "base64"] } diff --git a/crates/dkg/src/dkg.rs b/crates/dkg/src/dkg.rs index 08e0abc1..5e75daab 100644 --- a/crates/dkg/src/dkg.rs +++ b/crates/dkg/src/dkg.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, ffi::OsStr, num::TryFromIntError, path, time::Duration}; +use std::{collections::HashMap, ffi::OsStr, fmt, num::TryFromIntError, path, time::Duration}; use bon::Builder; use futures::StreamExt; @@ -219,7 +219,7 @@ pub enum DkgError { } /// Keymanager configuration accepted by the entrypoint. -#[derive(Debug, Clone, Default, Builder)] +#[derive(Clone, Default, Builder)] pub struct KeymanagerConfig { /// The keymanager URL. pub address: String, @@ -227,6 +227,15 @@ pub struct KeymanagerConfig { pub auth_token: String, } +impl fmt::Debug for KeymanagerConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("KeymanagerConfig") + .field("address", &self.address) + .field("auth_token", &"") + .finish() + } +} + /// Publish configuration accepted by the entrypoint. #[derive(Debug, Clone, Builder)] pub struct PublishConfig { @@ -1286,4 +1295,25 @@ mod tests { DkgError::Disk(crate::disk::DiskError::MissingRequiredFiles { .. }) )); } + + #[test] + fn keymanager_config_debug_redacts_auth_token() { + let cfg = KeymanagerConfig::builder() + .address("https://keymanager.example".to_string()) + .auth_token("super-secret-token".to_string()) + .build(); + let rendered = format!("{cfg:?}"); + assert!( + !rendered.contains("super-secret-token"), + "token leaked in Debug: {rendered}" + ); + assert!( + rendered.contains(""), + "expected marker in Debug: {rendered}" + ); + assert!( + rendered.contains("https://keymanager.example"), + "address should be visible" + ); + } } diff --git a/crates/eth2util/Cargo.toml b/crates/eth2util/Cargo.toml index 691bd3c8..b51289b0 100644 --- a/crates/eth2util/Cargo.toml +++ b/crates/eth2util/Cargo.toml @@ -21,6 +21,7 @@ pbkdf2.workspace = true scrypt.workspace = true unicode-normalization.workspace = true zeroize.workspace = true +secrecy.workspace = true pluto-k1util.workspace = true chrono.workspace = true regex.workspace = true diff --git a/crates/eth2util/src/keymanager.rs b/crates/eth2util/src/keymanager.rs index bb1c842f..fb9a31ce 100644 --- a/crates/eth2util/src/keymanager.rs +++ b/crates/eth2util/src/keymanager.rs @@ -2,6 +2,7 @@ //! (https://ethereum.github.io/keymanager-APIs/) functionalities. use crate::keystore::Keystore; +use secrecy::{ExposeSecret, SecretString}; use url::Url; /// Errors that can occur when using the keymanager client. @@ -63,7 +64,7 @@ pub type Result = std::result::Result; #[derive(Debug, Clone)] pub struct Client { base_url: Url, - auth_token: String, + auth_token: SecretString, http_client: reqwest::Client, } @@ -80,7 +81,7 @@ impl Client { }; Ok(Self { base_url: Url::parse(&normalized)?, - auth_token: auth_token.as_ref().to_owned(), + auth_token: SecretString::from(auth_token.as_ref().to_owned()), http_client: reqwest::Client::new(), }) } @@ -143,7 +144,10 @@ impl Client { .http_client .post(addr) .header("Content-Type", "application/json") - .header("Authorization", format!("Bearer {}", self.auth_token)) + .header( + "Authorization", + format!("Bearer {}", self.auth_token.expose_secret()), + ) .body(req_bytes) .timeout(timeout) .send() @@ -338,4 +342,18 @@ mod tests { let err = Client::new(input, AUTH_TOKEN).unwrap_err(); assert!(matches!(err, KeymanagerError::ParseUrl(_))); } + + #[test] + fn client_debug_redacts_auth_token() { + let client = Client::new("http://localhost:9999", "super-secret-token").unwrap(); + let rendered = format!("{client:?}"); + assert!( + !rendered.contains("super-secret-token"), + "token leaked in Debug: {rendered}" + ); + assert!( + rendered.contains("REDACTED"), + "expected REDACTED marker in Debug: {rendered}" + ); + } }