OXYGENHUB

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

131 of 163 lines run, 26 of 61 functions · run #19 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.
1722×
pub fn provider() -> Arc<rustls::crypto::CryptoProvider> {
1822×
    Arc::new(rustls::crypto::ring::default_provider())
1922×
}
20
21
/// SHA-256 of a certificate's SubjectPublicKeyInfo: what a client pins.
2214×
pub fn spki_sha256(cert_der: &[u8]) -> Result<[u8; 32]> {
2314×
    let (_, cert) = x509_parser::parse_x509_certificate(cert_der).map_err(|e| anyhow::anyhow!("not an X.509 certificate: {e}"))?;
2414×
    let d = ring::digest::digest(&ring::digest::SHA256, cert.public_key().raw);
2514×
    Ok(d.as_ref().try_into().expect("SHA-256 is 32 bytes"))
2614×
}
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 {
456×
    pub fn load(data: &Path, source: &CertSource) -> Result<ServerTls> {
466×
        let (cert_pem, key_pem, names) = match source {
476×
            CertSource::Own { names } => {
486×
                let keys = data.join("keys");
496×
                let (crt, key) = (keys.join("tls.crt"), keys.join("tls.key"));
506×
                if !crt.exists() || !key.exists() {
514×
                    make_own(&crt, &key, names)?;
522×
                }
536×
                (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
        };
616×
        let chain: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut cert_pem.as_bytes()).collect::<Result<_, _>>()?;
626×
        let leaf = chain.first().context("no certificate in the PEM")?;
636×
        let spki = spki_sha256(leaf)?;
646×
        let key: PrivateKeyDer<'static> = rustls_pemfile::private_key(&mut key_pem.as_bytes())?.context("no private key in the PEM")?;
656×
        let mut config = rustls::ServerConfig::builder_with_provider(provider())
666×
            .with_safe_default_protocol_versions()?
676×
            .with_no_client_auth()
686×
            .with_single_cert(chain, key)?;
696×
        config.alpn_protocols = vec![b"http/1.1".to_vec()];
706×
        Ok(ServerTls { config: Arc::new(config), spki_sha256: spki, names })
716×
    }
72
}
73
744×
fn make_own(crt: &Path, key: &Path, names: &[String]) -> Result<()> {
754×
    if names.is_empty() {
760×
        bail!("a certificate needs at least one name ([server] names)");
774×
    }
784×
    let ck = rcgen::generate_simple_self_signed(names.to_vec())?;
794×
    if let Some(dir) = crt.parent() {
804×
        std::fs::create_dir_all(dir)?;
810×
    }
824×
    write_private(key, ck.signing_key.serialize_pem().as_bytes())?;
834×
    std::fs::write(crt, ck.cert.pem())?;
844×
    Ok(())
854×
}
86
874×
fn write_private(path: &Path, bytes: &[u8]) -> Result<()> {
88
    #[cfg(unix)]
89
    {
90
        use std::io::Write;
91
        use std::os::unix::fs::OpenOptionsExt;
924×
        let mut f = std::fs::OpenOptions::new().write(true).create(true).truncate(true).mode(0o600).open(path)?;
934×
        f.write_all(bytes)?;
944×
        Ok(())
95
    }
96
    #[cfg(not(unix))]
97
    {
98
        std::fs::write(path, bytes)?;
99
        Ok(())
100
    }
1014×
}
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 {
1114×
    pub fn new(tcp: TcpListener, config: Arc<rustls::ServerConfig>) -> Result<TlsListener> {
1124×
        let addr = tcp.local_addr()?;
1134×
        let acceptor = tokio_rustls::TlsAcceptor::from(config);
1144×
        let (tx, rx) = tokio::sync::mpsc::channel(64);
1154×
        tokio::spawn(async move {
116
            loop {
11718×
                let (stream, peer) = match tcp.accept().await {
11814×
                    Ok(x) => x,
1190×
                    Err(e) => {
1200×
                        tracing::warn!("accept: {e}");
1210×
                        tokio::time::sleep(Duration::from_millis(50)).await;
1220×
                        continue;
123
                    }
124
                };
12514×
                let (acceptor, out) = (acceptor.clone(), tx.clone());
12614×
                tokio::spawn(async move {
12714×
                    match tokio::time::timeout(Duration::from_secs(10), acceptor.accept(stream)).await {
12813×
                        Ok(Ok(tls)) => {
12913×
                            let _ = out.send((tls, peer)).await;
130
                        }
1311×
                        Ok(Err(e)) => tracing::debug!(%peer, "TLS handshake failed: {e}"),
1320×
                        Err(_) => tracing::debug!(%peer, "TLS handshake timed out"),
133
                    }
13414×
                });
13514×
                if tx.is_closed() {
1360×
                    return;
13714×
                }
138
            }
1390×
        });
1404×
        Ok(TlsListener { rx, addr })
1414×
    }
142
}
143
144
impl axum::serve::Listener for TlsListener {
145
    type Io = tokio_rustls::server::TlsStream<TcpStream>;
146
    type Addr = SocketAddr;
147
14817×
    async fn accept(&mut self) -> (Self::Io, Self::Addr) {
14916×
        match self.rx.recv().await {
15013×
            Some(x) => x,
151
            // The accept loop never ends while we hold the receiver.
1520×
            None => std::future::pending().await,
153
        }
15413×
    }
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 {
1738×
    pub fn new(pin: Option<[u8; 32]>) -> Arc<Pinned> {
1748×
        Arc::new(Pinned { pin, seen: Mutex::new(None), algs: provider().signature_verification_algorithms })
1758×
    }
176
177
    /// The certificate (DER) and its pin from the last handshake.
1784×
    pub fn seen(&self) -> Option<(Vec<u8>, [u8; 32])> {
1794×
        self.seen.lock().unwrap().clone()
1804×
    }
181
1828×
    pub fn client_config(self: &Arc<Self>) -> Result<Arc<rustls::ClientConfig>> {
1838×
        let mut c = rustls::ClientConfig::builder_with_provider(provider())
1848×
            .with_safe_default_protocol_versions()?
1858×
            .dangerous()
1868×
            .with_custom_certificate_verifier(self.clone())
1878×
            .with_no_client_auth();
1888×
        c.alpn_protocols = vec![b"http/1.1".to_vec()];
1898×
        Ok(Arc::new(c))
1908×
    }
191
}
192
193
impl ServerCertVerifier for Pinned {
1948×
    fn verify_server_cert(
1958×
        &self,
1968×
        end_entity: &CertificateDer<'_>,
1978×
        _intermediates: &[CertificateDer<'_>],
1988×
        _server_name: &ServerName<'_>,
1998×
        _ocsp: &[u8],
2008×
        _now: UnixTime,
2018×
    ) -> Result<ServerCertVerified, rustls::Error> {
2028×
        let spki = spki_sha256(end_entity).map_err(|_| rustls::Error::InvalidCertificate(rustls::CertificateError::BadEncoding))?;
2038×
        *self.seen.lock().unwrap() = Some((end_entity.to_vec(), spki));
2044×
        match self.pin {
2054×
            Some(pin) if pin != spki => Err(rustls::Error::InvalidCertificate(rustls::CertificateError::ApplicationVerificationFailure)),
2067×
            _ => Ok(ServerCertVerified::assertion()),
207
        }
2088×
    }
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
2197×
    fn verify_tls13_signature(
2207×
        &self,
2217×
        message: &[u8],
2227×
        cert: &CertificateDer<'_>,
2237×
        dss: &DigitallySignedStruct,
2247×
    ) -> Result<HandshakeSignatureValid, rustls::Error> {
2257×
        rustls::crypto::verify_tls13_signature(message, cert, dss, &self.algs)
2267×
    }
227
22814×
    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
22914×
        self.algs.supported_schemes()
23014×
    }
231
}
232
233
/// Connect to `host:port` over TLS with `config`.
23414×
pub async fn connect(addr: &str, config: Arc<rustls::ClientConfig>) -> Result<tokio_rustls::client::TlsStream<TcpStream>> {
23514×
    let tcp = tokio::time::timeout(Duration::from_secs(10), TcpStream::connect(addr))
23614×
        .await
23714×
        .with_context(|| format!("connecting to {addr}: timed out"))?
23814×
        .with_context(|| format!("connecting to {addr}"))?;
23914×
    tcp.set_nodelay(true).ok();
24014×
    let host = addr.rsplit_once(':').map(|(h, _)| h.trim_start_matches('[').trim_end_matches(']')).unwrap_or(addr);
24114×
    let name = ServerName::try_from(host.to_string()).unwrap_or_else(|_| ServerName::try_from("spectra").unwrap());
24214×
    let tls = tokio_rustls::TlsConnector::from(config).connect(name, tcp).await.with_context(|| format!("TLS with {addr}"))?;
24313×
    Ok(tls)
24414×
}
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
}