8000 [OID4VC] Support ETSI Lists of Trusted Entities by dominikschlosser · Pull Request #54522 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

[OID4VC] Support ETSI Lists of Trusted Entities - #54522

Open
dominikschlosser wants to merge 1 commit into
keycloak:mainfrom
dominikschlosser:etsi-tl
Open

dominikschlosser wants to merge 1 commit into
keycloak:mainfrom
dominikschlosser:etsi-tl

Conversation

@dominikschlosser
@dominikschlosser dominikschlosser commented Nov 1, 2026 •
Copy link
Copy Markdown
Contributor

Closes #48106

Adds an etsi-trust-list identity provider that uses a signed ETSI TS 119 602 List of Trusted Entities (LoTE) as trust material for OID4VCI key attestations, attestation-based client authentication and OID4VP. This is necessary to use Keycloak as issuer or verifier in the EUDI ecosystem.

  • The list signature is verified against configured certificates, and the LoTE type and service types are enforced.
  • Listed CA certificates become X.509 trust anchors. x5c is required, as in HAIP, and keys cannot be selected by kid. Listed end entity certificates are ignored.
  • A list that fails to download or verify, or is past NextUpdate, fails the trust decision until a valid list is available. Lists without NextUpdate are not trusted at all.
  • X509TrustMaterial now accepts subordinate CAs as trust anchors. Real lists contain them, for example the German sandbox PID list.
  • Adds the admin console settings and documentation.

