From 75e19a1f1e48d5037e8da7cf5818b8a96d61d906 Mon Sep 17 00:00:00 2001 From: "Josef (kwart) Cacek" Date: Sun, 28 Jun 2026 16:44:53 +0200 Subject: [PATCH 1/6] Fix #432: make PAdES LT/LTA work with the DSS engine (EU LOTL) Wire the EU List of Trusted Lists correctly so LT/LTA can embed revocation data and resolve trust anchors: - DssTrustConfigurer: build a proper European LOTL (URL + bundled Official Journal keystore + OJ announcement predicate + pivot support), fixing the null-URL NPE and the missing LOTL-signature material. Custom trust.lotlUrls entries are wired with the OJ cert source + pivot; blank URLs are guarded. - Bundle the OJ keystore (dss-demonstrations 6.4+20260415) as a classpath resource, overridable via trust.eu.ojKeystoreFile/ojKeystorePassword/ojUrl and trust.eu.lotlUrl. Refresh scripts + README under engines/dss. - Group EU LOTL keys under trust.eu.* and rename trust.useDefaultLotl -> trust.eu.enabled (config, GUI label keys, FXML, prefs VM/controller). - Add DssLtTrustPreflight: CLI fails fast and the GUI offers to enable the online + trust prerequisites when LT/LTA is selected but not configured. - Offline unit tests for the LOTL wiring and the preflight predicate. Defaults stay opt-in (online + EU LOTL both false). Co-Authored-By: Claude Opus 4.8 --- design-doc/3.1-engine-dss.md | 26 +++- .../jsignpdf/conf/advanced.default.properties | 16 ++- .../jsignpdf/translations/messages.properties | 9 +- .../translations/messages_cs.properties | 2 +- .../translations/messages_de.properties | 2 +- .../translations/messages_el.properties | 2 +- .../translations/messages_es.properties | 2 +- .../translations/messages_fr.properties | 2 +- .../translations/messages_hr.properties | 2 +- .../translations/messages_hu.properties | 2 +- .../translations/messages_hy.properties | 2 +- .../translations/messages_it.properties | 2 +- .../translations/messages_ja.properties | 2 +- .../translations/messages_nb-NO.properties | 2 +- .../translations/messages_pl.properties | 2 +- .../translations/messages_pt.properties | 2 +- .../translations/messages_ru.properties | 2 +- .../translations/messages_sk.properties | 2 +- .../translations/messages_ta.properties | 2 +- .../translations/messages_zh_CN.properties | 2 +- .../translations/messages_zh_TW.properties | 2 +- .../engine/dss/DssTrustConfigurer.java | 103 ++++++++++++-- .../jsignpdf/engine/dss/README-oj-keystore.md | 27 ++++ .../sf/jsignpdf/engine/dss/eu-oj-keystore.p12 | Bin 0 -> 13942 bytes .../build-oj-keystore-from-live-lotl.sh | 51 +++++++ .../fetch-oj-keystore-from-dss-demos.sh | 39 ++++++ .../engine/dss/DssTrustConfigurerTest.java | 124 +++++++++++++++++ .../java/net/sf/jsignpdf/SignerLogic.java | 17 +++ .../jsignpdf/engine/DssLtTrustPreflight.java | 75 ++++++++++ .../fx/preferences/PreferencesController.java | 4 +- .../fx/preferences/PreferencesViewModel.java | 10 +- .../fx/view/MainWindowController.java | 58 ++++++++ .../net/sf/jsignpdf/fx/view/Preferences.fxml | 2 +- .../engine/DssLtTrustPreflightTest.java | 130 ++++++++++++++++++ 34 files changed, 687 insertions(+), 40 deletions(-) create mode 100644 engines/dss/src/main/resources/net/sf/jsignpdf/engine/dss/README-oj-keystore.md create mode 100644 engines/dss/src/main/resources/net/sf/jsignpdf/engine/dss/eu-oj-keystore.p12 create mode 100755 engines/dss/src/main/scripts/build-oj-keystore-from-live-lotl.sh create mode 100755 engines/dss/src/main/scripts/fetch-oj-keystore-from-dss-demos.sh create mode 100644 engines/dss/src/test/java/net/sf/jsignpdf/engine/dss/DssTrustConfigurerTest.java create mode 100644 jsignpdf/src/main/java/net/sf/jsignpdf/engine/DssLtTrustPreflight.java create mode 100644 jsignpdf/src/test/java/net/sf/jsignpdf/engine/DssLtTrustPreflightTest.java diff --git a/design-doc/3.1-engine-dss.md b/design-doc/3.1-engine-dss.md index e4ac339f..3c850b86 100644 --- a/design-doc/3.1-engine-dss.md +++ b/design-doc/3.1-engine-dss.md @@ -381,8 +381,16 @@ Read by the engine through the phase-1 `EngineConfig` view (which already maps # Required for producing LT/LTA. Default false to keep B/T fully offline. engine.dss.online.enabled=false -# Trusted-list (LOTL) support for building the trust anchor set. -engine.dss.trust.useDefaultLotl=false +# EU LOTL support for building the trust anchor set. trust.eu.enabled wires the +# bundled European LOTL and validates its signature against the bundled Official +# Journal (OJ) keystore; the eu.* overrides are optional (effective when enabled). +engine.dss.trust.eu.enabled=false +engine.dss.trust.eu.lotlUrl= +engine.dss.trust.eu.ojUrl= +engine.dss.trust.eu.ojKeystoreFile= +engine.dss.trust.eu.ojKeystorePassword= + +# Generic / advanced trust material. engine.dss.trust.lotlUrls= engine.dss.trust.certFiles= engine.dss.trust.certUrls= @@ -391,6 +399,20 @@ engine.dss.trust.truststoreType= engine.dss.trust.truststorePassword= ``` +EU LOTL keys live under the `trust.eu.*` sub-namespace (the turnkey path that +validates the LOTL signature against the bundled OJ keystore and follows the OJ +pivot chain); `trust.lotlUrls` and the cert/truststore keys stay un-prefixed as +the generic "bring your own trust" path. The bundled OJ keystore +(`engines/dss/src/main/resources/.../eu-oj-keystore.p12`) must be refreshed when +the OJ re-issues the LOTL signing certificates — see the keystore README. + +**LT/LTA preflight (issue #432).** Because LT/LTA only embed revocation data for a +*trusted* chain reachable *online*, a missing trust source or `online.enabled` +makes DSS fail deep with an opaque untrusted-chain alert. `DssLtTrustPreflight` +checks `online.enabled AND (trust.eu.enabled ∨ truststoreFile ∨ certFiles ∨ +certUrls ∨ lotlUrls)` up front: the CLI fails fast with the exact keys to set, and +the GUI offers to enable the prerequisites before signing. + `EngineConfig` currently exposes `getString` / `getBoolean` / `getInt`; list- valued keys (`lotlUrls`, `certFiles`, `certUrls`) are parsed by the engine from a delimiter-separated string, so no `EngineConfig` API change is required. (If a diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/conf/advanced.default.properties b/engines/api/src/main/resources/net/sf/jsignpdf/conf/advanced.default.properties index 2a37cef5..ac964f5f 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/conf/advanced.default.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/conf/advanced.default.properties @@ -59,9 +59,19 @@ tsa.hashAlgorithm=SHA-256 # Fetch revocation data (OCSP/CRL) and intermediate certs (AIA) online. # Required to produce LT/LTA. Default false keeps B/T fully offline. engine.dss.online.enabled=false -# Use the default EU List Of Trusted Lists (LOTL) to build the trust anchors. -engine.dss.trust.useDefaultLotl=false -# Comma-separated lists of custom trust material. +# Use the bundled EU List Of Trusted Lists (LOTL) to build the trust anchors. +# Validates the LOTL signature against the bundled Official Journal (OJ) keystore. +engine.dss.trust.eu.enabled=false +# Optional overrides for the EU LOTL machinery (effective when trust.eu.enabled=true): +# eu.lotlUrl - relocate the default EU LOTL (default https://ec.europa.eu/tools/lotl/eu-lotl.xml) +# eu.ojUrl - Official Journal scheme-information URL for the announcement predicate +# eu.ojKeystoreFile - external OJ keystore overriding the bundled one +# eu.ojKeystorePassword +engine.dss.trust.eu.lotlUrl= +engine.dss.trust.eu.ojUrl= +engine.dss.trust.eu.ojKeystoreFile= +engine.dss.trust.eu.ojKeystorePassword= +# Comma-separated lists of custom trust material (advanced). engine.dss.trust.lotlUrls= engine.dss.trust.certFiles= engine.dss.trust.certUrls= diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages.properties index 86873ff4..e0ce3be6 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages.properties @@ -38,6 +38,9 @@ console.dss.tsaUpgrade=A TSA is configured, upgrading the PAdES level B to T (so console.dss.ltNoRevocation=The PAdES level LT/LTA needs revocation data, but online fetching is disabled (engine.dss.online.enabled=false) or no trust material is reachable. Signing aborted. console.dss.ltNoTsa=The PAdES level LT/LTA builds on a signature timestamp, but no TSA is configured. Enable timestamping (-ts / TSA settings) or choose level B. Signing aborted. console.dss.trustConfigFailed=Failed to load the configured DSS trust material (truststore / certificate file / certificate URL / LOTL). Check the engine.dss.trust.* settings. Signing aborted. +console.dss.ltPreflightFailed=The PAdES level LT/LTA needs reachable revocation data and a trusted certificate chain, but the DSS engine is not configured for it. Signing aborted. +console.dss.ltPreflight.online=Enable online fetching of revocation data: set engine.dss.online.enabled=true. +console.dss.ltPreflight.trust=Configure a trust source: set engine.dss.trust.eu.enabled=true (EU LOTL), or provide engine.dss.trust.truststoreFile / certFiles / certUrls / lotlUrls. console.dss.socksProxyUnsupported=The DSS engine does not support SOCKS proxies; OCSP/CRL/AIA/TSA requests will bypass the proxy. Configure an HTTP proxy to route DSS revocation/timestamp traffic. console.dss.unsupportedHash=The DSS engine does not support the hash algorithm ''{0}'' for PAdES. Use SHA-256, SHA-384 or SHA-512. console.dss.cannotEncryptSigned=Cannot encrypt a PDF that already contains signatures. @@ -296,7 +299,7 @@ jfx.gui.padesLevel.help=PAdES baseline level for the produced signature. Only av jfx.gui.dss.section.label=DSS engine jfx.gui.dss.online.enabled=Fetch revocation data online (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=Required to produce LT/LTA. When off, the DSS engine works fully offline (B/T only). -jfx.gui.dss.trust.useDefaultLotl=Use the default EU List of Trusted Lists (LOTL) +jfx.gui.dss.trust.eu.enabled=Use the default EU List of Trusted Lists (LOTL) jfx.gui.dss.trust.lotlUrls=LOTL URLs (comma-separated) jfx.gui.dss.trust.certFiles=Trusted certificate files (comma-separated) jfx.gui.dss.trust.certUrls=Trusted certificate URLs (comma-separated) @@ -424,6 +427,10 @@ jfx.gui.dialog.resetSettings.title=Reset Settings jfx.gui.dialog.resetSettings.text=This will delete all saved settings and reset the application to its default state. Continue? jfx.gui.dialog.portalFallback.title=File dialog jfx.gui.dialog.portalFallback.text=Could not open the system file dialog. Using the built-in one instead. +jfx.gui.dialog.ltPreflight.title=LT/LTA requires online trust +jfx.gui.dialog.ltPreflight.text=The PAdES level LT/LTA needs online fetching of revocation data and a trusted certificate chain (EU LOTL). These are currently disabled, so signing would fail. Enable them now (online fetching and the EU LOTL), or sign anyway. +jfx.gui.dialog.ltPreflight.enable=Enable and sign +jfx.gui.dialog.ltPreflight.anyway=Sign anyway jfx.gui.status.settingsReset=Settings have been reset to defaults jfx.gui.console.title=Output Console jfx.gui.console.clear=Clear diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_cs.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_cs.properties index 3520f3b8..1d240ecd 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_cs.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_cs.properties @@ -271,7 +271,7 @@ jfx.gui.padesLevel.help=Základní úroveň PAdES pro vytvářený podpis. Dostu jfx.gui.dss.section.label=Engine DSS jfx.gui.dss.online.enabled=Stahovat data o zneplatnění online (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=Nutné pro vytvoření LT/LTA. Při vypnutí pracuje engine DSS plně offline (pouze B/T). -jfx.gui.dss.trust.useDefaultLotl=Použít výchozí seznam důvěryhodných seznamů EU (LOTL) +jfx.gui.dss.trust.eu.enabled=Použít výchozí seznam důvěryhodných seznamů EU (LOTL) jfx.gui.dss.trust.lotlUrls=URL adresy LOTL (oddělené čárkou) jfx.gui.dss.trust.certFiles=Soubory důvěryhodných certifikátů (oddělené čárkou) jfx.gui.dss.trust.certUrls=URL adresy důvěryhodných certifikátů (oddělené čárkou) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_de.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_de.properties index a8a532fb..69907b14 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_de.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_de.properties @@ -166,7 +166,7 @@ jfx.gui.padesLevel.help=PAdES-Baseline-Stufe für die erzeugte Signatur. Nur fü jfx.gui.dss.section.label=DSS-Engine jfx.gui.dss.online.enabled=Sperrdaten online abrufen (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=Erforderlich zum Erzeugen von LT/LTA. Wenn deaktiviert, arbeitet die DSS-Engine vollständig offline (nur B/T). -jfx.gui.dss.trust.useDefaultLotl=Die Standard-EU-Liste der Vertrauenslisten (LOTL) verwenden +jfx.gui.dss.trust.eu.enabled=Die Standard-EU-Liste der Vertrauenslisten (LOTL) verwenden jfx.gui.dss.trust.lotlUrls=LOTL-URLs (durch Komma getrennt) jfx.gui.dss.trust.certFiles=Vertrauenswürdige Zertifikatsdateien (durch Komma getrennt) jfx.gui.dss.trust.certUrls=Vertrauenswürdige Zertifikats-URLs (durch Komma getrennt) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_el.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_el.properties index ecb13a38..82080ff6 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_el.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_el.properties @@ -271,7 +271,7 @@ jfx.gui.padesLevel.help=Βασικό επίπεδο PAdES για την παρα jfx.gui.dss.section.label=Μηχανή DSS jfx.gui.dss.online.enabled=Λήψη δεδομένων ανάκλησης μέσω διαδικτύου (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=Απαιτείται για την παραγωγή LT/LTA. Όταν είναι απενεργοποιημένο, η μηχανή DSS λειτουργεί πλήρως εκτός σύνδεσης (μόνο B/T). -jfx.gui.dss.trust.useDefaultLotl=Χρήση της προεπιλεγμένης λίστας έμπιστων λιστών της ΕΕ (LOTL) +jfx.gui.dss.trust.eu.enabled=Χρήση της προεπιλεγμένης λίστας έμπιστων λιστών της ΕΕ (LOTL) jfx.gui.dss.trust.lotlUrls=URL LOTL (διαχωρισμένα με κόμμα) jfx.gui.dss.trust.certFiles=Αρχεία έμπιστων πιστοποιητικών (διαχωρισμένα με κόμμα) jfx.gui.dss.trust.certUrls=URL έμπιστων πιστοποιητικών (διαχωρισμένα με κόμμα) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_es.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_es.properties index 376142f1..5c16c572 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_es.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_es.properties @@ -261,7 +261,7 @@ jfx.gui.padesLevel.help=Nivel base PAdES para la firma producida. Solo disponibl jfx.gui.dss.section.label=Motor DSS jfx.gui.dss.online.enabled=Obtener datos de revocación en línea (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=Necesario para producir LT/LTA. Si está desactivado, el motor DSS funciona totalmente sin conexión (solo B/T). -jfx.gui.dss.trust.useDefaultLotl=Usar la lista de listas de confianza de la UE predeterminada (LOTL) +jfx.gui.dss.trust.eu.enabled=Usar la lista de listas de confianza de la UE predeterminada (LOTL) jfx.gui.dss.trust.lotlUrls=URL de LOTL (separadas por comas) jfx.gui.dss.trust.certFiles=Archivos de certificados de confianza (separados por comas) jfx.gui.dss.trust.certUrls=URL de certificados de confianza (separadas por comas) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_fr.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_fr.properties index 6cfdc89e..6bb660dc 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_fr.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_fr.properties @@ -257,7 +257,7 @@ jfx.gui.padesLevel.help=Niveau de base PAdES pour la signature produite. Disponi jfx.gui.dss.section.label=Moteur DSS jfx.gui.dss.online.enabled=Récupérer les données de révocation en ligne (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=Requis pour produire LT/LTA. Désactivé, le moteur DSS fonctionne entièrement hors ligne (B/T uniquement). -jfx.gui.dss.trust.useDefaultLotl=Utiliser la liste des listes de confiance de l'UE par défaut (LOTL) +jfx.gui.dss.trust.eu.enabled=Utiliser la liste des listes de confiance de l'UE par défaut (LOTL) jfx.gui.dss.trust.lotlUrls=URL LOTL (séparées par des virgules) jfx.gui.dss.trust.certFiles=Fichiers de certificats de confiance (séparés par des virgules) jfx.gui.dss.trust.certUrls=URL de certificats de confiance (séparées par des virgules) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_hr.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_hr.properties index d7fd37cf..5a9b205d 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_hr.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_hr.properties @@ -224,7 +224,7 @@ jfx.gui.padesLevel.help=Osnovna razina PAdES za stvoreni potpis. Dostupno samo z jfx.gui.dss.section.label=Pogon DSS jfx.gui.dss.online.enabled=Dohvati podatke o opozivu putem mreže (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=Potrebno za stvaranje LT/LTA. Kad je isključeno, pogon DSS radi potpuno izvanmrežno (samo B/T). -jfx.gui.dss.trust.useDefaultLotl=Upotrijebi zadani popis pouzdanih popisa EU-a (LOTL) +jfx.gui.dss.trust.eu.enabled=Upotrijebi zadani popis pouzdanih popisa EU-a (LOTL) jfx.gui.dss.trust.lotlUrls=URL-ovi LOTL (odvojeni zarezom) jfx.gui.dss.trust.certFiles=Datoteke pouzdanih certifikata (odvojene zarezom) jfx.gui.dss.trust.certUrls=URL-ovi pouzdanih certifikata (odvojeni zarezom) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_hu.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_hu.properties index 35c74783..c7da606f 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_hu.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_hu.properties @@ -406,7 +406,7 @@ jfx.gui.padesLevel.help=Az előállított aláírás PAdES-alapszintje. Csak PAd jfx.gui.dss.section.label=DSS-motor jfx.gui.dss.online.enabled=Visszavonási adatok online lekérése (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=Az LT/LTA előállításához szükséges. Kikapcsolva a DSS-motor teljesen offline működik (csak B/T). -jfx.gui.dss.trust.useDefaultLotl=Az alapértelmezett EU-s megbízhatósági listák listájának (LOTL) használata +jfx.gui.dss.trust.eu.enabled=Az alapértelmezett EU-s megbízhatósági listák listájának (LOTL) használata jfx.gui.dss.trust.lotlUrls=LOTL-URL-ek (vesszővel elválasztva) jfx.gui.dss.trust.certFiles=Megbízható tanúsítványfájlok (vesszővel elválasztva) jfx.gui.dss.trust.certUrls=Megbízható tanúsítvány-URL-ek (vesszővel elválasztva) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_hy.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_hy.properties index 72fc60f5..d817cb3c 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_hy.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_hy.properties @@ -394,7 +394,7 @@ jfx.gui.padesLevel.help=Ստեղծվող ստորագրության PAdES բազ jfx.gui.dss.section.label=DSS շարժիչ jfx.gui.dss.online.enabled=Չեղարկման տվյալների ստացում առցանց (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=Պահանջվում է LT/LTA ստեղծելու համար: Անջատված դեպքում DSS շարժիչն աշխատում է լիովին անցանց (միայն B/T): -jfx.gui.dss.trust.useDefaultLotl=Օգտագործել ԵՄ վստահելի ցուցակների կանխադրված ցուցակը (LOTL) +jfx.gui.dss.trust.eu.enabled=Օգտագործել ԵՄ վստահելի ցուցակների կանխադրված ցուցակը (LOTL) jfx.gui.dss.trust.lotlUrls=LOTL URL-ներ (ստորակետերով բաժանված) jfx.gui.dss.trust.certFiles=Վստահելի վկայագրերի ֆայլեր (ստորակետերով բաժանված) jfx.gui.dss.trust.certUrls=Վստահելի վկայագրերի URL-ներ (ստորակետերով բաժանված) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_it.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_it.properties index 4477738d..52c4e003 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_it.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_it.properties @@ -404,7 +404,7 @@ jfx.gui.padesLevel.help=Livello base PAdES per la firma prodotta. Disponibile so jfx.gui.dss.section.label=Motore DSS jfx.gui.dss.online.enabled=Recupera i dati di revoca online (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=Necessario per produrre LT/LTA. Se disattivato, il motore DSS funziona completamente offline (solo B/T). -jfx.gui.dss.trust.useDefaultLotl=Usa l'elenco delle liste di fiducia dell'UE predefinito (LOTL) +jfx.gui.dss.trust.eu.enabled=Usa l'elenco delle liste di fiducia dell'UE predefinito (LOTL) jfx.gui.dss.trust.lotlUrls=URL LOTL (separati da virgole) jfx.gui.dss.trust.certFiles=File di certificati attendibili (separati da virgole) jfx.gui.dss.trust.certUrls=URL di certificati attendibili (separati da virgole) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_ja.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_ja.properties index 4b745f29..077774d0 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_ja.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_ja.properties @@ -394,7 +394,7 @@ jfx.gui.padesLevel.help=生成される署名の PAdES ベースラインレベ jfx.gui.dss.section.label=DSS エンジン jfx.gui.dss.online.enabled=失効データをオンラインで取得(OCSP/CRL、AIA) jfx.gui.dss.online.enabled.tooltip=LT/LTA の生成に必要です。オフの場合、DSS エンジンは完全にオフラインで動作します(B/T のみ)。 -jfx.gui.dss.trust.useDefaultLotl=既定の EU 信頼リストのリスト(LOTL)を使用 +jfx.gui.dss.trust.eu.enabled=既定の EU 信頼リストのリスト(LOTL)を使用 jfx.gui.dss.trust.lotlUrls=LOTL URL(カンマ区切り) jfx.gui.dss.trust.certFiles=信頼する証明書ファイル(カンマ区切り) jfx.gui.dss.trust.certUrls=信頼する証明書 URL(カンマ区切り) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_nb-NO.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_nb-NO.properties index e93c9875..6e39bf6a 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_nb-NO.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_nb-NO.properties @@ -394,7 +394,7 @@ jfx.gui.padesLevel.help=PAdES-baselinenivå for den produserte signaturen. Kun t jfx.gui.dss.section.label=DSS-motor jfx.gui.dss.online.enabled=Hent tilbakekallingsdata på nett (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=Kreves for å produsere LT/LTA. Når deaktivert, fungerer DSS-motoren helt frakoblet (kun B/T). -jfx.gui.dss.trust.useDefaultLotl=Bruk EUs standardliste over tillitslister (LOTL) +jfx.gui.dss.trust.eu.enabled=Bruk EUs standardliste over tillitslister (LOTL) jfx.gui.dss.trust.lotlUrls=LOTL-URL-er (kommadelt) jfx.gui.dss.trust.certFiles=Klarerte sertifikatfiler (kommadelt) jfx.gui.dss.trust.certUrls=Klarerte sertifikat-URL-er (kommadelt) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_pl.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_pl.properties index 15a4e6d5..27d94b00 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_pl.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_pl.properties @@ -406,7 +406,7 @@ jfx.gui.padesLevel.help=Bazowy poziom PAdES dla tworzonego podpisu. Dostępny ty jfx.gui.dss.section.label=Silnik DSS jfx.gui.dss.online.enabled=Pobieraj dane o unieważnieniu online (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=Wymagane do utworzenia LT/LTA. Po wyłączeniu silnik DSS działa całkowicie offline (tylko B/T). -jfx.gui.dss.trust.useDefaultLotl=Użyj domyślnej listy list zaufania UE (LOTL) +jfx.gui.dss.trust.eu.enabled=Użyj domyślnej listy list zaufania UE (LOTL) jfx.gui.dss.trust.lotlUrls=Adresy URL LOTL (oddzielone przecinkami) jfx.gui.dss.trust.certFiles=Pliki zaufanych certyfikatów (oddzielone przecinkami) jfx.gui.dss.trust.certUrls=Adresy URL zaufanych certyfikatów (oddzielone przecinkami) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_pt.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_pt.properties index 3a391e42..8eccf322 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_pt.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_pt.properties @@ -238,7 +238,7 @@ jfx.gui.padesLevel.help=Nível base PAdES para a assinatura produzida. Disponív jfx.gui.dss.section.label=Motor DSS jfx.gui.dss.online.enabled=Obter dados de revogação online (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=Necessário para produzir LT/LTA. Quando desativado, o motor DSS funciona totalmente offline (apenas B/T). -jfx.gui.dss.trust.useDefaultLotl=Usar a lista de listas de confiança da UE predefinida (LOTL) +jfx.gui.dss.trust.eu.enabled=Usar a lista de listas de confiança da UE predefinida (LOTL) jfx.gui.dss.trust.lotlUrls=URLs de LOTL (separados por vírgulas) jfx.gui.dss.trust.certFiles=Ficheiros de certificados fidedignos (separados por vírgulas) jfx.gui.dss.trust.certUrls=URLs de certificados fidedignos (separados por vírgulas) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_ru.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_ru.properties index b0b21daa..dcb6fe39 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_ru.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_ru.properties @@ -394,7 +394,7 @@ jfx.gui.padesLevel.help=Базовый уровень PAdES для создав jfx.gui.dss.section.label=Движок DSS jfx.gui.dss.online.enabled=Загружать данные отзыва онлайн (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=Требуется для создания LT/LTA. Если отключено, движок DSS работает полностью офлайн (только B/T). -jfx.gui.dss.trust.useDefaultLotl=Использовать стандартный список доверенных списков ЕС (LOTL) +jfx.gui.dss.trust.eu.enabled=Использовать стандартный список доверенных списков ЕС (LOTL) jfx.gui.dss.trust.lotlUrls=URL-адреса LOTL (через запятую) jfx.gui.dss.trust.certFiles=Файлы доверенных сертификатов (через запятую) jfx.gui.dss.trust.certUrls=URL-адреса доверенных сертификатов (через запятую) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_sk.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_sk.properties index 7225490e..b9b8f21f 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_sk.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_sk.properties @@ -288,7 +288,7 @@ jfx.gui.padesLevel.help=Základná úroveň PAdES pre vytváraný podpis. Dostup jfx.gui.dss.section.label=Engine DSS jfx.gui.dss.online.enabled=Sťahovať údaje o zneplatnení online (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=Potrebné na vytvorenie LT/LTA. Pri vypnutí pracuje engine DSS plne offline (iba B/T). -jfx.gui.dss.trust.useDefaultLotl=Použiť predvolený zoznam dôveryhodných zoznamov EÚ (LOTL) +jfx.gui.dss.trust.eu.enabled=Použiť predvolený zoznam dôveryhodných zoznamov EÚ (LOTL) jfx.gui.dss.trust.lotlUrls=URL adresy LOTL (oddelené čiarkou) jfx.gui.dss.trust.certFiles=Súbory dôveryhodných certifikátov (oddelené čiarkou) jfx.gui.dss.trust.certUrls=URL adresy dôveryhodných certifikátov (oddelené čiarkou) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_ta.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_ta.properties index 11239dbe..c77db755 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_ta.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_ta.properties @@ -240,7 +240,7 @@ jfx.gui.padesLevel.help=உருவாக்கப்படும் கைய jfx.gui.dss.section.label=DSS எஞ்சின் jfx.gui.dss.online.enabled=ரத்து தரவை ஆன்லைனில் பெறவும் (OCSP/CRL, AIA) jfx.gui.dss.online.enabled.tooltip=LT/LTA உருவாக்க தேவை. முடக்கப்பட்டிருக்கும்போது, DSS எஞ்சின் முழுமையாக ஆஃப்லைனில் இயங்கும் (B/T மட்டும்). -jfx.gui.dss.trust.useDefaultLotl=இயல்புநிலை EU நம்பகப் பட்டியல்களின் பட்டியலை (LOTL) பயன்படுத்தவும் +jfx.gui.dss.trust.eu.enabled=இயல்புநிலை EU நம்பகப் பட்டியல்களின் பட்டியலை (LOTL) பயன்படுத்தவும் jfx.gui.dss.trust.lotlUrls=LOTL URL-கள் (காற்புள்ளியால் பிரிக்கப்பட்டவை) jfx.gui.dss.trust.certFiles=நம்பகமான சான்றிதழ் கோப்புகள் (காற்புள்ளியால் பிரிக்கப்பட்டவை) jfx.gui.dss.trust.certUrls=நம்பகமான சான்றிதழ் URL-கள் (காற்புள்ளியால் பிரிக்கப்பட்டவை) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_zh_CN.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_zh_CN.properties index ab7d8141..e62aab58 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_zh_CN.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_zh_CN.properties @@ -495,7 +495,7 @@ jfx.gui.padesLevel.help=所生成签名的 PAdES 基线级别。仅适用于 DSS jfx.gui.dss.section.label=DSS 引擎 jfx.gui.dss.online.enabled=在线获取吊销数据(OCSP/CRL、AIA) jfx.gui.dss.online.enabled.tooltip=生成 LT/LTA 时需要。关闭时,DSS 引擎将完全离线工作(仅 B/T)。 -jfx.gui.dss.trust.useDefaultLotl=使用默认的欧盟信任列表的列表(LOTL) +jfx.gui.dss.trust.eu.enabled=使用默认的欧盟信任列表的列表(LOTL) jfx.gui.dss.trust.lotlUrls=LOTL URL(以逗号分隔) jfx.gui.dss.trust.certFiles=受信任的证书文件(以逗号分隔) jfx.gui.dss.trust.certUrls=受信任的证书 URL(以逗号分隔) diff --git a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_zh_TW.properties b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_zh_TW.properties index aa953312..abc691cc 100644 --- a/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_zh_TW.properties +++ b/engines/api/src/main/resources/net/sf/jsignpdf/translations/messages_zh_TW.properties @@ -495,7 +495,7 @@ jfx.gui.padesLevel.help=所產生簽章的 PAdES 基準等級。僅適用於 DSS jfx.gui.dss.section.label=DSS 引擎 jfx.gui.dss.online.enabled=線上擷取撤銷資料(OCSP/CRL、AIA) jfx.gui.dss.online.enabled.tooltip=產生 LT/LTA 時需要。關閉時,DSS 引擎會完全離線運作(僅 B/T)。 -jfx.gui.dss.trust.useDefaultLotl=使用預設的歐盟信任清單的清單(LOTL) +jfx.gui.dss.trust.eu.enabled=使用預設的歐盟信任清單的清單(LOTL) jfx.gui.dss.trust.lotlUrls=LOTL URL(以逗號分隔) jfx.gui.dss.trust.certFiles=受信任的憑證檔案(以逗號分隔) jfx.gui.dss.trust.certUrls=受信任的憑證 URL(以逗號分隔) diff --git a/engines/dss/src/main/java/net/sf/jsignpdf/engine/dss/DssTrustConfigurer.java b/engines/dss/src/main/java/net/sf/jsignpdf/engine/dss/DssTrustConfigurer.java index d94aa573..eefbe7ab 100644 --- a/engines/dss/src/main/java/net/sf/jsignpdf/engine/dss/DssTrustConfigurer.java +++ b/engines/dss/src/main/java/net/sf/jsignpdf/engine/dss/DssTrustConfigurer.java @@ -26,10 +26,10 @@ import eu.europa.esig.dss.spi.tsl.TrustedListsCertificateSource; import eu.europa.esig.dss.spi.validation.CommonCertificateVerifier; import eu.europa.esig.dss.spi.x509.CertificateSource; -import eu.europa.esig.dss.spi.x509.CommonCertificateSource; import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource; import eu.europa.esig.dss.spi.x509.KeyStoreCertificateSource; import eu.europa.esig.dss.spi.x509.aia.DefaultAIASource; +import eu.europa.esig.dss.tsl.function.OfficialJournalSchemeInformationURI; import eu.europa.esig.dss.tsl.job.TLValidationJob; import eu.europa.esig.dss.tsl.source.LOTLSource; @@ -50,7 +50,20 @@ final class DssTrustConfigurer { static final String KEY_ONLINE_ENABLED = "online.enabled"; - static final String KEY_USE_DEFAULT_LOTL = "trust.useDefaultLotl"; + + // --- EU LOTL machinery (trust.eu.* sub-namespace) --- + /** Enable the bundled European LOTL (with the bundled OJ keystore). */ + static final String KEY_EU_ENABLED = "trust.eu.enabled"; + /** Override the default EU LOTL URL (only effective when {@link #KEY_EU_ENABLED} is set). */ + static final String KEY_EU_LOTL_URL = "trust.eu.lotlUrl"; + /** Override the Official Journal scheme-information URL used by the announcement predicate. */ + static final String KEY_EU_OJ_URL = "trust.eu.ojUrl"; + /** Override the bundled OJ keystore with an external file. */ + static final String KEY_EU_OJ_KEYSTORE_FILE = "trust.eu.ojKeystoreFile"; + /** Password for the OJ keystore override file. */ + static final String KEY_EU_OJ_KEYSTORE_PASSWORD = "trust.eu.ojKeystorePassword"; + + // --- generic / advanced trust material --- static final String KEY_LOTL_URLS = "trust.lotlUrls"; static final String KEY_CERT_FILES = "trust.certFiles"; static final String KEY_CERT_URLS = "trust.certUrls"; @@ -58,6 +71,24 @@ final class DssTrustConfigurer { static final String KEY_TRUSTSTORE_TYPE = "trust.truststoreType"; static final String KEY_TRUSTSTORE_PASSWORD = "trust.truststorePassword"; + /** Canonical EU List of Trusted Lists location. */ + static final String DEFAULT_EU_LOTL_URL = "https://ec.europa.eu/tools/lotl/eu-lotl.xml"; + + /** + * Default Official Journal scheme-information URL announcing the certificates allowed to sign the EU LOTL. + * Must stay in sync with the bundled OJ keystore ({@link #OJ_KEYSTORE_RESOURCE}); both were rotated by + * OJ C/2026/1944 (April 2026). Override via {@link #KEY_EU_OJ_URL} when pointing at a newer OJ notice. + */ + static final String DEFAULT_OJ_URL = + "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=OJ:C_202601944"; + + /** Classpath location of the bundled OJ keystore (validates the EU LOTL's own signature). */ + static final String OJ_KEYSTORE_RESOURCE = "/net/sf/jsignpdf/engine/dss/eu-oj-keystore.p12"; + + /** Keystore type / password of the bundled OJ keystore (matches the DSS demonstrations keystore). */ + private static final String OJ_KEYSTORE_TYPE = "PKCS12"; + private static final String OJ_KEYSTORE_PASSWORD = "dss-password"; + /** Separator for the list-valued keys (lotlUrls / certFiles / certUrls). */ private static final String LIST_SEPARATOR = "[,;]+"; @@ -125,7 +156,15 @@ private CertificateSource[] createTrustedCertSources() throws Exception { tlValidationJob.setListOfTrustedListSources(lotlSources); TrustedListsCertificateSource trustedListsCertificateSource = new TrustedListsCertificateSource(); tlValidationJob.setTrustedListCertificateSource(trustedListsCertificateSource); - tlValidationJob.onlineRefresh(); + try { + tlValidationJob.onlineRefresh(); + } catch (Exception e) { + // Surface an actionable cause instead of an opaque DSS stack trace; the caller logs this via + // console.dss.trustConfigFailed and aborts signing. Common causes: offline / proxy not + // configured, or a stale OJ keystore that can no longer validate the LOTL signature. + throw new IllegalStateException("Failed to refresh the EU LOTL / trusted lists (check network /" + + " proxy, or update the OJ keystore via " + KEY_EU_OJ_KEYSTORE_FILE + ")", e); + } trustedSources.add(trustedListsCertificateSource); } @@ -173,20 +212,68 @@ private static File tlCacheDirectory() { return cacheDir; } - private LOTLSource[] getLotlSources() { + LOTLSource[] getLotlSources() throws Exception { List lotlSources = new ArrayList<>(); - if (config.getBoolean(KEY_USE_DEFAULT_LOTL, false)) { - lotlSources.add(new LOTLSource()); + CertificateSource ojCertificateSource = null; + + if (config.getBoolean(KEY_EU_ENABLED, false)) { + ojCertificateSource = ojKeystoreCertificateSource(); + lotlSources.add(europeanLotlSource(ojCertificateSource)); + } + + List customLotlUrls = splitList(config.getString(KEY_LOTL_URLS)); + if (!customLotlUrls.isEmpty() && ojCertificateSource == null) { + ojCertificateSource = ojKeystoreCertificateSource(); } - for (String url : splitList(config.getString(KEY_LOTL_URLS))) { + for (String url : customLotlUrls) { + // Advanced / "bring your own trust": signed by the (bundled or overridden) OJ certs, pivot + // support on, but no OJ announcement predicate (a custom LOTL may not announce the EU OJ URL). LOTLSource lotlSource = new LOTLSource(); lotlSource.setUrl(url); - lotlSource.setCertificateSource(new CommonCertificateSource()); + lotlSource.setCertificateSource(ojCertificateSource); + lotlSource.setPivotSupport(true); lotlSources.add(lotlSource); } return lotlSources.toArray(new LOTLSource[0]); } + /** + * Builds the European LOTL source wired so DSS can validate the LOTL's own signature against the OJ + * keystore and follow the pivot chain to the current trust anchors. The URL and OJ scheme-information URL + * default to the canonical EU values and are overridable via {@link #KEY_EU_LOTL_URL} / {@link #KEY_EU_OJ_URL}. + */ + private LOTLSource europeanLotlSource(CertificateSource ojCertificateSource) { + LOTLSource lotl = new LOTLSource(); + lotl.setUrl(config.getString(KEY_EU_LOTL_URL, DEFAULT_EU_LOTL_URL)); + lotl.setCertificateSource(ojCertificateSource); + lotl.setSigningCertificatesAnnouncementPredicate( + new OfficialJournalSchemeInformationURI(config.getString(KEY_EU_OJ_URL, DEFAULT_OJ_URL))); + lotl.setPivotSupport(true); + return lotl; + } + + /** + * Loads the certificate source that validates the LOTL signature: an external keystore when + * {@link #KEY_EU_OJ_KEYSTORE_FILE} is set, otherwise the keystore bundled on the classpath. Unlike + * {@link #KEY_TRUSTSTORE_FILE} (trust anchors for the document signer), these certs are consumed only by + * the {@link TLValidationJob} to decide whether to accept the LOTL. + */ + private CertificateSource ojKeystoreCertificateSource() throws Exception { + final String overrideFile = config.getString(KEY_EU_OJ_KEYSTORE_FILE); + if (StringUtils.isNotEmpty(overrideFile)) { + final String pwd = config.getString(KEY_EU_OJ_KEYSTORE_PASSWORD, ""); + return new KeyStoreCertificateSource(new File(overrideFile), KeyStore.getDefaultType(), + pwd != null ? pwd.toCharArray() : null); + } + try (InputStream is = DssTrustConfigurer.class.getResourceAsStream(OJ_KEYSTORE_RESOURCE)) { + if (is == null) { + throw new IllegalStateException("Bundled OJ keystore resource not found on the classpath: " + + OJ_KEYSTORE_RESOURCE); + } + return new KeyStoreCertificateSource(is, OJ_KEYSTORE_TYPE, OJ_KEYSTORE_PASSWORD.toCharArray()); + } + } + private static List splitList(String value) { List out = new ArrayList<>(); if (StringUtils.isNotBlank(value)) { diff --git a/engines/dss/src/main/resources/net/sf/jsignpdf/engine/dss/README-oj-keystore.md b/engines/dss/src/main/resources/net/sf/jsignpdf/engine/dss/README-oj-keystore.md new file mode 100644 index 00000000..dfbbcc5f --- /dev/null +++ b/engines/dss/src/main/resources/net/sf/jsignpdf/engine/dss/README-oj-keystore.md @@ -0,0 +1,27 @@ +# EU LOTL Official Journal (OJ) keystore + +`eu-oj-keystore.p12` validates the **signature of the EU List of Trusted Lists (LOTL)** itself +(`LOTLSource.setCertificateSource(...)`). It is *not* a trust anchor for document signers — that is +`engine.dss.trust.truststoreFile`. + +- **Type:** PKCS12 **Password:** `dss-password` (matches the DSS demonstrations keystore convention) +- **Loaded by:** `DssTrustConfigurer.ojKeystoreCertificateSource()` (constant `OJ_KEYSTORE_RESOURCE`) +- **Override at runtime:** `engine.dss.trust.eu.ojKeystoreFile` / `engine.dss.trust.eu.ojKeystorePassword` + +## Current file + +Sourced from the `dss-demonstrations` repository, tag `6.4+20260415` (post the April-2026 OJ rotation). It +holds the EUROPEAN COMMISSION qualified signer certificates (organization + named statutory staff) that sign +the EU LOTL — all `trustedCertEntry`, no private keys. DSS follows the OJ pivot chain from these anchors, so +expired/legacy entries may legitimately remain. + +## Refreshing (release checklist) + +Refresh when the OJ re-issues the LOTL signing certificates (roughly every few years; pivot support absorbs +the more frequent changes). Helper scripts live in `engines/dss/src/main/scripts/`: + +1. Run `fetch-oj-keystore-from-dss-demos.sh ` (recommended) to copy the keystore from a matching + `dss-demonstrations` tag, or `build-oj-keystore-from-live-lotl.sh` to bootstrap from the live LOTL signer. +2. Verify the contained signer certificates (subjects / validity) against the OJ notice (the script prints them). +3. Keep the path / type PKCS12 / password `dss-password`, or update the `OJ_KEYSTORE_*` constants. +4. Update `DssTrustConfigurer.DEFAULT_OJ_URL` to the matching OJ notice URL. diff --git a/engines/dss/src/main/resources/net/sf/jsignpdf/engine/dss/eu-oj-keystore.p12 b/engines/dss/src/main/resources/net/sf/jsignpdf/engine/dss/eu-oj-keystore.p12 new file mode 100644 index 0000000000000000000000000000000000000000..82c673a6ddd2c2d7c24c6c44eb90a3ff809de651 GIT binary patch literal 13942 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 exit 1; } +command -v python3 >/dev/null || { echo "python3 required." >&2; exit 1; } + +tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT +echo ">> Downloading $LOTL_URL ..." +curl -fsSL "$LOTL_URL" -o "$tmp/eu-lotl.xml" + +echo ">> Extracting X509 certificate(s) from the LOTL signature ..." +python3 - "$tmp/eu-lotl.xml" "$tmp" <<'PY' +import re, base64, sys, os +xml = open(sys.argv[1], encoding="utf-8").read() +outdir = sys.argv[2] +b64s = re.findall(r'<(?:\w+:)?X509Certificate>\s*([A-Za-z0-9+/=\s]+?)\s*', xml) +if not b64s: + print("No X509Certificate found in the LOTL signature.", file=sys.stderr); sys.exit(1) +seen = set(); n = 0 +for b in b64s: + der = base64.b64decode("".join(b.split())) + if der in seen: # de-dup + continue + seen.add(der) + pem = "-----BEGIN CERTIFICATE-----\n" + "\n".join( + base64.b64encode(der).decode()[i:i+64] for i in range(0, len(base64.b64encode(der)), 64) + ) + "\n-----END CERTIFICATE-----\n" + open(os.path.join(outdir, f"oj-{n}.pem"), "w").write(pem) + n += 1 +print(n) +PY + +rm -f "$DEST" +i=0 +for pem in "$tmp"/oj-*.pem; do + keytool -importcert -noprompt -trustcacerts -alias "oj-signer-$i" \ + -file "$pem" -keystore "$DEST" -storetype PKCS12 -storepass "$PASS" + i=$((i+1)) +done + +echo ">> Built $DEST with $i certificate(s):" +keytool -list -v -keystore "$DEST" -storetype PKCS12 -storepass "$PASS" \ + | grep -E "Alias name:|Owner:|Valid from:" || true +echo ">> CROSS-CHECK the owners/validity above against the OJ notice before committing." diff --git a/engines/dss/src/main/scripts/fetch-oj-keystore-from-dss-demos.sh b/engines/dss/src/main/scripts/fetch-oj-keystore-from-dss-demos.sh new file mode 100755 index 00000000..d4e4360b --- /dev/null +++ b/engines/dss/src/main/scripts/fetch-oj-keystore-from-dss-demos.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# copy the OJ/LOTL trust keystore from the dss-demonstrations repo, +# matching our DSS version, into the engine resources. Password convention: dss-password. +set -euo pipefail + +TAG="${1:-6.4}" # dss-demonstrations tag == DSS version +PASS="dss-password" +DEST="engines/dss/src/main/resources/net/sf/jsignpdf/engine/dss/eu-oj-keystore.p12" +REPO="https://github.com/esig/dss-demonstrations.git" + +[ -f pom.xml ] || { echo "Run from the jsignpdf repo root." >&2; exit 1; } + +tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT +echo ">> Cloning $REPO @ $TAG ..." +git clone --depth 1 --branch "$TAG" "$REPO" "$tmp" 2>/dev/null \ + || { echo ">> Tag $TAG not found, falling back to default branch."; git clone --depth 1 "$REPO" "$tmp"; } + +# Find candidate keystores that (a) open with dss-password and (b) contain EU trusted-list signer certs. +echo ">> Searching for the OJ/LOTL keystore ..." +candidate="" +while IFS= read -r ks; do + if keytool -list -keystore "$ks" -storetype PKCS12 -storepass "$PASS" >/dev/null 2>&1; then + if keytool -list -v -keystore "$ks" -storetype PKCS12 -storepass "$PASS" 2>/dev/null \ + | grep -qiE "European Commission|Trusted List|C=EU|LOTL"; then + candidate="$ks"; break + fi + [ -z "$candidate" ] && candidate="$ks" # remember a dss-password p12 even if the heuristic misses + fi +done < <(find "$tmp" -name '*.p12' | sort) + +[ -n "$candidate" ] || { echo "No dss-password PKCS12 keystore found in the repo." >&2; exit 1; } + +echo ">> Using: ${candidate#$tmp/}" +cp "$candidate" "$DEST" +echo ">> Copied to $DEST" +echo ">> Contents:" +keytool -list -v -keystore "$DEST" -storetype PKCS12 -storepass "$PASS" \ + | grep -E "Alias name:|Owner:|Valid from:" || true +echo ">> Done. Review the certificate owners/validity above before committing." diff --git a/engines/dss/src/test/java/net/sf/jsignpdf/engine/dss/DssTrustConfigurerTest.java b/engines/dss/src/test/java/net/sf/jsignpdf/engine/dss/DssTrustConfigurerTest.java new file mode 100644 index 00000000..e00341e4 --- /dev/null +++ b/engines/dss/src/test/java/net/sf/jsignpdf/engine/dss/DssTrustConfigurerTest.java @@ -0,0 +1,124 @@ +package net.sf.jsignpdf.engine.dss; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import net.sf.jsignpdf.engine.EngineConfig; + +import org.apache.commons.lang3.StringUtils; +import org.junit.Test; + +import eu.europa.esig.dss.tsl.function.OfficialJournalSchemeInformationURI; +import eu.europa.esig.dss.tsl.source.LOTLSource; + +/** + * Offline wiring tests for {@link DssTrustConfigurer#getLotlSources()}. They assert the LOTL sources are + * built with a non-null URL, a certificate source, the OJ announcement predicate and pivot support — + * the regression guard for issue #432 (the bare {@code new LOTLSource()} with a null URL that NPE'd) and the + * RC3 fix (the custom {@code lotlUrls} branch left with an empty certificate source). No network is used. + * + * @author Josef Cacek + */ +public class DssTrustConfigurerTest { + + @Test + public void euEnabledBuildsEuropeanLotlSource() throws Exception { + LOTLSource[] sources = lotlSources(Map.of(DssTrustConfigurer.KEY_EU_ENABLED, "true")); + + assertEquals("trust.eu.enabled must yield exactly one LOTL source", 1, sources.length); + LOTLSource lotl = sources[0]; + assertEquals("default EU LOTL URL", DssTrustConfigurer.DEFAULT_EU_LOTL_URL, lotl.getUrl()); + assertNotNull("OJ keystore certificate source must be wired", lotl.getCertificateSource()); + assertTrue("OJ announcement predicate must be the Official Journal one", + lotl.getSigningCertificatesAnnouncementPredicate() instanceof OfficialJournalSchemeInformationURI); + assertTrue("pivot support must be enabled", lotl.isPivotSupport()); + } + + @Test + public void euLotlUrlOverrideIsApplied() throws Exception { + String override = "https://example.test/eu-lotl.xml"; + LOTLSource[] sources = lotlSources(Map.of( + DssTrustConfigurer.KEY_EU_ENABLED, "true", + DssTrustConfigurer.KEY_EU_LOTL_URL, override)); + + assertEquals(1, sources.length); + assertEquals("euLotlUrl must relocate the EU LOTL", override, sources[0].getUrl()); + } + + @Test + public void customLotlUrlsWiredWithOjCertSourceAndPivotNoPredicate() throws Exception { + LOTLSource[] sources = lotlSources(Map.of( + DssTrustConfigurer.KEY_LOTL_URLS, "https://a.test/lotl.xml, https://b.test/lotl.xml")); + + assertEquals("two custom LOTL URLs -> two sources", 2, sources.length); + for (LOTLSource lotl : sources) { + assertTrue("custom LOTL must have a non-blank URL", StringUtils.isNotBlank(lotl.getUrl())); + assertNotNull("custom LOTL must reuse the OJ certificate source", lotl.getCertificateSource()); + assertTrue("custom LOTL must keep pivot support", lotl.isPivotSupport()); + assertNull("custom LOTL must not carry the EU OJ predicate", + lotl.getSigningCertificatesAnnouncementPredicate()); + } + } + + @Test + public void blankAndGarbageLotlUrlEntriesAreSkipped() throws Exception { + // Empty / whitespace-only entries between separators must never become a LOTLSource with a null URL. + LOTLSource[] sources = lotlSources(Map.of( + DssTrustConfigurer.KEY_LOTL_URLS, "https://a.test/lotl.xml,, ; https://b.test/lotl.xml ,")); + + assertEquals("only the two real URLs survive", 2, sources.length); + for (LOTLSource lotl : sources) { + assertTrue("no LOTL source may have a blank URL", StringUtils.isNotBlank(lotl.getUrl())); + } + } + + @Test + public void noLotlConfigYieldsNoSources() throws Exception { + assertEquals("no LOTL config -> no sources", 0, lotlSources(Map.of()).length); + } + + private static LOTLSource[] lotlSources(Map cfg) throws Exception { + return new DssTrustConfigurer(new MapEngineConfig(cfg)).getLotlSources(); + } + + /** Minimal in-memory {@link EngineConfig} backed by a map (keys already engine-relative). */ + private static final class MapEngineConfig implements EngineConfig { + private final Map map; + + MapEngineConfig(Map map) { + this.map = new HashMap<>(map); + } + + @Override + public String getString(String key) { + return map.get(key); + } + + @Override + public String getString(String key, String fallback) { + return map.getOrDefault(key, fallback); + } + + @Override + public boolean getBoolean(String key, boolean fallback) { + String v = map.get(key); + return v == null ? fallback : Boolean.parseBoolean(v); + } + + @Override + public int getInt(String key, int fallback) { + String v = map.get(key); + try { + return v == null ? fallback : Integer.parseInt(v); + } catch (NumberFormatException e) { + return fallback; + } + } + } +} diff --git a/jsignpdf/src/main/java/net/sf/jsignpdf/SignerLogic.java b/jsignpdf/src/main/java/net/sf/jsignpdf/SignerLogic.java index 7a918db2..62f494db 100644 --- a/jsignpdf/src/main/java/net/sf/jsignpdf/SignerLogic.java +++ b/jsignpdf/src/main/java/net/sf/jsignpdf/SignerLogic.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.logging.Level; +import net.sf.jsignpdf.engine.DssLtTrustPreflight; import net.sf.jsignpdf.engine.EngineConfig; import net.sf.jsignpdf.engine.EngineMismatchValidator; import net.sf.jsignpdf.engine.EngineMismatchValidator.Mismatch; @@ -83,6 +84,22 @@ public boolean signFile() { } final EngineConfig engineConfig = AppConfig.engineConfigFor(engine.id()); + + // Fail fast on an LT/LTA request the engine isn't configured to satisfy (issue #432), before any + // key/PIN access or network round-trip, with the exact keys to set. + final DssLtTrustPreflight.Result preflight = + DssLtTrustPreflight.check(options, engine, engineConfig); + if (preflight.hasIssues()) { + LOGGER.severe(RES.get("console.dss.ltPreflightFailed")); + if (preflight.onlineMissing()) { + LOGGER.severe(RES.get("console.dss.ltPreflight.online")); + } + if (preflight.trustSourceMissing()) { + LOGGER.severe(RES.get("console.dss.ltPreflight.trust")); + } + return false; + } + finished = engine.sign(options, engineConfig); } catch (Exception e) { LOGGER.log(Level.SEVERE, RES.get("console.exception"), e); diff --git a/jsignpdf/src/main/java/net/sf/jsignpdf/engine/DssLtTrustPreflight.java b/jsignpdf/src/main/java/net/sf/jsignpdf/engine/DssLtTrustPreflight.java new file mode 100644 index 00000000..57aca4eb --- /dev/null +++ b/jsignpdf/src/main/java/net/sf/jsignpdf/engine/DssLtTrustPreflight.java @@ -0,0 +1,75 @@ +package net.sf.jsignpdf.engine; + +import net.sf.jsignpdf.BasicSignerOptions; +import net.sf.jsignpdf.types.PadesLevel; + +import org.apache.commons.lang3.StringUtils; + +/** + * Config preflight for the PAdES LT / LTA levels: those embed revocation data, which DSS only fetches for a + * trusted signer chain reachable online. Without both, signing fails deep inside DSS with an + * opaque {@code AlertException} ("Revocation data is skipped for untrusted certificate chain") — the + * defect behind issue #432. This validator detects the misconfiguration up front so the CLI can fail fast and + * the GUI can offer to fix it, before any expensive work (PDF load, key/PIN access, TSA round-trip). + * + *

