Properly extract cert names

This commit is contained in:
Dmitry Ivanov
2023-04-03 22:22:48 +03:00
parent cee9c726d2
commit a271ca6c8c
5 changed files with 84 additions and 11 deletions

14
Cargo.lock generated
View File

@@ -1659,6 +1659,19 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globset"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
dependencies = [
"aho-corasick",
"bstr",
"fnv",
"log",
"regex",
]
[[package]]
name = "h2"
version = "0.3.15"
@@ -3129,6 +3142,7 @@ dependencies = [
"consumption_metrics",
"futures",
"git-version",
"globset",
"hashbrown 0.13.2",
"hashlink",
"hex",

View File

@@ -47,6 +47,7 @@ futures = "0.3"
futures-core = "0.3"
futures-util = "0.3"
git-version = "0.3"
globset = "0.4.10"
hashbrown = "0.13"
hashlink = "0.8.1"
hex = "0.4"

View File

@@ -16,6 +16,7 @@ clap.workspace = true
consumption_metrics.workspace = true
futures.workspace = true
git-version.workspace = true
globset.workspace = true
hashbrown.workspace = true
hashlink.workspace = true
hex.workspace = true

View File

@@ -4,15 +4,16 @@ use rustls::{
};
use std::{io, sync::Arc};
/// App-level configuration structs for TLS certificates.
pub mod config {
use super::*;
use serde::Deserialize;
use serde::{de, Deserialize};
use std::path::Path;
/// Collection of TLS-related configurations of virtual proxy servers.
#[derive(Debug, Default, Clone, Deserialize)]
#[serde(transparent)]
pub struct TlsServers(Vec<TlsServer>);
pub struct TlsServers(pub Vec<TlsServer>);
impl TlsServers {
/// Load [`Self`] config from a file.
@@ -29,6 +30,7 @@ pub mod config {
}
}
/// Helps deserialize certificate chain from a string.
#[derive(Debug, Clone, Deserialize)]
#[serde(transparent)]
pub struct TlsCert(
@@ -42,9 +44,10 @@ pub mod config {
D: serde::Deserializer<'de>,
{
let text = String::deserialize(des)?;
parse_certs(&mut text.as_bytes()).map_err(serde::de::Error::custom)
parse_certs(&mut text.as_bytes()).map_err(de::Error::custom)
}
/// Helps deserialize private key from a string.
#[derive(Debug, Clone, Deserialize)]
#[serde(transparent)]
pub struct TlsKey(
@@ -58,17 +61,20 @@ pub mod config {
D: serde::Deserializer<'de>,
{
let text = String::deserialize(des)?;
parse_key(&mut text.as_bytes()).map_err(serde::de::Error::custom)
parse_key(&mut text.as_bytes()).map_err(de::Error::custom)
}
/// TODO: explain.
/// Represents TLS config of a single virtual proxy server.
#[derive(Debug, Clone, Deserialize)]
pub struct TlsServer {
/// Proxy server's certificate chain.
pub certificate: TlsCert,
/// Proxy server's private key.
pub private_key: TlsKey,
}
}
/// Parse TLS certificate chain from a byte buffer.
fn parse_certs(buf: &mut impl io::BufRead) -> io::Result<Vec<rustls::Certificate>> {
let chain = rustls_pemfile::certs(buf)?
.into_iter()
@@ -78,6 +84,7 @@ fn parse_certs(buf: &mut impl io::BufRead) -> io::Result<Vec<rustls::Certificate
Ok(chain)
}
/// Parse exactly one TLS private key from a byte buffer.
fn parse_key(buf: &mut impl io::BufRead) -> io::Result<rustls::PrivateKey> {
let mut keys = rustls_pemfile::pkcs8_private_keys(buf)?;
@@ -92,18 +99,68 @@ fn parse_key(buf: &mut impl io::BufRead) -> io::Result<rustls::PrivateKey> {
Ok(rustls::PrivateKey(keys.pop().unwrap()))
}
/// Extract domain names from a certificate: first CN, then SANs.
/// Further reading: https://www.rfc-editor.org/rfc/rfc4985
fn certificate_names(cert: &rustls::Certificate) -> anyhow::Result<Vec<String>> {
use x509_parser::{extensions::GeneralName, x509::AttributeTypeAndValue};
let get_dns_name = |gn: &GeneralName| match gn {
GeneralName::DNSName(name) => Some(name.to_string()),
_other => None,
};
let get_common_name = |attr: &AttributeTypeAndValue| {
// There really shouldn't be anything but string here.
attr.attr_value().as_string().expect("bad CN attribute")
};
let (rest, cert) = x509_parser::parse_x509_certificate(cert.0.as_ref())?;
anyhow::ensure!(rest.is_empty(), "excessive bytes in DER certificate");
// Extract CN, Common Name.
let mut names: Vec<String> = cert
.subject()
.iter_common_name()
.map(get_common_name)
.collect();
// Now append SANs, Subject Alternative Names, if any.
if let Some(extension) = cert.subject_alternative_name()? {
let alt_names = &extension.value.general_names;
names.extend(alt_names.iter().filter_map(get_dns_name));
}
Ok(names)
}
struct CertResolverEntry {
raw: Arc<rustls::sign::CertifiedKey>,
names: Vec<String>,
}
pub struct CertResolver {
resolver: rustls::server::ResolvesServerCertUsingSni,
certs: Vec<CertResolverEntry>,
}
impl CertResolver {
pub fn new() -> Self {
todo!("CertResolver ctor")
pub fn new(config: config::TlsServers) -> anyhow::Result<Self> {
let mut builder = globset::GlobSetBuilder::new();
for server in config.0 {
let Some(cert) = server.certificate.0.first() else {
tracing::warn!("found empty certificate, skipping");
continue;
};
let names = certificate_names(cert)?;
dbg!(names);
}
todo!()
}
}
impl ResolvesServerCert for CertResolver {
fn resolve(&self, message: ClientHello) -> Option<Arc<CertifiedKey>> {
self.resolver.resolve(message)
todo!("implement")
}
}

View File

@@ -133,7 +133,7 @@ fn build_tls_config(args: &clap::ArgMatches) -> anyhow::Result<Option<TlsConfig>
let tls_config = args.get_one::<PathBuf>("tls-config");
let main = tls_config.map(TlsServers::from_config_file).transpose()?;
tracing::info!(?main, "config");
certs::CertResolver::new(main.unwrap());
let tls_cert = args.get_one::<PathBuf>("tls-cert");
let tls_key = args.get_one::<PathBuf>("tls-key");
@@ -144,7 +144,7 @@ fn build_tls_config(args: &clap::ArgMatches) -> anyhow::Result<Option<TlsConfig>
_ => bail!("either both or neither tls-key and tls-cert must be specified"),
};
todo!()
todo!("TlsConfig")
}
fn build_metrics_config(args: &clap::ArgMatches) -> anyhow::Result<Option<MetricCollectionConfig>> {