use crate::signature::{self, SignatureInfo};
use anyhow::Result;
use authenticode::{AttributeCertificateIterator, PeTrait};
use object::read::pe::{PeFile32, PeFile64};
pub fn extract(data: &[u8]) -> Result<Option<SignatureInfo>> {
let pe: Box<dyn PeTrait + '_> = match PeFile64::parse(data) {
Ok(pe) => Box::new(pe),
Err(e) => match PeFile32::parse(data) {
Ok(pe) => Box::new(pe),
Err(_) => return Err(anyhow::anyhow!("not a PE: {e}")),
},
};
let Some(mut iter) = AttributeCertificateIterator::new(pe.as_ref())
.map_err(|e| anyhow::anyhow!("cert table: {e:?}"))?
else {
return Ok(None);
};
match iter.next() {
Some(cert) => {
let cert = cert.map_err(|e| anyhow::anyhow!("attribute certificate: {e:?}"))?;
let sig = cert
.get_authenticode_signature()
.map_err(|e| anyhow::anyhow!("authenticode signature: {e:?}"))?;
Ok(Some(signature::from_authenticode(&sig)))
}
None => Ok(None),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unsigned_has_no_signature() {
let data = std::fs::read("fixtures/unsigned.exe").unwrap();
assert!(extract(&data).unwrap().is_none());
}
#[test]
fn pe32_is_parsed_not_rejected() {
let data = std::fs::read("fixtures/unsigned-pe32.exe").unwrap();
assert!(extract(&data).unwrap().is_none());
}
#[test]
fn garbage_is_still_rejected() {
assert!(extract(b"not an executable at all").is_err());
}
#[test]
fn signed_fixture_parses() {
let data = std::fs::read("fixtures/selfsigned-sha256.exe").unwrap();
let info = extract(&data).unwrap().expect("should be signed");
assert_eq!(info.digest_algo_oid, "2.16.840.1.101.3.4.2.1"); assert!(!info.has_timestamp); assert!(info.leaf_cert.is_some());
}
}