Summary
The tsl-ssl-truststore.p12 shipped in release-3.10.1 can no longer establish TLS to https://ec.europa.eu/tools/lotl/eu-lotl.xml. Every LOTL refresh fails PKIX. Because SiVa holds trust state in memory only, any instance restarted after the rotation comes back with no trust anchors at all, while /monitoring/heartbeat continues to answer UP. Qualified signatures then validate as INDETERMINATE with "The certificate chain for signature is not trusted", and a Baseline-LT signature is reported at Baseline-T.
Cause
ec.europa.eu is now issued by GlobalSign Atlas R46 OV TLS CA 2026 Q3, chaining to GlobalSign Root R46. The shipped truststore contains only the previous intermediate, GlobalSign Atlas R3 OV TLS CA 2025 Q3.
Reproduce
curl -sL -o siva.p12 https://github.com/open-eid/SiVa/raw/release-3.10.1/validation-services-parent/tsl-loader/src/main/resources/tsl-ssl-truststore.p12
openssl pkcs12 -in siva.p12 -passin pass:digidoc4j-password -nokeys > siva.pem
echo | openssl s_client -connect ec.europa.eu:443 -servername ec.europa.eu -showcerts 2>/dev/null > served.txt
awk '/BEGIN CERT/{n++} n==1{print > "leaf.pem"} n==2{print > "chain.pem"}' served.txt
openssl verify -no-CApath -no-CAstore -CAfile siva.pem -untrusted chain.pem leaf.pem
Result:
CN = GlobalSign Atlas R46 OV TLS CA 2026 Q3
error 20 at 1 depth lookup: unable to get local issuer certificate
error leaf.pem: verification failed
Appending GlobalSign Root R46 to the truststore makes it pass. (-no-CApath -no-CAstore matters: without them openssl consults the system store and misleadingly reports OK, which hides the defect.)
Suggested fix
Anchor the roots rather than the issuing intermediates. GlobalSign Atlas rotates issuing CAs quarterly, so any pinned ... <year> Q<n> intermediate has a scheduled expiry as a working trust anchor, and each rotation silently breaks TSL loading for every operator until someone notices. GlobalSign Root R46 and GlobalSign Root E46 would cover the Commission's host across future rotations. The same applies to the other pinned intermediates in that file.
Two smaller observations
-
The failure is logged as Unable to process GET call for url [...]. Reason : [Network is unreachable]. That reason is the last address attempted: for a dual-stack host in an IPv4-only container, the IPv6 attempt fails after the real TLS rejection on IPv4, and its error is the one reported. A packet capture shows the server responding normally and the client sending a TLS alert. The message sends operators to look at egress and firewalls rather than at the truststore. Reporting the TLS failure, or all per-address errors, would save that detour.
-
An instance with an empty trust store answers /monitoring/heartbeat with UP and /monitoring/health with all components UP. Since that state turns every qualified signature into INDETERMINATE, it would be valuable if health, or a dedicated endpoint, exposed whether any trusted list is currently loaded and when it was last refreshed. We had to build an external canary that re-validates a known-good signed document in order to detect it.
Environment
SiVa 3.10.1 (upgraded from 3.9.0 today), running with a locally overridden truststore that adds the public CA roots. The defect above was verified against the stock release-3.10.1 artifact. Docker (Paketo image), IPv4-only container network.
Summary
The
tsl-ssl-truststore.p12shipped inrelease-3.10.1can no longer establish TLS tohttps://ec.europa.eu/tools/lotl/eu-lotl.xml. Every LOTL refresh fails PKIX. Because SiVa holds trust state in memory only, any instance restarted after the rotation comes back with no trust anchors at all, while/monitoring/heartbeatcontinues to answerUP. Qualified signatures then validate as INDETERMINATE with "The certificate chain for signature is not trusted", and a Baseline-LT signature is reported at Baseline-T.Cause
ec.europa.euis now issued byGlobalSign Atlas R46 OV TLS CA 2026 Q3, chaining toGlobalSign Root R46. The shipped truststore contains only the previous intermediate,GlobalSign Atlas R3 OV TLS CA 2025 Q3.Reproduce
Result:
Appending
GlobalSign Root R46to the truststore makes it pass. (-no-CApath -no-CAstorematters: without them openssl consults the system store and misleadingly reports OK, which hides the defect.)Suggested fix
Anchor the roots rather than the issuing intermediates. GlobalSign Atlas rotates issuing CAs quarterly, so any pinned
... <year> Q<n>intermediate has a scheduled expiry as a working trust anchor, and each rotation silently breaks TSL loading for every operator until someone notices.GlobalSign Root R46andGlobalSign Root E46would cover the Commission's host across future rotations. The same applies to the other pinned intermediates in that file.Two smaller observations
The failure is logged as
Unable to process GET call for url [...]. Reason : [Network is unreachable]. That reason is the last address attempted: for a dual-stack host in an IPv4-only container, the IPv6 attempt fails after the real TLS rejection on IPv4, and its error is the one reported. A packet capture shows the server responding normally and the client sending a TLS alert. The message sends operators to look at egress and firewalls rather than at the truststore. Reporting the TLS failure, or all per-address errors, would save that detour.An instance with an empty trust store answers
/monitoring/heartbeatwithUPand/monitoring/healthwith all componentsUP. Since that state turns every qualified signature into INDETERMINATE, it would be valuable if health, or a dedicated endpoint, exposed whether any trusted list is currently loaded and when it was last refreshed. We had to build an external canary that re-validates a known-good signed document in order to detect it.Environment
SiVa 3.10.1 (upgraded from 3.9.0 today), running with a locally overridden truststore that adds the public CA roots. The defect above was verified against the stock
release-3.10.1artifact. Docker (Paketo image), IPv4-only container network.