Skip to content

Shipped tsl-ssl-truststore cannot fetch the EU LOTL since ec.europa.eu rotated to GlobalSign Atlas R46 2026 Q3 #64

Description

@mkiskis

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

  1. 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.

  2. 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.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions