@@ -6,13 +6,94 @@ use once_cell::sync::Lazy;
66use 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" ) ]
1212use webpki_roots:: TLS_SERVER_ROOTS ;
1313
1414use 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+
1697static EXTRA_CA_CERTS : OnceLock < Vec < CertificateDer < ' static > > > = OnceLock :: new ( ) ;
1798
1899pub 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 {
85167pub 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