Distributed Erlang over iroh P2P + TLS, with a bot powers SDK
1//! TCP + rustls transport for classic BEAM peers (and pedants).
2//!
3//! Layering:
4//! ```text
5//! erl_dist handshake / messages
6//! │
7//! rustls TLS 1.2/1.3 (optional mutual auth)
8//! │
9//! TCP
10//! ```
11//!
12//! iroh already encrypts with TLS under QUIC; this module is for talking to
13//! (or proxying) traditional TCP listeners that require TLS.
14
15use crate::error::{Error, Result};
16use crate::stream::DistStream;
17use rustls::ClientConfig as RustlsClientConfig;
18use rustls::ServerConfig as RustlsServerConfig;
19use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName};
20use rustls::RootCertStore;
21use std::fs::File;
22use std::io::BufReader;
23use std::path::Path;
24use std::sync::Arc;
25use tokio::net::{TcpListener, TcpStream};
26use tokio_rustls::{TlsAcceptor, TlsConnector};
27use tracing::debug;
28
29/// TLS material for client and/or server roles.
30#[derive(Clone)]
31pub struct TlsConfig {
32 /// Client connector (required to dial).
33 pub client: Option<Arc<RustlsClientConfig>>,
34 /// Server acceptor (required to accept).
35 pub server: Option<Arc<RustlsServerConfig>>,
36}
37
38impl std::fmt::Debug for TlsConfig {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("TlsConfig")
41 .field("client", &self.client.is_some())
42 .field("server", &self.server.is_some())
43 .finish()
44 }
45}
46
47impl TlsConfig {
48 /// Build a client config that trusts the webpki root store (public CAs).
49 pub fn client_webpki() -> Result<Self> {
50 let mut roots = RootCertStore::empty();
51 roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
52 let config = RustlsClientConfig::builder()
53 .with_root_certificates(roots)
54 .with_no_client_auth();
55 Ok(Self {
56 client: Some(Arc::new(config)),
57 server: None,
58 })
59 }
60
61 /// Client that trusts a custom CA PEM file (and optionally presents a client cert).
62 pub fn client_with_ca(
63 ca_pem: impl AsRef<Path>,
64 client_cert_pem: Option<&Path>,
65 client_key_pem: Option<&Path>,
66 ) -> Result<Self> {
67 let mut roots = RootCertStore::empty();
68 let ca_file = File::open(ca_pem.as_ref()).map_err(|e| Error::tls(e))?;
69 let mut reader = BufReader::new(ca_file);
70 for cert in rustls_pemfile::certs(&mut reader) {
71 let cert = cert.map_err(|e| Error::tls(e))?;
72 roots
73 .add(cert)
74 .map_err(|e| Error::tls(format!("add ca: {e}")))?;
75 }
76
77 let builder = RustlsClientConfig::builder().with_root_certificates(roots);
78 let config = match (client_cert_pem, client_key_pem) {
79 (Some(cert_path), Some(key_path)) => {
80 let certs = load_certs(cert_path)?;
81 let key = load_key(key_path)?;
82 builder
83 .with_client_auth_cert(certs, key)
84 .map_err(|e| Error::tls(e))?
85 }
86 _ => builder.with_no_client_auth(),
87 };
88
89 Ok(Self {
90 client: Some(Arc::new(config)),
91 server: None,
92 })
93 }
94
95 /// Server config from cert + key PEM files. Optionally require client certs.
96 pub fn server(
97 cert_pem: impl AsRef<Path>,
98 key_pem: impl AsRef<Path>,
99 client_ca_pem: Option<&Path>,
100 ) -> Result<Self> {
101 let certs = load_certs(cert_pem.as_ref())?;
102 let key = load_key(key_pem.as_ref())?;
103
104 let config = if let Some(ca) = client_ca_pem {
105 let mut roots = RootCertStore::empty();
106 let ca_file = File::open(ca).map_err(|e| Error::tls(e))?;
107 let mut reader = BufReader::new(ca_file);
108 for cert in rustls_pemfile::certs(&mut reader) {
109 let cert = cert.map_err(|e| Error::tls(e))?;
110 roots
111 .add(cert)
112 .map_err(|e| Error::tls(format!("add client ca: {e}")))?;
113 }
114 let verifier = rustls::server::WebPkiClientVerifier::builder(Arc::new(roots))
115 .build()
116 .map_err(|e| Error::tls(e))?;
117 RustlsServerConfig::builder()
118 .with_client_cert_verifier(verifier)
119 .with_single_cert(certs, key)
120 .map_err(|e| Error::tls(e))?
121 } else {
122 RustlsServerConfig::builder()
123 .with_no_client_auth()
124 .with_single_cert(certs, key)
125 .map_err(|e| Error::tls(e))?
126 };
127
128 Ok(Self {
129 client: None,
130 server: Some(Arc::new(config)),
131 })
132 }
133
134 /// Combine client + server configs.
135 pub fn merge(mut self, other: TlsConfig) -> Self {
136 if other.client.is_some() {
137 self.client = other.client;
138 }
139 if other.server.is_some() {
140 self.server = other.server;
141 }
142 self
143 }
144
145 /// Dangerous: client that skips server cert verification (dev / self-signed).
146 pub fn client_insecure() -> Result<Self> {
147 use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
148 use rustls::{DigitallySignedStruct, Error as TlsError, SignatureScheme};
149 use rustls_pki_types::{ServerName as PkiServerName, UnixTime};
150
151 #[derive(Debug)]
152 struct NoVerify;
153 impl ServerCertVerifier for NoVerify {
154 fn verify_server_cert(
155 &self,
156 _end_entity: &CertificateDer<'_>,
157 _intermediates: &[CertificateDer<'_>],
158 _server_name: &PkiServerName<'_>,
159 _ocsp_response: &[u8],
160 _now: UnixTime,
161 ) -> std::result::Result<ServerCertVerified, TlsError> {
162 Ok(ServerCertVerified::assertion())
163 }
164 fn verify_tls12_signature(
165 &self,
166 _message: &[u8],
167 _cert: &CertificateDer<'_>,
168 _dss: &DigitallySignedStruct,
169 ) -> std::result::Result<HandshakeSignatureValid, TlsError> {
170 Ok(HandshakeSignatureValid::assertion())
171 }
172 fn verify_tls13_signature(
173 &self,
174 _message: &[u8],
175 _cert: &CertificateDer<'_>,
176 _dss: &DigitallySignedStruct,
177 ) -> std::result::Result<HandshakeSignatureValid, TlsError> {
178 Ok(HandshakeSignatureValid::assertion())
179 }
180 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
181 rustls::crypto::ring::default_provider()
182 .signature_verification_algorithms
183 .supported_schemes()
184 }
185 }
186
187 let config = RustlsClientConfig::builder()
188 .dangerous()
189 .with_custom_certificate_verifier(Arc::new(NoVerify))
190 .with_no_client_auth();
191 Ok(Self {
192 client: Some(Arc::new(config)),
193 server: None,
194 })
195 }
196}
197
198/// TLS dial/accept helpers.
199pub struct TlsTransport;
200
201impl TlsTransport {
202 /// Dial `host:port` over TLS. `server_name` is the SNI / cert name.
203 pub async fn connect(
204 host: &str,
205 port: u16,
206 server_name: &str,
207 tls: &TlsConfig,
208 ) -> Result<DistStream> {
209 let client = tls
210 .client
211 .as_ref()
212 .ok_or_else(|| Error::tls("TlsConfig has no client config"))?;
213 debug!(%host, port, %server_name, "tls connect");
214 let tcp = TcpStream::connect((host, port)).await?;
215 tcp.set_nodelay(true)?;
216 let connector = TlsConnector::from(Arc::clone(client));
217 let name = ServerName::try_from(server_name.to_string())
218 .map_err(|e| Error::tls(format!("invalid server name: {e}")))?;
219 let tls_stream = connector
220 .connect(name, tcp)
221 .await
222 .map_err(|e| Error::tls(e))?;
223 Ok(DistStream::from_rw(tls_stream))
224 }
225
226 /// Accept one TLS connection on an already-bound [`TcpListener`].
227 pub async fn accept(listener: &TcpListener, tls: &TlsConfig) -> Result<DistStream> {
228 let server = tls
229 .server
230 .as_ref()
231 .ok_or_else(|| Error::tls("TlsConfig has no server config"))?;
232 let (tcp, peer) = listener.accept().await?;
233 tcp.set_nodelay(true)?;
234 debug!(%peer, "tls accept");
235 let acceptor = TlsAcceptor::from(Arc::clone(server));
236 let tls_stream = acceptor.accept(tcp).await.map_err(|e| Error::tls(e))?;
237 Ok(DistStream::from_rw(tls_stream))
238 }
239}
240
241fn load_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>> {
242 let file = File::open(path).map_err(|e| Error::tls(e))?;
243 let mut reader = BufReader::new(file);
244 rustls_pemfile::certs(&mut reader)
245 .collect::<std::result::Result<Vec<_>, _>>()
246 .map_err(|e| Error::tls(e))
247}
248
249fn load_key(path: &Path) -> Result<PrivateKeyDer<'static>> {
250 let file = File::open(path).map_err(|e| Error::tls(e))?;
251 let mut reader = BufReader::new(file);
252 loop {
253 match rustls_pemfile::read_one(&mut reader).map_err(|e| Error::tls(e))? {
254 Some(rustls_pemfile::Item::Pkcs8Key(k)) => return Ok(PrivateKeyDer::Pkcs8(k)),
255 Some(rustls_pemfile::Item::Pkcs1Key(k)) => return Ok(PrivateKeyDer::Pkcs1(k)),
256 Some(rustls_pemfile::Item::Sec1Key(k)) => return Ok(PrivateKeyDer::Sec1(k)),
257 None => return Err(Error::tls(format!("no private key in {}", path.display()))),
258 _ => continue,
259 }
260 }
261}