cross_authenticode/
authenticode_info.rs1use 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#[derive(Debug)]
25pub struct DigestInfo {
26 pub algorithm: Algorithm,
28 pub hash: Vec<u8>,
33}
34
35pub struct AuthenticodeInfo<'a> {
37 pub certificates: Vec<AuthenticodeCertificate>,
39 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 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 pub fn authenticode_sha1(&self) -> Result<Vec<u8>, AuthenticodeError> {
80 self.authenticode_hash::<Sha1>()
81 }
82
83 pub fn authenticode_sha256(&self) -> Result<Vec<u8>, AuthenticodeError> {
85 self.authenticode_hash::<Sha256>()
86 }
87
88 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 let bytes = self.pe.data().get(..offsets.checksum).err_slice()?;
112 digest.update(bytes);
113
114 let bytes = self
116 .pe
117 .data()
118 .get(offsets.after_checksum..offsets.security_dir)
119 .err_slice()?;
120 digest.update(bytes);
121
122 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 let mut sum_of_bytes_hashed = offsets.after_header;
133
134 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 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 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
241impl<'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
250impl<'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}