Skip to content

TSA timestamping always fails with JSignPdf: --tsa-hash-algorithm is never passed, SHA-1 is used #8145

Description

@bast69

Describe the bug

When a TSA is configured, signing with the JSignPdf engine always fails against any TSA that
rejects SHA-1 — which is every serious authority today.

JSignPdfHandler::getTsaParameters() passes only --tsa-server-url, --tsa-policy-oid and the
authentication options. It never passes --tsa-hash-algorithm, so JSignPdf falls back to its
default of SHA-1 for the timestamp query. Certum and DigiCert answer HTTP 400.

Note that the admin setting "Signature hash algorithm" (-ha) does not help: it applies to
the document signature, not to the TSA query. Both parameters are independent.

Two things make this hard to diagnose:

  1. The error message blames the network. checkTsaError() triggers on any error containing
    TSAClientBouncyCastle, UnknownHostException or Invalid TSA, and reports
    "Timestamp Authority (TSA) service error. Check TSA endpoint and DNS/network/firewall
    connectivity from this server."
    Connectivity is fine — the request is simply rejected.
  2. The real Java error is never logged. checkTsaError() throws before the
    $this->logger->error(...) line in signWrapper(), so nothing about the actual cause reaches
    nextcloud.log. I only found it by running JSignPdf by hand.

To reproduce

  1. Configure a TSA that refuses SHA-1, e.g. http://time.certum.pl.
  2. Keep the default signature engine (JSignPdf).
  3. Sign any document → failure with the connectivity message above.

Connectivity is not the issue — a plain RFC 3161 exchange from inside the Nextcloud container
succeeds:

$ openssl ts -query -data f.txt -cert -sha256 -out f.tsq
$ curl -s -H "Content-Type: application/timestamp-query" --data-binary @f.tsq http://time.certum.pl -o f.tsr
$ openssl ts -reply -in f.tsr -text | grep Status
Status: Granted.

Running JSignPdf directly shows the real cause:

$ java -jar JSignPdf.jar test.pdf -kst PKCS12 -ksf k.p12 -ksp *** -ha SHA256 \
    --tsa-server-url http://time.certum.pl -d out
INFO Creating TSA client.
INFO Setting TSA hash algorithm: SHA-1
SEVERE Problem occured
ExceptionConverter: java.io.IOException: Server returned HTTP response code: 400 for URL: http://time.certum.pl
    at com.lowagie.text.pdf.TSAClientBouncyCastle.getTSAResponse(TSAClientBouncyCastle.java:288)
    ...

Adding -tsh SHA-256 makes it succeed:

INFO Setting TSA hash algorithm: SHA-256
INFO Finished: Signature succesfully created.

Measured against three TSAs, same document, same key:

TSA SHA-1 (what LibreSign sends today) SHA-256 (-tsh)
Certum http://time.certum.pl HTTP 400 signature created
DigiCert http://timestamp.digicert.com rejected —
FreeTSA https://freetsa.org/tsr accepted —

FreeTSA is the only one still accepting SHA-1, so it is currently the only TSA that works with the
JSignPdf engine. Falling back to it means SHA-1 timestamps from a service with no availability
commitment, which undermines the evidentiary value timestamping is meant to add.

Expected behavior

The timestamp query should use a modern digest (SHA-256 by default), and ideally follow the
configured signature hash algorithm.

Proposed fix

Add the option in getTsaParameters(), defaulting to SHA-256 — the wrapper passes the option
string through unchanged, so no jsignpdf-php change is required:

$params = [
    '--tsa-server-url' => $tsaUrl,
    '--tsa-policy-oid' => $this->appConfig->getValueString(Application::APP_ID, 'tsa_policy_oid', ''),
    '--tsa-hash-algorithm' => $this->appConfig->getValueString(Application::APP_ID, 'tsa_hash_algorithm', 'SHA-256'),
];

Two secondary improvements would have saved a lot of diagnosis time:

  • log the original JSignPdf message before checkTsaError() throws;
  • when the TSA answers HTTP 400, avoid pointing at DNS/firewall — the server was reached.

Relation to #8068

This is independent of the JSignPdf 3.x migration and does not need to wait for it: the option
already exists in JSignPdf 2.3.0 (-tsh, --tsa-hash-algorithm), and the fix lives entirely in
JSignPdfHandler, not in the wrapper.

It is however directly relevant to the checklist in #8068 — "confirm that the current JSignPdf
options still work correctly, including [...] hash algorithm, TSA"
. This is a concrete case where
a TSA option is currently missing rather than merely changed, and it would be worth covering in the
tests added there.

Workaround

Switch the signature engine to PhpNative, whose timestamp provider defaults to sha256
(DefaultTimestampOptionsProvider). Verified working against Certum:

occ config:app:set libresign signature_engine --value=PhpNative

Environment

  • LibreSign 13.3.0
  • Nextcloud 33.0.8 (All-in-One, nextcloud-aio-nextcloud)
  • Signature engine: JSignPdf 2.3.0, Java 21.0.8
  • Certificate engine: OpenSSL, self-signed internal root CA
  • Signature hash algorithm setting: SHA256

