153 lines
4.4 KiB
Rust
153 lines
4.4 KiB
Rust
use anyhow::Context;
|
|
use ring::digest::{digest, SHA256};
|
|
use rustls::server::{ClientHello, ResolvesServerCert};
|
|
use rustls::sign::CertifiedKey;
|
|
use rustls::{Certificate, PrivateKey, RootCertStore};
|
|
use rustls::{ClientConfig, ServerConfig};
|
|
use std::fs;
|
|
use std::io::{self, Cursor, Read};
|
|
use std::path;
|
|
use std::sync::Arc;
|
|
use webpki::{DnsNameRef, EndEntityCert};
|
|
|
|
use crate::Config;
|
|
|
|
#[derive(Clone)]
|
|
pub struct Tls {
|
|
// Support serving multiple certificates, choosing one that looks valid for the given SNI.
|
|
// We store the parsed certificate, and the certified cert/key that rustls expects
|
|
serve: Arc<ServeCerts>,
|
|
|
|
// Accept any cert that is trusted by the system's native trust store.
|
|
accept: Arc<RootCertStore>,
|
|
}
|
|
|
|
impl Tls {
|
|
pub fn load(config: &Config) -> anyhow::Result<Self> {
|
|
let mut serve = ServeCerts::default();
|
|
|
|
// Load the certificate and key files based on their index.
|
|
anyhow::ensure!(config.cert.len() == config.key.len(), "--cert and --key mismatch");
|
|
for (chain, key) in config.cert.iter().zip(config.key.iter()) {
|
|
serve.load(chain, key)?;
|
|
}
|
|
|
|
// Create a list of acceptable root certificates.
|
|
let mut roots = RootCertStore::empty();
|
|
|
|
// Add the platform's native root certificates.
|
|
for cert in rustls_native_certs::load_native_certs().context("could not load platform certs")? {
|
|
roots.add(&Certificate(cert.0)).context("failed to add root cert")?;
|
|
}
|
|
|
|
let certs = Self {
|
|
serve: Arc::new(serve),
|
|
accept: Arc::new(roots),
|
|
};
|
|
|
|
Ok(certs)
|
|
}
|
|
|
|
pub fn client(&self) -> ClientConfig {
|
|
rustls::ClientConfig::builder()
|
|
.with_safe_defaults()
|
|
.with_root_certificates(self.accept.clone())
|
|
.with_no_client_auth()
|
|
}
|
|
|
|
pub fn server(&self) -> ServerConfig {
|
|
rustls::ServerConfig::builder()
|
|
.with_safe_defaults()
|
|
.with_no_client_auth()
|
|
.with_cert_resolver(self.serve.clone())
|
|
}
|
|
|
|
// Return the SHA256 fingerprint of our certificates.
|
|
pub fn fingerprints(&self) -> Vec<String> {
|
|
self.serve.fingerprints()
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct ServeCerts {
|
|
list: Vec<Arc<CertifiedKey>>,
|
|
}
|
|
|
|
impl ServeCerts {
|
|
// Load a certificate and cooresponding key from a file
|
|
pub fn load(&mut self, chain: &path::PathBuf, key: &path::PathBuf) -> anyhow::Result<()> {
|
|
// Read the PEM certificate chain
|
|
let chain = fs::File::open(chain).context("failed to open cert file")?;
|
|
let mut chain = io::BufReader::new(chain);
|
|
|
|
let chain: Vec<Certificate> = rustls_pemfile::certs(&mut chain)?
|
|
.into_iter()
|
|
.map(Certificate)
|
|
.collect();
|
|
|
|
anyhow::ensure!(!chain.is_empty(), "could not find certificate");
|
|
|
|
// Read the PEM private key
|
|
let mut keys = fs::File::open(key).context("failed to open key file")?;
|
|
|
|
// Read the keys into a Vec so we can parse it twice.
|
|
let mut buf = Vec::new();
|
|
keys.read_to_end(&mut buf)?;
|
|
|
|
// Try to parse a PKCS#8 key
|
|
// -----BEGIN PRIVATE KEY-----
|
|
let mut keys = rustls_pemfile::pkcs8_private_keys(&mut Cursor::new(&buf))?;
|
|
|
|
// Try again but with EC keys this time
|
|
// -----BEGIN EC PRIVATE KEY-----
|
|
if keys.is_empty() {
|
|
keys = rustls_pemfile::ec_private_keys(&mut Cursor::new(&buf))?
|
|
};
|
|
|
|
anyhow::ensure!(!keys.is_empty(), "could not find private key");
|
|
anyhow::ensure!(keys.len() < 2, "expected a single key");
|
|
|
|
let key = PrivateKey(keys.remove(0));
|
|
let key = rustls::sign::any_supported_type(&key)?;
|
|
|
|
let certified = Arc::new(CertifiedKey::new(chain, key));
|
|
self.list.push(certified);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Return the SHA256 fingerprint of our certificates.
|
|
pub fn fingerprints(&self) -> Vec<String> {
|
|
self.list
|
|
.iter()
|
|
.map(|ck| {
|
|
let fingerprint = digest(&SHA256, ck.cert[0].as_ref());
|
|
let fingerprint = hex::encode(fingerprint.as_ref());
|
|
fingerprint
|
|
})
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
impl ResolvesServerCert for ServeCerts {
|
|
fn resolve(&self, client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
|
|
if let Some(name) = client_hello.server_name() {
|
|
if let Ok(dns_name) = DnsNameRef::try_from_ascii_str(name) {
|
|
for ck in &self.list {
|
|
// TODO I gave up on caching the parsed result because of lifetime hell.
|
|
// If this shows up on benchmarks, somebody should fix it.
|
|
let leaf = ck.cert.first().expect("missing certificate");
|
|
let parsed = EndEntityCert::try_from(leaf.0.as_ref()).expect("failed to parse certificate");
|
|
|
|
if parsed.verify_is_valid_for_dns_name(dns_name).is_ok() {
|
|
return Some(ck.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Default to the last certificate if we couldn't find one.
|
|
self.list.last().cloned()
|
|
}
|
|
}
|