Handoff - 2026-09-03 - verify-inbrowser
Goal: Make the /verify/ page do the verification itself (drop a PDF, get a
result), and reframe its copy from “check whether it’s really me” to a
provenance / “I stand behind my work” tone.
Status: shipped (committed 9c8ffd2 + this handoff; push pending)
Context
Follows 2026-09-03-pdf-signing-verify-page.md. That page only told people how
to verify (Acrobat / pyHanko / pdfsig — all need a tool install). This adds an
in-browser checker so a non-technical recipient gets a one-click answer.
Decisions
- pkijs + asn1js from jsdelivr at runtime, not vendored. The
+esmbundles don’t inline their transitive deps (pvtsutils, asn1js), so vendoring turns into a fetch-the-whole-tree exercise with version skew. jsdelivr resolves it all in the browser; matches the existing MathJax/Mermaid pattern.verify-sig.jspinsasn1js@3.0.5— the exact versionpkijs@3.2.4bundles — so both share one module instance (pkijs does internalinstanceofagainst asn1js classes). - Anshid-specific check (user chose this): hard-compares the signer cert’s SHA-256 to a constant. Also shows signer CN + signing date regardless.
- Link moved off the site footer onto an About-page button (user request).
- Node test keeps using the CDN via a custom module hook
(
tests/cdn-hook.mjs) that fetches each jsdelivr URL and rewrites its/npm/...sub-imports to absolute — Node dropped native https imports. No npm, no node_modules. Needs network to run.
Done
assets/js/verify-sig.js—verifyPdf(arrayBuffer)→{ok, checks[], signer, signedAt, reason}. ByteRange regex → reconstruct signed bytes → detached CMSSignedData.verify→ whole-file-coverage check → signer-cert SHA-256 match. ConstantANSHID_CERT_SHA256=7d21c7f4…bf1e(same as the page + pyhanko output).verify.md— copy rewritten (canvas-signature framing, dropped “confirm through a second channel” / “identity unknown” warnings, self-signed note is now a neutral one-time step). New “Check a document here” section with a drag/drop + file-input widget (module<script>inline in the page). File input is visually-hidden (nothidden) so it stays keyboard-focusable;<label for>drives click/keyboard, JS handles drag.<noscript>fallback. Cert block / fingerprints / manual steps unchanged (manual = power-user fallback).tests/verify-sig.test.mjs+cdn-hook.mjs+register-hook.mjs. Covers: genuine PDF passes all 3, appended byte fails coverage only, flipped byte fails signature, different self-signed cert → intact but not Anshid, unsigned → no-sig reason. Run:node --import ./tests/register-hook.mjs tests/verify-sig.test.mjs <signed.pdf> <signed-other.pdf>_includes/footer.html— removed “Verify Signatures”.about.md— added “Verify My Documents” button (shield-check icon) after certifications._config.yml—exclude: tests, node_modules.CLAUDE.md+AGENTS.md— updated the cert bullet (link now from About; refreshANSHID_CERT_SHA256on cert renewal too) + newverify-sig.jsbullet.
State / deployed
- Committed on
main(9c8ffd2), not pushed./verify/widget is live only after push + Pages build. - Test passed locally against a real pyhanko-signed fixture (Anshid’s key) and a throwaway different-cert fixture. Fixtures were in scratchpad, not committed.
- No local Jekyll (no Ruby) —
verify.mdLiquid/kramdown eyeballed against the prior version andcertifications.md.
Open threads / next steps
- Push, then verify at the live URL: drop a real signed course PDF → expect
“✓ Signed by Anshid Aboobacker” + 3 green checks; check the
<noscript>path, the About button, and that the footer no longer lists Verify. - No signed PDFs are committed in this repo yet (the
assets/**/CH*.pdfslides predate signing). If Anshid re-signs and commits those, they become live test material for the widget.
Blockers / watch-outs
- Cert expiry is deliberately not checked. After 2036 the widget will still show ✓ for a document signed while the cert was valid — correct behaviour for provenance (the file was genuinely released by Anshid). Don’t “fix” this.
- On cert renewal: update
assets/anshid-cert.pem/.cer, the fingerprints + dates inverify.md, andANSHID_CERT_SHA256inverify-sig.js. verify-sig.jsruntime-depends on jsdelivr being reachable. If jsdelivr is down the widget errors (caught → “Could not read this file”); the manual methods on the same page still work.- Widget assumes one signature (takes the last
/ByteRange). Fine for Anshid’s single invisible sig; a multi-signed PDF would only report the last signer.