Skip to content

Commit 966a169

Browse files
committed
Harden downstream TLS input handling
1 parent 50e6d54 commit 966a169

19 files changed

Lines changed: 922 additions & 196 deletions

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,22 @@ behavior when the change improves security or project direction.
2323

2424
### Security
2525

26+
- Make OpenSSL cipher allow-lists deterministic across protocol families:
27+
omitting all TLS 1.2 or TLS 1.3 suites now disables that protocol version
28+
instead of retaining Mozilla acceptor defaults. Use the current Mozilla v5
29+
acceptor baseline so configured TLS 1.3 policy is not disabled by the legacy
30+
v4 template.
31+
- Replace the rustls TLS-ALPN handshake loader callback with an atomically
32+
published, bounded in-memory SNI certificate store.
33+
- Bound downstream certificate chains, private keys, and client-auth CA
34+
bundles by bytes and certificate count for both rustls and OpenSSL.
35+
- Protect transient rustls private-key PEM and decoded DER allocations with
36+
`sanitization::SecretVec` through provider key construction, including
37+
partial-read and concurrent-growth error paths. Decode PEM key material with
38+
staged constant-time-oriented Base64 and expose only redacted error classes.
39+
- Disable rustls and tokio-rustls default provider features so normal Ring
40+
builds no longer compile AWS-LC; FIPS profiles continue to select AWS-LC
41+
explicitly.
2642
- Validate every `wasi_snapshot_preview1` import against the plugin's explicit
2743
capability grants before instantiation. Environment, arguments, inherited
2844
stdio, filesystem, sockets/network, polling, and process state remain denied.