+ * It is a config-level check, not a connectivity test: a configuration that passes can still fail at runtime + * (LOTL unreachable, proxy, stale OJ keystore), which the engine surfaces via {@code console.dss.trustConfigFailed}. + *

+ * + * @author Josef Cacek + */ +public final class DssLtTrustPreflight { + + // Engine-relative keys (resolved under engine..* by EngineConfig); mirror DssTrustConfigurer's keys. + static final String KEY_ONLINE_ENABLED = "online.enabled"; + static final String KEY_EU_ENABLED = "trust.eu.enabled"; + static final String KEY_TRUSTSTORE_FILE = "trust.truststoreFile"; + static final String KEY_CERT_FILES = "trust.certFiles"; + static final String KEY_CERT_URLS = "trust.certUrls"; + static final String KEY_LOTL_URLS = "trust.lotlUrls"; + + /** + * Outcome of the preflight. + * + * @param applicable whether the check applied at all (LT/LTA selected on an LT-capable engine) + * @param onlineMissing LT/LTA needs online fetching but {@code online.enabled} is off + * @param trustSourceMissing LT/LTA needs a trust anchor but none of the trust sources is configured + */ + public record Result(boolean applicable, boolean onlineMissing, boolean trustSourceMissing) { + /** @return {@code true} when the configuration would make LT/LTA signing fail. */ + public boolean hasIssues() { + return applicable && (onlineMissing || trustSourceMissing); + } + } + + private DssLtTrustPreflight() { + } + + /** + * Checks whether the engine is configured to satisfy an LT/LTA request. + * + * @param o the populated signing options + * @param engine the engine that would sign + * @param config the engine-scoped configuration view + * @return the preflight result; {@link Result#hasIssues()} is {@code false} when nothing is wrong or the + * check does not apply (non-LT/LTA level, or an engine that cannot produce LT) + */ + public static Result check(BasicSignerOptions o, SigningEngine engine, EngineConfig config) { + if (o == null || engine == null || config == null) { + return new Result(false, false, false); + } + final PadesLevel level = o.getPadesLevel(); + final boolean ltOrLta = level == PadesLevel.BASELINE_LT || level == PadesLevel.BASELINE_LTA; + if (!ltOrLta || !engine.capabilities().contains(Capability.PADES_BASELINE_LT)) { + return new Result(false, false, false); + } + final boolean online = config.getBoolean(KEY_ONLINE_ENABLED, false); + final boolean trustSource = config.getBoolean(KEY_EU_ENABLED, false) + || StringUtils.isNotBlank(config.getString(KEY_TRUSTSTORE_FILE)) + || StringUtils.isNotBlank(config.getString(KEY_CERT_FILES)) + || StringUtils.isNotBlank(config.getString(KEY_CERT_URLS)) + || StringUtils.isNotBlank(config.getString(KEY_LOTL_URLS)); + return new Result(true, !online, !trustSource); + } +} diff --git a/jsignpdf/src/main/java/net/sf/jsignpdf/fx/preferences/PreferencesController.java b/jsignpdf/src/main/java/net/sf/jsignpdf/fx/preferences/PreferencesController.java index 2ed6e3d2..0c5223ea 100644 --- a/jsignpdf/src/main/java/net/sf/jsignpdf/fx/preferences/PreferencesController.java +++ b/jsignpdf/src/main/java/net/sf/jsignpdf/fx/preferences/PreferencesController.java @@ -95,7 +95,7 @@ public class PreferencesController { @FXML private ComboBox cmbTsaHashAlgorithm; @FXML private CheckBox chkDssOnlineEnabled; - @FXML private CheckBox chkDssUseDefaultLotl; + @FXML private CheckBox chkDssEuEnabled; @FXML private TextField txtDssLotlUrls; @FXML private TextField txtDssCertFiles; @FXML private TextField txtDssCertUrls; @@ -231,7 +231,7 @@ void bind(PreferencesViewModel viewModel) { cmbTsaHashAlgorithm.valueProperty().bindBidirectional(vm.tsaHashAlgorithmProperty()); chkDssOnlineEnabled.selectedProperty().bindBidirectional(vm.dssOnlineEnabledProperty()); - chkDssUseDefaultLotl.selectedProperty().bindBidirectional(vm.dssUseDefaultLotlProperty()); + chkDssEuEnabled.selectedProperty().bindBidirectional(vm.dssEuEnabledProperty()); txtDssLotlUrls.textProperty().bindBidirectional(vm.dssLotlUrlsProperty()); txtDssCertFiles.textProperty().bindBidirectional(vm.dssCertFilesProperty()); txtDssCertUrls.textProperty().bindBidirectional(vm.dssCertUrlsProperty()); diff --git a/jsignpdf/src/main/java/net/sf/jsignpdf/fx/preferences/PreferencesViewModel.java b/jsignpdf/src/main/java/net/sf/jsignpdf/fx/preferences/PreferencesViewModel.java index 16bfbc5f..d00c59d4 100644 --- a/jsignpdf/src/main/java/net/sf/jsignpdf/fx/preferences/PreferencesViewModel.java +++ b/jsignpdf/src/main/java/net/sf/jsignpdf/fx/preferences/PreferencesViewModel.java @@ -49,7 +49,7 @@ public class PreferencesViewModel { // DSS engine (engine.dss.*) — trust material for PAdES LT/LTA private final BooleanProperty dssOnlineEnabled = new SimpleBooleanProperty(false); - private final BooleanProperty dssUseDefaultLotl = new SimpleBooleanProperty(false); + private final BooleanProperty dssEuEnabled = new SimpleBooleanProperty(false); private final StringProperty dssLotlUrls = new SimpleStringProperty(""); private final StringProperty dssCertFiles = new SimpleStringProperty(""); private final StringProperty dssCertUrls = new SimpleStringProperty(""); @@ -77,7 +77,7 @@ public void loadFrom(AdvancedConfig cfg, String pkcs11FileBody) { tsaHashAlgorithm.set(cfg.getNotEmptyProperty("tsa.hashAlgorithm", "SHA-256")); dssOnlineEnabled.set(cfg.getAsBool("engine.dss.online.enabled", false)); - dssUseDefaultLotl.set(cfg.getAsBool("engine.dss.trust.useDefaultLotl", false)); + dssEuEnabled.set(cfg.getAsBool("engine.dss.trust.eu.enabled", false)); dssLotlUrls.set(orEmpty(cfg.getProperty("engine.dss.trust.lotlUrls"))); dssCertFiles.set(orEmpty(cfg.getProperty("engine.dss.trust.certFiles"))); dssCertUrls.set(orEmpty(cfg.getProperty("engine.dss.trust.certUrls"))); @@ -102,7 +102,7 @@ public void writeTo(AdvancedConfig cfg) { cfg.setProperty("tsa.hashAlgorithm", tsaHashAlgorithm.get()); cfg.setProperty("engine.dss.online.enabled", dssOnlineEnabled.get()); - cfg.setProperty("engine.dss.trust.useDefaultLotl", dssUseDefaultLotl.get()); + cfg.setProperty("engine.dss.trust.eu.enabled", dssEuEnabled.get()); writeStringOrRemove(cfg, "engine.dss.trust.lotlUrls", dssLotlUrls.get()); writeStringOrRemove(cfg, "engine.dss.trust.certFiles", dssCertFiles.get()); writeStringOrRemove(cfg, "engine.dss.trust.certUrls", dssCertUrls.get()); @@ -152,7 +152,7 @@ public void applyTsaDefaults(AdvancedConfig defaults) { public void applyDssDefaults(AdvancedConfig defaults) { dssOnlineEnabled.set(parseBool(defaults.getBundledDefault("engine.dss.online.enabled"), false)); - dssUseDefaultLotl.set(parseBool(defaults.getBundledDefault("engine.dss.trust.useDefaultLotl"), false)); + dssEuEnabled.set(parseBool(defaults.getBundledDefault("engine.dss.trust.eu.enabled"), false)); dssLotlUrls.set(orEmpty(defaults.getBundledDefault("engine.dss.trust.lotlUrls"))); dssCertFiles.set(orEmpty(defaults.getBundledDefault("engine.dss.trust.certFiles"))); dssCertUrls.set(orEmpty(defaults.getBundledDefault("engine.dss.trust.certUrls"))); @@ -258,7 +258,7 @@ private static void writeStringOrRemove(AdvancedConfig cfg, String key, String v public IntegerProperty pdfLibOpenpdfOrderProperty() { return pdfLibOpenpdfOrder; } public StringProperty tsaHashAlgorithmProperty() { return tsaHashAlgorithm; } public BooleanProperty dssOnlineEnabledProperty() { return dssOnlineEnabled; } - public BooleanProperty dssUseDefaultLotlProperty() { return dssUseDefaultLotl; } + public BooleanProperty dssEuEnabledProperty() { return dssEuEnabled; } public StringProperty dssLotlUrlsProperty() { return dssLotlUrls; } public StringProperty dssCertFilesProperty() { return dssCertFiles; } public StringProperty dssCertUrlsProperty() { return dssCertUrls; } diff --git a/jsignpdf/src/main/java/net/sf/jsignpdf/fx/view/MainWindowController.java b/jsignpdf/src/main/java/net/sf/jsignpdf/fx/view/MainWindowController.java index ae3a14d2..ac368c25 100644 --- a/jsignpdf/src/main/java/net/sf/jsignpdf/fx/view/MainWindowController.java +++ b/jsignpdf/src/main/java/net/sf/jsignpdf/fx/view/MainWindowController.java @@ -52,9 +52,12 @@ import net.sf.jsignpdf.Constants; import net.sf.jsignpdf.PdfExtraInfo; import net.sf.jsignpdf.engine.Capability; +import net.sf.jsignpdf.engine.DssLtTrustPreflight; +import net.sf.jsignpdf.engine.EngineConfig; import net.sf.jsignpdf.engine.EngineRegistry; import net.sf.jsignpdf.engine.SigningEngine; import net.sf.jsignpdf.fx.EngineCapabilities; +import net.sf.jsignpdf.utils.AdvancedConfig; import net.sf.jsignpdf.utils.AppConfig; import javafx.scene.layout.VBox; import javafx.util.StringConverter; @@ -1085,6 +1088,12 @@ private void onSign() { options.setOutFile(nameBase + Constants.DEFAULT_OUT_SUFFIX + suffix); } + // LT/LTA preflight (issue #432): warn before signing if the DSS engine isn't configured for the + // online fetching + trust anchor that LT/LTA require, and offer to enable them. + if (!confirmLtPreflight()) { + return; + } + // Start signing signingService.cancel(); signingService.reset(); @@ -1095,6 +1104,55 @@ private void onSign() { signingService.start(); } + /** + * Runs the LT/LTA trust preflight ({@link DssLtTrustPreflight}) against the active engine and its config. + * When the configuration would make LT/LTA signing fail (issue #432), shows a confirmation offering to + * enable the missing prerequisites ({@code engine.dss.online.enabled} and, if no trust source is set, + * {@code engine.dss.trust.eu.enabled}) and persist them, with a "sign anyway" escape. + * + * @return {@code true} to proceed with signing, {@code false} to abort + */ + private boolean confirmLtPreflight() { + final SigningEngine engine = engineCapabilities.activeEngineProperty().get(); + if (engine == null) { + return true; + } + final EngineConfig engineConfig = AppConfig.engineConfigFor(engine.id()); + final DssLtTrustPreflight.Result preflight = DssLtTrustPreflight.check(options, engine, engineConfig); + if (!preflight.hasIssues()) { + return true; + } + + final Alert confirm = new Alert(Alert.AlertType.CONFIRMATION); + confirm.setTitle(RES.get("jfx.gui.dialog.ltPreflight.title")); + confirm.setHeaderText(null); + confirm.setContentText(RES.get("jfx.gui.dialog.ltPreflight.text")); + confirm.initOwner(stage); + final ButtonType enableAndSign = new ButtonType(RES.get("jfx.gui.dialog.ltPreflight.enable"), + ButtonBar.ButtonData.YES); + final ButtonType signAnyway = new ButtonType(RES.get("jfx.gui.dialog.ltPreflight.anyway"), + ButtonBar.ButtonData.NO); + confirm.getButtonTypes().setAll(enableAndSign, signAnyway, ButtonType.CANCEL); + + final Optional result = confirm.showAndWait(); + if (result.isEmpty() || result.get() == ButtonType.CANCEL) { + return false; + } + if (result.get() == enableAndSign) { + final AdvancedConfig cfg = PropertyStoreFactory.getInstance().advancedConfig(); + cfg.setProperty("engine.dss.online.enabled", true); + if (preflight.trustSourceMissing()) { + cfg.setProperty("engine.dss.trust.eu.enabled", true); + } + try { + cfg.save(); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Could not persist DSS LT/LTA prerequisites", e); + } + } + return true; + } + @FXML private void onZoomIn() { documentVM.zoomIn(); diff --git a/jsignpdf/src/main/resources/net/sf/jsignpdf/fx/view/Preferences.fxml b/jsignpdf/src/main/resources/net/sf/jsignpdf/fx/view/Preferences.fxml index 6b0bf87c..81b2b661 100644 --- a/jsignpdf/src/main/resources/net/sf/jsignpdf/fx/view/Preferences.fxml +++ b/jsignpdf/src/main/resources/net/sf/jsignpdf/fx/view/Preferences.fxml @@ -103,7 +103,7 @@