KEYCLOAK-18683 Client policy executor for check Backchannel signed request algorithms matching FAPI compliant algorithms
This commit is contained in:
parent
a79d28f115
commit
63f04c1118
9 changed files with 449 additions and 25 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
));
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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:
|
||||
logger.tracev("Passed. signatureAlgorithm = {0}", signatureAlgorithm);
|
||||
return;
|
||||
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.");
|
||||
}
|
||||
|
|
|
@ -11,4 +11,5 @@ org.keycloak.services.clientpolicy.executor.ConfidentialClientAcceptExecutorFact
|
|||
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.SecureCibaSignedAuthenticationRequestExecutorFactory
|
||||
org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor.SecureCibaAuthenticationRequestSigningAlgorithmExecutorFactory
|
|
@ -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<>());
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue