OXYGENHUB

Coverage / crates/spectra-mcp/src/tls.rs 80.4%

131 of 163 lines run, 26 of 61 functions · run #31 on main · run missed

1
//! TLS for client and server (spec §8.6). The server makes a certificate
2
//! of its own; the instance key vouches for it with an `instance.tls`
3
//! event; a client trusts that one certificate, pinned by the SHA-256 of
4
//! its SubjectPublicKeyInfo, and never a certificate authority.
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
/// The crypto provider: ring, as OxygenHub's.
1789×
pub fn provider() -> Arc<rustls::crypto::CryptoProvider> {
1889×
    Arc::new(rustls::crypto::ring::default_provider())
1989×
}
20
21
/// SHA-256 of a certificate's SubjectPublicKeyInfo: what a client pins.
2248×
pub fn spki_sha256(cert_der: &[u8]) -> Result<[u8; 32]> {
2348×
    let (_, cert) = x509_parser::parse_x509_certificate(cert_der).map_err(|e| anyhow::anyhow!("not an X.509 certificate: {e}"))?;
2448×
    let d = ring::digest::digest(&ring::digest::SHA256, cert.public_key().raw);
2548×
    Ok(d.as_ref().try_into().expect("SHA-256 is 32 bytes"))
2648×
}
27
28
/// Where a server's certificate comes from (`[server] tls`).
29
#[derive(Clone, Debug)]
30
pub enum CertSource {
31
    /// Made on first start in `<data>/keys/tls.{key,crt}`, naming these names.
32
    Own { names: Vec<String> },
33
    /// Files from elsewhere (PEM).
34
    Files { cert: PathBuf, key: PathBuf },
35
}
36
37
/// A server's TLS: its rustls configuration and the certificate's pin.
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 {
4511×
    pub fn load(data: &Path, source: &CertSource) -> Result<ServerTls> {
4611×
        let (cert_pem, key_pem, names) = match source {
4711×
            CertSource::Own { names } => {
4811×
                let keys = data.join("keys");
4911×
                let (crt, key) = (keys.join("tls.crt"), keys.join("tls.key"));
5011×
                if !crt.exists() || !key.exists() {
519×
                    make_own(&crt, &key, names)?;
522×
                }
5311×
                (std::fs::read_to_string(&crt)?, std::fs::read_to_string(&key)?, names.clone())
54
            }
550×
            CertSource::Files { cert, key } => (
560×
                std::fs::read_to_string(cert).with_context(|| format!("reading {}", cert.display()))?,
570×
                std::fs::read_to_string(key).with_context(|| format!("reading {}", key.display()))?,
580×
                vec![],
59
            ),
60
        };
6111×
        let chain: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut cert_pem.as_bytes()).collect::<Result<_, _>>()?;
6211×
        let leaf = chain.first().context("no certificate in the PEM")?;
6311×
        let spki = spki_sha256(leaf)?;
6411×
        let key: PrivateKeyDer<'static> = rustls_pemfile::private_key(&mut key_pem.as_bytes())?.context("no private key in the PEM")?;
6511×
        let mut config = rustls::ServerConfig::builder_with_provider(provider())
6611×
            .with_safe_default_protocol_versions()?
6711×
            .with_no_client_auth()
6811×
            .with_single_cert(chain, key)?;
6911×
        config.alpn_protocols = vec![b"http/1.1".to_vec()];
7011×
        Ok(ServerTls { config: Arc::new(config), spki_sha256: spki, names })
7111×
    }
