[OID4VCI] Harden key attestation x5c certificate validation - #51042
Conversation
Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com>
There was a problem hiding this comment.
Pull request overview
Hardens OID4VCI key-attestation certificate validation by introducing dedicated X.509 trust material and policies.
Changes:
- Adds isolated PKIX validation with EKU, end-entity, and revocation enforcement.
- Adds trust-provider and Admin Console X.509 configuration.
- Expands documentation and integration/UI tests.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
tests/base/.../OID4VCKeyAttestationTest.java |
Tests attestation certificate policies. |
services/.../X5cKeyUtils.java |
Decodes chains and converts leaf keys. |
services/.../TrustedAttestationKeyResolver.java |
Resolves configured X.509 trust. |
services/.../StaticAttestationKeyResolver.java |
Supports static X.509 trust material. |
services/.../JwtProofValidator.java |
Preserves proof-key x5c conveyance. |
services/.../AttestationX509CertificateValidator.java |
Implements PKIX policy validation. |
services/.../AttestationValidatorUtil.java |
Removes system-truststore attestation validation. |
services/.../AttestationKeyResolver.java |
Adds certificate-chain resolution API. |
services/.../DefaultTrustIdentityProviderConfig.java |
Defines and validates X.509 settings. |
services/.../DefaultTrustIdentityProvider.java |
Exposes X.509 trust material. |
services/.../TrustMaterialResolver.java |
Resolves X.509 material by provider. |
server-spi-private/.../X509TrustMaterial.java |
Models trust anchors and policy. |
server-spi-private/.../TrustMaterialIdentityProvider.java |
Extends the trust-provider SPI. |
js/.../default-trust.spec.ts |
Tests new Admin Console controls. |
js/.../DefaultTrustSettings.tsx |
Adds X.509 configuration fields. |
js/.../messages_en.properties |
Adds X.509 setting labels and help. |
docs/.../proofs.adoc |
Documents attestation trust configuration. |
Unreported flaky test detectedIf the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR. org.keycloak.testsuite.forms.BrowserFlowTest#testUserWithOneAdditionalFactorOtpSuccessorg.keycloak.testsuite.forms.MultipleTabsLoginTest#multipleTabsParallelLoginTestWithAuthSessionExpiredAndRequiredActionKeycloak CI - Forms IT (chrome) org.keycloak.testsuite.oauth.ClientAuthPostMethodTest#testPostAuthenticationNotAllowedWhenBasicRequestedorg.keycloak.testsuite.oauth.ClientAuthPostMethodTest#testBasicAuthenticationNotAllowedWhenPostRequestedorg.keycloak.testsuite.forms.MultipleTabsLoginTest#testEmptyBaseUrlKeycloak CI - Forms IT (firefox) org.keycloak.testsuite.forms.MultipleTabsLoginTest#multipleTabsParallelLoginTestKeycloak CI - Forms IT (firefox) |
Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com>
Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com>
Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com>
Unreported flaky test detectedIf the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR. org.keycloak.testsuite.forms.BrowserFlowTest#testUserWithOneAdditionalFactorOtpSuccessorg.keycloak.testsuite.forms.MultipleTabsLoginTest#multipleTabsParallelLoginTestWithAuthSessionExpiredInTheMiddleKeycloak CI - Forms IT (chrome) org.keycloak.testsuite.forms.MultipleTabsLoginTest#multipleTabsParallelLoginTestWithAuthSessionExpiredAndRefreshInTab1Keycloak CI - Forms IT (chrome) |
mposolda
left a comment
There was a problem hiding this comment.
@forkimenjeckayang Nice, Thanks for the PR!
I've added some comments inline. Also, one additional question: It looks that there are not much automated tests (or maybe none), which tests JwtProofValidator testing the full x5c validations (In other words: It looks that all the automated tests calling AttestationX509CertificateValidator.validate are calling it from AttestationProofValidator, but no tests for calling it from JwtProofValidator). Am I correct or did I overlooked?
IMO it is fine to go with this PR as is and then possibly create follow-up issue for add some more testing to JwtProofValidator. But just checking :-)
| . Create or configure a trust-material identity provider, such as **Default Trust**, that exposes the trusted attester public keys or X.509 trust anchors. | ||
| ** For `kid`-based attestations, configure a JWKS URL or validating public key. | ||
| ** For `x5c`-based attestations, enable **Use X.509 attestation trust**, provide a PEM bundle containing only self-signed device-vendor attestation CA roots, and list the Extended Key Usage OIDs allowed for attestation end-entity certificates. | ||
| ** Certificate revocation checking is enabled by default. Disable it only when the attestation ecosystem uses a separate mandatory revocation mechanism that is enforced elsewhere. |
There was a problem hiding this comment.
Is it possible to add more details about this option? For example:
- How exactly is revocation verified?
- Does the leaf certificate (or ca certificate) needs to have some elements in itself, which allow to validate this? Does it use revocation list, OCSP or something similar?
I am asking as it is not clear to me how this works and it looks that automated test OID4VCKeyAttestationTest does not have any successful test with certificate revocation checking enabled. If it is not clear, would it be possible to remove this option Certificate revocation checking for now and instead add some WARNING to this documentation that certificate revocation checking is not yet supported?
There was a problem hiding this comment.
I traced the current behavior through the configured PKIX provider. In the standard crypto provider, enabling this flag invokes Bouncy Castle’s default CRL checker, but we do not provide a CRL store or configure CRL Distribution Point retrieval, and no OCSP checker is wired into this path. Therefore, there is no supported successful revocation configuration through Keycloak, which also explains why the test only covers failure when revocation information is unavailable.
I will remove the option for now, explicitly disable provider-default PKIX revocation, and document the limitation with a warning. Proper CRL/OCSP support should be handled separately with explicit configuration, Keycloak-managed network access, and successful/revoked/unavailable test coverage.
| . Create or configure a trust-material identity provider, such as **Default Trust**, that exposes the trusted attester public keys. The identity provider must be configured with either a JWKS URL or a validating public key. | ||
| . Create or configure a trust-material identity provider, such as **Default Trust**, that exposes the trusted attester public keys or X.509 trust anchors. | ||
| ** For `kid`-based attestations, configure a JWKS URL or validating public key. | ||
| ** For `x5c`-based attestations, enable **Use X.509 attestation trust**, provide a PEM bundle containing only self-signed device-vendor attestation CA roots, and list the Extended Key Usage OIDs allowed for attestation end-entity certificates. |
There was a problem hiding this comment.
If I understand correctly, for x5c-based attestations, it is required that extended key usage needs to be configured. Is it please possible to mention this more clearly in the documentation, that it is required in the configuration and also required to be present in the certificate? Also possibly with some example value, which is typically used for this (not sure if 1.3.6.1.4.1.55555.1.1 is typical OID used for this purpose or if different OIDs are usually used for this?)
There was a problem hiding this comment.
@forkimenjeckayang One additional thing: The OIDCIdentityProvider also implements TrustMaterialIdentityProvider . However it doesn't override resolveX509Trust method. So if I understand correctly, for OIDCIdentityProvider, it is not yet supported to handle x5c-based attestations? Might be likely good to document this limitation of OIDC Identity provider with some warning here?
There was a problem hiding this comment.
I clarified that the EKU configuration is mandatory and that the attestation leaf certificate must contain at least one EKU OID exactly matching a configured value. OID4VCI does not define a universal key-attestation EKU, so the documentation directs administrators to use the device-vendor or trust-framework OID and provides a documentation-only private-PKI example.
I also added a warning that the built-in OIDC identity provider exposes only JWK trust material. It cannot validate x5c chains because it does not implement X.509 trust resolution; x5c configurations must currently use Default Trust or a custom X.509-capable trust-material provider.
| ---- | ||
|
|
||
| For a JWT proof with a `key_attestation` JOSE header, or for an `attestation` proof, {project_name} resolves the attester signing key from the configured identity providers. Key resolution can use the attestation JWT header and payload, including the `kid`, `alg`, and `iss` values. If no configured identity provider exposes a matching trusted key, proof validation fails. | ||
| For a JWT proof with a `key_attestation` JOSE header, or for an `attestation` proof, {project_name} resolves the attester signing key from the configured identity providers. Key resolution can use the attestation JWT header and payload, including the `kid`, `alg`, and `iss` values. If the attestation contains `x5c`, {project_name} builds the certificate path independently against each configured attestation trust provider and enforces the provider's EKU and revocation policy. The signing certificate must be an end-entity certificate, and configured trust anchors must be self-signed CA roots. A root included in the presented chain does not become trusted merely by being present; it must match a configured trust anchor. The JVM system truststore is never used as attestation trust. If no configured identity provider trusts the key or certificate chain, proof validation fails. |
There was a problem hiding this comment.
Would be good to replace EKU with something like Extended key usage (EKU) . As it may not be clear what the shortcut means...
It could be nice if this documentation paragraph is a bit expanded (or converted into dedicated section) - maybe with some example picture/diagram of 2 certificates like ca and leaf and example showing that ca should be configured on IDP side and both leaf and ca included in the x5c of proof etc. As this topic is not trivial and might be nice to make sure that documentation is not confusing for people, who are not deeply familiar with the topic...)
There was a problem hiding this comment.
I expanded this into a dedicated X.509 certificate-chain validation section with a diagram showing the relationship between the certificates presented in x5c and the root/EKU policy configured in the identity provider.
I clarified one detail: the leaf must be x5c[0], and any required intermediate certificates follow it. The configured root may also be included as the final entry, but it is not required because it is already available as the trust anchor. For a leaf issued directly by the configured root, x5c can therefore contain only the leaf. Including a root in x5c never makes it trusted by itself.
| String leafPrivateKeyPem = X5C_TEST_CERTIFICATE_CHAIN.leafPrivateKeyPem(); | ||
| runOnServer.run(session -> { | ||
| setupSessionContext(session); | ||
| runAttestationWithX5cCertificateChain(session, cNonce, caCertificatePem, leafCertificatePem, |
There was a problem hiding this comment.
nitpick: It seems confusing that using revocationEnabled=null as parameter of this method runAttestationWithX5cCertificateChain means testing with system truststore. Is it perhaps possible to rather introduce new flag to the method runAttestationWithX5cCertificateChain instead of mis-using revocationEnabled flag?
There was a problem hiding this comment.
This has been addressed as part of removing the unsupported certificate-revocation option. runAttestationWithX5cCertificateChain now has a dedicated configureTrustProvider boolean. The system-truststore regression passes false, while tests using the configured X.509 attestation trust provider pass true. There is no longer a nullable revocationEnabled parameter or overloaded meaning.
Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com>
You are correct. The existing |
mposolda
left a comment
There was a problem hiding this comment.
@forkimenjeckayang Thanks for the updates! I've added one comment inline, however I don't think that it is a blocker for this PR. Hopefully can be addressed as a follow-up...
|
|
||
| The first `x5c` entry must be the certificate containing the public key that signed the attestation JWT. Each following entry must be an issuer of the previous certificate. The entries are base64-encoded DER certificates, not PEM values or base64url-encoded values. | ||
|
|
||
| Include any intermediate CA certificates required to connect the leaf to the configured root. The root certificate can be the final `x5c` entry, but it is not required because the same root is already configured as a trust anchor. For example, if the configured root directly issued the leaf, `x5c` can contain only the leaf certificate. |
There was a problem hiding this comment.
When checking this, I am thinking about this corner-case scenario:
- default-trust identity provider has configured certificate with
subject=my-ca, issuer=my-ca, which is self-signed by ca-keypair1 - The proof has
x5celement with something like this (single leaf certificate, root certificate is omitted):- cert[0]:
subject=my-leaf, issuer=my-ca, which is signed by ca-keypair2
- cert[0]:
In other words, the issuer from x5c is same value my-ca as the trusted CA certificate from identity provider configuration. However the x5c is signed by incorrect keypair ca-keypair2 (not by the ca-keypair1 from IDP config).
Just thinking about possible attack, when attacker knows the name of configured CA, however attacker creates his own CA certificate of same name, but signed by his own keypair (ca-keypair2), which he would use to sign his own root certificate and leaf certificate attached.
Do you think that such scenario would be rejected? Might be good to have automated test for this though?
mposolda
left a comment
There was a problem hiding this comment.
@forkimenjeckayang Approving, Thanks! IMO the comment #51042 (comment) can be handled as a follow-up issue (either fixing this or just adding an automated test).
…#51042) closes keycloak#50518 Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com> Signed-off-by: alehhu <159355663+alehhu@users.noreply.github.com>
Summary
This change hardens validation of X.509 certificate chains supplied through the
x5cheader of OID4VCI key attestations.Previously, attestation chains were validated against the JVM system truststore,
which allowed certificates issued by general Web PKI certificate authorities.
Revocation checking was disabled and no Extended Key Usage policy was enforced.
The implementation now:
by the trust-material identity providers configured for the OID4VCI client;
certificate-signing usage;
path does not yet configure CRL or OCSP sources, and documents that revocation
must be enforced externally until dedicated support is implemented;
x5cas a key-conveyance mechanism for ordinary JWT proofs, whereattestation trust validation does not apply;
trust.
closes #50518