diff --git a/src/security/pairing.rs b/src/security/pairing.rs index 5f55603..f9a9a05 100644 --- a/src/security/pairing.rs +++ b/src/security/pairing.rs @@ -174,15 +174,27 @@ fn generate_token() -> String { format!("zc_{}", uuid::Uuid::new_v4().as_simple()) } -/// Constant-time string comparison to prevent timing attacks on pairing code. +/// Constant-time string comparison to prevent timing attacks. +/// +/// Does not short-circuit on length mismatch — always iterates over the +/// longer input to avoid leaking length information via timing. pub fn constant_time_eq(a: &str, b: &str) -> bool { - if a.len() != b.len() { - return false; + let a = a.as_bytes(); + let b = b.as_bytes(); + + // Track length mismatch as a usize (non-zero = different lengths) + let len_diff = a.len() ^ b.len(); + + // XOR each byte, padding the shorter input with zeros. + // Iterates over max(a.len(), b.len()) to avoid timing differences. + let max_len = a.len().max(b.len()); + let mut byte_diff = 0u8; + for i in 0..max_len { + let x = if i < a.len() { a[i] } else { 0 }; + let y = if i < b.len() { b[i] } else { 0 }; + byte_diff |= x ^ y; } - a.bytes() - .zip(b.bytes()) - .fold(0u8, |acc, (x, y)| acc | (x ^ y)) - == 0 + len_diff == 0 && byte_diff == 0 } /// Check if a host string represents a non-localhost bind address. diff --git a/src/security/secrets.rs b/src/security/secrets.rs index bafad38..3940843 100644 --- a/src/security/secrets.rs +++ b/src/security/secrets.rs @@ -241,7 +241,7 @@ fn hex_encode(data: &[u8]) -> String { /// Hex-decode a hex string to bytes. fn hex_decode(hex: &str) -> Result> { - if hex.len() % 2 != 0 { + if !hex.len().is_multiple_of(2) { anyhow::bail!("Hex string has odd length"); } (0..hex.len()) diff --git a/src/tools/browser.rs b/src/tools/browser.rs index 5ee9505..25be13c 100644 --- a/src/tools/browser.rs +++ b/src/tools/browser.rs @@ -366,6 +366,7 @@ impl BrowserTool { } #[async_trait] +#[allow(clippy::too_many_lines)] impl Tool for BrowserTool { fn name(&self) -> &str { "browser"