Skip to main content

netget/client/gemini/
tls.rs

1use super::wire::Request;
2use crate::protocol::ConnectContext;
3use anyhow::{ensure, Context, Result};
4use rustls::{pki_types::ServerName, ClientConfig, RootCertStore};
5use std::sync::Arc;
6#[derive(Clone)]
7pub struct Config {
8    pub tls: Arc<ClientConfig>,
9    pub name: ServerName<'static>,
10    pub host: String,
11    pub port: u16,
12}
13impl Config {
14    pub fn from_context(ctx: &ConnectContext) -> Result<Self> {
15        let optional = |name| {
16            ctx.startup_params
17                .as_ref()
18                .map(|p| p.get_optional_string(name))
19                .transpose()
20                .map(Option::flatten)
21        };
22        let remote = url::Url::parse(&format!("gemini://{}", ctx.remote_addr))
23            .context("Invalid remote address")?;
24        ensure!(
25            remote.username().is_empty()
26                && remote.password().is_none()
27                && remote.path().is_empty()
28                && remote.query().is_none()
29                && remote.fragment().is_none(),
30            "Remote address must contain only host:port"
31        );
32        let host = optional("server_name")?
33            .unwrap_or_else(|| remote.host().map(|h| h.to_string()).unwrap_or_default());
34        let host = host
35            .trim_start_matches('[')
36            .trim_end_matches(']')
37            .to_string();
38        let name = ServerName::try_from(host.clone()).context("Invalid TLS server name")?;
39        let mut roots = RootCertStore::empty();
40        if let Some(pem) = optional("custom_ca_cert_pem")? {
41            ensure!(pem.len() <= 256 * 1024, "CA PEM exceeds256KiB");
42            let certs = rustls_pemfile::certs(&mut pem.as_bytes())
43                .collect::<std::result::Result<Vec<_>, _>>()?;
44            ensure!(
45                !certs.is_empty() && certs.len() <= 32,
46                "Expected1..32 CA certificates"
47            );
48            for cert in certs {
49                roots.add(cert)?;
50            }
51        } else {
52            roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
53        }
54        let tls =
55            ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
56                .with_safe_default_protocol_versions()?
57                .with_root_certificates(roots)
58                .with_no_client_auth();
59        Ok(Self {
60            tls: Arc::new(tls),
61            name,
62            host,
63            port: remote.port().unwrap_or(1965),
64        })
65    }
66    pub fn validate(&self, r: &Request) -> Result<()> {
67        let host = r.url.host().context("Missing URL host")?.to_string();
68        ensure!(host.trim_start_matches('[').trim_end_matches(']').eq_ignore_ascii_case(&self.host)&&r.url.port().unwrap_or(1965)==self.port,"Request URL must match the configured server_name and remote port; open another client for another endpoint");
69        Ok(())
70    }
71}