Adds a new "Digital signature properties" doorhanger to the pdf.js toolbar that lists every digital signature found in the opened PDF, verifies each one (via NSS in the Firefox build through a new chrome bridge), and shows per-signature status + certificate state. The viewer side parses /Sig dicts in the worker (`PDFDocument.signatures`), strict-validates the /ByteRange offsets before slicing, and ships only signature metadata across the worker boundary. The PKCS#7 blob and signed-data byte spans live in a worker-side map and are fetched lazily one signature at a time via a new `getSignatureData(id)` RPC, immediately before verification runs, so the bytes never sit in main-thread memory for the document's lifetime. The panel is feature-gated by `pdfjs.enableSignatureVerification` (true in MOZCENTRAL/TESTING, off by default in the GENERIC build). External services expose a `createSignatureVerifier()` factory that the Firefox build wires up to `nsIX509CertDB.asyncVerifyPKCS7Object`; GENERIC builds return null and the toolbar button stays hidden. UI summary: - Toolbar button states: loading dots while in flight, then green check, orange `!`, or red `✕` based on the worst aggregate signature status. - Doorhanger contains a banner summarising the document state, then one card per signature with status row + certificate row (sub- signatures nested under their outer revision via /ByteRange containment). - Icons are mono SVGs themed via `mask-image` + `background-color` so they pick up light/dark/HCM via `--sig-icon-*` vars; flipped under RTL via `scaleX(var(--dir-factor))`. The HCM mapping reuses the alt-text vocabulary (ButtonFace / ButtonText / ButtonBorder / GrayText / AccentColor / LinkText) so this panel reads the same as the rest of the editor toolbar in high-contrast mode. - All visible strings are localized via Fluent (`pdfjs-digital-signature-properties-*`); status row, banner, and certificate row use explicit lookup tables instead of generated ids so a grep finds them. - Esc + outside-click close the panel through the viewer's existing handlers; the manager exposes `isOpen`, `close()`, and `shouldCloseOnClick(target)` for that. This commit also adds a `test/pdfs/sig_corpus/` directory holding a Python generator that produces a corpus of signed PDFs covering every visible state of the doorhanger (verified / untrusted / expired / invalid / unknown / multi-signature variants). The corpus is intentionally NOT part of the automated test suite — it is a manual-test tool. Generated `.pdf` files are gitignored; only the generator, README, and a `user.js.example` snippet are tracked. The generator shells out to mozilla-central's `security/manager/tools/pycms.py` (resolved via `--mozilla-central <path>` or the `MOZILLA_CENTRAL_SRC` env var) and the embedded test trust anchors (`pdf-sign-ca` / `pdf-sign-ca-expired`), gated by `security.pdf_signature_verification.enable_test_trust_anchors` so the test certificates never validate in shipping Firefox.
Digital signature properties — manual-test PDF corpus
This directory ships a Python generator that produces a small corpus of digitally signed PDFs covering every visible state of the Digital signature properties doorhanger. The intent is manual visual testing: open each PDF in a Firefox build that has the signature verification UI enabled, and compare what the toolbar / banner / cards render against what the PDF's own page content says they should render.
The PDFs themselves are not committed (*.pdf is ignored). Only
generate.py and this README are tracked, so you regenerate the
corpus when you need it.
Prerequisites
- A built mozilla-central checkout. The generator shells out to its
security/manager/tools/pycms.pyand reuses its vendored Python modules underthird_party/python/{ecdsa,rsa,pyasn1,pyasn1_modules,six}. - A Firefox build (Nightly or a local artefact / full build) that includes the pdf.js viewer + chrome bridge for the Signature Properties UI. Any Nightly built after the Bug 1943059 landing contains both pieces.
The generator finds your mozilla-central checkout in this order:
--mozilla-central </path/to/mozilla-central>CLI flag.MOZILLA_CENTRAL_SRCenvironment variable./opt/mozilla/firefox(fallback default; prints a warning).
Generate
From the pdf.js root, with the path resolved via any of the methods above:
python3 test/pdfs/sig_corpus/generate.py \
--mozilla-central ~/src/mozilla-central
# …or…
MOZILLA_CENTRAL_SRC=~/src/mozilla-central \
python3 test/pdfs/sig_corpus/generate.py
You should see eight .pdf files appear in this directory.
Enable the test trust anchors pref
Three of the cases (signed_verified, both verified multi-sig PDFs,
and the outer half of signed_multi_outer_verified_inner_expired)
need Firefox to trust the bundled pdf-sign-ca test root. That root
is gated behind one pref:
security.pdf_signature_verification.enable_test_trust_anchors = true
The pref defaults to false in every Firefox build (Release, Beta,
Nightly, local), so by default a Firefox cannot validate PDFs
signed with these test certs. To enable it for manual testing:
- Easiest: append the contents of
user.js.example(next to this README) to your dev profile'suser.jsand (re)launch Firefox. - Or via
about:config→ search for the pref name → toggle totrue.
⚠️ Do not enable this in your normal browsing profile. With the
pref on, any PDF signed with the publicly known mozilla-central test
private key validates as "trusted" until those certs expire
(pdf-sign-ca notAfter = 2027-01-01).
Open the PDFs
Launch any Firefox build that bundles the Digital signature properties UI
and open the PDFs via file:/// URLs, e.g.:
firefox file:///$(pwd)/test/pdfs/sig_corpus/signed_verified.pdf
Or ./mach run -- file:///…/signed_verified.pdf from your
mozilla-central checkout.
The page content of every PDF describes the expected toolbar icon, banner, status row, and certificate row. Compare it against the doorhanger.
Cases
| File | Toolbar icon | Banner | Notes |
|---|---|---|---|
signed_verified.pdf |
green ✓ | green | leaf ← pdf-sign-ca |
signed_untrusted.pdf |
orange ! | orange | self-signed root |
signed_expired.pdf |
orange ! | orange | leaf ← pdf-sign-ca-expired |
signed_invalid.pdf |
red × | red | one byte tampered post-sign |
signed_unknown.pdf |
red × | red | /SubFilter /ETSI.CAdES.detached (unsupported by pdf.js) |
signed_multi_verified.pdf |
green ✓ | green | both sigs valid, "Sub-signatures (1)" |
signed_multi_mixed.pdf |
orange ! | orange | outer verified, inner self-signed/untrusted |
signed_multi_outer_verified_inner_expired.pdf |
orange ! | orange | outer verified, inner expired — exercises worst-status-wins logic |
The last entry is the most informative for verifying that the banner aggregation isn't accidentally clamped to the outermost signature.
Out of scope
revokedstatus. Producing a revoked-certificate state end-to-end against NSS requires either an OCSP responder, a CRL file in the right path, or a OneCRL fixture — none of which are feasible to ship as a static PDF corpus. The UI path forrevoked(red banner / red cert row / red toolbar icon) is exercised only via the existing xpcshell tests.- CAdES validation.
signed_unknown.pdfonly proves that pdf.js short-circuits tounknownforETSI.CAdES.detached; real CAdES signature validation is follow-up Firefox work.
Sanity check the safeguard
Open signed_verified.pdf with the pref off (default). Every
single PDF should now report untrusted (unknown issuer). That's
the expected behavior in shipping Firefox and confirms the pref
guard is doing its job.