Skip to main content

wreq/
tls.rs

1//!  TLS options configuration
2//!
3//! - Various parts of TLS can also be configured or even disabled on the `ClientBuilder`.
4
5pub(super) mod conn;
6
7pub mod compress;
8pub mod keylog;
9pub mod session;
10pub mod trust;
11
12use std::borrow::Cow;
13
14/// Re-exports of TLS-related types from `btls` for public use.
15pub use btls::ssl::{ExtensionType, KeyShare};
16use bytes::{BufMut, Bytes, BytesMut};
17use compress::CertificateCompressor;
18
19/// Http extension carrying extra TLS layer information.
20/// Made available to clients on responses when `tls_info` is set.
21#[derive(Debug, Clone)]
22pub struct TlsInfo {
23    pub(crate) peer_certificate: Option<Bytes>,
24    pub(crate) peer_certificate_chain: Option<Vec<Bytes>>,
25}
26
27impl TlsInfo {
28    /// Get the DER encoded leaf certificate of the peer.
29    pub fn peer_certificate(&self) -> Option<&[u8]> {
30        self.peer_certificate.as_deref()
31    }
32
33    /// Get the DER encoded certificate chain of the peer.
34    ///
35    /// This includes the leaf certificate on the client side.
36    pub fn peer_certificate_chain(&self) -> Option<impl Iterator<Item = &[u8]>> {
37        self.peer_certificate_chain
38            .as_ref()
39            .map(|v| v.iter().map(|b| b.as_ref()))
40    }
41}
42
43/// A TLS protocol version.
44#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
45pub struct TlsVersion(btls::ssl::SslVersion);
46
47impl TlsVersion {
48    /// Version 1.0 of the TLS protocol.
49    pub const TLS_1_0: TlsVersion = TlsVersion(btls::ssl::SslVersion::TLS1);
50
51    /// Version 1.1 of the TLS protocol.
52    pub const TLS_1_1: TlsVersion = TlsVersion(btls::ssl::SslVersion::TLS1_1);
53
54    /// Version 1.2 of the TLS protocol.
55    pub const TLS_1_2: TlsVersion = TlsVersion(btls::ssl::SslVersion::TLS1_2);
56
57    /// Version 1.3 of the TLS protocol.
58    pub const TLS_1_3: TlsVersion = TlsVersion(btls::ssl::SslVersion::TLS1_3);
59}
60
61/// A TLS ALPN protocol.
62#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
63pub struct AlpnProtocol(&'static [u8]);
64
65impl AlpnProtocol {
66    /// Prefer HTTP/1.1
67    pub const HTTP1: AlpnProtocol = AlpnProtocol(b"http/1.1");
68
69    /// Prefer HTTP/2
70    pub const HTTP2: AlpnProtocol = AlpnProtocol(b"h2");
71
72    /// Prefer HTTP/3
73    pub const HTTP3: AlpnProtocol = AlpnProtocol(b"h3");
74
75    #[inline]
76    fn encode(self) -> Bytes {
77        Self::encode_sequence(std::iter::once(&self))
78    }
79
80    fn encode_sequence<'a, I>(items: I) -> Bytes
81    where
82        I: IntoIterator<Item = &'a AlpnProtocol>,
83    {
84        let mut buf = BytesMut::new();
85        for item in items {
86            buf.put_u8(item.0.len() as u8);
87            buf.extend_from_slice(item.0);
88        }
89        buf.freeze()
90    }
91}
92
93impl PartialEq<[u8]> for AlpnProtocol {
94    #[inline]
95    fn eq(&self, other: &[u8]) -> bool {
96        self.0 == other
97    }
98}
99
100/// A TLS ALPS protocol.
101#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
102pub struct AlpsProtocol(&'static [u8]);
103
104impl AlpsProtocol {
105    /// Prefer HTTP/1.1
106    pub const HTTP1: AlpsProtocol = AlpsProtocol(b"http/1.1");
107
108    /// Prefer HTTP/2
109    pub const HTTP2: AlpsProtocol = AlpsProtocol(b"h2");
110
111    /// Prefer HTTP/3
112    pub const HTTP3: AlpsProtocol = AlpsProtocol(b"h3");
113}
114
115impl PartialEq<[u8]> for AlpsProtocol {
116    #[inline]
117    fn eq(&self, other: &[u8]) -> bool {
118        self.0 == other
119    }
120}
121
122/// Builder for `[`TlsOptions`]`.
123#[must_use]
124#[derive(Debug, Clone)]
125pub struct TlsOptionsBuilder {
126    config: TlsOptions,
127}
128
129/// TLS connection configuration options.
130///
131/// This struct provides fine-grained control over the behavior of TLS
132/// connections, including:
133/// - **Protocol negotiation** (ALPN, ALPS, TLS versions)
134/// - **Session management** (tickets, PSK, key shares)
135/// - **Security & privacy** (OCSP, GREASE, ECH, delegated credentials)
136/// - **Performance tuning** (record size, cipher preferences, hardware overrides)
137///
138/// All fields are optional or have defaults. See each field for details.
139#[non_exhaustive]
140#[derive(Debug, Clone)]
141pub struct TlsOptions {
142    /// Application-Layer Protocol Negotiation ([RFC 7301](https://datatracker.ietf.org/doc/html/rfc7301)).
143    ///
144    /// Specifies which application protocols (e.g., HTTP/2, HTTP/1.1) may be negotiated
145    /// over a single TLS connection.
146    ///
147    /// **Default:** `Some([HTTP/2, HTTP/1.1])`
148    pub alpn_protocols: Option<Cow<'static, [AlpnProtocol]>>,
149
150    /// Application-Layer Protocol Settings (ALPS).
151    ///
152    /// Enables exchanging application-layer settings during the handshake
153    /// for protocols negotiated via ALPN.
154    ///
155    /// **Default:** `None`
156    pub alps_protocols: Option<Cow<'static, [AlpsProtocol]>>,
157
158    /// Whether to use an alternative ALPS codepoint for compatibility.
159    ///
160    /// Useful when larger ALPS payloads are required.
161    ///
162    /// **Default:** `false`
163    pub alps_use_new_codepoint: bool,
164
165    /// Enables TLS Session Tickets ([RFC 5077](https://tools.ietf.org/html/rfc5077)).
166    ///
167    /// Allows session resumption without requiring server-side state.
168    ///
169    /// **Default:** `true`
170    pub session_ticket: bool,
171
172    /// Minimum TLS version allowed for the connection.
173    ///
174    /// **Default:** `None` (library default applied)
175    pub min_tls_version: Option<TlsVersion>,
176
177    /// Maximum TLS version allowed for the connection.
178    ///
179    /// **Default:** `None` (library default applied)
180    pub max_tls_version: Option<TlsVersion>,
181
182    /// Enables Pre-Shared Key (PSK) cipher suites ([RFC 4279](https://datatracker.ietf.org/doc/html/rfc4279)).
183    ///
184    /// Authentication relies on out-of-band pre-shared keys instead of certificates.
185    ///
186    /// **Default:** `false`
187    pub pre_shared_key: bool,
188
189    /// Controls whether to send a GREASE Encrypted ClientHello (ECH) extension
190    /// when no supported ECH configuration is available.
191    ///
192    /// GREASE prevents protocol ossification by sending unknown extensions.
193    ///
194    /// **Default:** `false`
195    pub enable_ech_grease: bool,
196
197    /// Controls whether ClientHello extensions should be permuted.
198    ///
199    /// **Default:** `None` (implementation default)
200    pub permute_extensions: Option<bool>,
201
202    /// Controls whether GREASE extensions ([RFC 8701](https://datatracker.ietf.org/doc/html/rfc8701))
203    /// are enabled in general.
204    ///
205    /// **Default:** `None` (implementation default)
206    pub grease_enabled: Option<bool>,
207
208    /// Enables OCSP stapling for the connection.
209    ///
210    /// **Default:** `false`
211    pub enable_ocsp_stapling: bool,
212
213    /// Enables Signed Certificate Timestamps (SCT).
214    ///
215    /// **Default:** `false`
216    pub enable_signed_cert_timestamps: bool,
217
218    /// Sets the maximum TLS record size.
219    ///
220    /// **Default:** `None`
221    pub record_size_limit: Option<u16>,
222
223    /// Whether to skip session tickets when using PSK.
224    ///
225    /// **Default:** `false`
226    pub psk_skip_session_ticket: bool,
227
228    /// Whether to set specific key shares for TLS 1.3 handshakes.
229    ///
230    /// **Default:** `None`
231    pub key_shares: Option<Cow<'static, [KeyShare]>>,
232
233    /// Enables PSK with (EC)DHE key establishment (`psk_dhe_ke`).
234    ///
235    /// **Default:** `true`
236    pub psk_dhe_ke: bool,
237
238    /// Enables TLS renegotiation by sending the `renegotiation_info` extension.
239    ///
240    /// **Default:** `true`
241    pub renegotiation: bool,
242
243    /// Delegated Credentials ([RFC 9345](https://datatracker.ietf.org/doc/html/rfc9345)).
244    ///
245    /// Allows TLS 1.3 endpoints to use temporary delegated credentials
246    /// for authentication with reduced long-term key exposure.
247    ///
248    /// **Default:** `None`
249    pub delegated_credentials: Option<Cow<'static, str>>,
250
251    /// List of supported elliptic curves.
252    ///
253    /// **Default:** `None`
254    pub curves_list: Option<Cow<'static, str>>,
255
256    /// List of supported signature algorithms.
257    ///
258    /// **Default:** `None`
259    pub sigalgs_list: Option<Cow<'static, str>>,
260
261    /// Cipher suite configuration string.
262    ///
263    /// Uses BoringSSL's mini-language to select, enable, and prioritize ciphers.
264    ///
265    /// **Default:** `None`
266    pub cipher_list: Option<Cow<'static, str>>,
267
268    /// Sets whether to preserve the TLS 1.3 cipher list as configured by [`Self::cipher_list`].
269    ///
270    /// **Default:** `None`
271    pub preserve_tls13_cipher_list: Option<bool>,
272
273    /// Supported certificate compression algorithms ([RFC 8879](https://datatracker.ietf.org/doc/html/rfc8879)).
274    ///
275    /// **Default:** `None`
276    pub certificate_compressors: Option<Cow<'static, [&'static dyn CertificateCompressor]>>,
277
278    /// Supported TLS extensions, used for extension ordering/permutation.
279    ///
280    /// **Default:** `None`
281    pub extension_permutation: Option<Cow<'static, [ExtensionType]>>,
282
283    /// Overrides AES hardware acceleration.
284    ///
285    /// **Default:** `None`
286    pub aes_hw_override: Option<bool>,
287
288    /// Overrides the random AES hardware acceleration.
289    ///
290    /// **Default:** `false`
291    pub random_aes_hw_override: bool,
292}
293
294impl TlsOptionsBuilder {
295    /// Sets the ALPN protocols to use.
296    #[inline]
297    pub fn alpn_protocols<I>(mut self, alpn: I) -> Self
298    where
299        I: IntoIterator<Item = AlpnProtocol>,
300    {
301        self.config.alpn_protocols = Some(Cow::Owned(alpn.into_iter().collect()));
302        self
303    }
304
305    /// Sets the ALPS protocols to use.
306    #[inline]
307    pub fn alps_protocols<I>(mut self, alps: I) -> Self
308    where
309        I: IntoIterator<Item = AlpsProtocol>,
310    {
311        self.config.alps_protocols = Some(Cow::Owned(alps.into_iter().collect()));
312        self
313    }
314
315    /// Sets whether to use a new codepoint for ALPS.
316    #[inline]
317    pub fn alps_use_new_codepoint(mut self, enabled: bool) -> Self {
318        self.config.alps_use_new_codepoint = enabled;
319        self
320    }
321    /// Sets the session ticket flag.
322    #[inline]
323    pub fn session_ticket(mut self, enabled: bool) -> Self {
324        self.config.session_ticket = enabled;
325        self
326    }
327
328    /// Sets the minimum TLS version to use.
329    #[inline]
330    pub fn min_tls_version<T>(mut self, version: T) -> Self
331    where
332        T: Into<Option<TlsVersion>>,
333    {
334        self.config.min_tls_version = version.into();
335        self
336    }
337
338    /// Sets the maximum TLS version to use.
339    #[inline]
340    pub fn max_tls_version<T>(mut self, version: T) -> Self
341    where
342        T: Into<Option<TlsVersion>>,
343    {
344        self.config.max_tls_version = version.into();
345        self
346    }
347
348    /// Sets the pre-shared key flag.
349    #[inline]
350    pub fn pre_shared_key(mut self, enabled: bool) -> Self {
351        self.config.pre_shared_key = enabled;
352        self
353    }
354
355    /// Sets the GREASE ECH extension flag.
356    #[inline]
357    pub fn enable_ech_grease(mut self, enabled: bool) -> Self {
358        self.config.enable_ech_grease = enabled;
359        self
360    }
361
362    /// Sets whether to permute ClientHello extensions.
363    #[inline]
364    pub fn permute_extensions<T>(mut self, permute: T) -> Self
365    where
366        T: Into<Option<bool>>,
367    {
368        self.config.permute_extensions = permute.into();
369        self
370    }
371
372    /// Sets the GREASE enabled flag.
373    #[inline]
374    pub fn grease_enabled<T>(mut self, enabled: T) -> Self
375    where
376        T: Into<Option<bool>>,
377    {
378        self.config.grease_enabled = enabled.into();
379        self
380    }
381
382    /// Sets the OCSP stapling flag.
383    #[inline]
384    pub fn enable_ocsp_stapling(mut self, enabled: bool) -> Self {
385        self.config.enable_ocsp_stapling = enabled;
386        self
387    }
388
389    /// Sets the signed certificate timestamps flag.
390    #[inline]
391    pub fn enable_signed_cert_timestamps(mut self, enabled: bool) -> Self {
392        self.config.enable_signed_cert_timestamps = enabled;
393        self
394    }
395
396    /// Sets the record size limit.
397    #[inline]
398    pub fn record_size_limit<U: Into<Option<u16>>>(mut self, limit: U) -> Self {
399        self.config.record_size_limit = limit.into();
400        self
401    }
402
403    /// Sets the PSK skip session ticket flag.
404    #[inline]
405    pub fn psk_skip_session_ticket(mut self, skip: bool) -> Self {
406        self.config.psk_skip_session_ticket = skip;
407        self
408    }
409
410    /// Sets the PSK DHE key establishment flag.
411    #[inline]
412    pub fn psk_dhe_ke(mut self, enabled: bool) -> Self {
413        self.config.psk_dhe_ke = enabled;
414        self
415    }
416
417    /// Sets the renegotiation flag.
418    #[inline]
419    pub fn renegotiation(mut self, enabled: bool) -> Self {
420        self.config.renegotiation = enabled;
421        self
422    }
423
424    /// Sets the delegated credentials.
425    #[inline]
426    pub fn delegated_credentials<T>(mut self, creds: T) -> Self
427    where
428        T: Into<Cow<'static, str>>,
429    {
430        self.config.delegated_credentials = Some(creds.into());
431        self
432    }
433
434    /// Sets the client key shares to be used in the TLS 1.3 handshake.
435    #[inline]
436    pub fn key_shares<T>(mut self, key_shares: T) -> Self
437    where
438        T: Into<Cow<'static, [KeyShare]>>,
439    {
440        self.config.key_shares = Some(key_shares.into());
441        self
442    }
443
444    /// Sets the supported curves list.
445    #[inline]
446    pub fn curves_list<T>(mut self, curves: T) -> Self
447    where
448        T: Into<Cow<'static, str>>,
449    {
450        self.config.curves_list = Some(curves.into());
451        self
452    }
453
454    /// Sets the cipher list.
455    #[inline]
456    pub fn cipher_list<T>(mut self, ciphers: T) -> Self
457    where
458        T: Into<Cow<'static, str>>,
459    {
460        self.config.cipher_list = Some(ciphers.into());
461        self
462    }
463
464    /// Sets the supported signature algorithms.
465    #[inline]
466    pub fn sigalgs_list<T>(mut self, sigalgs: T) -> Self
467    where
468        T: Into<Cow<'static, str>>,
469    {
470        self.config.sigalgs_list = Some(sigalgs.into());
471        self
472    }
473
474    /// Sets the certificate compression algorithms.
475    #[inline]
476    pub fn certificate_compressors<T>(mut self, algs: T) -> Self
477    where
478        T: Into<Cow<'static, [&'static dyn CertificateCompressor]>>,
479    {
480        self.config.certificate_compressors = Some(algs.into());
481        self
482    }
483
484    /// Sets the extension permutation.
485    #[inline]
486    pub fn extension_permutation<T>(mut self, permutation: T) -> Self
487    where
488        T: Into<Cow<'static, [ExtensionType]>>,
489    {
490        self.config.extension_permutation = Some(permutation.into());
491        self
492    }
493
494    /// Sets the AES hardware override flag.
495    #[inline]
496    pub fn aes_hw_override<T>(mut self, enabled: T) -> Self
497    where
498        T: Into<Option<bool>>,
499    {
500        self.config.aes_hw_override = enabled.into();
501        self
502    }
503
504    /// Sets the random AES hardware override flag.
505    #[inline]
506    pub fn random_aes_hw_override(mut self, enabled: bool) -> Self {
507        self.config.random_aes_hw_override = enabled;
508        self
509    }
510
511    /// Sets whether to preserve the TLS 1.3 cipher list as configured by [`Self::cipher_list`].
512    ///
513    /// By default, BoringSSL does not preserve the TLS 1.3 cipher list. When this option is
514    /// disabled (the default), BoringSSL uses its internal default TLS 1.3 cipher suites in its
515    /// default order, regardless of what is set via [`Self::cipher_list`].
516    ///
517    /// When enabled, this option ensures that the TLS 1.3 cipher suites explicitly set via
518    /// [`Self::cipher_list`] are retained in their original order, without being reordered or
519    /// modified by BoringSSL's internal logic. This is useful for maintaining specific cipher suite
520    /// priorities for TLS 1.3. Note that if [`Self::cipher_list`] does not include any TLS 1.3
521    /// cipher suites, BoringSSL will still fall back to its default TLS 1.3 cipher suites and
522    /// order.
523    #[inline]
524    pub fn preserve_tls13_cipher_list<T>(mut self, enabled: T) -> Self
525    where
526        T: Into<Option<bool>>,
527    {
528        self.config.preserve_tls13_cipher_list = enabled.into();
529        self
530    }
531
532    /// Builds the `TlsOptions` from the builder.
533    #[inline]
534    pub fn build(self) -> TlsOptions {
535        self.config
536    }
537}
538
539impl TlsOptions {
540    /// Creates a new `TlsOptionsBuilder` instance.
541    pub fn builder() -> TlsOptionsBuilder {
542        TlsOptionsBuilder {
543            config: TlsOptions::default(),
544        }
545    }
546}
547
548impl Default for TlsOptions {
549    fn default() -> Self {
550        TlsOptions {
551            alpn_protocols: Some(Cow::Borrowed(&[AlpnProtocol::HTTP2, AlpnProtocol::HTTP1])),
552            alps_protocols: None,
553            alps_use_new_codepoint: false,
554            session_ticket: true,
555            min_tls_version: None,
556            max_tls_version: None,
557            pre_shared_key: false,
558            enable_ech_grease: false,
559            permute_extensions: None,
560            grease_enabled: None,
561            enable_ocsp_stapling: false,
562            enable_signed_cert_timestamps: false,
563            record_size_limit: None,
564            psk_skip_session_ticket: false,
565            key_shares: None,
566            psk_dhe_ke: true,
567            renegotiation: true,
568            delegated_credentials: None,
569            curves_list: None,
570            cipher_list: None,
571            sigalgs_list: None,
572            certificate_compressors: None,
573            extension_permutation: None,
574            aes_hw_override: None,
575            preserve_tls13_cipher_list: None,
576            random_aes_hw_override: false,
577        }
578    }
579}
580
581#[cfg(test)]
582mod tests {
583    use super::*;
584
585    #[test]
586    fn alpn_protocol_encode() {
587        let alpn = AlpnProtocol::encode_sequence(&[AlpnProtocol::HTTP1, AlpnProtocol::HTTP2]);
588        assert_eq!(alpn, Bytes::from_static(b"\x08http/1.1\x02h2"));
589
590        let alpn = AlpnProtocol::encode_sequence(&[AlpnProtocol::HTTP3]);
591        assert_eq!(alpn, Bytes::from_static(b"\x02h3"));
592
593        let alpn = AlpnProtocol::encode_sequence(&[AlpnProtocol::HTTP1, AlpnProtocol::HTTP3]);
594        assert_eq!(alpn, Bytes::from_static(b"\x08http/1.1\x02h3"));
595
596        let alpn = AlpnProtocol::encode_sequence(&[AlpnProtocol::HTTP2, AlpnProtocol::HTTP3]);
597        assert_eq!(alpn, Bytes::from_static(b"\x02h2\x02h3"));
598
599        let alpn = AlpnProtocol::encode_sequence(&[
600            AlpnProtocol::HTTP1,
601            AlpnProtocol::HTTP2,
602            AlpnProtocol::HTTP3,
603        ]);
604        assert_eq!(alpn, Bytes::from_static(b"\x08http/1.1\x02h2\x02h3"));
605    }
606
607    #[test]
608    fn alpn_protocol_encode_single() {
609        let alpn = AlpnProtocol::HTTP1.encode();
610        assert_eq!(alpn, b"\x08http/1.1".as_ref());
611
612        let alpn = AlpnProtocol::HTTP2.encode();
613        assert_eq!(alpn, b"\x02h2".as_ref());
614
615        let alpn = AlpnProtocol::HTTP3.encode();
616        assert_eq!(alpn, b"\x02h3".as_ref());
617    }
618}