pesign/
lib.rs

1//! > **PE Signature Parser for Rust**
2//!
3//! `pe-sign` is a cross-platform tool developed in Rust, designed for parsing and verifying digital signatures
4//! in PE files. It provides a simple command-line interface that supports extracting certificates, verifying
5//! digital signatures, calculating Authenticode digests, and printing certificate information. It can be used
6//! as a standalone command-line tool or integrated into your Rust project as a dependency.
7//!
8//! CommandLine Tool Document: [README.md](https://github.com/0xlane/pe-sign).
9//!
10//! ## Example
11//!
12//! Run
13//! ```console
14//! $ cargo add pe-sign
15//! ```
16//!
17//! Then use `pesign` and parse PE file sigature to [`PeSign`] struct in `main.rs`:
18//! ```no_run
19//! use pesign::PeSign;
20//!
21//! fn main() {
22//!     if let Some(pesign) = PeSign::from_pe_path("test.exe").unwrap() {
23//!         // Add your program logic.
24//!     } else {
25//!         println!("The file is no signed!!");
26//!     }
27//! }
28//! ```
29//!
30
31use std::{fmt::Display, path::Path};
32
33use asn1_types::SpcIndirectDataContent;
34use cert::Algorithm;
35use chrono::{DateTime, Local, Utc};
36use cms::{
37    attr::SigningTime,
38    cert::x509::der::{oid::db::rfc5911::ID_SIGNED_DATA, Decode, SliceReader},
39    content_info::ContentInfo,
40};
41use der::{asn1::SetOfVec, Encode, EncodePem};
42use errors::{PeSignError, PeSignErrorKind, PeSignResult};
43use signed_data::SignedData;
44use utils::{to_hex_str, DisplayBytes, IndentString, TryVecInto};
45
46mod pe;
47
48pub mod asn1_types;
49pub mod cert;
50pub mod errors;
51pub mod signed_data;
52pub mod utils;
53pub use der;
54pub use pe::*;
55
56/// Obtaining a PE file's signature
57///
58/// This includes all the information in the PE signature: certificate list, signer information, and Authenticode.
59///
60/// You can retrieve the signature data from a specified PE file path using [`PeSign::from_pe_path`], or parse it
61/// from an exported signature byte array using [`PeSign::from_certificate_table_buf`].
62///
63/// Example:
64///
65/// ```no_run
66/// use pesign::PeSign;
67///
68/// let pesign = PeSign::from_pe_path("test.exe").unwrap().unwrap();
69/// println!("{}", pesign.signed_data.signer_info);
70/// ```
71#[derive(Clone, Debug, Eq, PartialEq)]
72pub struct PeSign {
73    pub signed_data: SignedData,
74    pub authenticode_digest: String,
75    pub authenticode_digest_algorithm: Algorithm,
76    __inner: cms::content_info::ContentInfo,
77}
78
79impl der::Encode for PeSign {
80    fn encoded_len(&self) -> der::Result<der::Length> {
81        self.__inner.encoded_len()
82    }
83
84    fn encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> {
85        self.__inner.encode(encoder)
86    }
87}
88
89impl<'a> der::Decode<'a> for PeSign {
90    fn decode<R: der::Reader<'a>>(decoder: &mut R) -> der::Result<Self> {
91        Self::from_reader(decoder)
92            .map_err(|_| der::Error::new(der::ErrorKind::Failed, der::Length::ZERO))
93    }
94}
95
96impl der::pem::PemLabel for PeSign {
97    const PEM_LABEL: &'static str = "PKCS7";
98}
99
100impl<'a> PeSign {
101    pub fn from_reader<R: der::Reader<'a>>(decoder: &mut R) -> Result<Self, PeSignError> {
102        let ci = ContentInfo::decode(decoder).map_app_err(PeSignErrorKind::InvalidContentInfo)?;
103
104        let __inner = ci.clone();
105
106        // signedData
107        match ci.content_type {
108            ID_SIGNED_DATA => {
109                let signed_data: SignedData = ci
110                    .content
111                    .decode_as::<crate::asn1_types::SignedData>()
112                    .map_app_err(PeSignErrorKind::InvalidSignedData)?
113                    .try_into()?;
114
115                match &signed_data.encap_content_info.econtent_type[..] {
116                    "1.3.6.1.4.1.311.2.1.4" => {
117                        let spc_indirect_data_content = SpcIndirectDataContent::from_der(
118                            &signed_data.encap_content_info.econtent,
119                        )
120                        .map_app_err(PeSignErrorKind::InvalidSpcIndirectDataContent)?;
121                        let authenticode =
122                            to_hex_str(spc_indirect_data_content.message_digest.digest.as_bytes());
123                        let authenticode_digest =
124                            spc_indirect_data_content.message_digest.algorithm.into();
125                        Ok(Self {
126                            signed_data,
127                            authenticode_digest: authenticode,
128                            authenticode_digest_algorithm: authenticode_digest,
129                            __inner,
130                        })
131                    }
132                    _ => Err(PeSignError {
133                        kind: PeSignErrorKind::InvalidEncapsulatedContentType,
134                        message: signed_data.encap_content_info.econtent_type,
135                    }),
136                }
137            }
138            ct => Err(PeSignError {
139                kind: PeSignErrorKind::InvalidContentType,
140                message: ct.to_string(),
141            }
142            .into()),
143        }
144    }
145
146    /// Extract signature information from the exported certificate.
147    pub fn from_certificate_table_buf(bin: &[u8]) -> Result<Self, PeSignError> {
148        let mut reader = SliceReader::new(bin).map_unknown_err()?;
149        Self::from_reader(&mut reader)
150    }
151
152    /// Extract signature information from a disk file.
153    pub fn from_pe_path<P: AsRef<Path>>(filename: P) -> Result<Option<Self>, PeSignError> {
154        let mut image = PE::from_path(filename)?;
155
156        Self::from_pe_image(&mut image)
157    }
158
159    /// Extract signature information from a memory pe data.
160    pub fn from_pe_data(bin: &[u8]) -> Result<Option<Self>, PeSignError> {
161        let mut image = PE::from_bytes(bin)?;
162
163        Self::from_pe_image(&mut image)
164    }
165
166    /// Extract signature information from [`PE`].
167    pub fn from_pe_image(image: &mut PE) -> Result<Option<Self>, PeSignError> {
168        match image.get_security_data()? {
169            Some(pkcs7_bytes) => Ok(Some(Self::from_certificate_table_buf(&pkcs7_bytes)?)),
170            None => Ok(None),
171        }
172    }
173
174    /// Verify the validity of the certificate.
175    pub fn verify(self: &Self, option: &VerifyOption) -> Result<PeSignStatus, PeSignError> {
176        self.signed_data.verify(option)
177    }
178
179    /// Verify the validity of the certificate.
180    pub fn verify_pe_path<P: AsRef<Path>>(
181        self: &Self,
182        filename: P,
183        option: &VerifyOption,
184    ) -> Result<PeSignStatus, PeSignError> {
185        let mut image = PE::from_path(filename)?;
186
187        self.verify_pe_image(&mut image, option)
188    }
189
190    /// Verify the validity of the certificate.
191    pub fn verify_pe_data(
192        self: &Self,
193        bin: &[u8],
194        option: &VerifyOption,
195    ) -> Result<PeSignStatus, PeSignError> {
196        let mut image = PE::from_bytes(bin)?;
197
198        self.verify_pe_image(&mut image, option)
199    }
200
201    /// Verify the validity of the certificate.
202    pub fn verify_pe_image(
203        self: &Self,
204        image: &mut PE,
205        option: &VerifyOption,
206    ) -> Result<PeSignStatus, PeSignError> {
207        let authenticode = image.calc_authenticode(self.authenticode_digest_algorithm.clone())?;
208
209        if authenticode != self.authenticode_digest {
210            Ok(PeSignStatus::Invalid)
211        } else {
212            self.verify(option)
213        }
214    }
215
216    /// Export as DER.
217    pub fn export_der(self: &Self) -> Result<Vec<u8>, PeSignError> {
218        self.to_der().map_app_err(PeSignErrorKind::ExportDerError)
219    }
220
221    /// Export as PEM.
222    pub fn export_pem(self: &Self) -> Result<String, PeSignError> {
223        self.to_pem(Default::default())
224            .map_app_err(PeSignErrorKind::ExportPemError)
225    }
226}
227
228/// PE Signature Status.
229#[derive(Clone, Copy, Debug, Eq, PartialEq)]
230pub enum PeSignStatus {
231    /// Untrusted Certificate Chain.
232    UntrustedCertificateChain,
233
234    /// Expired Certificate.
235    Expired,
236
237    /// Invalid Certificate.
238    Invalid,
239
240    /// Valid Certificate.
241    Valid,
242}
243
244const DEFAULT_TRUSTED_CA_PEM: &str = include_str!("./cacert.pem");
245
246#[derive(Clone, Debug, Eq, PartialEq)]
247pub struct VerifyOption {
248    pub check_time: bool,
249    pub trusted_ca_pem: Option<String>,
250}
251
252impl Default for VerifyOption {
253    fn default() -> Self {
254        Self {
255            check_time: true,
256            trusted_ca_pem: None,
257        }
258    }
259}
260
261#[derive(Clone, Debug, Eq, PartialEq)]
262pub struct Attributes(pub Vec<Attribute>);
263
264impl TryFrom<x509_cert::attr::Attributes> for Attributes {
265    type Error = PeSignError;
266
267    fn try_from(value: x509_cert::attr::Attributes) -> Result<Self, Self::Error> {
268        Ok(Self(value.into_vec().try_vec_into().map_err(|err| {
269            Self::Error {
270                kind: PeSignErrorKind::Unknown,
271                message: err.to_string(),
272            }
273        })?))
274    }
275}
276
277impl Display for Attributes {
278    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279        write!(
280            f,
281            "{}",
282            self.0
283                .iter()
284                .map(|v| v.to_string())
285                .collect::<Vec<String>>()
286                .join("\n")
287        )
288    }
289}
290
291impl Attributes {
292    pub fn to_der(self: &Self) -> Result<Vec<u8>, PeSignError> {
293        let mut result = SetOfVec::<x509_cert::attr::Attribute>::new();
294
295        for vv in &self.0 {
296            result.insert(vv.__inner.clone()).map_unknown_err()?;
297        }
298
299        Ok(result.to_der().map_unknown_err()?)
300    }
301}
302
303#[derive(Clone, Debug, Eq, PartialEq)]
304pub struct Attribute {
305    pub oid: String,
306    pub values: Vec<Vec<u8>>,
307    __inner: x509_cert::attr::Attribute,
308}
309
310impl TryFrom<x509_cert::attr::Attribute> for Attribute {
311    type Error = PeSignError;
312
313    fn try_from(attr: x509_cert::attr::Attribute) -> Result<Self, Self::Error> {
314        let mut values = vec![];
315
316        for vv in attr.values.iter() {
317            values.push(vv.to_der().map_unknown_err()?);
318        }
319
320        Ok(Self {
321            oid: attr.oid.to_string(),
322            values: values,
323            __inner: attr,
324        })
325    }
326}
327
328impl Display for Attribute {
329    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
330        let attr_value = self.values.concat();
331        match self.oid.as_str() {
332            "1.2.840.113549.1.9.5" => {
333                // signingTime
334                writeln!(f, "{}", "Signing Time (1.2.840.113549.1.9.5):")?;
335                match SigningTime::from_der(&attr_value).map_err(|_| std::fmt::Error)? {
336                    x509_cert::time::Time::UtcTime(time) => {
337                        write!(
338                            f,
339                            "{}",
340                            DateTime::<Utc>::from(time.to_system_time())
341                                .with_timezone(&Local)
342                                .to_string()
343                                .indent(4)
344                        )
345                    }
346                    x509_cert::time::Time::GeneralTime(time) => {
347                        write!(
348                            f,
349                            "{}",
350                            DateTime::<Utc>::from(time.to_system_time())
351                                .with_timezone(&Local)
352                                .to_string()
353                                .indent(4)
354                        )
355                    }
356                }
357            }
358            "1.2.840.113549.1.9.6" => {
359                // counterSignature
360                writeln!(f, "{}", "Counter Signature (1.2.840.113549.1.9.6):")?;
361                write!(f, "{}", attr_value.to_bytes_string().indent(4))
362            }
363            _ => {
364                writeln!(f, "{}:", self.oid)?;
365                write!(f, "{}", attr_value.to_bytes_string().indent(4))
366            }
367        }
368    }
369}
370
371#[cfg(test)]
372mod tests {
373    use std::time::UNIX_EPOCH;
374
375    use cms::cert::x509::der::SliceReader;
376    use der::{DecodePem, EncodePem};
377
378    use super::*;
379
380    #[test]
381    fn test_parse_pkcs7_from_der() {
382        let bytes = include_bytes!("./examples/pkcs7.cer");
383        assert!(ContentInfo::from_der(bytes)
384            .unwrap_err()
385            .to_string()
386            .starts_with("trailing data"));
387    }
388
389    #[test]
390    fn test_parse_pkcs7_decode() {
391        let bytes = include_bytes!("./examples/pkcs7.cer");
392        let mut reader = SliceReader::new(bytes).unwrap();
393        assert!(ContentInfo::decode(&mut reader).is_ok());
394    }
395
396    #[test]
397    fn get_authenticode() {
398        let bytes = include_bytes!("./examples/pkcs7.cer");
399        let pesign = PeSign::from_certificate_table_buf(bytes).unwrap();
400
401        assert_eq!(
402            pesign.authenticode_digest,
403            "9253a6f72ee0e3970d5457e0f061fdb40b484f18"
404        );
405    }
406
407    #[test]
408    fn get_nested_authenticode() {
409        let bytes = include_bytes!("./examples/pkcs7.cer");
410        let pesign = PeSign::from_certificate_table_buf(bytes).unwrap();
411        let nested = pesign
412            .signed_data
413            .signer_info
414            .get_nested_signature()
415            .unwrap()
416            .unwrap();
417
418        assert_eq!(
419            nested.authenticode_digest,
420            "33a755311b428c2063f983058dbf9e1648d00d5fec4adf00e0a34ddee639f68b",
421        );
422    }
423
424    #[test]
425    fn get_signer_signature_data() {
426        let bytes = include_bytes!("./examples/dotnet.cer");
427        let pesign = PeSign::from_certificate_table_buf(bytes).unwrap();
428
429        let signature_value = &pesign.signed_data.signer_info.signature[..];
430
431        let signature_bin = include_bytes!("./examples/signature.bin");
432
433        assert_eq!(signature_value, signature_bin);
434    }
435
436    #[test]
437    fn parse_signingtime_from_cs() {
438        let bytes = include_bytes!("./examples/pkcs7.cer");
439        let pesign = PeSign::from_certificate_table_buf(bytes).unwrap();
440
441        assert_eq!(
442            pesign
443                .signed_data
444                .get_signing_time()
445                .unwrap()
446                .duration_since(UNIX_EPOCH)
447                .unwrap()
448                .as_secs(),
449            1459215302
450        );
451    }
452
453    #[test]
454    fn parse_signingtime_from_ms_tst_sign() {
455        let bytes = include_bytes!("./examples/pkcs7.cer");
456        let pesign = PeSign::from_certificate_table_buf(bytes).unwrap();
457
458        assert_eq!(
459            pesign
460                .signed_data
461                .signer_info
462                .get_nested_signature()
463                .unwrap()
464                .unwrap()
465                .signed_data
466                .get_signing_time()
467                .unwrap()
468                .duration_since(UNIX_EPOCH)
469                .unwrap()
470                .as_secs(),
471            1459215303
472        );
473    }
474
475    #[test]
476    fn parse_signingtime_from_attr() {
477        let bytes = include_bytes!("./examples/pkcs7_with_signing_time.cer");
478        let pesign = PeSign::from_certificate_table_buf(bytes).unwrap();
479
480        assert_eq!(
481            pesign
482                .signed_data
483                .get_signing_time()
484                .unwrap()
485                .duration_since(UNIX_EPOCH)
486                .unwrap()
487                .as_secs(),
488            1717347664
489        );
490    }
491
492    #[test]
493    fn export_pem() {
494        let bytes = include_bytes!("./examples/pkcs7.cer");
495        let pesign = PeSign::from_certificate_table_buf(bytes).unwrap();
496
497        assert_eq!(
498            pesign.to_pem(Default::default()).unwrap(),
499            include_str!("./examples/pkcs7.pem")
500        );
501    }
502
503    #[test]
504    fn from_pem() {
505        let pem = include_str!("./examples/pkcs7.pem");
506        let result = PeSign::from_pem(pem);
507
508        assert!(result.is_ok());
509    }
510
511    #[test]
512    fn from_pe() {
513        let result = PeSign::from_pe_data(include_bytes!("./examples/ProcessHacker.exe"));
514
515        assert!(result.is_ok());
516    }
517
518    #[test]
519    fn verify_pe() {
520        let pedata = include_bytes!("./examples/ProcessHacker.exe");
521
522        let status = PeSign::from_pe_data(pedata)
523            .unwrap()
524            .unwrap()
525            .verify_pe_data(pedata, &Default::default())
526            .unwrap();
527
528        assert_eq!(status, PeSignStatus::Expired);
529    }
530
531    #[test]
532    fn test_cert_include_attr_cert_v1() {
533        let bytes = include_bytes!("./examples/dotnet.cer");
534        let pesign = PeSign::from_certificate_table_buf(bytes).unwrap();
535
536        let signing_time = pesign.signed_data.get_signing_time().unwrap();
537
538        assert_eq!(
539            signing_time.duration_since(UNIX_EPOCH).unwrap().as_secs(),
540            1715980852
541        );
542    }
543}