Skip to content

Latest commit

 

History

History
61 lines (51 loc) · 3.28 KB

File metadata and controls

61 lines (51 loc) · 3.28 KB

authenticode 0.4.3 — the API this crate depends on

Verified against a real signed installer and the unsigned fixture in fixtures/. This corner of the crate is thinly documented and easy to get subtly wrong. See the SignerInfo.sid note in the README.

Parsing flow

use object::read::pe::PeFile64;
use authenticode::AttributeCertificateIterator;

let pe = PeFile64::parse(&*data)?;                       // Err if not a PE
match AttributeCertificateIterator::new(&pe)? {          // Result<Option<Iterator>>
    None => /* UNSIGNED — no attribute certificate table */,
    Some(iter) => for cert in iter {                     // item: Result<AttributeCertificate>
        let sig = cert?.get_authenticode_signature()?;   // owned AuthenticodeSignature
        // ...
    }
}

AuthenticodeSignature accessors (confirmed)

  • sig.signer_info() -> &cms::signed_data::SignerInfo
    • .digest_alg.oid → digest algorithm OID (Display = dotted string)
    • .unsigned_attrs: Option<Attributes> → .iter() yields attrs with .oid (timestamp lives here)
    • .sid: SignerIdentifier → identifies the signer (leaf) cert
  • sig.certificates() -> impl Iterator<Item = &x509_cert::Certificate> — the full chain bundle (leaf + CAs)
  • sig.digest() -> &[u8], sig.signature() -> &[u8], sig.encapsulated_content() -> Option<&[u8]>

⚠ Critical gotcha (found by the spike)

certificates() is NOT ordered leaf-first — on the real installer .next() was the Sectigo root, not the signer. The signer (leaf) MUST be selected by matching SignerInfo.sid:

use cms::signed_data::SignerIdentifier;
let leaf: Option<&x509_cert::Certificate> = match &si.sid {
    SignerIdentifier::IssuerAndSerialNumber(isn) => sig.certificates().find(|c| {
        c.tbs_certificate.issuer == isn.issuer
            && c.tbs_certificate.serial_number == isn.serial_number
    }),
    SignerIdentifier::SubjectKeyIdentifier(_) => None, // rare for Authenticode; handle later
};

x509_cert::Certificate fields (confirmed)

  • c.tbs_certificate.subject / .issuer — Name, Display = RFC 4514 (e.g. CN=...,O=Acme,C=US)
  • c.tbs_certificate.serial_number — SerialNumber (PartialEq works for sid match)
  • c.tbs_certificate.validity.not_after — Display = RFC 3339 (2024-11-18T23:59:59Z); for unix secs use .to_unix_duration().as_secs() on the inner Time

Timestamp OIDs (in unsigned_attrs)

  • 1.2.840.113549.1.9.6 — PKCS#9 counterSignature (legacy Authenticode timestamp; seen on ce-cli)
  • 1.3.6.1.4.1.311.3.3.1 — Microsoft RFC-3161 timestamp token (modern) Presence of either ⇒ timestamped.

Digest OIDs

  • 2.16.840.1.101.3.4.2.1 SHA-256 (good)
  • 1.3.14.3.2.26 SHA-1 (deprecated; ce-cli's primary sig uses this)

Design impact on the plan

  • No need to separately cms::SignedData::from_der — AuthenticodeSignature already exposes signer_info() + certificates(). The signature.rs module takes &AuthenticodeSignature, does the sid-based leaf selection, and returns owned SignatureInfo { digest_algo_oid, has_timestamp, leaf_cert }.
  • pe.rs returns an owned Option<AuthenticodeSignature> (the type owns its parsed data; from_bytes/get_authenticode_signature return Self, not a borrow).
  • cert.rs takes &x509_cert::Certificate (the leaf), unchanged in spirit.