Skip to content

Commit cb28171

Browse files
committed
Use sanitization constant-time comparisons
1 parent cc58e67 commit cb28171

6 files changed

Lines changed: 46 additions & 11 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ proxy = [
5454
"dep:rustix",
5555
"dep:serde_json",
5656
"dep:sha2",
57-
"dep:subtle",
57+
"dep:sanitization",
5858
"dep:ureq",
5959
"dep:zeroize",
6060
]
@@ -280,7 +280,7 @@ rustls = { version = "0.23.40", optional = true }
280280
rustls-native-certs = { version = "0.8.3", optional = true }
281281
rustix = { version = "1.1.4", features = ["fs", "net", "process"], optional = true }
282282
sha2 = { version = "0.11.0", optional = true }
283-
subtle = { version = "2.6.1", optional = true }
283+
sanitization = { version = "1.2.0", optional = true }
284284
tokio = { version = "1.52.3", default-features = false, features = ["fs", "io-util", "net", "process", "rt", "time"], optional = true }
285285
tokio-openssl = { version = "0.6.5", optional = true }
286286
tokio-rustls = { version = "0.26.4", optional = true }

crates/fluxheim-load-balancer/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ rustls = { version = "0.23.40", optional = true }
3333
rustls-native-certs = { version = "0.8.3", optional = true }
3434
serde = { version = "1.0.228", features = ["derive"] }
3535
serde_json = "1.0.150"
36-
subtle = "2.6.1"
36+
sanitization = "1.2.0"
3737
tokio = { version = "1.52.3", default-features = false, features = ["io-util", "macros", "net", "process", "rt", "sync", "time"] }
3838
tokio-openssl = { version = "0.6.5", optional = true }
3939
tokio-rustls = { version = "0.26.4", optional = true }

crates/fluxheim-load-balancer/src/persistence.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::net::IpAddr;
22
use std::sync::{Mutex, OnceLock};
33
use std::time::{Duration, Instant};
44

5-
use subtle::ConstantTimeEq;
5+
use sanitization::ct::ConstantTimeEq;
66
use zeroize::{Zeroize, Zeroizing};
77

88
use fluxheim_config::{
@@ -750,4 +750,20 @@ mod tests {
750750
let replay = TestRequest::default().with_header("cookie", format!("other_lb={token}"));
751751
assert_eq!(managed_cookie_key(&replay, "other_lb"), None);
752752
}
753+
754+
#[test]
755+
fn managed_cookie_hmac_rejects_tampered_tag() {
756+
let key = [3_u8; MANAGED_COOKIE_KEY_BYTES];
757+
let token = managed_cookie_token(b"fluxheim_lb", &key).unwrap();
758+
let mut tampered = token.into_bytes();
759+
let last = tampered
760+
.last_mut()
761+
.expect("managed-cookie token is non-empty");
762+
*last = if *last == b'A' { b'B' } else { b'A' };
763+
let tampered = String::from_utf8(tampered).unwrap();
764+
let request =
765+
TestRequest::default().with_header("cookie", format!("fluxheim_lb={tampered}"));
766+
767+
assert_eq!(managed_cookie_key(&request, "fluxheim_lb"), None);
768+
}
753769
}

src/admin.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ use http::{HeaderMap, Response, StatusCode, header};
1616
use pingora::apps::http_app::{HttpServer, ServeHttp};
1717
use pingora::protocols::http::ServerSession;
1818
use pingora::services::listening::Service;
19+
use sanitization::ct::ConstantTimeEq;
1920
use serde::Serialize;
2021
use serde_json::{Value, json};
21-
use subtle::ConstantTimeEq;
2222
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
2323

2424
use crate::config::{AdminAuthThrottleConfig, AdminConfig, AdminHealthResponseMode, Config};
@@ -3197,7 +3197,8 @@ fn constant_time_eq(candidate: &[u8], token: &AdminToken) -> bool {
31973197
let candidate_digest = digest_admin_token(candidate, token.mac_provider);
31983198
let candidate_len = (candidate.len() as u64).to_le_bytes();
31993199
let token_len = (token.len as u64).to_le_bytes();
3200-
bool::from(candidate_digest.ct_eq(&token.digest) & candidate_len.ct_eq(&token_len))
3200+
(candidate_digest.ct_eq(&token.digest) & candidate_len.ct_eq(&token_len))
3201+
.declassify("admin bearer-token comparison result is public")
32013202
}
32023203

32033204
fn digest_admin_token(
@@ -4240,8 +4241,9 @@ mod tests {
42404241

42414242
use super::{
42424243
AdminApp, AdminAuthThrottle, AdminToken, MAX_ADMIN_TOKEN_FILE_BYTES,
4243-
admin_services_from_config, authorized, constant_time_eq, error_response, json_response,
4244-
native_admin_target_parts, read_bounded_secret_file, read_secret_file,
4244+
admin_fingerprint_list_contains, admin_services_from_config, authorized, constant_time_eq,
4245+
error_response, json_response, native_admin_target_parts, read_bounded_secret_file,
4246+
read_secret_file,
42454247
};
42464248
#[cfg(feature = "cache")]
42474249
use crate::config::ByteSize;
@@ -7842,6 +7844,7 @@ mod tests {
78427844
let token = AdminToken::new("secret-token", false);
78437845
assert!(authorized(Some("Bearer secret-token"), &token));
78447846
assert!(!authorized(Some("Bearer secret"), &token));
7847+
assert!(!authorized(Some("Bearer secret-token-extra"), &token));
78457848
assert!(!constant_time_eq(b"secret", &token));
78467849
assert!(!authorized(
78477850
Some(&format!(
@@ -7852,6 +7855,22 @@ mod tests {
78527855
));
78537856
}
78547857

7858+
#[test]
7859+
fn certificate_fingerprint_comparison_uses_exact_length_match() {
7860+
let fingerprint = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
7861+
let values = vec![fingerprint.to_owned()];
7862+
7863+
assert!(admin_fingerprint_list_contains(&values, fingerprint));
7864+
assert!(!admin_fingerprint_list_contains(
7865+
&values,
7866+
&fingerprint[..63]
7867+
));
7868+
assert!(!admin_fingerprint_list_contains(
7869+
&values,
7870+
&format!("{fingerprint}a")
7871+
));
7872+
}
7873+
78557874
#[test]
78567875
fn admin_client_certificate_policy_requires_trusted_fingerprint_header() {
78577876
let app = app_with_config(Config {

src/edge_policy.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
44
use std::sync::{Arc, Mutex};
55
use std::time::{Duration, Instant};
66

7-
use subtle::ConstantTimeEq;
7+
use sanitization::ct::ConstantTimeEq;
88
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
99

1010
use crate::config::RateLimitMode;

0 commit comments

Comments
 (0)