From 30cd40e097bdb98516c5f082c9abfdad2650df3a Mon Sep 17 00:00:00 2001 From: Justin Tay <49700559+justin-tay@users.noreply.github.com> Date: Fri, 29 Mar 2024 08:00:49 +0800 Subject: [PATCH] Use realm default signature algorithm for id_token_signed_response_alg Closes #9695 Signed-off-by: Justin Tay <49700559+justin-tay@users.noreply.github.com> --- .../oidc/DescriptionConverter.java | 11 +- ...lientRegistrationDefaultSignatureTest.java | 105 ++++++++++++++++++ .../client/OIDCClientRegistrationTest.java | 34 ++++++ 3 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationDefaultSignatureTest.java 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 90a259ccec..951f561d00 100755 --- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java @@ -21,6 +21,7 @@ import org.keycloak.OAuth2Constants; import org.keycloak.authentication.ClientAuthenticator; import org.keycloak.authentication.ClientAuthenticatorFactory; import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; +import org.keycloak.crypto.Algorithm; import org.keycloak.jose.jwk.JSONWebKeySet; import org.keycloak.jose.jwk.JWK; import org.keycloak.jose.jwk.JWKParser; @@ -159,9 +160,7 @@ public class DescriptionConverter { configWrapper.setAllowRegexPatternComparison(false); } - if (clientOIDC.getIdTokenSignedResponseAlg() != null) { - configWrapper.setIdTokenSignedResponseAlg(clientOIDC.getIdTokenSignedResponseAlg()); - } + configWrapper.setIdTokenSignedResponseAlg(clientOIDC.getIdTokenSignedResponseAlg()); if (clientOIDC.getIdTokenEncryptedResponseAlg() != null) { configWrapper.setIdTokenEncryptedResponseAlg(clientOIDC.getIdTokenEncryptedResponseAlg()); @@ -379,6 +378,10 @@ public class DescriptionConverter { throw new ClientRegistrationException("Illegal jwks format"); } } + String defaultSignatureAlgorithm = session.getContext().getRealm().getDefaultSignatureAlgorithm(); + if (Algorithm.RS256.equals(defaultSignatureAlgorithm) || StringUtil.isBlank(defaultSignatureAlgorithm)) { + defaultSignatureAlgorithm = null; + } // KEYCLOAK-6771 Certificate Bound Token // https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.5 if (config.isUseMtlsHokToken()) { @@ -391,6 +394,8 @@ public class DescriptionConverter { } if (config.getIdTokenSignedResponseAlg() != null) { response.setIdTokenSignedResponseAlg(config.getIdTokenSignedResponseAlg()); + } else if (defaultSignatureAlgorithm != null){ + response.setIdTokenSignedResponseAlg(defaultSignatureAlgorithm); } if (config.getIdTokenEncryptedResponseAlg() != null) { response.setIdTokenEncryptedResponseAlg(config.getIdTokenEncryptedResponseAlg()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationDefaultSignatureTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationDefaultSignatureTest.java new file mode 100644 index 0000000000..4864a7da1b --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationDefaultSignatureTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 2024 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.client; + + +import java.util.Collections; + +import org.junit.Before; +import org.junit.Test; +import org.keycloak.client.registration.Auth; +import org.keycloak.crypto.Algorithm; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; +import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation; +import org.keycloak.representations.idm.ClientInitialAccessPresentation; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.oidc.OIDCClientRepresentation; +import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.util.TokenSignatureUtil; + +public class OIDCClientRegistrationDefaultSignatureTest extends AbstractClientRegistrationTest { + @Before + public void before() throws Exception { + super.before(); + + ClientInitialAccessPresentation token = adminClient.realm(REALM_NAME).clientInitialAccess().create(new ClientInitialAccessCreatePresentation(0, 10)); + reg.auth(Auth.token(token)); + } + + private OIDCClientRepresentation createRep() { + OIDCClientRepresentation client = new OIDCClientRepresentation(); + client.setClientName("RegistrationAccessTokenTest"); + client.setClientUri("http://root"); + client.setRedirectUris(Collections.singletonList("http://redirect")); + client.setFrontChannelLogoutUri("http://frontchannel"); + client.setFrontchannelLogoutSessionRequired(true); + return client; + } + + @Test + public void testIdTokenSignedResponse() throws Exception { + OIDCClientRepresentation response = null; + OIDCClientRepresentation updated = null; + try { + TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.ES512); + + // create (no specification) + OIDCClientRepresentation clientRep = createRep(); + + response = reg.oidc().create(clientRep); + Assert.assertEquals(Algorithm.ES512, response.getIdTokenSignedResponseAlg()); + + // Test Keycloak representation + ClientRepresentation kcClient = getClient(response.getClientId()); + OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient); + // Client representation of id.token.signed.response.alg is null as from realm + Assert.assertNull(config.getIdTokenSignedResponseAlg()); + + // update + reg.auth(Auth.token(response)); + response.setIdTokenSignedResponseAlg(Algorithm.ES256); + updated = reg.oidc().update(response); + Assert.assertEquals(Algorithm.ES256, updated.getIdTokenSignedResponseAlg()); + + // Test Keycloak representation + kcClient = getClient(updated.getClientId()); + config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient); + Assert.assertEquals(Algorithm.ES256, config.getIdTokenSignedResponseAlg()); + + // update after changing default realm token signature + TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.ES384); + reg.auth(Auth.token(updated)); + updated.setIdTokenSignedResponseAlg(null); + response = reg.oidc().update(updated); + Assert.assertEquals(Algorithm.ES384, response.getIdTokenSignedResponseAlg()); + } finally { + // revert + TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256); + reg.auth(Auth.token(response)); + response.setIdTokenSignedResponseAlg(null); + updated = reg.oidc().update(response); + Assert.assertNull(updated.getIdTokenSignedResponseAlg()); + + // Test Keycloak representation + ClientRepresentation kcClient = getClient(updated.getClientId()); + OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient); + // Client representation of id.token.signed.response.alg is null as from realm + Assert.assertNull(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 ae8b7144e2..8e3ba20f33 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 @@ -477,6 +477,40 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { } } + @Test + public void testIdTokenSignedResponse() throws Exception { + OIDCClientRepresentation response = null; + OIDCClientRepresentation updated = null; + try { + // create (no specification) + OIDCClientRepresentation clientRep = createRep(); + + response = reg.oidc().create(clientRep); + Assert.assertNull(response.getIdTokenSignedResponseAlg()); + + // Test Keycloak representation + ClientRepresentation kcClient = getClient(response.getClientId()); + OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient); + Assert.assertNull(config.getIdTokenSignedResponseAlg()); + + // update + reg.auth(Auth.token(response)); + response.setIdTokenSignedResponseAlg(Algorithm.ES256); + updated = reg.oidc().update(response); + Assert.assertEquals(Algorithm.ES256, updated.getIdTokenSignedResponseAlg()); + + // Test Keycloak representation + kcClient = getClient(updated.getClientId()); + config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient); + Assert.assertEquals(Algorithm.ES256, config.getIdTokenSignedResponseAlg()); + } finally { + // revert + reg.auth(Auth.token(updated)); + updated.setIdTokenSignedResponseAlg(null); + reg.oidc().update(updated); + } + } + @Test public void testTokenEndpointSigningAlg() throws Exception { OIDCClientRepresentation response = null;