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.
This commit is contained in:
Zhengchao An
2026-07-08 09:31:51 +08:00
committed by GitHub
parent 7b20554056
commit 3c3113619e
3 changed files with 22 additions and 4 deletions
+2 -2
View File
@@ -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]
+10 -1
View File
@@ -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,
+10 -1
View File
@@ -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,