netget/client/gemini/
tls.rs1use 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}