From 993ba3179c3287c15848f453af463e27a96d82f2 Mon Sep 17 00:00:00 2001 From: Takashi Norimatsu Date: Tue, 28 Jan 2020 22:55:48 +0900 Subject: [PATCH] KEYCLOAK-12615 HS384 and HS512 support for Client Authentication by Client Secret Signed JWT (#6633) --- .../JWTClientSecretCredentialsProvider.java | 86 +++++++++++++++---- .../client/JWTClientSecretAuthenticator.java | 57 ++++++------ .../ClientMacSignatureVerifierContext.java | 46 ++++++++++ ...lientSignatureVerifierProviderFactory.java | 34 ++++++++ ...lientSignatureVerifierProviderFactory.java | 34 ++++++++ ...lientSignatureVerifierProviderFactory.java | 34 ++++++++ ...SecretClientSignatureVerifierProvider.java | 37 ++++++++ ...pto.ClientSignatureVerifierProviderFactory | 3 + .../ClientSecretJwtSecurePortalValidAlg.java | 39 +++++++++ .../servlet/DemoServletsAdapterTest.java | 85 +++++++++++------- .../oauth/ClientAuthSecretSignedJWTTest.java | 78 ++++++++++++----- .../oidc/OIDCWellKnownProviderTest.java | 4 +- .../META-INF/content.xml | 20 +++++ .../WEB-INF/jetty-web.xml | 46 ++++++++++ .../WEB-INF/keycloak.json | 12 +++ .../WEB-INF/web.xml | 57 ++++++++++++ .../resources/adapter-test/demorealm.json | 11 +++ 17 files changed, 582 insertions(+), 101 deletions(-) create mode 100644 services/src/main/java/org/keycloak/crypto/ClientMacSignatureVerifierContext.java create mode 100644 services/src/main/java/org/keycloak/crypto/HS256ClientSignatureVerifierProviderFactory.java create mode 100644 services/src/main/java/org/keycloak/crypto/HS384ClientSignatureVerifierProviderFactory.java create mode 100644 services/src/main/java/org/keycloak/crypto/HS512ClientSignatureVerifierProviderFactory.java create mode 100644 services/src/main/java/org/keycloak/crypto/MacSecretClientSignatureVerifierProvider.java create mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/ClientSecretJwtSecurePortalValidAlg.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/META-INF/content.xml create mode 100644 testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/WEB-INF/jetty-web.xml create mode 100644 testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/WEB-INF/keycloak.json create mode 100644 testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/WEB-INF/web.xml diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientSecretCredentialsProvider.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientSecretCredentialsProvider.java index 3537d80ac1..ec5e3fcdc4 100644 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientSecretCredentialsProvider.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientSecretCredentialsProvider.java @@ -1,3 +1,19 @@ +/* + * Copyright 2018 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.adapters.authentication; import java.nio.charset.StandardCharsets; @@ -11,6 +27,8 @@ import org.keycloak.OAuth2Constants; import org.keycloak.adapters.AdapterUtils; import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.common.util.Time; +import org.keycloak.crypto.Algorithm; +import org.keycloak.crypto.JavaAlgorithm; import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.representations.JsonWebToken; @@ -18,63 +36,95 @@ import org.keycloak.representations.JsonWebToken; * Client authentication based on JWT signed by client secret instead of private key . * See specs for more details. * - * @author Takashi Norimatsu */ public class JWTClientSecretCredentialsProvider implements ClientCredentialsProvider { - - private static final Logger logger = Logger.getLogger(JWTClientSecretCredentialsProvider.class); - + + private static final Logger logger = Logger.getLogger(JWTClientSecretCredentialsProvider.class); + public static final String PROVIDER_ID = "secret-jwt"; - + private SecretKey clientSecret; - + + private String clientSecretJwtAlg = Algorithm.HS256; + @Override public String getId() { return PROVIDER_ID; } - + @Override public void init(KeycloakDeployment deployment, Object config) { if (!(config instanceof Map)) { throw new RuntimeException("Configuration of jwt credentials by client secret is missing or incorrect for client '" + deployment.getResourceName() + "'. Check your adapter configuration"); } - + Map cfg = (Map) config; String clientSecretString = (String) cfg.get("secret"); if (clientSecretString == null) { throw new RuntimeException("Missing parameter secret-jwt in configuration of jwt for client " + deployment.getResourceName()); } - setClientSecret(clientSecretString); + + String clientSecretJwtAlg = (String) cfg.get("algorithm"); + if (clientSecretJwtAlg == null) { + // "algorithm" field is optional. fallback to HS256. + setClientSecret(clientSecretString); + } else if (isValidClientSecretJwtAlg(clientSecretJwtAlg)) { + setClientSecret(clientSecretString, clientSecretJwtAlg); + } else { + // invalid "algorithm" field + throw new RuntimeException("Invalid parameter secret-jwt in configuration of jwt for client " + deployment.getResourceName()); + } } - + + private boolean isValidClientSecretJwtAlg(String clientSecretJwtAlg) { + boolean ret = false; + if (Algorithm.HS256.equals(clientSecretJwtAlg) || Algorithm.HS384.equals(clientSecretJwtAlg) || Algorithm.HS512.equals(clientSecretJwtAlg)) + ret = true; + return ret; + } + @Override public void setClientCredentials(KeycloakDeployment deployment, Map requestHeaders, Map formParams) { String signedToken = createSignedRequestToken(deployment.getResourceName(), deployment.getRealmInfoUrl()); formParams.put(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT); formParams.put(OAuth2Constants.CLIENT_ASSERTION, signedToken); } - + public void setClientSecret(String clientSecretString) { // Get client secret and validate signature // According to OIDC's client authentication spec, // The HMAC (Hash-based Message Authentication Code) is calculated using the octets of the UTF-8 representation of the client_secret as the shared key. // Use "HmacSHA256" consulting java8 api // because it must be implemented in every java platform. - clientSecret = new SecretKeySpec(clientSecretString.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); + setClientSecret(clientSecretString, Algorithm.HS256); } - + + public void setClientSecret(String clientSecretString, String algorithm) { + clientSecret = new SecretKeySpec(clientSecretString.getBytes(StandardCharsets.UTF_8), JavaAlgorithm.getJavaAlgorithm(algorithm)); + clientSecretJwtAlg = algorithm; + } + public String createSignedRequestToken(String clientId, String realmInfoUrl) { + return createSignedRequestToken(clientId, realmInfoUrl, clientSecretJwtAlg); + } + + public String createSignedRequestToken(String clientId, String realmInfoUrl, String algorithm) { JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl); - // JOSE header {"alg":"HS256","typ" : "JWT"} no need "kid" due to using only one registered client secret. - // Use "HmacSHA256" consulting java8 api. - // because it must be implemented in every java platform. - return new JWSBuilder().jsonContent(jwt).hmac256(clientSecret); + String signedRequestToken = null; + if (Algorithm.HS512.equals(algorithm)) { + signedRequestToken = new JWSBuilder().jsonContent(jwt).hmac512(clientSecret); + } else if (Algorithm.HS384.equals(algorithm)) { + signedRequestToken = new JWSBuilder().jsonContent(jwt).hmac384(clientSecret); + } else { + signedRequestToken = new JWSBuilder().jsonContent(jwt).hmac256(clientSecret); + } + return signedRequestToken; } private JsonWebToken createRequestToken(String clientId, String realmInfoUrl) { // According to OIDC's client authentication spec, // JWT claims is the same as one by private_key_jwt - + JsonWebToken reqToken = new JsonWebToken(); reqToken.id(AdapterUtils.generateId()); reqToken.issuer(clientId); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientSecretAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientSecretAuthenticator.java index 5c7eb7b920..4e0a386cbe 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientSecretAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientSecretAuthenticator.java @@ -1,6 +1,21 @@ +/* + * Copyright 2018 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.authentication.authenticators.client; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -10,8 +25,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; @@ -21,7 +34,6 @@ import org.keycloak.authentication.AuthenticationFlowError; import org.keycloak.authentication.ClientAuthenticationFlowContext; import org.keycloak.common.util.Time; import org.keycloak.jose.jws.JWSInput; -import org.keycloak.jose.jws.crypto.HMACProvider; import org.keycloak.models.AuthenticationExecutionModel.Requirement; import org.keycloak.models.SingleUseTokenStoreProvider; import org.keycloak.protocol.oidc.OIDCLoginProtocol; @@ -42,13 +54,12 @@ import org.keycloak.services.Urls; * * TODO: Try to create abstract superclass to be shared with {@link JWTClientAuthenticator}. Most of the code can be reused * - * @author Takashi Norimatsu */ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator { - private static final Logger logger = Logger.getLogger(JWTClientSecretAuthenticator.class); - - public static final String PROVIDER_ID = "client-secret-jwt"; + private static final Logger logger = Logger.getLogger(JWTClientSecretAuthenticator.class); + + public static final String PROVIDER_ID = "client-secret-jwt"; @Override public void authenticateClient(ClientAuthenticationFlowContext context) { @@ -56,7 +67,7 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator { String clientAssertionType = params.getFirst(OAuth2Constants.CLIENT_ASSERTION_TYPE); String clientAssertion = params.getFirst(OAuth2Constants.CLIENT_ASSERTION); - + if (clientAssertionType == null) { Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Parameter client_assertion_type is missing"); context.challenge(challengeResponse); @@ -75,7 +86,7 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator { context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, challengeResponse); return; } - + try { JWSInput jws = new JWSInput(clientAssertion); JsonWebToken token = jws.readJsonContent(JsonWebToken.class); @@ -99,22 +110,17 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator { context.failure(AuthenticationFlowError.CLIENT_DISABLED, null); return; } - + String clientSecretString = client.getSecret(); if (clientSecretString == null) { context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, null); return; } - // Get client secret and validate signature - // According to OIDC's client authentication spec, - // The HMAC (Hash-based Message Authentication Code) is calculated using the octets of the UTF-8 representation of the client_secret as the shared key. - // Use "HmacSHA256" consulting java8 api. - SecretKey clientSecret = new SecretKeySpec(clientSecretString.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); - boolean signatureValid; try { - signatureValid = HMACProvider.verify(jws, clientSecret); + JsonWebToken jwt = context.getSession().tokens().decodeClientJWT(clientAssertion, client, JsonWebToken.class); + signatureValid = jwt != null; } catch (RuntimeException e) { Throwable cause = e.getCause() != null ? e.getCause() : e; throw new RuntimeException("Signature on JWT token by client secret failed validation", cause); @@ -124,7 +130,7 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator { } // According to OIDC's client authentication spec, // JWT contents and verification in client_secret_jwt is the same as in private_key_jwt - + // Allow both "issuer" or "token-endpoint" as audience String issuerUrl = Urls.realmIssuer(context.getUriInfo().getBaseUri(), realm.getName()); String tokenUrl = OIDCLoginProtocolService.tokenUrl(context.getUriInfo().getBaseUriBuilder()).build(realm.getName()).toString(); @@ -163,7 +169,7 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator { context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS, challengeResponse); } } - + @Override public boolean isConfigurable() { return false; @@ -177,14 +183,16 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator { @Override public Map getAdapterConfiguration(ClientModel client) { - // e.g. - // "credentials": { + // e.g. client adapter's keycloak.json + // "credentials": { // "secret-jwt": { - // "secret": "234234-234234-234234" - // } + // "secret": "234234-234234-234234", + // "algorithm": "HS256" + // } // } Map props = new HashMap<>(); props.put("secret", client.getSecret()); + // "algorithm" field is not saved because keycloak does not manage client's property of which algorithm is used for client secret signed JWT. Map config = new HashMap<>(); config.put("secret-jwt", props); @@ -227,6 +235,5 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator { public List getConfigProperties() { return new LinkedList<>(); } - } diff --git a/services/src/main/java/org/keycloak/crypto/ClientMacSignatureVerifierContext.java b/services/src/main/java/org/keycloak/crypto/ClientMacSignatureVerifierContext.java new file mode 100644 index 0000000000..b14c82b496 --- /dev/null +++ b/services/src/main/java/org/keycloak/crypto/ClientMacSignatureVerifierContext.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020 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.crypto; + +import java.nio.charset.StandardCharsets; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.keycloak.common.VerificationException; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; + +public class ClientMacSignatureVerifierContext extends MacSignatureVerifierContext { + + public ClientMacSignatureVerifierContext(KeycloakSession session, ClientModel client, String algorithm) throws VerificationException { + super(getKey(session, client, algorithm)); + } + + private static KeyWrapper getKey(KeycloakSession session, ClientModel client, String algorithm) throws VerificationException { + if (algorithm == null) algorithm = Algorithm.HS256; + String clientSecretString = client.getSecret(); + SecretKey clientSecret = new SecretKeySpec(clientSecretString.getBytes(StandardCharsets.UTF_8), JavaAlgorithm.getJavaAlgorithm(algorithm)); + KeyWrapper key = new KeyWrapper(); + key.setSecretKey(clientSecret); + key.setUse(KeyUse.SIG); + key.setType(KeyType.OCT); + key.setAlgorithm(algorithm); + return key; + } + +} diff --git a/services/src/main/java/org/keycloak/crypto/HS256ClientSignatureVerifierProviderFactory.java b/services/src/main/java/org/keycloak/crypto/HS256ClientSignatureVerifierProviderFactory.java new file mode 100644 index 0000000000..79af98e516 --- /dev/null +++ b/services/src/main/java/org/keycloak/crypto/HS256ClientSignatureVerifierProviderFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 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.crypto; + +import org.keycloak.models.KeycloakSession; + +public class HS256ClientSignatureVerifierProviderFactory implements ClientSignatureVerifierProviderFactory { + + public static final String ID = Algorithm.HS256; + + @Override + public String getId() { + return ID; + } + + @Override + public ClientSignatureVerifierProvider create(KeycloakSession session) { + return new MacSecretClientSignatureVerifierProvider(session, Algorithm.HS256); + } +} diff --git a/services/src/main/java/org/keycloak/crypto/HS384ClientSignatureVerifierProviderFactory.java b/services/src/main/java/org/keycloak/crypto/HS384ClientSignatureVerifierProviderFactory.java new file mode 100644 index 0000000000..3e544da220 --- /dev/null +++ b/services/src/main/java/org/keycloak/crypto/HS384ClientSignatureVerifierProviderFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 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.crypto; + +import org.keycloak.models.KeycloakSession; + +public class HS384ClientSignatureVerifierProviderFactory implements ClientSignatureVerifierProviderFactory { + + public static final String ID = Algorithm.HS384; + + @Override + public String getId() { + return ID; + } + + @Override + public ClientSignatureVerifierProvider create(KeycloakSession session) { + return new MacSecretClientSignatureVerifierProvider(session, Algorithm.HS384); + } +} diff --git a/services/src/main/java/org/keycloak/crypto/HS512ClientSignatureVerifierProviderFactory.java b/services/src/main/java/org/keycloak/crypto/HS512ClientSignatureVerifierProviderFactory.java new file mode 100644 index 0000000000..4b989763d0 --- /dev/null +++ b/services/src/main/java/org/keycloak/crypto/HS512ClientSignatureVerifierProviderFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 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.crypto; + +import org.keycloak.models.KeycloakSession; + +public class HS512ClientSignatureVerifierProviderFactory implements ClientSignatureVerifierProviderFactory { + + public static final String ID = Algorithm.HS512; + + @Override + public String getId() { + return ID; + } + + @Override + public ClientSignatureVerifierProvider create(KeycloakSession session) { + return new MacSecretClientSignatureVerifierProvider(session, Algorithm.HS512); + } +} diff --git a/services/src/main/java/org/keycloak/crypto/MacSecretClientSignatureVerifierProvider.java b/services/src/main/java/org/keycloak/crypto/MacSecretClientSignatureVerifierProvider.java new file mode 100644 index 0000000000..430dbd2026 --- /dev/null +++ b/services/src/main/java/org/keycloak/crypto/MacSecretClientSignatureVerifierProvider.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020 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.crypto; + +import org.keycloak.common.VerificationException; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; + +public class MacSecretClientSignatureVerifierProvider implements ClientSignatureVerifierProvider { + private final KeycloakSession session; + private final String algorithm; + + public MacSecretClientSignatureVerifierProvider(KeycloakSession session, String algorithm) { + this.session = session; + this.algorithm = algorithm; + } + + @Override + public SignatureVerifierContext verifier(ClientModel client, JWSInput input) throws VerificationException { + return new ClientMacSignatureVerifierContext(session, client, algorithm); + } +} diff --git a/services/src/main/resources/META-INF/services/org.keycloak.crypto.ClientSignatureVerifierProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.crypto.ClientSignatureVerifierProviderFactory index e68801994a..a169353ccf 100644 --- a/services/src/main/resources/META-INF/services/org.keycloak.crypto.ClientSignatureVerifierProviderFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.crypto.ClientSignatureVerifierProviderFactory @@ -7,3 +7,6 @@ org.keycloak.crypto.ES512ClientSignatureVerifierProviderFactory org.keycloak.crypto.PS256ClientSignatureVerifierProviderFactory org.keycloak.crypto.PS384ClientSignatureVerifierProviderFactory org.keycloak.crypto.PS512ClientSignatureVerifierProviderFactory +org.keycloak.crypto.HS256ClientSignatureVerifierProviderFactory +org.keycloak.crypto.HS384ClientSignatureVerifierProviderFactory +org.keycloak.crypto.HS512ClientSignatureVerifierProviderFactory diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/ClientSecretJwtSecurePortalValidAlg.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/ClientSecretJwtSecurePortalValidAlg.java new file mode 100644 index 0000000000..581861797a --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/ClientSecretJwtSecurePortalValidAlg.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 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.adapter.page; + +import org.jboss.arquillian.container.test.api.OperateOnDeployment; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl; + +import java.net.URL; + +public class ClientSecretJwtSecurePortalValidAlg extends AbstractPageWithInjectedUrl { + + public static final String DEPLOYMENT_NAME = "client-secret-jwt-secure-portal-valid-alg"; + + @ArquillianResource + @OperateOnDeployment(DEPLOYMENT_NAME) + private URL url; + + @Override + public URL getInjectedUrl() { + return url; + } + +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java index 05f0d16c59..bba4e7670f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/DemoServletsAdapterTest.java @@ -39,7 +39,6 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessTokenResponse; -import org.keycloak.representations.VersionRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; @@ -49,6 +48,7 @@ import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest; import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter; import org.keycloak.testsuite.adapter.page.BasicAuth; import org.keycloak.testsuite.adapter.page.ClientSecretJwtSecurePortal; +import org.keycloak.testsuite.adapter.page.ClientSecretJwtSecurePortalValidAlg; import org.keycloak.testsuite.adapter.page.CustomerCookiePortal; import org.keycloak.testsuite.adapter.page.CustomerCookiePortalRoot; import org.keycloak.testsuite.adapter.page.CustomerDb; @@ -72,6 +72,7 @@ import org.keycloak.testsuite.auth.page.login.OAuthGrant; import org.keycloak.testsuite.auth.page.login.OIDCLogin; import org.keycloak.testsuite.console.page.events.Config; import org.keycloak.testsuite.console.page.events.LoginEvents; +import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl; import org.keycloak.testsuite.util.FollowRedirectsEngine; import org.keycloak.testsuite.util.JavascriptBrowser; import org.keycloak.testsuite.util.Matchers; @@ -112,7 +113,6 @@ import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -183,6 +183,8 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest { @Page private ClientSecretJwtSecurePortal clientSecretJwtSecurePortal; @Page + private ClientSecretJwtSecurePortalValidAlg clientSecretJwtSecurePortalValidAlg; + @Page private CustomerCookiePortal customerCookiePortal; @Page private CustomerCookiePortalRoot customerCookiePortalRoot; @@ -270,6 +272,11 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest { return servletDeployment(ClientSecretJwtSecurePortal.DEPLOYMENT_NAME, CallAuthenticatedServlet.class); } + @Deployment(name = ClientSecretJwtSecurePortalValidAlg.DEPLOYMENT_NAME) + protected static WebArchive clientSecretSecurePortalValidAlg() { + return servletDeployment(ClientSecretJwtSecurePortalValidAlg.DEPLOYMENT_NAME, CallAuthenticatedServlet.class); + } + @Deployment(name = CustomerCookiePortalRoot.DEPLOYMENT_NAME) protected static WebArchive customerCookiePortalRoot() { return servletDeployment(CustomerCookiePortalRoot.DEPLOYMENT_NAME, AdapterActionsFilter.class, CustomerServlet.class, ErrorServlet.class, ServletTestUtils.class); @@ -1232,48 +1239,60 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest { @Test public void testClientAuthenticatedInClientSecretJwt() { // test login to customer-portal which does a bearer request to customer-db - // JWS Client Assertion in client_secret_jwt - // http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication + // JWS Client Assertion in client_secret_jwt + // http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication String targetClientId = "client-secret-jwt-secure-portal"; - - expectResultOfClientAuthenticatedInClientSecretJwt(targetClientId); + + expectResultOfClientAuthenticatedInClientSecretJwt(targetClientId, clientSecretJwtSecurePortal); // test logout String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder()) .queryParam(OAuth2Constants.REDIRECT_URI, clientSecretJwtSecurePortal.toString()).build("demo").toString(); driver.navigate().to(logoutUri); } - + @Test public void testClientNotAuthenticatedInClientSecretJwtBySharedSecretOutOfSync() { - // JWS Client Assertion in client_secret_jwt - // http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication - String targetClientId = "client-secret-jwt-secure-portal"; - String expectedErrorString = "invalid_client_credentials"; - + // JWS Client Assertion in client_secret_jwt + // http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication + String targetClientId = "client-secret-jwt-secure-portal"; + String expectedErrorString = "invalid_client_credentials"; + ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), targetClientId); ClientRepresentation client = clientResource.toRepresentation(); client.setSecret("passwordChanged"); clientResource.update(client); - + expectResultOfClientNotAuthenticatedInClientSecretJwt(targetClientId, expectedErrorString); } - + @Test public void testClientNotAuthenticatedInClientSecretJwtByAuthnMethodOutOfSync() { - // JWS Client Assertion in client_secret_jwt - // http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication - String targetClientId = "client-secret-jwt-secure-portal"; - String expectedErrorString = "invalid_client_credentials"; - + // JWS Client Assertion in client_secret_jwt + // http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication + String targetClientId = "client-secret-jwt-secure-portal"; + String expectedErrorString = "invalid_client_credentials"; + ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), targetClientId); ClientRepresentation client = clientResource.toRepresentation(); client.setClientAuthenticatorType("client-secret"); clientResource.update(client); - + expectResultOfClientNotAuthenticatedInClientSecretJwt(targetClientId, expectedErrorString); } + @Test + public void testClientAuthenticatedInClientSecretJwtValidAlg() { + String targetClientId = "client-secret-jwt-secure-portal-valid-alg"; + + expectResultOfClientAuthenticatedInClientSecretJwt(targetClientId, clientSecretJwtSecurePortalValidAlg); + + // test logout + String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder()) + .queryParam(OAuth2Constants.REDIRECT_URI, clientSecretJwtSecurePortalValidAlg.toString()).build("demo").toString(); + driver.navigate().to(logoutUri); + } + @Test public void testTokenInCookieSSORoot() { // Login @@ -1321,20 +1340,20 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest { customerCookiePortalRoot.navigateTo(); assertCurrentUrlStartsWithLoginUrlOf(testRealmPage); } - - private void expectResultOfClientAuthenticatedInClientSecretJwt(String targetClientId) { + + private void expectResultOfClientAuthenticatedInClientSecretJwt(String targetClientId, AbstractPageWithInjectedUrl portal) { RealmRepresentation realm = testRealmResource().toRepresentation(); realm.setEventsEnabled(true); realm.setEnabledEventTypes(Arrays.asList("LOGIN", "CODE_TO_TOKEN")); realm.setEventsListeners(Arrays.asList("jboss-logging", "event-queue")); testRealmResource().update(realm); - - clientSecretJwtSecurePortal.navigateTo(); + + portal.navigateTo(); assertCurrentUrlStartsWithLoginUrlOf(testRealmPage); testRealmLoginPage.form().login("bburke@redhat.com", "password"); - + String userId = ApiUtil.findUserByUsername(testRealmResource(), "bburke@redhat.com").getId(); - + assertEvents.expectLogin() .realm(realm.getId()) .client(targetClientId) @@ -1342,11 +1361,11 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest { .detail(Details.USERNAME, "bburke@redhat.com") .detail(Details.CONSENT, Details.CONSENT_VALUE_NO_CONSENT_REQUIRED) .detail(Details.REDIRECT_URI, - org.hamcrest.Matchers.anyOf(org.hamcrest.Matchers.equalTo(clientSecretJwtSecurePortal.getInjectedUrl().toString()), - org.hamcrest.Matchers.equalTo(clientSecretJwtSecurePortal.getInjectedUrl().toString() + "/"))) + org.hamcrest.Matchers.anyOf(org.hamcrest.Matchers.equalTo(portal.getInjectedUrl().toString()), + org.hamcrest.Matchers.equalTo(portal.getInjectedUrl().toString() + "/"))) .removeDetail(Details.CODE_ID) .assertEvent(); - + assertEvents.expectCodeToToken(null, null) .realm(realm.getId()) .client(targetClientId) @@ -1355,18 +1374,18 @@ public class DemoServletsAdapterTest extends AbstractServletsAdapterTest { .clearDetails() .assertEvent(); } - + private void expectResultOfClientNotAuthenticatedInClientSecretJwt(String targetClientId, String expectedErrorString) { RealmRepresentation realm = testRealmResource().toRepresentation(); realm.setEventsEnabled(true); realm.setEnabledEventTypes(Arrays.asList("LOGIN", "CODE_TO_TOKEN_ERROR")); realm.setEventsListeners(Arrays.asList("jboss-logging", "event-queue")); testRealmResource().update(realm); - - clientSecretJwtSecurePortal.navigateTo(); + + clientSecretJwtSecurePortal.navigateTo(); assertCurrentUrlStartsWithLoginUrlOf(testRealmPage); testRealmLoginPage.form().login("bburke@redhat.com", "password"); - + String userId = ApiUtil.findUserByUsername(testRealmResource(), "bburke@redhat.com").getId(); assertEvents.expectLogin() diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSecretSignedJWTTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSecretSignedJWTTest.java index 2a4f83d8fe..ea522bb661 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSecretSignedJWTTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSecretSignedJWTTest.java @@ -1,3 +1,19 @@ +/* + * Copyright 2018 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.oauth; import static org.junit.Assert.assertEquals; @@ -21,6 +37,7 @@ import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenti import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.UriUtils; import org.keycloak.constants.ServiceUrlConstants; +import org.keycloak.crypto.Algorithm; import org.keycloak.events.Details; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; @@ -31,31 +48,43 @@ import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer; import org.keycloak.testsuite.util.OAuthClient; -/** - * @author Takashi Norimatsu - */ @AuthServerContainerExclude(AuthServer.REMOTE) public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest { - private static final Logger logger = Logger.getLogger(ClientAuthSecretSignedJWTTest.class); - + + private static final Logger logger = Logger.getLogger(ClientAuthSecretSignedJWTTest.class); + @Rule public AssertEvents events = new AssertEvents(this); - + @Override public void beforeAbstractKeycloakTest() throws Exception { super.beforeAbstractKeycloakTest(); } - + @Override public void addTestRealms(List testRealms) { RealmRepresentation realm = AbstractAdminTest.loadJson(getClass().getResourceAsStream("/client-auth-test/testrealm-jwt-client-secret.json"), RealmRepresentation.class); testRealms.add(realm); } - + // TEST SUCCESS - + @Test - public void testCodeToTokenRequestSuccess() throws Exception { + public void testCodeToTokenRequestSuccess() throws Exception { + testCodeToTokenRequestSuccess(Algorithm.HS256); + } + + @Test + public void testCodeToTokenRequestSuccessHS384() throws Exception { + testCodeToTokenRequestSuccess(Algorithm.HS384); + } + + @Test + public void testCodeToTokenRequestSuccessHS512() throws Exception { + testCodeToTokenRequestSuccess(Algorithm.HS512); + } + + private void testCodeToTokenRequestSuccess(String algorithm) throws Exception { oauth.clientId("test-app"); oauth.doLogin("test-user@localhost", "password"); EventRepresentation loginEvent = events.expectLogin() @@ -63,8 +92,8 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest { .assertEvent(); String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); - OAuthClient.AccessTokenResponse response = doAccessTokenRequest(code, getClientSignedJWT("password", 20)); - + OAuthClient.AccessTokenResponse response = doAccessTokenRequest(code, getClientSignedJWT("password", 20, algorithm)); + assertEquals(200, response.getStatusCode()); oauth.verifyToken(response.getAccessToken()); oauth.parseRefreshToken(response.getRefreshToken()); @@ -73,9 +102,9 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest { .detail(Details.CLIENT_AUTH_METHOD, JWTClientSecretAuthenticator.PROVIDER_ID) .assertEvent(); } - + // TEST ERRORS - + @Test public void testAssertionInvalidSignature() throws Exception { oauth.clientId("test-app"); @@ -92,7 +121,6 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest { assertEquals("unauthorized_client", response.getError()); } - @Test public void testAssertionReuse() throws Exception { oauth.clientId("test-app"); @@ -132,18 +160,21 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest { assertEquals("unauthorized_client", response.getError()); } - private String getClientSignedJWT(String secret, int timeout) { - JWTClientSecretCredentialsProvider jwtProvider = new JWTClientSecretCredentialsProvider(); - jwtProvider.setClientSecret(secret); - return jwtProvider.createSignedRequestToken(oauth.getClientId(), getRealmInfoUrl()); + return getClientSignedJWT(secret, timeout, Algorithm.HS256); } - + + private String getClientSignedJWT(String secret, int timeout, String algorithm) { + JWTClientSecretCredentialsProvider jwtProvider = new JWTClientSecretCredentialsProvider(); + jwtProvider.setClientSecret(secret, algorithm); + return jwtProvider.createSignedRequestToken(oauth.getClientId(), getRealmInfoUrl(), algorithm); + } + private String getRealmInfoUrl() { String authServerBaseUrl = UriUtils.getOrigin(oauth.getRedirectUri()) + "/auth"; return KeycloakUriBuilder.fromUri(authServerBaseUrl).path(ServiceUrlConstants.REALM_INFO_PATH).build("test").toString(); } - + private OAuthClient.AccessTokenResponse doAccessTokenRequest(String code, String signedJwt) throws Exception { List parameters = new LinkedList<>(); parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.AUTHORIZATION_CODE)); @@ -151,11 +182,11 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest { parameters.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, oauth.getRedirectUri())); parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT)); parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt)); - + CloseableHttpResponse response = sendRequest(oauth.getAccessTokenUrl(), parameters); return new OAuthClient.AccessTokenResponse(response); } - + private CloseableHttpResponse sendRequest(String requestUrl, List parameters) throws Exception { CloseableHttpClient client = new DefaultHttpClient(); try { @@ -167,4 +198,5 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest { oauth.closeClient(client); } } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java index 82a2600d4a..d92148c2db 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java @@ -129,7 +129,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest { // Signature algorithms Assert.assertNames(oidcConfig.getIdTokenSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512); Assert.assertNames(oidcConfig.getUserInfoSigningAlgValuesSupported(), "none", Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512); - Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), "none", Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512); + Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), "none", Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512); // Encryption algorithms Assert.assertNames(oidcConfig.getIdTokenEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP); @@ -137,7 +137,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest { // Client authentication Assert.assertNames(oidcConfig.getTokenEndpointAuthMethodsSupported(), "client_secret_basic", "client_secret_post", "private_key_jwt", "client_secret_jwt", "tls_client_auth"); - Assert.assertNames(oidcConfig.getTokenEndpointAuthSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512); + Assert.assertNames(oidcConfig.getTokenEndpointAuthSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512); // Claims assertContains(oidcConfig.getClaimsSupported(), IDToken.NAME, IDToken.EMAIL, IDToken.PREFERRED_USERNAME, IDToken.FAMILY_NAME, IDToken.ACR); diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/META-INF/content.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/META-INF/content.xml new file mode 100644 index 0000000000..e90c415660 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/META-INF/content.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/WEB-INF/jetty-web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/WEB-INF/jetty-web.xml new file mode 100644 index 0000000000..78b64a7c4d --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/WEB-INF/jetty-web.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/WEB-INF/keycloak.json new file mode 100644 index 0000000000..30d7614de3 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/WEB-INF/keycloak.json @@ -0,0 +1,12 @@ +{ + "realm": "demo", + "auth-server-url": "http://localhost:8180/auth", + "ssl-required": "external", + "resource": "client-secret-jwt-secure-portal-valid-alg", + "credentials": { + "secret-jwt": { + "secret": "234234-234234-234234", + "algorithm": "HS512" + } + } +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/WEB-INF/web.xml new file mode 100644 index 0000000000..a4f2a85b8c --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/client-secret-jwt-secure-portal-valid-alg/WEB-INF/web.xml @@ -0,0 +1,57 @@ + + + + + + client-secret-jwt-secure-portal-valid-alg + + + Servlet + org.keycloak.testsuite.adapter.servlet.CallAuthenticatedServlet + + + + Servlet + /* + + + + + Permit all + /* + + + * + + + + + KEYCLOAK + demo + + + + admin + + + user + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json index f9094e63d1..c5b335894f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json @@ -351,6 +351,17 @@ "/client-secret-jwt-secure-portal/*" ], "secret": "234234-234234-234234" + }, + { + "clientId": "client-secret-jwt-secure-portal-valid-alg", + "enabled": true, + "adminUrl": "/client-secret-jwt-secure-portal-valid-alg", + "baseUrl": "/client-secret-jwt-secure-portal-valid-alg", + "clientAuthenticatorType": "client-secret-jwt", + "redirectUris": [ + "/client-secret-jwt-secure-portal-valid-alg/*" + ], + "secret": "234234-234234-234234" } ] }