| 1 | |
|
| 2 | |
|
| 3 | |
|
| 4 | |
|
| 5 | |
|
| 6 | | use anyhow::{bail, Context, Result}; |
| 7 | | use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; |
| 8 | | use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime}; |
| 9 | | use rustls::{DigitallySignedStruct, SignatureScheme}; |
| 10 | | use std::net::SocketAddr; |
| 11 | | use std::path::{Path, PathBuf}; |
| 12 | | use std::sync::{Arc, Mutex}; |
| 13 | | use std::time::Duration; |
| 14 | | use tokio::net::{TcpListener, TcpStream}; |
| 15 | |
|
| 16 | |
|
| 17 | 89× | pub fn provider() -> Arc<rustls::crypto::CryptoProvider> { |
| 18 | 89× | Arc::new(rustls::crypto::ring::default_provider()) |
| 19 | 89× | } |
| 20 | |
|
| 21 | |
|
| 22 | 48× | pub fn spki_sha256(cert_der: &[u8]) -> Result<[u8; 32]> { |
| 23 | 48× | let (_, cert) = x509_parser::parse_x509_certificate(cert_der).map_err(|e| anyhow::anyhow!("not an X.509 certificate: {e}"))?; |
| 24 | 48× | let d = ring::digest::digest(&ring::digest::SHA256, cert.public_key().raw); |
| 25 | 48× | Ok(d.as_ref().try_into().expect("SHA-256 is 32 bytes")) |
| 26 | 48× | } |
| 27 | |
|
| 28 | |
|
| 29 | | #[derive(Clone, Debug)] |
| 30 | | pub enum CertSource { |
| 31 | | |
| 32 | | Own { names: Vec<String> }, |
| 33 | | |
| 34 | | Files { cert: PathBuf, key: PathBuf }, |
| 35 | | } |
| 36 | |
|
| 37 | |
|
| 38 | | pub struct ServerTls { |
| 39 | | pub config: Arc<rustls::ServerConfig>, |
| 40 | | pub spki_sha256: [u8; 32], |
| 41 | | pub names: Vec<String>, |
| 42 | | } |
| 43 | |
|
| 44 | | impl ServerTls { |
| 45 | 11× | pub fn load(data: &Path, source: &CertSource) -> Result<ServerTls> { |
| 46 | 11× | let (cert_pem, key_pem, names) = match source { |
| 47 | 11× | CertSource::Own { names } => { |
| 48 | 11× | let keys = data.join("keys"); |
| 49 | 11× | let (crt, key) = (keys.join("tls.crt"), keys.join("tls.key")); |
| 50 | 11× | if !crt.exists() || !key.exists() { |
| 51 | 9× | make_own(&crt, &key, names)?; |
| 52 | 2× | } |
| 53 | 11× | (std::fs::read_to_string(&crt)?, std::fs::read_to_string(&key)?, names.clone()) |
| 54 | | } |
| 55 | 0× | CertSource::Files { cert, key } => ( |
| 56 | 0× | std::fs::read_to_string(cert).with_context(|| format!("reading {}", cert.display()))?, |
| 57 | 0× | std::fs::read_to_string(key).with_context(|| format!("reading {}", key.display()))?, |
| 58 | 0× | vec![], |
| 59 | | ), |
| 60 | | }; |
| 61 | 11× | let chain: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut cert_pem.as_bytes()).collect::<Result<_, _>>()?; |
| 62 | 11× | let leaf = chain.first().context("no certificate in the PEM")?; |
| 63 | 11× | let spki = spki_sha256(leaf)?; |
| 64 | 11× | let key: PrivateKeyDer<'static> = rustls_pemfile::private_key(&mut key_pem.as_bytes())?.context("no private key in the PEM")?; |
| 65 | 11× | let mut config = rustls::ServerConfig::builder_with_provider(provider()) |
| 66 | 11× | .with_safe_default_protocol_versions()? |
| 67 | 11× | .with_no_client_auth() |
| 68 | 11× | .with_single_cert(chain, key)?; |
| 69 | 11× | config.alpn_protocols = vec![b"http/1.1".to_vec()]; |
| 70 | 11× | Ok(ServerTls { config: Arc::new(config), spki_sha256: spki, names }) |
| 71 | 11× | } |
| 72 | | } |
| 73 | |
|
| 74 | 9× | fn make_own(crt: &Path, key: &Path, names: &[String]) -> Result<()> { |
| 75 | 9× | if names.is_empty() { |
| 76 | 0× | bail!("a certificate needs at least one name ([server] names)"); |
| 77 | 9× | } |
| 78 | 9× | let ck = rcgen::generate_simple_self_signed(names.to_vec())?; |
| 79 | 9× | if let Some(dir) = crt.parent() { |
| 80 | 9× | std::fs::create_dir_all(dir)?; |
| 81 | 0× | } |
| 82 | 9× | write_private(key, ck.signing_key.serialize_pem().as_bytes())?; |
| 83 | 9× | std::fs::write(crt, ck.cert.pem())?; |
| 84 | 9× | Ok(()) |
| 85 | 9× | } |
| 86 | |
|
| 87 | 9× | fn write_private(path: &Path, bytes: &[u8]) -> Result<()> { |
| 88 | | #[cfg(unix)] |
| 89 | | { |
| 90 | | use std::io::Write; |
| 91 | | use std::os::unix::fs::OpenOptionsExt; |
| 92 | 9× | let mut f = std::fs::OpenOptions::new().write(true).create(true).truncate(true).mode(0o600).open(path)?; |
| 93 | 9× | f.write_all(bytes)?; |
| 94 | 9× | Ok(()) |
| 95 | | } |
| 96 | | #[cfg(not(unix))] |
| 97 | | { |
| 98 | | std::fs::write(path, bytes)?; |
| 99 | | Ok(()) |
| 100 | | } |
| 101 | 9× | } |
| 102 | |
|
| 103 | |
|
| 104 | |
|
| 105 | | pub struct TlsListener { |
| 106 | | rx: tokio::sync::mpsc::Receiver<(tokio_rustls::server::TlsStream<TcpStream>, SocketAddr)>, |
| 107 | | addr: SocketAddr, |
| 108 | | } |
| 109 | |
|
| 110 | | impl TlsListener { |
| 111 | 9× | pub fn new(tcp: TcpListener, config: Arc<rustls::ServerConfig>) -> Result<TlsListener> { |
| 112 | 9× | let addr = tcp.local_addr()?; |
| 113 | 9× | let acceptor = tokio_rustls::TlsAcceptor::from(config); |
| 114 | 9× | let (tx, rx) = tokio::sync::mpsc::channel(64); |
| 115 | 9× | tokio::spawn(async move { |
| 116 | | loop { |
| 117 | 81× | let (stream, peer) = match tcp.accept().await { |
| 118 | 72× | Ok(x) => x, |
| 119 | 0× | Err(e) => { |
| 120 | 0× | tracing::warn!("accept: {e}"); |
| 121 | 0× | tokio::time::sleep(Duration::from_millis(50)).await; |
| 122 | 0× | continue; |
| 123 | | } |
| 124 | | }; |
| 125 | 72× | let (acceptor, out) = (acceptor.clone(), tx.clone()); |
| 126 | 72× | tokio::spawn(async move { |
| 127 | 72× | match tokio::time::timeout(Duration::from_secs(10), acceptor.accept(stream)).await { |
| 128 | 70× | Ok(Ok(tls)) => { |
| 129 | 70× | let _ = out.send((tls, peer)).await; |
| 130 | | } |
| 131 | 2× | Ok(Err(e)) => tracing::debug!(%peer, "TLS handshake failed: {e}"), |
| 132 | 0× | Err(_) => tracing::debug!(%peer, "TLS handshake timed out"), |
| 133 | | } |
| 134 | 72× | }); |
| 135 | 72× | if tx.is_closed() { |
| 136 | 0× | return; |
| 137 | 72× | } |
| 138 | | } |
| 139 | 0× | }); |
| 140 | 9× | Ok(TlsListener { rx, addr }) |
| 141 | 9× | } |
| 142 | | } |
| 143 | |
|
| 144 | | impl axum::serve::Listener for TlsListener { |
| 145 | | type Io = tokio_rustls::server::TlsStream<TcpStream>; |
| 146 | | type Addr = SocketAddr; |
| 147 | |
|
| 148 | 79× | async fn accept(&mut self) -> (Self::Io, Self::Addr) { |
| 149 | 79× | match self.rx.recv().await { |
| 150 | 70× | Some(x) => x, |
| 151 | | |
| 152 | 0× | None => std::future::pending().await, |
| 153 | | } |
| 154 | 70× | } |
| 155 | |
|
| 156 | 0× | fn local_addr(&self) -> std::io::Result<Self::Addr> { |
| 157 | 0× | Ok(self.addr) |
| 158 | 0× | } |
| 159 | | } |
| 160 | |
|
| 161 | |
|
| 162 | |
|
| 163 | |
|
| 164 | |
|
| 165 | | #[derive(Debug)] |
| 166 | | pub struct Pinned { |
| 167 | | pin: Option<[u8; 32]>, |
| 168 | | seen: Mutex<Option<(Vec<u8>, [u8; 32])>>, |
| 169 | | algs: rustls::crypto::WebPkiSupportedAlgorithms, |
| 170 | | } |
| 171 | |
|
| 172 | | impl Pinned { |
| 173 | 39× | pub fn new(pin: Option<[u8; 32]>) -> Arc<Pinned> { |
| 174 | 39× | Arc::new(Pinned { pin, seen: Mutex::new(None), algs: provider().signature_verification_algorithms }) |
| 175 | 39× | } |
| 176 | |
|
| 177 | | |
| 178 | 13× | pub fn seen(&self) -> Option<(Vec<u8>, [u8; 32])> { |
| 179 | 13× | self.seen.lock().unwrap().clone() |
| 180 | 13× | } |
| 181 | |
|
| 182 | 39× | pub fn client_config(self: &Arc<Self>) -> Result<Arc<rustls::ClientConfig>> { |
| 183 | 39× | let mut c = rustls::ClientConfig::builder_with_provider(provider()) |
| 184 | 39× | .with_safe_default_protocol_versions()? |
| 185 | 39× | .dangerous() |
| 186 | 39× | .with_custom_certificate_verifier(self.clone()) |
| 187 | 39× | .with_no_client_auth(); |
| 188 | 39× | c.alpn_protocols = vec![b"http/1.1".to_vec()]; |
| 189 | 39× | Ok(Arc::new(c)) |
| 190 | 39× | } |
| 191 | | } |
| 192 | |
|
| 193 | | impl ServerCertVerifier for Pinned { |
| 194 | 37× | fn verify_server_cert( |
| 195 | 37× | &self, |
| 196 | 37× | end_entity: &CertificateDer<'_>, |
| 197 | 37× | _intermediates: &[CertificateDer<'_>], |
| 198 | 37× | _server_name: &ServerName<'_>, |
| 199 | 37× | _ocsp: &[u8], |
| 200 | 37× | _now: UnixTime, |
| 201 | 37× | ) -> Result<ServerCertVerified, rustls::Error> { |
| 202 | 37× | let spki = spki_sha256(end_entity).map_err(|_| rustls::Error::InvalidCertificate(rustls::CertificateError::BadEncoding))?; |
| 203 | 37× | *self.seen.lock().unwrap() = Some((end_entity.to_vec(), spki)); |
| 204 | 23× | match self.pin { |
| 205 | 23× | Some(pin) if pin != spki => Err(rustls::Error::InvalidCertificate(rustls::CertificateError::ApplicationVerificationFailure)), |
| 206 | 35× | _ => Ok(ServerCertVerified::assertion()), |
| 207 | | } |
| 208 | 37× | } |
| 209 | |
|
| 210 | 0× | fn verify_tls12_signature( |
| 211 | 0× | &self, |
| 212 | 0× | message: &[u8], |
| 213 | 0× | cert: &CertificateDer<'_>, |
| 214 | 0× | dss: &DigitallySignedStruct, |
| 215 | 0× | ) -> Result<HandshakeSignatureValid, rustls::Error> { |
| 216 | 0× | rustls::crypto::verify_tls12_signature(message, cert, dss, &self.algs) |
| 217 | 0× | } |
| 218 | |
|
| 219 | 35× | fn verify_tls13_signature( |
| 220 | 35× | &self, |
| 221 | 35× | message: &[u8], |
| 222 | 35× | cert: &CertificateDer<'_>, |
| 223 | 35× | dss: &DigitallySignedStruct, |
| 224 | 35× | ) -> Result<HandshakeSignatureValid, rustls::Error> { |
| 225 | 35× | rustls::crypto::verify_tls13_signature(message, cert, dss, &self.algs) |
| 226 | 35× | } |
| 227 | |
|
| 228 | 64× | fn supported_verify_schemes(&self) -> Vec<SignatureScheme> { |
| 229 | 64× | self.algs.supported_schemes() |
| 230 | 64× | } |
| 231 | | } |
| 232 | |
|
| 233 | |
|
| 234 | 64× | pub async fn connect(addr: &str, config: Arc<rustls::ClientConfig>) -> Result<tokio_rustls::client::TlsStream<TcpStream>> { |
| 235 | 64× | let tcp = tokio::time::timeout(Duration::from_secs(10), TcpStream::connect(addr)) |
| 236 | 64× | .await |
| 237 | 64× | .with_context(|| format!("connecting to {addr}: timed out"))? |
| 238 | 64× | .with_context(|| format!("connecting to {addr}"))?; |
| 239 | 64× | tcp.set_nodelay(true).ok(); |
| 240 | 64× | let host = addr.rsplit_once(':').map(|(h, _)| h.trim_start_matches('[').trim_end_matches(']')).unwrap_or(addr); |
| 241 | 64× | let name = ServerName::try_from(host.to_string()).unwrap_or_else(|_| ServerName::try_from("spectra").unwrap()); |
| 242 | 64× | let tls = tokio_rustls::TlsConnector::from(config).connect(name, tcp).await.with_context(|| format!("TLS with {addr}"))?; |
| 243 | 62× | Ok(tls) |
| 244 | 64× | } |
| 245 | |
|
| 246 | | #[cfg(test)] |
| 247 | | mod tests { |
| 248 | | use super::*; |
| 249 | |
|
| 250 | | #[test] |
| 251 | 1× | fn own_certificate_is_made_once_and_pinned() { |
| 252 | 1× | let dir = tempfile::tempdir().unwrap(); |
| 253 | 1× | let src = CertSource::Own { names: vec!["localhost".into()] }; |
| 254 | 1× | let a = ServerTls::load(dir.path(), &src).unwrap(); |
| 255 | 1× | let b = ServerTls::load(dir.path(), &src).unwrap(); |
| 256 | 1× | assert_eq!(a.spki_sha256, b.spki_sha256, "the certificate is kept between starts"); |
| 257 | | #[cfg(unix)] |
| 258 | | { |
| 259 | | use std::os::unix::fs::PermissionsExt; |
| 260 | 1× | let mode = std::fs::metadata(dir.path().join("keys/tls.key")).unwrap().permissions().mode(); |
| 261 | 1× | assert_eq!(mode & 0o777, 0o600); |
| 262 | | } |
| 263 | 1× | } |
| 264 | | } |