ureq/
rtls.rs

1use std::convert::TryFrom;
2use std::fmt;
3use std::io::{self, Read, Write};
4use std::net::TcpStream;
5use std::sync::Arc;
6
7use once_cell::sync::Lazy;
8
9use crate::ErrorKind;
10use crate::{
11    stream::{ReadWrite, TlsConnector},
12    Error,
13};
14
15#[allow(deprecated)]
16fn is_close_notify(e: &std::io::Error) -> bool {
17    if e.kind() != io::ErrorKind::ConnectionAborted {
18        return false;
19    }
20
21    if let Some(msg) = e.get_ref() {
22        // :(
23
24        return msg.description().contains("CloseNotify");
25    }
26
27    false
28}
29
30struct RustlsStream(rustls::StreamOwned<rustls::ClientConnection, Box<dyn ReadWrite>>);
31
32impl ReadWrite for RustlsStream {
33    fn socket(&self) -> Option<&TcpStream> {
34        self.0.get_ref().socket()
35    }
36}
37
38// TODO: After upgrading to rustls 0.20 or higher, we can remove these Read
39// and Write impls, leaving only `impl TlsStream for rustls::StreamOwned...`.
40// Currently we need to implement Read in order to treat close_notify specially.
41// The next release of rustls will handle close_notify in a more intuitive way.
42impl Read for RustlsStream {
43    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
44        match self.0.read(buf) {
45            Ok(size) => Ok(size),
46            Err(ref e) if is_close_notify(e) => Ok(0),
47            Err(e) => Err(e),
48        }
49    }
50}
51
52impl Write for RustlsStream {
53    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
54        self.0.write(buf)
55    }
56
57    fn flush(&mut self) -> io::Result<()> {
58        self.0.flush()
59    }
60}
61
62#[cfg(feature = "native-certs")]
63fn root_certs() -> rustls::RootCertStore {
64    use log::error;
65
66    let mut root_cert_store = rustls::RootCertStore::empty();
67    let native_certs = rustls_native_certs::load_native_certs().unwrap_or_else(|e| {
68        error!("loading native certificates: {}", e);
69        vec![]
70    });
71    let (valid_count, invalid_count) =
72        root_cert_store.add_parsable_certificates(native_certs.into_iter().map(|c| c.into()));
73    if valid_count == 0 && invalid_count > 0 {
74        error!(
75            "no valid certificates loaded by rustls-native-certs. all HTTPS requests will fail."
76        );
77    }
78    root_cert_store
79}
80
81#[cfg(not(feature = "native-certs"))]
82fn root_certs() -> rustls::RootCertStore {
83    rustls::RootCertStore {
84        roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
85    }
86}
87
88impl TlsConnector for Arc<rustls::ClientConfig> {
89    fn connect(
90        &self,
91        dns_name: &str,
92        mut io: Box<dyn ReadWrite>,
93    ) -> Result<Box<dyn ReadWrite>, Error> {
94        let dns_name = if dns_name.starts_with('[') && dns_name.ends_with(']') {
95            // rustls doesn't like ipv6 addresses with brackets
96            &dns_name[1..dns_name.len() - 1]
97        } else {
98            dns_name
99        };
100
101        let sni = rustls_pki_types::ServerName::try_from(dns_name)
102            .map_err(|e| ErrorKind::Dns.msg(format!("parsing '{}'", dns_name)).src(e))?
103            .to_owned();
104
105        let mut sess = rustls::ClientConnection::new(self.clone(), sni)
106            .map_err(|e| ErrorKind::Io.msg("tls connection creation failed").src(e))?;
107
108        sess.complete_io(&mut io).map_err(|e| {
109            ErrorKind::ConnectionFailed
110                .msg("tls connection init failed")
111                .src(e)
112        })?;
113        let stream = rustls::StreamOwned::new(sess, io);
114
115        Ok(Box::new(RustlsStream(stream)))
116    }
117}
118
119pub fn default_tls_config() -> Arc<dyn TlsConnector> {
120    static TLS_CONF: Lazy<Arc<dyn TlsConnector>> = Lazy::new(|| {
121        // `ureq` prefers to use `*ring*` by default. It unconditionally activates the Rustls
122        // feature for this backend, so we know `rustls::crypto::ring::default_provider()` is
123        // available. We use `builder_with_provider()` instead of `builder()` here to avoid the
124        // risk that the rustls features are ambiguous and no process wide default crypto provider
125        // has been set yet.
126        let config = rustls::ClientConfig::builder_with_provider(
127            rustls::crypto::ring::default_provider().into(),
128        )
129        .with_protocol_versions(&[&rustls::version::TLS12, &rustls::version::TLS13])
130        .unwrap() // Safety: the *ring* default provider always configures ciphersuites compatible w/ both TLS 1.2 & TLS 1.3
131        .with_root_certificates(root_certs())
132        .with_no_client_auth();
133        Arc::new(Arc::new(config))
134    });
135    TLS_CONF.clone()
136}
137
138impl fmt::Debug for RustlsStream {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        f.debug_tuple("RustlsStream").finish()
141    }
142}