Activity

  1. vitormattos commented on Aug 31, 2026

    @vitormattos
    Member

    There is an important detail to consider here.

    In LibreSign 15 / stable35 and also in main branch, TSA settings were migrated to the Policies system. Because of this, this fix should not read tsa_hash_algorithm directly from appConfig. It should be part of the TSA policy settings and exposed together with the other TSA options in the frontend, so it keeps working correctly in multi-tenant scenarios.

    I also checked JSignPdf 2.3.0, which is the version currently used by LibreSign. The report is correct: -tsh / --tsa-hash-algorithm exists and the default is SHA-1 when it is not provided.

    JSignPdfHandler already has logic to resolve the signature hash algorithm. Maybe this logic should be moved to a small dedicated class, with isolated unit tests for both signature and TSA hash behavior. I would not directly reuse the same result for TSA, because the current logic also considers PDF version compatibility and may select SHA-1 for old PDFs.

    The point about logging also makes sense: checkTsaError() can throw before the original JSignPdf error is logged, so this should be reviewed too.

  2. changed the issue type fromtoon Aug 31, 2026
  3. bast69 commented on Aug 31, 2026

    @bast69
    Author

    Thanks for the quick confirmation and for checking JSignPdf 2.3.0 independently.

    On the Policies system — agreed, and I withdraw the appConfig snippet from my report. It was
    written against 13.3.0 (Nextcloud 33), where TSA options live in appConfig; reading
    tsa_hash_algorithm from there would be the wrong shape going forward. Exposing it alongside the
    other TSA options in the policy settings is clearly the right place.

    On not reusing the signature hash for the TSA — that is a subtlety I had not anticipated, and
    it is worth recording in the issue: if the existing resolution can deliberately select SHA-1 for
    older PDF versions, then deriving the TSA hash from it would silently reintroduce the very failure
    reported here for any document that happens to trigger that path. That argues for a genuinely
    separate TSA hash setting rather than a derived value, so a compatibility choice about the document
    cannot leak into the timestamp request.

    Data point: the PhpNative workaround, verified in production

    For anyone landing here before a fix, switching the signature engine works:

    occ config:app:set libresign signature_engine --value=PhpNative
    

    DefaultTimestampOptionsProvider defaults to sha256, so Certum accepts the request. Verified on
    a real signed contract by extracting both signatures from the PDF and inspecting them with
    openssl alone (pdfsig is not available in the Nextcloud AIO image):

    • /SubFilter/adbe.pkcs7.detached + /SubFilter/ETSI.RFC3161 + /DocMDP all present;
    • timestamp token: Status: Granted, Hash Algorithm: sha256, Policy OID: 1.2.616.1.113527.2.5.1.11, signed by CN = Certum Timestamp 2026, O = Asseco Data Systems S.A.;
    • openssl smime -verify against the internal root CA: Verification successful, and flipping a
      single bit in the covered byte range turns it into digest failure, so integrity detection
      behaves as expected.

    One caveat on that workaround, so nobody adopts it blind: PhpNativeHandler renders the
    visible signature stamp with /Type1 /Helvetica and escapePdfText() performs no UTF-8 to WinAnsi
    conversion, so non-ASCII characters in signature_text_template are mangled — a French template
    with Signé / Émetteur came out as Sign^ / ^ metteur. Easy to work around with an ASCII-only
    template, but it means the two engines are not interchangeable in both directions. Happy to open
    that as a separate issue if it is not already tracked.

    One question about the migration

    Does the move to Policies plan to carry over the existing tsa_url / tsa_auth_type /
    tsa_policy_oid appConfig values, or should instances configured on 13.x/14.x expect to
    reconfigure the TSA after upgrading? Asking because a silently dropped tsa_url would produce
    signatures without a timestamp, which is not visible unless the resulting PDF is inspected.

  4. vitormattos commented on Aug 31, 2026

    @vitormattos
    Member

    Thanks for checking this in detail.

    About the migration: yes, the existing TSA configuration is migrated automatically. The migration reads the legacy tsa_url, tsa_policy_oid, tsa_auth_type, tsa_username and tsa_password values and moves them to the new TSA policy configuration. We also have unit tests covering this migration.

    So existing instances should not need to configure the TSA again after the upgrade.

    One important note about LibreSign 15 / Nextcloud 35: some API contracts have changed. If you use the LibreSign API in an external integration, please validate your integration before upgrading.

    About the non-ASCII problem with PhpNativeHandler: this is outside the scope of this TSA issue. Please open a separate issue for it so we can investigate the rendering flow independently.

  5. vitormattos commented on Aug 31, 2026

    @vitormattos
    Member

    Attention!!!

    To clarify for anyone who wishes to work on this issue, here is what needs to be done:

    #8145 (comment)

  6. 7 remaining items

  7. moved this from 0. Backlog to 4. to release in LibreSign Roadmapon Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendBackend workfrontendFrontend workgood first issueGood for newcomersjavascriptWork involving JavaScript codephpWork involving PHP code

    Type

    Fields

    Priority

    High

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions