fix(cli): reject empty HMAC keys #123
No files matched your search
@@ -296,6 +296,19 @@ mod tests {
|
||||
assert!(help.contains("--loose"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_empty_hmac_key_is_rejected() {
|
||||
let err = parse(&["--hmac", "", "127.0.0.1:2112"]).unwrap_err();
|
||||
assert!(err.to_string().contains("HMAC key must not be empty"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_non_empty_hmac_key_is_accepted() {
|
||||
let args = parse(&["--hmac", "secret", "127.0.0.1:2112"]).unwrap();
|
||||
let config = args.prepare().unwrap().client;
|
||||
assert_eq!(config.hmac_key, Some(b"secret".to_vec()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_format_summary_policy_is_table_only() {
|
||||
assert!(OutputFormat::Table.prints_summary());
|
||||
|
||||
@@ -26,6 +26,7 @@ pub struct ServerArgs {
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "KEY",
|
||||
value_parser = parse_hmac_key,
|
||||
long_help = "HMAC key, taken as the UTF-8 bytes of this argument.\n\nAuthentication is global: with a key configured, every request must carry a valid MAC and every reply is authenticated. Without one, authenticated requests are dropped. The key is visible in the process arguments."
|
||||
)]
|
||||
pub hmac: Option<String>,
|
||||
@@ -160,6 +161,15 @@ impl ServerArgs {
|
||||
}
|
||||
}
|
||||
|
||||
/// Rejects an empty HMAC key so a blank shell expansion cannot silently
|
||||
/// enable authentication with predictable, empty key material.
|
||||
fn parse_hmac_key(input: &str) -> Result<String, String> {
|
||||
if input.is_empty() {
|
||||
return Err("HMAC key must not be empty".to_owned());
|
||||
}
|
||||
Ok(input.to_owned())
|
||||
}
|
||||
|
||||
/// Parses a duration whose zero is meaningful server policy.
|
||||
fn parse_duration_allow_zero(input: &str) -> Result<Duration, String> {
|
||||
if input == "0" {
|
||||
@@ -404,6 +414,18 @@ mod tests {
|
||||
assert!(parse(&["--bind", "127.0.0.1:2112", "--idle-timeout", "ms"]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_empty_hmac_key_is_rejected() {
|
||||
let err = parse(&["--bind", "127.0.0.1:2112", "--hmac", ""]).unwrap_err();
|
||||
assert!(err.to_string().contains("HMAC key must not be empty"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_whitespace_only_hmac_key_is_accepted_literally() {
|
||||
let config = bound(&["--hmac", " "]).server_config();
|
||||
assert_eq!(config.hmac_key(), Some(b" ".as_slice()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_zero_maximum_duration_is_not_a_spelling_of_no_maximum() {
|
||||
assert!(parse(&["--bind", "127.0.0.1:2112", "--max-duration", "0"]).is_err());
|
||||
|
||||
@@ -270,6 +270,14 @@ mod tests {
|
||||
assert_eq!(shared.socket_config.ttl, tui.socket_config.ttl);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_empty_hmac_key_is_rejected_via_the_shared_args() {
|
||||
// CommonClientArgs is flattened into both the client and TUI parsers,
|
||||
// so proving rejection once here covers both without duplicating it.
|
||||
let err = parse(&["--hmac", "", "127.0.0.1:2112"]).unwrap_err();
|
||||
assert!(err.to_string().contains("HMAC key must not be empty"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tui_help_lists_multi_target_options() {
|
||||
let help = TuiArgs::command().render_help().to_string();
|
||||
|
||||
@@ -15,7 +15,7 @@ pub struct CommonClientArgs {
|
||||
pub length: u32,
|
||||
|
||||
/// HMAC key.
|
||||
#[arg(long)]
|
||||
#[arg(long, value_parser = parse_hmac_key)]
|
||||
pub hmac: Option<String>,
|
||||
|
||||
/// Clock mode to request.
|
||||
@@ -189,6 +189,13 @@ pub fn parse_length(input: &str) -> Result<u32, String> {
|
||||
Ok(length)
|
||||
}
|
||||
|
||||
pub fn parse_hmac_key(input: &str) -> Result<String, String> {
|
||||
if input.is_empty() {
|
||||
return Err("HMAC key must not be empty".to_owned());
|
||||
}
|
||||
Ok(input.to_owned())
|
||||
}
|
||||
|
||||
pub fn parse_server_fill(input: &str) -> Result<String, String> {
|
||||
if input.is_empty() {
|
||||
return Err("server fill must not be empty".to_owned());
|
||||
|
||||
Reference in new issue
Block a user