Skip to content

Add EU DSS (PAdES) signing engine#422

Open
kwart wants to merge 16 commits into
masterfrom
claude/dss-engine
Open

Add EU DSS (PAdES) signing engine#422
kwart wants to merge 16 commits into
masterfrom
claude/dss-engine

Conversation

@kwart

@kwart kwart commented Jun 17, 2026

Copy link
Copy Markdown
Member

Implements the dss signing engine described in design-doc/3.1-engine-dss.md — the first phase-2 engine behind the phase-1 pluggable-engine API. It produces PAdES signatures (ETSI.CAdES.detached) at baseline levels B / T / LT / LTA, which the OpenPDF engine cannot create. The default engine stays openpdf, so nothing changes unless you opt in.

What's included

  • New engines/dss moduleDssSigningEngine (signing flow lifted from jsignpdf-pades onto JSignPdf's BasicSignerOptions/KeyStoreUtils/logging), PrivateKeySignatureToken, DSS enum mappings, a bundled-font helper, and DssTrustConfigurer building the CommonCertificateVerifier from engine.dss.* (fails LT/LTA fast when revocation data is unreachable). Registered via ServiceLoader.
  • Model — backend-neutral net.sf.jsignpdf.types.PadesLevel; new BasicSignerOptions.padesLevel (persisted, copied, in equals/hashCode).
  • CLI-pl / --pades-level (B/T/LT/LTA), plus the matching EngineMismatchValidator rule so unsupported levels fail fast.
  • JavaFX — gated PAdES-level toolbar dropdown and a DSS engine Preferences tab for the engine.dss.* trust keys.
  • TSA hash hardening — the free-text TSA hash algorithm (CLI --tsa-hash-alg, Swing dialog, properties) is now canonicalised in getTsaHashAlgWithFallback(), so a lowercase entry such as sha256 no longer NPEs in iText (setDigestName) or throws in DSS (DigestAlgorithm.forJavaName). Fixes [Question] TSA: org.bouncycastle.asn1.ASN1ObjectIdentifier.<init> 'identifier' cannot be null #126 / One working piece of timestamping settings please #181.
  • Builddss-bom 6.4 import + managed dep in the root pom, the new module in the engines aggregator, a runtime dep in distribution, and licenseMerge entries for the new DSS/JAXB transitive license names.
  • i18n & docs — new messages.properties keys; JSignPdf.adoc (--pades-level row + "PAdES & the DSS engine" section, plus a visible-signature placeholder table calling out ${timestamp} for showing the signing date/time — Show time/date on signature #114), 3.1.0 release notes, and README.

Related issues

Selecting -eng dss (with engine.dss.online.enabled=true or local trust material for LT/LTA) closes the review's LTV-compliance cluster, which the OpenPDF engine structurally cannot satisfy:

Also addressed:

Not covered: #255 (RSASSA-PSS) — the token still produces RSA PKCS#1 v1.5, not PSS.

Verification

  • engines/dss tests pass (signing B / visible signature / LT-fails-offline / capabilities), incl. checkstyle + license check.
  • All jsignpdf tests pass, with added --pades-level cases in the validator / registry / cmdline suites and a new getTsaHashAlgWithFallback canonicalisation regression test.
  • Assembled distribution lib/ contains the DSS jars; --list-engines lists dss; an end-to-end CLI sign with -eng dss -pl B succeeds, and -eng openpdf -pl LTA fails fast with the capability-mismatch message.

Notes

  • Bundling DSS adds ~30 MB+ of jars and puts an LGPL-2.1 dependency on the active signing path when dss is selected (license-compatible with JSignPdf's dual MPL-2.0 / LGPL-2.1).
  • Out of scope (per the design): byte-identical output, certificate-based encryption under DSS, CloudFoxy via DSS, and LTA refresh.

🤖 Generated with Claude Code

@kwart kwart left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Automated review pass. Most findings are correctness gaps in the LT/LTA + networking paths plus a gap between claimed and actual test coverage. Design and model wiring look clean. Inline comments below; a few minor items folded in here:

  • DssTrustConfigurer.buildVerifier swallows trusted-source exceptions (logs WARNING, continues), so a misconfigured truststore/cert proceeds without trust anchors and fails later with an opaque DSS error instead of console.dss.ltNoRevocation.
  • Truststore password is stored plaintext in advanced.properties and bound to a plain TextField (not PasswordField).
  • Visible signature: graphic and background image are mutually exclusive, and GRAPHIC_AND_DESCRIPTION with no graphic path silently uses bgImgPath as the graphic.
  • DssSigningEngineTest relies on BouncyCastle/PDFBox via transitive compile-scope deps rather than declared test deps.
  • Benign scope creep: MainWindowController now gates the whole signatureAppearanceAccordionPane on VISIBLE_SIGNATURE.

Comment thread design-doc/3.1-engine-dss.md Outdated
Comment thread engines/dss/src/main/java/net/sf/jsignpdf/engine/dss/DssSigningEngine.java Outdated
Comment thread engines/dss/src/main/java/net/sf/jsignpdf/engine/dss/DssSigningEngine.java Outdated
Comment thread engines/dss/src/main/java/net/sf/jsignpdf/engine/dss/DssFontUtils.java Outdated
Comment thread engines/dss/src/test/java/net/sf/jsignpdf/engine/dss/DssSigningEngineTest.java Outdated
kwart and others added 5 commits June 18, 2026 09:31
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Wire JSignPdf's HTTP proxy into the DSS TSP/OCSP/CRL/AIA data loaders so
  PROXY_SUPPORT is honoured; SOCKS is reported as unsupported instead of
  silently bypassing the proxy.
- Fail fast with a specific message when level LT/LTA is requested without a
  TSA (LT/LTA build on a signature timestamp), instead of failing deep in DSS.
- Resolve the default port (443/80) from the URI scheme when the TSA URL omits
  an explicit port, so basic-auth credentials match the real connection.
- Document that the encrypt-before-sign 128-bit key length matches the OpenPDF
  engine's password encryption (no strength downgrade).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- DssFontUtils: materialise the font bytes into an InMemoryDocument while the
  stream is open, instead of handing DSS the stream that this method closes on
  return (DSSFileFont re-reads the font lazily in getJavaFont()/getInputStream()).
- DssSigningEngineTest: assert the achieved baseline level via DSS's own
  validator (SimpleReport.getSignatureFormat) rather than only the subfilter,
  which is identical for B/T/LT/LTA. Assert visible-signature placement (page +
  rectangle). Add dss-validation / dss-policy-jaxb as test-scoped deps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Port jsignpdf-pades' EmbeddedTsaServer (an in-JVM RFC 3161 TSA on a loopback
port, backed by a self-signed timestamping cert and BouncyCastle TSP) into the
engine-dss test suite, so level T can be produced and asserted without external
network. Adds:
- baselineBWithTsaUpgradesToT: B + TSA auto-upgrades to and validates as T
- explicitBaselineTWithTsaProducesT: explicit T validates as T

This closes the level-T verification gap; LT/LTA success still needs reachable
revocation/AIA and stays out of the unit suite (the reference project covers
LT/LTA only as no-trust failure cases too).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…fallback, declare test deps

- DssTrustConfigurer.buildVerifier now propagates trust-source load failures
  instead of swallowing them (WARNING + continue). DssSigningEngine catches and
  fails fast with the new console.dss.trustConfigFailed message, so a
  misconfigured truststore/cert/LOTL no longer signs without the intended trust
  anchors and surfaces an opaque DSS error later.
- Visible signature: stop silently using the background image as the signature
  graphic in GRAPHIC_AND_DESCRIPTION mode when no graphic is configured. DSS
  renders a single image; graphic vs background are now cleanly mutually
  exclusive (graphic mode w/o graphic -> text only).
- engines/dss now declares pdfbox (compile; used directly by the engine) and
  bouncycastle (test; BC provider + embedded TSA) explicitly instead of relying
  on transitive compile-scope deps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kwart

kwart commented Jun 18, 2026

Copy link
Copy Markdown
Member Author

Thanks — responses to the five folded-in bullets (fixes in 8df522b):

1. buildVerifier swallowing trusted-source exceptions — fixed. It no longer logs-and-continues. createTrustedCertSources() now propagates, and DssSigningEngine catches it and fails fast with a new specific message console.dss.trustConfigFailed ("Failed to load the configured DSS trust material … Check the engine.dss.trust.* settings."). A misconfigured truststore/cert/LOTL no longer signs without the intended anchors or surfaces an opaque DSS error later.

3. Visible-signature image fallback — fixed. Removed the silent bgImgPath-as-graphic substitution. DSS renders a single image, so the mapping is now cleanly mutually exclusive: GRAPHIC_AND_DESCRIPTION → the graphic (imgPath); other render modes → the background image (bgImgPath); graphic mode with no graphic configured → text only. Documented the single-image limitation in a comment.

4. Test relying on transitive compile-scope deps — fixed. engines/dss now declares pdfbox (compile — the engine itself uses Loader/PDDocument for encrypt-before-sign and page geometry, so it shouldn't lean on dss-pades-pdfbox transitively) and bcprov/bcpkix (test — BC provider + the embedded TSA) explicitly.

2. Truststore password — partly fixed, partly a deliberate boundary call. The input is already a PasswordField (masked) — Preferences.fxml:106; the controller field is typed TextField only because PasswordField extends it. On at-rest storage: it's currently persisted in plaintext, unlike JSignPdf's other secrets which are obfuscated via setEncrypted/EPROPERTY_* and gated behind isStorePasswords(). The catch is that those helpers live in the BasicSignerOptions/Props layer, while the DSS truststore password is a config value in the engine.dss.* advanced namespace, read by the engine through the generic EngineConfig string SPI — encrypting it at rest would force the engine to decrypt and break that clean SPI boundary. Given it guards a truststore of public trust anchors (no private key — the lowest-sensitivity secret here), I left storage as-is pending your call. Options: (a) don't persist it (require re-entry per session), (b) extend the obfuscation mechanism across the engine-config boundary, or (c) accept as-is. Which do you prefer?

5. signatureAppearanceAccordionPane gating — intentional, happy to drop. It's consistent with the sibling capability gates added alongside it (btnTsa, tsaAccordionPane, encryptionAccordionPane, cmbPadesLevel) and is a no-op for both shipping engines (each declares VISIBLE_SIGNATURE); the wiring exists for a future reduced-capability engine, as the adjacent comment notes. If you'd rather keep this PR's UI surface minimal I'll remove just this gate — let me know.

All green after the changes: engines/dss 8/8 (incl. checkstyle + license), jsignpdf 244/244.

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.

[Question] TSA: org.bouncycastle.asn1.ASN1ObjectIdentifier.<init> 'identifier' cannot be null

1 participant