Skip to content

feat: update JSignPdf to 3.1.0 with jsignpdf-php 3.0.0 - #8256

Merged
vitormattos merged 8 commits into
LibreSign:mainfrom
maia-andre:feat/8068-jsignpdf-3
Sep 5, 2026
Merged

feat: update JSignPdf to 3.1.0 with jsignpdf-php 3.0.0#8256
vitormattos merged 8 commits into
LibreSign:mainfrom
maia-andre:feat/8068-jsignpdf-3

Conversation

@maia-andre

Copy link
Copy Markdown
Contributor

Resolves: #8068

📝 Summary

Updates the JSignPdf signing backend to JSignPdf 3.1.0 through jsignpdf-php 3.0.0 (LibreSign/3rdparty#96). JSignPdf 3.1.0 also uses SHA-256 as the default TSA hash, which is what #8145 is about.

Install. JSignPdf 3.1 dropped the single JSignPdf.jar: the minimal package, meant for headless and command line use, ships a lib/ directory that the wrapper starts through the classpath. InstallService now downloads jsignpdf-3.1.0-minimal.zip, verifies it against the SHA256SUMS file published with the release (instead of a hard-coded md5), and stores the extracted directory in the new jsignpdf_path app config key. The legacy jsignpdf_jar_path key is deleted on install and on uninstall. The release metadata lives in the new JSignPdfRelease class so it can be unit tested. SignSetupService and the setup check read the new key.

Handler. jsignpdf-php 3 escapes every argument itself and takes the options as a list, so JSignPdfHandler stops building shell syntax: the parameters are arrays (addJSignParameters(), one cloned JSignParam per visible element), the signature text is passed as is (no more quoting of " and $), the JVM option -Duser.home and the JSIGNPDF_HOME variable go through setJavaOptions() / setEnvironmentVariables(), and the TSA password goes through setTsaPassword(), which the wrapper writes to the stdin of JSignPdf instead of the command line. The wrapper now reports failure through the exit code instead of the "Finished: Signature succesfully created." message; the error parsing in checkTsaError() / checkHashAlgorithmError() is unchanged.

Tests. JSignPdfHandlerTest asserts the parameters that reach the wrapper (captured from setParam()), with the sign cases as structured providers, a case with shell special characters in the signature text, and a TSA provider covering no TSA, URL only, policy OID, basic authentication with the password over stdin, shell characters in the password, and the two cases with incomplete credentials. JSignPdfReleaseTest covers the download URL, checksum URL and install path. JSignPdfSetupCheckTest and SignSetupServiceTest follow the new key; the setup check gained two cases for mutants that escaped.

While doing this I found that 68 tests of JSignPdfHandlerTest were being skipped as "Certificate initialization failed": generateRootCert() needs a CA identifier to resolve the CRL distribution point and the class never generated one. setUpBeforeClass() now calls CaIdentifierService::generateCaId(), like OpenSslHandlerTest does, and all 96 tests run.

Existing installations. JSignPdf 2.3.0 cannot read passwords from stdin, so it does not work with the new wrapper: after upgrading, occ libresign:install --jsignpdf (or the install button in the admin settings) has to run once. Until then the setup check reports "JSignPdf not found" with that command as the tip, because jsignpdf_path is empty. I kept this explicit instead of downloading 35 MB during occ upgrade; say so if you prefer a repair step. The -a (append) default of the wrapper is a no-op in 3.x, where appending is the default, and LibreSign already rewrites the PDF header before signing, so the 3.x limitation that append mode cannot raise the PDF version for SHA-256 does not apply.

🧪 How to test

  1. occ libresign:install --use-local-cert --java and occ libresign:install --use-local-cert --jsignpdf: the jsignpdf appdata folder gets jsignpdf-3.1.0/lib/*.jar, occ config:app:get libresign jsignpdf_path points to it and jsignpdf_jar_path is gone.
  2. occ libresign:configure:check (or the admin overview) reports JSignPdf version 3.1.0.
  3. Request a signature and sign it (clickToSign is enough). With a TSA configured with basic authentication, the password no longer appears in the JSignPdf command line (ps while signing, or the --enable-stdin-passwords -ksp - -tsp - in the command).
  4. composer run test:unit -- tests/php/Unit/Handler/SignEngine/JSignPdfHandlerTest.php: 96 tests, none skipped.

⚙️ API / Back‑end changes

  • InstallService, SignSetupService, JSignPdfSetupCheck, JSignPdfHandler, new JSignPdfRelease; new app config key jsignpdf_path (replaces jsignpdf_jar_path)
  • Unit and/or integration tests added
  • Capabilities updated (not applicable)
  • Documentation updated (not applicable)
  • API documentation updated with composer openapi (no API change)

🚧 Tasks

✅ Checklist

  • I have read and followed the contribution guide.
  • Validation (PHP 8.3 devcontainer):
    • PHPUnit: JSignPdfHandlerTest 107/107 (68 of them used to be skipped), JSignPdfReleaseTest 6/6, JSignPdfSetupCheckTest 11/11, SignSetupServiceTest 23/23; whole unit suite 3600 tests, the only 4 failures are in AEngineHandlerTest and pass when the class runs alone (state leaking between classes, unrelated to this change).
    • Infection on the lines changed against main (--git-diff-lines): 68 mutants, 68 killed, 100% coverage and 100% MSI. Per class: JSignPdfRelease 32/32, JSignPdfSetupCheck 33/33. InstallService has no unit coverage, as before; the install path is exercised by the behat and Playwright jobs (libresign:install --jsignpdf) and by the manual run below.
    • Psalm and php-cs-fixer clean on the changed files.
    • End to end: occ libresign:install --use-local-cert --jsignpdf downloaded jsignpdf-3.1.0-minimal.zip, verified it and set jsignpdf_path to .../x86_64/jsignpdf/jsignpdf-3.1.0 (76 jars in lib/, legacy key gone); java -classpath 'lib/*' com.intoolswetrust.jsignpdf.Bootstrap --version prints JSignPdf version 3.1.0; a signature request signed with clickToSign returned 200 and the validation endpoint reports the signature as adbe.pkcs7.detached, RSA-SHA256, valid.

🤖 AI

  • The content of this PR was partially or fully generated using AI

Assisted by Claude Code (claude-fable-5-1); I reviewed and tested every change.

JSignPdf 3.x features worth a look (not implemented here)

  • Pluggable signing engines (--list-engines, -eng): OpenPDF stays the default; the new EU DSS engine produces PAdES signatures at the ETSI baseline levels B, T, LT and LTA, which OpenPDF cannot create.
  • Passwords over stdin (--enable-stdin-passwords): used by this pull request through the wrapper.
  • --overwrite replaces existing signatures instead of appending. LibreSign relies on appending, so nothing to do, but it is the switch to stay away from.
  • SHA-256 as the default hash, TSA included: --tsa-hash-algorithm defaults to AppConfig.defaultTsaHashAlg() = SHA-256, which fixes the SHA-1 failure of TSA timestamping always fails with JSignPdf: --tsa-hash-algorithm is never passed, SHA-1 is used #8145; a configurable TSA hash in the TSA policy stays as the follow-up discussed there.
  • 3.2 (beta): sign into an existing signature field (--sig-field, --list-sig-fields), the feature the issue points at for pre-placed signature boxes; a debug output logging the certificate chain and every AIA/CRL/OCSP request, useful for TSA diagnostics; buffering.mode=temp to sign very large PDFs without a bigger heap.
  • Checksums for every artifact (SHA256SUMS.txt): used by the installer here.

Points the submodule at LibreSign/3rdparty#96, which bumps
jsignpdf/jsignpdf-php from 1.3.1 to 3.0.0 for JSignPdf 3.1.0. The pointer
will move to the merge commit once that pull request lands.

Signed-off-by: André Maia <andrefnkmm@gmail.com>
Assisted-by: Claude Code:claude-fable-5-1
JSignPdf 3.1 dropped the single JSignPdf.jar. The minimal package, meant
for headless use, ships a lib/ directory that the wrapper starts through
the classpath, so the configured path becomes the extracted directory,
stored in the new jsignpdf_path key. The legacy jsignpdf_jar_path key is
removed on install and on uninstall, and the archive is verified against
the SHA256SUMS file published with the release instead of a hard-coded
md5. The release metadata lives in JSignPdfRelease so it can be unit
tested; SignSetupService and the setup check read the new key.

Signed-off-by: André Maia <andrefnkmm@gmail.com>
Assisted-by: Claude Code:claude-fable-5-1
…wrapper

jsignpdf-php 3 escapes every argument itself and takes the options as a
list, so the handler stops building shell syntax: the parameters are
arrays, one cloned JSignParam per visible element, the signature text is
passed as is instead of quoted, the JVM option and the JSIGNPDF_HOME
variable go through their own setters, and the TSA password goes through
setTsaPassword(), which the wrapper writes to the stdin of JSignPdf
instead of the command line. The wrapper also renamed the jar path to a
directory path and reports failures through the exit code.

The tests assert the parameters that reach the wrapper, captured from
setParam(), with structured providers, a case with shell special
characters in the signature text and a TSA provider that checks the
password never reaches the command line.

Signed-off-by: André Maia <andrefnkmm@gmail.com>
Assisted-by: Claude Code:claude-fable-5-1
…ertificate

generateRootCert() resolves the CRL distribution point from the CA
identifier. Without one the certificate generation failed and 68 tests
of this class were skipped as "Certificate initialization failed".

Signed-off-by: André Maia <andrefnkmm@gmail.com>
Assisted-by: Claude Code:claude-fable-5-1

@vitormattos vitormattos left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Excellent work!
I think that only have a small improvement and small changes to make psalm happy.

Comment thread tests/php/Unit/SetupCheck/JSignPdfSetupCheckTest.php Outdated
@github-project-automation github-project-automation Bot moved this from 0. Backlog to 1. to do in Roadmap Sep 5, 2026
@vitormattos

Copy link
Copy Markdown
Member

@maia-andre this PR is blocking the CI because I already made the merge at 3rdparty. I'll need to go ahead with the small changes to merge this. After the merge, will be pending the backport flow.

Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
@vitormattos

Copy link
Copy Markdown
Member

/backport to stable35

@vitormattos

Copy link
Copy Markdown
Member

/backport to stable34

@vitormattos

Copy link
Copy Markdown
Member

/backport to stable33

@vitormattos
vitormattos merged commit 2bf96d2 into LibreSign:main Sep 5, 2026
68 checks passed
@github-project-automation github-project-automation Bot moved this from 1. to do to 4. to release in Roadmap Sep 5, 2026
@vitormattos vitormattos added this to the Next Major (36) milestone Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 4. to release

Development

Successfully merging this pull request may close these issues.

Update JSignPdf to 3.x

2 participants