cross_authenticode/
authenticode_info.rs

1use crate::algorithm::Algorithm;
2use crate::error::OptionExt;
3use crate::pe_file::PeFile;
4use crate::spc_indirect_data::{SPC_INDIRECT_DATA_OBJID, SpcIndirectDataContent};
5use crate::win_certificate::WinCertificate;
6use crate::{authenticode_certificate::AuthenticodeCertificate, error::AuthenticodeError};
7use cms::cert::x509::spki::ObjectIdentifier;
8use cms::{
9    cert::{
10        CertificateChoices,
11        x509::der::{Decode, SliceReader},
12    },
13    content_info::ContentInfo,
14    signed_data::SignedData,
15};
16use object::read::pe::{PeFile32, PeFile64};
17use sha1::{Digest, Sha1};
18use sha2::Sha256;
19
20/// Information about the digest of the PE file.
21/// The information includes the algorithm used and the hash,
22/// which are taken from the PE file itself.
23///
24#[derive(Debug)]
25pub struct DigestInfo {
26    /// The algorithm used for the Authenticode hash.
27    pub algorithm: Algorithm,
28    /// The hash of the Authenticode signature.
29    ///
30    /// ATTENTION: The hash can be wrong and has to be verified by comparing it to the computed hash
31    /// of the Authenticode signature.
32    pub hash: Vec<u8>,
33}
34
35/// Contains information about the Authenticode signature of a PE file.
36pub struct AuthenticodeInfo<'a> {
37    /// List of certificates with additional information found in the PE file.
38    pub certificates: Vec<AuthenticodeCertificate>,
39    /// Information about the digest of the Authenticode signature file.
40    pub digest: DigestInfo,
41
42    pe: Box<dyn PeFile + 'a>,
43}
44
45impl AuthenticodeInfo<'_> {
46    fn create(data: &[u8]) -> Result<AuthenticodeInfo, AuthenticodeError> {
47        let pe: Box<dyn PeFile> = match PeFile64::parse(data) {
48            Ok(pe) => Box::new(pe),
49            Err(_) => Box::new(PeFile32::parse(data)?),
50        };
51        let content_info = Self::content_info(&pe.win_certificate()?)?;
52        let signed_data = Self::signed_data(&content_info)?;
53        let authenticode_certificates = Self::certificates(&signed_data)?;
54        let digest_info = Self::digest_info(&content_info, &signed_data)?;
55
56        Ok(AuthenticodeInfo {
57            certificates: authenticode_certificates,
58            digest: digest_info,
59            pe,
60        })
61    }
62
63    /// Verifies the Authenticode signature by comparing the hash of the PE file with the hash
64    /// found in the Authenticode signature.
65    ///
66    /// Supports SHA1 and SHA256 algorithms, for all other algorithms, use the `authenticode_hash`
67    /// function to compute the hash and compare it manually with the `DigestInfo.hash`.
68    pub fn verify(&self) -> Result<bool, AuthenticodeError> {
69        match &self.digest.algorithm {
70            Algorithm::Sha1 => Ok(self.authenticode_sha1()? == self.digest.hash),
71            Algorithm::Sha256 => Ok(self.authenticode_sha256()? == self.digest.hash),
72            alg => Err(AuthenticodeError::InvalidHashAlgorithmWithName(
73                alg.to_string(),
74            )),
75        }
76    }
77
78    /// Returns the SHA1 hash of the Authenticode signature computed from the PE file.
79    pub fn authenticode_sha1(&self) -> Result<Vec<u8>, AuthenticodeError> {
80        self.authenticode_hash::<Sha1>()
81    }
82
83    /// Returns the SHA256 hash of the Authenticode signature computed from the PE file.
84    pub fn authenticode_sha256(&self) -> Result<Vec<u8>, AuthenticodeError> {
85        self.authenticode_hash::<Sha256>()
86    }
87
88    /// Returns the hash of the Authenticode signature computed from the PE file.
89    ///
90    /// This function is generic and can be used with any hashing algorithm that implements the
91    /// `Digest` trait.
92    ///
93    /// # Example
94    /// ```rust
95    /// use sha2::Sha512;
96    /// use cross_authenticode::AuthenticodeInfo;
97    /// use std::fs::File;
98    /// use std::path::PathBuf;
99    ///
100    /// let pe_path = PathBuf::from("test-pe/test-signed-64.bin");
101    /// let pe_file = std::fs::read(pe_path).unwrap();
102    ///
103    /// let authenticode_info = AuthenticodeInfo::try_from(&pe_file).unwrap();
104    /// let hash = authenticode_info.authenticode_hash::<Sha512>().unwrap();
105    /// ```
106    pub fn authenticode_hash<D: Digest>(&self) -> Result<Vec<u8>, AuthenticodeError> {
107        let mut digest = D::new();
108        let offsets = self.pe.offsets()?;
109
110        // Hash from beginning to checksum.
111        let bytes = self.pe.data().get(..offsets.checksum).err_slice()?;
112        digest.update(bytes);
113
114        // Hash from checksum to the security data directory.
115        let bytes = self
116            .pe
117            .data()
118            .get(offsets.after_checksum..offsets.security_dir)
119            .err_slice()?;
120        digest.update(bytes);
121
122        // Hash from the security data directory to the end of the header.
123        let bytes = self
124            .pe
125            .data()
126            .get(offsets.after_security_dir..offsets.after_header)
127            .err_slice()?;
128        digest.update(bytes);
129
130        // Track offset as sections are hashed. This is used to hash data
131        // after the sections.
132        let mut sum_of_bytes_hashed = offsets.after_header;
133
134        // First sort the sections.
135        let mut sections = (1..=offsets.num_sections)
136            .map(|i| self.pe.section_data_range(i))
137            .collect::<Result<Vec<_>, AuthenticodeError>>()?;
138        sections.sort_unstable_by_key(|r| r.start);
139
140        // Then hash each section's data.
141        for section_range in sections {
142            let bytes = self.pe.data().get(section_range).err_slice()?;
143
144            digest.update(bytes);
145            sum_of_bytes_hashed = sum_of_bytes_hashed.checked_add(bytes.len()).err_pe_oor()?;
146        }
147
148        let mut extra_hash_len = self
149            .pe
150            .data()
151            .len()
152            .checked_sub(sum_of_bytes_hashed)
153            .err_pe_oor()?;
154
155        // The certificate table is not included in the hash.
156        if let Some(security_data_dir) = offsets.certificate_table_range {
157            let size = security_data_dir
158                .end
159                .checked_sub(security_data_dir.start)
160                .err_pe_oor()?;
161            extra_hash_len = extra_hash_len.checked_sub(size).err_pe_oor()?;
162        }
163
164        digest.update(
165            self.pe
166                .data()
167                .get(
168                    sum_of_bytes_hashed
169                        ..sum_of_bytes_hashed
170                            .checked_add(extra_hash_len)
171                            .err_pe_oor()?,
172                )
173                .err_slice()?,
174        );
175
176        Ok(digest.finalize().to_vec())
177    }
178
179    fn digest_info(
180        content_info: &ContentInfo,
181        signed_data: &SignedData,
182    ) -> Result<DigestInfo, AuthenticodeError> {
183        if content_info.content_type != ObjectIdentifier::new_unwrap("1.2.840.113549.1.7.2") {
184            return Err(AuthenticodeError::InvalidContentType(
185                content_info.content_type.to_string(),
186            ));
187        }
188
189        if signed_data.encap_content_info.econtent_type != SPC_INDIRECT_DATA_OBJID {
190            return Err(AuthenticodeError::InvalidEncapsulatedContentType(
191                signed_data.encap_content_info.econtent_type.to_string(),
192            ));
193        }
194
195        let indirect_data = signed_data
196            .clone()
197            .encap_content_info
198            .econtent
199            .ok_or(AuthenticodeError::NoEncapsulatedContent)?
200            .decode_as::<SpcIndirectDataContent>()?;
201
202        let hash = indirect_data.message_digest.digest.as_bytes();
203
204        Ok(DigestInfo {
205            algorithm: Algorithm::try_from(hash)?,
206            hash: hash.to_vec(),
207        })
208    }
209
210    fn signed_data(content_info: &ContentInfo) -> Result<SignedData, AuthenticodeError> {
211        let signed_data = content_info.content.decode_as::<SignedData>()?;
212        Ok(signed_data)
213    }
214
215    fn content_info(win_certificate: &WinCertificate) -> Result<ContentInfo, AuthenticodeError> {
216        let mut reader = SliceReader::new(win_certificate.certificate)?;
217        let content_info = ContentInfo::decode(&mut reader)?;
218        Ok(content_info)
219    }
220
221    fn certificates(
222        signed_data: &SignedData,
223    ) -> Result<Vec<AuthenticodeCertificate>, AuthenticodeError> {
224        let authenticode_certificates = signed_data
225            .certificates
226            .as_ref()
227            .ok_or(AuthenticodeError::NoCertificates)?
228            .0
229            .iter()
230            .filter_map(|cert| match cert {
231                CertificateChoices::Certificate(cert) => Some(cert),
232                _ => None,
233            })
234            .map(|cert| AuthenticodeCertificate::try_from(cert.to_owned()))
235            .collect::<Result<Vec<_>, _>>()?;
236
237        Ok(authenticode_certificates)
238    }
239}
240
241/// Tries to create an `AuthenticodeInfo` struct from a slice of bytes.
242impl<'a> TryFrom<&'a [u8]> for AuthenticodeInfo<'a> {
243    type Error = AuthenticodeError;
244
245    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
246        Self::create(data)
247    }
248}
249
250/// Tries to create an `AuthenticodeInfo` struct from a vector of bytes.
251impl<'a> TryFrom<&'a Vec<u8>> for AuthenticodeInfo<'a> {
252    type Error = AuthenticodeError;
253
254    fn try_from(data: &'a Vec<u8>) -> Result<Self, Self::Error> {
255        Self::create(data)
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262    use crate::ToHex;
263    use std::path::PathBuf;
264
265    #[test]
266    fn sha1_thumbprints_signed_64() {
267        let pe_path = PathBuf::from("test-pe/test-signed-64.bin");
268        let pe_file = std::fs::read(pe_path).unwrap();
269
270        let ai = AuthenticodeInfo::try_from(&pe_file).unwrap();
271
272        assert_eq!(ai.certificates.len(), 2);
273        assert_eq!(
274            ai.certificates[0].sha1.to_hex(),
275            "f55115d2439ce0a7529ffaaea654be2c71dce955"
276        );
277        assert_eq!(
278            ai.certificates[1].sha1.to_hex(),
279            "580a6f4cc4e4b669b9ebdc1b2b3e087b80d0678d"
280        );
281        assert_eq!(ai.digest.algorithm, Algorithm::Sha256);
282        assert!(ai.verify().unwrap());
283    }
284
285    #[test]
286    fn sha256_thumbprints_signed_64() {
287        let pe_path = PathBuf::from("test-pe/test-signed-64.bin");
288        let pe_file = std::fs::read(pe_path).unwrap();
289
290        let ai = AuthenticodeInfo::try_from(&pe_file).unwrap();
291
292        assert_eq!(ai.certificates.len(), 2);
293        assert_eq!(
294            ai.certificates[0].sha256.to_hex(),
295            "9267a08c9fc07b6ab194dc4df3121b264e825330a39ffc42cdb0942f5115eb97"
296        );
297        assert_eq!(
298            ai.certificates[1].sha256.to_hex(),
299            "e8e95f0733a55e8bad7be0a1413ee23c51fcea64b3c8fa6a786935fddcc71961"
300        );
301        assert_eq!(ai.digest.algorithm, Algorithm::Sha256);
302        assert!(ai.verify().unwrap());
303    }
304
305    #[test]
306    fn sha1_thumbprints_signed_32() {
307        let pe_path = PathBuf::from("test-pe/test-signed-32.bin");
308        let pe_file = std::fs::read(pe_path).unwrap();
309
310        let ai = AuthenticodeInfo::try_from(&pe_file).unwrap();
311
312        assert_eq!(ai.certificates.len(), 2);
313        assert_eq!(
314            ai.certificates[0].sha1.to_hex(),
315            "aeb9b61e47d91c42fff213992b7810a3d562fb12"
316        );
317        assert_eq!(
318            ai.certificates[1].sha1.to_hex(),
319            "580a6f4cc4e4b669b9ebdc1b2b3e087b80d0678d"
320        );
321        assert_eq!(ai.digest.algorithm, Algorithm::Sha256);
322        assert!(ai.verify().unwrap());
323    }
324
325    #[test]
326    fn sha256_thumbprints_signed_32() {
327        let pe_path = PathBuf::from("test-pe/test-signed-32.bin");
328        let pe_file = std::fs::read(pe_path).unwrap();
329
330        let ai = AuthenticodeInfo::try_from(&pe_file).unwrap();
331
332        assert_eq!(ai.certificates.len(), 2);
333        assert_eq!(
334            ai.certificates[0].sha256.to_hex(),
335            "bb91b9f1a11556a6556a804d0b5c984c3d1281a04dc918ab7b0a90d8b0747fde"
336        );
337        assert_eq!(
338            ai.certificates[1].sha256.to_hex(),
339            "e8e95f0733a55e8bad7be0a1413ee23c51fcea64b3c8fa6a786935fddcc71961"
340        );
341        assert_eq!(ai.digest.algorithm, Algorithm::Sha256);
342        assert!(ai.verify().unwrap());
343    }
344
345    #[test]
346    fn no_cert_unsigned_32() {
347        let pe_path = PathBuf::from("test-pe/test-unsigned-32.bin");
348        let pe_file = std::fs::read(pe_path).unwrap();
349
350        let error = AuthenticodeInfo::try_from(&pe_file).err().unwrap();
351
352        assert_eq!(error, AuthenticodeError::NoWinCertificate);
353    }
354
355    #[test]
356    fn no_cert_unsigned_64() {
357        let pe_path = PathBuf::from("test-pe/test-unsigned-64.bin");
358        let pe_file = std::fs::read(pe_path).unwrap();
359
360        let error = AuthenticodeInfo::try_from(&pe_file).err().unwrap();
361
362        assert_eq!(error, AuthenticodeError::NoWinCertificate);
363    }
364
365    #[test]
366    fn not_a_pe_file() {
367        let pe_path = PathBuf::from("test-pe/test-no-pe.bin");
368        let pe_file = std::fs::read(pe_path).unwrap();
369
370        let error = AuthenticodeInfo::try_from(&pe_file).err().unwrap();
371
372        assert_eq!(
373            error,
374            AuthenticodeError::ParsePe("Invalid DOS header size or alignment".to_string())
375        );
376    }
377}