use {
rustls::{
CertificateError, ClientConfig, DigitallySignedStruct, Error,
RootCertStore, SignatureScheme,
client::danger::{
HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier,
},
crypto::{CryptoProvider, WebPkiSupportedAlgorithms, ring},
pki_types::{CertificateDer, ServerName, UnixTime},
},
std::{sync::Arc, time::SystemTime},
x509_cert::der::Decode,
};
pub trait CertificateStore: Send {
fn load(
&mut self,
hostname: &str,
port: u16,
) -> anyhow::Result<Option<Vec<u8>>>;
fn save(
&mut self,
hostname: &str,
port: u16,
certificate: &[u8],
) -> anyhow::Result<()>;
}
#[derive(Debug)]
struct TrustOnFirstUseVerifier {
signature_algorithms: WebPkiSupportedAlgorithms,
}
impl TrustOnFirstUseVerifier {
fn new() -> Self {
Self {
signature_algorithms: crypto_provider().signature_verification_algorithms,
}
}
}
impl ServerCertVerifier for TrustOnFirstUseVerifier {
fn verify_server_cert(
&self,
end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, Error> {
x509_cert::Certificate::from_der(end_entity)
.map_err(|_| Error::InvalidCertificate(CertificateError::BadEncoding))?;
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
message: &[u8],
certificate: &CertificateDer<'_>,
signature: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, Error> {
rustls::crypto::verify_tls12_signature(
message,
certificate,
signature,
&self.signature_algorithms,
)
}
fn verify_tls13_signature(
&self,
message: &[u8],
certificate: &CertificateDer<'_>,
signature: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, Error> {
rustls::crypto::verify_tls13_signature(
message,
certificate,
signature,
&self.signature_algorithms,
)
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
self.signature_algorithms.supported_schemes()
}
}
fn crypto_provider() -> CryptoProvider { ring::default_provider() }
fn client_config_builder()
-> Result<rustls::ConfigBuilder<ClientConfig, rustls::WantsVerifier>, Error> {
ClientConfig::builder_with_provider(Arc::new(crypto_provider()))
.with_safe_default_protocol_versions()
}
pub fn tofu_client_config() -> Result<ClientConfig, Error> {
Ok(
client_config_builder()?
.dangerous()
.with_custom_certificate_verifier(
Arc::new(TrustOnFirstUseVerifier::new()),
)
.with_no_client_auth(),
)
}
pub fn check_tofu(
store: &mut dyn CertificateStore,
hostname: &str,
port: u16,
certificate: Option<&CertificateDer<'_>>,
) -> anyhow::Result<()> {
let certificate = certificate.ok_or_else(|| {
anyhow::anyhow!("Gemini server did not provide a certificate")
})?;
match store.load(hostname, port)? {
Some(previous) if previous == certificate.as_ref() => Ok(()),
Some(previous) if previous_certificate_expired(&previous) =>
store.save(hostname, port, certificate),
Some(_) =>
anyhow::bail!("Gemini server certificate changed for {hostname}:{port}"),
None => store.save(hostname, port, certificate),
}
}
fn previous_certificate_expired(certificate: &[u8]) -> bool {
let Ok(certificate) = x509_cert::Certificate::from_der(certificate) else {
return false;
};
let expires_at = SystemTime::UNIX_EPOCH
+ certificate.tbs_certificate().validity().not_after.to_unix_duration();
expires_at < SystemTime::now()
}
#[must_use]
pub fn default_root_certificates() -> RootCertStore {
RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.to_vec() }
}
pub fn client_config(roots: RootCertStore) -> Result<ClientConfig, Error> {
Ok(
client_config_builder()?
.with_root_certificates(roots)
.with_no_client_auth(),
)
}
#[cfg(test)]
mod tests {
use {
super::{CertificateStore, TrustOnFirstUseVerifier, check_tofu},
rustls::{
CertificateError, Error,
client::danger::ServerCertVerifier,
pki_types::{CertificateDer, ServerName, UnixTime},
},
std::collections::HashMap,
};
#[derive(Default)]
struct MemoryCertificates(HashMap<(String, u16), Vec<u8>>);
impl CertificateStore for MemoryCertificates {
fn load(
&mut self,
hostname: &str,
port: u16,
) -> anyhow::Result<Option<Vec<u8>>> {
Ok(self.0.get(&(hostname.to_owned(), port)).cloned())
}
fn save(
&mut self,
hostname: &str,
port: u16,
certificate: &[u8],
) -> anyhow::Result<()> {
self.0.insert((hostname.to_owned(), port), certificate.to_vec());
Ok(())
}
}
fn verify_certificate(
parameters: &rcgen::CertificateParams,
) -> Result<(), Error> {
let key_pair = rcgen::KeyPair::generate().unwrap();
let certificate = parameters.self_signed(&key_pair).unwrap();
TrustOnFirstUseVerifier::new().verify_server_cert(
certificate.der(),
&[],
&ServerName::try_from("localhost").unwrap(),
&[],
UnixTime::now(),
)?;
Ok(())
}
#[test]
fn accepts_certificate_with_unrelated_common_name() {
let mut parameters = rcgen::CertificateParams::default();
parameters
.distinguished_name
.push(rcgen::DnType::CommonName, "elsewhere.test");
verify_certificate(¶meters).unwrap();
}
#[test]
fn rejects_malformed_certificate() {
let error = TrustOnFirstUseVerifier::new()
.verify_server_cert(
&CertificateDer::from(vec![1, 2, 3]),
&[],
&ServerName::try_from("localhost").unwrap(),
&[],
UnixTime::now(),
)
.unwrap_err();
assert_eq!(error, Error::InvalidCertificate(CertificateError::BadEncoding));
}
#[test]
fn pins_are_scoped_to_hostname_and_port() {
let certificate = CertificateDer::from(vec![1, 2, 3]);
let other_certificate = CertificateDer::from(vec![4, 5, 6]);
let mut store = MemoryCertificates::default();
check_tofu(&mut store, "one.test", 1965, Some(&certificate)).unwrap();
check_tofu(&mut store, "one.test", 1965, Some(&certificate)).unwrap();
check_tofu(&mut store, "one.test", 1966, Some(&other_certificate)).unwrap();
check_tofu(&mut store, "two.test", 1965, Some(&other_certificate)).unwrap();
assert_eq!(store.0.len(), 3);
assert!(
check_tofu(&mut store, "one.test", 1965, Some(&other_certificate))
.is_err()
);
}
#[test]
fn replaces_an_expired_certificate() {
let mut parameters = rcgen::CertificateParams::default();
parameters.distinguished_name.push(rcgen::DnType::CommonName, "localhost");
parameters.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
parameters.not_before = rcgen::date_time_ymd(2018, 1, 1);
parameters.not_after = rcgen::date_time_ymd(2019, 1, 1);
let key_pair = rcgen::KeyPair::generate().unwrap();
let expired_certificate =
parameters.self_signed(&key_pair).unwrap().der().to_vec();
let replacement = CertificateDer::from(vec![7, 8, 9]);
let mut store = MemoryCertificates::default();
store.0.insert(("localhost".to_owned(), 1965), expired_certificate);
check_tofu(&mut store, "localhost", 1965, Some(&replacement)).unwrap();
assert_eq!(
store.0.get(&("localhost".to_owned(), 1965)),
Some(&replacement.to_vec())
);
}
}