use crate::algorithm::Algorithm;
use crate::error::OptionExt;
use crate::pe_file::PeFile;
use crate::spc_indirect_data::{SPC_INDIRECT_DATA_OBJID, SpcIndirectDataContent};
use crate::win_certificate::WinCertificate;
use crate::{authenticode_certificate::AuthenticodeCertificate, error::AuthenticodeError};
use cms::cert::x509::spki::ObjectIdentifier;
use cms::{
cert::{
CertificateChoices,
x509::der::{Decode, SliceReader},
},
content_info::ContentInfo,
signed_data::SignedData,
};
use object::read::pe::{PeFile32, PeFile64};
use sha1::{Digest, Sha1};
use sha2::Sha256;
#[derive(Debug)]
pub struct DigestInfo {
pub algorithm: Algorithm,
pub hash: Vec<u8>,
}
pub struct AuthenticodeInfo<'a> {
pub certificates: Vec<AuthenticodeCertificate>,
pub digest: DigestInfo,
pe: Box<dyn PeFile + 'a>,
}
impl AuthenticodeInfo<'_> {
fn create(data: &[u8]) -> Result<AuthenticodeInfo<'_>, AuthenticodeError> {
let pe: Box<dyn PeFile> = match PeFile64::parse(data) {
Ok(pe) => Box::new(pe),
Err(_) => Box::new(PeFile32::parse(data)?),
};
let content_info = Self::content_info(&pe.win_certificate()?)?;
let signed_data = Self::signed_data(&content_info)?;
let authenticode_certificates = Self::certificates(&signed_data)?;
let digest_info = Self::digest_info(&content_info, &signed_data)?;
Ok(AuthenticodeInfo {
certificates: authenticode_certificates,
digest: digest_info,
pe,
})
}
pub fn verify(&self) -> Result<bool, AuthenticodeError> {
match &self.digest.algorithm {
Algorithm::Sha1 => Ok(self.authenticode_sha1()? == self.digest.hash),
Algorithm::Sha256 => Ok(self.authenticode_sha256()? == self.digest.hash),
alg => Err(AuthenticodeError::InvalidHashAlgorithmWithName(
alg.to_string(),
)),
}
}
pub fn authenticode_sha1(&self) -> Result<Vec<u8>, AuthenticodeError> {
self.authenticode_hash::<Sha1>()
}
pub fn authenticode_sha256(&self) -> Result<Vec<u8>, AuthenticodeError> {
self.authenticode_hash::<Sha256>()
}
pub fn authenticode_hash<D: Digest>(&self) -> Result<Vec<u8>, AuthenticodeError> {
let mut digest = D::new();
let offsets = self.pe.offsets()?;
let bytes = self.pe.data().get(..offsets.checksum).err_slice()?;
digest.update(bytes);
let bytes = self
.pe
.data()
.get(offsets.after_checksum..offsets.security_dir)
.err_slice()?;
digest.update(bytes);
let bytes = self
.pe
.data()
.get(offsets.after_security_dir..offsets.after_header)
.err_slice()?;
digest.update(bytes);
let mut sum_of_bytes_hashed = offsets.after_header;
let mut sections = (1..=offsets.num_sections)
.map(|i| self.pe.section_data_range(i))
.collect::<Result<Vec<_>, AuthenticodeError>>()?;
sections.sort_unstable_by_key(|r| r.start);
for section_range in sections {
let bytes = self.pe.data().get(section_range).err_slice()?;
digest.update(bytes);
sum_of_bytes_hashed = sum_of_bytes_hashed.checked_add(bytes.len()).err_pe_oor()?;
}
let mut extra_hash_len = self
.pe
.data()
.len()
.checked_sub(sum_of_bytes_hashed)
.err_pe_oor()?;
if let Some(security_data_dir) = offsets.certificate_table_range {
let size = security_data_dir
.end
.checked_sub(security_data_dir.start)
.err_pe_oor()?;
extra_hash_len = extra_hash_len.checked_sub(size).err_pe_oor()?;
}
digest.update(
self.pe
.data()
.get(
sum_of_bytes_hashed
..sum_of_bytes_hashed
.checked_add(extra_hash_len)
.err_pe_oor()?,
)
.err_slice()?,
);
Ok(digest.finalize().to_vec())
}
fn digest_info(
content_info: &ContentInfo,
signed_data: &SignedData,
) -> Result<DigestInfo, AuthenticodeError> {
if content_info.content_type != ObjectIdentifier::new_unwrap("1.2.840.113549.1.7.2") {
return Err(AuthenticodeError::InvalidContentType(
content_info.content_type.to_string(),
));
}
if signed_data.encap_content_info.econtent_type != SPC_INDIRECT_DATA_OBJID {
return Err(AuthenticodeError::InvalidEncapsulatedContentType(
signed_data.encap_content_info.econtent_type.to_string(),
));
}
let indirect_data = signed_data
.clone()
.encap_content_info
.econtent
.ok_or(AuthenticodeError::NoEncapsulatedContent)?
.decode_as::<SpcIndirectDataContent>()?;
let hash = indirect_data.message_digest.digest.as_bytes();
Ok(DigestInfo {
algorithm: Algorithm::try_from(hash)?,
hash: hash.to_vec(),
})
}
fn signed_data(content_info: &ContentInfo) -> Result<SignedData, AuthenticodeError> {
let signed_data = content_info.content.decode_as::<SignedData>()?;
Ok(signed_data)
}
fn content_info(win_certificate: &WinCertificate) -> Result<ContentInfo, AuthenticodeError> {
let mut reader = SliceReader::new(win_certificate.certificate)?;
let content_info = ContentInfo::decode(&mut reader)?;
Ok(content_info)
}
fn certificates(
signed_data: &SignedData,
) -> Result<Vec<AuthenticodeCertificate>, AuthenticodeError> {
let authenticode_certificates = signed_data
.certificates
.as_ref()
.ok_or(AuthenticodeError::NoCertificates)?
.0
.iter()
.filter_map(|cert| match cert {
CertificateChoices::Certificate(cert) => Some(cert),
_ => None,
})
.map(|cert| AuthenticodeCertificate::try_from(cert.to_owned()))
.collect::<Result<Vec<_>, _>>()?;
Ok(authenticode_certificates)
}
}
impl<'a> TryFrom<&'a [u8]> for AuthenticodeInfo<'a> {
type Error = AuthenticodeError;
fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
Self::create(data)
}
}
impl<'a> TryFrom<&'a Vec<u8>> for AuthenticodeInfo<'a> {
type Error = AuthenticodeError;
fn try_from(data: &'a Vec<u8>) -> Result<Self, Self::Error> {
Self::create(data)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ToHex;
use std::path::PathBuf;
#[test]
fn sha1_thumbprints_signed_64() {
let pe_path = PathBuf::from("test-pe/test-signed-64.bin");
let pe_file = std::fs::read(pe_path).unwrap();
let ai = AuthenticodeInfo::try_from(&pe_file).unwrap();
assert_eq!(ai.certificates.len(), 2);
assert_eq!(
ai.certificates[0].sha1.to_hex(),
"f55115d2439ce0a7529ffaaea654be2c71dce955"
);
assert_eq!(
ai.certificates[1].sha1.to_hex(),
"580a6f4cc4e4b669b9ebdc1b2b3e087b80d0678d"
);
assert_eq!(ai.digest.algorithm, Algorithm::Sha256);
assert!(ai.verify().unwrap());
}
#[test]
fn sha256_thumbprints_signed_64() {
let pe_path = PathBuf::from("test-pe/test-signed-64.bin");
let pe_file = std::fs::read(pe_path).unwrap();
let ai = AuthenticodeInfo::try_from(&pe_file).unwrap();
assert_eq!(ai.certificates.len(), 2);
assert_eq!(
ai.certificates[0].sha256.to_hex(),
"9267a08c9fc07b6ab194dc4df3121b264e825330a39ffc42cdb0942f5115eb97"
);
assert_eq!(
ai.certificates[1].sha256.to_hex(),
"e8e95f0733a55e8bad7be0a1413ee23c51fcea64b3c8fa6a786935fddcc71961"
);
assert_eq!(ai.digest.algorithm, Algorithm::Sha256);
assert!(ai.verify().unwrap());
}
#[test]
fn sha1_thumbprints_signed_32() {
let pe_path = PathBuf::from("test-pe/test-signed-32.bin");
let pe_file = std::fs::read(pe_path).unwrap();
let ai = AuthenticodeInfo::try_from(&pe_file).unwrap();
assert_eq!(ai.certificates.len(), 2);
assert_eq!(
ai.certificates[0].sha1.to_hex(),
"aeb9b61e47d91c42fff213992b7810a3d562fb12"
);
assert_eq!(
ai.certificates[1].sha1.to_hex(),
"580a6f4cc4e4b669b9ebdc1b2b3e087b80d0678d"
);
assert_eq!(ai.digest.algorithm, Algorithm::Sha256);
assert!(ai.verify().unwrap());
}
#[test]
fn sha256_thumbprints_signed_32() {
let pe_path = PathBuf::from("test-pe/test-signed-32.bin");
let pe_file = std::fs::read(pe_path).unwrap();
let ai = AuthenticodeInfo::try_from(&pe_file).unwrap();
assert_eq!(ai.certificates.len(), 2);
assert_eq!(
ai.certificates[0].sha256.to_hex(),
"bb91b9f1a11556a6556a804d0b5c984c3d1281a04dc918ab7b0a90d8b0747fde"
);
assert_eq!(
ai.certificates[1].sha256.to_hex(),
"e8e95f0733a55e8bad7be0a1413ee23c51fcea64b3c8fa6a786935fddcc71961"
);
assert_eq!(ai.digest.algorithm, Algorithm::Sha256);
assert!(ai.verify().unwrap());
}
#[test]
fn no_cert_unsigned_32() {
let pe_path = PathBuf::from("test-pe/test-unsigned-32.bin");
let pe_file = std::fs::read(pe_path).unwrap();
let error = AuthenticodeInfo::try_from(&pe_file).err().unwrap();
assert_eq!(error, AuthenticodeError::NoWinCertificate);
}
#[test]
fn no_cert_unsigned_64() {
let pe_path = PathBuf::from("test-pe/test-unsigned-64.bin");
let pe_file = std::fs::read(pe_path).unwrap();
let error = AuthenticodeInfo::try_from(&pe_file).err().unwrap();
assert_eq!(error, AuthenticodeError::NoWinCertificate);
}
#[test]
fn not_a_pe_file() {
let pe_path = PathBuf::from("test-pe/test-no-pe.bin");
let pe_file = std::fs::read(pe_path).unwrap();
let error = AuthenticodeInfo::try_from(&pe_file).err().unwrap();
assert_eq!(
error,
AuthenticodeError::ParsePe("Invalid DOS header size or alignment".to_string())
);
}
}