use crate::signature::{self, ImageHash, SignatureInfo};
use anyhow::Result;
use authenticode::{AttributeCertificateIterator, AuthenticodeSignature, PeTrait};
use object::read::pe::{PeFile32, PeFile64};
use sha1::Sha1;
use sha2::digest::Update;
use sha2::{Digest, Sha256, Sha384, Sha512};
fn image_hash(pe: &dyn PeTrait, sig: &AuthenticodeSignature) -> ImageHash {
let expected = sig.digest();
let computed: Option<Vec<u8>> = match expected.len() {
20 => hash_pe::<Sha1>(pe),
32 => hash_pe::<Sha256>(pe),
48 => hash_pe::<Sha384>(pe),
64 => hash_pe::<Sha512>(pe),
_ => return ImageHash::Unverified,
};
match computed {
Some(bytes) if bytes.as_slice() == expected => ImageHash::Match,
Some(_) => ImageHash::Mismatch,
None => ImageHash::Unverified, }
}
fn hash_pe<D: Digest + Update>(pe: &dyn PeTrait) -> Option<Vec<u8>> {
let mut hasher = D::new();
authenticode::authenticode_digest(pe, &mut hasher).ok()?;
Some(hasher.finalize().to_vec())
}
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:?}"))?;
let mut info = signature::from_authenticode(&sig);
info.image_hash = image_hash(pe.as_ref(), &sig);
Ok(Some(info))
}
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());
}
#[test]
fn signed_fixture_image_hash_matches() {
let data = std::fs::read("fixtures/selfsigned-sha256.exe").unwrap();
let info = extract(&data).unwrap().expect("should be signed");
assert_eq!(info.image_hash, ImageHash::Match);
}
#[test]
fn tampered_fixture_image_hash_mismatches() {
let mut data = std::fs::read("fixtures/selfsigned-sha256.exe").unwrap();
data[2] ^= 0xff;
let info = extract(&data).unwrap().expect("still parses as signed");
assert_eq!(info.image_hash, ImageHash::Mismatch);
}
}