Skip to main content

reqwest/
tls.rs

1//! TLS configuration and types
2//!
3//! A `Client` will use transport layer security (TLS) by default to connect to
4//! HTTPS destinations.
5//!
6//! # Backends
7//!
8//! `reqwest-boring` is a fork of reqwest with BoringSSL as its default TLS
9//! layer. An optional system-native backend is also available through Cargo
10//! features.
11//!
12//! ## default-tls
13//!
14//! The `default-tls` feature enables BoringSSL through the `boring` and
15//! `tokio-boring` crates. HTTP/3 uses Quiche with the same BoringSSL build.
16//!
17//! <div class="warning">This feature is enabled by default, and takes
18//! precedence if any other crate enables it. This is true even if you declare
19//! `features = []`. You must set `default-features = false` instead.</div>
20//!
21//! Since Cargo features are additive, other crates in your dependency tree can
22//! cause the default backend to be enabled. If you wish to ensure your
23//! `Client` uses a specific backend, call the appropriate builder methods
24//! (such as [`tls_backend_rustls()`][]).
25//!
26//! [`tls_backend_rustls()`]: crate::ClientBuilder::tls_backend_rustls()
27//!
28//! ## native-tls
29//!
30//! This backend uses the [native-tls][] crate. That will try to use the system
31//! TLS on Windows and Mac, and OpenSSL on Linux targets.
32//!
33//! Enabling the feature explicitly allows for `native-tls`-specific
34//! configuration options.
35//!
36//! [native-tls]: https://crates.io/crates/native-tls
37//!
38//! ## boring, rustls, rustls-no-provider
39//!
40//! These features select BoringSSL through the `boring` crate. The legacy
41//! rustls feature and builder names are retained for source compatibility.
42//! No Rustls crypto provider is needed. Preconfigured TLS accepts a
43//! `boring::ssl::SslConnector` instead of Rustls configuration objects (or a
44//! native-tls connector when that backend is enabled). HTTP/3 is configured
45//! through the standard builder methods.
46
47use std::{
48    fmt,
49    io::{BufRead, BufReader},
50};
51
52/// Represents a X509 certificate revocation list.
53#[cfg(feature = "__rustls")]
54pub struct CertificateRevocationList {
55    #[cfg(feature = "__rustls")]
56    inner: Vec<u8>,
57}
58
59/// Represents a server X509 certificate.
60#[derive(Clone)]
61pub struct Certificate {
62    #[cfg(feature = "__native-tls")]
63    native: native_tls_crate::Certificate,
64    #[cfg(feature = "__rustls")]
65    original: Cert,
66}
67
68#[cfg(feature = "__rustls")]
69#[derive(Clone)]
70enum Cert {
71    Der(Vec<u8>),
72    Pem(Vec<u8>),
73}
74
75/// Represents a private key and X509 cert as a client certificate.
76#[derive(Clone)]
77pub struct Identity {
78    #[cfg_attr(
79        not(any(feature = "__native-tls", feature = "__rustls")),
80        allow(unused)
81    )]
82    inner: ClientCert,
83}
84
85enum ClientCert {
86    #[cfg(feature = "__native-tls")]
87    Pkcs12(native_tls_crate::Identity),
88    #[cfg(feature = "__native-tls")]
89    Pkcs8(native_tls_crate::Identity),
90    #[cfg(feature = "__rustls")]
91    Pem { key: Vec<u8>, certs: Vec<Vec<u8>> },
92}
93
94impl Clone for ClientCert {
95    fn clone(&self) -> Self {
96        match self {
97            #[cfg(feature = "__native-tls")]
98            Self::Pkcs8(i) => Self::Pkcs8(i.clone()),
99            #[cfg(feature = "__native-tls")]
100            Self::Pkcs12(i) => Self::Pkcs12(i.clone()),
101            #[cfg(feature = "__rustls")]
102            ClientCert::Pem { key, certs } => ClientCert::Pem {
103                key: key.clone(),
104                certs: certs.clone(),
105            },
106            #[cfg_attr(
107                any(feature = "__native-tls", feature = "__rustls"),
108                allow(unreachable_patterns)
109            )]
110            _ => unreachable!(),
111        }
112    }
113}
114
115impl Certificate {
116    /// Create a `Certificate` from a binary DER encoded certificate
117    ///
118    /// # Examples
119    ///
120    /// ```
121    /// # use std::fs::File;
122    /// # use std::io::Read;
123    /// # fn cert() -> Result<(), Box<dyn std::error::Error>> {
124    /// let mut buf = Vec::new();
125    /// File::open("my_cert.der")?
126    ///     .read_to_end(&mut buf)?;
127    /// let cert = reqwest::Certificate::from_der(&buf)?;
128    /// # drop(cert);
129    /// # Ok(())
130    /// # }
131    /// ```
132    pub fn from_der(der: &[u8]) -> crate::Result<Certificate> {
133        Ok(Certificate {
134            #[cfg(feature = "__native-tls")]
135            native: native_tls_crate::Certificate::from_der(der).map_err(crate::error::builder)?,
136            #[cfg(feature = "__rustls")]
137            original: Cert::Der(der.to_owned()),
138        })
139    }
140
141    /// Create a `Certificate` from a PEM encoded certificate
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// # use std::fs::File;
147    /// # use std::io::Read;
148    /// # fn cert() -> Result<(), Box<dyn std::error::Error>> {
149    /// let mut buf = Vec::new();
150    /// File::open("my_cert.pem")?
151    ///     .read_to_end(&mut buf)?;
152    /// let cert = reqwest::Certificate::from_pem(&buf)?;
153    /// # drop(cert);
154    /// # Ok(())
155    /// # }
156    /// ```
157    pub fn from_pem(pem: &[u8]) -> crate::Result<Certificate> {
158        Ok(Certificate {
159            #[cfg(feature = "__native-tls")]
160            native: native_tls_crate::Certificate::from_pem(pem).map_err(crate::error::builder)?,
161            #[cfg(feature = "__rustls")]
162            original: Cert::Pem(pem.to_owned()),
163        })
164    }
165
166    /// Create a collection of `Certificate`s from a PEM encoded certificate bundle.
167    /// Example byte sources may be `.crt`, `.cer` or `.pem` files.
168    ///
169    /// # Examples
170    ///
171    /// ```
172    /// # use std::fs::File;
173    /// # use std::io::Read;
174    /// # fn cert() -> Result<(), Box<dyn std::error::Error>> {
175    /// let mut buf = Vec::new();
176    /// File::open("ca-bundle.crt")?
177    ///     .read_to_end(&mut buf)?;
178    /// let certs = reqwest::Certificate::from_pem_bundle(&buf)?;
179    /// # drop(certs);
180    /// # Ok(())
181    /// # }
182    /// ```
183    pub fn from_pem_bundle(pem_bundle: &[u8]) -> crate::Result<Vec<Certificate>> {
184        let mut reader = BufReader::new(pem_bundle);
185
186        Self::read_pem_certs(&mut reader)?
187            .iter()
188            .map(|cert_vec| Certificate::from_der(cert_vec))
189            .collect::<crate::Result<Vec<Certificate>>>()
190    }
191
192    #[cfg(feature = "__native-tls")]
193    pub(crate) fn add_to_native_tls(self, tls: &mut native_tls_crate::TlsConnectorBuilder) {
194        tls.add_root_certificate(self.native);
195    }
196
197    #[cfg(all(feature = "__rustls", target_vendor = "apple"))]
198    pub(crate) fn ders(&self) -> crate::Result<Vec<Vec<u8>>> {
199        match &self.original {
200            Cert::Der(der) => Ok(vec![der.clone()]),
201            Cert::Pem(pem) => Self::read_pem_certs(&mut &pem[..]),
202        }
203    }
204
205    #[cfg(feature = "__rustls")]
206    pub(crate) fn add_to_boring(
207        self,
208        store: &mut boring::x509::store::X509StoreBuilder,
209    ) -> crate::Result<()> {
210        let certs = match self.original {
211            Cert::Der(der) => vec![der],
212            Cert::Pem(pem) => Self::read_pem_certs(&mut &pem[..])?,
213        };
214        if certs.is_empty() {
215            return Err(crate::error::builder("no certificates found"));
216        }
217        for der in certs {
218            store
219                .add_cert(boring::x509::X509::from_der(&der).map_err(crate::error::builder)?)
220                .map_err(crate::error::builder)?;
221        }
222        Ok(())
223    }
224
225    fn read_pem_certs(reader: &mut impl BufRead) -> crate::Result<Vec<Vec<u8>>> {
226        let mut buf = Vec::new();
227        reader
228            .read_to_end(&mut buf)
229            .map_err(crate::error::builder)?;
230        Ok(pem::parse_many(buf)
231            .map_err(crate::error::builder)?
232            .into_iter()
233            .filter(|p| p.tag() == "CERTIFICATE")
234            .map(|p| p.into_contents())
235            .collect())
236    }
237}
238
239impl Identity {
240    /// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key.
241    ///
242    /// The archive should contain a leaf certificate and its private key, as well any intermediate
243    /// certificates that allow clients to build a chain to a trusted root.
244    /// The chain certificates should be in order from the leaf certificate towards the root.
245    ///
246    /// PKCS #12 archives typically have the file extension `.p12` or `.pfx`, and can be created
247    /// with the OpenSSL `pkcs12` tool:
248    ///
249    /// ```bash
250    /// openssl pkcs12 -export -out identity.pfx -inkey key.pem -in cert.pem -certfile chain_certs.pem
251    /// ```
252    ///
253    /// # Examples
254    ///
255    /// ```
256    /// # use std::fs::File;
257    /// # use std::io::Read;
258    /// # fn pkcs12() -> Result<(), Box<dyn std::error::Error>> {
259    /// let mut buf = Vec::new();
260    /// File::open("my-ident.pfx")?
261    ///     .read_to_end(&mut buf)?;
262    /// let pkcs12 = reqwest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?;
263    /// # drop(pkcs12);
264    /// # Ok(())
265    /// # }
266    /// ```
267    ///
268    /// # Optional
269    ///
270    /// This requires the `native-tls` Cargo feature enabled.
271    #[cfg(feature = "__native-tls")]
272    pub fn from_pkcs12_der(der: &[u8], password: &str) -> crate::Result<Identity> {
273        Ok(Identity {
274            inner: ClientCert::Pkcs12(
275                native_tls_crate::Identity::from_pkcs12(der, password)
276                    .map_err(crate::error::builder)?,
277            ),
278        })
279    }
280
281    /// Parses a chain of PEM encoded X509 certificates, with the leaf certificate first.
282    /// `key` is a PEM encoded PKCS #8 formatted private key for the leaf certificate.
283    ///
284    /// The certificate chain should contain any intermediate certificates that should be sent to
285    /// clients to allow them to build a chain to a trusted root.
286    ///
287    /// A certificate chain here means a series of PEM encoded certificates concatenated together.
288    ///
289    /// # Examples
290    ///
291    /// ```
292    /// # use std::fs;
293    /// # fn pkcs8() -> Result<(), Box<dyn std::error::Error>> {
294    /// let cert = fs::read("client.pem")?;
295    /// let key = fs::read("key.pem")?;
296    /// let pkcs8 = reqwest::Identity::from_pkcs8_pem(&cert, &key)?;
297    /// # drop(pkcs8);
298    /// # Ok(())
299    /// # }
300    /// ```
301    ///
302    /// # Optional
303    ///
304    /// This requires the `native-tls` Cargo feature enabled.
305    #[cfg(feature = "__native-tls")]
306    pub fn from_pkcs8_pem(pem: &[u8], key: &[u8]) -> crate::Result<Identity> {
307        Ok(Identity {
308            inner: ClientCert::Pkcs8(
309                native_tls_crate::Identity::from_pkcs8(pem, key).map_err(crate::error::builder)?,
310            ),
311        })
312    }
313
314    /// Parses PEM encoded private key and certificate.
315    ///
316    /// The input should contain a PEM encoded private key
317    /// and at least one PEM encoded certificate.
318    ///
319    /// Note: The private key must be in RSA, SEC1 Elliptic Curve or PKCS#8 format.
320    ///
321    /// # Examples
322    ///
323    /// ```
324    /// # use std::fs::File;
325    /// # use std::io::Read;
326    /// # fn pem() -> Result<(), Box<dyn std::error::Error>> {
327    /// let mut buf = Vec::new();
328    /// File::open("my-ident.pem")?
329    ///     .read_to_end(&mut buf)?;
330    /// let id = reqwest::Identity::from_pem(&buf)?;
331    /// # drop(id);
332    /// # Ok(())
333    /// # }
334    /// ```
335    ///
336    /// # Optional
337    ///
338    /// This requires the `boring` (or its legacy `rustls` aliases) Cargo feature enabled.
339    #[cfg(feature = "__rustls")]
340    pub fn from_pem(buf: &[u8]) -> crate::Result<Identity> {
341        let blocks = pem::parse_many(buf).map_err(crate::error::builder)?;
342        let mut certs = Vec::new();
343        let mut key = None;
344        for block in blocks {
345            match block.tag() {
346                "CERTIFICATE" => certs.push(block.into_contents()),
347                "PRIVATE KEY" | "RSA PRIVATE KEY" | "EC PRIVATE KEY" => {
348                    key = Some(pem::encode(&block).into_bytes())
349                }
350                _ => return Err(crate::error::builder("invalid identity PEM section")),
351            }
352        }
353        let key = key.ok_or_else(|| crate::error::builder("private key not found"))?;
354        if certs.is_empty() {
355            return Err(crate::error::builder("certificate not found"));
356        }
357        Ok(Identity {
358            inner: ClientCert::Pem { key, certs },
359        })
360    }
361
362    #[cfg(feature = "__native-tls")]
363    pub(crate) fn add_to_native_tls(
364        self,
365        tls: &mut native_tls_crate::TlsConnectorBuilder,
366    ) -> crate::Result<()> {
367        match self.inner {
368            ClientCert::Pkcs12(id) | ClientCert::Pkcs8(id) => {
369                tls.identity(id);
370                Ok(())
371            }
372            #[cfg(feature = "__rustls")]
373            ClientCert::Pem { .. } => Err(crate::error::builder("incompatible TLS identity type")),
374        }
375    }
376
377    #[cfg(feature = "__rustls")]
378    pub(crate) fn add_to_boring(
379        self,
380        tls: &mut boring::ssl::SslContextBuilder,
381    ) -> crate::Result<()> {
382        match self.inner {
383            ClientCert::Pem { key, certs } => {
384                let mut certs = certs.into_iter();
385                let cert = boring::x509::X509::from_der(
386                    &certs
387                        .next()
388                        .ok_or_else(|| crate::error::builder("certificate not found"))?,
389                )
390                .map_err(crate::error::builder)?;
391                tls.set_certificate(&cert).map_err(crate::error::builder)?;
392                for cert in certs {
393                    tls.add_extra_chain_cert(
394                        boring::x509::X509::from_der(&cert).map_err(crate::error::builder)?,
395                    )
396                    .map_err(crate::error::builder)?;
397                }
398                let key = boring::pkey::PKey::private_key_from_pem(&key)
399                    .map_err(crate::error::builder)?;
400                tls.set_private_key(&key).map_err(crate::error::builder)?;
401                tls.check_private_key().map_err(crate::error::builder)
402            }
403            #[cfg(feature = "__native-tls")]
404            _ => Err(crate::error::builder("incompatible TLS identity type")),
405        }
406    }
407}
408
409#[cfg(feature = "__rustls")]
410impl CertificateRevocationList {
411    /// Parses a PEM encoded CRL.
412    ///
413    /// # Examples
414    ///
415    /// ```
416    /// # use std::fs::File;
417    /// # use std::io::Read;
418    /// # fn crl() -> Result<(), Box<dyn std::error::Error>> {
419    /// let mut buf = Vec::new();
420    /// File::open("my_crl.pem")?
421    ///     .read_to_end(&mut buf)?;
422    /// let crl = reqwest::tls::CertificateRevocationList::from_pem(&buf)?;
423    /// # drop(crl);
424    /// # Ok(())
425    /// # }
426    /// ```
427    ///
428    /// # Optional
429    ///
430    /// This requires the `boring` (or its legacy `rustls` aliases) Cargo feature enabled.
431    #[cfg(feature = "__rustls")]
432    pub fn from_pem(pem: &[u8]) -> crate::Result<CertificateRevocationList> {
433        let block = pem::parse(pem).map_err(crate::error::builder)?;
434        if block.tag() != "X509 CRL" {
435            return Err(crate::error::builder("invalid crl encoding"));
436        }
437        Ok(CertificateRevocationList {
438            inner: block.into_contents(),
439        })
440    }
441
442    /// Creates a collection of `CertificateRevocationList`s from a PEM encoded CRL bundle.
443    /// Example byte sources may be `.crl` or `.pem` files.
444    ///
445    /// # Examples
446    ///
447    /// ```
448    /// # use std::fs::File;
449    /// # use std::io::Read;
450    /// # fn crls() -> Result<(), Box<dyn std::error::Error>> {
451    /// let mut buf = Vec::new();
452    /// File::open("crl-bundle.crl")?
453    ///     .read_to_end(&mut buf)?;
454    /// let crls = reqwest::tls::CertificateRevocationList::from_pem_bundle(&buf)?;
455    /// # drop(crls);
456    /// # Ok(())
457    /// # }
458    /// ```
459    ///
460    /// # Optional
461    ///
462    /// This requires the `boring` (or its legacy `rustls` aliases) Cargo feature enabled.
463    #[cfg(feature = "__rustls")]
464    pub fn from_pem_bundle(pem_bundle: &[u8]) -> crate::Result<Vec<CertificateRevocationList>> {
465        Ok(pem::parse_many(pem_bundle)
466            .map_err(crate::error::builder)?
467            .into_iter()
468            .filter(|p| p.tag() == "X509 CRL")
469            .map(|p| CertificateRevocationList {
470                inner: p.into_contents(),
471            })
472            .collect())
473    }
474
475    pub(crate) fn add_to_boring(
476        &self,
477        store: &mut boring::x509::store::X509StoreBuilder,
478    ) -> crate::Result<()> {
479        use foreign_types::ForeignType;
480        let mut input = self.inner.as_ptr();
481        let len = self.inner.len().try_into().map_err(crate::error::builder)?;
482        // SAFETY: DER input is valid for `len` bytes. The returned owned CRL is
483        // freed after the store takes its own reference, including on failure.
484        let ok = unsafe {
485            let crl = boring_sys::d2i_X509_CRL(std::ptr::null_mut(), &mut input, len);
486            if crl.is_null() {
487                return Err(crate::error::builder(boring::error::ErrorStack::get()));
488            }
489            let ok = boring_sys::X509_STORE_add_crl(store.as_ptr(), crl);
490            boring_sys::X509_CRL_free(crl);
491            ok
492        };
493        if ok != 1 {
494            return Err(crate::error::builder(boring::error::ErrorStack::get()));
495        }
496        Ok(())
497    }
498}
499
500impl fmt::Debug for Certificate {
501    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
502        f.debug_struct("Certificate").finish()
503    }
504}
505
506impl fmt::Debug for Identity {
507    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
508        f.debug_struct("Identity").finish()
509    }
510}
511
512#[cfg(feature = "__rustls")]
513impl fmt::Debug for CertificateRevocationList {
514    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
515        f.debug_struct("CertificateRevocationList").finish()
516    }
517}
518
519/// A TLS protocol version.
520#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
521pub struct Version(InnerVersion);
522
523#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
524#[non_exhaustive]
525enum InnerVersion {
526    Tls1_0,
527    Tls1_1,
528    Tls1_2,
529    Tls1_3,
530}
531
532// These could perhaps be From/TryFrom implementations, but those would be
533// part of the public API so let's be careful
534impl Version {
535    /// Version 1.0 of the TLS protocol.
536    pub const TLS_1_0: Version = Version(InnerVersion::Tls1_0);
537    /// Version 1.1 of the TLS protocol.
538    pub const TLS_1_1: Version = Version(InnerVersion::Tls1_1);
539    /// Version 1.2 of the TLS protocol.
540    pub const TLS_1_2: Version = Version(InnerVersion::Tls1_2);
541    /// Version 1.3 of the TLS protocol.
542    pub const TLS_1_3: Version = Version(InnerVersion::Tls1_3);
543
544    #[cfg(feature = "__native-tls")]
545    pub(crate) fn to_native_tls(self) -> Option<native_tls_crate::Protocol> {
546        match self.0 {
547            InnerVersion::Tls1_0 => Some(native_tls_crate::Protocol::Tlsv10),
548            InnerVersion::Tls1_1 => Some(native_tls_crate::Protocol::Tlsv11),
549            InnerVersion::Tls1_2 => Some(native_tls_crate::Protocol::Tlsv12),
550            InnerVersion::Tls1_3 => Some(native_tls_crate::Protocol::Tlsv13),
551        }
552    }
553
554    #[cfg(feature = "__rustls")]
555    pub(crate) fn to_boring(self) -> boring::ssl::SslVersion {
556        use boring::ssl::SslVersion;
557        match self.0 {
558            InnerVersion::Tls1_0 => SslVersion::TLS1,
559            InnerVersion::Tls1_1 => SslVersion::TLS1_1,
560            InnerVersion::Tls1_2 => SslVersion::TLS1_2,
561            InnerVersion::Tls1_3 => SslVersion::TLS1_3,
562        }
563    }
564
565    #[cfg(feature = "__rustls")]
566    pub(crate) fn from_boring(version: boring::ssl::SslVersion) -> Option<Self> {
567        [Self::TLS_1_0, Self::TLS_1_1, Self::TLS_1_2, Self::TLS_1_3]
568            .into_iter()
569            .find(|v| v.to_boring() == version)
570    }
571}
572
573pub(crate) enum TlsBackend {
574    // This is the default and HTTP/3 feature does not use it so suppress it.
575    #[allow(dead_code)]
576    #[cfg(feature = "__native-tls")]
577    NativeTls,
578    #[cfg(feature = "__native-tls")]
579    BuiltNativeTls(native_tls_crate::TlsConnector),
580    #[cfg(feature = "__rustls")]
581    Boring,
582    #[cfg(feature = "__rustls")]
583    BuiltBoring(boring::ssl::SslConnector),
584    #[cfg(any(feature = "__native-tls", feature = "__rustls",))]
585    UnknownPreconfigured,
586}
587
588impl fmt::Debug for TlsBackend {
589    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
590        match self {
591            #[cfg(feature = "__native-tls")]
592            TlsBackend::NativeTls => write!(f, "NativeTls"),
593            #[cfg(feature = "__native-tls")]
594            TlsBackend::BuiltNativeTls(_) => write!(f, "BuiltNativeTls"),
595            #[cfg(feature = "__rustls")]
596            TlsBackend::Boring => write!(f, "Boring"),
597            #[cfg(feature = "__rustls")]
598            TlsBackend::BuiltBoring(_) => write!(f, "BuiltBoring"),
599            #[cfg(any(feature = "__native-tls", feature = "__rustls",))]
600            TlsBackend::UnknownPreconfigured => write!(f, "UnknownPreconfigured"),
601        }
602    }
603}
604
605#[allow(clippy::derivable_impls)]
606impl Default for TlsBackend {
607    fn default() -> TlsBackend {
608        #[cfg(any(
609            all(feature = "__rustls", not(feature = "__native-tls")),
610            feature = "http3"
611        ))]
612        {
613            TlsBackend::Boring
614        }
615
616        #[cfg(all(feature = "__native-tls", not(feature = "http3")))]
617        {
618            TlsBackend::NativeTls
619        }
620    }
621}
622
623/// Hyper extension carrying extra TLS layer information.
624/// Made available to clients on responses when `tls_info` is set.
625#[derive(Clone)]
626pub struct TlsInfo {
627    pub(crate) peer_certificate: Option<Vec<u8>>,
628    pub(crate) version: Option<Version>,
629}
630
631impl TlsInfo {
632    /// Get the DER encoded leaf certificate of the peer.
633    pub fn peer_certificate(&self) -> Option<&[u8]> {
634        self.peer_certificate.as_ref().map(|der| &der[..])
635    }
636
637    /// Get the TLS protocol version negotiated with the peer.
638    ///
639    /// Returns `None` if the TLS backend cannot report it. The `native-tls`
640    /// backend never reports a version.
641    pub fn version(&self) -> Option<Version> {
642        self.version
643    }
644}
645
646impl std::fmt::Debug for TlsInfo {
647    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
648        f.debug_struct("TlsInfo")
649            .field("version", &self.version)
650            .finish()
651    }
652}
653
654#[cfg(test)]
655mod tests {
656    use super::*;
657
658    #[cfg(feature = "__native-tls")]
659    #[test]
660    fn certificate_from_der_invalid() {
661        Certificate::from_der(b"not der").unwrap_err();
662    }
663
664    #[cfg(feature = "__native-tls")]
665    #[test]
666    fn certificate_from_pem_invalid() {
667        Certificate::from_pem(b"not pem").unwrap_err();
668    }
669
670    #[cfg(feature = "__native-tls")]
671    #[test]
672    fn identity_from_pkcs12_der_invalid() {
673        Identity::from_pkcs12_der(b"not der", "nope").unwrap_err();
674    }
675
676    #[cfg(feature = "__native-tls")]
677    #[test]
678    fn identity_from_pkcs8_pem_invalid() {
679        Identity::from_pkcs8_pem(b"not pem", b"not key").unwrap_err();
680    }
681
682    #[cfg(feature = "__rustls")]
683    #[test]
684    fn identity_from_pem_invalid() {
685        Identity::from_pem(b"not pem").unwrap_err();
686    }
687
688    #[cfg(feature = "__rustls")]
689    #[test]
690    fn identity_from_pem_pkcs1_key() {
691        let pem = b"-----BEGIN CERTIFICATE-----\n\
692            -----END CERTIFICATE-----\n\
693            -----BEGIN RSA PRIVATE KEY-----\n\
694            -----END RSA PRIVATE KEY-----\n";
695
696        Identity::from_pem(pem).unwrap();
697    }
698
699    #[test]
700    fn certificates_from_pem_bundle() {
701        const PEM_BUNDLE: &[u8] = b"
702            -----BEGIN CERTIFICATE-----
703            MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5
704            MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
705            Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
706            A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
707            Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl
708            ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j
709            QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr
710            ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr
711            BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM
712            YyRIHN8wfdVoOw==
713            -----END CERTIFICATE-----
714
715            -----BEGIN CERTIFICATE-----
716            MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5
717            MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
718            Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
719            A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
720            Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi
721            9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk
722            M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB
723            /zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB
724            MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw
725            CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW
726            1KyLa2tJElMzrdfkviT8tQp21KW8EA==
727            -----END CERTIFICATE-----
728        ";
729
730        assert!(Certificate::from_pem_bundle(PEM_BUNDLE).is_ok())
731    }
732
733    #[cfg(feature = "__rustls")]
734    #[test]
735    fn crl_from_pem() {
736        let pem = b"-----BEGIN X509 CRL-----\n-----END X509 CRL-----\n";
737
738        CertificateRevocationList::from_pem(pem).unwrap();
739    }
740
741    #[cfg(feature = "__rustls")]
742    #[test]
743    fn invalid_crl_from_pem() {
744        CertificateRevocationList::from_pem(b"Invalid").unwrap_err();
745    }
746
747    #[cfg(feature = "__rustls")]
748    #[test]
749    fn crl_from_pem_bundle() {
750        let pem_bundle = std::fs::read("tests/support/crl.pem").unwrap();
751
752        let result = CertificateRevocationList::from_pem_bundle(&pem_bundle);
753
754        assert!(result.is_ok());
755        let result = result.unwrap();
756        assert_eq!(result.len(), 1);
757    }
758}