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