Cargo.lock

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

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ tls-rustls-backend = [
9696
"dep:tokio-rustls",
9797
"fluxheim-load-balancer?/tls-rustls-backend",
9898
]
99-
tls-rustls = ["tls-rustls-backend", "rustls/ring", "fluxheim-server/tls-rustls", "fluxheim-tls/tls-rustls"]
100-
tls-rustls-fips = ["tls-rustls-backend", "dep:aws-lc-rs", "fluxheim-config/tls-rustls-fips", "fluxheim-server/tls-rustls-fips", "fluxheim-tls/tls-rustls-fips", "rustls/fips"]
99+
tls-rustls = ["tls-rustls-backend", "rustls/ring", "tokio-rustls/ring", "fluxheim-server/tls-rustls", "fluxheim-tls/tls-rustls"]
100+
tls-rustls-fips = ["tls-rustls-backend", "dep:aws-lc-rs", "fluxheim-config/tls-rustls-fips", "fluxheim-server/tls-rustls-fips", "fluxheim-tls/tls-rustls-fips", "rustls/fips", "tokio-rustls/fips"]
101101
tls-rustls-iso19790 = ["tls-rustls-fips"]
102102
tls-openssl = [
103103
"tls",
@@ -279,14 +279,14 @@ prometheus = { version = "0.14.0", optional = true }
279279
regex = "1.13.0"
280280
ring = { version = "0.17.14", optional = true }
281281
rpassword = { version = "7.5.4", optional = true }
282-
rustls = { version = "0.23.41", optional = true }
282+
rustls = { version = "0.23.41", optional = true, default-features = false, features = ["std", "tls12", "logging"] }
283283
rustls-native-certs = { version = "0.8.4", optional = true }
284284
rustix = { version = "1.1.4", features = ["fs", "net", "process"], optional = true }
285285
sha2 = { version = "0.11.0", optional = true }
286286
sanitization = { version = "1.2.4", features = ["alloc"], optional = true }
287287
tokio = { version = "1.52.3", default-features = false, features = ["fs", "io-util", "net", "process", "rt", "rt-multi-thread", "signal", "time"], optional = true }
288288
tokio-openssl = { version = "0.6.5", optional = true }
289-
tokio-rustls = { version = "0.26.4", optional = true }
289+
tokio-rustls = { version = "0.26.4", optional = true, default-features = false, features = ["logging", "tls12"] }
290290
serde = { version = "1.0.228", features = ["derive"] }
291291
serde_json = { version = "1.0.150", optional = true }
292292
thiserror = "2.0.18"

crates/fluxheim-geoip/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ license = "EUPL-1.2"
77
publish = false
88
description = "Internal Fluxheim GeoIP and Geo-Context boundary."
99

10+
[[example]]
11+
name = "lookup"
12+
required-features = ["runtime"]
13+
1014
[features]
1115
default = []
1216
runtime = ["dep:fluxheim-config", "dep:maxminddb", "dep:rustix"]

crates/fluxheim-load-balancer/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ description = "Internal Fluxheim load-balancer core."
1111
metrics = []
1212
privacy-mode = []
1313
test-support = ["fluxheim-common/test-support"]
14-
tls-rustls-backend = ["dep:rustls", "dep:rustls-native-certs", "dep:tokio-rustls", "rustls/ring"]
14+
tls-rustls-backend = ["dep:rustls", "dep:rustls-native-certs", "dep:tokio-rustls", "rustls/ring", "tokio-rustls/ring"]
1515
tls-openssl-backend = ["dep:openssl", "dep:tokio-openssl"]
1616

1717
[dependencies]
@@ -30,18 +30,18 @@ http = "1.4.2"
3030
log = "0.4.33"
3131
openssl = { version = "0.10.81", optional = true }
3232
ring = "0.17.14"
33-
rustls = { version = "0.23.41", optional = true }
33+
rustls = { version = "0.23.41", optional = true, default-features = false, features = ["std", "tls12", "logging"] }
3434
rustls-native-certs = { version = "0.8.4", optional = true }
3535
serde = { version = "1.0.228", features = ["derive"] }
3636
serde_json = "1.0.150"
3737
sanitization = { version = "1.2.4", features = ["alloc"] }
3838
tokio = { version = "1.52.3", default-features = false, features = ["io-util", "macros", "net", "process", "rt", "sync", "time"] }
3939
tokio-openssl = { version = "0.6.5", optional = true }
40-
tokio-rustls = { version = "0.26.4", optional = true }
40+
tokio-rustls = { version = "0.26.4", optional = true, default-features = false, features = ["logging", "tls12"] }
4141
ureq = { version = "3.3.0", default-features = false, features = ["json", "rustls"] }
4242
zeroize = "1.9.0"
4343

4444
[dev-dependencies]
4545
fluxheim-common = { path = "../fluxheim-common", features = ["test-support"] }
4646
proptest = "1.6.0"
47-
rustls = { version = "0.23.41", features = ["ring"] }
47+
rustls = { version = "0.23.41", default-features = false, features = ["std", "tls12", "logging", "ring"] }

crates/fluxheim-server/Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ geoip = ["dep:fluxheim-geoip", "fluxheim-config/geoip", "fluxheim-geoip/runtime"
2121
load-balancer = ["dep:fluxheim-load-balancer"]
2222
otel-tracing = ["dep:fluxheim-observability", "fluxheim-config/otel-tracing"]
2323
php-fpm = ["dep:fastcgi-client", "dep:fluxheim-php-fpm", "tokio/fs"]
24-
tls-rustls-backend = ["dep:fluxheim-tls", "dep:rustls", "dep:rustls-native-certs", "dep:tokio-rustls", "fluxheim-tls/tls-rustls-backend", "rustls/ring", "sanitization/alloc"]
25-
tls-rustls = ["tls-rustls-backend", "fluxheim-tls/tls-rustls", "rustls/ring"]
26-
tls-rustls-fips = ["tls-rustls-backend", "fluxheim-tls/tls-rustls-fips", "rustls/fips"]
24+
tls-rustls-backend = ["dep:fluxheim-tls", "dep:rustls", "dep:rustls-native-certs", "dep:tokio-rustls", "fluxheim-tls/tls-rustls-backend", "sanitization/alloc"]
25+
tls-rustls = ["tls-rustls-backend", "fluxheim-tls/tls-rustls", "rustls/ring", "tokio-rustls/ring"]
26+
tls-rustls-fips = ["tls-rustls-backend", "fluxheim-tls/tls-rustls-fips", "rustls/fips", "tokio-rustls/fips"]
2727
tls-openssl-backend = ["dep:fluxheim-tls", "dep:openssl", "dep:tokio-openssl", "fluxheim-tls/tls-openssl", "sanitization/alloc"]
2828
tls-openssl = ["tls-openssl-backend"]
2929
tls-openssl-fips = ["tls-openssl"]
@@ -63,15 +63,15 @@ openssl = { version = "0.10.81", optional = true }
6363
percent-encoding = "2.3.2"
6464
regex = "1.13.0"
6565
ring = "0.17.14"
66-
rustls = { version = "0.23.41", optional = true }
66+
rustls = { version = "0.23.41", optional = true, default-features = false, features = ["std", "tls12", "logging"] }
6767
rustls-native-certs = { version = "0.8.4", optional = true }
6868
sanitization = { version = "1.2.4", features = ["alloc"] }
6969
serde_json = { version = "1.0.150", optional = true }
7070
sha2 = "0.11.0"
7171
socket2 = { version = "0.6.3", features = ["all"] }
7272
tokio = { version = "1.52.3", default-features = false, features = ["io-util", "macros", "net", "rt", "sync", "time"] }
7373
tokio-openssl = { version = "0.6.5", optional = true }
74-
tokio-rustls = { version = "0.26.4", optional = true }
74+
tokio-rustls = { version = "0.26.4", optional = true, default-features = false, features = ["logging", "tls12"] }
7575
ureq = { version = "3.3.0", default-features = false, features = ["rustls"], optional = true }
7676
zeroize = "1.9.0"
7777

crates/fluxheim-tls/Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ description = "Internal Fluxheim TLS provider and listener planning boundaries."
1010
[features]
1111
default = []
1212
acme = ["fluxheim-config/acme"]
13-
tls-rustls-backend = ["dep:rustls", "fluxheim-config/tls-rustls-backend"]
13+
tls-rustls-backend = ["dep:rustls", "dep:sanitization", "fluxheim-config/tls-rustls-backend"]
1414
tls-rustls = ["tls-rustls-backend", "rustls/ring"]
1515
tls-rustls-fips = ["tls-rustls-backend", "fluxheim-config/tls-rustls-fips", "rustls/fips"]
1616
tls-openssl = ["dep:openssl", "dep:sanitization", "fluxheim-config/tls-openssl"]
@@ -22,10 +22,14 @@ tls-openssl-fips = [
2222

2323
[dependencies]
2424
arc-swap = "1.9.2"
25+
base64-ng = "1.3.7"
2526
fluxheim-config = { path = "../fluxheim-config" }
2627
fluxheim-openssl-fips-support = { path = "../../vendor/fluxheim-openssl-fips-support", optional = true }
2728
log = "0.4.33"
2829
openssl = { version = "0.10.81", optional = true }
29-
rustls = { version = "0.23.41", optional = true }
30+
rustls = { version = "0.23.41", optional = true, default-features = false, features = ["std", "tls12", "logging"] }
3031
sanitization = { version = "1.2.4", features = ["alloc"], optional = true }
3132
thiserror = "2.0.18"
33+
34+
[dev-dependencies]
35+
tempfile = "3.27.0"

crates/fluxheim-tls/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ mod provider;
1313
mod rustls_resolver;
1414
#[cfg(feature = "tls-rustls-backend")]
1515
mod rustls_server_config;
16+
#[cfg(any(feature = "tls-openssl", feature = "tls-rustls-backend"))]
17+
mod tls_input;
1618

1719
pub use listener::{
1820
DownstreamCertificateSelector, DownstreamCertificateSource, DownstreamTlsListenerPlan,
@@ -39,7 +41,7 @@ pub use provider::{install_rustls_crypto_provider, rustls_crypto_provider};
3941
#[cfg(feature = "tls-rustls-backend")]
4042
pub use rustls_resolver::{
4143
RustlsDownstreamCertificateError, RustlsDownstreamCertificateResolver,
42-
RustlsTlsAlpnCertificateLoader, load_rustls_certified_key_from_paths,
44+
RustlsTlsAlpnCertificateStore, load_rustls_certified_key_from_paths,
4345
set_pending_managed_certificate_recorder,
4446
};
4547
#[cfg(feature = "tls-rustls-backend")]
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use fluxheim_config::{TlsClientAuthMode, TlsConfig};
2+
use openssl::ssl::{SslAcceptorBuilder, SslVerifyMode};
3+
use openssl::stack::Stack;
4+
use openssl::x509::store::X509StoreBuilder;
5+
use openssl::x509::{X509, X509Name};
6+
7+
use super::OpenSslDownstreamAcceptorError;
8+
use crate::tls_input::{MAX_CA_BUNDLE_BYTES, MAX_CA_CERTIFICATES, read_bounded_file};
9+
10+
pub(super) fn apply_client_auth(
11+
builder: &mut SslAcceptorBuilder,
12+
tls: &TlsConfig,
13+
) -> Result<(), OpenSslDownstreamAcceptorError> {
14+
if tls.client_auth.mode == TlsClientAuthMode::Off {
15+
return Ok(());
16+
}
17+
let ca_path = tls
18+
.client_auth
19+
.ca_path
20+
.as_deref()
21+
.ok_or(OpenSslDownstreamAcceptorError::MissingClientAuthCa)?;
22+
let verify = match tls.client_auth.mode {
23+
TlsClientAuthMode::Off => SslVerifyMode::NONE,
24+
TlsClientAuthMode::Optional => SslVerifyMode::PEER,
25+
TlsClientAuthMode::Required => SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT,
26+
};
27+
builder.set_verify(verify);
28+
let bytes = read_bounded_file(ca_path, MAX_CA_BUNDLE_BYTES).map_err(|source| {
29+
OpenSslDownstreamAcceptorError::ReadClientAuthCa {
30+
path: ca_path.to_path_buf(),
31+
source,
32+
}
33+
})?;
34+
let certificates = X509::stack_from_pem(&bytes).map_err(|source| {
35+
OpenSslDownstreamAcceptorError::ParseClientAuthCa {
36+
path: ca_path.to_path_buf(),
37+
source,
38+
}
39+
})?;
40+
if certificates.is_empty() {
41+
return Err(OpenSslDownstreamAcceptorError::EmptyClientAuthCa {
42+
path: ca_path.to_path_buf(),
43+
});
44+
}
45+
if certificates.len() > MAX_CA_CERTIFICATES {
46+
return Err(OpenSslDownstreamAcceptorError::TooManyClientAuthCa {
47+
path: ca_path.to_path_buf(),
48+
count: certificates.len(),
49+
maximum: MAX_CA_CERTIFICATES,
50+
});
51+
}
52+
let mut store = X509StoreBuilder::new().map_err(|source| {
53+
OpenSslDownstreamAcceptorError::ApplyClientAuthCa {
54+
path: ca_path.to_path_buf(),
55+
source,
56+
}
57+
})?;
58+
let mut names = Stack::<X509Name>::new().map_err(|source| {
59+
OpenSslDownstreamAcceptorError::ApplyClientAuthCaList {
60+
path: ca_path.to_path_buf(),
61+
source,
62+
}
63+
})?;
64+
for certificate in certificates {
65+
names
66+
.push(certificate.subject_name().to_owned().map_err(|source| {
67+
OpenSslDownstreamAcceptorError::ApplyClientAuthCaList {
68+
path: ca_path.to_path_buf(),
69+
source,
70+
}
71+
})?)
72+
.map_err(
73+
|source| OpenSslDownstreamAcceptorError::ApplyClientAuthCaList {
74+
path: ca_path.to_path_buf(),
75+
source,
76+
},
77+
)?;
78+
store.add_cert(certificate).map_err(|source| {
79+
OpenSslDownstreamAcceptorError::ApplyClientAuthCa {
80+
path: ca_path.to_path_buf(),
81+
source,
82+
}
83+
})?;
84+
}
85+
builder.set_cert_store(store.build());
86+
builder.set_client_ca_list(names);
87+
Ok(())
88+
}

0 commit comments

Comments
 (0)