signalscreen-checker 0.2.1

Windows code-signing hygiene checker. Reads the Authenticode signature in a PE file and grades it A-F. Pure Rust, no Windows dependency.
Documentation
# 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
```rust
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`:

```rust
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.