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] [features]
default = [] 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 = [ swift = [
"dep:rustfs-keystone", "dep:rustfs-keystone",
"dep:rustfs-ecstore", "dep:rustfs-ecstore",
@@ -53,7 +53,7 @@ swift = [
"dep:base64", "dep:base64",
"dep:async-compression", "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"] sftp = ["dep:russh", "dep:russh-sftp", "dep:uuid", "dep:subtle", "dep:tokio-util", "dep:socket2"]
[dependencies] [dependencies]
+10 -1
View File
@@ -26,6 +26,7 @@ use std::net::IpAddr;
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use subtle::ConstantTimeEq;
use tokio::sync::broadcast; use tokio::sync::broadcast;
use tokio::sync::watch; use tokio::sync::watch;
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
@@ -464,7 +465,15 @@ impl Authenticator for FtpsAuthenticator {
AuthenticationError::BadUser 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!( warn!(
event = EVENT_FTPS_AUTH_STATE, event = EVENT_FTPS_AUTH_STATE,
component = LOG_COMPONENT_PROTOCOLS, component = LOG_COMPONENT_PROTOCOLS,
+10 -1
View File
@@ -32,6 +32,7 @@ use std::convert::Infallible;
use std::net::IpAddr; use std::net::IpAddr;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use subtle::ConstantTimeEq;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tokio::sync::{broadcast, watch}; use tokio::sync::{broadcast, watch};
use tokio_rustls::TlsAcceptor; use tokio_rustls::TlsAcceptor;
@@ -441,7 +442,15 @@ where
WebDavInitError::Server("User not found".to_string()) 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!( warn!(
event = EVENT_WEBDAV_AUTH_STATE, event = EVENT_WEBDAV_AUTH_STATE,
component = LOG_COMPONENT_PROTOCOLS, component = LOG_COMPONENT_PROTOCOLS,