Skip to content

Don't refuse to sign when append mode can't bump the declared PDF version - #467

Merged
kwart merged 1 commit into
masterfrom
fix/append-mode-pdf-version
Jul 26, 2026
Merged

Don't refuse to sign when append mode can't bump the declared PDF version#467
kwart merged 1 commit into
masterfrom
fix/append-mode-pdf-version

Conversation

@kwart

@kwart kwart commented Jul 26, 2026

Copy link
Copy Markdown
Member

Signing a PDF whose declared version is older than the one nominally associated with the chosen hash algorithm — e.g. SHA-256 on a PDF-1.3 document, which is what demo/service-agreement.pdf is — aborted the run in append mode:

INFO Creating signature
INFO The chosen hash algorithm (SHA-256) requires a newer PDF version (PDF-1.6) than the
original (PDF-1.3). The PDF version update is impossible in the "append" signature mode. ...

Why the gate goes

The SHA-1 → 1.3, SHA-256 → 1.6, SHA-384/SHA-512/RIPEMD160 → 1.7 table is a correct citation of Adobe's Digital Signatures in a PDF and the /DigestMethod notes in ISO 32000-1, but it doesn't justify refusing to sign:

  • The declared version is a declaration, not a capability gate. The digest lives inside the CMS blob; nothing about the signature bytes depends on it. Acrobat has validated SHA-256 since version 7 (2005) regardless of the declared version, as do Foxit, poppler, PDFBox and the EU DSS validators.
  • The table primarily governs the /DigestMethod entry, which is only meaningful for adbe.x509.rsa_sha1. JSignPdf writes adbe.pkcs7.detached, so no version-tagged dictionary entry is emitted at all.
  • The check was already inconsistent within the product: DssSigningEngine has no equivalent, so the same document signed fine through DSS and hard-failed through OpenPDF.

The failure it prevented was cosmetic; the failure it caused was total. Now it logs a WARNING and signs.

Not changed: writing a catalog /Version override to keep the declaration honest. It's feasible (PdfStamper.markUsed is public), but DSS's PdfObjectModificationsFilter.isVersionChange whitelists /Version only when the action type is MODIFICATION. Adding the key to a document that lacks one is a CREATION, which falls through to "undefined changes" — and that's precisely the case where we'd want to write it. Not worth risking how a pre-existing signature gets classified for a version number no validator consults.

Two defects fixed in the same block

  • console.updateVersion rendered garbage. The message was Updating PDF version info 1.{0} -> 1.{1} while the caller already passed full version strings, producing "Updating PDF version info 11.3 -> 11.6". A leftover from OpenPDF 1.x, where getPdfVersion() returned a bare digit. Corrected in all 19 locales; the arguments now pass version names so both messages read alike.
  • NPE on PDF-1.0/1.1 input. PdfVersion.fromStringVersion() returns null outside 1.2–1.7, so .getVersionName() threw — on exactly the old documents this branch targets. Now falls back to the raw version string.

Messages

console.updateVersionNotPossibleInAppendModeForGivenHash is replaced by console.appendModeVersionNotUpdated; the old text told users to disable append mode or pick another algorithm, which is no longer the remedy. The obsolete key is removed from all locales so no translation keeps showing the old "impossible" wording, along with some stale commented-out copies of a related key.

Verification

Append mode, SHA-256, on the PDF-1.3 demo document — previously a hard failure:

WARNING The chosen hash algorithm (SHA-256) is declared for PDF-1.6 and newer, but the
original document declares PDF-1.3. The declared version cannot be updated in the "append"
signature mode, so the document keeps declaring PDF-1.3. This is a conformance detail only -
the signature itself is not affected. ...
INFO Finished: Signature succesfully created.

pdfsig on that output confirms the premise:

  - Signing Hash Algorithm: SHA-256
  - Total document signed
  - Signature Validation: Signature is Valid.

A SHA-256 signature in a document still declaring PDF-1.3 validates fine. (Certificate issuer is unknown is just the self-signed demo cert.)

Non-append (--overwrite) path: logs Updating PDF version info PDF-1.3 -> PDF-1.6 and the output header really is %PDF-1.6.

mvn test on engines/api + engines/openpdf: 23 tests, 0 failures.

🤖 Generated with Claude Code

…sion

Signing a PDF whose declared version is older than the one nominally
associated with the chosen hash algorithm (e.g. SHA-256 on a PDF-1.3
document) aborted in append mode, because OpenPDF cannot rewrite the
header version of an incremental update.

The declared version has no bearing on the signature: the digest lives
inside the CMS blob, no /DigestMethod entry is written for
adbe.pkcs7.detached, and validators accept SHA-256 regardless of the
declared version. The DSS engine has never had an equivalent check, so
the same input signed fine there and failed here. Log a warning and
carry on instead of aborting.

Also fixes two defects in the same block:

- console.updateVersion was "... 1.{0} -> 1.{1}" while the caller
  already passed full version strings, rendering "1 1.3 -> 11.6". A
  leftover from OpenPDF 1.x, where getPdfVersion() returned a bare
  digit. Corrected in all locales; arguments now pass version names so
  both messages read alike.

- PdfVersion.fromStringVersion() returns null outside 1.2-1.7, so a
  PDF-1.0 or PDF-1.1 input hit an NPE on getVersionName() - exactly the
  documents this branch targets. Falls back to the raw version string.

console.updateVersionNotPossibleInAppendModeForGivenHash is replaced by
console.appendModeVersionNotUpdated; the old text told users to disable
append mode or pick another algorithm, which is no longer the remedy.
Removed from all locales so no translation keeps showing the old
wording, along with some stale commented-out copies of a related key.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kwart
kwart merged commit 863de54 into master Jul 26, 2026
1 check passed
@kwart
kwart deleted the fix/append-mode-pdf-version branch July 26, 2026 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant