Visitar URL original
Don't decrypt the /Contents of signature dictionaries by wleeaf · Pull Request #22335 · mozilla/pdf.js · GitHub
Skip to content

Don't decrypt the /Contents of signature dictionaries - #22335

Open
wleeaf wants to merge 2 commits into
mozilla:masterfrom
wleeaf:signature-contents-unencrypted
Open

wleeaf wants to merge 2 commits into
mozilla:masterfrom
wleeaf:signature-contents-unencrypted

Conversation

@wleeaf

@wleeaf wleeaf commented Oct 26, 2026 •

Copy link
Copy Markdown

In an encrypted PDF, the parser decrypts every string, including the /Contents of signature dictionaries. That value isn't encrypted, though: the signature is written into the space reserved for it only after the file has been encrypted. As a result, the pkcs7 data that getSignatureData returns for an encrypted signed PDF is garbage, and the signature can't be verified.

issue17069.pdf from the test suite is an example. On master, its PKCS#7 data starts with c9 1a 1e 12, and openssl cms -verify -binary -inform DER -noverify can't parse it ("wrong tag"). The same happens with qpdf's minimal-signed-restricted.pdf, which is signed by Adobe Acrobat Sign. With this patch, the data of both files starts with the DER header 30 82, and openssl reports "CMS Verification successful" against the signed byte ranges that getSignatureData returns.

PDFBox and PDFium don't decrypt this value either. With this patch, getSignatureData reads the /Contents value of an encrypted PDF from the file, in the gap between the two /ByteRange spans, where it's stored unencrypted, and falls back to the parsed value if the gap doesn't hold a single string. The parser, the writer and unencrypted documents are unchanged.

The new api_spec.js test reads the signature of issue17069.pdf and checks the length and the DER header of its PKCS#7 data. It fails without this change.

The signature is written into an encrypted file after the encryption, hence its value isn't encrypted. Decrypting it broke the PKCS#7 data returned by `getSignatureData` for encrypted PDFs.

@Snuffleupagus Snuffleupagus left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, but as-is this looks more like a "hacky" work-around rather than a proper solution.

Instead of special-casing signature dictionaries in the parser and the
writer, `getSignatureData` now reads the /Contents value of an encrypted
PDF from the gap between the two /ByteRange spans, where it's stored
unencrypted, and falls back to the parsed value if the gap doesn't hold a
single string.
@wleeaf

wleeaf commented Oct 26, 2026

Copy link
Copy Markdown
Author

I've reworked it: parser.js and writer.js are back to master, and the fix is now in getSignatureData. For an encrypted document it reads the signature value from the file, in the gap between the two /ByteRange spans. That's where the unencrypted <hex> string is stored, and where signature verifiers read it from. If that gap doesn't hold exactly one string, it falls back to the parsed /Contents as before. Unencrypted documents are unaffected, and the issue17069.pdf test still checks the DER header and length (29207c6).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants