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>
This commit is contained in:
Justin Tay 2024-03-29 08:00:49 +08:00 committed by Marek Posolda
parent c1a471755d
commit 30cd40e097
3 changed files with 147 additions and 3 deletions

View file

@ -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());

View file

@ -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());
}
}
}

View file

@ -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;