cross_authenticode/
authenticode_info.rs

1use crate::win_certificate::WinCertificate;
2use crate::{authenticode_certificate::AuthenticodeCertificate, error::AuthenticodeError};
3use cms::{
4    cert::{
5        CertificateChoices,
6        x509::der::{Decode, SliceReader},
7    },
8    content_info::ContentInfo,
9    signed_data::SignedData,
10};
11use object::{
12    LittleEndian, pe::IMAGE_DIRECTORY_ENTRY_SECURITY, read::pe::PeFile32, read::pe::PeFile64,
13};
14
15/// Contains information about the Authenticode signature of a PE file.
16#[derive(Debug)]
17pub struct AuthenticodeInfo {
18    /// List of certificates with additional information found in the PE file.
19    pub certificates: Vec<AuthenticodeCertificate>,
20}
21
22impl AuthenticodeInfo {
23    fn create(data: &[u8]) -> Result<AuthenticodeInfo, AuthenticodeError> {
24        let win_certificate = Self::win_certificate(data)?;
25        let signed_data = Self::signed_data(&win_certificate)?;
26        let authenticode_certificates = Self::certificates(signed_data)?;
27
28        Ok(AuthenticodeInfo {
29            certificates: authenticode_certificates,
30        })
31    }
32
33    fn signed_data(win_certificate: &WinCertificate) -> Result<SignedData, AuthenticodeError> {
34        let mut reader = SliceReader::new(win_certificate.certificate)?;
35        let content_info = ContentInfo::decode(&mut reader)?;
36        let signed_data = content_info.content.decode_as::<SignedData>()?;
37        Ok(signed_data)
38    }
39
40    fn win_certificate(data: &[u8]) -> Result<WinCertificate, AuthenticodeError> {
41        let security_dir = match PeFile64::parse(data) {
42            Ok(pe) => pe
43                .data_directory(IMAGE_DIRECTORY_ENTRY_SECURITY)
44                .ok_or(AuthenticodeError::NoWinCertificate)?,
45            Err(_) => PeFile32::parse(data)?
46                .data_directory(IMAGE_DIRECTORY_ENTRY_SECURITY)
47                .ok_or(AuthenticodeError::NoWinCertificate)?,
48        };
49
50        let win_certificate =
51            WinCertificate::new(data, security_dir.virtual_address.get(LittleEndian))?;
52
53        Ok(win_certificate)
54    }
55
56    fn certificates(
57        signed_data: SignedData,
58    ) -> Result<Vec<AuthenticodeCertificate>, AuthenticodeError> {
59        let authenticode_certificates = signed_data
60            .certificates
61            .as_ref()
62            .ok_or(AuthenticodeError::NoCertificates)?
63            .0
64            .iter()
65            .filter_map(|cert| match cert {
66                CertificateChoices::Certificate(cert) => Some(cert),
67                _ => None,
68            })
69            .map(|cert| AuthenticodeCertificate::try_from(cert.to_owned()))
70            .collect::<Result<Vec<_>, _>>()?;
71
72        Ok(authenticode_certificates)
73    }
74}
75
76/// Tries to create an `AuthenticodeInfo` struct from a slice of bytes.
77impl TryFrom<&[u8]> for AuthenticodeInfo {
78    type Error = AuthenticodeError;
79
80    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
81        Self::create(data)
82    }
83}
84
85/// Tries to create an `AuthenticodeInfo` struct from a vector of bytes.
86impl TryFrom<&Vec<u8>> for AuthenticodeInfo {
87    type Error = AuthenticodeError;
88
89    fn try_from(data: &Vec<u8>) -> Result<Self, Self::Error> {
90        Self::create(data)
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use std::path::PathBuf;
98
99    #[test]
100    fn sha1_thumbprints_signed_64() {
101        let pe_path = PathBuf::from("test-pe/test-signed-64.bin");
102        let pe_file = std::fs::read(pe_path).unwrap();
103
104        let ai = AuthenticodeInfo::try_from(&pe_file).unwrap();
105
106        assert_eq!(ai.certificates.len(), 2);
107        assert_eq!(
108            ai.certificates[0].sha1,
109            "f55115d2439ce0a7529ffaaea654be2c71dce955"
110        );
111        assert_eq!(
112            ai.certificates[1].sha1,
113            "580a6f4cc4e4b669b9ebdc1b2b3e087b80d0678d"
114        );
115    }
116
117    #[test]
118    fn sha256_thumbprints_signed_64() {
119        let pe_path = PathBuf::from("test-pe/test-signed-64.bin");
120        let pe_file = std::fs::read(pe_path).unwrap();
121
122        let ai = AuthenticodeInfo::try_from(&pe_file).unwrap();
123
124        assert_eq!(ai.certificates.len(), 2);
125        assert_eq!(
126            ai.certificates[0].sha256,
127            "9267a08c9fc07b6ab194dc4df3121b264e825330a39ffc42cdb0942f5115eb97"
128        );
129        assert_eq!(
130            ai.certificates[1].sha256,
131            "e8e95f0733a55e8bad7be0a1413ee23c51fcea64b3c8fa6a786935fddcc71961"
132        );
133    }
134
135    #[test]
136    fn sha1_thumbprints_signed_32() {
137        let pe_path = PathBuf::from("test-pe/test-signed-32.bin");
138        let pe_file = std::fs::read(pe_path).unwrap();
139
140        let ai = AuthenticodeInfo::try_from(&pe_file).unwrap();
141
142        assert_eq!(ai.certificates.len(), 2);
143        assert_eq!(
144            ai.certificates[0].sha1,
145            "aeb9b61e47d91c42fff213992b7810a3d562fb12"
146        );
147        assert_eq!(
148            ai.certificates[1].sha1,
149            "580a6f4cc4e4b669b9ebdc1b2b3e087b80d0678d"
150        );
151    }
152
153    #[test]
154    fn sha256_thumbprints_signed_32() {
155        let pe_path = PathBuf::from("test-pe/test-signed-32.bin");
156        let pe_file = std::fs::read(pe_path).unwrap();
157
158        let ai = AuthenticodeInfo::try_from(&pe_file).unwrap();
159
160        assert_eq!(ai.certificates.len(), 2);
161        assert_eq!(
162            ai.certificates[0].sha256,
163            "bb91b9f1a11556a6556a804d0b5c984c3d1281a04dc918ab7b0a90d8b0747fde"
164        );
165        assert_eq!(
166            ai.certificates[1].sha256,
167            "e8e95f0733a55e8bad7be0a1413ee23c51fcea64b3c8fa6a786935fddcc71961"
168        );
169    }
170
171    #[test]
172    fn no_cert_unsigned_32() {
173        let pe_path = PathBuf::from("test-pe/test-unsigned-32.bin");
174        let pe_file = std::fs::read(pe_path).unwrap();
175
176        let error = AuthenticodeInfo::try_from(&pe_file).err().unwrap();
177
178        assert_eq!(error, AuthenticodeError::NoWinCertificate);
179    }
180
181    #[test]
182    fn no_cert_unsigned_64() {
183        let pe_path = PathBuf::from("test-pe/test-unsigned-64.bin");
184        let pe_file = std::fs::read(pe_path).unwrap();
185
186        let error = AuthenticodeInfo::try_from(&pe_file).err().unwrap();
187
188        assert_eq!(error, AuthenticodeError::NoWinCertificate);
189    }
190
191    #[test]
192    fn not_a_pe_file() {
193        let pe_path = PathBuf::from("test-pe/test-no-pe.bin");
194        let pe_file = std::fs::read(pe_path).unwrap();
195
196        let error = AuthenticodeInfo::try_from(&pe_file).err().unwrap();
197
198        assert_eq!(
199            error,
200            AuthenticodeError::ParsePe("Invalid DOS header size or alignment".to_string())
201        );
202    }
203}