EU LOTL Commission cert rotation runbook

Operator runbook for pinning + rotating the European Commission's LOTL (List of Trusted Lists) XMLDSig signing cert in Koder Signer. Per Decision (EU) 2015/1505 (and successors), the trust chain for the eIDAS profile rests on the Commission-published cert bundle. This runbook covers initial bootstrap, rotation, and the operator- side checks Koder Signer expects.

EU LOTL Commission cert rotation — Koder Signer operator runbook

Why this runbook exists

The EU LOTL XML at https://ec.europa.eu/tools/lotl/eu-lotl.xml is signed by a cert the European Commission publishes via Commission Implementing Decision. Koder Signer's eu_lotl.Verifier validates incoming LOTL fetches against that cert before trusting any of the 27+ national-TL pointers inside.

Two trust modes are supported:

Mode What When
*rustonfirst-use (TOFU)* Verifier harvests the cert from the first successfully-fetched LOTL and pins it from then on. Bootstrap / dev / staging only
*inned bundle* Operator loads the Commission cert(s) externally via --eu-lotl-certs flag (or KSIGNER_EU_LOTL_CERTS env). *roduction*— required by Decision (EU) 2015/1505

TOFU is fundamentally trustonfirst-fetch — an attacker who can intercept the first refresh wins. The legal trust chain in eIDAS rests on the Commission publication, not on whatever cert the LOTL HTTP response happens to carry.

Initial bootstrap (new deployment)

Step 1 — Download the current Commission signing cert

The Commission publishes the current signing cert chain via its trustedlist browser at <https:/idas.ec.europa.euefdatlbrowser/>. The legal basis (Decision (EU) 2019/1130 et seq.) names the cert fingerprint.

mkdir -p /etc/koder-signer/eu-lotl-certs
# Pull the cert from the current LOTL body (sanity-check fingerprint
# against eIDAS portal before trusting):
curl -fsSL https://ec.europa.eu/tools/lotl/eu-lotl.xml |
  python3 -c '
import sys, base64, xml.etree.ElementTree as ET
NS = {"ds": "http://www.w3.org/2000/09/xmldsig#"}
root = ET.fromstring(sys.stdin.read())
cert_b64 = root.find(".//ds:X509Certificate", NS).text
der = base64.b64decode("".join(cert_b64.split()))
print("-----BEGIN CERTIFICATE-----")
print(base64.encodebytes(der).decode().strip())
print("-----END CERTIFICATE-----")
' > /etc/koder-signer/eu-lotl-certs/01-current.pem

Step 2 — Verify the fingerprint

Crosscheck the SHA256 fingerprint against the eIDAS portal entry for the current European Commission operator. *o not skip this step*— this is the whole point of the runbook (otherwise you've just laundered TOFU into a file).

openssl x509 -in /etc/koder-signer/eu-lotl-certs/01-current.pem \
  -noout -fingerprint -sha256
# SHA256 Fingerprint=XX:XX:...:XX
# Compare with the value listed at:
#   https://eidas.ec.europa.eu/efda/tl-browser/#/screen/tl/EU

Step 3 — Configure Koder Signer

Set the flag or environment variable:

# In /etc/koder-signer/koder-signer.env (or systemd EnvironmentFile):
KSIGNER_EU_LOTL_CERTS=/etc/koder-signer/eu-lotl-certs/

# Or via flag on the daemon command line:
ksigner --eu-lotl-certs /etc/koder-signer/eu-lotl-certs/

The path can be either a single .pem file or a directory of .pem / .crt files (handy during rotation overlap windows — see Step 4).

Step 4 — Restart and verify

systemctl restart koder-signer
journalctl -u koder-signer -n 50 --no-pager | grep eu_lotl
# Expect:
#   ksigner | eu_lotl pinned Commission certs loaded path=... cert_count=1
#   ksigner | eu_lotl refreshed old_sequence=0 new_sequence=387 national_tls=27
# NOT:
#   ksigner | eu_lotl using trust-on-first-use

Rotation procedure

The Commission rotates the signing cert periodically (every ~3–5 years). Watch for announcements at the eIDAS portal or by subscribing to https://ec.europa.eu/tools/lotl/eu-lotl.xml ETag changes that correlate with <X509Certificate> body churn.

Rotation timeline

  1. *-30 days (or earlier):*Commission publishes the NEXT cert

    at the eIDAS portal alongside the CURRENT one. Both certs are considered valid during the overlap window.

  1. *30 to T0:*add the NEW cert to your directory *ithout

    removing the OLD one*— Koder Signer accepts any cert in the bundle.

`bash curl ... > etckodersigner/eulotlcerts/02next.pem openssl x509 in etckodersignereulotlcerts02next.pem noout -fingerprint -sha256 # verify against portal! systemctl restart koder-signer # picks up the new cert at boot

`

  1. *+0:*Commission switches the LOTL to be signed by the NEW

    cert. Koder Signer's refresh continues seamlessly because the bundle now contains both. journalctl should still show eu_lotl refreshed with no signature failures.

  1. *+30 days (or later):*retire the OLD cert. Move it to

    archive/ and restart:

`bash mv etckodersigner/eulotlcerts/01current.pem etckodersigner/eulotlcertsarchive01old-$(date +%Y%m%d).pem mv etckodersigner/eulotlcerts/02next.pem etckodersigner/eulotlcerts/01current.pem systemctl restart koder-signer

`

(The numeric prefix ordering keeps the "current" file name stable for monitoring scripts; the file content has rotated.)

Failure modes & symptoms

Symptom Cause Fix
Daemon refuses to start, eu_lotl pinned cert load failed in logs KSIGNER_EU_LOTL_CERTS path missing or contains no .pem/.crt files Verify path exists; populate with at least one cert
Daemon starts but logs eu_lotl using trust-on-first-use KSIGNER_EU_LOTL_CERTS unset Set the env var / flag per Step 3
KSIGNER-EIDAS-2001 lotl_signature_invalid on every refresh after T-0 (rotation) OLD cert in bundle, NEW cert is what the LOTL now uses Apply Step 2 of rotation — add the NEW cert without removing OLD
KSIGNER-EIDAS-3001 lotl_stale_for_ades Store hasn't refreshed in > 24h (refresher failures piled up) Check journalctl -u koder-signer | grep "eu_lotl refresh failure" for the underlying cause
KSIGNER-EIDAS-3002 trust_not_loaded (503 on every eu+ades request) Daemon hasn't completed first refresh Wait ~30s after boot; if persistent, check network reachability to ec.europa.eu

Compliance note

For audit purposes, the path mapping Decision (EU) 2015/1505 → bundle at /etc/koder-signer/eu-lotl-certs/ should be documented in the operator's eIDAS compliance dossier alongside this runbook reference. Fingerprint verification log (Step 2) is the audit artefact.

Source: ../home/koder/dev/koder/meta/docs/stack/runbooks/eu-lotl-cert-rotation.kmd