Distributed Erlang over iroh P2P + TLS, with a bot powers SDK
0

Configure Feed

Select the types of activity you want to include in your feed.

erl-iroh / src / transport / tls.rs
9.6 kB 261 lines
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}