Skip to content

Commit f5e0a36

Browse files
committed
feat(tls): implement ciphers and TLS version filtering options
- Add min_version/max_version options to filter TLS protocol versions - Add ciphers option to filter cipher suites using OpenSSL-style names - Implement reverse mapping from OpenSSL cipher names to rustls suites - Update build_client_config to apply cipher and version filtering
1 parent af36b27 commit f5e0a36

File tree

1 file changed

+120
-10
lines changed

1 file changed

+120
-10
lines changed

modules/llrt_tls/src/config.rs

Lines changed: 120 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,94 @@ use once_cell::sync::Lazy;
66
use rustls::{
77
crypto::ring,
88
pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer},
9-
ClientConfig, RootCertStore, SupportedProtocolVersion,
9+
CipherSuite, ClientConfig, RootCertStore, SupportedCipherSuite, SupportedProtocolVersion,
1010
};
1111
#[cfg(feature = "webpki-roots")]
1212
use webpki_roots::TLS_SERVER_ROOTS;
1313

1414
use crate::no_verification::NoCertificateVerification;
1515

16+
/// Parse TLS version string to rustls SupportedProtocolVersion
17+
fn parse_tls_version(version: &str) -> Option<&'static SupportedProtocolVersion> {
18+
match version {
19+
"TLSv1.2" | "TLSv1_2" => Some(&rustls::version::TLS12),
20+
"TLSv1.3" | "TLSv1_3" => Some(&rustls::version::TLS13),
21+
_ => None,
22+
}
23+
}
24+
25+
/// Get TLS versions filtered by min/max version options
26+
fn get_filtered_tls_versions(
27+
min_version: Option<&str>,
28+
max_version: Option<&str>,
29+
) -> Option<Vec<&'static SupportedProtocolVersion>> {
30+
// All supported versions in order (oldest to newest)
31+
const ALL_VERSIONS: [&SupportedProtocolVersion; 2] =
32+
[&rustls::version::TLS12, &rustls::version::TLS13];
33+
34+
let min_idx = min_version
35+
.and_then(parse_tls_version)
36+
.and_then(|v| ALL_VERSIONS.iter().position(|&x| std::ptr::eq(x, v)))
37+
.unwrap_or(0);
38+
39+
let max_idx = max_version
40+
.and_then(parse_tls_version)
41+
.and_then(|v| ALL_VERSIONS.iter().position(|&x| std::ptr::eq(x, v)))
42+
.unwrap_or(ALL_VERSIONS.len() - 1);
43+
44+
if min_idx > max_idx {
45+
return None; // Invalid range
46+
}
47+
48+
let versions: Vec<_> = ALL_VERSIONS[min_idx..=max_idx].to_vec();
49+
if versions.is_empty() {
50+
None
51+
} else {
52+
Some(versions)
53+
}
54+
}
55+
56+
/// Parse OpenSSL-style cipher name to rustls CipherSuite
57+
fn openssl_name_to_cipher_suite(name: &str) -> Option<CipherSuite> {
58+
use CipherSuite::*;
59+
match name.trim() {
60+
// TLS 1.3 cipher suites
61+
"TLS_AES_256_GCM_SHA384" => Some(TLS13_AES_256_GCM_SHA384),
62+
"TLS_AES_128_GCM_SHA256" => Some(TLS13_AES_128_GCM_SHA256),
63+
"TLS_CHACHA20_POLY1305_SHA256" => Some(TLS13_CHACHA20_POLY1305_SHA256),
64+
// TLS 1.2 cipher suites
65+
"ECDHE-ECDSA-AES256-GCM-SHA384" => Some(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384),
66+
"ECDHE-ECDSA-AES128-GCM-SHA256" => Some(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256),
67+
"ECDHE-ECDSA-CHACHA20-POLY1305" => Some(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256),
68+
"ECDHE-RSA-AES256-GCM-SHA384" => Some(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384),
69+
"ECDHE-RSA-AES128-GCM-SHA256" => Some(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256),
70+
"ECDHE-RSA-CHACHA20-POLY1305" => Some(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256),
71+
_ => None,
72+
}
73+
}
74+
75+
/// Filter cipher suites based on OpenSSL-style cipher string
76+
fn filter_cipher_suites(
77+
cipher_string: &str,
78+
available: &[SupportedCipherSuite],
79+
) -> Vec<SupportedCipherSuite> {
80+
// Parse cipher string (colon or comma separated)
81+
let requested: Vec<CipherSuite> = cipher_string
82+
.split([':', ','])
83+
.filter_map(openssl_name_to_cipher_suite)
84+
.collect();
85+
86+
if requested.is_empty() {
87+
return available.to_vec();
88+
}
89+
90+
// Filter available suites to only those requested, preserving requested order
91+
requested
92+
.iter()
93+
.filter_map(|&suite| available.iter().find(|s| s.suite() == suite).copied())
94+
.collect()
95+
}
96+
1697
static EXTRA_CA_CERTS: OnceLock<Vec<CertificateDer<'static>>> = OnceLock::new();
1798

