From 3c3113619e64e30c97f06edea2cd060192731e47 Mon Sep 17 00:00:00 2001 From: Zhengchao An Date: Wed, 8 Jul 2026 09:31:51 +0800 Subject: [PATCH] fix(protocols): use constant-time secret comparison in FTPS and WebDAV auth (#4403) The FTPS and WebDAV authentication handlers compared the client-supplied secret key against the stored secret with `String::eq`, which short-circuits on the first differing byte. A network attacker who knows (or enumerates) a valid access key can recover the secret key byte-by-byte via response-timing analysis; neither path is rate limited. Switch both to a constant-time comparison using `subtle::ConstantTimeEq`, the same primitive the SFTP handler and `rustfs/src/auth.rs::constant_time_eq` already use. `subtle` is added to the `ftps` and `webdav` feature dependency sets (it was previously gated on `sftp` only). Addresses GHSA-3p3x-734c-h5vx. --- crates/protocols/Cargo.toml | 4 ++-- crates/protocols/src/ftps/server.rs | 11 ++++++++++- crates/protocols/src/webdav/server.rs | 11 ++++++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/crates/protocols/Cargo.toml b/crates/protocols/Cargo.toml index 83306c7da..b58189192 100644 --- a/crates/protocols/Cargo.toml +++ b/crates/protocols/Cargo.toml @@ -27,7 +27,7 @@ categories = ["network-programming", "filesystem"] [features] default = [] -ftps = ["dep:libunftp", "dep:unftp-core", "dep:rustls", "dep:rustfs-tls-runtime"] +ftps = ["dep:libunftp", "dep:unftp-core", "dep:rustls", "dep:rustfs-tls-runtime", "dep:subtle"] swift = [ "dep:rustfs-keystone", "dep:rustfs-ecstore", @@ -53,7 +53,7 @@ swift = [ "dep:base64", "dep:async-compression", ] -webdav = ["dep:dav-server", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:tokio-rustls", "dep:base64", "dep:rustls", "dep:percent-encoding", "dep:rustfs-tls-runtime"] +webdav = ["dep:dav-server", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:tokio-rustls", "dep:base64", "dep:rustls", "dep:percent-encoding", "dep:rustfs-tls-runtime", "dep:subtle"] sftp = ["dep:russh", "dep:russh-sftp", "dep:uuid", "dep:subtle", "dep:tokio-util", "dep:socket2"] [dependencies] diff --git a/crates/protocols/src/ftps/server.rs b/crates/protocols/src/ftps/server.rs index f4d973602..6903c4afb 100644 --- a/crates/protocols/src/ftps/server.rs +++ b/crates/protocols/src/ftps/server.rs @@ -26,6 +26,7 @@ use std::net::IpAddr; use std::path::Path; use std::sync::Arc; use std::time::Duration; +use subtle::ConstantTimeEq; use tokio::sync::broadcast; use tokio::sync::watch; use tracing::{debug, error, info, warn}; @@ -464,7 +465,15 @@ impl Authenticator for FtpsAuthenticator { AuthenticationError::BadUser })?; - if !identity.credentials.secret_key.eq(&s3_creds.secret_key) { + // Constant-time secret comparison to prevent timing side-channel + // attacks. Same primitive used by the SFTP handler and rustfs/src/auth.rs. + let secret_matches: bool = identity + .credentials + .secret_key + .as_bytes() + .ct_eq(s3_creds.secret_key.as_bytes()) + .into(); + if !secret_matches { warn!( event = EVENT_FTPS_AUTH_STATE, component = LOG_COMPONENT_PROTOCOLS, diff --git a/crates/protocols/src/webdav/server.rs b/crates/protocols/src/webdav/server.rs index 85b0eb560..2023207db 100644 --- a/crates/protocols/src/webdav/server.rs +++ b/crates/protocols/src/webdav/server.rs @@ -32,6 +32,7 @@ use std::convert::Infallible; use std::net::IpAddr; use std::sync::Arc; use std::time::Duration; +use subtle::ConstantTimeEq; use tokio::net::TcpListener; use tokio::sync::{broadcast, watch}; use tokio_rustls::TlsAcceptor; @@ -441,7 +442,15 @@ where WebDavInitError::Server("User not found".to_string()) })?; - if !identity.credentials.secret_key.eq(&s3_creds.secret_key) { + // Constant-time secret comparison to prevent timing side-channel + // attacks. Same primitive used by the SFTP handler and rustfs/src/auth.rs. + let secret_matches: bool = identity + .credentials + .secret_key + .as_bytes() + .ct_eq(s3_creds.secret_key.as_bytes()) + .into(); + if !secret_matches { warn!( event = EVENT_WEBDAV_AUTH_STATE, component = LOG_COMPONENT_PROTOCOLS,