KEYCLOAK-18683 Client policy executor for check Backchannel signed request algorithms matching FAPI compliant algorithms

This commit is contained in:
Takashi Norimatsu 2021-07-09 10:28:59 +09:00 committed by Marek Posolda
parent a79d28f115
commit 63f04c1118
9 changed files with 449 additions and 25 deletions

View file

@ -0,0 +1,150 @@
/*
* Copyright 2021 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.protocol.oidc.grants.ciba.clientpolicy.executor;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.jboss.logging.Logger;
import org.keycloak.OAuthErrorException;
import org.keycloak.crypto.Algorithm;
import org.keycloak.models.CibaConfig;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.context.AdminClientRegisterContext;
import org.keycloak.services.clientpolicy.context.AdminClientUpdateContext;
import org.keycloak.services.clientpolicy.context.DynamicClientRegisterContext;
import org.keycloak.services.clientpolicy.context.DynamicClientUpdateContext;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
import org.keycloak.services.clientpolicy.executor.FapiConstant;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureCibaAuthenticationRequestSigningAlgorithmExecutor implements ClientPolicyExecutorProvider<SecureCibaAuthenticationRequestSigningAlgorithmExecutor.Configuration> {
private static final Logger logger = Logger.getLogger(SecureCibaAuthenticationRequestSigningAlgorithmExecutor.class);
private final KeycloakSession session;
private Configuration configuration;
private static final String sigTarget = CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG;
private static final String DEFAULT_ALGORITHM_VALUE = Algorithm.PS256;
public SecureCibaAuthenticationRequestSigningAlgorithmExecutor(KeycloakSession session) {
this.session = session;
}
@Override
public String getProviderId() {
return SecureCibaAuthenticationRequestSigningAlgorithmExecutorFactory.PROVIDER_ID;
}
@Override
public void setupConfiguration(SecureCibaAuthenticationRequestSigningAlgorithmExecutor.Configuration config) {
this.configuration = Optional.ofNullable(config).orElse(createDefaultConfiguration());
if (config.getDefaultAlgorithm() == null || !isSecureAlgorithm(config.getDefaultAlgorithm())) config.setDefaultAlgorithm(DEFAULT_ALGORITHM_VALUE);
}
@Override
public Class<Configuration> getExecutorConfigurationClass() {
return Configuration.class;
}
public static class Configuration extends ClientPolicyExecutorConfigurationRepresentation {
@JsonProperty("default-algorithm")
protected String defaultAlgorithm;
public String getDefaultAlgorithm() {
return defaultAlgorithm;
}
public void setDefaultAlgorithm(String defaultAlgorithm) {
if (isSecureAlgorithm(defaultAlgorithm)) {
this.defaultAlgorithm = defaultAlgorithm;
} else {
logger.tracev("defaultAlgorithm = {0}, fall back to {1}.", defaultAlgorithm, DEFAULT_ALGORITHM_VALUE);
this.defaultAlgorithm = DEFAULT_ALGORITHM_VALUE;
}
}
}
@Override
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
switch (context.getEvent()) {
case REGISTER:
if (context instanceof AdminClientRegisterContext) {
verifyAndEnforceSecureSigningAlgorithm(((AdminClientRegisterContext)context).getProposedClientRepresentation());
} else if (context instanceof DynamicClientRegisterContext) {
verifyAndEnforceSecureSigningAlgorithm(((DynamicClientRegisterContext)context).getProposedClientRepresentation());
} else {
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "not allowed input format.");
}
break;
case UPDATE:
if (context instanceof AdminClientUpdateContext) {
verifyAndEnforceSecureSigningAlgorithm(((AdminClientUpdateContext)context).getProposedClientRepresentation());
} else if (context instanceof DynamicClientUpdateContext) {
verifyAndEnforceSecureSigningAlgorithm(((DynamicClientUpdateContext)context).getProposedClientRepresentation());
} else {
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "not allowed input format.");
}
break;
default:
return;
}
}
private Configuration createDefaultConfiguration() {
Configuration conf = new Configuration();
conf.setDefaultAlgorithm(DEFAULT_ALGORITHM_VALUE);
return conf;
}
private void verifyAndEnforceSecureSigningAlgorithm(ClientRepresentation clientRep) throws ClientPolicyException {
Map<String, String> attributes = Optional.ofNullable(clientRep.getAttributes()).orElse(new HashMap<>());
String sigAlg = attributes.get(sigTarget);
if (sigAlg == null) {
logger.tracev("Signing algorithm not specified explicitly, signature target = {0}. set default algorithm = {1}.", sigTarget, configuration.getDefaultAlgorithm());
attributes.put(sigTarget, configuration.getDefaultAlgorithm());
clientRep.setAttributes(attributes);
return;
}
if (isSecureAlgorithm(sigAlg)) {
logger.tracev("Passed. signature target = {0}, signature algorithm = {1}", sigTarget, sigAlg);
return;
}
logger.tracev("NOT allowed signatureAlgorithm. signature target = {0}, signature algorithm = {1}", sigTarget, sigAlg);
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "not allowed signature algorithm.");
}
private static boolean isSecureAlgorithm(String sigAlg) {
return FapiConstant.ALLOWED_ALGORITHMS.contains(sigAlg);
}
}

View file

@ -0,0 +1,79 @@
/*
* Copyright 2021 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.protocol.oidc.grants.ciba.clientpolicy.executor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import org.keycloak.Config.Scope;
import org.keycloak.crypto.Algorithm;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProviderFactory;
import org.keycloak.services.clientpolicy.executor.FapiConstant;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class SecureCibaAuthenticationRequestSigningAlgorithmExecutorFactory implements ClientPolicyExecutorProviderFactory {
public static final String PROVIDER_ID = "secure-ciba-req-sig-algorithm";
public static final String DEFAULT_ALGORITHM = "default-algorithm";
private static final ProviderConfigProperty DEFAULT_ALGORITHM_PROPERTY = new ProviderConfigProperty(
DEFAULT_ALGORITHM, "Default Algorithm", "Default signature algorithm, which will be set to clients during client registration/update in case that client does not specify any algorithm",
ProviderConfigProperty.LIST_TYPE, Algorithm.PS256, new LinkedList<>(FapiConstant.ALLOWED_ALGORITHMS).toArray(new String[] {}));
@Override
public ClientPolicyExecutorProvider create(KeycloakSession session) {
return new SecureCibaAuthenticationRequestSigningAlgorithmExecutor(session);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getHelpText() {
return "It refuses the client whose signature algorithms are considered not to be secure. This is applied by server for CIBA backchannel signed authentication request. It accepts ES256, ES384, ES512, PS256, PS384 and PS512.";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return new ArrayList<>(Arrays.asList(DEFAULT_ALGORITHM_PROPERTY));
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright 2021 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.services.clientpolicy.executor;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import org.keycloak.crypto.Algorithm;
/**
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public final class FapiConstant {
public static final Set<String> ALLOWED_ALGORITHMS = new LinkedHashSet<>(Arrays.asList(
Algorithm.PS256,
Algorithm.PS384,
Algorithm.PS512,
Algorithm.ES256,
Algorithm.ES384,
Algorithm.ES512
));
}

View file

@ -63,15 +63,6 @@ public class SecureSigningAlgorithmExecutor implements ClientPolicyExecutorProvi
private static final String DEFAULT_ALGORITHM_VALUE = Algorithm.PS256;
static final Set<String> ALLOWED_ALGORITHMS = new LinkedHashSet<>(Arrays.asList(
Algorithm.PS256,
Algorithm.PS384,
Algorithm.PS512,
Algorithm.ES256,
Algorithm.ES384,
Algorithm.ES512
));
public SecureSigningAlgorithmExecutor(KeycloakSession session) {
this.session = session;
}
@ -175,7 +166,7 @@ public class SecureSigningAlgorithmExecutor implements ClientPolicyExecutorProvi
}
private static boolean isSecureAlgorithm(String sigAlg) {
return ALLOWED_ALGORITHMS.contains(sigAlg);
return FapiConstant.ALLOWED_ALGORITHMS.contains(sigAlg);
}
}

View file

@ -39,7 +39,7 @@ public class SecureSigningAlgorithmExecutorFactory implements ClientPolicyExecut
private static final ProviderConfigProperty DEFAULT_ALGORITHM_PROPERTY = new ProviderConfigProperty(
DEFAULT_ALGORITHM, "Default Algorithm", "Default signature algorithm, which will be set to clients during client registration/update in case that client does not specify any algorithm",
ProviderConfigProperty.LIST_TYPE, Algorithm.PS256, new LinkedList<>(SecureSigningAlgorithmExecutor.ALLOWED_ALGORITHMS).toArray(new String[] {}));
ProviderConfigProperty.LIST_TYPE, Algorithm.PS256, new LinkedList<>(FapiConstant.ALLOWED_ALGORITHMS).toArray(new String[] {}));
@Override
public ClientPolicyExecutorProvider create(KeycloakSession session) {

View file

@ -94,8 +94,7 @@ public class SecureSigningAlgorithmForSignedJwtExecutor implements ClientPolicyE
} catch (JWSInputException e) {
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "not allowed input format.");
}
String alg = jws.getHeader().getAlgorithm().name();
verifySecureSigningAlgorithm(alg);
verifySecureSigningAlgorithm(jws.getHeader().getAlgorithm().name());
break;
default:
return;
@ -103,17 +102,11 @@ public class SecureSigningAlgorithmForSignedJwtExecutor implements ClientPolicyE
}
private void verifySecureSigningAlgorithm(String signatureAlgorithm) throws ClientPolicyException {
// Please change also SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.getHelpText() if you are changing any algorithms here.
switch (signatureAlgorithm) {
case Algorithm.PS256:
case Algorithm.PS384:
case Algorithm.PS512:
case Algorithm.ES256:
case Algorithm.ES384:
case Algorithm.ES512:
if (FapiConstant.ALLOWED_ALGORITHMS.contains(signatureAlgorithm)) {
logger.tracev("Passed. signatureAlgorithm = {0}", signatureAlgorithm);
return;
}
logger.tracev("NOT allowed signatureAlgorithm = {0}", signatureAlgorithm);
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "not allowed signature algorithm.");
}

View file

@ -12,3 +12,4 @@ org.keycloak.services.clientpolicy.executor.ConsentRequiredExecutorFactory
org.keycloak.services.clientpolicy.executor.FullScopeDisabledExecutorFactory
org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor.SecureCibaSessionEnforceExecutorFactory
org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor.SecureCibaSignedAuthenticationRequestExecutorFactory
org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor.SecureCibaAuthenticationRequestSigningAlgorithmExecutorFactory

View file

@ -36,11 +36,14 @@ import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.QUARKUS;
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE;
import static org.keycloak.testsuite.util.ClientPoliciesUtil.createAnyClientConditionConfig;
import static org.keycloak.testsuite.util.ClientPoliciesUtil.createClientUpdateContextConditionConfig;
import static org.keycloak.testsuite.util.ClientPoliciesUtil.createSecureCibaAuthenticationRequestSigningAlgorithmExecutorConfig;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -66,9 +69,12 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
import org.keycloak.models.CibaConfig;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.protocol.oidc.grants.ciba.CibaGrantType;
import org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelRequest;
import org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelResponse;
import org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor.SecureCibaAuthenticationRequestSigningAlgorithmExecutor;
import org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor.SecureCibaAuthenticationRequestSigningAlgorithmExecutorFactory;
import org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor.SecureCibaSessionEnforceExecutorFactory;
import org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor.SecureCibaSignedAuthenticationRequestExecutor;
import org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor.SecureCibaSignedAuthenticationRequestExecutorFactory;
@ -84,7 +90,11 @@ import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.representations.oidc.TokenMetadataRepresentation;
import org.keycloak.services.Urls;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
import org.keycloak.services.clientpolicy.ClientPoliciesUtil;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory;
import org.keycloak.services.clientpolicy.condition.ClientUpdaterContextConditionFactory;
import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmExecutorFactory;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
@ -1401,7 +1411,6 @@ public class CIBATest extends AbstractClientPoliciesTest {
testBackchannelAuthenticationFlowNotRegisterSigAlgInAdvanceWithSignedAuthentication("valid-CIBA-CD-Zwei", true, null, Algorithm.PS256, 400, "Client requested algorithm not registered in advance or request signed with different algorithm other than client requested algorithm");
}
@Test
public void testExtendedClientPolicyIntefacesForBackchannelAuthenticationRequest() throws Exception {
String clientId = generateSuffixedName("confidential-app");
@ -1494,6 +1503,161 @@ public class CIBATest extends AbstractClientPoliciesTest {
assertThat(tokenRes.getErrorDescription(), is("Exception thrown intentionally"));
}
@Test
public void testSecureCibaAuthenticationRequestSigningAlgorithmEnforceExecutor() throws Exception {
// register profiles
String json = (new ClientProfilesBuilder()).addProfile(
(new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen")
.addExecutor(SecureCibaAuthenticationRequestSigningAlgorithmExecutorFactory.PROVIDER_ID, null)
.toRepresentation()
).toString();
updateProfiles(json);
// register policies
json = (new ClientPoliciesBuilder()).addPolicy(
(new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "Den Forsta Policyn", Boolean.TRUE)
.addCondition(ClientUpdaterContextConditionFactory.PROVIDER_ID,
createClientUpdateContextConditionConfig(Arrays.asList(
ClientUpdaterContextConditionFactory.BY_AUTHENTICATED_USER,
ClientUpdaterContextConditionFactory.BY_INITIAL_ACCESS_TOKEN,
ClientUpdaterContextConditionFactory.BY_REGISTRATION_ACCESS_TOKEN)))
.addProfile(PROFILE_NAME)
.toRepresentation()
).toString();
updatePolicies(json);
// create by Admin REST API - fail
try {
createClientByAdmin(generateSuffixedName("App-by-Admin"), (ClientRepresentation clientRep) -> {
clientRep.setSecret("secret");
clientRep.setAttributes(new HashMap<>());
clientRep.getAttributes().put(CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG, "none");
});
fail();
} catch (ClientPolicyException e) {
assertEquals(OAuthErrorException.INVALID_REQUEST, e.getMessage());
}
// create by Admin REST API - success
String cAppAdminId = createClientByAdmin(generateSuffixedName("App-by-Admin"), (ClientRepresentation clientRep) -> {
clientRep.setAttributes(new HashMap<>());
clientRep.getAttributes().put(CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG, org.keycloak.crypto.Algorithm.ES256);
});
ClientRepresentation cRep = getClientByAdmin(cAppAdminId);
assertEquals(org.keycloak.crypto.Algorithm.ES256, cRep.getAttributes().get(CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG));
// create by Admin REST API - success, PS256 enforced
String cAppAdmin2Id = createClientByAdmin(generateSuffixedName("App-by-Admin2"), (ClientRepresentation client2Rep) -> {
});
ClientRepresentation cRep2 = getClientByAdmin(cAppAdmin2Id);
assertEquals(org.keycloak.crypto.Algorithm.PS256, cRep2.getAttributes().get(CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG));
// update by Admin REST API - fail
try {
updateClientByAdmin(cAppAdminId, (ClientRepresentation clientRep) -> {
clientRep.setAttributes(new HashMap<>());
clientRep.getAttributes().put(CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG, org.keycloak.crypto.Algorithm.RS512);
});
} catch (ClientPolicyException cpe) {
assertEquals(Errors.INVALID_REQUEST, cpe.getError());
}
cRep = getClientByAdmin(cAppAdminId);
assertEquals(org.keycloak.crypto.Algorithm.ES256, cRep.getAttributes().get(CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG));
// update by Admin REST API - success
updateClientByAdmin(cAppAdminId, (ClientRepresentation clientRep) -> {
clientRep.setAttributes(new HashMap<>());
clientRep.getAttributes().put(CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG, org.keycloak.crypto.Algorithm.PS384);
});
cRep = getClientByAdmin(cAppAdminId);
assertEquals(org.keycloak.crypto.Algorithm.PS384, cRep.getAttributes().get(CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG));
// update profiles, ES256 enforced
json = (new ClientProfilesBuilder()).addProfile(
(new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen")
.addExecutor(SecureCibaAuthenticationRequestSigningAlgorithmExecutorFactory.PROVIDER_ID,
createSecureCibaAuthenticationRequestSigningAlgorithmExecutorConfig(org.keycloak.crypto.Algorithm.ES256))
.toRepresentation()
).toString();
updateProfiles(json);
// update by Admin REST API - success
updateClientByAdmin(cAppAdmin2Id, (ClientRepresentation client2Rep) -> {
client2Rep.getAttributes().remove(CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG);
});
cRep2 = getClientByAdmin(cAppAdmin2Id);
assertEquals(org.keycloak.crypto.Algorithm.ES256, cRep2.getAttributes().get(CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG));
// update profiles, fall back to PS256
json = (new ClientProfilesBuilder()).addProfile(
(new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen")
.addExecutor(SecureCibaAuthenticationRequestSigningAlgorithmExecutorFactory.PROVIDER_ID,
createSecureCibaAuthenticationRequestSigningAlgorithmExecutorConfig(org.keycloak.crypto.Algorithm.RS512))
.toRepresentation()
).toString();
updateProfiles(json);
// create dynamically - fail
try {
createClientByAdmin(generateSuffixedName("App-in-Dynamic"), (ClientRepresentation clientRep) -> {
clientRep.setSecret("secret");
clientRep.setAttributes(new HashMap<>());
clientRep.getAttributes().put(CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG, org.keycloak.crypto.Algorithm.RS384);
});
fail();
} catch (ClientPolicyException e) {
assertEquals(OAuthErrorException.INVALID_REQUEST, e.getMessage());
}
// create dynamically - success
String cAppDynamicClientId = createClientDynamically(generateSuffixedName("App-in-Dynamic"), (OIDCClientRepresentation clientRep) -> {
clientRep.setBackchannelAuthenticationRequestSigningAlg(org.keycloak.crypto.Algorithm.ES256);
});
events.expect(EventType.CLIENT_REGISTER).client(cAppDynamicClientId).user(org.hamcrest.Matchers.isEmptyOrNullString()).assertEvent();
// update dynamically - fail
try {
updateClientDynamically(cAppDynamicClientId, (OIDCClientRepresentation clientRep) -> {
clientRep.setBackchannelAuthenticationRequestSigningAlg(org.keycloak.crypto.Algorithm.RS256);
});
fail();
} catch (ClientRegistrationException e) {
assertEquals(ERR_MSG_CLIENT_REG_FAIL, e.getMessage());
}
assertEquals(org.keycloak.crypto.Algorithm.ES256, getClientDynamically(cAppDynamicClientId).getBackchannelAuthenticationRequestSigningAlg());
// update dynamically - success
updateClientDynamically(cAppDynamicClientId, (OIDCClientRepresentation clientRep) -> {
clientRep.setBackchannelAuthenticationRequestSigningAlg(org.keycloak.crypto.Algorithm.ES384);
});
assertEquals(org.keycloak.crypto.Algorithm.ES384, getClientDynamically(cAppDynamicClientId).getBackchannelAuthenticationRequestSigningAlg());
// create dynamically - success, PS256 enforced
restartAuthenticatedClientRegistrationSetting();
String cAppDynamicClient2Id = createClientDynamically(generateSuffixedName("App-in-Dynamic"), (OIDCClientRepresentation client2Rep) -> {
});
OIDCClientRepresentation cAppDynamicClient2Rep = getClientDynamically(cAppDynamicClient2Id);
assertEquals(org.keycloak.crypto.Algorithm.PS256, cAppDynamicClient2Rep.getBackchannelAuthenticationRequestSigningAlg());
// update profiles, enforce ES256
json = (new ClientProfilesBuilder()).addProfile(
(new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Den Forsta Profilen")
.addExecutor(SecureCibaAuthenticationRequestSigningAlgorithmExecutorFactory.PROVIDER_ID,
createSecureCibaAuthenticationRequestSigningAlgorithmExecutorConfig(org.keycloak.crypto.Algorithm.ES256))
.toRepresentation()
).toString();
updateProfiles(json);
// update dynamically - success, ES256 enforced
updateClientDynamically(cAppDynamicClient2Id, (OIDCClientRepresentation client2Rep) -> {
client2Rep.setBackchannelAuthenticationRequestSigningAlg(null);
});
cAppDynamicClient2Rep = getClientDynamically(cAppDynamicClient2Id);
assertEquals(org.keycloak.crypto.Algorithm.ES256, cAppDynamicClient2Rep.getBackchannelAuthenticationRequestSigningAlg());
}
private void testBackchannelAuthenticationFlowNotRegisterSigAlgInAdvanceWithSignedAuthentication(String clientName, boolean useRequestUri, String requestedSigAlg, String sigAlg, int statusCode, String errorDescription) throws Exception {
String clientId = createClientDynamically(clientName, (OIDCClientRepresentation clientRep) -> {
List<String> grantTypes = Optional.ofNullable(clientRep.getGrantTypes()).orElse(new ArrayList<>());

View file

@ -20,6 +20,8 @@ package org.keycloak.testsuite.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor.SecureCibaAuthenticationRequestSigningAlgorithmExecutor;
import org.keycloak.representations.idm.ClientPoliciesRepresentation;
import org.keycloak.representations.idm.ClientPolicyConditionConfigurationRepresentation;
import org.keycloak.representations.idm.ClientPolicyConditionRepresentation;
@ -191,6 +193,12 @@ public final class ClientPoliciesUtil {
return config;
}
public static SecureCibaAuthenticationRequestSigningAlgorithmExecutor.Configuration createSecureCibaAuthenticationRequestSigningAlgorithmExecutorConfig(String defaultAlgorithm) {
SecureCibaAuthenticationRequestSigningAlgorithmExecutor.Configuration config = new SecureCibaAuthenticationRequestSigningAlgorithmExecutor.Configuration();
config.setDefaultAlgorithm(defaultAlgorithm);
return config;
}
public static class ClientPoliciesBuilder {
private final ClientPoliciesRepresentation policiesRep;