use rustls_pki_types::CertificateDer;
use x509_parser::prelude::*;
#[derive(Clone, Debug)]
pub struct PeerCertificates {
chain: Vec<CertificateDer<'static>>,
}
impl PeerCertificates {
pub fn new(chain: Vec<CertificateDer<'static>>) -> Self {
Self { chain }
}
pub fn empty() -> Self {
Self { chain: Vec::new() }
}
pub fn is_present(&self) -> bool {
!self.chain.is_empty()
}
pub fn is_empty(&self) -> bool {
self.chain.is_empty()
}
pub fn chain(&self) -> &[CertificateDer<'static>] {
&self.chain
}
pub fn leaf(&self) -> Option<&CertificateDer<'static>> {
self.chain.first()
}
pub fn leaf_cn(&self) -> Option<String> {
let leaf = self.leaf()?;
let (_, cert) = X509Certificate::from_der(leaf.as_ref()).ok()?;
let cn = cert
.subject()
.iter_common_name()
.next()
.and_then(|cn| cn.as_str().ok().map(String::from));
cn
}
pub fn leaf_sans(&self) -> Vec<String> {
let Some(leaf) = self.leaf() else {
return Vec::new();
};
let Ok((_, cert)) = X509Certificate::from_der(leaf.as_ref()) else {
return Vec::new();
};
let san_ext = match cert.subject_alternative_name() {
Ok(Some(ext)) => ext,
_ => return Vec::new(),
};
san_ext
.value
.general_names
.iter()
.filter_map(|name| match name {
GeneralName::DNSName(dns) => Some(dns.to_string()),
GeneralName::RFC822Name(email) => Some(email.to_string()),
GeneralName::IPAddress(ip) => {
match ip.len() {
4 => Some(format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3])),
16 => {
let addr: std::net::Ipv6Addr = {
let mut octets = [0u8; 16];
octets.copy_from_slice(ip);
octets.into()
};
Some(addr.to_string())
}
_ => None,
}
}
_ => None,
})
.collect()
}
pub fn leaf_serial_hex(&self) -> Option<String> {
let leaf = self.leaf()?;
let (_, cert) = X509Certificate::from_der(leaf.as_ref()).ok()?;
Some(cert.serial.to_str_radix(16))
}
pub fn leaf_not_after_unix(&self) -> Option<i64> {
let leaf = self.leaf()?;
let (_, cert) = X509Certificate::from_der(leaf.as_ref()).ok()?;
Some(cert.validity().not_after.timestamp())
}
}
impl Default for PeerCertificates {
fn default() -> Self {
Self::empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_certs() {
let certs = PeerCertificates::empty();
assert!(certs.is_empty());
assert!(!certs.is_present());
assert!(certs.leaf().is_none());
assert!(certs.leaf_cn().is_none());
assert!(certs.leaf_sans().is_empty());
assert!(certs.leaf_serial_hex().is_none());
assert!(certs.leaf_not_after_unix().is_none());
}
}