Add support for OAuth 2.0 Attestation-based client authentication#47962
Add support for OAuth 2.0 Attestation-based client authentication#47962mposolda merged 7 commits intokeycloak:mainfrom
Conversation
a8154d7 to
acbefee
Compare
16f3181 to
2b22fa0
Compare
2f7ef7d to
4d1a665
Compare
6374b0c to
3ce4b68
Compare
| execution.setParentFlow(clients.getId()); | ||
| execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE); | ||
| execution.setAuthenticator("attestation-based"); | ||
| execution.setPriority(5); |
There was a problem hiding this comment.
| execution.setPriority(5); | |
| execution.setPriority(60); |
Is it possible to use priority 60, so that this new authenticator is added after existing ones (also note other authenticators that they increase by 10)
There was a problem hiding this comment.
We can unfortunately not do that at the moment. AFAIU, the first (alternative) client authenticator that succeeds determines the outcome. e.g. if client secret passes, abca is not checked
ABCA is the stronger authorization mechanism - this is checked by OpenID conformance.
I have more details on how this plays with public clients and direct access grants if you care to know.
There was a problem hiding this comment.
Yes, ok. Maybe we can discuss that on one of next meetings? At least for this PR it should be OK since the feature is experimental.
| // when AttestationBasedClientAuthenticator is processed | ||
| // | ||
| String abcaAuthType = AttestationBasedClientAuthenticator.PROVIDER_ID; | ||
| if (client.isPublicClient() && factory.getId().equals(abcaAuthType) && Profile.isFeatureEnabled(Profile.Feature.CLIENT_AUTH_ABCA)) { |
There was a problem hiding this comment.
I wonder if this is really needed? I assume that if client is authenticating with ABCA, it should be always confidential client? Is it possible to make sure that only confidential clients authenticate with ABCA?
Public clients are not supposed to authenticate with any client authentication methods. They usually use just client_id parameter to "identify" themselves in the request andfor this purpose, we just use expectedClientAuthType set to KeycloakModelUtils.getDefaultClientAuthenticatorType() .
There was a problem hiding this comment.
There was indeed a change in the spec from “mechanism for public clients to authenticate” to “general client authentication mechanism”. The draft decoupled itself from OAuth’s client-type distinction entirely - it effectively introduces a third model: attested clients (orthogonal to public/confidential).
This shift aligns with the underlying idea: attestation provides hardware/software-backed proof which can substitute:
- secrets (confidential clients)
- or lack thereof (public clients)
Without this change, the AttestationBasedClientAuthenticator is not called for confidential clients. I added a test thereof.
Note, an invalid abca request or missing attester pub key currently fails in the AttestationBasedClientAuthenticator but is silently ignored because some other ClientAuthenticator succeeds - this might be a TODO
There was a problem hiding this comment.
Yes, more details would be good. Maybe we can discuss on one of our next meetings?
Just to doublecheck: Is it this specification draft, which you talk about https://datatracker.ietf.org/doc/html/draft-ietf-oauth-attestation-based-client-auth-08 ?
This change is OK in this PR, but we possibly need to figure this as a follow-up to avoid directly referencing the concrete authenticator from ClientAuthenticationFlow .
There was a problem hiding this comment.
It is actually https://www.ietf.org/archive/id/draft-ietf-oauth-attestation-based-client-auth-07.html
which I have bookmarked. I could however not find a reference to it in https://openid.net/specs/openid4vc-high-assurance-interoperability-profile-1_0-final.html @thomasdarimont could you perhaps confirm the exact abca spec version that we ought to use?
| if (!normalize(new URI(t.getHttpUri())).equals(normalize(uri))) | ||
| throw new DPoPVerificationException(t, "DPoP HTTP URL mismatch"); | ||
| URI dpopUri = new URI(t.getHttpUri()); | ||
| if (!normalize(dpopUri).equals(normalize(uri))) { |
There was a problem hiding this comment.
I think this change should not be needed and may have security implications.
For the setup of Keycloak behind reverse proxy, you may take a look on how to configure hostname. See the "Server" guides from https://www.keycloak.org/guides . Especially https://www.keycloak.org/server/hostname or https://www.keycloak.org/server/reverseproxy might be probably useful.
Also not sure if you are aware, but there is environment in the Keycloak org, which is maintained mostly by @tnorimat and which contains some support for running conformance tests: https://github.com/keycloak/keycloak-oauth-sig .
For example see here how Keycloak is configured (there are other config files as well): https://github.com/keycloak/keycloak-oauth-sig/blob/main/conformance-tests-env/docker-compose.yml#L159
There was a problem hiding this comment.
I already set the hostname
start-dev
--hostname=https://keycloak.nessustech.io:8443
--proxy-headers=xforwarded
--features=oid4vc-vci,oid4vc-vci-preauth-code,client-auth-abca
--bootstrap-admin-username=admin
--bootstrap-admin-password=*****
--db=postgres
--db-url=jdbc:postgresql://localhost:32543/keycloak
--db-username=postgres
--db-password=*****
--log-level=org.keycloak.protocol.oid4vc:debug,org.keycloak.services:debug,org.keycloak.events:debug,org.keycloak.authentication:debug,root:info
Can we perhaps investigate this separately?
There was a problem hiding this comment.
Yes, it would be good to investigate this separately. But will be good if you can remove this change from this PR as it is not directly related to OAuth 2.0 attestation-based client auth, but seems rather as a setup issue.
DPoP is supported feature and should not be updated to possibly insecure way due your (maybe incorrect) setup of hostname.
Do you have a chance to try https://github.com/keycloak/keycloak-oauth-sig and compare with your setup?
| public List<ProviderConfigProperty> getConfigProperties() { | ||
| return List.of(); | ||
| ProviderConfigProperty jwks = new ProviderConfigProperty(); | ||
| jwks.setName(OAUTH_CLIENT_ATTESTATION_CONFIG_ATTESTER_JWKS); |
There was a problem hiding this comment.
I assume this is temporary (as we briefly discussed on the meeting yesterday) and should probably identity provider mechanisms similarly like for example CLIENT_AUTH_FEDERATED is using (See for example FederatedJWTClientAuthenticator and related classes for the inspiration).
But it is probably fine this way for the initial PRand we can update later.
89512ae to
2e4d430
Compare
IngridPuppet
left a comment
There was a problem hiding this comment.
No new concerns in addition to Marek's. All my previous concerns were addressed.
872349d to
55d81b5
Compare
| execution.setParentFlow(clients.getId()); | ||
| execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE); | ||
| execution.setAuthenticator("attestation-based"); | ||
| execution.setPriority(5); |
There was a problem hiding this comment.
Yes, ok. Maybe we can discuss that on one of next meetings? At least for this PR it should be OK since the feature is experimental.
| // when AttestationBasedClientAuthenticator is processed | ||
| // | ||
| String abcaAuthType = AttestationBasedClientAuthenticator.PROVIDER_ID; | ||
| if (client.isPublicClient() && factory.getId().equals(abcaAuthType) && Profile.isFeatureEnabled(Profile.Feature.CLIENT_AUTH_ABCA)) { |
There was a problem hiding this comment.
Yes, more details would be good. Maybe we can discuss on one of our next meetings?
Just to doublecheck: Is it this specification draft, which you talk about https://datatracker.ietf.org/doc/html/draft-ietf-oauth-attestation-based-client-auth-08 ?
This change is OK in this PR, but we possibly need to figure this as a follow-up to avoid directly referencing the concrete authenticator from ClientAuthenticationFlow .
| if (!normalize(new URI(t.getHttpUri())).equals(normalize(uri))) | ||
| throw new DPoPVerificationException(t, "DPoP HTTP URL mismatch"); | ||
| URI dpopUri = new URI(t.getHttpUri()); | ||
| if (!normalize(dpopUri).equals(normalize(uri))) { |
There was a problem hiding this comment.
Yes, it would be good to investigate this separately. But will be good if you can remove this change from this PR as it is not directly related to OAuth 2.0 attestation-based client auth, but seems rather as a setup issue.
DPoP is supported feature and should not be updated to possibly insecure way due your (maybe incorrect) setup of hostname.
Do you have a chance to try https://github.com/keycloak/keycloak-oauth-sig and compare with your setup?
Signed-off-by: Thomas Diesler <[email protected]>
Signed-off-by: Thomas Diesler <[email protected]>
Signed-off-by: Thomas Diesler <[email protected]>
Signed-off-by: Thomas Diesler <[email protected]>
Signed-off-by: Thomas Diesler <[email protected]>
Signed-off-by: Thomas Diesler <[email protected]>
Signed-off-by: Thomas Diesler <[email protected]>
|
Reverse unrelated DPoP changes |
mposolda
left a comment
There was a problem hiding this comment.
@tdiesler Thanks!
@IngridPuppet Thank you as well for the review!
FYI. I've created dedicated issue #48265 for this PR and added it as subtask to #43136 (parent issue having other ABCA related issues).
Also will create some follow-up GH issues for additional things discussed in the PR review of this PR.
…keycloak#47962) closes keycloak#48265 Signed-off-by: Thomas Diesler <[email protected]>
closes #48265