72
}
73
749×
fn make_own(crt: &Path, key: &Path, names: &[String]) -> Result<()> {
759×
    if names.is_empty() {
760×
        bail!("a certificate needs at least one name ([server] names)");
779×
    }
789×
    let ck = rcgen::generate_simple_self_signed(names.to_vec())?;
799×
    if let Some(dir) = crt.parent() {
809×
        std::fs::create_dir_all(dir)?;
810×
    }
829×
    write_private(key, ck.signing_key.serialize_pem().as_bytes())?;
839×
    std::fs::write(crt, ck.cert.pem())?;
849×
    Ok(())
859×
}
86
879×
fn write_private(path: &Path, bytes: &[u8]) -> Result<()> {
88
    #[cfg(unix)]
89
    {
90
        use std::io::Write;
91
        use std::os::unix::fs::OpenOptionsExt;
929×
        let mut f = std::fs::OpenOptions::new().write(true).create(true).truncate(true).mode(0o600).open(path)?;
939×
        f.write_all(bytes)?;
949×
        Ok(())
95
    }
96
    #[cfg(not(unix))]
97
    {
98
        std::fs::write(path, bytes)?;
99
        Ok(())
100
    }
1019×
}
102
103
/// A TCP listener that hands axum connections once their TLS handshake is
104
/// done; handshakes run concurrently, so a slow one holds up nobody.
105
pub struct TlsListener {
106
    rx: tokio::sync::mpsc::Receiver<(tokio_rustls::server::TlsStream<TcpStream>, SocketAddr)>,
107
    addr: SocketAddr,
108
}
109
110
impl TlsListener {
1119×
    pub fn new(tcp: TcpListener, config: Arc<rustls::ServerConfig>) -> Result<TlsListener> {
1129×
        let addr = tcp.local_addr()?;
1139×
        let acceptor = tokio_rustls::TlsAcceptor::from(config);
1149×
        let (tx, rx) = tokio::sync::mpsc::channel(64);
1159×
        tokio::spawn(async move {
116
            loop {
11781×
                let (stream, peer) = match tcp.accept().await {
11872×
                    Ok(x) => x,
1190×
                    Err(e) => {
1200×
                        tracing::warn!("accept: {e}");
1210×
                        tokio::time::sleep(Duration::from_millis(50)).await;
1220×
                        continue;
123
                    }
124
                };
12572×
                let (acceptor, out) = (acceptor.clone(), tx.clone());
12672×
                tokio::spawn(async move {
12772×
                    match tokio::time::timeout(Duration::from_secs(10), acceptor.accept(stream)).await {
12870×
                        Ok(Ok(tls)) => {
12970×
                            let _ = out.send((tls, peer)).await;
130
                        }
1312×
                        Ok(Err(e)) => tracing::debug!(%peer, "TLS handshake failed: {e}"),
1320×
                        Err(_) => tracing::debug!(%peer, "TLS handshake timed out"),
133
                    }
13472×
                });
13572×
                if tx.is_closed() {
1360×
                    return;
13772×
                }
138
            }
1390×
        });
1409×
        Ok(TlsListener { rx, addr })
1419×
    }
142
}
143
144
impl axum::serve::Listener for TlsListener {
145
    type Io = tokio_rustls::server::TlsStream<TcpStream>;
146
    type Addr = SocketAddr;
147
14879×
    async fn accept(&mut self) -> (Self::Io, Self::Addr) {
14979×
        match self.rx.recv().await {
15070×
            Some(x) => x,
151
            // The accept loop never ends while we hold the receiver.
1520×
            None => std::future::pending().await,
153
        }
15470×
    }
155
1560×
    fn local_addr(&self) -> std::io::Result<Self::Addr> {
1570×
        Ok(self.addr)
1580×
    }
159
}
160
161
/// A server certificate verifier that accepts exactly one public key, or,
162
/// with no pin, any key, remembering it for the caller to check
163
/// (`spectra connect`). The handshake's signature is always checked, so
164
/// the server must hold the certificate's private key.
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 {
17339×
    pub fn new(pin: Option<[u8; 32]>) -> Arc<Pinned> {
17439×
        Arc::new(Pinned { pin, seen: Mutex::new(None), algs: provider().signature_verification_algorithms })
17539×
    }
176
177
    /// The certificate (DER) and its pin from the last handshake.
17813×
    pub fn seen(&self) -> Option<(Vec<u8>, [u8; 32])> {
17913×
        self.seen.lock().unwrap().clone()
18013×
    }
181
18239×
    pub fn client_config(self: &Arc<Self>) -> Result<Arc<rustls::ClientConfig>> {
18339×
        let mut c = rustls::ClientConfig::builder_with_provider(provider())
18439×
            .with_safe_default_protocol_versions()?
18539×
            .dangerous()
18639×
            .with_custom_certificate_verifier(self.clone())
18739×
            .with_no_client_auth();
18839×
        c.alpn_protocols = vec![b"http/1.1".to_vec()];
18939×
        Ok(Arc::new(c))
19039×
    }
191
}
192
193
impl ServerCertVerifier for Pinned {
19437×
    fn verify_server_cert(
19537×
        &self,
19637×
        end_entity: &CertificateDer<'_>,
19737×
        _intermediates: &[CertificateDer<'_>],
19837×
        _server_name: &ServerName<'_>,
19937×
        _ocsp: &[u8],
20037×
        _now: UnixTime,
20137×
    ) -> Result<ServerCertVerified, rustls::Error> {
20237×
        let spki = spki_sha256(end_entity).map_err(|_| rustls::Error::InvalidCertificate(rustls::CertificateError::BadEncoding))?;
20337×
        *self.seen.lock().unwrap() = Some((end_entity.to_vec(), spki));
20423×
        match self.pin {
20523×
            Some(pin) if pin != spki => Err(rustls::Error::InvalidCertificate(rustls::CertificateError::ApplicationVerificationFailure)),
20635×
            _ => Ok(ServerCertVerified::assertion()),
207
        }
20837×
    }
209
2100×
    fn verify_tls12_signature(
2110×
        &self,
2120×
        message: &[u8],
2130×
        cert: &CertificateDer<'_>,
2140×
        dss: &DigitallySignedStruct,
2150×
    ) -> Result<HandshakeSignatureValid, rustls::Error> {
2160×
        rustls::crypto::verify_tls12_signature(message, cert, dss, &self.algs)
2170×
    }
218
21935×
    fn verify_tls13_signature(
22035×
        &self,
22135×
        message: &[u8],
22235×
        cert: &CertificateDer<'_>,
22335×
        dss: &DigitallySignedStruct,
22435×
    ) -> Result<HandshakeSignatureValid, rustls::Error> {
22535×
        rustls::crypto::verify_tls13_signature(message, cert, dss, &self.algs)
22635×
    }
227
22864×
    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
22964×
        self.algs.supported_schemes()
23064×
    }
231
}
232
233
/// Connect to `host:port` over TLS with `config`.
23464×
pub async fn connect(addr: &str, config: Arc<rustls::ClientConfig>) -> Result<tokio_rustls::client::TlsStream<TcpStream>> {
23564×
    let tcp = tokio::time::timeout(Duration::from_secs(10), TcpStream::connect(addr))
23664×
        .await
23764×
        .with_context(|| format!("connecting to {addr}: timed out"))?
23864×
        .with_context(|| format!("connecting to {addr}"))?;
23964×
    tcp.set_nodelay(true).ok();
24064×
    let host = addr.rsplit_once(':').map(|(h, _)| h.trim_start_matches('[').trim_end_matches(']')).unwrap_or(addr);
24164×
    let name = ServerName::try_from(host.to_string()).unwrap_or_else(|_| ServerName::try_from("spectra").unwrap());
24264×
    let tls = tokio_rustls::TlsConnector::from(config).connect(name, tcp).await.with_context(|| format!("TLS with {addr}"))?;
24362×
    Ok(tls)
24464×
}
245
246
#[cfg(test)]
247
mod tests {
248
    use super::*;
249
250
    #[test]
2511×
    fn own_certificate_is_made_once_and_pinned() {
2521×
        let dir = tempfile::tempdir().unwrap();
2531×
        let src = CertSource::Own { names: vec!["localhost".into()] };
2541×
        let a = ServerTls::load(dir.path(), &src).unwrap();
2551×
        let b = ServerTls::load(dir.path(), &src).unwrap();
2561×
        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;
2601×
            let mode = std::fs::metadata(dir.path().join("keys/tls.key")).unwrap().permissions().mode();
2611×
            assert_eq!(mode & 0o777, 0o600);
262
        }
2631×
    }
264
}