1899
pub fn set_extra_ca_certs(certs: Vec<CertificateDer<'static>>) {
@@ -59,11 +140,12 @@ pub struct BuildClientConfigOptions {
59140
pub key: Option<Vec<u8>>,
60141
/// Key log callback for debugging TLS connections
61142
pub key_log: Option<Arc<dyn rustls::KeyLog>>,
62-
/// Cipher suites (OpenSSL format) - currently unused, reserved for future
143+
/// Cipher suites in OpenSSL format (colon or comma separated)
144+
/// e.g., "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384"
63145
pub ciphers: Option<String>,
64-
/// Minimum TLS version (e.g., "TLSv1.2") - currently unused, reserved for future
146+
/// Minimum TLS version: "TLSv1.2" or "TLSv1.3"
65147
pub min_version: Option<String>,
66-
/// Maximum TLS version (e.g., "TLSv1.3") - currently unused, reserved for future
148+
/// Maximum TLS version: "TLSv1.2" or "TLSv1.3"
67149
pub max_version: Option<String>,
68150
}
69151

@@ -85,14 +167,42 @@ impl Default for BuildClientConfigOptions {
85167
pub fn build_client_config(
86168
options: BuildClientConfigOptions,
87169
) -> Result<ClientConfig, Box<dyn std::error::Error + Send + Sync>> {
88-
let provider = Arc::new(ring::default_provider());
170+
let default_provider = ring::default_provider();
171+
172+
// Filter cipher suites if specified
173+
let provider = if let Some(ref cipher_string) = options.ciphers {
174+
let filtered = filter_cipher_suites(cipher_string, &default_provider.cipher_suites);
175+
if filtered.is_empty() {
176+
Arc::new(default_provider)
177+
} else {
178+
Arc::new(rustls::crypto::CryptoProvider {
179+
cipher_suites: filtered,
180+
..default_provider
181+
})
182+
}
183+
} else {
184+
Arc::new(default_provider)
185+
};
186+
89187
let builder = ClientConfig::builder_with_provider(provider.clone());
90188

91-
// TLS versions
92-
let builder = match get_tls_versions() {
93-
Some(versions) => builder.with_protocol_versions(&versions),
94-
None => builder.with_safe_default_protocol_versions(),
95-
}?;
189+
// TLS versions - check options first, then global setting, then defaults
190+
let builder = if options.min_version.is_some() || options.max_version.is_some() {
191+
// Use per-connection version filtering
192+
match get_filtered_tls_versions(
193+
options.min_version.as_deref(),
194+
options.max_version.as_deref(),
195+
) {
196+
Some(versions) => builder.with_protocol_versions(&versions)?,
197+
None => builder.with_safe_default_protocol_versions()?,
198+
}
199+
} else {
200+
// Fall back to global TLS version setting
201+
match get_tls_versions() {
202+
Some(versions) => builder.with_protocol_versions(&versions)?,
203+
None => builder.with_safe_default_protocol_versions()?,
204+
}
205+
};
96206

97207
// Certificate verification
98208
let builder = if !options.reject_unauthorized {

0 commit comments

Comments
 (0)