Lists of Trusted Lists (#54562) and Certificate Revocation (using CRL, #54561) are follow ups.
XML Trusted Lists are not supported for now, but may be needed in the future as well (QEAA providers might use them).

Live demo to test it: https://demo.eudi-test.dev/etsi-tl/
Keycloak admin UI with the changes in this PR: https://keycloak.eudi-test.dev/etsi-tl/admin/wallet-demo/console/ (Login: viewer/viewer)

Wallet/Key Attestation LoTE: https://eudi-test.dev/api/trustlists/wallet-provider
Credential LoTE (for OID4VP): https://keycloak.eudi-test.dev/trust-lists/issuers.jwt

German Sandbox LoTEs: https://bmi.usercontent.opencode.de/eudi-wallet/test-trust-lists/

Note: Client attestations with an ETSI trust list need x5c support in AttestationBasedClientAuthenticator, which #52883 adds. Key attestations and OID4VP work without it.

Copilot AI balanced review requested due to automatic review settings November 1, 2026 23:58
@dominikschlosser
dominikschlosser requested review from a team as code owners November 1, 2026 23:58
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Refresh throttling can leave expired trust material usable, while public-key-based deduplication can discard valid renewed CA certificates and reject valid chains.

2 open issues
What changed in this PR

Adds ETSI List of Trusted Entities support to Keycloak’s trust-material identity providers, enabling EUDI Wallet trust for issuance, attestation-based client authentication, and presentation flows.

Changes:

  • Adds signed trust-list loading with list-type, service-scope, and freshness checks.
  • Supports subordinate CA trust anchors and client-attestation x5c validation.
  • Adds Admin Console configuration, documentation, and integration tests.
File Description
tests/​base/​src/​test/​java/​org/​keycloak/​tests/​oid4vc/​issuance/​signing/​OID4VCKeyAttestationTest.java Updates expectations for subordinate CA anchors.
tests/​base/​src/​test/​java/​org/​keycloak/​tests/​oid4vc/​abca/​OIDCMockClientAttester.java Supports certificate chains in mock attestations.
tests/​base/​src/​test/​java/​org/​keycloak/​tests/​oid4vc/​abca/​OIDCAttestationBasedClientAuthenticationTest.java Adds trust-list-backed authentication coverage.
tests/​base/​src/​test/​java/​org/​keycloak/​tests/​broker/​trust/​TrustListTestSupport.java Generates certificates and serves signed test lists.
tests/​base/​src/​test/​java/​org/​keycloak/​tests/​broker/​trust/​EtsiTrustListIdentityProviderTest.java Tests trust selection, validation, and refresh behavior.
services/​src/​main/​resources/​META-INF/​services/​org.keycloak.broker.provider.IdentityProviderFactory Registers the ETSI provider.
services/​src/​main/​java/​org/​keycloak/​broker/​trust/​etsi/​ListOfTrustedEntities.java Models trust-list JSON fields.
services/​src/​main/​java/​org/​keycloak/​broker/​trust/​etsi/​EtsiTrustListPublicKeyLoader.java Downloads, verifies, and extracts list certificates.
services/​src/​main/​java/​org/​keycloak/​broker/​trust/​etsi/​EtsiTrustListIdentityProviderFactory.java Defines provider settings and feature availability.
services/​src/​main/​java/​org/​keycloak/​broker/​trust/​etsi/​EtsiTrustListIdentityProviderConfig.java Parses and validates configuration.
services/​src/​main/​java/​org/​keycloak/​broker/​trust/​etsi/​EtsiTrustListIdentityProvider.java Exposes listed CA certificates as trust anchors.
services/​src/​main/​java/​org/​keycloak/​broker/​trust/​DefaultTrustIdentityProviderConfig.java Updates validation wording for CA anchors.
services/​src/​main/​java/​org/​keycloak/​broker/​oid4vp/​OID4VPIdentityProvider.java Removes the trust-list resolver TODO comment.
services/​src/​main/​java/​org/​keycloak/​authentication/​authenticators/​client/​AttestationBasedClientAuthenticator.java Adds attestation certificate-chain validation.
server-spi-private/​src/​main/​java/​org/​keycloak/​broker/​provider/​X509TrustMaterial.java Permits subordinate CA trust anchors.
js/​apps/​admin-ui/​src/​identity-providers/​routes/​IdentityProviderEtsiTrustList.tsx Defines the provider creation route.
js/​apps/​admin-ui/​src/​identity-providers/​routes.ts Registers the new route.
js/​apps/​admin-ui/​src/​identity-providers/​add/​EtsiTrustListSettings.tsx Renders trust-list configuration fields.
js/​apps/​admin-ui/​src/​identity-providers/​add/​DetailSettings.tsx Integrates provider-specific settings.
js/​apps/​admin-ui/​src/​identity-providers/​add/​AddEtsiTrustList.tsx Adds the provider creation form.
js/​apps/​admin-ui/​maven-resources/​theme/​keycloak.v2/​admin/​messages/​messages_en.properties Updates CA certificate help text.
docs/​documentation/​server_admin/​topics/​oid4vci/​proofs.adoc Documents ETSI trust-list setup and trust behavior.

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings November 2, 2026 00:32
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Critical security gaps allow expired trust-list CAs to remain trusted during cache reload throttling and permit missing x5c chains to bypass mandatory X.509 validation through kid fallback.

1 open issue
2 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings November 2, 2026 01:56
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Domain-owner security review is needed to validate ETSI/JAdES conformance, real signed LoTE interoperability, and broader X.509 anchor trust semantics beyond the synthetic-list tests.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The service-scope test uses an end-entity certificate independently rejected by isCaCertificate, masking missing service-type filtering; using Pki.ROOT would make it detect that regression.

1 open issue

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings November 3, 2026 10:35
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

Copilot AI balanced review requested due to automatic review settings November 3, 2026 10:53
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

Copilot AI balanced review requested due to automatic review settings November 3, 2026 16:09
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

Copilot AI balanced review requested due to automatic review settings November 3, 2026 16:42
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

Signed-off-by: Dominik Schlosser <dominik.schlosser@gmail.com>
Copilot AI balanced review requested due to automatic review settings November 3, 2026 17:43
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@Awambeng Awambeng left a comment •
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the PR. I’ve left a few minor comments.

Also, adding copyright headers to the new files is not necessary.

if (getServiceTypes().isEmpty()) {
throw new IllegalArgumentException("At least one trust list service type is required");
}
try {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The isEmpty check is inside the try, so its own exception gets caught by the catch below. An empty field ends up reporting "must be a valid PEM bundle" instead of "at least one trust list signing certificate is required". Move the check above the try. Default Trust has the same problem, so this is copied, but no need to copy it.

try {
return Integer.parseInt(refreshInterval.trim());
} catch (NumberFormatException e) {
return -1;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Returning -1 for a value that does not parse means abc is reported as "must be a positive number of seconds", which sends the admin in the wrong direction. A separate message for unparseable input would be clearer.

/**
* Exposes the CA certificates of an ETSI TS 119 602 List of Trusted Entities as X.509 trust anchors.
* HAIP requires the signing certificate in x5c and keeps the trust anchor out of it, so an artifact
* needs a chain to a listed CA. No key can be selected by kid and listed end entity certificates are
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Clause 6.6.3 allows any X.509 certificate in ServiceDigitalIdentity, not only CA certificates. We drop end entity certificates, so a scheme that publishes a leaf gives us nothing to trust. The docs do say this, just want to be sure it is a deliberate scope decision and not something we plan to support later.

}

@Override
public void close() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

reloadKeys() is not overridden here, so GET /identity-providers/{alias}/reload-keys returns false and there is no way to force a refresh. Default Trust, OIDC and SAML all override it. Worth adding so an admin does not have to wait out the refresh interval after a certificate is withdrawn.

import org.jboss.logging.Logger;

/**
* Loads the certificates of the configured service types from a signed ETSI TS 119 602 V1.1.1 trust
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Clause E.4 requires a compact JAdES Baseline B signature. What happens here is a plain JWS verify against the configured certificates. It is safe, just more lenient than the spec. A sentence in the docs would help so nobody expects full JAdES validation.

return new PublicKeysWrapper(toKeys(list), Math.min(refreshAt, nextUpdate));
}

private void verifySignature(JWSInput jws) throws JWSInputException {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Clause 6.8.0 of TS 119 602 says the C and O fields of the signing certificate subject shall match SchemeTerritory and SchemeOperatorName. We never read those two fields. The German sandbox list would not pass that check either (cert has C=DE, O=Trust Lists, list says EU and SPRIND GmbH), so I assume leaving it out is deliberate. Could you note that somewhere so it does not look like an oversight?

Trust list signing certificates:: A PEM bundle of the certificates allowed to sign the list. A list is used only when one of these certificates verifies its signature. To rotate the signing key, configure the old and the new certificate.
LoTE type:: The required type of the list, for example `+http://uri.etsi.org/19602/LoTEType/EUWalletProvidersList+`. {project_name} rejects a list whose `LoTEType` value does not match.
Service types:: The service type identifiers whose certificates are trusted, for example `+http://uri.etsi.org/19602/SvcType/WalletSolution/Issuance+`.
Service statuses:: Services with one of these statuses are trusted. Services without a status are always trusted.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One detail is missing: with Service statuses left empty, services that do carry a status are ignored (see servicesWithAStatusAreIgnoredWithoutConfiguredStatuses). Worth spelling that out here.

.type("trustlist+jwt")
.jsonContent(Map.of("LoTE", Map.of(
"ListAndSchemeInformation", scheme,
"TrustedEntitiesList", List.of(Map.of("TrustedEntityServices", services)))))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The test list is thinner than a real one. A published list also carries TrustedEntityInformation, ListIssueDateTime, SchemeTerritory and friends. The mapping handles them fine today, but one test with the full shape would stop someone breaking that later.

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.

[OID4VCI] Support for ETSI Trust Lists

3 participants

0