Treat an MD5 or SHA-1 messageImprint as an insecure digest and show the
imprint algorithm in Technical Details.
Ignore Reason, Location, ContactInfo and Name on document timestamps, as
ISO 32000-2 Table 255 requires for ETSI.RFC3161.
The insecure digest status could not be reached because such results are
never valid, so these cards read "Invalid Signature". Show it in the
invalid branch instead.
PDFs whose only signature is an RFC 3161 document timestamp
(/Type /DocTimeStamp, /SubFilter /ETSI.RFC3161) were reported as having
no signatures, because timestamp dictionaries were skipped on purpose.
node-forge also cannot parse a timestamp token directly, since it only
accepts SignedData that wraps plain data.
Timestamps are now extracted and flagged as such. The TSTInfo is read
from the token, and verification checks that its messageImprint matches
the signed byte range and that the TSA signature covers the TSTInfo.
The timestamp's genTime is shown as the date, and the result card uses
timestamp wording.
Fixes#952
Tools that combine several files into a single PDF used the upload
order as the page order, with no way to change it short of clearing
and re-adding files. Each file row now has a drag handle, and the
new order is used when the PDF is built.
Applies to the JPG, PNG, WebP, BMP, HEIC, SVG, TIFF, Image and TXT
to PDF tools. The shared logic lives in utils/sortable-file-list.ts
(built on the existing sortablejs dependency) instead of being
duplicated per page.
The blog is BentoPDF's own content, so generate-blog.mjs and the
hand-written blog pages keep their canonical on www.bentopdf.com in
self-hosted builds too. seo-audit.mjs now expects that official canonical
(and host) for blog/ pages instead of failing every custom-SITE_URL build.
Review follow-up: the origin regex also matched a port
(https://www.bentopdf.com:8443) and userinfo
(https://www.bentopdf.com@other.example), which are different origins;
the lookahead now excludes ':' and '@'. The replacement is a callback,
so `$&`/`$1` in SITE_URL stay literal. Tests cover both.
A build with a custom SITE_URL failed seo-audit on the blog pages:
generate-blog.mjs hard-coded https://www.bentopdf.com, and the
hand-written blog/author-alam.html carries that origin literally while
generate-i18n-pages.mjs only rewrites top-level pages.
generate-blog.mjs now reads SITE_URL like its sibling scripts, and
enhance-seo-pages.mjs rebases the default origin in dist/blog/*.html
to SITE_URL (dist only, no-op for the default URL) through a small
side-effect-free helper, scripts/site-origin.mjs, covered by
src/tests/site-origin.test.ts.
/Type is optional in a signature dictionary (PDF 32000-1, 12.8.1) and some
signers leave it out, so those documents still reported "No Signatures
Found". A dictionary without /Type that carries both /ByteRange and
/Contents is now read as a signature; document timestamps
(/Type /DocTimeStamp, /SubFilter /ETSI.RFC3161) stay out because they hold
an RFC 3161 token, not a CMS signature over the document.
Also covers a /Contents placeholder of 74,506 hex characters, the largest
seen in real signed documents, which the former fixed scan window dropped.
Fixes#831
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>