use anyhow::Result;
use x509_cert::Certificate;
#[derive(Debug, Clone, PartialEq)]
pub struct CertInfo {
pub subject_o: Option<String>,
pub issuer: String,
pub not_after_unix: i64,
pub self_signed: bool,
pub ev_hint: bool,
}
const OID_CERT_POLICIES: &str = "2.5.29.32";
const EV_OID_BODY: &[u8] = &[0x67, 0x81, 0x0c, 0x01, 0x03];
pub fn from_certificate(cert: &Certificate) -> Result<CertInfo> {
let tbs = &cert.tbs_certificate;
let subject = tbs.subject.to_string();
let issuer = tbs.issuer.to_string();
let subject_o = extract_rdn(&subject, "O");
let self_signed = tbs.subject == tbs.issuer;
let not_after_unix = tbs.validity.not_after.to_unix_duration().as_secs() as i64;
let ev_hint = tbs.extensions.as_ref().is_some_and(|exts| {
exts.iter().any(|e| {
e.extn_id.to_string() == OID_CERT_POLICIES
&& contains_subslice(e.extn_value.as_bytes(), EV_OID_BODY)
})
});
Ok(CertInfo {
subject_o,
issuer,
not_after_unix,
self_signed,
ev_hint,
})
}
pub(crate) fn extract_rdn(dn: &str, key: &str) -> Option<String> {
let prefix = format!("{key}=");
let mut fields = Vec::new();
let mut current = String::new();
let mut chars = dn.chars();
while let Some(c) = chars.next() {
match c {
'\\' => {
if let Some(next) = chars.next() {
current.push(next);
}
}
',' => fields.push(std::mem::take(&mut current)),
_ => current.push(c),
}
}
fields.push(current);
fields
.into_iter()
.find_map(|f| f.trim().strip_prefix(&prefix).map(|v| v.to_string()))
.filter(|v| !v.is_empty())
}
fn contains_subslice(haystack: &[u8], needle: &[u8]) -> bool {
needle.len() <= haystack.len() && haystack.windows(needle.len()).any(|w| w == needle)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn escaped_commas_in_a_dn_do_not_split_the_field() {
let dn = r"C=US,O=DigiCert\, Inc.,CN=DigiCert Trusted G4 CA";
assert_eq!(extract_rdn(dn, "O").as_deref(), Some("DigiCert, Inc."));
assert_eq!(extract_rdn(dn, "C").as_deref(), Some("US"));
}
#[test]
fn extract_rdn_works() {
assert_eq!(
extract_rdn("O=Acme Corp,CN=Foo", "O"),
Some("Acme Corp".to_string())
);
assert_eq!(extract_rdn("CN=Foo,C=US", "O"), None);
}
#[test]
fn parses_selfsigned_leaf() {
let data = std::fs::read("fixtures/selfsigned-sha256.exe").unwrap();
let sig = crate::pe::extract(&data).unwrap().unwrap();
let leaf = sig.leaf_cert.unwrap();
let info = from_certificate(&leaf).unwrap();
assert!(info.self_signed);
assert_eq!(info.subject_o.as_deref(), Some("SignalScreen Test"));
assert!(!info.ev_hint);
}
}