OXYGENHUB

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

131 of 163 lines run, 26 of 61 functions · run #35 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.
1782×
pub fn provider() -> Arc<rustls::crypto::CryptoProvider> {
1882×
    Arc::new(rustls::crypto::ring::default_provider())
1982×
}
20
21
/// SHA-256 of a certificate's SubjectPublicKeyInfo: what a client pins.
2244×
pub fn spki_sha256(cert_der: &[u8]) -> Result<[u8; 32]> {
2344×
    let (_, cert) = x509_parser::parse_x509_certificate(cert_der).map_err(|e| anyhow::anyhow!("not an X.509 certificate: {e}"))?;
2444×
    let d = ring::digest::digest(&ring::digest::SHA256, cert.public_key().raw);
2544×
    Ok(d.as_ref().try_into().expect("SHA-256 is 32 bytes"))
2644×
}
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 {
4510×
    pub fn load(data: &Path, source: &CertSource) -> Result<ServerTls> {
4610×
        let (cert_pem, key_pem, names) = match source {
4710×
            CertSource::Own { names } => {
4810×
                let keys = data.join("keys");
4910×
                let (crt, key) = (keys.join("tls.crt"), keys.join("tls.key"));
5010×
                if !crt.exists() || !key.exists() {
518×
                    make_own(&crt, &key, names)?;
522×
                }
5310×
                (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
        };
6110×
        let chain: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut cert_pem.as_bytes()).collect::<Result<_, _>>()?;
6210×
        let leaf = chain.first().context("no certificate in the PEM")?;
6310×
        let spki = spki_sha256(leaf)?;
6410×
        let key: PrivateKeyDer<'static> = rustls_pemfile::private_key(&mut key_pem.as_bytes())?.context("no private key in the PEM")?;
6510×
        let mut config = rustls::ServerConfig::builder_with_provider(provider())
6610×
            .with_safe_default_protocol_versions()?
6710×
            .with_no_client_auth()
6810×
            .with_single_cert(chain, key)?;
6910×
        config.alpn_protocols = vec![b"http/1.1".to_vec()];
7010×
        Ok(ServerTls { config: Arc::new(config), spki_sha256: spki, names })
7110×
    }
72
}
73
748×
fn make_own(crt: &Path, key: &Path, names: &[String]) -> Result<()> {
758×
    if names.is_empty() {
760×
        bail!("a certificate needs at least one name ([server] names)");
778×
    }
788×
    let ck = rcgen::generate_simple_self_signed(names.to_vec())?;
798×
    if let Some(dir) = crt.parent() {
808×
        std::fs::create_dir_all(dir)?;
810×
    }
828×
    write_private(key, ck.signing_key.serialize_pem().as_bytes())?;
838×
    std::fs::write(crt, ck.cert.pem())?;
848×
    Ok(())
858×
}
86
878×
fn write_private(path: &Path, bytes: &[u8]) -> Result<()> {
88
    #[cfg(unix)]
89
    {
90
        use std::io::Write;
91
        use std::os::unix::fs::OpenOptionsExt;
928×
        let mut f = std::fs::OpenOptions::new().write(true).create(true).truncate(true).mode(0o600).open(path)?;
938×
        f.write_all(bytes)?;
948×
        Ok(())
95
    }
96
    #[cfg(not(unix))]
97
    {
98
        std::fs::write(path, bytes)?;
99
        Ok(())
100
    }
1018×
}
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 {
1118×
    pub fn new(tcp: TcpListener, config: Arc<rustls::ServerConfig>) -> Result<TlsListener> {
1128×
        let addr = tcp.local_addr()?;
1138×
        let acceptor = tokio_rustls::TlsAcceptor::from(config);
1148×
        let (tx, rx) = tokio::sync::mpsc::channel(64);
1158×
        tokio::spawn(async move {
116
            loop {
11770×
                let (stream, peer) = match tcp.accept().await {
11862×
                    Ok(x) => x,
1190×
                    Err(e) => {
1200×
                        tracing::warn!("accept: {e}");
1210×
                        tokio::time::sleep(Duration::from_millis(50)).await;
1220×
                        continue;
123
                    }
124
                };
12562×
                let (acceptor, out) = (acceptor.clone(), tx.clone());
12662×
                tokio::spawn(async move {
12762×
                    match tokio::time::timeout(Duration::from_secs(10), acceptor.accept(stream)).await {
12860×
                        Ok(Ok(tls)) => {
12960×
                            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
                    }
13462×
                });
13562×
                if tx.is_closed() {
1360×
                    return;
13762×
                }
138
            }
1390×
        });
1408×
        Ok(TlsListener { rx, addr })
1418×
    }
142
}
143
144
impl axum::serve::Listener for TlsListener {
145
    type Io = tokio_rustls::server::TlsStream<TcpStream>;
146
    type Addr = SocketAddr;
147
14868×
    async fn accept(&mut self) -> (Self::Io, Self::Addr) {
14968×
        match self.rx.recv().await {
15060×
            Some(x) => x,
151
            // The accept loop never ends while we hold the receiver.
1520×
            None => std::future::pending().await,
153
        }
15460×
    }
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 {
17336×
    pub fn new(pin: Option<[u8; 32]>) -> Arc<Pinned> {
17436×
        Arc::new(Pinned { pin, seen: Mutex::new(None), algs: provider().signature_verification_algorithms })
17536×
    }
176
177
    /// The certificate (DER) and its pin from the last handshake.
17812×
    pub fn seen(&self) -> Option<(Vec<u8>, [u8; 32])> {
17912×
        self.seen.lock().unwrap().clone()
18012×
    }
181
18236×
    pub fn client_config(self: &Arc<Self>) -> Result<Arc<rustls::ClientConfig>> {
18336×
        let mut c = rustls::ClientConfig::builder_with_provider(provider())
18436×
            .with_safe_default_protocol_versions()?
18536×
            .dangerous()
18636×
            .with_custom_certificate_verifier(self.clone())
18736×
            .with_no_client_auth();
18836×
        c.alpn_protocols = vec![b"http/1.1".to_vec()];
18936×
        Ok(Arc::new(c))
19036×
    }
191
}
192
193
impl ServerCertVerifier for Pinned {
19434×
    fn verify_server_cert(
19534×
        &self,
19634×
        end_entity: &CertificateDer<'_>,
19734×
        _intermediates: &[CertificateDer<'_>],
19834×
        _server_name: &ServerName<'_>,
19934×
        _ocsp: &[u8],
20034×
        _now: UnixTime,
20134×
    ) -> Result<ServerCertVerified, rustls::Error> {
20234×
        let spki = spki_sha256(end_entity).map_err(|_| rustls::Error::InvalidCertificate(rustls::CertificateError::BadEncoding))?;
20334×
        *self.seen.lock().unwrap() = Some((end_entity.to_vec(), spki));
20421×
        match self.pin {
20521×
            Some(pin) if pin != spki => Err(rustls::Error::InvalidCertificate(rustls::CertificateError::ApplicationVerificationFailure)),
20632×
            _ => Ok(ServerCertVerified::assertion()),
207
        }
20834×
    }
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
21932×
    fn verify_tls13_signature(
22032×
        &self,
22132×
        message: &[u8],
22232×
        cert: &CertificateDer<'_>,
22332×
        dss: &DigitallySignedStruct,
22432×
    ) -> Result<HandshakeSignatureValid, rustls::Error> {
22532×
        rustls::crypto::verify_tls13_signature(message, cert, dss, &self.algs)
22632×
    }
227
22860×
    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
22960×
        self.algs.supported_schemes()
23060×
    }
231
}
232
233
/// Connect to `host:port` over TLS with `config`.
23460×
pub async fn connect(addr: &str, config: Arc<rustls::ClientConfig>) -> Result<tokio_rustls::client::TlsStream<TcpStream>> {
23560×
    let tcp = tokio::time::timeout(Duration::from_secs(10), TcpStream::connect(addr))
23660×
        .await
23760×
        .with_context(|| format!("connecting to {addr}: timed out"))?
23860×
        .with_context(|| format!("connecting to {addr}"))?;
23960×
    tcp.set_nodelay(true).ok();
24060×
    let host = addr.rsplit_once(':').map(|(h, _)| h.trim_start_matches('[').trim_end_matches(']')).unwrap_or(addr);
24160×
    let name = ServerName::try_from(host.to_string()).unwrap_or_else(|_| ServerName::try_from("spectra").unwrap());
24260×
    let tls = tokio_rustls::TlsConnector::from(config).connect(name, tcp).await.with_context(|| format!("TLS with {addr}"))?;
24358×
    Ok(tls)
24460×
}
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
}