fix(cli): reject empty HMAC keys #123
No files matched your search
@@ -296,6 +296,19 @@ mod tests {
|
|||||||
assert!(help.contains("--loose"));
|
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]
|
#[test]
|
||||||
fn output_format_summary_policy_is_table_only() {
|
fn output_format_summary_policy_is_table_only() {
|
||||||
assert!(OutputFormat::Table.prints_summary());
|
assert!(OutputFormat::Table.prints_summary());
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ pub struct ServerArgs {
|
|||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
value_name = "KEY",
|
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."
|
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>,
|
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.
|
/// Parses a duration whose zero is meaningful server policy.
|
||||||
fn parse_duration_allow_zero(input: &str) -> Result<Duration, String> {
|
fn parse_duration_allow_zero(input: &str) -> Result<Duration, String> {
|
||||||
if input == "0" {
|
if input == "0" {
|
||||||
@@ -404,6 +414,18 @@ mod tests {
|
|||||||
assert!(parse(&["--bind", "127.0.0.1:2112", "--idle-timeout", "ms"]).is_err());
|
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]
|
#[test]
|
||||||
fn a_zero_maximum_duration_is_not_a_spelling_of_no_maximum() {
|
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());
|
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);
|
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]
|
#[test]
|
||||||
fn tui_help_lists_multi_target_options() {
|
fn tui_help_lists_multi_target_options() {
|
||||||
let help = TuiArgs::command().render_help().to_string();
|
let help = TuiArgs::command().render_help().to_string();
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ pub struct CommonClientArgs {
|
|||||||
pub length: u32,
|
pub length: u32,
|
||||||
|
|
||||||
/// HMAC key.
|
/// HMAC key.
|
||||||
#[arg(long)]
|
#[arg(long, value_parser = parse_hmac_key)]
|
||||||
pub hmac: Option<String>,
|
pub hmac: Option<String>,
|
||||||
|
|
||||||
/// Clock mode to request.
|
/// Clock mode to request.
|
||||||
@@ -189,6 +189,13 @@ pub fn parse_length(input: &str) -> Result<u32, String> {
|
|||||||
Ok(length)
|
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> {
|
pub fn parse_server_fill(input: &str) -> Result<String, String> {
|
||||||
if input.is_empty() {
|
if input.is_empty() {
|
||||||
return Err("server fill must not be empty".to_owned());
|
return Err("server fill must not be empty".to_owned());
|
||||||
|
|||||||
Reference in new issue
Block a user