1use std::io;
8use std::sync::Arc;
9
10use rustls::pki_types::pem::PemObject;
11use rustls::pki_types::ServerName;
12use tokio::net::TcpStream;
13use tokio_rustls::TlsConnector;
14
15use crate::connection::Transport;
16
17#[derive(Clone, Debug, Default)]
22pub struct TlsClientConfig {
23 pub ca_cert: Option<String>,
27
28 pub insecure: bool,
33}
34
35pub(crate) async fn connect(
39 host: &str,
40 port: u16,
41 config: &TlsClientConfig,
42) -> io::Result<Transport> {
43 let tcp = TcpStream::connect((host, port)).await?;
44 let client_config = build_client_config(config)?;
45 let connector = TlsConnector::from(Arc::new(client_config));
46
47 let server_name = ServerName::try_from(host.to_string()).map_err(|e| {
48 io::Error::new(
49 io::ErrorKind::InvalidInput,
50 format!("invalid server name '{host}': {e}"),
51 )
52 })?;
53
54 let tls_stream = connector.connect(server_name, tcp).await?;
55 Ok(Transport::Tls(Box::new(tls_stream)))
56}
57
58fn build_client_config(config: &TlsClientConfig) -> io::Result<rustls::ClientConfig> {
60 if config.insecure {
61 eprintln!("warning: TLS certificate verification is disabled");
62 return build_insecure_config();
63 }
64
65 let roots = load_root_certs(config.ca_cert.as_deref())?;
66
67 rustls::ClientConfig::builder()
68 .with_root_certificates(roots)
69 .with_no_client_auth()
70 .pipe_ok()
71}
72
73fn load_root_certs(ca_cert_path: Option<&str>) -> io::Result<rustls::RootCertStore> {
75 let mut roots = rustls::RootCertStore::empty();
76
77 if let Some(path) = ca_cert_path {
78 let pem = std::fs::read(path).map_err(|e| {
79 io::Error::new(
80 io::ErrorKind::NotFound,
81 format!("failed to read CA cert '{path}': {e}"),
82 )
83 })?;
84 let certs = rustls::pki_types::CertificateDer::pem_slice_iter(&pem)
85 .collect::<Result<Vec<_>, _>>()
86 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
87
88 if certs.is_empty() {
89 return Err(io::Error::new(
90 io::ErrorKind::InvalidData,
91 format!("no certificates found in '{path}'"),
92 ));
93 }
94
95 for cert in certs {
96 roots.add(cert).map_err(|e| {
97 io::Error::new(
98 io::ErrorKind::InvalidData,
99 format!("invalid CA certificate: {e}"),
100 )
101 })?;
102 }
103 } else {
104 let native_certs = rustls_native_certs::load_native_certs();
105 for cert in native_certs.certs {
106 roots.add(cert).map_err(|e| {
107 io::Error::new(
108 io::ErrorKind::InvalidData,
109 format!("invalid native CA certificate: {e}"),
110 )
111 })?;
112 }
113 }
114
115 Ok(roots)
116}
117
118fn build_insecure_config() -> io::Result<rustls::ClientConfig> {
122 let config = rustls::ClientConfig::builder()
123 .dangerous()
124 .with_custom_certificate_verifier(Arc::new(NoVerifier))
125 .with_no_client_auth();
126 Ok(config)
127}
128
129#[derive(Debug)]
131struct NoVerifier;
132
133impl rustls::client::danger::ServerCertVerifier for NoVerifier {
134 fn verify_server_cert(
135 &self,
136 _end_entity: &rustls::pki_types::CertificateDer<'_>,
137 _intermediates: &[rustls::pki_types::CertificateDer<'_>],
138 _server_name: &ServerName<'_>,
139 _ocsp_response: &[u8],
140 _now: rustls::pki_types::UnixTime,
141 ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
142 Ok(rustls::client::danger::ServerCertVerified::assertion())
143 }
144
145 fn verify_tls12_signature(
146 &self,
147 _message: &[u8],
148 _cert: &rustls::pki_types::CertificateDer<'_>,
149 _dss: &rustls::DigitallySignedStruct,
150 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
151 Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
152 }
153
154 fn verify_tls13_signature(
155 &self,
156 _message: &[u8],
157 _cert: &rustls::pki_types::CertificateDer<'_>,
158 _dss: &rustls::DigitallySignedStruct,
159 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
160 Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
161 }
162
163 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
164 rustls::crypto::aws_lc_rs::default_provider()
165 .signature_verification_algorithms
166 .supported_schemes()
167 }
168}
169
170trait PipeOk: Sized {
172 fn pipe_ok(self) -> io::Result<Self> {
173 Ok(self)
174 }
175}
176
177impl PipeOk for rustls::ClientConfig {}