signalscreen-checker
Windows code-signing hygiene checker. Reads the Authenticode signature inside a PE file and grades it A–F.
Pure Rust, no Windows dependency. Runs on Linux, macOS and Windows.
This is the engine behind the free checker at signalscreen.dev.
Quick start
$ signalscreen-check fixtures/selfsigned-sha256.exe
Signing hygiene: F (20/100)
file: fixtures/selfsigned-sha256.exe
[Pass] signature: Authenticode signature is present and well-formed.
[Fail] timestamp: No RFC-3161 timestamp; the signature stops validating once the certificate expires.
[Pass] digest: Modern digest algorithm.
[Fail] chain: Certificate is self-signed (no issuing CA); it has no reputation.
[Pass] expiry: Certificate validity is healthy.
Timestamps: there are two dialects, not one
A signature without a countersignature stops validating the day its certificate expires. So the checker asks whether one is there. It also reads it.
Two formats are in the wild. The modern one is an RFC 3161 token
in a Microsoft unsigned attribute. The older one is a PKCS#9
countersignature carrying signingTime, as a UTCTime with a
two-digit year.
Signing tools pick one and never say which. Both appear on current, commercially signed binaries. Handle only the modern form and you report "no timestamp" on a correctly timestamped file — a false accusation, and worse than saying nothing.
The report gives the date and the authority for either:
"timestamp":
timestamp_present without timestamp means one is there and
could not be read. That is deliberate. Collapsing the two would
report a hardening failure where there is only a parsing one.
What it does not do
Saying this first, because the distinction is the whole point.
A grade here is not a SmartScreen prediction. SmartScreen makes a reputation judgement. Reputation is separate from whether the certificate is valid. A properly signed release with grade A can still show "Windows protected your PC". This crate only says whether the signing is clean. Necessary, not sufficient.
Two more limits, so nothing is oversold:
signature.validmeans the signature parsed and a signer certificate was found. The image hash is not recomputed and compared.- Both PE32 and PE32+ parse, but only the 64-bit path has a signed fixture. The signature lives in the attribute certificate table, located the same way for both widths, so the 32-bit signed path should work. It is not proven by a test.
CLI
Human-readable by default. --json emits the canonical output that every other
surface (the web app, CI) consumes:
signalscreen-check installer.exe --json
Library
use analyze;
let data = read?;
let now = now
.duration_since?
.as_secs as i64;
let report = analyze?;
println!;
now_unix is injected, not read inside. Keeps expiry checks deterministic under test.
Grading
Score starts at 100. Each failing check subtracts.
| Check | Condition | Penalty |
|---|---|---|
signature |
no Authenticode signature | 100 (grade F outright) |
timestamp |
no RFC-3161 timestamp | 40 |
chain |
self-signed | 40 |
digest |
SHA-1 digest algorithm | 30 |
expiry |
certificate expired, and not timestamped | 20 |
expiry |
expires within 30 days, and not timestamped | 10 (warning) |
A 90–100 · B 80–89 · C 70–79 · D 60–69 · F below 60.
Weights encode a thesis, not a standard. Missing timestamp costs as much as a self-signed chain. Reason: when the certificate expires, an untimestamped signature stops validating everywhere, retroactively, on binaries shipped years ago.
A timestamped signature is exempt from the expiry penalty. Timestamping is exactly what keeps a signature valid past its certificate's expiry, and Authenticode accepts it with no warning — so taking points off would make the grade assert something false about whether the binary was properly signed. The expiry is still shown as a fact in the report; it just no longer costs points when a timestamp covers it.
One subtlety
The signer's leaf certificate is selected by SignerInfo.sid. Not by taking the first
entry of the PKCS#7 certificate bag.
That bag holds the whole bundle, leaf plus intermediates plus root, in no guaranteed order. First entry is very often a CA. Naive code grades it and looks like it works: a CA certificate is not self-signed the way the leaf would be, and its expiry is years away.
API_NOTES.md records the parts of the authenticode 0.4 API this depends on, verified
against real signed binaries.
Tests
cargo test
15 tests. Fixtures are self-contained. unsigned.exe and unsigned-pe32.exe are
compiled do-nothing PEs, 64- and 32-bit. selfsigned-sha256.exe is the 64-bit one signed
with a throwaway test CA. No third-party binary is committed.
Licence
Dual-licensed under either Apache-2.0 or MIT, at your option.