From be0ba79daa55e3f88f98bcba8eb13cd32f2c4efd Mon Sep 17 00:00:00 2001 From: Takashi Norimatsu Date: Tue, 25 Feb 2020 10:02:40 +0900 Subject: [PATCH] KEYCLOAK-7997 Implement Client Registration Metadata based on Mutual TLS --- .../oidc/OIDCClientRepresentation.java | 11 +++++ .../oidc/OIDCAdvancedConfigWrapper.java | 9 ++++ .../oidc/DescriptionConverter.java | 7 ++++ .../client/OIDCClientRegistrationTest.java | 42 +++++++++++++++++++ 4 files changed, 69 insertions(+) diff --git a/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java b/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java index c8cf6ffead..9b81228502 100644 --- a/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java @@ -100,6 +100,8 @@ public class OIDCClientRepresentation { // https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.5 private Boolean tls_client_certificate_bound_access_tokens; + private String tls_client_auth_subject_dn; + // OIDC Session Management private List post_logout_redirect_uris; @@ -446,4 +448,13 @@ public class OIDCClientRepresentation { public void setTlsClientCertificateBoundAccessTokens(Boolean tls_client_certificate_bound_access_tokens) { this.tls_client_certificate_bound_access_tokens = tls_client_certificate_bound_access_tokens; } + + public String getTlsClientAuthSubjectDn() { + return tls_client_auth_subject_dn; + } + + public void setTlsClientAuthSubjectDn(String tls_client_auth_subject_dn) { + this.tls_client_auth_subject_dn = tls_client_auth_subject_dn; + } + } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java index 3cf255b8e9..78572be1e0 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java @@ -17,6 +17,7 @@ package org.keycloak.protocol.oidc; +import org.keycloak.authentication.authenticators.client.X509ClientAuthenticator; import org.keycloak.jose.jws.Algorithm; import org.keycloak.models.ClientModel; import org.keycloak.representations.idm.ClientRepresentation; @@ -118,6 +119,14 @@ public class OIDCAdvancedConfigWrapper { setAttribute(OIDCConfigAttributes.USE_MTLS_HOK_TOKEN, val); } + public String getTlsClientAuthSubjectDn() { + return getAttribute(X509ClientAuthenticator.ATTR_SUBJECT_DN); + } + + public void setTlsClientAuthSubjectDn(String tls_client_auth_subject_dn) { + setAttribute(X509ClientAuthenticator.ATTR_SUBJECT_DN, tls_client_auth_subject_dn); + } + public String getPkceCodeChallengeMethod() { return getAttribute(OIDCConfigAttributes.PKCE_CODE_CHALLENGE_METHOD); } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java index fb316f731e..c92ef3fea2 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java @@ -121,6 +121,10 @@ public class DescriptionConverter { else configWrapper.setUseMtlsHoKToken(false); } + if (clientOIDC.getTlsClientAuthSubjectDn() != null) { + configWrapper.setTlsClientAuthSubjectDn(clientOIDC.getTlsClientAuthSubjectDn()); + } + if (clientOIDC.getIdTokenSignedResponseAlg() != null) { configWrapper.setIdTokenSignedResponseAlg(clientOIDC.getIdTokenSignedResponseAlg()); } @@ -215,6 +219,9 @@ public class DescriptionConverter { } else { response.setTlsClientCertificateBoundAccessTokens(Boolean.FALSE); } + if (config.getTlsClientAuthSubjectDn() != null) { + response.setTlsClientAuthSubjectDn(config.getTlsClientAuthSubjectDn()); + } if (config.getIdTokenSignedResponseAlg() != null) { response.setIdTokenSignedResponseAlg(config.getIdTokenSignedResponseAlg()); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java index 2792d992a4..d0bed60a7a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java @@ -22,6 +22,7 @@ import org.junit.Before; import org.junit.Test; import org.keycloak.OAuth2Constants; import org.keycloak.admin.client.resource.ClientsResource; +import org.keycloak.authentication.authenticators.client.X509ClientAuthenticator; import org.keycloak.client.registration.Auth; import org.keycloak.client.registration.ClientRegistrationException; import org.keycloak.client.registration.HttpErrorException; @@ -422,6 +423,47 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { clientsResource.get(samlClient.getId()).update(samlClient); } + @Test + public void testTlsClientAuthSubjectDn() throws Exception { + OIDCClientRepresentation response = null; + OIDCClientRepresentation updated = null; + try { + // create (no specification) + OIDCClientRepresentation clientRep = createRep(); + clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.TLS_CLIENT_AUTH); + clientRep.setTlsClientAuthSubjectDn("Ein"); + + response = reg.oidc().create(clientRep); + Assert.assertEquals(OIDCLoginProtocol.TLS_CLIENT_AUTH, response.getTokenEndpointAuthMethod()); + Assert.assertEquals("Ein", response.getTlsClientAuthSubjectDn()); + + // Test Keycloak representation + ClientRepresentation kcClient = getClient(response.getClientId()); + OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient); + Assert.assertEquals(X509ClientAuthenticator.PROVIDER_ID, kcClient.getClientAuthenticatorType()); + Assert.assertEquals("Ein", config.getTlsClientAuthSubjectDn()); + + // update + reg.auth(Auth.token(response)); + response.setTlsClientAuthSubjectDn("(.*?)(?:$)"); + updated = reg.oidc().update(response); + Assert.assertEquals(OIDCLoginProtocol.TLS_CLIENT_AUTH, updated.getTokenEndpointAuthMethod()); + Assert.assertEquals("(.*?)(?:$)", updated.getTlsClientAuthSubjectDn()); + + // Test Keycloak representation + kcClient = getClient(updated.getClientId()); + config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient); + Assert.assertEquals(X509ClientAuthenticator.PROVIDER_ID, kcClient.getClientAuthenticatorType()); + Assert.assertEquals("(.*?)(?:$)", config.getTlsClientAuthSubjectDn()); + } finally { + // revert + reg.auth(Auth.token(updated)); + updated.setTokenEndpointAuthMethod(null); + updated.setTlsClientAuthSubjectDn(null); + reg.oidc().update(updated); + } + } + private ClientRepresentation getKeycloakClient(String clientId) { return ApiUtil.findClientByClientId(adminClient.realms().realm(REALM_NAME), clientId).toRepresentation(); }